[feat] 오늘의 한마디 조회 api 개발#242
Conversation
- QueryProjection 도입
Walkthrough방 입장 멤버의 “오늘의 한마디” 조회 API를 신설. 컨트롤러 엔드포인트(/rooms/{roomId}/daily-greeting) 추가, 입력 포트/서비스 구현, 커서 기반 페이지네이션용 조회 포트·리포지토리(쿼리DSL)·DTO·매퍼 도입. 응답 레코드 정의 및 통합 테스트 3건 추가. Changes
Sequence Diagram(s)sequenceDiagram
participant C as Client
participant Ctrl as RoomPostQueryController
participant Svc as AttendanceCheckShowService
participant Val as RoomParticipantValidator
participant Port as AttendanceCheckQueryPort
participant Repo as AttendanceCheckQueryRepository
C->>Ctrl: GET /rooms/{roomId}/daily-greeting?cursor=...
Ctrl->>Svc: showDailyGreeting(userId, roomId, cursor)
Svc->>Val: validate(userId, roomId)
Val-->>Svc: valid
Svc->>Port: findAttendanceChecksByCreatedAtDesc(roomId, Cursor)
Port->>Repo: query createdAt desc with cursor, limit size+1
Repo-->>Port: List<AttendanceCheckQueryDto>
Port-->>Svc: CursorBasedList<AttendanceCheckQueryDto>
Svc->>Svc: map DTOs -> AttendanceCheckShowResponse (nextCursor, isLast)
Svc-->>Ctrl: AttendanceCheckShowResponse
Ctrl-->>C: BaseResponse<AttendanceCheckShowResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Test Results407 tests 407 ✅ 32s ⏱️ Results for commit 318913c. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (11)
src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckShowResponse.java (1)
11-20: boolean 컴포넌트 명명(isLast, isWriter) 직렬화 키 확인 권장Jackson(레코드 지원 활성화 전제)에서는 레코드 컴포넌트 이름을 그대로 JSON 키로 사용하므로 "isLast", "isWriter"로 직렬화되는 게 일반적입니다. 다만, 팀 전역 ObjectMapper 설정에 따라 boolean 접두어를 다르게 처리하는 경우가 있어 E2E 응답 스냅샷으로 한번 검증하는 것을 권장합니다. 필요 시 @JsonProperty로 고정 가능합니다.
예시(필요 시):
- public record AttendanceCheckShowDto( + public record AttendanceCheckShowDto( Long attendanceCheckId, Long creatorId, String creatorNickname, String creatorProfileImageUrl, String todayComment, String postDate, LocalDate date, // 해당 오늘의 한마디 데이터의 작성 날짜 - boolean isWriter + @com.fasterxml.jackson.annotation.JsonProperty("isWriter") boolean isWriter ) { }src/main/java/konkuk/thip/roompost/application/mapper/AttendanceCheckQueryMapper.java (1)
17-23: createdAt null 가능성에 대한 방어적 매핑은 선택 사항도메인 상 createdAt이 항상 존재한다면 현재 구현으로 충분합니다. 혹시나 null 가능성이 있는 경우 toLocalDate()에서 NPE가 발생할 수 있어 가드 추가를 고려할 수 있습니다. 성능 영향은 미미합니다.
- @Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(dto.createdAt()))") - @Mapping(target = "date", expression = "java(dto.createdAt().toLocalDate())") + @Mapping(target = "postDate", expression = "java(dto.createdAt() == null ? null : DateUtil.formatBeforeTime(dto.createdAt()))") + @Mapping(target = "date", expression = "java(dto.createdAt() == null ? null : dto.createdAt().toLocalDate())")src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java (2)
14-20: 반열림구간 [startOfDay, endOfDay) 정확히 구현됨. 메서드명(Between)과 실제 조건 불일치 주석 보강 제안
- JPQL에서
>= startOfDay AND < endOfDay로 경계값 처리, 과거 리뷰 러닝과 일치합니다.- 다만 메서드명이 “Between”이라 오해 소지가 있으므로, 주석으로 반열림임을 명확히 하거나 메서드명을 리네이밍하는 것이 가독성에 유리합니다(파생쿼리 아님: @query 사용이므로 이름 변경 영향도 낮음).
추가로, 조회 빈도가 높다면 (userId, roomId, status, createdAt) 복합 인덱스를 고려해 주세요. Count 쿼리의 범위 조건(createdAt)과 동등 조건 3개가 모두 활용됩니다.
14-20: 반복적 상태값 전달을 줄이는 편의 메서드 고려(선택)항상 ACTIVE만 카운트한다면 아래와 같은 디폴트 메서드(또는 별도 쿼리 메서드)를 두면 호출부 중복을 줄일 수 있습니다.
default int countActiveByUserAndRoomAndCreatedAtBetween(Long userId, Long roomId, LocalDateTime startOfDay, LocalDateTime endOfDay) { return countByUserIdAndRoomIdAndCreatedAtBetween(userId, roomId, startOfDay, endOfDay, StatusType.ACTIVE); }src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckShowUseCase.java (1)
7-7: cursorStr 네이밍/커서 규약 문서화 제안
- 매개변수명을
cursorStr→cursor로 단순화하면 가독성이 좋아집니다.- Javadoc에 커서 인코딩 규약을 명시해 주세요: createdAt 기준 내림차순, 경계는 배타(<), 첫 요청 시 null/빈 커서 처리 등.
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckShowService.java (1)
19-21: 페이지 사이즈를 설정화(프로퍼티)하여 운영 유연성 확보
PAGE_SIZE = 10하드코딩 대신 프로퍼티 주입(예:@Value("${api.attendance-check.page-size:10}"))으로 운영 중 튜닝이 가능하도록 하면 좋습니다. 동일 패턴을 다른 엔드포인트에도 적용해 일관성 있게 관리할 수 있습니다.Also applies to: 31-31
src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java (2)
101-113: 응답 필드 추가 검증 제안: isWriter/nextCursor정렬·콘텐츠 검증 훌륭합니다. 추가로 다음을 검증하면 회귀 방지에 효과적입니다.
- 각 항목의
isWriter가 기대대로(me → true, others → false)인지- 첫 페이지의
nextCursor존재 여부예시(발췌)에 다음 단언을 추가하는 것을 권장합니다.
.andExpect(jsonPath("$.data.todayCommentList", hasSize(3))) +.andExpect(jsonPath("$.data.nextCursor", notNullValue())) // 정렬 순서 : 오늘의 한마디 작성 시각 기준 최신순 .andExpect(jsonPath("$.data.todayCommentList[0].creatorId", is(me.getUserId().intValue()))) .andExpect(jsonPath("$.data.todayCommentList[0].todayComment", is("오한3"))) +.andExpect(jsonPath("$.data.todayCommentList[0].isWriter", is(true))) .andExpect(jsonPath("$.data.todayCommentList[1].creatorId", is(user1.getUserId().intValue()))) .andExpect(jsonPath("$.data.todayCommentList[1].todayComment", is("오한2"))) +.andExpect(jsonPath("$.data.todayCommentList[1].isWriter", is(false))) .andExpect(jsonPath("$.data.todayCommentList[2].creatorId", is(me.getUserId().intValue()))) -.andExpect(jsonPath("$.data.todayCommentList[2].todayComment", is("오한1"))); +.andExpect(jsonPath("$.data.todayCommentList[2].todayComment", is("오한1"))) +.andExpect(jsonPath("$.data.todayCommentList[2].isWriter", is(true)));
207-236: 페이지네이션 종료 조건까지 검증하면 더 견고2페이지 응답에 대해
isLast == true및nextCursor미노출(또는 null/empty)까지 함께 검증하면 페이징 종료 판단 로직 회귀를 막을 수 있습니다..andExpect(status().isOk()) .andExpect(jsonPath("$.data.todayCommentList", hasSize(2))) +.andExpect(jsonPath("$.data.isLast", is(true))) +// nextCursor가 null이거나 빈 문자열로 내려가는 정책에 맞춰 단언 선택 +.andExpect(jsonPath("$.data.nextCursor", anyOf(nullValue(), isEmptyString()))) // 정렬 순서 : 오늘의 한마디 작성 시각 기준 최신순 .andExpect(jsonPath("$.data.todayCommentList[0].todayComment", is("오한2"))) .andExpect(jsonPath("$.data.todayCommentList[1].todayComment", is("오한1")));src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java (1)
87-94: 엔드포인트 Swagger 문서화 및 파라미터 설명 보완 제안기존 메서드들처럼 @operation을 붙여 요약/설명을 노출하면 일관성이 좋아집니다. 또한 roomId 파라미터 설명이 “게시글”로 되어 있어 오해 소지가 있습니다. “오늘의 한마디”로 수정 권장합니다.
아래와 같이 반영을 제안드립니다.
@GetMapping("/rooms/{roomId}/daily-greeting") +@Operation( + summary = "오늘의 한마디 조회", + description = "방 구성원만 조회할 수 있으며, 작성 시각 내림차순 커서 기반 페이지네이션을 지원합니다." +) public BaseResponse<AttendanceCheckShowResponse> showDailyGreeting( - @Parameter(description = "게시글을 조회할 방 ID", example = "1") @PathVariable final Long roomId, - @Parameter(description = "커서 (첫번째 요청시 : null, 다음 요청시 : 이전 요청에서 반환받은 nextCursor 값)") + @Parameter(description = "오늘의 한마디를 조회할 방 ID", example = "1") @PathVariable final Long roomId, + @Parameter(description = "커서 (첫 요청: null, 이후 요청: 이전 응답의 nextCursor). 예: 2025-08-17T10:15:30") @RequestParam(required = false) final String cursor, @Parameter(hidden = true) @UserId final Long userId) { return BaseResponse.ok(attendanceCheckShowUseCase.showDailyGreeting(userId, roomId, cursor)); }src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepositoryImpl.java (2)
41-43: 별칭(alias) 조인 left join 전환 검토 (데이터 누락 방지)사용자별 별칭/프로필 이미지가 선택적일 수 있다면 inner join으로 인해 해당 필드 미보유 사용자 레코드가 통째로 제외될 수 있습니다. 안전하게 left join을 권장합니다.
- .join(attendanceCheck.userJpaEntity, user) - .join(user.aliasForUserJpaEntity, alias) + .join(attendanceCheck.userJpaEntity, user) + .leftJoin(user.aliasForUserJpaEntity, alias)도메인 정책상 별칭이 항상 존재한다면 현 상태 유지도 가능합니다. 존재 보장 여부 확인 부탁드립니다.
28-31: 단일 LocalDateTime 커서의 동일 타임스탬프 충돌 가능성 안내현재 커서는 createdAt 단일 키와
createdAt < lastCreatedAt조건으로 페이징합니다. 정렬은createdAt desc이므로 일반적으로 정상 동작하나, 동일 초(또는 ms) 타임스탬프로 여러 건이 생성되는 경우 페이지 경계에서 소실/중복 가능성이 있습니다.
- 팀 선호(단일 LocalDateTime 커서의 단순성/성능 우선)를 알고 있어 필수 변경은 아닙니다.
- 만약 충돌 사례가 관측되면 보조 tie-breaker(id) 도입을 고려해 보세요. 예시(참고용):
- 정렬:
createdAt desc, attendanceCheckId desc- 커서:
(lastCreatedAt, lastId)- 조건:
createdAt < lastCreatedAt OR (createdAt = lastCreatedAt AND attendanceCheckId < lastId)현 구조에서는 시그니처 변경이 필요하므로 실제 적용은 추후 과제로 두고, 우선 모니터링을 추천드립니다.
Also applies to: 44-46
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java(3 hunks)src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckShowResponse.java(1 hunks)src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java(1 hunks)src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepository.java(1 hunks)src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/roompost/application/mapper/AttendanceCheckQueryMapper.java(1 hunks)src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckShowUseCase.java(1 hunks)src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckQueryPort.java(1 hunks)src/main/java/konkuk/thip/roompost/application/port/out/dto/AttendanceCheckQueryDto.java(1 hunks)src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckShowService.java(1 hunks)src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/port/out/AttendanceCheckQueryPort.java:5-5
Timestamp: 2025-08-14T07:15:54.900Z
Learning: 오늘의 한마디(AttendanceCheck) 작성 제한은 사용자 전역이 아닌 방(room) 단위로 적용되어야 함. 각 사용자는 각 방에서 하루 최대 5개의 오늘의 한마디를 작성할 수 있음.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.332Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
📚 Learning: 2025-08-14T09:27:36.393Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java:20-24
Timestamp: 2025-08-14T09:27:36.393Z
Learning: AttendanceCheckJpaRepository의 countByUserIdAndCreatedAtBetween 메서드는 메서드명과 달리 사용자 정의 JPQL 쿼리를 사용한다. "a.createdAt >= :startOfDay AND a.createdAt < :endOfDay"로 반열림구간 [startOfDay, endOfDay)을 정확히 구현하고 있어서 경계값 버그가 없다. 메서드명만으로 Spring Data JPA의 기본 Between 동작을 가정하면 안 된다.
Applied to files:
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.javasrc/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java
📚 Learning: 2025-08-14T09:27:36.393Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java:20-24
Timestamp: 2025-08-14T09:27:36.393Z
Learning: AttendanceCheckJpaRepository에서 countByUserIdAndCreatedAtBetween 메서드는 실제로는 사용자 정의 JPQL 쿼리 "createdAt >= :startOfDay AND createdAt < :endOfDay"를 사용하여 반열림구간 [startOfDay, endOfDay)을 구현하고 있다. 메서드 이름만으로 Spring Data JPA의 기본 Between 동작을 가정하면 안 된다.
Applied to files:
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.javasrc/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java
📚 Learning: 2025-08-14T07:19:27.250Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/port/out/AttendanceCheckQueryPort.java:5-5
Timestamp: 2025-08-14T07:19:27.250Z
Learning: AttendanceCheck 엔티티는 BaseJpaEntity를 상속받아 status 필드(StatusType.ACTIVE/INACTIVE/EXPIRED)로 소프트 딜리트를 구현하므로, 카운트 쿼리에서 status = 'ACTIVE' 조건을 포함해야 삭제된 레코드를 제외할 수 있음.
Applied to files:
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.javasrc/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java
🧬 Code Graph Analysis (2)
src/main/java/konkuk/thip/roompost/application/mapper/AttendanceCheckQueryMapper.java (1)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
DateUtil(12-62)
src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(29-413)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (14)
src/main/java/konkuk/thip/roompost/application/port/out/dto/AttendanceCheckQueryDto.java (1)
7-14: DTO 구성 필드 선정 적절 (조회/응답 흐름과 일치).조회용 필드 집합이 Mapper/Response와 1:1로 대응됩니다. createdAt까지 포함해 후속 페이징 커서 생성과 상대시간 포맷팅에 활용 가능해요.
src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckShowResponse.java (1)
6-10: 응답 페이로드 구조 명확합니다.
- todayCommentList, nextCursor, isLast로 무한 스크롤 페이징 계약이 분명합니다.
src/main/java/konkuk/thip/roompost/application/mapper/AttendanceCheckQueryMapper.java (2)
10-14: MapStruct 설정 적절합니다.
- componentModel=spring 및 unmappedTargetPolicy=ERROR로 누락 매핑 차단하는 점 좋습니다.
24-27: isWriter 컨텍스트 매핑 적합@context 기반 사용자 비교로 작성자 여부 도출이 명확합니다.
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java (1)
11-11: QueryRepository를 JpaRepository에 통합 확장한 방향 적절쿼리 전용 메서드를 동일 리포지토리 진입점으로 노출하여 주입 지점을 단순화합니다.
src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckQueryPort.java (2)
11-12: 커서 기반 포트 추가 방향 적절CursorBasedList로 nextCursor/isLast를 표준화한 점 좋습니다. 커서로 createdAt 단일 키를 쓰는 설계는 과거 러닝(단일 커서 선호)과 일치합니다.
11-12: AttendanceCheckQueryRepositoryImpl – 페이징 정렬 안정화 위해id.desc()추가 필요현재
AttendanceCheckQueryRepositoryImpl의 쿼리가 아래처럼createdAt.desc()만 사용하고 있어, 동일한createdAt값을 갖는 레코드가 페이지 경계에서 중복·누락될 수 있습니다. 보조 정렬자로id.desc()를 추가해 안정적인 커서 기반 페이징을 구현해 주세요.– 위치
• 파일:
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepositoryImpl.java
• 메서드: 커서 조회 쿼리의.orderBy(...)부분 (약 44–46라인)– 제안하는 변경 예시
-.orderBy( - attendanceCheck.createdAt.desc() -) +.orderBy( + attendanceCheck.createdAt.desc(), + attendanceCheck.id.desc() +)Likely an incorrect or invalid review comment.
src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckShowUseCase.java (1)
5-8: 포트 시그니처 명확 — LGTM유즈케이스 포트로 API 요구사항이 잘 드러납니다. 의존 방향/네이밍 모두 자연스럽습니다.
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckShowService.java (1)
24-42: 멤버 검증 → 커서 페이지네이션 → 매핑 흐름 깔끔 — LGTM
- 방 참여자 검증 선행, 커서 생성, 포트 호출, 매핑 반환까지 흐름이 간결합니다.
- 단일 LocalDateTime 커서 전략 선호에 부합합니다.
src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckQueryPersistenceAdapter.java (1)
33-43: 불필요한 fetch size 조정 제거 & 소프트 삭제 필터 추가 필요
- 어댑터(
AttendanceCheckQueryPersistenceAdapter)는 기존대로cursor.getPageSize()만 넘겨주시면 됩니다.
리포지토리 구현부(AttendanceCheckQueryRepositoryImpl)에서 이미
.limit(size + 1)으로 hasNext 판정용 1건 프리페치를 처리하고 있습니다.- 다만, 소프트 삭제(ACTIVE 상태만 조회)를 위한
status = ACTIVE조건이 빠져 있습니다.- 경계 조건(
createdAt < lastCreatedAt)은 이미 올바르게 적용되어 있습니다.수정 예시(리포지토리 구현부):
.where( roomPredicate, cursorPredicate, + attendanceCheck.status.eq(StatusType.ACTIVE) // 소프트 삭제 제외 )⛔ Skipped due to learnings
Learnt from: buzz0331 PR: THIP-TextHip/THIP-Server#180 File: src/main/java/konkuk/thip/room/application/port/out/AttendanceCheckQueryPort.java:5-5 Timestamp: 2025-08-14T07:19:27.250Z Learning: AttendanceCheck 엔티티는 BaseJpaEntity를 상속받아 status 필드(StatusType.ACTIVE/INACTIVE/EXPIRED)로 소프트 딜리트를 구현하므로, 카운트 쿼리에서 status = 'ACTIVE' 조건을 포함해야 삭제된 레코드를 제외할 수 있음.src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java (2)
33-33: 새 UseCase 의존성 주입 추가 적절컨트롤러 단의 책임 분리가 명확해졌고, @requiredargsconstructor와도 일관됩니다.
87-94: 엔드포인트 명세 확인 요청 (멤버십 검증, 커서 포맷/검증, 페이지 사이즈 정책)
서비스 계층 멤버십 검증
- AttendanceCheckShowService#showDailyGreeting 에서
roomParticipantValidator.validateUserIsRoomMember(roomId, userId)호출로 검증이 이루어집니다.- 비회원 접근 시 발생하는 예외가 글로벌 핸들러에서
ROOM_ACCESS_FORBIDDEN(403)으로 매핑되는지 확인 부탁드립니다.커서 포맷 및 유효성 검증
- Cursor.from()/toEncodedString 를 통해
- URL-인코딩된 문자열(URLEncoder/URLDecoder)
SPLIT_DELIMITER/JOIN_DELIMITER기반 다중 토큰 분할·조합
방식을 사용하고 있습니다.- 잘못된 인코딩·파싱 오류 시
IllegalArgumentException이 발생하는데,
글로벌 예외 핸들러가 400 Bad Request 로 처리하는지,
사용자가 이해하기 쉬운 에러 메시지로 응답되는지 검증 및 문서화하면 API 사용성이 높아집니다.페이지 사이즈 정책
- AttendanceCheckShowService 에서
private static final int PAGE_SIZE = 10으로 하드코딩되어 있습니다.- 최대 허용치나 설정 프로퍼티가 별도로 없으므로,
Controller vs Service vs application.yml 등에서 정책을 일원화·관리할 방안을 논의해주세요.src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepositoryImpl.java (2)
32-39: 프로젝션 필드 선택 검토(별칭 닉네임 vs 사용자 닉네임)지금은 nickname을
user.nickname에서 가져오고 imageUrl은alias.imageUrl에서 가져옵니다. 만약 방/별칭 단위로 닉네임도 달라지는 도메인이라면alias.nickname(또는 대응 필드) 사용이 더 자연스러울 수 있습니다. 현 도메인 모델에서 닉네임 소스가 어디인지 확인 부탁드립니다.
47-48: size + 1 패턴으로 다음 페이지 유무 판별 OK커서 기반 페이징의 관용 패턴을 잘 사용했습니다. 이후 어댑터에서 nextCursor 생성만 일관적으로 처리하면 됩니다.
| List<AttendanceCheckQueryDto> findAttendanceChecksByCreatedAtDesc(Long roomId, LocalDateTime lastCreatedAt, int size); | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
페이지네이션 안정성(배타 경계·타이브레이커) 명시 필요
lastCreatedAt는 정렬키가 createdAt 내림차순일 때 “배타 경계(created_at < :lastCreatedAt)”여야 중복/누락 없이 페이징됩니다. 인터페이스 수준 Javadoc으로 이 규약을 명확히 해두는 것을 권장합니다.- 동일한
createdAt다건 사례에서 안정 정렬을 위해 2차 키(예:attendanceCheckId DESC)를 구현체(QueryDSL)에서 함께 적용해 주세요. 단일 LocalDateTime 커서를 선호하시더라도(리소스 참고) 서버 쿼리에서는 안정 정렬이 필수입니다.
[suggest_optional_refactor]
다음 스크립트로 구현체에서 배타 경계(<), ACTIVE 필터, 2차 정렬 키 적용 여부를 확인해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 커스텀 구현체 확인
fd -t f 'AttendanceCheckQueryRepositoryImpl.java' | while read -r file; do
echo "== $file =="
rg -n -C3 -e 'findAttendanceChecksByCreatedAtDesc' -e 'where' -e 'status' -e 'createdAt' -e 'order' "$file" || sed -n '1,200p' "$file"
done
# 2) JPA 리포지토리에 커스텀 인터페이스가 혼합되어 있는지 확인
fd -t f 'AttendanceCheckJpaRepository.java' | while read -r file; do
echo "== $file =="
sed -n '1,200p' "$file"
doneLength of output: 3628
페이지네이션 안정성: 배타 경계 확인됐으나 2차 정렬 키·상태 필터 누락
현재 QueryDSL 구현체에서
-
attendanceCheck.createdAt.lt(lastCreatedAt)로 배타 경계는 맞게 적용되어 있습니다.
하지만 아래 두 가지가 빠져 있어 안정적인 페이지네이션을 보장하지 못합니다:• 상태 필터(
status = ACTIVE)
• 동일한createdAt동시여러건에 대비한 2차 정렬 키(e.g.attendanceCheckId DESC)
수정 제안:
- 인터페이스 Javadoc (
AttendanceCheckQueryRepository.java)에 배타 경계·2차 정렬 키·상태 필터 규약을 명시 - 구현체 (
AttendanceCheckQueryRepositoryImpl.java)의 쿼리 체인 수정
// 기존
.where(roomPredicate, cursorPredicate)
.orderBy(
attendanceCheck.createdAt.desc()
)
// 제안
.where(
roomPredicate,
cursorPredicate,
+ attendanceCheck.status.eq(StatusType.ACTIVE)
)
.orderBy(
attendanceCheck.createdAt.desc(),
+ attendanceCheck.attendanceCheckId.desc()
)AttendanceCheckQueryRepository.java 메소드 Javadoc 예시:
/**
* roomId에 해당하는 출석 체크를 createdAt 내림차순으로 조회합니다.
* • 배타경계(cursor): createdAt < :lastCreatedAt
* • 2차정렬(stable sort): attendanceCheckId DESC
* • 상태필터: status = ACTIVE
* @param roomId 조회할 방 ID
* @param lastCreatedAt 커서(Null 시 최신부터 조회)
* @param size 페이지당 개수
*/
List<AttendanceCheckQueryDto> findAttendanceChecksByCreatedAtDesc(...);🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckQueryRepository.java
around lines 10-11, the method lacks Javadoc documenting the pagination contract
and the implementation is missing the ACTIVE status filter and a stable
secondary sort key; add Javadoc that states: exclusive cursor createdAt <
:lastCreatedAt, secondary sort attendanceCheckId DESC, and status = ACTIVE (and
that null lastCreatedAt means start from newest), then update the implementation
(AttendanceCheckQueryRepositoryImpl) to apply where(status.eq(ACTIVE)) in the
predicate, use createdAt.lt(lastCreatedAt) for the exclusive cursor (skip when
lastCreatedAt is null), and orderBy(createdAt.desc(), attendanceCheckId.desc())
while limiting results to size to ensure stable, correct pagination.
| @QueryProjection | ||
| public AttendanceCheckQueryDto {} |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
@QueryProjection의 compact constructor 사용은 컴파일 실패 가능성이 높습니다. 명시적 canonical constructor로 전환 권장
QueryDSL의 @QueryProjection은 일반적으로 “파라미터가 명시된 생성자”에 부착될 때 안정적으로 Q타입을 생성합니다. record의 compact constructor(파라미터 없는 형태)에 붙이면 처리되지 않아 QAttendanceCheckQueryDto 생성이 실패할 수 있습니다.
두 가지 대안 중 하나를 적용해 주세요.
- 옵션 A(권장): canonical constructor에 @QueryProjection 적용
- 옵션 B: DTO에서 @QueryProjection 제거하고 Repository 구현에서 Projections.constructor(...) 사용
옵션 A 적용용 패치:
@QueryProjection
- public AttendanceCheckQueryDto {}
+ public AttendanceCheckQueryDto(
+ Long attendanceCheckId,
+ Long creatorId,
+ String creatorNickname,
+ String creatorProfileImageUrl,
+ String todayComment,
+ LocalDateTime createdAt
+ ) {
+ this.attendanceCheckId = attendanceCheckId;
+ this.creatorId = creatorId;
+ this.creatorNickname = creatorNickname;
+ this.creatorProfileImageUrl = creatorProfileImageUrl;
+ this.todayComment = todayComment;
+ this.createdAt = createdAt;
+ }Q타입을 사용하는지 확인하려면 다음 스크립트를 실행해 주세요. 만약 new QAttendanceCheckQueryDto(...) 사용이 발견되면 옵션 A가 필수입니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -nP -C3 'new\s+QAttendanceCheckQueryDto\b'
rg -nP -C3 'AttendanceCheckQueryRepositoryImpl|findAttendanceChecksByCreatedAtDesc'Length of output: 7673
AttendanceCheckQueryDto의 @QueryProjection을 canonical constructor로 전환하세요
QueryDSL은 파라미터가 명시된 생성자에만 @QueryProjection을 인식해 Q타입을 생성하므로, compact constructor에 붙인 현재 코드는 컴파일/Q타입 생성에 실패합니다. 아래와 같이 수정해 주세요.
수정 위치:
- src/main/java/konkuk/thip/roompost/application/port/out/dto/AttendanceCheckQueryDto.java (약 15–16라인)
제안된 패치:
public record AttendanceCheckQueryDto(
Long attendanceCheckId,
Long creatorId,
String creatorNickname,
String creatorProfileImageUrl,
String todayComment,
LocalDateTime createdAt
) {
- @QueryProjection
- public AttendanceCheckQueryDto {}
+ @QueryProjection
+ public AttendanceCheckQueryDto(
+ Long attendanceCheckId,
+ Long creatorId,
+ String creatorNickname,
+ String creatorProfileImageUrl,
+ String todayComment,
+ LocalDateTime createdAt
+ ) {
+ this.attendanceCheckId = attendanceCheckId;
+ this.creatorId = creatorId;
+ this.creatorNickname = creatorNickname;
+ this.creatorProfileImageUrl = creatorProfileImageUrl;
+ this.todayComment = todayComment;
+ this.createdAt = createdAt;
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @QueryProjection | |
| public AttendanceCheckQueryDto {} | |
| public record AttendanceCheckQueryDto( | |
| Long attendanceCheckId, | |
| Long creatorId, | |
| String creatorNickname, | |
| String creatorProfileImageUrl, | |
| String todayComment, | |
| LocalDateTime createdAt | |
| ) { | |
| @QueryProjection | |
| public AttendanceCheckQueryDto( | |
| Long attendanceCheckId, | |
| Long creatorId, | |
| String creatorNickname, | |
| String creatorProfileImageUrl, | |
| String todayComment, | |
| LocalDateTime createdAt | |
| ) { | |
| this.attendanceCheckId = attendanceCheckId; | |
| this.creatorId = creatorId; | |
| this.creatorNickname = creatorNickname; | |
| this.creatorProfileImageUrl = creatorProfileImageUrl; | |
| this.todayComment = todayComment; | |
| this.createdAt = createdAt; | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/application/port/out/dto/AttendanceCheckQueryDto.java
around lines 15–16, the @QueryProjection is currently on a compact
(no-parameter) constructor which QueryDSL does not recognize; replace it with a
canonical (parameter) constructor annotated with @QueryProjection that accepts
parameters for all fields (matching the DTO properties and types), remove the
annotation from any compact constructor, and ensure necessary imports
(@QueryProjection) remain so QueryDSL can generate the Q-type successfully (then
rebuild to trigger the annotation processor).
| roomParticipantValidator.validateUserIsRoomMember(roomId, userId); | ||
|
|
||
| // 2. Cursor 생성 | ||
| Cursor cursor = Cursor.from(cursorStr, PAGE_SIZE); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
잘못된 커서 입력에 대한 예외 매핑(400) 확인 권장
Cursor.from(cursorStr, ...) 파싱 실패 시 예외가 전역 핸들러에서 400으로 매핑되는지 확인 부탁드립니다. 500으로 누수되면 API UX에 영향이 큽니다.
다음 스크립트로 전역 예외 핸들러에서 커서 관련 예외를 매핑하는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# Cursor/커서 디코딩 관련 예외와 @ControllerAdvice 매핑 검색
rg -n -C3 -e '@ControllerAdvice' -e 'ExceptionHandler' -e 'Cursor' -e 'InvalidCursor' src/main/javaLength of output: 193009
Invalid Cursor 파싱 오류에 대해 400 Bad Request로 매핑 필요
현재 Cursor.from(…) 로 잘못된 커서를 입력했을 때, 파싱 실패(IllegalStateException, IndexOutOfBoundsException 등)가 전역 핸들러의 IllegalStateException 및 RuntimeException 처리 로직으로 넘어가 500으로 응답됩니다. 잘못된 클라이언트 입력은 400으로 처리해야 API UX 충돌을 방지할 수 있습니다.
수정 제안:
- GlobalExceptionHandler.java (src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java)
- IllegalStateException 핸들러를 별도로 분리하거나, 파싱 오류 전용 예외(InvalidCursorException 등)를 던진 뒤 @ExceptionHandler에서 400으로 매핑
- 예시:
@ExceptionHandler({InvalidCursorException.class, IllegalArgumentException.class, IndexOutOfBoundsException.class}) public ResponseEntity<ErrorResponse> handleInvalidCursor(Exception e) { log.error("[InvalidCursorExceptionHandler] {}", e.getMessage()); return ResponseEntity .badRequest() .body(ErrorResponse.of(ErrorCode.INVALID_CURSOR, e.getMessage())); }
- Cursor.from 또는 getAs 메서드에서 파싱 실패 시 InvalidCursorException을 던지도록 변경
위 반영 후 잘못된 커서 입력 시 HTTP 400으로 응답되는지 확인 부탁드립니다.
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckShowService.java
around line 31, the call Cursor.from(cursorStr, PAGE_SIZE) can throw parsing
errors (IllegalStateException, IndexOutOfBoundsException) that are currently
mapped to 500; change Cursor.from (or its internal getAs methods) to catch
parsing failures and throw a new checked or runtime InvalidCursorException (or
IllegalArgumentException) when the cursor is invalid, and update
GlobalExceptionHandler
(src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java)
to add an @ExceptionHandler for InvalidCursorException (and/or
IllegalArgumentException/IndexOutOfBoundsException) that logs the error and
returns ResponseEntity.badRequest() with
ErrorResponse.of(ErrorCode.INVALID_CURSOR, e.getMessage()) so invalid client
input returns HTTP 400.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)
298-301: 오류 코드 누락 가능성: ROOM_NOT_FOUND/USER_NOT_FOUND 추가 제안서비스 레이어에서 방 존재 여부와 사용자 존재 여부를 검증한다면, 문서에도
ROOM_NOT_FOUND,USER_NOT_FOUND를 포함하는 것이 일관됩니다. 많은 기존 조회/검증 API가 동일한 조합(ROOM_NOT_FOUND, USER_NOT_FOUND, ROOM_ACCESS_FORBIDDEN)을 사용하고 있습니다.아래처럼 보강을 제안합니다. 존재 은닉(존재 여부 비노출) 정책으로 의도적으로 제외한 것이라면 현 상태 유지도 가능합니다. 정책을 확인해 주세요.
- ATTENDANCE_CHECK_SHOW(new LinkedHashSet<>(Set.of( - ROOM_ACCESS_FORBIDDEN - ))), + ATTENDANCE_CHECK_SHOW(new LinkedHashSet<>(Set.of( + ROOM_ACCESS_FORBIDDEN, + ROOM_NOT_FOUND, + USER_NOT_FOUND + ))),src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java (1)
86-98: 스웨거 문구 정정(‘게시글’ → ‘오늘의 한마디’) 및 커서 예시 추가 제안
- Line 93: 파라미터 설명의 ‘게시글’은 오타로 보입니다. ‘오늘의 한마디’로 정정 제안.
- Line 94: 커서 기반 페이지네이션을 명확히 하기 위해 예시(example) 값을 추가하면 클라이언트 구현이 수월합니다. LocalDateTime 단일 커서 전략을 사용한다면 LDT 예시를, 다른 인코딩을 사용한다면 그에 맞는 예시로 조정해 주세요.
- @Parameter(description = "게시글을 조회할 방 ID", example = "1") @PathVariable final Long roomId, - @Parameter(description = "커서 (첫번째 요청시 : null, 다음 요청시 : 이전 요청에서 반환받은 nextCursor 값)") + @Parameter(description = "오늘의 한마디를 조회할 방 ID", example = "1") @PathVariable final Long roomId, + @Parameter(description = "커서 (첫 요청: null, 이후: 이전 응답의 nextCursor)", example = "2025-08-17T12:34:56.000Z") @RequestParam(required = false) final String cursor,추가 확인 요청:
- 커서가 LocalDateTime 단일 키인지, 다른 인코딩(Base64 등)인지 문서에 명시 가능할까요? seongjunnoh의 선호(단일 LocalDateTime 커서)를 고려하면 LDT 예시가 적합합니다.
- 정렬/슬라이싱이 생성시간 내림차순(최신순) 기준임을 Operation 설명에 한 줄 추가하면 이해가 더 쉬워집니다.
- 네이밍 통일성: 본 컨트롤러 내 문서에서 ‘방’/‘모임’ 용어가 혼재합니다. 본 메서드는 ‘방’, 다른 메서드는 ‘모임’을 사용합니다. 외부 문서화 관점에서 용어 통일을 가벼운 수준으로 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java(1 hunks)src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java(4 hunks)src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckShowResponse.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckShowResponse.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/port/out/AttendanceCheckQueryPort.java:5-5
Timestamp: 2025-08-14T07:15:54.900Z
Learning: 오늘의 한마디(AttendanceCheck) 작성 제한은 사용자 전역이 아닌 방(room) 단위로 적용되어야 함. 각 사용자는 각 방에서 하루 최대 5개의 오늘의 한마디를 작성할 수 있음.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.332Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
🔇 Additional comments (2)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostQueryController.java (2)
9-9: 새 API에 필요한 의존성 및 정적 임포트 추가 LGTM
AttendanceCheckShowResponse/UseCase임포트와SwaggerResponseDescription.*정적 임포트 사용이 새 엔드포인트와 잘 맞습니다.Also applies to: 12-12, 23-23
32-32: 생성자 주입으로 UseCase 추가 LGTM불변 필드 +
@RequiredArgsConstructor조합으로 의존성 주입 일관성이 유지됩니다.
| @Schema(description = "작성 시각(상대 시간 등 가공된 문자열)", example = "5분 전") | ||
| String postDate, | ||
|
|
||
| @Schema(description = "작성 날짜(yyyy-MM-dd), 이걸로 날짜별로 끊어서 화면에 보여주시면 됩니다.", example = "2025-08-17") |
| List<AttendanceCheckShowResponse.AttendanceCheckShowDto> toAttendanceCheckShowResponse(List<AttendanceCheckQueryDto> dtos, @Context Long userId); | ||
|
|
||
| @Named("isWriter") | ||
| default boolean isWriter(Long creatorId, @Context Long userId) { |
| public interface AttendanceCheckQueryMapper { | ||
|
|
||
| @Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(dto.createdAt()))") | ||
| @Mapping(target = "date", expression = "java(dto.createdAt().toLocalDate())") |
| } | ||
|
|
||
| @Test | ||
| @DisplayName("방의 멤버가 아닌 사람이 오늘의 한마디 조회 요청을 보낼 경우, 403 error가 발생한다.") |
| @Schema(description = "작성 시각(상대 시간 등 가공된 문자열)", example = "5분 전") | ||
| String postDate, | ||
|
|
||
| @Schema(description = "작성 날짜(yyyy-MM-dd), 이걸로 날짜별로 끊어서 화면에 보여주시면 됩니다.", example = "2025-08-17") |
| @Override | ||
| public List<AttendanceCheckQueryDto> findAttendanceChecksByCreatedAtDesc(Long roomId, LocalDateTime lastCreatedAt, int size) { | ||
| QAttendanceCheckJpaEntity attendanceCheck = QAttendanceCheckJpaEntity.attendanceCheckJpaEntity; | ||
| QUserJpaEntity user = QUserJpaEntity.userJpaEntity; | ||
| QAliasJpaEntity alias = QAliasJpaEntity.aliasJpaEntity; | ||
|
|
||
| BooleanExpression roomPredicate = attendanceCheck.roomJpaEntity.roomId.eq(roomId); | ||
| BooleanExpression cursorPredicate = (lastCreatedAt == null) ? null : attendanceCheck.createdAt.lt(lastCreatedAt); | ||
|
|
||
| return jpaQueryFactory | ||
| .select(new QAttendanceCheckQueryDto( | ||
| attendanceCheck.attendanceCheckId, | ||
| user.userId, | ||
| user.nickname, | ||
| alias.imageUrl, | ||
| attendanceCheck.todayComment, | ||
| attendanceCheck.createdAt | ||
| )) | ||
| .from(attendanceCheck) | ||
| .join(attendanceCheck.userJpaEntity, user) | ||
| .join(user.aliasForUserJpaEntity, alias) | ||
| .where(roomPredicate, cursorPredicate) | ||
| .orderBy( | ||
| attendanceCheck.createdAt.desc() | ||
| ) | ||
| .limit(size + 1) // 다음 페이지 존재 여부를 확인하기 위해 | ||
| .fetch(); | ||
| } |
#️⃣ 연관된 이슈
📝 작업 내용
오늘의 한마디 조회 api를 개발하였습니다
📸 스크린샷
로컬 서버에서 postman 로 확인 또한 마쳤습니다
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신기능
테스트
문서