[refactor] RoomStatus 도입에 따른 방 만료 조건 수정 및 만료된 방 내부 게시글 조회만 가능하도록 수정#300
[refactor] RoomStatus 도입에 따른 방 만료 조건 수정 및 만료된 방 내부 게시글 조회만 가능하도록 수정#300
Conversation
WalkthroughRoomStatus 기반으로 날짜 필터를 제거하고 조회·카운트·검증 로직을 상태 기반으로 전환했으며, 여러 서비스와 접근 정책에 진행중 검증을 추가하고 관련 포트·퍼시스턴스·DTO·테스트를 업데이트했습니다. Swagger 에러 코드도 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant API as "Controller/API"
participant Policy as "AccessPolicy / Service"
participant Validator as "RoomValidator"
participant Cmd as "RoomCommandPort"
participant Room as "Room(domain)"
Client->>API: 요청(댓글/좋아요/출석/투표/기타)
API->>Policy: 멤버십 검증
Policy-->>API: OK
API->>Validator: validateRoomInProgress(roomId)
Validator->>Cmd: getByIdOrThrow(roomId)
Cmd-->>Validator: Room
Validator->>Room: validateRoomInProgress()
alt 방 진행 중
Room-->>Validator: OK
Validator-->>API: 승인
API->>Service: 작업 계속(저장/업데이트/삭제)
else 진행 아님 / 만료
Room-->>Validator: 예외(ROOM_NOT_IN_PROGRESS / ROOM_IS_EXPIRED)
Validator-->>API: 예외 전파
API-->>Client: 에러 응답
end
sequenceDiagram
autonumber
participant BookSvc as "BookRecruitingRoomsService"
participant RoomQuery as "RoomQueryPort"
participant Repo as "RoomJpaRepository"
BookSvc->>RoomQuery: countRecruitingRoomsByBookIsbn(isbn)
RoomQuery->>Repo: countRecruitingRoomsByBookIsbn(isbn)
Repo-->>RoomQuery: int count
RoomQuery-->>BookSvc: int count
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)
153-158: 만료 방 “쓰기 금지” 일관성: RECORD_PIN/VOTE_DELETE/VOTE_UPDATE에도 ROOM_IS_EXPIRED 추가 필요요구사항상 만료 방은 읽기 전용이므로 고정(PIN), 투표 수정/삭제도 차단되어야 합니다. 아래처럼 코드 추가를 권장합니다.
RECORD_PIN(new LinkedHashSet<>(Set.of( ROOM_ACCESS_FORBIDDEN, BOOK_NOT_FOUND, RECORD_NOT_FOUND, - RECORD_ACCESS_FORBIDDEN + RECORD_ACCESS_FORBIDDEN, + ROOM_IS_EXPIRED ))), VOTE_DELETE(new LinkedHashSet<>(Set.of( ROOM_ACCESS_FORBIDDEN, VOTE_NOT_FOUND, - VOTE_ACCESS_FORBIDDEN + VOTE_ACCESS_FORBIDDEN, + ROOM_IS_EXPIRED ))), VOTE_UPDATE(new LinkedHashSet<>(Set.of( ROOM_ACCESS_FORBIDDEN, VOTE_NOT_FOUND, - VOTE_ACCESS_FORBIDDEN + VOTE_ACCESS_FORBIDDEN, + ROOM_IS_EXPIRED ))),Also applies to: 183-187, 188-192
132-146: ROOM_IS_EXPIRED 누락 — 즉시 추가 필요다음 응답셋에 ROOM_IS_EXPIRED가 빠져 있습니다. 만료 차단이 필요한 쓰기/수정/삭제 엔드포인트의 LinkedHashSet에 ROOM_IS_EXPIRED를 추가하세요.
파일: src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
- RECORD_PIN (lines 153–158)
- VOTE_DELETE (lines 183–187)
- VOTE_UPDATE (lines 188–190)
- ATTENDANCE_CHECK_DELETE (lines 337–339)
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (2)
33-46: 모집중 목록의 커서 필드 불일치(정렬키는 startDate, 커서는 endDate)를 수정하세요현재 헬퍼
findRoomsByDeadlineCursor는 nextCursor에dto.endDate()를 사용합니다. 하지만 이 두 메서드는 startDate로 정렬된 결과를 반환하므로 커서도 startDate를 써야 중복/누락이 없습니다.- public CursorBasedList<RoomQueryDto> searchRecruitingRoomsByDeadline(String keyword, Cursor cursor) { - return findRoomsByDeadlineCursor(cursor, ((lastLocalDate, lastId, pageSize) -> - roomJpaRepository.findRecruitingRoomsOrderByStartDateAsc(keyword, lastLocalDate, lastId, pageSize))); - } + public CursorBasedList<RoomQueryDto> searchRecruitingRoomsByDeadline(String keyword, Cursor cursor) { + return findRoomsByStartDateCursor(cursor, (lastLocalDate, lastId, pageSize) -> + roomJpaRepository.findRecruitingRoomsOrderByStartDateAsc(keyword, lastLocalDate, lastId, pageSize)); + } - public CursorBasedList<RoomQueryDto> searchRecruitingRoomsWithCategoryByDeadline(String keyword, Category category, Cursor cursor) { - return findRoomsByDeadlineCursor(cursor, (lastLocalDate, lastId, pageSize) -> - roomJpaRepository.findRecruitingRoomsWithCategoryOrderByStartDateAsc( - keyword, category, lastLocalDate, lastId, pageSize - ) - ); - } + public CursorBasedList<RoomQueryDto> searchRecruitingRoomsWithCategoryByDeadline(String keyword, Category category, Cursor cursor) { + return findRoomsByStartDateCursor(cursor, (lastLocalDate, lastId, pageSize) -> + roomJpaRepository.findRecruitingRoomsWithCategoryOrderByStartDateAsc( + keyword, category, lastLocalDate, lastId, pageSize)); + }또한 아래의 startDate 정렬 기반 메서드들도 동일하게 교체하세요.
- public CursorBasedList<RoomQueryDto> findRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { - return findRoomsByDeadlineCursor(cursor, (lastLocalDate, lastId, pageSize) -> - roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize)); - } + public CursorBasedList<RoomQueryDto> findRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { + return findRoomsByStartDateCursor(cursor, (lastLocalDate, lastId, pageSize) -> + roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize)); + } - public CursorBasedList<RoomQueryDto> findRoomsByIsbnOrderByDeadline(String isbn, Cursor cursor) { - return findRoomsByDeadlineCursor(cursor, (lastLocalDate, lastId, pageSize) -> - roomJpaRepository.findRoomsByIsbnOrderByStartDateAsc(isbn, lastLocalDate, lastId, pageSize)); - } + public CursorBasedList<RoomQueryDto> findRoomsByIsbnOrderByDeadline(String isbn, Cursor cursor) { + return findRoomsByStartDateCursor(cursor, (lastLocalDate, lastId, pageSize) -> + roomJpaRepository.findRoomsByIsbnOrderByStartDateAsc(isbn, lastLocalDate, lastId, pageSize)); + }startDate 전용 헬퍼 추가:
+ private CursorBasedList<RoomQueryDto> findRoomsByStartDateCursor(Cursor cursor, LocalDateCursorRoomQueryFunction queryFunction) { + LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); + Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + int pageSize = cursor.getPageSize(); + List<RoomQueryDto> dtos = queryFunction.apply(lastLocalDate, lastId, pageSize); + return CursorBasedList.of(dtos, pageSize, dto -> { + Cursor nextCursor = new Cursor(List.of( + dto.startDate().toString(), + dto.roomId().toString() + )); + return nextCursor.toEncodedString(); + }); + }
115-124: 혼합 목록(nextCursor)이 항상 endDate를 사용해 2페이지 이후 페이징이 깨질 수 있습니다혼합에서는 진행중=끝나는날짜, 모집중=시작날짜를 커서로 사용해야 합니다. 현재는 무조건 endDate를 넣고 있어 모집중 구간에서 중복/누락 가능성이 있습니다. 우선 상태 도입 전까지는 startDate 기준 판별로 커서를 분기하세요.
- return CursorBasedList.of(dtos, pageSize, dto -> { - int priority = dto.startDate().isAfter(LocalDate.now()) ? 1 : 0; // 0 : 진행중인 방, 1 : 모집중인 방 // TODO : dto에 RoomStatus 도입되면 수정해야함 - - Cursor nextCursor = new Cursor(List.of( - String.valueOf(priority), - dto.endDate().toString(), - dto.roomId().toString() - )); - return nextCursor.toEncodedString(); - }); + return CursorBasedList.of(dtos, pageSize, dto -> { + // TODO: RoomStatus 필드 도입 시 priority 계산 및 커서 날짜 추출을 status 기반으로 교체 + boolean recruiting = dto.startDate().isAfter(LocalDate.now()); + int priority = recruiting ? 1 : 0; // 0: 진행중, 1: 모집중 + String dateKey = (recruiting ? dto.startDate() : dto.endDate()).toString(); + Cursor nextCursor = new Cursor(List.of( + String.valueOf(priority), + dateKey, + dto.roomId().toString() + )); + return nextCursor.toEncodedString(); + });
♻️ Duplicate comments (1)
src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (1)
197-206: 호스트 분기 검증 흐름 적절멤버→HOST 교체 후 isHost=true 분기 검증이 상태 기반 시나리오와 잘 맞습니다. 위 인덱스 접근 건만 반영하면 좋겠습니다.
Also applies to: 207-221, 241-244
🧹 Nitpick comments (35)
src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java (8)
9-9: RoomStatus 도입 반영 OK. 픽스처도 상태를 명시적으로 세팅해 테스트를 날짜·스케줄러로부터 분리하세요.현재 setUp에서 생성되는 기본 방 2개(private/public)는 RoomStatus를 세팅하지 않습니다. 상태 기반 정적 필터로 전환되었으므로, 테스트 안정성을 위해 RECRUITING을 명시하세요.
다음과 같이 setUp 내 두 빌더에 상태를 추가하면 시계/스케줄러에 독립적입니다.
RoomJpaEntity privateRoom = roomJpaRepository.save( RoomJpaEntity.builder() .bookJpaEntity(book) .category(category) .title("비공개방") .description("비공개방입니다") .isPublic(false) .password(PASSWORD_ENCODER.encode("1234")) .startDate(LocalDate.now().plusDays(1)) .endDate(LocalDate.now().plusDays(5)) .recruitCount(3) + .roomStatus(RoomStatus.RECRUITING) .build() ); RoomJpaEntity publicRoom = roomJpaRepository.save( RoomJpaEntity.builder() .bookJpaEntity(book) .category(category) .title("공개방") .description("공개방입니다") .isPublic(true) .password(null) .startDate(LocalDate.now().plusDays(1)) .endDate(LocalDate.now().plusDays(5)) .recruitCount(3) + .roomStatus(RoomStatus.RECRUITING) .build() );
69-83: setUp의 비공개 방 픽스처에 RoomStatus 추가 제안상태 기반 로직에서 기본 상태가 null이면 예기치 않은 실패가 발생할 수 있습니다. RECRUITING을 명시해 주세요.
동일 제안은 위 코멘트의 diff에 포함되어 있습니다.
88-99: setUp의 공개 방 픽스처에도 RoomStatus 추가 제안공개/비공개 모두 동일하게 RECRUITING 명시를 권장합니다.
동일 제안은 위 코멘트의 diff에 포함되어 있습니다.
149-168: “모집기간 만료(= 진행중 IN_PROGRESS)” 시나리오 매핑이 정확합니다. 주석/표현을 상태 기준으로 정리하면 더 명확합니다.IN_PROGRESS 설정은 “모집 종료”를 잘 표현합니다. 다만 본 테스트는 더 이상 날짜 비교가 핵심이 아니므로, 설명을 상태 중심으로 정리하면 의도가 선명해집니다.
아래처럼 DisplayName/주석을 조금 다듬는 것을 제안합니다.
-@DisplayName("[모집기간이 만료된] 비공개 방에 비밀번호 입력 검증 시 400 Bad Request 반환") +@DisplayName("[진행중(IN_PROGRESS)] 비공개 방에 비밀번호 입력 검증 시 400 Bad Request 반환") -// 모집기간: startDate.minusDays(1) 이전이면 만료 -// startDate를 오늘로 설정해 모집기간이 이미 지난 상태로 저장 +// 상태 기반 검증: 모집 종료(= IN_PROGRESS)인 방은 비밀번호 검증 불가
157-158: findAll().get(0) 의존 제거: 저장한 Book 엔티티를 필드로 보관해 직접 사용하세요.단일 데이터셋이라도 findAll().get(0)은 암묵적 가정에 의존합니다. setUp에서 저장한 Book을 필드에 보관 후 재사용하는 편이 안전합니다.
다음과 같이 최소 수정안을 제안합니다.
@@ - private Long userId; + private Long userId; private Long privateRoomId; private Long publicRoomId; + private BookJpaEntity book; @@ - BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() + book = bookJpaRepository.save(BookJpaEntity.builder() .isbn("1234567890123") ... .build()); @@ - RoomJpaEntity privateRoom = roomJpaRepository.save( + RoomJpaEntity privateRoom = roomJpaRepository.save( RoomJpaEntity.builder() - .bookJpaEntity(book) + .bookJpaEntity(book) ... .build() ); @@ - RoomJpaEntity publicRoom = roomJpaRepository.save( + RoomJpaEntity publicRoom = roomJpaRepository.save( RoomJpaEntity.builder() - .bookJpaEntity(book) + .bookJpaEntity(book) ... .build() ); @@ - RoomJpaEntity expiredRoom = roomJpaRepository.save( + RoomJpaEntity expiredRoom = roomJpaRepository.save( RoomJpaEntity.builder() - .bookJpaEntity(bookJpaRepository.findAll().get(0)) + .bookJpaEntity(book) ... .build() );
47-47: PasswordEncoder는 애플리케이션 Bean을 주입해 테스트와 운영 설정을 일치시키는 것을 권장합니다.강도(cost)나 알고리즘 변경 시 테스트가 실제와 어긋날 수 있습니다. @Autowired로 주입해 사용하세요.
- private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(); + @Autowired + private PasswordEncoder passwordEncoder; @@ - .password(PASSWORD_ENCODER.encode("1234")) + .password(passwordEncoder.encode("1234")) @@ - .password(PASSWORD_ENCODER.encode("1234")) + .password(passwordEncoder.encode("1234"))
179-183: EXPIRED 상태 케이스도 별도 테스트로 커버하세요.본 테스트는 IN_PROGRESS(모집 종료)를 검증합니다. 만료(EXPIRED) 케이스는 새로 추가된 ROOM_IS_EXPIRED 코드로 검증되도록 별도 테스트를 추가하는 것이 좋습니다.
예시:
@Test @DisplayName("[만료(EXPIRED)] 비공개 방에 비밀번호 입력 검증 시 400 Bad Request 및 ROOM_IS_EXPIRED 반환") void verifyRoomPassword_roomExpired() throws Exception { RoomJpaEntity expiredRoom = roomJpaRepository.save( RoomJpaEntity.builder() .bookJpaEntity(book) .category(Category.LITERATURE) .title("완전만료방") .description("기간이 완전히 만료된 방입니다") .isPublic(false) .password(passwordEncoder.encode("1234")) .startDate(LocalDate.now().minusDays(10)) .endDate(LocalDate.now().minusDays(5)) .recruitCount(3) .roomStatus(RoomStatus.EXPIRED) .build() ); mockMvc.perform(post("/rooms/{roomId}/password", expiredRoom.getRoomId()) .requestAttr("userId", userId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(new RoomVerifyPasswordRequest("1234")))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(ROOM_IS_EXPIRED.getCode())); }원하시면 제가 테스트 케이스 추가 PR 초안까지 준비해 드리겠습니다.
153-164: 도메인 규칙 관련 과거 학습 내용 메모: Room 도메인에선 startDate가 현재 이후여야 해서 만료/진행중 시뮬레이션이 번거롭습니다. 본 테스트처럼 JPA 엔티티에 직접 status를 세팅하면 도메인 제약을 피해 간단히 재현 가능합니다. 주석에 이 의도를 남겨두면 유지보수에 도움 됩니다.주석 한 줄 추가 제안:
+// 참고: 도메인 규칙(startDate > today) 제약을 우회하기 위해 JPA 엔티티에 status를 직접 세팅하여 상태를 시뮬레이션합니다.src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java (1)
16-16: 표현식 가독성 소폭 향상 제안.동치이지만 괄호로 의도를 드러내면 읽기가 더 쉽습니다:
!(test | local).적용 diff:
-@Profile("!test & !local") +@Profile("!(test | local)")src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java (2)
149-149: deleteAll → deleteAllInBatch로 일관/성능 정리대량 삭제 테스트에서는 batch API가 더 단순하고 빠릅니다. 아래 변경을 권장합니다.
- roomJpaRepository.deleteAll(); + roomJpaRepository.deleteAllInBatch();
151-164: 테스트 데이터 팩토리 사용 고려직접 엔티티 빌더 대신 TestEntityFactory로 RoomStatus(IN_PROGRESS) 방을 생성하면 테스트 의도가 더 명확합니다.
src/test/java/konkuk/thip/room/domain/RoomTest.java (1)
171-181: 설명 문구(status 기반으로 미세 조정 제안)테스트 설명이 날짜 기반 용어(“마감일”)을 사용합니다. 상태 기반으로 변경된 맥락에 맞춰 설명을 미세 조정하면 향후 혼선을 줄일 수 있습니다.
-@DisplayName("isRecruitmentPeriodExpired: 모집기간이 오늘이 마감일인 경우 false 반환") +@DisplayName("isRecruitmentPeriodExpired: 모집 시작 전(=RECRUITING)인 경우 false 반환")src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java (1)
142-145: 리플렉션 대신 테스트용 세터 사용 권장RoomJpaEntity.updateRoomStatus가 추가되었으므로 리플렉션 없이 더 명확하게 표현하세요.
- Field field = RoomJpaEntity.class.getDeclaredField("roomStatus"); - field.setAccessible(true); - field.set(room, RoomStatus.IN_PROGRESS); - roomJpaRepository.save(room); + room.updateRoomStatus(RoomStatus.IN_PROGRESS); + roomJpaRepository.saveAndFlush(room);src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java (1)
47-60: 변수 네이밍 소문자 카멜 케이스로 정리SavedattendanceCheck는 자바 컨벤션에 어긋납니다. 아래처럼 정리 권장.
- AttendanceCheck SavedattendanceCheck = attendanceCheckCommandPort.getByIdOrThrow(attendanceCheckCommandPort.save(attendanceCheck)); + AttendanceCheck savedAttendanceCheck = attendanceCheckCommandPort.getByIdOrThrow(attendanceCheckCommandPort.save(attendanceCheck)); ... - .postDate(DateUtil.formatBeforeTime(SavedattendanceCheck.getCreatedAt())) - .date(SavedattendanceCheck.getCreatedAt().toLocalDate()) + .postDate(DateUtil.formatBeforeTime(savedAttendanceCheck.getCreatedAt())) + .date(savedAttendanceCheck.getCreatedAt().toLocalDate())src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java (1)
110-121: LocalDate.now() 다중 사용으로 인한 경계시각 플래키니스 가능각 테스트에서 now를 여러 번 호출합니다. 자정 경계 등에서 날짜가 뒤바뀌면 정렬/페이징 어설션이 간헐적으로 실패할 수 있습니다. 테스트 메서드 시작부에서 base = LocalDate.now()를 캡처한 뒤 이를 사용하도록 정리하면 안정적입니다.
- RoomJpaEntity recruitingRoom1 = saveScienceRoom(..., LocalDate.now().plusDays(1), ...); + LocalDate base = LocalDate.now(); + RoomJpaEntity recruitingRoom1 = saveScienceRoom(..., base.plusDays(1), ...);src/main/java/konkuk/thip/room/application/service/RoomJoinService.java (1)
68-76: 중복 파라미터 제거
handleCancel(... Optional<RoomParticipant> participantOptional, Optional<RoomParticipant> roomParticipantOptional, ...)에서 두 Optional은 의미가 동일하며 후자는 사용되지 않습니다. 시그니처와 호출부에서 제거해주세요.-private void handleCancel(RoomJoinCommand roomJoinCommand, Optional<RoomParticipant> participantOptional, Optional<RoomParticipant> roomParticipantOptional, Room room) { +private void handleCancel(RoomJoinCommand roomJoinCommand, Optional<RoomParticipant> participantOptional, Room room) { ... - case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, roomParticipantOptional, room); + case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, room);src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
139-153: 네이밍/역할 불일치
findRoomsByDeadlineCursor가 내부적으로 endDate를 커서 키로 고정 사용합니다. 위에서 제안한 대로 startDate 전용 헬퍼를 분리하면 함수명이 역할과 일치합니다. 기존 함수 주석에 “endDate 기반 커서”임을 명시하는 것도 좋습니다.- private CursorBasedList<RoomQueryDto> findRoomsByDeadlineCursor(Cursor cursor, LocalDateCursorRoomQueryFunction queryFunction) { + // endDate 기반 커서 헬퍼 (진행/만료용) + private CursorBasedList<RoomQueryDto> findRoomsByDeadlineCursor(Cursor cursor, LocalDateCursorRoomQueryFunction queryFunction) {src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java (1)
20-26: 검증 컴포넌트 일원화 제안(RoomValidator)다른 모듈(예: RoomPostCommentAccessPolicy)은 RoomValidator를 통해 만료 검증을 일원화합니다. 본 서비스도 동일 패턴을 적용하면 테스트/유지보수 비용을 줄일 수 있습니다.
- private final RoomCommandPort roomCommandPort; + private final RoomCommandPort roomCommandPort; // (또는) private final RoomValidator roomValidator; ... - Room room = roomCommandPort.getByIdOrThrow(command.roomId()); - room.validateRoomExpired(); + // roomValidator.validateRoomExpired(command.roomId());src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (2)
57-85: 중복/미사용 코드 정리 제안
- 두 팩토리 메서드 모두
Alias alias = ...변수를 생성하지만 사용하지 않습니다.- 공통 빌더 중복이 커서 유지보수성이 떨어집니다.
TestEntityFactory로 흡수하거나 공통 헬퍼로 축소하는 것을 권장합니다.다음과 같이 불필요 변수를 제거하세요:
- Alias alias = TestEntityFactory.createScienceAlias(); ... - Alias alias = TestEntityFactory.createLiteratureAlias();또는
TestEntityFactory에createCustomRoom(..., recruitCount, roomStatus)를 추가하고 본문에서 해당 메서드를 사용하도록 단순화할 것을 고려해주세요. 원하시면 초안 제공 가능합니다.Also applies to: 87-115
151-152: 인덱스 접근의 비결정성 최소화
findAllByRoomId(...).get(1)은 정렬 보장이 없어 테스트 플레키성이 생길 수 있습니다. 첫 원소를 사용하거나 ID 오름차순 정렬된 리포지토리 메서드를 쓰는 방식으로 안정화하세요.예:
-UserJpaEntity joiningUser = roomParticipantJpaRepository.findAllByRoomId(targetRoom.getRoomId()).get(1).getUserJpaEntity(); +UserJpaEntity joiningUser = roomParticipantJpaRepository.findAllByRoomId(targetRoom.getRoomId()) + .stream().findFirst().orElseThrow().getUserJpaEntity();src/main/java/konkuk/thip/room/application/service/validator/RoomValidator.java (1)
14-17: 메서드 명확성 개선 제안
validateRoomExpired()는 “만료되었는지 검증(=만료 시 예외)” 의미로 혼동 여지가 있습니다. “만료 아님 보장”을 드러내는 이름이 가독성에 유리합니다.- public void validateRoomExpired(Long roomId) { + public void validateRoomNotExpired(Long roomId) { Room room = roomCommandPort.getByIdOrThrow(roomId); - room.validateRoomExpired(); + room.validateRoomExpired(); }참고: 호출부도 동일 이름으로 일괄 변경 필요.
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (1)
66-69: 파라미터 네이밍 정정
validateRoom(Long command)의 파라미터명command는 의미가 모호합니다.roomId로 교체하세요.- private void validateRoom(Long command) { - Room room = roomCommandPort.getByIdOrThrow(command); + private void validateRoom(Long roomId) { + Room room = roomCommandPort.getByIdOrThrow(roomId); room.validateRoomExpired(); }src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (1)
31-31: 주석 오타 수정
creator 겁증→creator 검증src/test/java/konkuk/thip/book/adapter/in/web/BookRecruitingRoomApiTest.java (1)
70-79: 미사용 테스트 데이터 제거생성된
alias/user가 이후에 사용되지 않습니다. 불필요 생성 비용을 줄이세요.- Alias alias = TestEntityFactory.createLiteratureAlias(); - - UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder() - .oauth2Id("kakao_999") - .nickname("유저") - .nicknameUpdatedAt(LocalDate.now().minusMonths(6)) - .role(UserRole.USER) - .alias(alias) - .build()); + // 사용자/별칭 데이터는 본 테스트에서 사용되지 않으므로 생략src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomJpaRepository.java (1)
21-22: Enum 상수 사용 권장 — JPQL에서 문자열 리터럴('RECRUITING') 제거RoomJpaEntity.roomStatus는 @Enumerated(EnumType.STRING)로 매핑되어 있습니다 (src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java:61). JPQL에서 문자열 대신 Enum 상수(FQCN)를 사용하거나, 더 안전하게는 파라미터 바인딩을 사용하세요. 예(파라미터 바인딩 권장):
@query("SELECT COUNT(r) FROM RoomJpaEntity r WHERE r.bookJpaEntity.isbn = :isbn AND r.roomStatus = :status")
int countRecruitingRoomsByBookIsbn(@param("isbn") String isbn, @param("status") RoomStatus status);FQCN 사용 시에는 실제 패키지명을 정확히 지정하세요.
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
21-21: RoomStatus 파라미터화 좋습니다 + 기본값/가변 인자 확장 제안
- 상태 기반 테스트 전환을 잘 반영했습니다.
- 자주 쓰이는 기본 케이스를 줄이기 위해 기본값 오버로드(예: 기본 RECRUITING)와
recruitCount커스터마이즈 오버로드 추가를 제안합니다.예: 파일 외 추가 메서드(참고용)
public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, LocalDate start, LocalDate end) { return createCustomRoom(book, category, start, end, RoomStatus.RECRUITING); } public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, String roomName, LocalDate start, LocalDate end, int recruitCount, RoomStatus roomStatus) { return RoomJpaEntity.builder() .title(roomName) .description("설명") .isPublic(true) .startDate(start) .endDate(end) .recruitCount(recruitCount) .bookJpaEntity(book) .category(category) .roomStatus(roomStatus) .build(); }Also applies to: 147-159, 161-173
src/main/java/konkuk/thip/room/domain/Room.java (2)
172-178: startRoomProgress에서 startDate를 now로 덮어쓰는 것은 데이터 의미를 훼손할 수 있음운영 중 정렬/커서에 startDate를 사용하는 쿼리가 존재합니다(예: 홈 참여중 목록 정렬 보조 키). 실제 시작 시각을 기록하려면 별도 필드(startedAt 등)를 두고, 예정일(startDate)은 보존하는 편이 안전합니다.
다음 변경을 검토해 주세요(예정일 보존):
public void startRoomProgress() { validateRoomRecruitExpired(); -// validateRoomExpired(); - - startDate = LocalDate.now(); roomStatus = RoomStatus.IN_PROGRESS; }우선 이 변경 시, IN_PROGRESS 방들의 정렬/커서 동작이 기대와 일치하는지 통합 테스트(홈 참여중 정렬, 무한스크롤 커서)를 한번 더 확인 부탁드립니다.
144-147: 주석 처리된 과거 날짜 로직 정리주석으로 남겨둔 과거(today 기준) 로직은 혼란을 줍니다. Git 히스토리로 추적 가능하니 제거해 주세요.
-// LocalDate today = LocalDate.now(); -// // 모집 마감일: startDate.minusDays(1) -// return today.isAfter(this.startDate.minusDays(1)); -// validateRoomExpired();Also applies to: 175-175
src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java (2)
66-73: 상태 세팅을 팩토리로 일원화createRoom + updateRoomStatus + save 대신, createCustomRoom(..., RoomStatus.IN_PROGRESS)를 사용하면 의도가 더 분명하고 중복 저장을 줄일 수 있습니다.
- room1 = TestEntityFactory.createRoom(book, category); - room1.updateRoomStatus(RoomStatus.IN_PROGRESS); - roomJpaRepository.save(room1); + room1 = roomJpaRepository.save( + TestEntityFactory.createCustomRoom(book, category, LocalDate.now(), LocalDate.now().plusDays(5), RoomStatus.IN_PROGRESS) + );동일 패턴의 room2도 동일 적용 권장.
276-277: 페이지네이션 대량 데이터 준비 시 상태 일괄 세팅 OKsaveScienceRoom 후 상태만 IN_PROGRESS로 바꾸는 접근은 테스트 가독성과 성능 균형이 좋습니다. 필요하면 saveScienceRoom 자체에서 상태 파라미터 받도록 확장해 중복 제거를 고려해도 됩니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (2)
60-66: 중복 헬퍼 통합카테고리만 다른 RECRUITING/IN_PROGRESS 방 생성 헬퍼가 중복됩니다. category와 status를 파라미터로 받아 하나의 헬퍼로 합치면 유지보수성이 올라갑니다.
- private RoomJpaEntity saveScienceRecruitingRoom(String bookTitle, String roomName, LocalDate s, LocalDate e) { ... RoomStatus.RECRUITING ... } - private RoomJpaEntity saveScienceProgressRoom(String bookTitle, String roomName, LocalDate s, LocalDate e) { ... RoomStatus.IN_PROGRESS ... } - private RoomJpaEntity saveLiteratureRecruitingRoom(...){...} - private RoomJpaEntity saveLiteratureProgressRoom(...){...} + private RoomJpaEntity saveRoom(String bookTitle, Category category, String roomName, LocalDate s, LocalDate e, RoomStatus status) { + BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBookWithBookTitle(bookTitle)); + return roomJpaRepository.save(TestEntityFactory.createCustomRoom(book, category, roomName, s, e, status)); + }Also applies to: 67-73, 74-80, 81-86
88-93: member_count 직접 업데이트는 테스트 용도로는 OK, 주석 보강 제안JdbcTemplate로 직접 갱신하는 이유(영속성 컨텍스트/트리거 회피)를 한 줄 코멘트로 남기면 이해가 빨라집니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (3)
39-43: 네이밍 정리 제안recruitingActiveWhere → recruitingWhere 가 더 직관적입니다(현재 RECRUITING만 포함).
205-221: 키셋 커서 논리 괄호 우선순위 명시 필요(AND/OR 혼합)현재 체이닝은 의도와 동일하게 컴파일되지만 가독성과 실수 여지를 줄이려면 중첩식을 명시적으로 묶는 편이 좋습니다.
- where.and(participant.userPercentage.lt(userPercentageCursor) - .or(participant.userPercentage.eq(userPercentageCursor) - .and(room.startDate.gt(startDateCursor)) - .or(participant.userPercentage.eq(userPercentageCursor) - .and(room.startDate.eq(startDateCursor)) - .and(room.roomId.gt(roomIdCursor)) - ) - ) - ); + BooleanExpression cursorCond = + participant.userPercentage.lt(userPercentageCursor) + .or( + participant.userPercentage.eq(userPercentageCursor) + .and( + room.startDate.gt(startDateCursor) + .or( + room.startDate.eq(startDateCursor) + .and(room.roomId.gt(roomIdCursor)) + ) + ) + ); + where.and(cursorCond);추가로 유사한 3중 커서(findMyRoomsWithPriority)에도 동일 패턴을 적용하면 일관성이 좋아집니다.
362-390: 카테고리/ISBN 기반 모집중 조회도 상태 기준으로 일원화 OK — 인덱스 권고조회 빈도가 높을 구간입니다. 다음 인덱스를 제안합니다.
- rooms(room_status, start_date, room_id)
- rooms(room_status, member_count, room_id)
- rooms(category, room_status, start_date, room_id)
- rooms(isbn, room_status, start_date, room_id)
마이그레이션/인덱스 적용 여부를 확인하고 필요 시 스크립트 준비를 도와드릴게요.
Also applies to: 392-398
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (30)
src/main/java/konkuk/thip/book/application/service/BookRecruitingRoomsService.java(1 hunks)src/main/java/konkuk/thip/book/application/service/BookSearchService.java(1 hunks)src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java(2 hunks)src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java(7 hunks)src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomJpaRepository.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java(14 hunks)src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java(1 hunks)src/main/java/konkuk/thip/room/application/service/RoomJoinService.java(1 hunks)src/main/java/konkuk/thip/room/application/service/validator/RoomValidator.java(1 hunks)src/main/java/konkuk/thip/room/domain/Room.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java(2 hunks)src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java(4 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java(1 hunks)src/main/java/konkuk/thip/roompost/application/service/policy/RoomPostLikeAccessPolicy.java(2 hunks)src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java(2 hunks)src/test/java/konkuk/thip/book/adapter/in/web/BookRecruitingRoomApiTest.java(4 hunks)src/test/java/konkuk/thip/common/util/TestEntityFactory.java(4 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java(2 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java(2 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java(5 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java(9 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java(9 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java(12 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java(2 hunks)src/test/java/konkuk/thip/room/domain/RoomTest.java(3 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler의 changeRoomStateToExpired()와 changeRoomStateToProgress()는 서로 다른 날짜 조건으로 완전히 다른 방들을 대상으로 하므로(end_date < today vs start_date <= today AND end_date >= today), 비동기 실행 순서와 관계없이 데이터 정합성에 문제가 없다.
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler에서 changeRoomStateToExpired()와 changeRoomStateToProgress()는 각각 다른 트랜잭션에서 실행되며, 날짜 기반으로 서로 다른 방들을 대상으로 하므로 실행 순서가 바뀌어도 데이터 정합성에 문제가 없다.
📚 Learning: 2025-07-08T16:30:33.771Z
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
Applied to files:
src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.javasrc/test/java/konkuk/thip/room/domain/RoomTest.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java
📚 Learning: 2025-08-14T07:15:54.958Z
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.958Z
Learning: 오늘의 한마디(AttendanceCheck) 작성 제한은 사용자 전역이 아닌 방(room) 단위로 적용되어야 함. 각 사용자는 각 방에서 하루 최대 5개의 오늘의 한마디를 작성할 수 있음.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java
📚 Learning: 2025-09-05T04:11:01.456Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler의 changeRoomStateToExpired()와 changeRoomStateToProgress()는 서로 다른 날짜 조건으로 완전히 다른 방들을 대상으로 하므로(end_date < today vs start_date <= today AND end_date >= today), 비동기 실행 순서와 관계없이 데이터 정합성에 문제가 없다.
Applied to files:
src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.javasrc/test/java/konkuk/thip/room/domain/RoomTest.javasrc/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.javasrc/main/java/konkuk/thip/room/domain/Room.java
📚 Learning: 2025-09-05T04:11:01.456Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler에서 changeRoomStateToExpired()와 changeRoomStateToProgress()는 각각 다른 트랜잭션에서 실행되며, 날짜 기반으로 서로 다른 방들을 대상으로 하므로 실행 순서가 바뀌어도 데이터 정합성에 문제가 없다.
Applied to files:
src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.javasrc/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.javasrc/main/java/konkuk/thip/room/domain/Room.java
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.javasrc/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java
📚 Learning: 2025-08-14T12:29:00.150Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#183
File: src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java:33-45
Timestamp: 2025-08-14T12:29:00.150Z
Learning: 방 생성 시 항상 HOST 역할의 RoomParticipant가 생성되며, RoomProgressHelper.updateUserAndRoomProgress() 메서드는 사용자가 해당 방의 참여자임이 사전 검증된 상황에서만 호출되므로 참여자 목록이 비어있을 수 없다.
Applied to files:
src/main/java/konkuk/thip/room/application/service/RoomJoinService.java
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java
📚 Learning: 2025-08-14T09:27:36.450Z
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.450Z
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/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java
🧬 Code graph analysis (3)
src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(33-373)
src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(33-373)
src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(33-373)
⏰ 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 (36)
src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java (2)
16-16: 프로필 조건 수정 방향 타당함 (OR-of-negations → AND-of-negations).이전
@Profile({"!test", "!local"})는 사실상 대부분의 환경에서 빈이 등록되는 조건이었는데(둘 중 하나만 비활성이어도 참),@Profile("!test & !local")로 바뀌면서 test/local 활성 시에는 명확히 비활성화됩니다. 의도에 부합합니다.
16-16: 프로필 표현식(@Profile("!test & !local")) 호환성 확인 필요해당 boolean 프로필 표현식은 Spring Framework/Boot 버전별로 지원 여부가 다릅니다—저장소의 Spring Boot/Framework 버전을 확인해 호환성을 보장하세요.
제공하신 스크립트 출력으로는 빌드 설정 파일(build.gradle(.kts), pom.xml, gradle.properties, gradle/libs.versions.toml)을 찾을 수 없어 자동 확인이 불가합니다. 빌드 파일 내용 또는 Spring Boot 버전을 붙여넣어 주세요.src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)
147-152: ROOM_IS_EXPIRED 추가 적용 방향 LGTM만료된 방에서의 쓰기 금지 정책을 Swagger에 반영한 점 일관적입니다. 관련 응답들에 ROOM_IS_EXPIRED가 포함되어 있어 API 문서와 실제 정책의 괴리가 줄어듭니다.
Also applies to: 159-164, 175-182, 257-259, 270-272, 282-284, 329-331
src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java (1)
159-159: 상태 기반 시나리오로 전환 LGTM모집 여부 판정을 RoomStatus.IN_PROGRESS로 명시해 상태 기반 필터링으로 자연스럽게 검증하고 있습니다.
src/test/java/konkuk/thip/room/domain/RoomTest.java (2)
191-193: 도메인 규칙 우회(reflection)로 만료 상태 강제 — 적절합니다startDate 도메인 제약을 깨지 않고 RoomStatus로 만료 시나리오를 구성해 테스트 안정성이 올라갑니다.
204-206: verifyPassword 만료 시나리오 구성 LGTM만료 상태에서의 비밀번호 검증이 올바르게 ROOM_RECRUITMENT_PERIOD_EXPIRED를 유발하는지 잘 검증했습니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java (1)
59-79: 확인 완료 — oauth2Id에 DB 유니크 제약 없음UserJpaEntity의 oauth2Id는 @column(name = "oauth2_id", length = 50, nullable = false)로만 정의되어 있고 unique=true가 없습니다 (src/main/java/konkuk/thip/user/adapter/out/jpa/UserJpaEntity.java:38). 마이그레이션에서도 oauth2_id에 대한 유니크 제약을 찾지 못했습니다. 따라서 src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java(59–79)처럼 동일한 oauth2Id를 여러 사용자에 사용해도 DB 제약 위반은 발생하지 않습니다.
권장: oauth2Id를 시스템상 유니크로 보장하려면 엔티티에 unique=true 추가 후 DB 마이그레이션을 생성하거나(필요 시), 테스트에서는 고유한 oauth2Id를 사용하세요.
src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java (1)
104-106: LGTM — 테스트 전용 사용으로 확인됨검색 결과 updateRoomStatus는 선언부와 src/test에서만 호출되며 프로덕션 경로에서 호출되지 않습니다. @VisibleForTesting 적절.
- public void updateRoomStatus(RoomStatus roomStatus) {this.roomStatus = roomStatus;} + public void updateRoomStatus(RoomStatus roomStatus) { + this.roomStatus = roomStatus; + }src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java (1)
38-41: 만료 방 쓰기 차단 선반영 — 적절한 위치에 검증 추가멤버십 검증 직후 만료 여부를 단일 책임(도메인)으로 확인하는 흐름이 깔끔합니다.
src/main/java/konkuk/thip/book/application/service/BookSearchService.java (1)
116-118: 상태 기반 카운팅으로 전환 LGTMRoomStatus 기반 집계 포트로 위임해 날짜 비교를 제거한 방향이 요구사항과 일치합니다.
src/main/java/konkuk/thip/book/application/service/BookRecruitingRoomsService.java (1)
26-28: count 전환(날짜→상태) 적절합니다초기 요청에서만 totalRoomCount 계산 유지도 좋습니다. 다만 해당 count가 RoomStatus.RECRUITING 조건만 집계하도록 JPA 쿼리/리포지토리 구현과 일치하는지 한 번만 확인 부탁드립니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java (2)
57-85: 테스트 헬퍼에 RoomStatus 주입한 변경 좋습니다엔티티 빌더에 roomStatus를 명시적으로 넣어 시나리오 의도가 선명해졌습니다. 재사용성 측면에서도 OK.
436-442: 커서 포맷(startDate|id) 사용 확인모집중 탭의 커서를 startDate|roomId로 구성한 점은 레포지토리의 정렬 키(활동 시작일 오름차순)와 일치합니다. 응답에서 반환되는 nextCursor의 포맷도 동일(startDate 기준)인지 컨트롤러/어댑터 계층과 합치 여부만 점검 부탁드립니다.
src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)
15-16: 상태 기반 count API로의 전환 LGTM메서드 네이밍과 의도가 명확합니다. 도메인 상 “모집중” 정의와 JPA 쿼리의 WHERE 조건이 일치하는지만 최종 점검해주세요.
src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java (1)
14-22: 만료 방 차단을 중앙 검증으로 위임한 점 좋습니다참여자 검증 후
roomValidator.validateRoomExpired로 쓰기 행위를 차단하는 흐름이 요구사항에 부합합니다.src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java (1)
73-109: 테스트 데이터에 RoomStatus 반영 OK마감임박/인기 후보군을 RECRUITING, 제외 대상을 EXPIRED/비공개로 명시해 의도가 선명합니다.
today를 고정해 사용해 플래키니스도 완화되었습니다.src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
28-30: count 어댑터 전환 OK포트/리포지토리와 일관성 있습니다.
src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java (1)
23-36: 삭제 전 만료 검증 추가 👍도메인에서
room.validateRoomExpired()로 차단하는 흐름이 요구사항과 일치합니다.src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java (3)
149-167: 상태 기반 데이터 시드와 정렬 검증 LGTM
RoomStatus.RECRUITING/IN_PROGRESS로 명시해 추천 목록이 ‘모집 마감 임박 순’으로만 노출되도록 한 구성 적절합니다. 정렬 기준이 쿼리에서 보장되는지(예: startDate ASC)도 한번 확인만 해주세요.Also applies to: 188-191
250-271: 추천 방 5개 제한 케이스 충실데이터 셋과 검증이 요구사항을 정확히 커버합니다. 👍
Also applies to: 286-296
302-311: 교차 카테고리/상태 배제 검증 명확문학 카테고리/IN_PROGRESS 방 배제 검증이 명확합니다.
Also applies to: 316-327
src/main/java/konkuk/thip/roompost/application/service/policy/RoomPostLikeAccessPolicy.java (1)
6-7: 만료 방 좋아요 차단 로직 추가 적합멤버십 검증 후 만료 검증을 추가해 “조회만 가능” 정책과 일치합니다. 동일 패턴을 다른 Post 액션에도 일관 적용되었는지 확인만 부탁드립니다.
Also applies to: 16-23
src/main/java/konkuk/thip/room/application/service/validator/RoomValidator.java (1)
8-17: 만료 검증의 도메인 집중화 좋습니다Room 로딩 후 도메인에 위임하는 구조가 깔끔합니다.
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (1)
32-34: 만료 검증 추가 위치 적절멤버십 → 만료 → 조회/수정 순으로 흐름이 명확합니다. 정책과 일치합니다.
Also applies to: 52-54
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (1)
27-30: 만료 방 삭제 차단 로직 적합삭제 전에 만료 검증을 넣어 정책에 부합합니다.
src/test/java/konkuk/thip/book/adapter/in/web/BookRecruitingRoomApiTest.java (1)
89-89: 상태 기반 필터 테스트 전환 LGTM
RoomStatus.RECRUITING/IN_PROGRESS명시로 의도가 분명합니다. 커서 페이징 케이스도 적절합니다.Also applies to: 102-102, 146-147
src/main/java/konkuk/thip/room/domain/Room.java (2)
143-148: 모집 만료 판정: 상태 기반 전환은 의도에 부합roomStatus가 RECRUITING이 아니면 모집 만료로 간주하는 로직이 요구사항(모집 종료 후 참여 불가)과 일치합니다. 이로 인해 IN_PROGRESS/EXPIRED 모두 참여 차단되므로 사이드이펙트 의도대로입니다.
180-184: 만료 검증 단일화 적용 확인 — 추가 조치 불필요Record/Vote/Attendance/Like/Comment 등 주요 쓰기 경로에서 room.validateRoomExpired() / roomValidator.validateRoomExpired(...) 호출이 확인됩니다. 날짜 기반 비교는 주석 처리되고 RoomStatus 기준으로 대체되어 있습니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java (2)
168-179: 정렬 시나리오 재현 방식 적절IN_PROGRESS 상태를 명시하고 startDate를 서로 다르게 둬 정렬 검증을 안정화했습니다. 테스트 목적과 잘 맞습니다.
216-223: 참여중 목록에서 RECRUITING 제외 케이스 명확RECRUITING/IN_PROGRESS를 분리 생성하여 필터 동작을 명료하게 검증하고 있습니다. 좋습니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java (2)
95-139: 모집중 검색(마감임박순) 시나리오 타당RECRUITING만 조회되고 정렬/타이브레이커(roomId asc) 검증이 포함되어 있어 회귀 방지에 유효합니다.
341-405: 키셋 페이지네이션 회귀 테스트 충실nextCursor 추출→2페이지 검증 플로우가 명료합니다. 대량 데이터에서도 안정적임을 보장합니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (4)
97-128: 상태 기반 필터 전환 LGTM모집 검색 전용 where를 공통화하고 키셋 조건을 일관되게 적용했습니다. 정렬 키와 커서 조건의 (> / = and id >) 조합도 적절합니다.
Also applies to: 132-161
246-259: 참여한 방(모집/진행/혼합/만료) 상태 기반 전환 적합날짜 비교 제거로 쿼리 단순화와 캐시 적합성이 높아졌습니다. 혼합 조회의 priority(진행=0, 모집=1)도 명료합니다.
Also applies to: 263-277, 281-303, 307-321
166-178: 모집중 추천 방 where 절 보완 적절roomStatus=RECRUITING + 공개방만. 현재 방 제외 조건도 포함되어 있어 중복 노출을 방지합니다.
415-455: 공통 fetch 키셋 구현 일관성 유지(date,id)와 (priority,date,id) 키셋 구현이 명확하고 limit(pageSize+1) 관례를 준수합니다.
Also applies to: 460-504
| // 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리 | ||
| Room room = roomCommandPort.findById(roomJoinCommand.roomId()) | ||
| .orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL)); | ||
|
|
||
| validateRoom(room); | ||
| room.validateRoomRecruitExpired(); | ||
|
|
There was a problem hiding this comment.
JOIN/CANCEL 공통 선검증이 모집기간 만료 체크로 고정되어 취소 흐름을 막을 수 있습니다
현재 changeJoinState 초반의 room.validateRoomRecruitExpired()는 IN_PROGRESS 상태에서의 취소(CANCEL)까지 차단할 위험이 있습니다. 만료(EXPIRED) 차단은 공통, 모집기간(RECRUITING) 여부는 JOIN에만 적용하는 편이 안전합니다.
- // 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리
+ // 방이 존재하지 않거나 만료된 경우 예외 처리
Room room = roomCommandPort.findById(roomJoinCommand.roomId())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL));
-
-room.validateRoomRecruitExpired();
+// 공통: 만료(EXPIRED) 차단
+room.validateRoomExpired();
switch (type) {
- case JOIN -> handleJoin(roomJoinCommand, roomParticipantOptional, room);
+ case JOIN -> handleJoin(roomJoinCommand, roomParticipantOptional, room); // JOIN 내에서 모집기간 검증
case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, roomParticipantOptional, room);
}그리고 handleJoin 내부에서 모집기간 만료 검증을 수행하세요.
private void handleJoin(RoomJoinCommand roomJoinCommand, Optional<RoomParticipant> participantOptional, Room room) {
+ // JOIN 전용: 모집기간(RECRUITING) 상태 검증
+ room.validateRoomRecruitExpired();🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/service/RoomJoinService.java
around lines 37-42, the current common pre-check always calls
room.validateRoomRecruitExpired(), which can incorrectly block CANCEL flows;
remove that expired check from the shared pre-validation and keep only
existence/other neutral checks there, then add a call to
room.validateRoomRecruitExpired() inside the handleJoin method so that EXPIRED
is enforced only for JOIN operations; ensure CANCEL flows do not invoke the
expired check.
There was a problem hiding this comment.
p3 : 현재 api 는 방 참여 상태 변경 api 이므로 아직 모집중인 방에 대해서 유저가 참여신청 or 참여신청 취소 하는 것이 맞는거죠??
코래의 답변은 저희 요구사항에 부합하지 않은 내용이 맞는지 확인차 여쭤봅니다!
There was a problem hiding this comment.
@seongjunnoh 맞습니다! 제가 요구사항을 잘못 이해했네요.
현재 방 참여 상태 변경 API는 모집중인 방(RECRUITING)에서만 JOIN과 CANCEL이 모두 가능해야 하는 것이 맞습니다. 따라서 현재 구현이 올바릅니다:
// 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리
Room room = roomCommandPort.findById(roomJoinCommand.roomId())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL));
room.validateRoomRecruitExpired(); // JOIN, CANCEL 모두 모집중인 방에서만 가능비즈니스 로직상 이유:
- JOIN: 당연히 모집중일 때만 참여 가능
- CANCEL: 모집중일 때 참여신청을 취소하는 것이므로, 이미 진행중(IN_PROGRESS)이거나 만료된(EXPIRED) 방에서는 취소할 이유가 없음
제가 이전에 제안한 내용은 틀렸으니 무시해 주세요. 현재 구현이 요구사항에 정확히 부합합니다.
✏️ Learnings added
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#300
File: src/main/java/konkuk/thip/room/application/service/RoomJoinService.java:37-42
Timestamp: 2025-09-13T06:46:25.061Z
Learning: seongjunnoh 프로젝트에서 방 참여 상태 변경 API(RoomJoinService.changeJoinState)는 모집중인 방(RECRUITING)에서만 JOIN과 CANCEL이 모두 가능하며, 공통 검증에서 room.validateRoomRecruitExpired()를 사용하는 것이 올바른 구현이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
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#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
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.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
There was a problem hiding this comment.
넵 모집중인 방에 한해서만 참여, 참여취소가 가능합니다.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)
153-158: RECORD_PIN에도 ROOM_IS_EXPIRED 추가 필요핀 고정/해제는 쓰기 동작이므로 만료 방에서 금지되어야 합니다. 스웨거와 실제 예외 처리를 일치시켜 주세요.
적용 예시:
RECORD_PIN(new LinkedHashSet<>(Set.of( ROOM_ACCESS_FORBIDDEN, BOOK_NOT_FOUND, RECORD_NOT_FOUND, - RECORD_ACCESS_FORBIDDEN + RECORD_ACCESS_FORBIDDEN, + ROOM_IS_EXPIRED ))),원하시면 관련 서비스 레이어(RecordPinService 등)에도 만료 검증 추가 패치를 제안드릴게요.
115-124: CHANGE_ROOM_LIKE_STATE에도 ROOM_IS_EXPIRED 고려게시글 좋아요 토글 역시 쓰기 동작입니다. 만료 방에서 차단되어야 하므로 스웨거/서비스에 ROOM_IS_EXPIRED를 반영하는 것이 좋습니다.
적용 예시:
CHANGE_ROOM_LIKE_STATE(new LinkedHashSet<>(Set.of( USER_NOT_FOUND, RECORD_NOT_FOUND, VOTE_NOT_FOUND, POST_ALREADY_LIKED, POST_NOT_LIKED_CANNOT_CANCEL, POST_LIKE_COUNT_UNDERFLOW, ROOM_ACCESS_FORBIDDEN, - ROOM_POST_TYPE_NOT_MATCH + ROOM_POST_TYPE_NOT_MATCH, + ROOM_IS_EXPIRED ))),서비스 단(Like 토글)에도 만료 검증이 누락되었는지 점검해 드릴 수 있습니다.
src/main/java/konkuk/thip/roompost/application/service/VoteService.java (2)
42-49: voteId–roomId 및 voteItem–vote 매핑 검증 추가 필요현재 흐름상
voteId와voteItemId가 서로/해당roomId와 일치하는지 직접 보장하지 않습니다. 교차 리소스 업데이트를 차단해야 합니다.간단한 방어 코드 예시:
- if (command.type()) { + // 투표와 방 매핑 확인 + var vote = voteCommandPort.getByIdOrThrow(command.voteId()); + if (!vote.getRoomId().equals(command.roomId())) { + throw new BusinessException(ErrorCode.VOTE_ACCESS_FORBIDDEN); + } + + // 항목과 투표 매핑 확인 + var voteItemForCheck = voteCommandPort.getVoteItemByIdOrThrow(command.voteItemId()); + if (!voteItemForCheck.getVoteId().equals(command.voteId())) { + throw new BusinessException(ErrorCode.VOTE_ITEM_NOT_FOUND); + } + + if (command.type()) { // 투표하기 voteOrUpdate(command.userId(), command.voteId(), command.voteItemId()); } else { // 투표 취소 cancelVote(command.userId(), command.voteItemId()); }추가 fetch 중복을 줄이려면
updateVoteCount에 이미 조회된VoteItem을 전달하도록 시그니처를 조정하는 소규모 리팩터링을 권장합니다.원하시면 포트에
existsVoteItemInVote(itemId, voteId)유틸 쿼리 추가로 비용을 O(1)로 낮추는 방향도 제안드릴게요.Also applies to: 65-74, 76-87
108-116: 동시성 위험 — VoteItem 카운트 갱신이 원자적이지 않음검증 결과: 저장소 전체에서
@Version어노테이션이 발견되지 않았습니다. VoteItem은 src/main/java/konkuk/thip/roompost/domain/VoteItem.java(클래스 선언 ~라인 15)이며 increaseCount()/decreaseCount()는 각각 라인 80,84에 정의되어 있고, VoteService.updateVoteCount(src/main/java/konkuk/thip/roompost/application/service/VoteService.java:108-116)는 read-modify-write 패턴입니다. 이 상태에서는 동시성 레이스로 카운트 손실이 발생할 수 있습니다.권장 조치:
- 우선권: VoteItem 또는 BaseDomainEntity에 JPA
@Version필드 추가(낙관적 락 적용).- 또는 원자적 DB 업데이트로 변경(예: repository/JPQL 또는 네이티브 쿼리로
UPDATE vote_item SET count = count + 1 WHERE id = :id— 낙관적 락을 사용하려면AND version = :version조건 포함).
🧹 Nitpick comments (3)
src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java (2)
3-3: 트랜잭션 애노테이션 통일본 프로젝트 내 다른 서비스(VoteService)는 Spring의
org.springframework.transaction.annotation.Transactional을 사용합니다. 동일하게 통일하는 것을 권장합니다.-import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional;Also applies to: 28-29
20-26: 만료 검증 로직의 일원화(RoomValidator 사용)다른 모듈에서
RoomValidator.validateRoomExpired(roomId)를 쓰는 패턴이 있다면 동일하게 사용해 중복을 줄이는 편이 좋습니다.적용 방향:
- 필드 추가:
private final RoomValidator roomValidator;- 사용처 교체:
roomValidator.validateRoomExpired(command.roomId());필요 시 DI/테스트 수정까지 포함한 전체 패치 제안 가능합니다.
Also applies to: 34-37
src/main/java/konkuk/thip/roompost/application/service/VoteService.java (1)
38-41: RoomValidator 사용으로 중복 제거 가능다른 서비스와 동일하게
RoomValidator.validateRoomExpired(roomId)를 사용하면 도메인 호출/포트 의존이 간결해집니다.적용 시 import/생성자 주입 변경이 필요합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira 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(8 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteService.java(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler의 changeRoomStateToExpired()와 changeRoomStateToProgress()는 서로 다른 날짜 조건으로 완전히 다른 방들을 대상으로 하므로(end_date < today vs start_date <= today AND end_date >= today), 비동기 실행 순서와 관계없이 데이터 정합성에 문제가 없다.
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler에서 changeRoomStateToExpired()와 changeRoomStateToProgress()는 각각 다른 트랜잭션에서 실행되며, 날짜 기반으로 서로 다른 방들을 대상으로 하므로 실행 순서가 바뀌어도 데이터 정합성에 문제가 없다.
⏰ 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 (3)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)
150-152: ROOM_IS_EXPIRED 추가 방향 LGTM만료 방에서의 쓰기 차단 요구사항과 정확히 매칭됩니다. 스웨거 문서화 일관성도 좋아요.
Also applies to: 162-164, 180-182, 186-188, 192-194, 259-261, 272-274, 284-286, 331-333, 343-344
src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java (1)
41-42: 확인됨 — Vote.validateDeletable가 roomId 매핑 및 작성자 검증을 수행합니다.
Vote.validateDeletable(Long userId, Long roomId) 내부에서 validateRoomId(roomId)와 validateCreator(userId)를 호출합니다 (src/main/java/konkuk/thip/roompost/domain/Vote.java).src/main/java/konkuk/thip/roompost/application/service/VoteService.java (1)
38-41: 만료 검증 추가 LGTM쓰기 전 만료 검증 위치 적절합니다.
| // 1.1 방 존재 여부 및 만료 검증 | ||
| Room room = roomCommandPort.getByIdOrThrow(command.roomId()); | ||
| room.validateRoomExpired(); | ||
|
|
There was a problem hiding this comment.
voteId–roomId 정합성 검증 추가 필요
현재는 room 만료/멤버십을 검증한 뒤 voteId로 엔티티를 가져오지만, 해당 투표가 같은 room에 속하는지 직접 검증이 없습니다. 교차 리소스 조작을 막기 위해 필수입니다.
아래와 같이 검증을 추가해 주세요:
// 1.1 방 존재 여부 및 만료 검증
- Room room = roomCommandPort.getByIdOrThrow(command.roomId());
- room.validateRoomExpired();
+ Room room = roomCommandPort.getByIdOrThrow(command.roomId());
+ room.validateRoomExpired();
// 2. 투표 조회 및 검증
Vote vote = voteCommandPort.getByIdOrThrow(command.voteId());
+ if (!vote.getRoomId().equals(command.roomId())) {
+ throw new konkuk.thip.common.exception.BusinessException(
+ konkuk.thip.common.exception.code.ErrorCode.VOTE_ACCESS_FORBIDDEN
+ );
+ }필요 시 상단 import도 추가하세요:
+import konkuk.thip.common.exception.BusinessException;
+import konkuk.thip.common.exception.code.ErrorCode;원하시면 포트 레벨에 getByIdAndRoomIdOrThrow(voteId, roomId) 메서드 도입으로 보일러플레이트를 줄이는 패치도 제안드리겠습니다.
Also applies to: 39-41
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java
around lines 34-37 (and similarly 39-41), after loading Room and Vote entities
add an explicit validation that the Vote's roomId equals the current Room's id
and throw an appropriate NotFound/BadRequest (or domain) exception when they
differ; implement this by comparing vote.getRoomId() (or vote.roomId()) to
room.getId() and raising the same type of exception used elsewhere for invalid
cross-resource access, or alternatively introduce and call a port method
getByIdAndRoomIdOrThrow(voteId, roomId) to fetch/validate atomically and reduce
boilerplate.
There was a problem hiding this comment.
확인했습니다! 번거로운 작업인데 꼼꼼히 해주신것 같네요!!
만료된 방은 조회만 할 수 있도록 해주신것도 확인했습니다
그런데 지금 방 조회와 관련된 코드에서 startDate, endDate 와 LocalDate.now() 로 비교하여 해당 방의 진행 상태를 구분하던 것을 RoomStatus 의 값으로 비교하도록 수정해주셨는데,
추가로 RoomQueryDto 에도 RoomStatus 필드를 추가하는건 어떤가요??
RoomQueryDto 로 조회한 방의 진행 상태를 명시적으로 알 수 없어서, 서비스 레이어에서 dto.startDate 의 값과 현재 시각을 비교하는 로직이 있는데, dto에 RoomStatus 필드가 추가되면 훨씬 가독성이 좋은 코드로 리펙할 수 있을것 같아서 제안드립니다! (RoomShowMineService 코드 참고 부탁드립니다!!)
기존 방 조회 queryDsl 코드에서 굳이 dto에 roomStatus 값이 필요없을 수 있으니(= 애초에 특정 방 상태만을 조회할 수도 있으므로), 해당 필드를 nullable 로 설정하고, QueryProjection 생성자를 추가하면 기존 코드의 수정을 최소화하면서 유연하게 변경 가능할 것 같습니다!!
| @HelperService | ||
| @RequiredArgsConstructor | ||
| public class RoomValidator { | ||
|
|
||
| private final RoomCommandPort roomCommandPort; | ||
|
|
||
| public void validateRoomExpired(Long roomId) { | ||
| Room room = roomCommandPort.getByIdOrThrow(roomId); | ||
| room.validateRoomExpired(); | ||
| } |
| public boolean isRecruitmentPeriodExpired() { | ||
| LocalDate today = LocalDate.now(); | ||
| // 모집 마감일: startDate.minusDays(1) | ||
| return today.isAfter(this.startDate.minusDays(1)); | ||
| // LocalDate today = LocalDate.now(); | ||
| // // 모집 마감일: startDate.minusDays(1) | ||
| // return today.isAfter(this.startDate.minusDays(1)); | ||
| return this.roomStatus != RoomStatus.RECRUITING; |
There was a problem hiding this comment.
기존 코드 주석이 아니라 지워주시는게 좋을 것 같습니다!!
There was a problem hiding this comment.
넵 그 주석 부분은 리뷰에서 코드 변경사항 확인하기 편하도록 + 혹시나 리뷰에서 정적 필터링, 동적 필터링 모두 사용하자 라는 의견이 나올 수도 있을때를 대비해서 남겨두었습니다. 리뷰 모두 마치면 지우도록 하겠습니다~
| // 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리 | ||
| Room room = roomCommandPort.findById(roomJoinCommand.roomId()) | ||
| .orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL)); | ||
|
|
||
| validateRoom(room); | ||
| room.validateRoomRecruitExpired(); | ||
|
|
There was a problem hiding this comment.
p3 : 현재 api 는 방 참여 상태 변경 api 이므로 아직 모집중인 방에 대해서 유저가 참여신청 or 참여신청 취소 하는 것이 맞는거죠??
코래의 답변은 저희 요구사항에 부합하지 않은 내용이 맞는지 확인차 여쭤봅니다!
| public void validatePostLikeAccess(CountUpdatable post, Long userId) { | ||
| RoomPost roomPost = (RoomPost) post; | ||
| roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); | ||
| roomValidator.validateRoomExpired(roomPost.getRoomId()); |
| // where.and(room.startDate.loe(LocalDate.now())); | ||
| // where.and(room.endDate.goe(LocalDate.now())); | ||
| where.and(room.roomStatus.eq(RoomStatus.IN_PROGRESS)); // 활동 기간 중인 방만: IN_PROGRESS 상태 |
There was a problem hiding this comment.
이것도 기존 코드를 주석이 아니라 삭제하는건 어떤가요??
@buzz0331 오 그렇네요 핀, 좋아요 같은 쓰기 작업이 누락된 것 같습니다!! |
엇 핀같은 경우는 단순히 자신의 피드로 정보를 가져오는 것이라 만료된 방에 제한을 두지 않았습니다! 좋아요 같은 경우는 이미 수정이 되어 있는 상황입니다! |
hd0rable
left a comment
There was a problem hiding this comment.
번거로운 작업 너무 수고하셨습니다!! 기록 생성시에 방 상태 검증 수정이 누락된것같습니다!! 사실 누락은아니고 투표/기록 생성시 방검증 코드는 똑같은데 기록생성시에는 따로 서비스 메서드로 한번더 검증하시길래 혹시 누락된것일까봐 ㅎㅎ,,, 의도한것이라면 수정하지않아도될것같습니다!!
관련하여서 모집중인 방일경우에도 검증이 필요한것같아 리뷰 간단히 남겨보았는데 확인해주시면 감사하겠습니다!!
| public void validateCommentAccess(CountUpdatable post, Long userId) { | ||
| RoomPost roomPost = (RoomPost) post; | ||
| roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); | ||
| roomValidator.validateRoomExpired(roomPost.getRoomId()); |
| roomStatus = RoomStatus.IN_PROGRESS; | ||
| } | ||
|
|
||
| public void validateRoomExpired() { |
There was a problem hiding this comment.
p2: 지금 모든 방 게시물 관련 활동할때 방 상태 검증에서 방 검증을 만료될 경우에만 하고있는데 모집중일 경우에도 검증이 필요하지않을까요?? (방 참여 상태 변경 제외)
추가적으로 기록 생성시에 방을 검증하는 코드수정이 누락된것같습니다!!
|
@seongjunnoh @hd0rable 리뷰 반영 마쳤습니다. 희진님이 말씀하신 모집 중인 경우의 예외처리는 진행 중 상태가 아닌 경우를 검증해서 '진행 중인 방이 아닙니다'라는 예외 메시지가 나오는 것이 더 자연스러울 것 같아서 그렇게 예외처리되도록 하였습니다. (방 만료상태의 경우는 방 만료 예외 메시지가 뜨게끔 했습니다.) |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)
115-125: 좋아요 토글(CHANGE_ROOM_LIKE_STATE)에 방 상태 예외 누락쓰기 동작이라면 상태 검증 결과를 명세에 반영해야 합니다. 최소 ROOM_NOT_IN_PROGRESS(또는 정책에 따라 ROOM_IS_EXPIRED) 추가해 주세요.
적용 예시(diff):
CHANGE_ROOM_LIKE_STATE(new LinkedHashSet<>(Set.of( USER_NOT_FOUND, RECORD_NOT_FOUND, VOTE_NOT_FOUND, POST_ALREADY_LIKED, POST_NOT_LIKED_CANNOT_CANCEL, POST_LIKE_COUNT_UNDERFLOW, ROOM_ACCESS_FORBIDDEN, - ROOM_POST_TYPE_NOT_MATCH + ROOM_POST_TYPE_NOT_MATCH, + ROOM_IS_EXPIRED, + ROOM_NOT_IN_PROGRESS ))),
155-161: RECORD_PIN에 방 상태 예외(ROOM_IS_EXPIRED, ROOM_NOT_IN_PROGRESS) 누락 — 서비스 검증 및 스웨거 문서화 필요
- Swagger: src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java 의 RECORD_PIN에 ROOM_IS_EXPIRED, ROOM_NOT_IN_PROGRESS 추가.
RECORD_PIN(new LinkedHashSet<>(Set.of( ROOM_ACCESS_FORBIDDEN, BOOK_NOT_FOUND, RECORD_NOT_FOUND, RECORD_ACCESS_FORBIDDEN, + ROOM_IS_EXPIRED, + ROOM_NOT_IN_PROGRESS ))),
- 서비스: src/main/java/konkuk/thip/roompost/application/service/RecordPinService.java 의 pinRecord(...)에 방 상태 검증 추가. 예: roomParticipantValidator.validateUserIsRoomMember(...) 직후에 roomValidator.validateRoomInProgress(query.roomId()) 호출(또는 Room 조회 후 room.validateRoomInProgress()).
정책상 핀을 허용할 경우에는 스웨거에 “만료 방에서도 핀 허용”을 명시하고 상태 관련 예외 코드를 제거해 문서·동작 일관성 확보.
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (1)
31-38: 교차 리소스 삭제 방지: attendanceCheck의 roomId 일치 검증 추가attendanceCheckId만으로 삭제 시 다른 방 리소스를 조작할 여지가 있습니다. 조회 후 roomId 일치 여부를 확인하세요.
적용 예시:
- AttendanceCheck attendanceCheck = attendanceCheckCommandPort.getByIdOrThrow(attendanceCheckId); + AttendanceCheck attendanceCheck = attendanceCheckCommandPort.getByIdOrThrow(attendanceCheckId); + if (!attendanceCheck.getRoomId().equals(roomId)) { + throw new InvalidStateException(ErrorCode.ROOM_POST_MISMATCH); + }src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (2)
35-43: Record-방 매핑 검증 누락: 교차 업데이트 차단 필요recordId가 전달된 roomId에 속하는지 확인이 없습니다. 방어 로직 추가를 권장합니다.
Record record = recordCommandPort.getByIdOrThrow(command.postId()); + if (!record.getRoomId().equals(command.roomId())) { + throw new InvalidStateException(ErrorCode.ROOM_POST_MISMATCH); + }
55-63: Vote-방 매핑 검증 누락voteId가 전달된 roomId에 속하는지 확인이 없습니다. 동일 이슈로 교차 업데이트가 가능해집니다.
Vote vote = voteCommandPort.getByIdOrThrow(command.postId()); + if (!vote.getRoomId().equals(command.roomId())) { + throw new InvalidStateException(ErrorCode.ROOM_POST_MISMATCH); + }src/main/java/konkuk/thip/roompost/application/service/VoteService.java (2)
65-74: 매핑 검증 추가: voteId ↔ roomId, voteItemId ↔ voteId현재 voteId와 voteItemId의 상호 연관성 검증이 없어 교차 리소스 조작 위험이 있습니다(특히 cancel 경로).
적용 예시(핵심 아이디어, 포트 존재 여부에 맞춰 조정 필요):
public VoteResult vote(VoteCommand command) { roomParticipantValidator.validateUserIsRoomMember(command.roomId(), command.userId()); Room room = roomCommandPort.getByIdOrThrow(command.roomId()); room.validateRoomInProgress(); + + // 1.2 리소스 매핑 검증 + var vote = voteCommandPort.getVoteByIdOrThrow(command.voteId()); + if (!vote.getRoomId().equals(command.roomId())) { + throw new BusinessException(ErrorCode.ROOM_POST_MISMATCH); + } + var voteItem = voteCommandPort.getVoteItemByIdOrThrow(command.voteItemId()); + if (!voteItem.getVoteId().equals(command.voteId())) { + throw new BusinessException(ErrorCode.VOTE_ITEM_MISMATCH); + }cancelVote 경로에서도 userId+voteItemId 조회 전/후에 voteId 일치 확인을 추가해 주세요(예: participant 조회 후 voteItemId → voteItem → voteId 비교).
108-116: 치명적: VoteItem 카운트 동시성(RMW) 취약 — 즉시 수정 필요검증: 제출된 검색 결과에서 VoteItem 및 관련 베이스 엔티티에 @Version 어노테이션이 발견되지 않았습니다(예: src/main/java/konkuk/thip/roompost/domain/VoteItem.java 등). 현재 updateVoteCount는 read-modify-write 패턴으로 카운트 유실 가능.
- 권장 조치(대안 A, 우선 적용): 원자적 증가/감소 쿼리로 변경.
- private void updateVoteCount(Long voteItemId, boolean isIncrease) { - VoteItem voteItem = voteCommandPort.getVoteItemByIdOrThrow(voteItemId); - if (isIncrease) { - voteItem.increaseCount(); - } else { - voteItem.decreaseCount(); - } - voteCommandPort.updateVoteItem(voteItem); - } + private void updateVoteCount(Long voteItemId, boolean isIncrease) { + int delta = isIncrease ? 1 : -1; + voteCommandPort.incrementVoteItemCount(voteItemId, delta); // JPQL/네이티브: update vote_item set count = count + :delta where id = :id + }
- 대안 B: VoteItem 엔티티(또는 공통 베이스)에 @Version 필드 추가 후 낙관적 락 처리와 재시도 로직(OptimisticLockException 처리) 구현.
수정 위치: src/main/java/konkuk/thip/roompost/application/service/VoteService.java (108–116) 및 관련 VoteCommandPort/Repository 구현.
♻️ Duplicate comments (3)
src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java (1)
39-41: voteId–roomId 매핑 검증이 보이지 않습니다(교차 자원 삭제 가능성).현재는 vote를 ID로만 조회한 뒤 삭제합니다. 다른 room의 vote를 조작할 수 없도록 roomId 일치 여부를 명시적으로 확인하세요. 과거 코멘트와 동일 이슈입니다.
// 2. 투표 조회 및 검증 - Vote vote = voteCommandPort.getByIdOrThrow(command.voteId()); + Vote vote = voteCommandPort.getByIdOrThrow(command.voteId()); + if (!vote.getRoomId().equals(command.roomId())) { + // 접근 제어 또는 잘못된 요청으로 분류 — 기존 사용 코드에 맞춰 예외 선택 + throw new konkuk.thip.common.exception.BusinessException( + konkuk.thip.common.exception.code.ErrorCode.VOTE_ACCESS_FORBIDDEN + ); + } // 2-1. 투표 삭제 권한 검증 vote.validateDeletable(command.userId(),command.roomId());포트 레벨에
getByIdAndRoomIdOrThrow(voteId, roomId)를 도입하면 보일러플레이트를 줄일 수 있습니다.src/main/java/konkuk/thip/room/domain/Room.java (1)
143-148: 주석 처리된 과거 코드 제거이미 상태 기반으로 전환되었으므로 과거 날짜 로직 주석은 혼란만 유발합니다. 머지 전 제거해 주세요.
-// LocalDate today = LocalDate.now(); -// -// // 모집 마감일: startDate.minusDays(1) -// -// return today.isAfter(this.startDate.minusDays(1));src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (1)
206-209: 주석 처리된 과거 날짜 조건은 삭제하고 git 히스토리에 맡기기이미 status 기반으로 이행했으므로, 남은 주석은 소음만 유발합니다. 이전 코드는 git으로 추적 가능합니다.
-// where.and(room.startDate.loe(LocalDate.now())); -// where.and(room.endDate.goe(LocalDate.now())); -// .and(room.startDate.after(today)); // 유저가 참여한 방 && 모집중인 방 -// .and(room.startDate.loe(today)) -// .and(room.endDate.goe(today)); // 유저가 참여한 방 && 현재 진행중인 방 -// BooleanExpression playing = room.startDate.loe(today).and(room.endDate.goe(today)); -// BooleanExpression recruiting = room.startDate.after(today); -// .and(room.endDate.before(today)); // 유저가 참여한 방 && 만료된 방 -// .and(room.startDate.after(LocalDate.now())); // 모집 마감 시각 > 현재 시각 -// .and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각Also applies to: 252-254, 269-272, 286-290, 313-315, 368-370, 398-400
🧹 Nitpick comments (18)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)
132-141: ROOM_IS_EXPIRED와 ROOM_NOT_IN_PROGRESS의 병기 — 중복·모호성 제거 필요여러 엔드포인트에 두 에러가 동시에 나열되어 있습니다. 실제 구현이 “진행 중(IN_PROGRESS)에서만 쓰기 허용”이라면, 쓰기 동작들의 실패 코드는 ROOM_NOT_IN_PROGRESS 하나로 충분합니다. 반대로 만료만 제한해야 하는 경로가 있다면 ROOM_IS_EXPIRED만 남기는 식으로 각 엔드포인트별 정책을 명확히 해 주세요. 현재처럼 병기하면 클라이언트가 어떤 코드를 기대해야 하는지 불명확합니다.
Also applies to: 148-154, 161-167, 170-178, 179-187, 188-194, 195-201, 258-269, 270-283, 284-296, 336-344, 350-356
src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (1)
63-67: 검증 순서 미세 조정 제안(선 상태, 후 레코드 검증)실패를 빠르게 반환하고 불필요한 메시지/로깅을 줄이려면 방 상태 검증을 가장 먼저 수행하는 것이 낫습니다.
- validateRoom(room); - validateRoomParticipant(roomParticipant, command.isOverview()); - validateRecord(record, book); + validateRoom(room); // IN_PROGRESS 선확인 + validateRoomParticipant(roomParticipant, command.isOverview()); + validateRecord(record, book);src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java (1)
54-59: 검증 순서 정렬(선 상태, 후 도메인 검증)으로 불필요 연산 최소화방 상태부터 확인하면 책/페이지 검증 이전에 요청을 빠르게 거절할 수 있습니다.
- Book book = bookCommandPort.findById(room.getBookId()); - validateVote(vote, book); - room.validateRoomInProgress(); + room.validateRoomInProgress(); + Book book = bookCommandPort.findById(room.getBookId()); + validateVote(vote, book);src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java (2)
3-3: 트랜잭션 애노테이션 통일 권장본 프로젝트 내 다른 서비스들은
org.springframework.transaction.annotation.Transactional을 사용합니다. 본 파일만jakarta.transaction.Transactional을 쓰고 있어 속성/롤백 정책 혼선을 야기할 수 있습니다. 스프링 애노테이션으로 통일해 주세요.-import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional;
34-36: 주석 용어 정합성“만료 검증”이라는 주석이지만 실제 코드는 “진행 중” 검증입니다. 오독을 막기 위해 주석을 업데이트해 주세요.
- // 1.1 방 존재 여부 및 만료 검증 + // 1.1 방 존재 여부 및 진행 중(IN_PROGRESS) 상태 검증src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java (1)
14-22: 방 진행중 검증 추가 좋습니다.댓글 작성/수정/삭제의 쓰기 동작에 대해 IN_PROGRESS 검증이 일관되게 적용됩니다. 다른 서비스들(예: AttendanceCheck/Vote/Record)도 가능하면 RoomValidator로 통일하면 가독성과 재사용성이 좋아집니다.
src/main/java/konkuk/thip/room/application/service/validator/RoomValidator.java (1)
14-17: 도메인 규칙의 외부 진입점 캡슐화 👍진행중 검증을 포트 뒤로 숨겨 재사용하기 좋습니다. 추후 RECRUITING 전용 검증(예: validateRecruiting)도 여기에 추가해 일관성을 유지하는 것을 권장합니다.
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (2)
27-30: Room 조회+검증을 RoomValidator로 교체해 중복 제거 권장현재 직접 조회 후 room.validateRoomInProgress() 호출합니다. 다른 클래스들과 일관성을 위해 RoomValidator 사용을 권장합니다.
적용 예시:
- // 1-1. 방이 만료되었는지 검증 - Room room = roomCommandPort.getByIdOrThrow(roomId); - room.validateRoomInProgress(); + // 1-1. 방이 진행중인지 검증 + roomValidator.validateRoomInProgress(roomId);또한 필드/생성자에 RoomValidator 주입으로 교체 필요합니다.
- private final RoomCommandPort roomCommandPort; + private final RoomValidator roomValidator;
31-33: 주석 오타 수정"creator 겁증" → "creator 검증".
- // 2. creator 겁증 + // 2. creator 검증src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (2)
32-34: Room 검증 유틸 통일(Validator 사용) 및 네이밍 개선
- RoomCommandPort 직접 사용 대신 RoomValidator 사용 권장.
- validateRoom 메서드 파라미터명을 Long command → Long roomId로 명확히.
적용 예시:
- private final RoomCommandPort roomCommandPort; + private final RoomValidator roomValidator; ... - validateRoom(command.roomId()); + validateRoom(command.roomId()); ... - private void validateRoom(Long command) { - Room room = roomCommandPort.getByIdOrThrow(command); - room.validateRoomInProgress(); + private void validateRoom(Long roomId) { + roomValidator.validateRoomInProgress(roomId); }
66-69: 메서드 시그니처 네이밍 정정Long command → Long roomId 로 의미를 명확히 하세요.
- private void validateRoom(Long command) { - Room room = roomCommandPort.getByIdOrThrow(command); + private void validateRoom(Long roomId) { + Room room = roomCommandPort.getByIdOrThrow(roomId);src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java (1)
45-46: 주석 용어 통일"피드 게시물 좋아요" → "기록 게시물 좋아요"가 문맥상 자연스럽습니다.
- // 3-2. 피드 게시물 좋아요 삭제 + // 3-2. 기록 게시물 좋아요 삭제src/main/java/konkuk/thip/room/domain/Room.java (1)
132-141: 모집 마감 메시지의 기준일 혼선 가능성validateRoomRecruitExpired에서 메시지에 startDate-1을 사용합니다. startRoomProgress가 startDate를 변경하면 메시지 날짜가 왜곡됩니다. 위 제안대로 startDate를 보존하거나, 메시지용 deadline을 별도 필드/계산으로 유지하세요.
src/main/java/konkuk/thip/room/application/port/out/dto/RoomQueryDto.java (1)
34-45: endDate 슬롯에 ‘모집 마감일(=startDate)’을 싣는 설계, 코드로 명확화현재 QRoomQueryDto 오버로드(7/8 인자)는 startDate를 endDate 자리에 넣어 “deadline” 의미로 전달합니다. 혼동을 줄이기 위해 파라미터명을 deadline으로 바꾸는 소폭 리팩터를 제안합니다. (바이트코드 시그니처 불변)
@QueryProjection public RoomQueryDto( Long roomId, String bookImageUrl, String roomName, Integer recruitCount, Integer memberCount, - LocalDate endDate, + LocalDate deadline, RoomStatus roomStatus ) { - this(roomId, bookImageUrl, roomName, recruitCount, memberCount, null, endDate, null, roomStatus); + this(roomId, bookImageUrl, roomName, recruitCount, memberCount, null, deadline, null, roomStatus); } // 방 검색 시 활용 @QueryProjection public RoomQueryDto( Long roomId, String bookImageUrl, String roomName, Integer recruitCount, Integer memberCount, - LocalDate endDate, + LocalDate deadline, Boolean isPublic, RoomStatus roomStatus ) { - this(roomId, bookImageUrl, roomName, recruitCount, memberCount, null, endDate, isPublic, roomStatus); + this(roomId, bookImageUrl, roomName, recruitCount, memberCount, null, deadline, isPublic, roomStatus); }Also applies to: 49-60
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (4)
78-88: 프로젝션의 ‘deadline(endDate 슬롯)’ 의미를 주석으로 고정recruiting 검색 프로젝션에서 endDate 슬롯에 startDate(=모집 마감 의미)를 넣는 점을 코드로 명확히 남겨 두는 것을 권장합니다.
private QRoomQueryDto projectionForRecruitingRoomSearch() { - return new QRoomQueryDto( + // NOTE: RoomQueryDto.endDate 슬롯에는 '모집 마감일(=room.startDate)'을 담습니다. + return new QRoomQueryDto( room.roomId, book.imageUrl, room.title, room.recruitCount, room.memberCount, room.startDate, room.isPublic, room.roomStatus ); }
250-250: 미사용 변수 today 제거컴파일러/정적 분석 경고 제거 및 가독성 정리.
-LocalDate today = LocalDate.now();Also applies to: 267-267, 285-285, 311-311
54-58: 키워드 검색 성능 개선 제안(Like %keyword%)containsIgnoreCase는 선행 와일드카드로 인덱스를 타지 않습니다. 트라이그램(pg_trgm) 또는 FTS로 전환을 검토해 주세요. 최소안으로 title_lower 컬럼+접두사 매칭으로 UX를 유지하는 방법도 있습니다.
- Postgres 권장 인덱스 예: CREATE INDEX CONCURRENTLY idx_room_title_trgm ON room USING gin (title gin_trgm_ops);
- 책 제목(book.title)에도 동일 적용.
22-23: RoomStatus 전면 도입에 따른 인덱스 전략 보완상태 기반 조회·정렬이 핵심이므로 아래 인덱스를 고려해 주세요.
- room(room_status, start_date, room_id)
- room(category, room_status, start_date, room_id)
- room(book_isbn, room_status, start_date, room_id)
- room(room_status, member_count DESC, room_id) — 인기순 페이지 대비
- room(is_public, room_status) — 공개 필터 공통 최적화
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (17)
src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java(2 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(1 hunks)src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java(9 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java(20 hunks)src/main/java/konkuk/thip/room/application/port/out/dto/RoomQueryDto.java(4 hunks)src/main/java/konkuk/thip/room/application/service/validator/RoomValidator.java(1 hunks)src/main/java/konkuk/thip/room/domain/Room.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java(2 hunks)src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java(1 hunks)src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java(4 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java(1 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/VoteService.java(2 hunks)src/main/java/konkuk/thip/roompost/application/service/policy/RoomPostLikeAccessPolicy.java(2 hunks)src/test/java/konkuk/thip/roompost/application/service/VoteServiceTest.java(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckCreateService.java
- src/main/java/konkuk/thip/roompost/application/service/policy/RoomPostLikeAccessPolicy.java
- src/test/java/konkuk/thip/roompost/application/service/VoteServiceTest.java
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-13T06:46:25.061Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#300
File: src/main/java/konkuk/thip/room/application/service/RoomJoinService.java:37-42
Timestamp: 2025-09-13T06:46:25.061Z
Learning: seongjunnoh 프로젝트에서 방 참여 상태 변경 API(RoomJoinService.changeJoinState)는 모집중인 방(RECRUITING)에서만 JOIN과 CANCEL이 모두 가능하며, 공통 검증에서 room.validateRoomRecruitExpired()를 사용하는 것이 올바른 구현이다.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.javasrc/main/java/konkuk/thip/roompost/application/service/RecordCreateService.javasrc/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.javasrc/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.javasrc/main/java/konkuk/thip/room/domain/Room.java
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.
Applied to files:
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.javasrc/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java
📚 Learning: 2025-09-05T04:11:01.456Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler의 changeRoomStateToExpired()와 changeRoomStateToProgress()는 서로 다른 날짜 조건으로 완전히 다른 방들을 대상으로 하므로(end_date < today vs start_date <= today AND end_date >= today), 비동기 실행 순서와 관계없이 데이터 정합성에 문제가 없다.
Applied to files:
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.javasrc/main/java/konkuk/thip/room/domain/Room.java
📚 Learning: 2025-08-14T09:27:36.450Z
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.450Z
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/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java
📚 Learning: 2025-09-05T04:11:01.456Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/common/scheduler/RoomStateScheduler.java:20-0
Timestamp: 2025-09-05T04:11:01.456Z
Learning: RoomStateScheduler에서 changeRoomStateToExpired()와 changeRoomStateToProgress()는 각각 다른 트랜잭션에서 실행되며, 날짜 기반으로 서로 다른 방들을 대상으로 하므로 실행 순서가 바뀌어도 데이터 정합성에 문제가 없다.
Applied to files:
src/main/java/konkuk/thip/room/domain/Room.java
⏰ 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 (8)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)
104-104: 오류 코드 추가 자체는 적절합니다 — 용어·사용처 일관성만 확인해 주세요ROOM_NOT_IN_PROGRESS(100010, BAD_REQUEST) 추가는 상태 기반 검증과 잘 맞습니다. 다만 동일 PR에서 ROOM_IS_EXPIRED와 혼용되고 있어, 어느 케이스에 어떤 코드를 던질지 서비스 전반의 기준을 한 번 더 정리해 주세요(예: 쓰기 동작은 전부 ROOM_NOT_IN_PROGRESS로 단일화 등). 스웨거와 실제 예외 사용처가 불일치하면 클라이언트 대응이 어려워집니다.
src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (1)
100-103: 진행 중 상태 검증 추가 LGTMRoomStatus 기반으로 쓰기 차단 지점을 앞단에서 명확히 막고 있어 좋습니다.
src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java (1)
58-59: 진행 중 상태 검증 추가 LGTM투표 생성 경로에서도 일관되게 IN_PROGRESS를 선행 검증하는 방향이 맞습니다.
src/main/java/konkuk/thip/roompost/application/service/VoteDeleteService.java (1)
34-37: IN_PROGRESS 검증 추가 LGTM삭제도 쓰기 흐름이므로 상태 선검증이 맞습니다.
src/main/java/konkuk/thip/roompost/application/service/VoteService.java (1)
38-41: 진행중 검증 추가는 적절합니다만료/미진행 방에서의 투표 쓰기 동작 차단이 명확해졌습니다.
src/main/java/konkuk/thip/roompost/application/service/RecordDeleteService.java (2)
34-36: 진행중 검증 추가 👍삭제와 같은 쓰기 동작 차단이 명확합니다.
37-41: 검증 불필요 — Record.validateDeletable이 이미 roomId 매칭을 검증합니다Record.validateDeletable(Long userId, Long roomId)는 validateRoomId(roomId)와 validateCreator(userId)를 호출해 roomId 불일치 및 작성자 권한을 검증합니다. (src/main/java/konkuk/thip/roompost/domain/Record.java) 관련 단위테스트도 존재합니다.
Likely an incorrect or invalid review comment.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (1)
98-101: 검색 결과에 비공개 방 노출 위험: isPublic 필터 누락모집중 방 검색 API 4곳과 ISBN 기반 목록에서 isPublic 조건이 빠져 비공개 방이 노출될 수 있습니다. 공개 목록이라면 명시적으로 차단해야 합니다.
빠른 최소 수정안(각 메서드 내 where에 한 줄 추가):
// findRecruitingRoomsOrderByStartDateAsc BooleanBuilder where = recruitingActiveWhere(); applyKeyword(where, keyword); +where.and(room.isPublic.isTrue()); applyCursorStartDateAsc(where, cursorExpr, lastStartDate, roomId); // findRecruitingRoomsWithCategoryOrderByStartDateAsc BooleanBuilder where = recruitingActiveWhere(); applyCategory(where, category); applyKeyword(where, keyword); +where.and(room.isPublic.isTrue()); applyCursorStartDateAsc(where, cursorExpr, lastStartDate, roomId); // findRecruitingRoomsOrderByMemberCountDesc BooleanBuilder where = recruitingActiveWhere(); applyKeyword(where, keyword); +where.and(room.isPublic.isTrue()); applyCursorMemberCountDesc(where, lastMemberCount, roomId); // findRecruitingRoomsWithCategoryOrderByMemberCountDesc BooleanBuilder where = recruitingActiveWhere(); applyCategory(where, category); applyKeyword(where, keyword); +where.and(room.isPublic.isTrue()); applyCursorMemberCountDesc(where, lastMemberCount, roomId); // findRoomsByIsbnOrderByStartDateAsc BooleanExpression baseCondition = room.bookJpaEntity.isbn.eq(isbn) .and(room.roomStatus.eq(RoomStatus.RECRUITING)); +baseCondition = baseCondition.and(room.isPublic.isTrue()); if (dateCursor != null && roomIdCursor != null) { baseCondition = baseCondition.and(cursorExpr.gt(dateCursor) .or(cursorExpr.eq(dateCursor).and(room.roomId.gt(roomIdCursor)))); }대안: recruitingActiveWhere()에 isPublic.isTrue()를 포함시키는 리팩터도 가능합니다(해당 헬퍼가 검색 전용으로만 쓰인다는 전제 확인 필요).
Also applies to: 116-120, 133-136, 149-153, 365-376
⛔ Skipped due to learnings
Learnt from: seongjunnoh PR: THIP-TextHip/THIP-Server#287 File: src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java:37-41 Timestamp: 2025-09-01T10:29:31.377Z Learning: seongjunnoh 프로젝트에서 방 검색 시에는 공개방과 비공개방을 모두 포함하여 노출하는 것이 요구사항이다. isPublic 필터는 추천/인기방 등 특정 컨텍스트에서만 적용된다.
src/main/java/konkuk/thip/room/application/port/out/dto/RoomQueryDto.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (4)
134-146: 기본 RoomStatus를 IN_PROGRESS로 고정 OK, 다만 LocalDate.now() 자정 경계 니트픽createRoom 기본값은 요구사항과 일치합니다. 단, 자정 경계/타임존에 따라 드물게 플레이키해질 수 있으니 필요 시 고정 기준일(예: TEST_TODAY) 또는 Clock 주입/오버로드 사용을 고려해 주세요.
148-160: 시그니처 확장 👍 — 이전 호출부 호환용 오버로드 유지 제안테스트들이 모두 수정되었더라도, 호출부 churn을 줄이기 위해 이전 시그니처(4파라미터)를 유지하는 얇은 오버로드를 추가하면 이득이 큽니다.
다음 diff를 추가하면 기존 호출부를 안전하게 수용할 수 있습니다:
@@ public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, LocalDate startDate, LocalDate endDate, RoomStatus roomStatus) { return RoomJpaEntity.builder() ... .roomStatus(roomStatus) .build(); } + + // 호환 오버로드: 상태 미지정 시 기본 IN_PROGRESS + public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, LocalDate startDate, LocalDate endDate) { + return createCustomRoom(book, category, startDate, endDate, RoomStatus.IN_PROGRESS); + }
162-174: 이름 포함 오버로드도 동일하게 호환 오버로드 추가 권장이전(5파라미터) 시그니처 호출부를 흡수하는 얇은 오버로드를 제공하면 마이그레이션 비용을 줄일 수 있습니다.
@@ public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, String roomName, LocalDate startDate, LocalDate endDate, RoomStatus roomStatus) { return RoomJpaEntity.builder() ... .roomStatus(roomStatus) .build(); } + + // 호환 오버로드: 상태 미지정 시 기본 IN_PROGRESS + public static RoomJpaEntity createCustomRoom(BookJpaEntity book, Category category, String roomName, LocalDate startDate, LocalDate endDate) { + return createCustomRoom(book, category, roomName, startDate, endDate, RoomStatus.IN_PROGRESS); + }
148-160: 테스트 가독성/일관성 향상을 위한 상태별 편의 팩토리 제안상태-날짜 일관성이 필요한 테스트에서 반복 코드가 생길 수 있습니다. 예: “오늘 시작·N일 뒤 종료의 진행 중 방”, “어제 종료된 만료 방” 등 의미 있는 헬퍼(예: createInProgressRoomTodayNDays, createExpiredRoomSinceYesterday 등)를 추가하면 테스트 의도가 더 분명해집니다.
Also applies to: 162-174
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java(4 hunks)
⏰ 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 (2)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (2)
21-21: RoomStatus import 추가 적절상태 기반 테스트 생성을 위한 준비로 타당합니다.
148-160: 레거시 createCustomRoom(4/5-파라미터) 호출 잔존 여부 — 수동 검증 필요rg가 워크스페이스 파일을 검색하지 못해 자동 검사 실패. 리포지토리 루트에서 아래 명령 중 하나를 실행한 출력 결과를 올려라.
rg --debug -n 'createCustomRoom' || true rg -nP '\bcreateCustomRoom\s*\(\s*([^,]+,\s*){3}[^)]*\)' || true # 4-파라미터 호출(콤마 3개) rg -nP '\bcreateCustomRoom\s*\(\s*([^,]+,\s*){4}[^)]*\)' || true # 5-파라미터 호출(콤마 4개) rg -nP 'createCustomRoom\s*\([^)]*\)' | rg -nPv 'RoomStatus\s*\.' || true # RoomStatus 리터럴 미포함 호출(휴리스틱)
| ROOM_MEMBER_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 100007, "방의 인원 수가 1 이하(방장 포함)입니다."), | ||
| ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, "방이 만료되었습니다."), | ||
| ROOM_POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 100009, "일치하는 방 게시물 타입 이름이 없습니다. [RECORD, VOTE] 중 하나여야 합니다."), | ||
| ROOM_NOT_IN_PROGRESS(HttpStatus.BAD_REQUEST, 100010, "진행 중인 방이 아닙니다."), |
| LocalDate endDate, // 방 진행 마감일 or 방 모집 마감일 | ||
| Boolean isPublic // 공개방 여부 | ||
| Boolean isPublic, // 공개방 여부 | ||
| RoomStatus roomStatus |
| if (this.roomStatus == RoomStatus.EXPIRED) { | ||
| throw new InvalidStateException(ErrorCode.ROOM_IS_EXPIRED); | ||
| } | ||
| if (this.roomStatus != RoomStatus.IN_PROGRESS) { | ||
| throw new InvalidStateException(ErrorCode.ROOM_NOT_IN_PROGRESS); | ||
| } |
src/main/java/konkuk/thip/room/application/port/out/dto/RoomQueryDto.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (4)
64-64: 주석/오류코드 정합성 확인 및 미스매치 수정 제안메서드는 “진행 중(in-progress)” 여부를 검증하지만 주석은 “만료 검증”으로 남아 있어 혼동 소지가 있습니다. 또한 이 도메인 검증이 실제로 ROOM_NOT_IN_PROGRESS(또는 요구된 코드명)로 매핑되어 Controller/Swagger 응답 세트에 반영되는지 확인 부탁드립니다.
적용 diff 예:
- room.validateRoomInProgress(); // 방이 만료되었는지 검증 + room.validateRoomInProgress(); // 방이 진행 중인지 검증
58-66: 불필요한 DB I/O 절감: 상태 검증을 Book 조회 이전에 수행방이 진행 중이 아니면 어차피 요청이 실패하므로 Book 조회를 뒤로 미루면 1회 쿼리를 절약할 수 있습니다.
- // 2. RoomParticipant, Room, Book 조회 + // 2. RoomParticipant, Room 조회 RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(command.userId(), command.roomId()); Room room = roomCommandPort.getByIdOrThrow(record.getRoomId()); - Book book = bookCommandPort.findById(room.getBookId()); - // 3. 유효성 검증 - room.validateRoomInProgress(); // 방이 만료되었는지 검증 + // 3. 유효성 검증 + room.validateRoomInProgress(); // 방이 진행 중인지 검증 + // 3-1. Book 조회 (검증 통과 후 I/O 수행) + Book book = bookCommandPort.getByIdOrThrow(room.getBookId()); validateRoomParticipant(roomParticipant, command.isOverview()); validateRecord(record, book);
80-87: 알림 발행은 트랜잭션 커밋 이후로 이동 권장롤백 시에도 알림이 발행되는 리스크를 줄이기 위해 AFTER_COMMIT 시점으로 미루는 것을 권장합니다. 메시징 아웃박스/이벤트 발행으로의 분리도 고려해 주세요.
예시(개념):
// 도메인 이벤트 발행 domainEventPublisher.publish(new RecordCreatedEvent(...)); // 리스너 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handle(RecordCreatedEvent e) { roomEventCommandPort.publishRoomRecordCreatedEvent(...); }
71-73: 진행률 갱신의 동시성 안전성 확인
updateUserAndRoomProgress가 누적 카운트/퍼센티지 갱신을 한다면 다중 요청에서 레이스가 발생할 수 있습니다. 관련 엔티티에@Version(낙관적 락) 사용 또는 저장소 레벨의 원자적 업데이트 쿼리 적용 여부를 확인 부탁드립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira 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/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java(20 hunks)src/main/java/konkuk/thip/room/domain/Room.java(3 hunks)src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java
- src/main/java/konkuk/thip/room/domain/Room.java
⏰ 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 (1)
src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (1)
61-61: 검증 결과 — 변경 불필요: Book 조회는 이미 예외 처리됩니다BookCommandPort.findById(Long)는 Book을 반환하고 구현체 BookCommandPersistenceAdapter.findById는 내부에서 bookJpaRepository.findById(id).orElseThrow(...)로 조회 실패 시 예외를 던집니다 (src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java, src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java). 따라서 해당 라인을 getByIdOrThrow로 변경할 필요가 없습니다.
Likely an incorrect or invalid review comment.
#️⃣ 연관된 이슈
📝 작업 내용
크게 3가지 작업을 수행했습니다.
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit