-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 오늘의 한마디 작성 api 개발 #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ab79baf
f0d656a
64416f8
77aa623
68401f1
1319c76
0ac3659
5b0c8ba
d7e603b
4fdc327
e8fbc02
a096083
36589ac
d02b355
37e98af
298e3cc
cdeff6f
243fd73
c86d2f9
5aded9c
85b9303
5c8ae44
87ac1af
b21077d
155259b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package konkuk.thip.room.adapter.in.web.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import konkuk.thip.room.application.port.in.dto.AttendanceCheckCreateCommand; | ||
|
|
||
| public record AttendanceCheckCreateRequest( | ||
| @Schema(description = "유저가 작성한 오늘의 한마디 내용") | ||
| @NotBlank(message = "오늘의 한마디 내용은 필수입니다.") | ||
| String content | ||
| ) { | ||
| public AttendanceCheckCreateCommand toCommand(Long creatorId, Long roomId) { | ||
| return new AttendanceCheckCreateCommand(creatorId, roomId, content); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package konkuk.thip.room.adapter.in.web.response; | ||
|
|
||
| import konkuk.thip.room.application.port.in.dto.AttendanceCheckCreateResult; | ||
|
|
||
| public record AttendanceCheckCreateResponse( | ||
| Long attendanceCheckId, | ||
| Long roomId, | ||
| boolean isFirstWrite | ||
| ) { | ||
| public static AttendanceCheckCreateResponse of(AttendanceCheckCreateResult result) { | ||
| boolean isFirstWrite = false; | ||
| if (result.todayWriteCountOfUser() == 1) isFirstWrite = true; | ||
|
|
||
| return new AttendanceCheckCreateResponse( | ||
| result.attendanceCheckId(), | ||
| result.roomId(), | ||
| isFirstWrite | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package konkuk.thip.room.adapter.out.persistence; | ||
|
|
||
| import konkuk.thip.room.adapter.out.mapper.AttendanceCheckMapper; | ||
| import konkuk.thip.room.adapter.out.persistence.repository.attendancecheck.AttendanceCheckJpaRepository; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏻 👍🏻 |
||
| import konkuk.thip.room.application.port.out.AttendanceCheckCommandPort; | ||
| import konkuk.thip.room.domain.AttendanceCheck; | ||
| import konkuk.thip.common.exception.EntityNotFoundException; | ||
| import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; | ||
| import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; | ||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||
| import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import static konkuk.thip.common.exception.code.ErrorCode.ROOM_NOT_FOUND; | ||
| import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class AttendanceCheckCommandPersistenceAdapter implements AttendanceCheckCommandPort { | ||
|
|
||
| private final AttendanceCheckJpaRepository attendanceCheckJpaRepository; | ||
| private final RoomJpaRepository roomJpaRepository; | ||
| private final UserJpaRepository userJpaRepository; | ||
| private final AttendanceCheckMapper attendanceCheckMapper; | ||
|
|
||
| @Override | ||
| public Long save(AttendanceCheck attendanceCheck) { | ||
| RoomJpaEntity roomJpaEntity = roomJpaRepository.findById(attendanceCheck.getRoomId()).orElseThrow( | ||
| () -> new EntityNotFoundException(ROOM_NOT_FOUND) | ||
| ); | ||
|
|
||
| UserJpaEntity userJpaEntity = userJpaRepository.findById(attendanceCheck.getCreatorId()).orElseThrow( | ||
| () -> new EntityNotFoundException(USER_NOT_FOUND) | ||
| ); | ||
|
|
||
| return attendanceCheckJpaRepository.save( | ||
| attendanceCheckMapper.toJpaEntity(attendanceCheck, roomJpaEntity, userJpaEntity) | ||
| ).getAttendanceCheckId(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package konkuk.thip.room.adapter.out.persistence; | ||
|
|
||
| import konkuk.thip.room.adapter.out.mapper.AttendanceCheckMapper; | ||
| import konkuk.thip.room.adapter.out.persistence.repository.attendancecheck.AttendanceCheckJpaRepository; | ||
| import konkuk.thip.room.application.port.out.AttendanceCheckQueryPort; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import static konkuk.thip.common.entity.StatusType.ACTIVE; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class AttendanceCheckQueryPersistenceAdapter implements AttendanceCheckQueryPort { | ||
|
|
||
| private final AttendanceCheckJpaRepository attendanceCheckJpaRepository; | ||
| private final AttendanceCheckMapper attendanceCheckMapper; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| public int countAttendanceChecksOnTodayByUser(Long userId, Long roomId) { | ||
| LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay(); | ||
| LocalDateTime endOfDay = startOfDay.plusDays(1); | ||
|
|
||
| return attendanceCheckJpaRepository.countByUserIdAndRoomIdAndCreatedAtBetween(userId, roomId, startOfDay, endOfDay, ACTIVE); | ||
|
Comment on lines
+22
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 시간대 의존성 및 테스트 용이성 개선: Clock 주입으로 교체
아래와 같이 적용 가능합니다(별도의 +import java.time.Clock;
@@
-public class AttendanceCheckQueryPersistenceAdapter implements AttendanceCheckQueryPort {
+public class AttendanceCheckQueryPersistenceAdapter implements AttendanceCheckQueryPort {
@@
- public int countAttendanceChecksOnTodayByUser(Long userId, Long roomId) {
- LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay();
- LocalDateTime endOfDay = startOfDay.plusDays(1);
+ private final Clock clock;
+
+ public int countAttendanceChecksOnTodayByUser(Long userId, Long roomId) {
+ LocalDateTime startOfDay = LocalDateTime.now(clock).toLocalDate().atStartOfDay();
+ LocalDateTime endOfDay = startOfDay.plusDays(1);추가(별도 파일) 예시: // 예: TimeConfig.java
@Bean
public Clock clock() {
return Clock.system(ZoneId.of("Asia/Seoul"));
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package konkuk.thip.room.adapter.out.persistence.repository.attendancecheck; | ||
|
|
||
| import konkuk.thip.common.entity.StatusType; | ||
| import konkuk.thip.room.adapter.out.jpa.AttendanceCheckJpaEntity; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public interface AttendanceCheckJpaRepository extends JpaRepository<AttendanceCheckJpaEntity, Long> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion COUNT 반환 타입은 long 사용 권장 JPA COUNT는 Long으로 계산됩니다. 불필요한 narrowing을 피하려면 반환 타입을 long으로 두는 것이 안전합니다. 아래처럼 시그니처를 변경해주세요(어댑터/포트/서비스 호출부 전파 수정 필요). -public interface AttendanceCheckJpaRepository extends JpaRepository<AttendanceCheckJpaEntity, Long> {
+public interface AttendanceCheckJpaRepository extends JpaRepository<AttendanceCheckJpaEntity, Long> {
@@
- int countByUserIdAndRoomIdAndCreatedAtBetween(...);
+ long countByUserIdAndRoomIdAndCreatedAtBetween(...);
🤖 Prompt for AI Agents |
||
|
|
||
| // TODO : count 값을 매번 쿼리를 통해 계산하는게 아니라 DB에 저장 or redis 캐시에 저장하는 방법도 좋을 듯 | ||
| @Query("SELECT COUNT(a) FROM AttendanceCheckJpaEntity a " + | ||
| "WHERE a.userJpaEntity.userId = :userId " + | ||
| "AND a.roomJpaEntity.roomId = :roomId " + | ||
| "AND a.status = :status " + | ||
| "AND a.createdAt >= :startOfDay " + | ||
| "AND a.createdAt < :endOfDay") | ||
| int countByUserIdAndRoomIdAndCreatedAtBetween(@Param("userId") Long userId, @Param("roomId") Long roomId, @Param("startOfDay") LocalDateTime startOfDay, @Param("endOfDay") LocalDateTime endOfDay, @Param("status")StatusType status); | ||
| } | ||
|
Comment on lines
+1
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain리포지토리 단위 테스트/슬라이스 테스트 보강 제안 경계시간(자정 직전/직후), 타임존(Asia/Seoul) 및 status 필터링(활성/삭제/만료)을 검증하는 슬라이스 테스트를 추가하면 회귀를 방지할 수 있습니다. 아래 스크립트로 관련 테스트 존재 여부를 확인해 주세요. 없다면 추가를 권장합니다. 🏁 Script executed: #!/bin/bash
# 리포지토리/어댑터 테스트 존재 여부 점검
rg -n "AttendanceCheckJpaRepository|AttendanceCheckQueryPersistenceAdapter" src/test src/it || true
# 경계 케이스 키워드 탐색
rg -n "startOfDay|endOfDay|Asia/Seoul|ZoneId|Clock" src/test || trueLength of output: 569 테스트 보강 필요 — AttendanceCheckJpaRepository의 경계시간/타임존/상태 필터링 검증 추가 간단 검증 결과: src/test에서 AttendanceCheckCreateApiTest가 AttendanceCheckJpaRepository를 사용함을 확인했으나, "startOfDay|endOfDay|Asia/Seoul|ZoneId|Clock" 관련 키워드는 검색되지 않아 경계/타임존/상태 케이스에 대한 테스트가 없습니다. 확인된 위치
권장 조치 (간단)
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package konkuk.thip.room.application.port.in; | ||
|
|
||
| import konkuk.thip.room.application.port.in.dto.AttendanceCheckCreateCommand; | ||
| import konkuk.thip.room.application.port.in.dto.AttendanceCheckCreateResult; | ||
|
|
||
| public interface AttendanceCheckCreateUseCase { | ||
|
|
||
| AttendanceCheckCreateResult create(AttendanceCheckCreateCommand command); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package konkuk.thip.room.application.port.in.dto; | ||
|
|
||
| public record AttendanceCheckCreateCommand( | ||
| Long creatorId, | ||
| Long roomId, | ||
| String content | ||
| ) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
입력 길이 제약 및 예시 추가 제안
DB 컬럼 길이와 정합성을 맞추기 위해 최대 길이 제한을 권장합니다. 또한 Swagger 예시를 제공하면 클라이언트 UX가 좋아집니다.
추가로 import가 필요 없다면 FQN 유지로 충분하지만, 선호하시면 상단에
import jakarta.validation.constraints.Size;를 추가하고 어노테이션에 FQN을 제거해도 됩니다.📝 Committable suggestion
🤖 Prompt for AI Agents