Skip to content

[feat] 오늘의 한마디 삭제 api 개발#278

Merged
seongjunnoh merged 18 commits into
developfrom
feat/#276-daily-greeting-remove
Aug 29, 2025
Merged

[feat] 오늘의 한마디 삭제 api 개발#278
seongjunnoh merged 18 commits into
developfrom
feat/#276-daily-greeting-remove

Conversation

@seongjunnoh

@seongjunnoh seongjunnoh commented Aug 24, 2025

Copy link
Copy Markdown
Collaborator

#️⃣ 연관된 이슈

closes #276

📝 작업 내용

오늘의 한마디를 삭제하는 api를 개발하였습니다

  • 주요 코드 설명

    • service
      • 오늘의 한마디 삭제 요청을 보내는 유저가 방 참여자인지를 검증
      • 오늘의 한마디 삭제 요청을 보내는 유저가 삭제하려는 오늘의 한마디 작성자인지를 검증
      • 문제없을 경우, 오늘의 한마디 삭제 진행 (jpa entity의 @SQLDelete 어노테이션을 통해 soft delete 진행)
    • api 통합 test
      • 오늘의 한마디 삭제 이후, DB 데이터의 status 가 INACTIVE 인지를 검증하였습니다
  • 추가로 오늘의 한마디 entity 의 조회 관련 영속성 코드에서 soft delete 임에도 불구하고 status 조건 없이 entity를 find 하고 있어서, findByIdAndStatus 메서드를 통해 entity를 find 하도록 수정하였습니다

  • 위처럼 soft delete 대상인 entity들의 조회 관련 코드에 status 조건이 빠진 부분이 아직 남아있는것 같아, api feature 개발 이후 전체 코드에 대해서 점검을 해보겠습니다

  • 또한 개발 편의를 위해 access token 을 발급하는 테스트 용 api 를 뚫어놨습니다

📸 스크린샷

imageimage

💬 리뷰 요구사항

개발 편의를 위해 access token 을 발급하는 테스트 용 api 를 추가했는데, 배포환경에서 해당 api 가 악용되는 것을 막기 위해 access token 발급 controller 에 property 조건을 추가하였습니다

또한 dev yml, prod yml 에 해당 property 의 값을 false로 정의하여 github secret 값을 update 해두었습니다!

local yml 에서는 true 로 설정해서 사용하시면 됩니다! (노션 yml 정리 파일 참고해주시면 됩니다)

사전에 의논이 된 부분은 아니지만, 로컬 서버에서의 테스트 용이성을 위해 access token 을 발급하는 개발 편의용 api 가 있으면 좋겠다 싶어서 추가해봤습니다!
추후에 개발 편의용 api 가 추가될 경우 동일한 방식으로 관리하면 좋을 것 같습니다!

관련해서 의견 자유롭게 주시면 감사하겠습니다!!

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • New Features
    • 오늘의 한마디 삭제 기능 추가: DELETE /rooms/{roomId}/daily-greeting/{attendanceCheckId} (작성자만 삭제 가능, 삭제 시 roomId 반환)
    • 기록/투표 수정 엔드포인트 추가: PATCH /rooms/{roomId}/records/{recordId}, PATCH /rooms/{roomId}/votes/{voteId}
  • Documentation
    • 삭제 API 스웨거 응답 및 오류 케이스 문서화(권한 없음, 존재하지 않음, 본인만 삭제 가능 — "오늘의 한마디는 본인만 삭제할 수 있습니다.")
  • Refactor
    • 오늘의 한마디 소프트 삭제 도입으로 데이터 보존 강화
  • Tests / Chores
    • 삭제 API 통합·단위 테스트 추가
    • 테스트용 토큰 엔드포인트(/api/test/token/access) 및 보안 화이트리스트 추가 (테스트 모드)

@seongjunnoh seongjunnoh linked an issue Aug 24, 2025 that may be closed by this pull request
2 tasks
@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown

Walkthrough

오늘의 한마디(AttendanceCheck) 삭제 기능을 추가하고 관련 포트·서비스·퍼시스턴스(소프트 삭제)·도메인 검증·에러·스웨거 항목 및 통합/단위 테스트를 추가했으며 테스트용 토큰 엔드포인트와 보안 예외 경로, 일부 트랜잭션 어노테이션과 주석이 추가되었습니다.

Changes

Cohort / File(s) Summary
에러/스웨거 정의 확장
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java, src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
삭제 권한 오류 코드 ATTENDANCE_CHECK_CAN_NOT_DELETE 추가 및 ATTENDANCE_CHECK_DELETE 스웨거 응답 그룹 추가
컨트롤러 및 응답 DTO
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java, src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckDeleteResponse.java
DELETE /rooms/{roomId}/daily-greeting/{attendanceCheckId} 엔드포인트 추가, 생성 메서드명 변경(createFeed → createAttendanceCheck), 삭제 응답 레코드 추가 및 레코드/투표 업데이트 엔드포인트 추가
도메인/유스케이스/서비스/포트
src/main/java/konkuk/thip/roompost/domain/AttendanceCheck.java, src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckDeleteUseCase.java, src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckCommandPort.java, src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java
작성자 검증 메서드 validateCreator 추가, 삭제용 입력/출력 포트에 delete 메서드 추가 및 삭제 서비스 구현 (참여자 검증 → 작성자 검증 → 삭제)
퍼시스턴스(소프트 삭제/상태 필터링)
src/main/java/konkuk/thip/roompost/adapter/out/jpa/AttendanceCheckJpaEntity.java, .../adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java, .../adapter/out/persistence/AttendanceCheckCommandPersistenceAdapter.java
@SQLDelete로 소프트 삭제 적용(STATUS='INACTIVE'), 상태 기반 조회 메서드(findByAttendanceCheckIdAndStatus) 추가, ACTIVE 기반 조회/삭제 로직 추가
테스트
src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java, src/test/java/konkuk/thip/roompost/domain/AttendanceCheckTest.java
삭제 API 통합 테스트(작성자 삭제, 비참여자 차단, 타인 삭제 차단) 및 도메인 validateCreator 단위 테스트 추가
테스트 토큰 및 보안 설정
src/main/java/konkuk/thip/TestTokenController.java, src/main/java/konkuk/thip/config/SecurityConfig.java
테스트용 토큰 생성 컨트롤러 추가 및 /api/test/** 화이트리스트 등록
비기능성 주석/형식 변경
src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java, src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java
TODO 주석 및 공백 추가 (기능 변경 없음)
트랜잭션 적용
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java
updateRecord/updateVote@Transactional 추가

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 사용자
  participant Ctrl as RoomPostCommandController
  participant UC as AttendanceCheckDeleteUseCase
  participant Val as RoomParticipantValidator
  participant Port as AttendanceCheckCommandPort
  participant Repo as AttendanceCheckJpaRepository

  User->>Ctrl: DELETE /rooms/{roomId}/daily-greeting/{attendanceCheckId} (userId)
  Ctrl->>UC: delete(userId, roomId, attendanceCheckId)

  UC->>Val: validateMember(userId, roomId)
  Val-->>UC: ok

  UC->>Port: findById(attendanceCheckId)
  Port->>Repo: findByAttendanceCheckIdAndStatus(id, ACTIVE)
  alt not found
    Repo-->>Port: empty
    Port-->>UC: throw EntityNotFound(ATTENDANCE_CHECK_NOT_FOUND)
    UC-->>Ctrl: 404
  else found
    Port-->>UC: AttendanceCheck
    UC->>UC: attendanceCheck.validateCreator(userId)
    alt creator mismatch
      UC-->>Ctrl: InvalidState(ATTENDANCE_CHECK_CAN_NOT_DELETE) -> 403
    else ok
      UC->>Port: delete(attendanceCheck)
      Port->>Repo: delete(jpaEntity)
      Note right of Repo: @SQLDelete => UPDATE ... SET status='INACTIVE'
      Repo-->>Port: done
      UC-->>Ctrl: roomId
      Ctrl-->>User: 200 {roomId}
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
오늘의 한마디 삭제 API 제공 (#276)
삭제 권한 및 방 참여자 검증 포함 (#276)
스웨거/에러코드에 삭제 케이스 반영 (#276)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
테스트 토큰 컨트롤러 추가 (src/main/java/konkuk/thip/TestTokenController.java) 테스트 인프라용 엔드포인트로 삭제 API 요구사항과 직접적 연관 없음.
시큐리티 화이트리스트 추가 (src/main/java/konkuk/thip/config/SecurityConfig.java) 테스트 엔드포인트 접근 허용 설정으로 기능 요구사항과 무관함.
TODO 주석 추가 (src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java, src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java) 향후 리팩터 제안 주석으로 현재 요구사항과 직접 관련 없음.

Possibly related PRs

Suggested reviewers

  • buzz0331
  • hd0rable

Poem

깡총깡총, 토끼가 속삭이네 🐇
"한마디는 주인만 지울 수 있어요"
흔적은 INACTIVE로 살며시 남기고
검증은 먼저, 삭제는 깔끔히
머지되면 당근 하나 톡!

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#276-daily-greeting-remove

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions

github-actions Bot commented Aug 24, 2025

Copy link
Copy Markdown

Test Results

431 tests   431 ✅  36s ⏱️
129 suites    0 💤
129 files      0 ❌

Results for commit 8ddcf61.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (16)
src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (1)

61-67: Enum 비교는 == 사용, Base 리팩터 대비 변경 지점 최소화

  • Java enum 비교는 equals보다 ==가 명확하고 NPE 걱정도 없습니다.
  • Base에서 status를 private+protected 변경 메서드로 바꿀 예정이므로, 여기서도 그 메서드를 호출하는 형태로 틀을 잡아두면 이후 변경 파급을 최소화할 수 있습니다.

아래처럼 수정 제안합니다.

-    // TODO : Base Entity 의 status를 private 로 변경 & 상태변경을 위한 protected method 추가 후 수정
     public void softDelete() {
-        if(this.status.equals(INACTIVE)){
+        if (status == INACTIVE) {
             throw new InvalidStateException(POST_ALREADY_DELETED);
         }
-        this.status = INACTIVE;
+        this.status = INACTIVE; // Base 리팩터 이후: changeStatus(INACTIVE);
     }

추가 메모:

  • 현재 양방향 연관에 cascade = REMOVE, orphanRemoval = true가 걸려 있습니다(댓글/좋아요). 실수로 JPA delete()가 호출되면 연쇄 물리 삭제가 작동할 수 있습니다. 소프트 딜리트 일관성을 위해 실제 삭제 경로가 막혀 있는지(레포지토리 레벨에서 delete 차단, @SQLDelete(+ @where) 사용 등) 점검을 권장합니다.
src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckDeleteResponse.java (1)

1-9: Response DTO는 primitive로 null 불가를 명시하세요

roomId는 응답에서 null일 수 없는 값으로 보입니다. primitive long을 쓰면 불필요한 오토박싱을 피하고 스키마 의도가 명확해집니다. Swagger 문서화(@Schema)도 함께 고려해 주세요.

-public record AttendanceCheckDeleteResponse(
-        Long roomId
-) {
-    public static AttendanceCheckDeleteResponse of(Long roomId) {
+public record AttendanceCheckDeleteResponse(
+        long roomId
+) {
+    public static AttendanceCheckDeleteResponse of(long roomId) {
         return new AttendanceCheckDeleteResponse(roomId);
     }
 }

선택 사항:

  • @Schema(description = "...", example = "123") 추가로 API 문서 가독성 향상.
src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckCommandPort.java (1)

21-21: delete 메서드 구현 확인 및 JavaDoc 추가 제안

AttendanceCheckCommandPersistenceAdapter에서 delete(AttendanceCheck attendanceCheck)가 포트 시그니처와 일치하게 구현된 것을 확인했습니다.
이제 해당 메서드가 “소프트 삭제(상태를 INACTIVE로 변경)”한다는 의미와 멱등성, 예외 계약을 JavaDoc으로 명확히 문서화해 주세요. 선택적으로 아래와 같은 주석을 추가하여 가독성과 유지보수성을 높일 수 있습니다:

/**
 * Soft-delete the given AttendanceCheck (set status to INACTIVE).
 * <p>
 * Idempotent: no-op if already INACTIVE.
 * </p>
 * <p>
 * Throws:
 *   - EntityNotFoundException when the AttendanceCheck with ACTIVE status is not found  
 *   - InvalidStateException when the caller is not authorized to delete
 * </p>
 */
void delete(AttendanceCheck attendanceCheck);
  • 소프트 삭제 기반(@SQLDelete 혹은 엔티티 내부 상태 변경)임을 명시
  • 이미 INACTIVE인 경우 멱등 처리(no-op)
  • 존재하지 않거나 권한 검증 실패 시 던지는 예외 종류와 계약을 문서화
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java (1)

21-21: 카운트 쿼리의 상태조건 추가는 타당 — 인덱스/성능 고려 권장

status 파라미터를 포함해 소프트 삭제 레코드 배제를 보장한 점 좋습니다. 일 단위 카운트가 트래픽 경로라면 다음을 검토해 주세요.

  • 컬럼 인덱스: (user_id, room_id, status, created_at) 복합 인덱스 권장. created_at 범위 조건과 status 동시 선택성 확보.
  • 캐시/서머리 테이블: TODO 주석대로 Redis 카운터 또는 일별 집계테이블로 read 경로 비용 상한 설정.
src/test/java/konkuk/thip/roompost/domain/AttendanceCheckTest.java (2)

50-63: 테스트 시그니처 단순화 및 스타일 통일

throws Exception는 불필요합니다. 또한 본 파일 내에서 JUnit 단언 스타일을 통일하면 가독성이 좋아집니다(아래 실패 케이스 코멘트 참고).

제안 diff:

-    void validate_creator_success() throws Exception {
+    void validate_creator_success() {

65-80: 실패 케이스 단언 스타일 통일 + 메시지 의존도 최소화

현재 assertThatThrownBy를 사용하고, 메시지 문자열에 직접 의존하고 있습니다. 동일 파일에서 사용 중인 JUnit 스타일(AssertThrows)로 통일하고, 메시지 검증은 유지하되 예외 인스턴스를 받아 최소화된 의존으로 테스트해 주세요.

제안 diff:

-        assertThatThrownBy(() -> ac.validateCreator(2L))
-                .isInstanceOf(InvalidStateException.class)
-                .hasMessage(ATTENDANCE_CHECK_CAN_NOT_DELETE.getMessage());
+        InvalidStateException ex =
+                assertThrows(InvalidStateException.class, () -> ac.validateCreator(2L));
+        assertEquals(ATTENDANCE_CHECK_CAN_NOT_DELETE.getMessage(), ex.getMessage());

추가 제안(선택): InvalidStateExceptiongetErrorCode()가 존재한다면 메시지 대신 코드로 검증하세요.

assertEquals(ATTENDANCE_CHECK_CAN_NOT_DELETE, ex.getErrorCode());

엣지 케이스 보강(선택): creatorId == null, userId == null 입력에 대한 NPE 미발생/예외 메시지 확인 테스트 추가 가능.

src/main/java/konkuk/thip/roompost/adapter/out/jpa/AttendanceCheckJpaEntity.java (1)

16-16: AttendanceCheckJpaEntity @SQLDelete 최적화 제안

검증 결과

  • BaseJpaEntity의 status 필드는 @Enumerated(EnumType.STRING)로 매핑되어 있어 문자열 상수 'INACTIVE' 사용과 정확히 일치합니다.
  • 도메인 조회 로직(AttendanceCheckCommandPersistenceAdapter)에서는 findByAttendanceCheckIdAndStatus(..., StatusType.ACTIVE)를 사용해 이미 비활성화된 항목을 자동으로 제외하고 있습니다.

선택적 리팩토링 제안

  • @SQLDelete 구문에 AND status <> 'INACTIVE' 조건을 추가하여, 이미 비활성화된 레코드에 대한 불필요한 UPDATE를 방지할 수 있습니다.
  • 엔티티 단 전역 필터(@where(clause = "status = 'ACTIVE'") 또는 Hibernate @filter) 적용을 검토해, 각 리포지토리마다 status 조건을 반복하는 수고를 줄이세요.
  • updated_at 자동 갱신이 필요하다면 DB 트리거나 엔티티 리스너를 활용해 보완할 수 있습니다.

제안 diff:

- @SQLDelete(sql = "UPDATE attendance_checks SET status = 'INACTIVE' WHERE attendancecheck_id = ?")
+ @SQLDelete(sql = "UPDATE attendance_checks SET status = 'INACTIVE' WHERE attendancecheck_id = ? AND status <> 'INACTIVE'")
src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckDeleteUseCase.java (1)

3-7: UseCase 반환 타입에 대한 선택 재고(선택 사항)

현재 Long(roomId) 반환은 컨트롤러 응답 DTO와 결합도가 생깁니다. 도메인 관점에서는 void 또는 별도 결과 VO(예: DeleteResult.of(roomId))가 더 명확합니다. 유지보수성과 테스트 대역 구성에도 이점이 있습니다.

원하시면 DeleteResult VO 초안과 매핑 변경(diff)까지 같이 제안드리겠습니다.

src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckCommandPersistenceAdapter.java (1)

52-58: DB 라운드트립 축소 제안: 조건부 소프트 삭제 단일 쿼리화(선택 사항)

서비스에서 getByIdOrThrow(SELECT) 후 여기서 다시 SELECT→DELETE(@SQLDelete Update)로 최소 2번의 DB 접근이 발생합니다. JPA @Modifying UPDATE로 status = INACTIVE를 조건부 변경하면 1쿼리로 끝낼 수 있어 지연시간·부하가 줄고 TOCTOU 가능성도 낮아집니다.

예시(리포지토리에 추가):

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update AttendanceCheckJpaEntity a set a.status = konkuk.thip.common.entity.StatusType.INACTIVE " +
       "where a.attendanceCheckId = :id and a.status = konkuk.thip.common.entity.StatusType.ACTIVE")
int softDeleteIfActive(@Param("id") Long id);

적용 예(본 어댑터):

-        AttendanceCheckJpaEntity attendanceCheckJpaEntity = attendanceCheckJpaRepository...
-        attendanceCheckJpaRepository.delete(attendanceCheckJpaEntity);
+        int updated = attendanceCheckJpaRepository.softDeleteIfActive(attendanceCheck.getAttendanceCheckId());
+        if (updated == 0) {
+            throw new EntityNotFoundException(ATTENDANCE_CHECK_NOT_FOUND);
+        }

효과:

  • 쿼리 수: 2→1
  • 일관성: 상태 조건을 DB가 원자적으로 보장
src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java (4)

61-87: 행복 경로 테스트 훌륭합니다 + 경로 변수 변환 간소화 제안

기능 검증이 명확합니다. 다만 intValue()로 불필요한 다운캐스팅을 하고 있어 Long 그대로 전달하면 가독성이 좋아집니다.

적용 제안(diff):

-        mockMvc.perform(delete("/rooms/{roomId}/daily-greeting/{attendanceCheckId}", room.getRoomId().intValue(), ac1.getAttendanceCheckId().intValue())
+        mockMvc.perform(delete("/rooms/{roomId}/daily-greeting/{attendanceCheckId}", room.getRoomId(), ac1.getAttendanceCheckId())
                         .requestAttr("userId", me.getUserId()))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.data.roomId", is(room.getRoomId().intValue())));

84-87: 삭제 검증을 상태 스코프 메소드로 직접 확인(선택 사항)

향후 엔티티에 @where가 추가되면 findById로는 INACTIVE 레코드 접근이 막힐 수 있습니다. 상태 조건 메소드로 바로 검증하는 편이 견고합니다.

적용 제안(diff):

-        AttendanceCheckJpaEntity deleted = attendanceCheckJpaRepository.findById(ac1.getAttendanceCheckId()).orElse(null);
-        Assertions.assertNotNull(deleted);
-        assertThat(deleted.getStatus()).isEqualTo(INACTIVE);
+        AttendanceCheckJpaEntity deleted = attendanceCheckJpaRepository
+                .findByAttendanceCheckIdAndStatus(ac1.getAttendanceCheckId(), INACTIVE)
+                .orElse(null);
+        assertThat(deleted).isNotNull();

45-45: 미사용 필드 정리 제안

ObjectMapper가 사용되지 않습니다. 제거하면 경고 감소 및 가독성 향상됩니다.

적용 제안(diff):

-    @Autowired private ObjectMapper objectMapper;

61-87: 부가 시나리오 테스트 추가 권장(선택 사항)

  • 이미 삭제된 항목 재삭제 → 404(ATTENDANCE_CHECK_NOT_FOUND) 기대
  • 존재하지 않는 ID 삭제 → 404 기대
    실제 운영 이슈 재현에 유용합니다. 원하시면 테스트 케이스 템플릿을 드리겠습니다.
src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (2)

21-31: 삭제 플로우 구성 적절 + 주석 오탈자

  • 방 참여자 검증 → 소유자 검증 → 삭제의 순서가 명확합니다.
  • 주석 오탈자 수정 권장: “creator 겁증” → “creator 검증”.

적용 제안(diff):

-        // 2. creator 겁증
+        // 2. creator 검증

18-31: 쿼리 수 2→1로 줄이는 아키텍처 옵션(선택 사항)

현재 흐름은 (1) getByIdOrThrow(SELECT) → (2) delete(SELECT+UPDATE)로 최소 2회의 DB 접근이 필요합니다. 퍼시스턴스 어댑터에 “creatorId까지 조건으로 소프트 삭제”를 캡슐화하면 단일 UPDATE로 끝낼 수 있습니다.

예:

  • Adapter: deleteIfCreator(attendanceCheckId, creatorId) 추가
  • Repository: update ... set status=INACTIVE where attendanceCheckId=:id and creator.userId=:creatorId and status=ACTIVE
  • 영향: 네트워크 RTT·락 점유 시간 감소, 경합 환경에서 TOCTOU 축소

원하시면 구체 diff(리포지토리/포트/서비스)까지 제안드리겠습니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (1)

138-151: DELETE 응답 설계에 대한 선택 사항: 200 + payload vs 204 No Content

현재는 roomId를 반환(200)합니다. API 일관성이 200+payload라면 유지 OK. 만약 삭제는 204를 표준으로 쓰는 컨벤션이라면 204로 맞추고 응답 본문을 제거하는 것도 고려 가능합니다. 스웨거 문서에 응답 예시(성공/실패)를 추가하면 클라이언트 구현이 더 쉬워집니다.

원하시면 204 스타일로의 변경 diff(BaseResponse, 컨트롤러, 테스트)도 제공 가능합니다.

📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 31decda and d828eed.

📒 Files selected for processing (15)
  • src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (2 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/response/AttendanceCheckDeleteResponse.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/out/jpa/AttendanceCheckJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java (2 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/in/AttendanceCheckDeleteUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/service/AttendanceCheckDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/domain/AttendanceCheck.java (2 hunks)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/roompost/domain/AttendanceCheckTest.java (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
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.371Z
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-14T07:19:27.310Z
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.310Z
Learning: AttendanceCheck 엔티티는 BaseJpaEntity를 상속받아 status 필드(StatusType.ACTIVE/INACTIVE/EXPIRED)로 소프트 딜리트를 구현하므로, 카운트 쿼리에서 status = 'ACTIVE' 조건을 포함해야 삭제된 레코드를 제외할 수 있음.

Applied to files:

  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java
  • src/main/java/konkuk/thip/roompost/application/port/out/AttendanceCheckCommandPort.java
  • src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java
  • src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java
  • src/main/java/konkuk/thip/roompost/adapter/out/jpa/AttendanceCheckJpaEntity.java
  • src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckCommandPersistenceAdapter.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/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.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 쿼리 "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.java
🧬 Code graph analysis (1)
src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (30-394)
⏰ 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 (11)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)

314-318: 검증 스크립트 실행 완료 후 요약

AttendanceCheckDeleteService에서는

  1. roomParticipantValidator.validateUserIsRoomMember(roomId, creatorId) 호출로 방 참여 여부를 404(ROOM_NOT_FOUND)가 아닌 403(ROOM_ACCESS_FORBIDDEN)로 처리
  2. AttendanceCheckCommandPort.delete(...) 호출 시 존재하지 않는 attendanceCheckEntityNotFoundException(ATTENDANCE_CHECK_NOT_FOUND)으로 404 처리
  3. 작성자 불일치는 도메인의 validateCreator에서 InvalidStateException(ATTENDANCE_CHECK_CAN_NOT_DELETE)로 403 처리

– 컨트롤러(RoomPostCommandController)는 @ExceptionDescription(ATTENDANCE_CHECK_DELETE) 하나로 위 예외들을 매핑
– 테스트(AttendanceCheckDeleteApiTest) 시나리오
• 정상 삭제 → 200
• 타인 글 삭제 요청 → 403
• 비활성 상태 등 잘못된 삭제 요청 → 400

결론 및 제안 정리

  • 에러 매핑 구성 적절
    SwaggerResponseDescriptionATTENDANCE_CHECK_DELETE
    ROOM_ACCESS_FORBIDDEN(403), ATTENDANCE_CHECK_NOT_FOUND(404), ATTENDANCE_CHECK_CAN_NOT_DELETE(403) 세 가지로 구성돼 있어 서비스 흐름과 일치합니다.
  • 상태코드 변경 시 점검 불필요
    ATTENDANCE_CHECK_CAN_NOT_DELETE를 403으로 바꿔도 열거 상수만 변경되므로 스웨거 매핑은 그대로 유효합니다.
  • 추가 검토 사항 (선택)
    방이 존재하지 않을 때 404(ROOM_NOT_FOUND)를 내보낼 수 있다면, 다른 Delete API와 일관성을 위해 ATTENDANCE_CHECK_DELETEROOM_NOT_FOUND가 포함된 구성이 필요한지 확인해 보세요. 테스트 시나리오에서 불필요하다면 그대로 두셔도 무방합니다.
src/main/java/konkuk/thip/roompost/domain/AttendanceCheck.java (1)

8-8: 에러코드 의존성 추가 적절

삭제 권한 검증 용도로 ATTENDANCE_CHECK_CAN_NOT_DELETE를 명시적 static import 한 점, 도메인 의도와 일치합니다.

src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/attendancecheck/AttendanceCheckJpaRepository.java (2)

10-10: Optional import 추가 OK

새로운 finder 메서드 반환형과 일치합니다.


23-23: 기존 findById 호출 경로 없음 — 추가 검증 불필요

  • attendanceCheckJpaRepository.findById 호출이 전혀 존재하지 않음을 확인했습니다.
  • 직접 작성된 JPQL(count 쿼리)에도 status 필터가 포함되어 있어 삭제된(비활성) 레코드를 제외하고 있습니다.
  • 따라서 findByAttendanceCheckIdAndStatus 추가에 따른 혼용 우려가 없으며, 별도의 전환 누락 점검은 필요하지 않습니다.
src/test/java/konkuk/thip/roompost/domain/AttendanceCheckTest.java (1)

7-8: 정확한 예외 메시지 검증을 위한 상수 import 👍

테스트 가독성 향상에 도움이 됩니다.

src/main/java/konkuk/thip/roompost/adapter/out/persistence/AttendanceCheckCommandPersistenceAdapter.java (2)

47-49: 소프트 삭제에 따른 ACTIVE 스코프 조회 적용, 적절합니다

findById가 ACTIVE 상태만 조회하도록 바뀐 점이 일관성 확보와 버그 예방에 유리합니다. 동일한 제약을 기대하는 호출부(특히 카운트/리스트 쿼리)도 점진적으로 정렬하는 것이 좋습니다.

향후 조회 전체를 상태 조건으로 통일할 계획이라면 체크리스트에 추가해 주세요.


52-58: 리뷰 코멘트 무시: getId()가 올바른 도메인 식별자 접근자입니다.

도메인 AttendanceCheck 엔티티는 BaseDomainEntity를 상속받아 식별자를 getId()로 제공하며, JPA 어댑터의 findByAttendanceCheckIdAndStatus(attendanceCheck.getId(), …) 호출은 도메인 ID를 JPA 엔티티의 attendanceCheckId와 매핑하여 정상 동작합니다. 도메인에 getAttendanceCheckId() 메서드는 존재하지 않아 제안된 변경은 컴파일 에러를 발생시킵니다.

Likely an incorrect or invalid review comment.

src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java (2)

89-112: 비참여자 403 시나리오 테스트 적절합니다

접근 제어와 에러 메시지 단언이 명확합니다. 유지하세요.


114-139: 작성자 불일치 400 시나리오 테스트 적절합니다

도메인 소유권 검증의 실패 흐름이 잘 드러납니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (2)

38-38: UseCase 의존성 주입 추가 LGTM

필드 주입이 아닌 생성자 주입 패턴 유지되어 일관적입니다.


129-136: createAttendanceCheck 엔드포인트 명세 정리 LGTM

메서드명과 설명이 기능과 일치합니다. 기존 클라이언트가 메서드명에 의존하지 않으므로 호환성 문제도 없습니다.

Comment on lines +38 to +39

// TODO : status를 private로 변경, status를 변경하는 protected 메서드 추가

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

status를 private 전환 시, 변경 경로 단일화/검증 훅을 함께 설계하세요

TODO 방향성에 동의합니다. 다음을 함께 반영하면 도메인 일관성과 오용 방지에 도움이 됩니다.

  • 변경 경로 단일화: protected final 메서드로만 상태 전이를 허용하고, 외부에서 직접 필드 접근을 금지.
  • 전이 검증: 동일 상태 재전이 방지, 삭제 불가 상태(예: EXPIRED 등) 사전 검증 훅 제공.
  • 감사/감시 포인트: 상태 전이 시점에 도메인 이벤트/감사 로그를 연결하기 쉬워집니다.

아래와 같은 형태를 고려해 보세요(파일 밖 변경 예시):

// BaseJpaEntity
protected final void changeStatus(StatusType next) {
    if (this.status == next) return; // idempotent
    // 전이 검증 훅 (override 가능)
    validateStatusTransition(this.status, next);
    this.status = next;
    // onStatusChanged(next); // 이벤트 훅이 필요하면
}

protected void validateStatusTransition(StatusType current, StatusType next) {
    // 기본 구현: 항상 허용, 하위 클래스에서 제약 가능
}

또한, 소프트 딜리트 누락을 줄이기 위해

  • 각 엔티티에 @Where(clause = "status = 'ACTIVE'") 적용(공통화가 어려우면 최소한 조회량이 많은 엔티티부터),
  • 혹은 Hibernate Filter(편의성과 성능 사이 절충) 도입
    을 검토해 주세요. findByIdAndStatus 패턴 반복을 줄이고, 쿼리 누락 위험을 낮춥니다. 상태 컬럼 인덱스(또는 복합 인덱스 status + FK)도 함께 고려하면 조회 성능이 안정적입니다.

소프트 딜리트 조건 누락 지점을 빠르게 점검하려면 아래 스크립트를 실행해 보세요.


🏁 Script executed:

#!/bin/bash
# 상태 조건 없이 findById, getReferenceById 등을 사용하는 지점 탐색
rg -nP -C2 --type=java '\b(findById|getReferenceById|getById)\s*\('

# 상태 포함 메서드 패턴 점검
rg -nP -C1 --type=java '\bfindByIdAndStatus\b|\bfind.*Status\b'

Length of output: 105947


status 필드 접근 제어 및 soft delete 필터 일괄 적용 필수

TODO에 제안된 방향성에 동의합니다. 다음 필수 조치를 함께 반영해 주세요:

  • 접근 제어 및 상태 전이 메서드 통합
    BaseJpaEntity의 statusprivate으로 변경하고, 오직 아래와 같은 protected final changeStatus(StatusType next) 메서드를 통해서만 상태 전이를 허용하세요.
    // BaseJpaEntity
    protected final void changeStatus(StatusType next) {
        if (this.status == next) return;               // idempotent
        validateStatusTransition(this.status, next);   // 전이 검증 hook
        this.status = next;
        // onStatusChanged(next);                      // 이벤트 hook 필요 시
    }
    
    protected void validateStatusTransition(StatusType current, StatusType next) {
        // 기본 구현: 항상 허용, 하위 클래스에서 override로 제약 가능
    }
  • soft delete 필터 일괄 적용
    BaseJpaEntity(또는 각 엔티티) 클래스 상단에
    @Where(clause = "status = 'ACTIVE'")
    또는 Hibernate Filter를 도입해, 모든 조회에 자동으로 status = 'ACTIVE' 조건이 포함되도록 구성하세요.
  • 서비스·퍼시스턴스 레이어 조회 메서드 점검
    스크립트 실행 결과, FeedShowUserInfoService·FeedShowSingleService·RoomJoinService 등에서
    userCommandPort.findById, bookCommandPort.findById, roomCommandPort.findById
    상태 필터 없이 직접 조회하는 지점이 다수 발견되었습니다.
    해당 호출부를 status 필터가 적용된 커스텀 메서드 또는 필터링 로직을 거치는 형태로 수정해 주세요.
  • 상태 컬럼 인덱스 검토
    조회 성능 안정화를 위해 status 컬럼 단독 또는 FK와 복합 인덱스 생성을 고려하세요.

위 항목들을 반영한 후 다시 리뷰 요청 부탁드립니다.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java around lines
38-39: change the status field to private and add a protected final
changeStatus(StatusType next) method that enforces idempotency, calls a
protected validateStatusTransition(current,next) hook (default no-op) and then
sets the status; add an optional protected onStatusChanged hook if needed;
annotate the base entity (or each entity) with a global soft-delete filter such
as @Where(clause = "status = 'ACTIVE'") or configure a Hibernate Filter so reads
automatically exclude non-active rows; update service/repository lookup points
(e.g., userCommandPort.findById, bookCommandPort.findById,
roomCommandPort.findById and the callers in FeedShowUserInfoService,
FeedShowSingleService, RoomJoinService) to use status-aware find methods or
apply the filter before returning; and consider adding an index on the status
column (or composite index with relevant FKs) for read performance.

Comment thread src/main/java/konkuk/thip/common/exception/code/ErrorCode.java Outdated
Comment on lines +41 to +45
public void validateCreator(Long userId) {
if (!creatorId.equals(userId)) {
throw new InvalidStateException(ATTENDANCE_CHECK_CAN_NOT_DELETE);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

NPE 방지: Long 비교는 Objects.equals 사용 권장

creatorId 또는 인자 userId가 null일 경우 creatorId.equals(userId)에서 NPE 위험이 있습니다. Objects.equals로 치환해 주세요.

아래 수정 제안:

-    public void validateCreator(Long userId) {
-        if (!creatorId.equals(userId)) {
-            throw new InvalidStateException(ATTENDANCE_CHECK_CAN_NOT_DELETE);
-        }
-    }
+    public void validateCreator(Long userId) {
+        if (!java.util.Objects.equals(creatorId, userId)) {
+            throw new InvalidStateException(ATTENDANCE_CHECK_CAN_NOT_DELETE);
+        }
+    }

파일 상단에 import를 별도로 두고 싶다면:

import java.util.Objects;

그리고 본문은 Objects.equals(creatorId, userId)로 치환해 주세요.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/roompost/domain/AttendanceCheck.java around lines
41 to 45, the method uses creatorId.equals(userId) which can throw NPE if either
value is null; replace the comparison with Objects.equals(creatorId, userId) and
negate it (if (!Objects.equals(creatorId, userId)) throw ...), and add the
import java.util.Objects at the top of the file.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/main/java/konkuk/thip/TestTokenController.java (3)

15-18: HTTP GET으로 토큰 발급 + 평문 문자열 반환은 운영 습관과 다릅니다 — POST와 JSON 응답으로 정렬하세요.

민감 토큰 발급은 일반적으로 POST이고, 응답은 JSON이 표준적입니다. 테스트 코드/도구의 파싱 안정성도 좋아집니다.

제안 변경안:

-import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import java.util.Map;
@@
-    @GetMapping("/api/test/token/access")
-    public String generateAccessToken(@RequestParam Long userId) {
-        return jwtUtil.createAccessToken(userId);
+    @PostMapping("/api/test/token/access")
+    public Map<String, String> generateAccessToken(@RequestParam Long userId) {
+        return Map.of("accessToken", jwtUtil.createAccessToken(userId));
     }

참고: GET 유지가 꼭 필요하면 그대로 두셔도 되지만, JSON 응답으로 바꾸는 것만이라도 권장합니다.


15-17: 파라미터 최소 검증 추가 권장 — 음수/0 userId 차단.

로컬 테스트에서도 잘못된 입력을 초기에 걸러두면 디버깅 비용이 내려갑니다. 간단히 Bean Validation을 적용하세요.

예시:

+import org.springframework.validation.annotation.Validated;
+import jakarta.validation.constraints.Min;
@@
-@RestController
-@RequiredArgsConstructor
+@RestController
+@RequiredArgsConstructor
+@Validated
 public class TestTokenController {
@@
-    public Map<String, String> generateAccessToken(@RequestParam Long userId) {
+    public Map<String, String> generateAccessToken(@RequestParam @Min(1) Long userId) {
         return Map.of("accessToken", jwtUtil.createAccessToken(userId));
     }

추가로, 필요 시 존재하지 않는 사용자에 대한 토큰 발급을 막도록 JwtUtil 앞단에서 사용자 존재 여부 확인도 고려해 주세요.


15-18: 테스트 토큰의 오남용 방지: 짧은 TTL 또는 “source=test” 클레임 부여 후 비테스트 프로파일에서 거부.

운영 키/검증 로직을 공유한다면 테스트 토큰의 악용을 더 줄여야 합니다. 생성 토큰에 별도 클레임(source=test)을 넣고, 필터에서 prod 프로파일일 때 해당 클레임이 있으면 거부하도록 하세요. 또는 테스트 토큰 TTL을 1~5분으로 제한하세요.

JwtUtil에 손대야 하므로 여기서는 개념 제안만 남깁니다. 구현 필요하시면 말씀 주세요.

📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d828eed and a3b6ed6.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/TestTokenController.java (1 hunks)
  • src/main/java/konkuk/thip/config/SecurityConfig.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.371Z
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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Comment on lines +57 to +58
"/api/test/**", // for test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

프로덕션에서 /api/test/ 무인증 허용은 치명적 — 누구나 임의 userId로 액세스 토큰을 발급받을 수 있습니다.**

이 PR에 포함된 TestTokenController와 결합되면, 프로덕션에서도 /api/test/token/access?userId=... 호출만으로 유효 JWT를 생성할 수 있습니다(서명 키가 동일하다는 가정). 이는 계정 탈취로 직결되는 보안 이슈입니다. 최소한 프로파일/프로퍼티로 비활성화하고 기본 화이트리스트에서는 제거해야 합니다.

해결책 1(권장): 기본 화이트리스트에서 제거

-            "/api/test/**",  // for test

해결책 2: 프로퍼티 기반 조건부 허용(코드 외 추가 제안)

  • 필드 추가:
    @Value("${thip.security.enable-test-endpoints:false}")
    private boolean enableTestEndpoints;
  • authorize 섹션에서 조건부로 허용:
    .authorizeHttpRequests(auth -> {
        auth.requestMatchers(WHITELIST).permitAll();
        if (enableTestEndpoints) {
            auth.requestMatchers("/api/test/**").permitAll();
        }
        auth.anyRequest().authenticated();
    })
  • application-local.yml 등에만 thip.security.enable-test-endpoints: true 설정, prod에는 생략 또는 false 유지.

또는, TestTokenController 자체를 @Profile({"local","dev","test"})로 제한하면 SecurityConfig에 남아 있어도 prod에서는 404로 차단됩니다(아래 컨트롤러 코멘트 참고).

🤖 Prompt for AI Agents
src/main/java/konkuk/thip/config/SecurityConfig.java around lines 57-58, the
WHITELIST currently includes "/api/test/**" which allows unauthenticated JWT
minting in prod; remove "/api/test/**" from the default WHITELIST and instead
either (A) conditionally permit it via a property: add a boolean field (e.g.
@Value("${thip.security.enable-test-endpoints:false}") private boolean
enableTestEndpoints;) and in the authorize section only call
auth.requestMatchers("/api/test/**").permitAll() when that flag is true, or (B)
remove it entirely and annotate TestTokenController with a non-prod profile
(e.g. @Profile("local|dev|test")) so the endpoint is not available in
production.

Comment on lines +1 to +19
package konkuk.thip;

import konkuk.thip.common.security.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class TestTokenController {

private final JwtUtil jwtUtil;

@GetMapping("/api/test/token/access")
public String generateAccessToken(@RequestParam Long userId) {
return jwtUtil.createAccessToken(userId);
}
}

@coderabbitai coderabbitai Bot Aug 24, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

검증 스크립트: 테스트 엔드포인트가 실제로 프로덕션에서 비활성화되는지 확인해 주세요.

아래 스크립트로 컨트롤러 게이팅 어노테이션/프로퍼티와 SecurityConfig 화이트리스트 상태를 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== TestTokenController 게이팅 어노테이션 확인(@Profile/@ConditionalOnProperty) =="
rg -nP '@Profile|@ConditionalOnProperty' src/main/java/konkuk/thip/TestTokenController.java || {
  echo "경고: 게이팅 어노테이션이 없습니다."
}

echo "== SecurityConfig에 /api/test 화이트리스트 존재 여부 =="
rg -nP '/api/test/\*\*' src/main/java/konkuk/thip/config/SecurityConfig.java || echo "OK: SecurityConfig 화이트리스트에 없음"

echo "== application* 설정에서 테스트 엔드포인트 프로퍼티 존재 여부 =="
rg -n --glob 'application*.yml' 'thip\.security\.enable-test-endpoints' -n -C2 || echo "참고: 프로퍼티 미정의(기본 false 가정)"

Length of output: 929


⚠️ TestTokenController 테스트 엔드포인트 프로덕션 노출 방지 조치 필요

TestTokenController에 프로파일/프로퍼티 기반 게이팅이 없고, SecurityConfig에 화이트리스트가 등록되어 있어 실제 운영 환경에서도 엔드포인트가 활성화됩니다. 아래 수정 사항을 반영해주세요:

  • src/main/java/konkuk/thip/TestTokenController.java
    • @Profile("test") 또는
      @ConditionalOnProperty(prefix = "thip.security", name = "enable-test-endpoints", havingValue = "true") 어노테이션 추가
  • src/main/java/konkuk/thip/config/SecurityConfig.java
    • "/api/test/**" 경로를 화이트리스트에서 제거하거나,
      프로퍼티/프로파일 조건부로 등록하도록 변경
  • application.yml (및 application-*.yml)
    • thip.security.enable-test-endpoints 프로퍼티 정의 (기본값 false, 테스트 환경에서만 true)

예시 diff:

@@ -7,6 +7,8 @@
 @RestController
 @RequiredArgsConstructor
+@ConditionalOnProperty(
+    prefix = "thip.security", name = "enable-test-endpoints", havingValue = "true", matchIfMissing = false
+)
 public class TestTokenController {

     private final JwtUtil jwtUtil;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/TestTokenController.java around lines 1-19, the
test-only controller is unguarded and exposed in production; add gating by
annotating the class with either @Profile("test") or
@ConditionalOnProperty(prefix = "thip.security", name = "enable-test-endpoints",
havingValue = "true") so it only loads when intended; in
src/main/java/konkuk/thip/config/SecurityConfig.java remove "/api/test/**" from
the whitelist or register that whitelist entry conditionally based on the same
property/profile; and add thip.security.enable-test-endpoints: false to
application.yml (override to true in test-specific yml) so the endpoint is
disabled by default in production.

@seongjunnoh seongjunnoh Aug 24, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai 너의 리뷰처럼 yml 파일에서의 property 값에 따라서 TestTokenController 의 visibility 를 설정할 수 있도록 코드를 수정하였어.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@seongjunnoh 수정사항을 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== TestTokenController 현재 상태 확인 =="
cat src/main/java/konkuk/thip/TestTokenController.java

echo -e "\n== SecurityConfig WHITELIST 현재 상태 =="
rg -nP -A 10 -B 5 'WHITELIST.*=' src/main/java/konkuk/thip/config/SecurityConfig.java

echo -e "\n== yml 파일들에서 테스트 관련 프로퍼티 확인 =="
find . -name 'application*.yml' -exec echo "=== {} ===" \; -exec cat {} \; 2>/dev/null || echo "yml 파일 없음"

Length of output: 1936


좋습니다! TestTokenController에 @ConditionalOnProperty 어노테이션이 정상적으로 추가된 것을 확인했습니다. 추가로 yml 설정 파일들을 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== resources 폴더에서 yml 파일들 찾기 =="
find . -path "*/resources/*" -name '*.yml' -exec echo "=== {} ===" \; -exec cat {} \;

echo -e "\n== properties 파일들도 확인 =="
find . -path "*/resources/*" -name '*.properties' -exec echo "=== {} ===" \; -exec cat {} \;

echo -e "\n== thip.test-api 관련 프로퍼티 검색 =="
rg -r --type yaml --type properties "thip\.test-api" . || echo "thip.test-api 프로퍼티 미발견"

Length of output: 572


설정 파일들을 더 광범위하게 검색해보겠습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 전체 폴더에서 yml/yaml 파일들 검색 =="
find . -name '*.yml' -o -name '*.yaml' | head -10

echo -e "\n== application 관련 설정 파일들 검색 =="
find . -name 'application*' | head -10

echo -e "\n== 모든 파일에서 thip 관련 프로퍼티 검색 =="
rg -l "thip\." . 2>/dev/null | head -5 || echo "thip 프로퍼티 설정 파일 미발견"

echo -e "\n== SecurityConfig에서 조건부 화이트리스트 설정 확인 =="
rg -n -A 3 -B 3 "api/test" src/main/java/konkuk/thip/config/SecurityConfig.java

Length of output: 1904


@seongjunnoh 좋습니다! TestTokenController에 @ConditionalOnProperty(name = "thip.test-api.enabled", havingValue = "true") 어노테이션을 추가하신 것을 확인했습니다.

하지만 아직 완전하지 않은 부분이 있습니다:

✅ 완료된 부분:

  • TestTokenController에 조건부 빈 생성 로직 추가

❌ 남은 보안 이슈:
SecurityConfig의 화이트리스트에 /api/test/**가 여전히 하드코딩되어 있어서, 컨트롤러가 비활성화되어도 해당 경로는 인증 없이 접근 가능합니다.

추가 수정 필요사항:

// SecurityConfig.java에서 조건부 화이트리스트 등록
@ConditionalOnProperty(name = "thip.test-api.enabled", havingValue = "true")
@Bean 
public String[] getTestWhitelist() {
    return new String[]{"/api/test/**"};
}

또는 SecurityConfig 자체에서 프로퍼티를 주입받아 동적으로 화이트리스트를 구성하는 방법도 있습니다.

현재 상태로는 컨트롤러는 비활성화되지만 경로는 여전히 열려있어 보안상 완전하지 않습니다. 이 부분도 함께 수정해주시겠어요?


🧠 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#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#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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai 음 그런데 controller가 비활성화되면 해당 api 엔드포인트로 접근이 가능하더라도 404 error 가 발생하니 문제없는거 아닌가??

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seongjunnoh 맞습니다! 기술적으로 정확한 지적입니다.

@ConditionalOnProperty로 TestTokenController 빈이 생성되지 않으면 Spring이 해당 엔드포인트를 전혀 등록하지 않기 때문에, SecurityConfig의 화이트리스트에 /api/test/**가 있어도 실제로는 404 에러가 발생합니다.

보안상 실질적인 위험은 없습니다:

  • 컨트롤러 빈이 없으면 → 엔드포인트 매핑이 없음 → 404 응답
  • 인증을 우회해도 처리할 핸들러가 존재하지 않음

SecurityConfig에서 해당 경로를 제거하는 것은 코드 정리 차원의 문제일 뿐, 보안상 필수는 아니네요. 현재 구현으로도 충분히 안전합니다.

실용적인 관점에서 현재 상태로 두시는 것이 합리적입니다! 👍


✏️ Learnings added
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.961Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.

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#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#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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Comment thread src/main/java/konkuk/thip/TestTokenController.java
buzz0331
buzz0331 previously approved these changes Aug 25, 2025

@buzz0331 buzz0331 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~~

Comment on lines +10 to +20
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "thip.test-api.enabled", havingValue = "true")
public class TestTokenController {

private final JwtUtil jwtUtil;

@GetMapping("/api/test/token/access")
public String generateAccessToken(@RequestParam Long userId) {
return jwtUtil.createAccessToken(userId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인이욥

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네여

@Column(nullable = false)
protected StatusType status = StatusType.ACTIVE;

// TODO : status를 private로 변경, status를 변경하는 protected 메서드 추가

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이건 왜 추가된거죠??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저번에 posts 관련 jpa entity 에서 soft delete 를 처리할때 BaseJpaEntity 의 status 필드를 private로 유지하고, 대신 BaseJpaEntity 내부에 status 의 상태변경을 위한 메서드를 뚫어놓는 식으로 수정하는게 좋을것 같다고 얘기 나눴던것 같아서 TODO로 추가해보았습니다!

Comment on lines +50 to +80
@Test
@DisplayName("오늘의 한마디 작성자가 맞을 경우, 예외를 터뜨리지 않는다.")
void validate_creator_success() throws Exception {
//given
AttendanceCheck ac = AttendanceCheck.builder()
.id(1L)
.creatorId(1L)
.todayComment("오늘의 한마디!!!")
.roomId(1L)
.build();

//when //then
assertDoesNotThrow(() -> ac.validateCreator(1L));
}

@Test
@DisplayName("오늘의 한마디 작성자가 아닐 경우, InvalidStateException 을 터뜨린다.")
void validate_creator_fail() throws Exception {
//given
AttendanceCheck ac = AttendanceCheck.builder()
.id(1L)
.creatorId(1L)
.todayComment("오늘의 한마디!!!")
.roomId(1L)
.build();

//when //then
assertThatThrownBy(() -> ac.validateCreator(2L))
.isInstanceOf(InvalidStateException.class)
.hasMessage(ATTENDANCE_CHECK_CAN_NOT_DELETE.getMessage());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

hd0rable
hd0rable previously approved these changes Aug 27, 2025

@hd0rable hd0rable left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니닷닷

Comment on lines +10 to +20
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "thip.test-api.enabled", havingValue = "true")
public class TestTokenController {

private final JwtUtil jwtUtil;

@GetMapping("/api/test/token/access")
public String generateAccessToken(@RequestParam Long userId) {
return jwtUtil.createAccessToken(userId);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네여


// 2. creator 겁증
AttendanceCheck attendanceCheck = attendanceCheckCommandPort.getByIdOrThrow(attendanceCheckId);
attendanceCheck.validateCreator(creatorId);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이렇게 바로 사용하셨군요 좋네요

}

@Test
@DisplayName("다른 사람이 작성한 오늘의 한마디의 삭제 요청을 할 경우, 400 error 를 반환한다.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 403 에러 던지는거같은데 오타난거같아요요~~

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예리하시다

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (1)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (1)

120-131: Swagger 예외 설명 상수 오타(VOTE 삭제인데 RECORD_DELETE 사용)

문서화/스웨거 응답 설명이 잘못 매핑되어 소비자 혼선을 야기합니다. VOTE_DELETE로 교체해 주세요.

적용 diff:

-    @ExceptionDescription(RECORD_DELETE)
+    @ExceptionDescription(VOTE_DELETE)
🧹 Nitpick comments (4)
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (2)

41-55: Vote 수정도 동일한 권한 검증 필요

Vote.updateVote에서도 작성자 검증이 도메인/서비스 중 한 곳에서 확실히 이뤄지는지 확인 부탁드립니다. 위 스크립트로 함께 점검 가능합니다.


24-38: 서비스 레이어에서 작성자 검증 중복 제거 및 도메인 검증 확인

  • Record.updateRecord 메서드 내부에서 이미 작성자 검증(validateCreator(userId))이 수행되고 있습니다 (src/main/java/konkuk/thip/roompost/domain/Record.java:106)
  • 따라서 서비스 레이어에 별도 record.validateCreator(command.userId()) 검증 코드는 중복이므로 제거해도 무방합니다

선택 권장 사항:

  • JPA @Version 애노테이션을 활용한 낙관적 락을 도입하여 동시 수정 충돌을 방지하고, 충돌 발생 시 HTTP 409 또는 비즈니스 예외로 매핑하세요
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (2)

168-180: 오늘의 한마디 삭제 엔드포인트 추가 👍

소프트 삭제 흐름에 맞는 API 설계로 보입니다. BaseResponse로 200 OK 응답을 유지하는 현재 컨벤션과도 일치합니다. 권한/작성자 검증 실패(403/404) 케이스에 대한 통합 테스트도 추가 부탁드립니다.

원하시면 삭제·권한 실패·이미 삭제된 경우(멱등) 커버리지를 포함한 API 통합 테스트 케이스 템플릿을 드리겠습니다.


69-85: 업데이트 엔드포인트 테스트 보강 제안

  • 작성자가 아닌 사용자의 수정 시 403
  • 존재하지 않거나 비활성(소프트 삭제)된 게시물 수정 시 404
  • 성공 케이스 200 + 응답 페이로드 필드 검증

현재 PR 요지(삭제)와 직접적이진 않으나, 이번에 함께 추가된 엔드포인트의 회귀를 막기 위해 최소 케이스 권장드립니다.

Also applies to: 133-149

📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between f022b8e and 39ea5af.

📒 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/RoomPostCommandController.java (4 hunks)
  • src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
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.561Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
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.961Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
⏰ 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 (4)
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (1)

13-13: 트랜잭션 경계 추가 적절합니다

쓰기 연산인 updateRecord/updateVote에 @transactional을 부여한 선택 좋습니다. 메서드 단위 적용으로 의도도 명확합니다.

Also applies to: 24-24, 41-41

src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (3)

34-34: 삭제 유스케이스 DI 추가 👍

AttendanceCheckDeleteUseCase 주입으로 책임이 명확해졌습니다.


159-159: 메서드명(createAttendanceCheck)으로 의도 명확화 👍

엔드포인트 역할이 분명해졌습니다.


69-85: 검증 결과: @PathVariable 및 도메인 권한 검증 모두 적절히 처리됨 확인됨

  • @PathVariable 이름 미지정 사례가 스크립트로 탐지되지 않았습니다. 컨트롤러 메서드들의 파라미터 이름이 URI 템플릿({roomId}, {recordId})과 일치하여 암묵적 바인딩이 정상적으로 동작합니다.
  • 도메인 계층(Vote, Record, AttendanceCheck, Feed, Comment 등)에서 validateCreator(...) 메서드를 통해 작성자 검증 로직이 이미 구현되어 있어, 컨트롤러 레벨에서 추가 검증 없이도 권한이 보장됩니다.

위 두 사항 모두 문제없음이 확인되었으므로 추가 조치 불필요합니다.

Comment on lines +69 to +85
@Operation(
summary = "기록 수정",
description = "사용자가 방 기록을 수정합니다. (기록 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/records/{recordId}")
@ExceptionDescription(RECORD_UPDATE)
public BaseResponse<RecordUpdateResponse> updateRecord(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
@RequestBody @Valid final RecordUpdateRequest request
) {
return BaseResponse.ok(RecordUpdateResponse.of(
roomPostUpdateUseCase.updateRecord(request.toCommand(userId, roomId, recordId))
));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

@PathVariable 이름 미지정으로 바인딩 실패 가능성 — 명시화 권장

현재 컴파일 시 -parameters 미설정이면 파라미터 이름 소실로 바인딩 실패 위험이 있습니다. 기존 메서드들과도 일관되게 변수명을 명시해 주세요.

적용 diff:

-    public BaseResponse<RecordUpdateResponse> updateRecord(
-            @Parameter(hidden = true) @UserId Long userId,
-            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
+    public BaseResponse<RecordUpdateResponse> updateRecord(
+            @Parameter(hidden = true) @UserId Long userId,
+            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
+            @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable("recordId") Long recordId,
             @RequestBody @Valid final RecordUpdateRequest request

또한 생성(POST /rooms/{roomId}/record)과 수정(PATCH /rooms/{roomId}/records/{recordId})의 단수/복수 경로가 불일치합니다. 레거시와의 호환을 고려해야 하므로 즉시 변경은 권하지 않지만, 차기 버전에서 REST 리소스 명 복수형으로의 통일을 고려해 주세요.

마지막으로, RoomPostUpdateService.updateRecord가 roomId를 반환하는데, RecordUpdateResponse.of의 생성 인자와 의미가 일치하는지 확인 부탁드립니다.


검증을 위해 아래 스크립트를 실행하겠습니다.
해당 스크립트는

  1. 빌드 설정에서 -parameters 옵션 사용 여부 확인
  2. 컨트롤러의 POST/ PATCH 매핑 경로 일관성 확인
  3. roomPostUpdateUseCase.updateRecord 반환 타입과 RecordUpdateResponse.of() 인자 일치 여부 확인

를 수행합니다.


🏁 Script executed:

#!/bin/bash
set -e

# 1. 빌드 설정에서 -parameters 옵션 사용 여부 확인
echo "==> pom.xml 또는 build.gradle 확인"
fd pom.xml build.gradle* -c never || true

if [ -f pom.xml ]; then
  echo "--> pom.xml 내 maven-compiler-plugin 설정:"
  grep -R "<parameters>" -n pom.xml || echo "parameters 설정 없음"
fi

if ls build.gradle* 1> /dev/null 2>&1; then
  echo "--> build.gradle* 내 compileJava.compilerArgs 설정:"
  grep -R "compilerArgs" -n build.gradle* || echo "compilerArgs 설정 없음"
fi

# 2. 컨트롤러 매핑 경로 일관성 확인
echo "==> RoomPostCommandController의 매핑 확인"
rg -n "@PostMapping" -C1 src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java || true
rg -n "@PatchMapping" -C1 src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java || true

# 전체 컨트롤러에서 POST 경로 검색 (singular vs plural)
echo "==> 전체 @PostMapping 경로 목록:"
rg -n "@PostMapping" -C0 src/main/java/konkuk/thip/roompost/adapter/in/web || true

echo "==> 전체 @PatchMapping 경로 목록:"
rg -n "@PatchMapping" -C0 src/main/java/konkuk/thip/roompost/adapter/in/web || true

# 3. use case 반환 타입 및 of() 인자 일치 여부 확인
echo "==> useCase 필드 및 타입 확인"
rg -n "roomPostUpdateUseCase" -n src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java || true

echo "==> updateRecord 메서드 구현 위치 찾기"
rg -n "updateRecord" -n src/main/java/konkuk/thip/roompost || true

echo "==> RecordUpdateResponse.of 선언부 확인"
rg -n "class RecordUpdateResponse" -C3 -n src/main/java/konkuk/thip/roompost || true

Length of output: 4284


검증을 위해 아래 스크립트를 실행하겠습니다:


🏁 Script executed:

#!/usr/bin/env bash
set -e

# 1. updateRecord 구현 상세 확인
echo "==> RoomPostUpdateService.updateRecord 구현 (클래스/리턴/return) 확인"
grep -R "public Long updateRecord" -n -C3 src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java || true
grep -R "return" -n -C3 src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java || true

# 2. RecordUpdateResponse 클래스 및 of() 메서드 위치 확인
echo "==> RecordUpdateResponse 클래스/메서드 위치 확인"
grep -R "RecordUpdateResponse" -n -C5 src || true

Length of output: 4210


룸 기록 수정 API: @PathVariable 명시·경로 일관성·반환 값 검증 요청

검증 결과, 아래 사항들이 확인되었습니다. 런타임 오류 방지 및 API 일관성 유지를 위해 조치가 필요합니다.

  • 빌드 설정(pom.xml·build.gradle)에 -parameters 옵션이 설정되어 있지 않아, 파라미터 이름이 제거될 경우 @PathVariable 이름 바인딩이 실패할 수 있습니다.
    @PathVariable("roomId"), @PathVariable("recordId")로 이름을 명시해 주세요.
  • 생성 API는 /rooms/{roomId}/record (단수), 수정 API는 /rooms/{roomId}/records/{recordId} (복수)를 사용하고 있어 경로가 불일치합니다. 레거시 호환성을 유지해야 하므로 즉시 변경은 권장하지 않지만, 차기 버전에서 REST 리소스 명을 복수형으로 통일하는 방안을 검토해 주세요.
  • RoomPostUpdateService.updateRecord()command.roomId()를 반환하고, RecordUpdateResponse.of()roomId만 받도록 설계되어 있습니다. 응답에 포함해야 할 값(예: 갱신된 recordId 등)과 의미가 일치하는지 다시 한 번 확인 부탁드립니다.

적용 제안 diff:

@@ src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
-    @PatchMapping("/rooms/{roomId}/records/{recordId}")
+    @PatchMapping("/rooms/{roomId}/records/{recordId}")
     @ExceptionDescription(RECORD_UPDATE)
     public BaseResponse<RecordUpdateResponse> updateRecord(
         @Parameter(hidden = true) @UserId Long userId,
-        @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-        @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
+        @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
+        @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable("recordId") Long recordId,
         @RequestBody @Valid final RecordUpdateRequest request
     ) {
         return BaseResponse.ok(RecordUpdateResponse.of(
             roomPostUpdateUseCase.updateRecord(
-                request.toCommand(userId, roomId, recordId)
+                request.toCommand(userId, roomId, recordId)
             )
         ));
     }
  • @PathVariable 이름 명시 적용
  • 차기 버전 REST 리소스 명 복수형 통일 검토
  • 반환 값(roomId)이 의도한 응답 데이터와 일치하는지 확인
📝 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.

Suggested change
@Operation(
summary = "기록 수정",
description = "사용자가 방 기록을 수정합니다. (기록 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/records/{recordId}")
@ExceptionDescription(RECORD_UPDATE)
public BaseResponse<RecordUpdateResponse> updateRecord(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
@RequestBody @Valid final RecordUpdateRequest request
) {
return BaseResponse.ok(RecordUpdateResponse.of(
roomPostUpdateUseCase.updateRecord(request.toCommand(userId, roomId, recordId))
));
}
@Operation(
summary = "기록 수정",
description = "사용자가 방 기록을 수정합니다. (기록 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/records/{recordId}")
@ExceptionDescription(RECORD_UPDATE)
public BaseResponse<RecordUpdateResponse> updateRecord(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
@Parameter(description = "수정할 기록 ID", example = "1") @PathVariable("recordId") Long recordId,
@RequestBody @Valid final RecordUpdateRequest request
) {
return BaseResponse.ok(RecordUpdateResponse.of(
roomPostUpdateUseCase.updateRecord(
request.toCommand(userId, roomId, recordId)
)
));
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
around lines 69-85, update the @PathVariable annotations to explicitly name the
path variables (use @PathVariable("roomId") and @PathVariable("recordId")) to
avoid parameter-name binding issues when -parameters is not enabled; keep the
current route naming for now but add a TODO/comment to review and migrate
resource paths to plural form in the next major version for consistency; and
verify the data returned by roomPostUpdateUseCase.updateRecord() and
RecordUpdateResponse.of() — ensure the response includes the intended
identifiers (e.g., include the updated recordId as well as roomId or change
RecordUpdateResponse to reflect whatever the service actually returns) and
adjust the mapping/construction of RecordUpdateResponse accordingly.

Comment on lines +133 to +149
@Operation(
summary = "투표 수정",
description = "사용자가 방 투표를 수정합니다. (투표 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/votes/{voteId}")
@ExceptionDescription(VOTE_UPDATE)
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

@PathVariable 이름 명시 + 응답 값 일치 여부 확인

vote 수정도 동일하게 @PathVariable 이름을 명시해 주세요. 또한 RoomPostUpdateService.updateVote의 반환값과 VoteUpdateResponse.of의 기대값 일치 여부를 점검해 주세요.

적용 diff:

-    public BaseResponse<VoteUpdateResponse> updateVote(
-            @Parameter(hidden = true) @UserId Long userId,
-            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
+    public BaseResponse<VoteUpdateResponse> updateVote(
+            @Parameter(hidden = true) @UserId Long userId,
+            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
+            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable("voteId") Long voteId,
             @RequestBody @Valid final VoteUpdateRequest request

일관성을 확인하기 위해 @PathVariable 사용 방식과 반환 값 매핑을 점검하겠습니다. 다음 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1. 컨트롤러 파일 경로 및 상단 메서드 확인
CONTROLLER=$(fd RoomPostCommandController.java)
echo "Controller file: $CONTROLLER"
sed -n '1,200p' "$CONTROLLER"

# 2. UseCase의 updateVote 메서드 위치 및 시그니처 확인
USECASE_FILE=$(rg -l "updateVote" -tjava src/main/java/konkuk/thip/roompost/application)
echo "UseCase file: $USECASE_FILE"
sed -n '1,200p' "$USECASE_FILE"

# 3. VoteUpdateResponse.of 메서드 시그니처 확인
RESPONSE_FILE=$(fd VoteUpdateResponse.java)
echo "VoteUpdateResponse class file: $RESPONSE_FILE"
sed -n '1,200p' "$RESPONSE_FILE"

Length of output: 10287


@PathVariable 이름 명시와 반환값 매핑을 정확히 검증하기 위해, UseCase 인터페이스·서비스의 updateVote 시그니처와 VoteUpdateResponse.of(...) 메서드를 확인해 주세요. 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1. RoomPostUpdateUseCase에서 updateVote 시그니처 확인
USECASE_FILE=$(rg -l --glob="*RoomPostUpdateUseCase.java" -tjava src/main/java)
echo ">> UseCase 파일: $USECASE_FILE"
rg -nP "updateVote" -C3 "$USECASE_FILE"

# 2. RoomPostUpdateService에서 updateVote 구현부 확인
SERVICE_FILE=$(rg -l --glob="*RoomPostUpdateService.java" -tjava src/main/java)
echo ">> Service 파일: $SERVICE_FILE"
rg -nP "updateVote" -C5 "$SERVICE_FILE"

# 3. VoteUpdateResponse.of 메서드 시그니처 확인
RESPONSE_FILE=$(fd VoteUpdateResponse.java)
echo ">> Response 파일: $RESPONSE_FILE"
rg -nP "static.*of" -C3 "$RESPONSE_FILE"

Length of output: 91


일관성 있는 @PathVariable 이름 명시와 서비스 반환값 매핑 검증을 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1. UseCase 인터페이스: updateVote 시그니처 확인
USECASE_FILE=$(fd RoomPostUpdateUseCase.java)
echo "🔍 UseCase 파일: $USECASE_FILE"
sed -n '1,200p' "$USECASE_FILE"
rg -nP 'updateVote' -C3 "$USECASE_FILE"

# 2. Service 구현체: updateVote 구현부 확인
SERVICE_FILE=$(fd RoomPostUpdateService.java)
echo "🔍 Service 파일: $SERVICE_FILE"
sed -n '1,200p' "$SERVICE_FILE"
rg -nP 'updateVote\(' -C5 "$SERVICE_FILE"

# 3. 응답 DTO: VoteUpdateResponse.of 메서드 시그니처 확인
RESPONSE_FILE=$(fd VoteUpdateResponse.java)
echo "🔍 Response DTO 파일: $RESPONSE_FILE"
sed -n '1,200p' "$RESPONSE_FILE"
rg -nP 'static\s+.*of' -C3 "$RESPONSE_FILE"

Length of output: 5236


@PathVariable 이름 명시 및 반환값 매핑 검증

– 아래와 같이 @PathVariable에 경로 변수 이름을 명시해주세요.
RoomPostUpdateService.updateVote가 반환하는 Long roomIdVoteUpdateResponse.of(Long roomId)의 파라미터가 일치함을 확인했습니다.

수정 제안 diff:

     public BaseResponse<VoteUpdateResponse> updateVote(
             @Parameter(hidden = true) @UserId Long userId,
-            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
+            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
+            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable("voteId") Long voteId,
             @RequestBody @Valid final VoteUpdateRequest request
     ) {
         return BaseResponse.ok(VoteUpdateResponse.of(
             roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
         ));
     }
📝 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.

Suggested change
@Operation(
summary = "투표 수정",
description = "사용자가 방 투표를 수정합니다. (투표 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/votes/{voteId}")
@ExceptionDescription(VOTE_UPDATE)
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") Long roomId,
@Parameter(description = "수정할 투표 ID", example = "1") @PathVariable("voteId") Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
around lines 133 to 149, the @PathVariable annotations are missing explicit path
variable names; update the method signature to use @PathVariable("roomId") Long
roomId and @PathVariable("voteId") Long voteId so Spring binds variables by
name, and ensure the value returned from roomPostUpdateUseCase.updateVote (Long
roomId) is passed directly to VoteUpdateResponse.of(...) as already intended.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (2)

69-85: @PathVariable 이름 명시 누락 — 바인딩 실패 위험 및 경로 일관성 TODO

  • -parameters 미설정 시 이름 소실로 바인딩 실패 소지가 있습니다. 기존 deleteRecord와 동일하게 명시해 주세요.
  • 생성(POST /rooms/{roomId}/record) vs 수정(PATCH /rooms/{roomId}/records/{recordId})의 단수/복수 불일치에 대한 vNext TODO 코멘트 권장합니다.

적용 diff:

-    @PatchMapping("/rooms/{roomId}/records/{recordId}")
+    // TODO(vNext): /rooms/{roomId}/record/{recordId} 등으로 리소스 경로 일관성(단/복수) 정리
+    @PatchMapping("/rooms/{roomId}/records/{recordId}")
     @ExceptionDescription(RECORD_UPDATE)
     public BaseResponse<RecordUpdateResponse> updateRecord(
-            @Parameter(hidden = true) @UserId Long userId,
-            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
+            @Parameter(hidden = true) @UserId final Long userId,
+            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") final Long roomId,
+            @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable("recordId") final Long recordId,
             @RequestBody @Valid final RecordUpdateRequest request
     ) {
         return BaseResponse.ok(RecordUpdateResponse.of(
                 roomPostUpdateUseCase.updateRecord(request.toCommand(userId, roomId, recordId))
         ));
     }

검증 스크립트:

#!/bin/bash
# -parameters 사용 여부 및 해당 컨트롤러의 PathVariable 명시 상태 점검
rg -n "<parameters>|compilerArgs" pom.xml build.gradle* || true
rg -nP '@PathVariable\((?!")' src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java

133-149: updateVote도 @PathVariable 이름 명시 필요

updateRecord와 동일 리스크입니다. 일관성 있게 명시해 주세요.

적용 diff:

     @ExceptionDescription(VOTE_UPDATE)
     public BaseResponse<VoteUpdateResponse> updateVote(
-            @Parameter(hidden = true) @UserId Long userId,
-            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
+            @Parameter(hidden = true) @UserId final Long userId,
+            @Parameter(description = "수정할 방 ID", example = "1") @PathVariable("roomId") final Long roomId,
+            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable("voteId") final Long voteId,
             @RequestBody @Valid final VoteUpdateRequest request
     ) {
         return BaseResponse.ok(VoteUpdateResponse.of(
                 roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
         ));
     }
🧹 Nitpick comments (1)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (1)

169-181: deleteAttendanceCheck: 멱등성(idempotency)·에러 매핑 정책 확인 필요

소프트 삭제(@SQLDelete) 환경에서 재요청 시 동작 정의가 중요합니다.

  • 이미 INACTIVE인 경우: no-op 성공(200/204)로 처리할지, 404로 숨길지 정책 합의 필요.
  • 본인 아님/비참여자는 403/401로 매핑되는지(ErrorCode 일관성 포함) 확인 바랍니다.

권장:

  • 서비스 레벨에서 “이미 삭제됨”을 no-op으로 처리하여 DELETE의 멱등성 보장.
  • 스웨거 문서에 상태별 응답 예시 추가(200/204/403/404).
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 39ea5af and 8ddcf61.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (5 hunks)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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#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#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#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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
⏰ 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 (4)
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (4)

34-34: AttendanceCheckDeleteUseCase DI 추가 적절

삭제 유스케이스 주입 위치/가시성 모두 적절합니다. 추가 조치 없습니다.


124-124: VOTE_DELETE 예외 설명으로 정정, 적합

스웨거 예외 설명이 도메인 동작과 일치합니다.


151-151: 섹션 주석 갱신 적합

도메인 구분이 명확해졌습니다.


159-166: createAttendanceCheck 엔드포인트 설계 적절

경로, 바인딩(@PathVariable 이름 명시), 검증(@Valid) 모두 적절합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-320] [feat] 오늘의 한마디 삭제 api

3 participants