Skip to content

Conversation

@anxi01
Copy link
Member

@anxi01 anxi01 commented Nov 25, 2025

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

🚀 작업 내용

이번 PR에서 작업한 내용을 구체적으로 설명해주세요. (이미지 첨부 가능)

  • 디스코드에서 논의한 사항 토대로 가이드 확인 여부 조회 및 저장 API 구현했습니다.
  • GuideViewStatus 엔티티를 생성하여, 조회 여부와 더불어 조회 시점도 파악할 수 있게 하였습니다.
  • 유저 탈퇴 시, 가이드 조회 데이터도 같이 삭제될 수 있도록 구현했습니다.
스크린샷 2025-11-25 오후 8 53 27 스크린샷 2025-11-25 오후 8 53 52

📝️ 관련 이슈

본인이 작업한 내용이 어떤 Issue와 관련이 있는지 작성해주세요.


💬 기타 사항 or 추가 코멘트

남기고 싶은 말, 참고 블로그 등이 있다면 기록해주세요.

  • 운영 서버 배포 시, 아래 DDL을 적용해야 합니다.
create table if not exists `guide_view_status`
(
    guide_view_status_id bigint auto_increment primary key,
    guide_type           enum ('SCHEDULE_GUIDE_MODAL_001') not null,
    is_viewed            bit                               not null,
    viewed_at            datetime(6)                       not null,
    users_id             bigint                            not null,
    constraint unique_user_guide_type
        unique (users_id, guide_type),
    constraint guide_view_status_fk_users_id
        foreign key (users_id) references users (users_id)
)

Summary by CodeRabbit

  • 새로운 기능

    • 가이드 조회 기록 관리 추가
    • 가이드 조회 로그 생성, 조회, 삭제 기능 제공
  • 버그 수정

    • 이미 조회한 가이드 중복 등록 차단
    • 가이드를 찾을 수 없을 때 오류 처리 개선
    • 사용자 탈퇴 시 가이드 조회 로그도 정리되도록 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@anxi01 anxi01 self-assigned this Nov 25, 2025
@anxi01 anxi01 requested a review from bbbang105 as a code owner November 25, 2025 11:54
@anxi01 anxi01 added 🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 😉 seongmin 성민 PR labels Nov 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

사용자 가이드 조회 로그 기능을 추가합니다: GuideViewLog 엔티티·레포지토리·DTO·GuideType 열거형을 도입하고, UserService와 UserController에 생성/조회/삭제 엔드포인트 및 로직을 추가하며, 관련 성공/오류 상태와 테스트·회원탈퇴 정리 로직을 확장했습니다.

Changes

코호트 / 파일(s) 변경 요약
컨트롤러 / API 엔드포인트
src/main/java/side/onetime/controller/UserController.java
가이드 조회 로그용 3개 엔드포인트 추가: POST/GET/DELETE /guides/view-log (userService 위임, ApiResponse + SuccessStatus 반환)
도메인 엔티티
src/main/java/side/onetime/domain/GuideViewLog.java
신규 JPA 엔티티 GuideViewLog 추가 (table: guide_view_logs, unique(users_id, guide_type), viewedAt 타임스탬프)
열거형
src/main/java/side/onetime/domain/enums/GuideType.java
신규 enum GuideType 추가 (상수: SCHEDULE_GUIDE_MODAL_001)
레포지토리
src/main/java/side/onetime/repository/GuideViewLogRepository.java, src/main/java/side/onetime/repository/custom/UserRepositoryImpl.java
GuideViewLogRepository 인터페이스 추가 (existsByUserAndGuideType, findByUser); 회원 탈퇴 시 guideViewLog 삭제 로직 추가 (UserRepositoryImpl)
서비스
src/main/java/side/onetime/service/UserService.java
가이드 로그 생성/조회/삭제 메서드 추가: createGuideViewLog(CreateGuideViewLogRequest), getGuideViewLog(GuideType), deleteGuideViewLog() (현재 사용자 조회, 중복/미존재 검증, 저장/삭제)
DTOs
src/main/java/side/onetime/dto/user/request/CreateGuideViewLogRequest.java, src/main/java/side/onetime/dto/user/response/GetGuideViewLogResponse.java
요청 DTO(guideType, NotNull) 및 응답 DTO(isViewed) 추가
상태 코드 / 예외 상태
src/main/java/side/onetime/global/common/status/SuccessStatus.java, src/main/java/side/onetime/exception/status/UserErrorStatus.java
성공 상태 3개 추가: _CREATE_GUIDE_VIEW_LOG, _GET_GUIDE_VIEW_LOG, _DELETE_GUIDE_VIEW_LOG; 오류 상태 2개 추가: _IS_ALREADY_VIEWED_GUIDE, _NOT_FOUND_GUIDE
테스트
src/test/java/side/onetime/user/UserControllerTest.java
가이드 뷰로그 관련 테스트 6개 추가(생성/유효성/중복/조회/삭제/삭제-미발견) 및 REST Docs 문서화; DTO import 정리 및 GuideType 등 추가 import

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant UserController
    participant UserService
    participant GuideViewLogRepository
    participant Database

    rect rgb(235,247,255)
    Note over Client,Database: 생성 흐름
    Client->>UserController: POST /guides/view-log (guideType)
    UserController->>UserService: createGuideViewLog(request)
    UserService->>UserService: 현재 사용자 조회
    UserService->>GuideViewLogRepository: existsByUserAndGuideType(user, guideType)
    GuideViewLogRepository->>Database: SELECT exists...
    Database-->>GuideViewLogRepository: 존재 여부
    alt 이미 조회됨
        GuideViewLogRepository-->>UserService: true
        UserService-->>UserController: throw CustomException(_IS_ALREADY_VIEWED_GUIDE)
        UserController-->>Client: 409 CONFLICT
    else 미조회
        GuideViewLogRepository-->>UserService: false
        UserService->>GuideViewLogRepository: save(GuideViewLog)
        GuideViewLogRepository->>Database: INSERT
        Database-->>GuideViewLogRepository: 저장 완료
        UserService-->>UserController: 성공
        UserController-->>Client: 201 CREATED
    end
    end

    rect rgb(235,255,235)
    Note over Client,Database: 조회 흐름
    Client->>UserController: GET /guides/view-log?guide_type=...
    UserController->>UserService: getGuideViewLog(guideType)
    UserService->>UserService: 현재 사용자 조회
    UserService->>GuideViewLogRepository: existsByUserAndGuideType(user, guideType)
    GuideViewLogRepository->>Database: SELECT exists...
    Database-->>GuideViewLogRepository: boolean
    GuideViewLogRepository-->>UserService: boolean
    UserService-->>UserController: GetGuideViewLogResponse(isViewed)
    UserController-->>Client: 200 OK
    end

    rect rgb(255,245,235)
    Note over Client,Database: 삭제 흐름
    Client->>UserController: DELETE /guides/view-log
    UserController->>UserService: deleteGuideViewLog()
    UserService->>UserService: 현재 사용자 조회
    UserService->>GuideViewLogRepository: findByUser(user)
    GuideViewLogRepository->>Database: SELECT ...
    Database-->>GuideViewLogRepository: Optional<GuideViewLog>
    alt 존재
        GuideViewLogRepository-->>UserService: GuideViewLog
        UserService->>GuideViewLogRepository: delete(guideViewLog)
        GuideViewLogRepository->>Database: DELETE
        Database-->>GuideViewLogRepository: 삭제 완료
        UserService-->>UserController: 성공
        UserController-->>Client: 200 OK
    else 미존재
        GuideViewLogRepository-->>UserService: empty
        UserService-->>UserController: throw CustomException(_NOT_FOUND_GUIDE)
        UserController-->>Client: 404 NOT FOUND
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 주의할 파일/영역:
    • UserService.createGuideViewLog()의 중복 체크 및 예외 처리 흐름
    • GuideViewLog 엔티티의 복합 유니크 제약과 DB 인덱스/마이그레이션 영향
    • UserRepositoryImpl.withdraw()에 추가된 삭제 로직이 기존 삭제 순서/트랜잭션과 충돌 없는지
    • 테스트 케이스가 실제 저장소/예외 흐름을 충분히 커버하는지

Possibly related PRs

Suggested reviewers

  • bbbang105

Poem

"새 로그가 폴짝, 토끼가 말하네—
클릭 한 번의 흔적도 소중히!
만들고, 읽고, 지우는 춤을 추며,
작은 발자국은 기록으로 남아라. 🐇✨"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 가이드 확인 여부를 조회/저장/삭제하는 기능을 명확하게 설명하며, 변경사항의 주요 내용을 정확히 반영합니다.
Description check ✅ Passed PR 설명은 템플릿의 모든 필수 섹션(PR 유형, 작업 내용, 관련 이슈)을 완벽하게 포함하고 있으며, 스크린샷과 배포 필요 사항까지 상세히 기술되어 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#299/guide-view

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7b2f1c and 1a3a623.

📒 Files selected for processing (1)
  • src/main/java/side/onetime/controller/UserController.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/side/onetime/controller/UserController.java

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/main/java/side/onetime/domain/GuideViewStatus.java (3)

42-48: 엔티티 생성자에서 LocalDateTime.now() 사용을 고려해보세요.

엔티티 생성자 내에서 LocalDateTime.now()를 직접 호출하면 테스트 작성이 어려워지고 시간대 관련 문제가 발생할 수 있습니다. 서비스 계층에서 타임스탬프를 생성하여 전달하거나, 별도의 시간 제공 컴포넌트를 사용하는 것을 권장합니다.

다음과 같이 리팩토링할 수 있습니다:

 @Builder
-public GuideViewStatus(User user, GuideType guideType) {
+public GuideViewStatus(User user, GuideType guideType, LocalDateTime viewedAt) {
     this.user = user;
     this.guideType = guideType;
     this.isViewed = true;
-    this.viewedAt = LocalDateTime.now();
+    this.viewedAt = viewedAt;
 }

그리고 서비스 계층에서 호출 시:

GuideViewStatus guideViewStatus = GuideViewStatus.builder()
        .user(user)
        .guideType(guideType)
        .viewedAt(LocalDateTime.now())
        .build();

36-37: Boolean 래퍼 타입 대신 boolean 원시 타입 사용을 고려하세요.

nullable = false 제약 조건이 있으므로 Boolean 래퍼 타입 대신 boolean 원시 타입을 사용하는 것이 더 적합합니다.

-@Column(name = "is_viewed", nullable = false)
-private Boolean isViewed;
+@Column(name = "is_viewed", nullable = false)
+private boolean isViewed;

36-40: isViewed 필드의 필요성을 재검토하세요.

현재 설계에서 GuideViewStatus 엔티티가 생성되면 isViewed는 항상 true로 설정됩니다. 엔티티의 존재 자체가 가이드를 확인했음을 의미한다면, 이 필드는 중복될 수 있습니다. viewedAt 필드만으로도 확인 여부와 시점을 모두 표현할 수 있습니다.

향후 "미확인" 상태를 추적할 계획이 없다면, 이 필드를 제거하고 엔티티 존재 여부로 확인 상태를 판단하는 것을 고려해보세요.

src/main/java/side/onetime/service/UserService.java (2)

206-231: 변수명을 더 명확하게 변경하는 것을 고려하세요.

Line 220의 isViewed 변수명이 다소 혼란스럽습니다. existsByUserAndGuideType 메서드는 레코드의 존재 여부를 반환하므로, exists 또는 alreadyExists 같은 이름이 더 의도를 명확히 전달합니다.

-boolean isViewed = guideViewStatusRepository.existsByUserAndGuideType(user, guideType);
-if (isViewed) {
+boolean exists = guideViewStatusRepository.existsByUserAndGuideType(user, guideType);
+if (exists) {
     throw new CustomException(UserErrorStatus._IS_ALREADY_VIEWED_GUIDE);
 }

233-248: 변수명을 더 명확하게 변경하는 것을 고려하세요.

Line 246의 isViewed 변수명이 혼란스러울 수 있습니다. existsByUserAndGuideType은 레코드 존재 여부를 확인하므로, 변수명을 exists로 변경하면 코드 의도가 더 명확해집니다. 다만 최종 반환값은 존재 여부가 곧 확인 여부를 의미하므로 로직은 정확합니다.

-boolean isViewed = guideViewStatusRepository.existsByUserAndGuideType(user, guideType);
-return GetGuideViewStatusResponse.from(isViewed);
+boolean exists = guideViewStatusRepository.existsByUserAndGuideType(user, guideType);
+return GetGuideViewStatusResponse.from(exists);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9dcfb5 and af128f2.

📒 Files selected for processing (11)
  • src/main/java/side/onetime/controller/UserController.java (2 hunks)
  • src/main/java/side/onetime/domain/GuideViewStatus.java (1 hunks)
  • src/main/java/side/onetime/domain/enums/GuideType.java (1 hunks)
  • src/main/java/side/onetime/dto/user/request/CreateGuideViewStatusRequest.java (1 hunks)
  • src/main/java/side/onetime/dto/user/response/GetGuideViewStatusResponse.java (1 hunks)
  • src/main/java/side/onetime/exception/status/UserErrorStatus.java (1 hunks)
  • src/main/java/side/onetime/global/common/status/SuccessStatus.java (1 hunks)
  • src/main/java/side/onetime/repository/GuideViewStatusRepository.java (1 hunks)
  • src/main/java/side/onetime/repository/custom/UserRepositoryImpl.java (2 hunks)
  • src/main/java/side/onetime/service/UserService.java (3 hunks)
  • src/test/java/side/onetime/user/UserControllerTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/side/onetime/exception/status/UserErrorStatus.java (4)
src/main/java/side/onetime/global/common/status/ErrorStatus.java (1)
  • Override (36-44)
src/main/java/side/onetime/exception/status/FixedErrorStatus.java (2)
  • Override (22-29)
  • Getter (9-40)
src/main/java/side/onetime/exception/status/AdminErrorStatus.java (2)
  • Override (31-38)
  • Override (40-48)
src/main/java/side/onetime/exception/status/EventErrorStatus.java (1)
  • Getter (9-41)
src/main/java/side/onetime/service/UserService.java (1)
src/main/java/side/onetime/util/UserAuthorizationUtil.java (1)
  • UserAuthorizationUtil (9-32)
⏰ 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 and Push to ECR
🔇 Additional comments (7)
src/main/java/side/onetime/domain/enums/GuideType.java (1)

1-7: 구현이 올바릅니다

GuideType enum이 적절하게 정의되었습니다. 단일 상수로 시작하는 것은 합리적이며, 향후 추가 가이드 타입을 쉽게 확장할 수 있는 구조입니다.

src/main/java/side/onetime/exception/status/UserErrorStatus.java (1)

16-16: 적절한 에러 상태 정의

이미 조회한 가이드에 대한 에러 상태가 올바르게 정의되었습니다. HTTP 409 CONFLICT 상태 코드는 중복 리소스 생성 시나리오에 적합합니다.

src/main/java/side/onetime/dto/user/response/GetGuideViewStatusResponse.java (1)

13-15: 간결한 팩토리 메서드 구현

from 메서드가 명확하고 간결하게 구현되었습니다.

src/main/java/side/onetime/repository/GuideViewStatusRepository.java (1)

1-11: 올바른 Repository 구현

Spring Data JPA 네이밍 컨벤션을 따르는 적절한 repository 인터페이스입니다. existsByUserAndGuideType 메서드는 중복 체크에 효율적입니다.

src/main/java/side/onetime/global/common/status/SuccessStatus.java (1)

56-57: 적절한 성공 상태 정의

가이드 조회 상태 API에 대한 성공 상태가 올바르게 정의되었습니다. HTTP 상태 코드가 적절하게 사용되었습니다 (생성: 201, 조회: 200).

src/main/java/side/onetime/controller/UserController.java (1)

176-191: 가이드 조회 상태 API 구현 확인

가이드 확인 여부 조회 엔드포인트가 적절하게 구현되었습니다. query parameter의 snake_case 네이밍(guide_type)은 프로젝트의 JSON 네이밍 전략과 일관성이 있습니다.

src/test/java/side/onetime/user/UserControllerTest.java (1)

460-539: 테스트 구현이 잘 되어 있습니다!

두 개의 새로운 테스트 메서드(createGuideViewStatus, getGuideViewStatus)가 모두 적절하게 구현되어 있습니다:

  • 적절한 모킹 사용
  • 응답 검증 포함
  • REST Docs 설정 완료
  • 기존 테스트 패턴 준수

@bbbang105 bbbang105 added the 🚨 fix 버그 수정 / 에러 해결 label Nov 25, 2025
Copy link
Member

@bbbang105 bbbang105 left a comment

Choose a reason for hiding this comment

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

코드가 깔끔하게 잘 짜진 것 같습니다 👍🏻

.build()
)
));
}
Copy link
Member

Choose a reason for hiding this comment

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

여기에 400 409 예외 처리 테스트 코드도 추가해주시면 좋을 것 같아요~!
제가 이번에 에타 기능 개선하면서 예외 케이스도 테스트 코드로 추가해봤는데 가능하더라구요

Copy link
Member

Choose a reason for hiding this comment

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

400

{
  "code": "E_BAD_REQUEST",
  "message": "올바르지 않은 enum 값입니다. 허용되지 않은 값: SCHEDULE_GUIDE_MODAL_0013232",
  "is_success": false
}

Copy link
Member

Choose a reason for hiding this comment

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

409

{
  "code": "USER-005",
  "message": "이미 조회한 가이드입니다.",
  "is_success": false
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/test/java/side/onetime/user/UserControllerTest.java (1)

561-600: 가이드 조회 테스트에 payload 검증과 문서 필드를 조금만 보강하면 좋겠습니다

현재 성공 메타데이터(is_success, code, message)만 검증하고 있어, 실제 페이로드인 payload.is_viewed 값도 함께 검증하면 회귀를 더 잘 잡을 수 있습니다. 또한 다른 테스트들처럼 payload 객체 자체에 대한 필드 설명을 추가하면 RestDocs 문서도 더 일관됩니다. 예시는 아래처럼 수정할 수 있습니다.

@@
-        mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/guides/view-status")
+        mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/guides/view-status")
                         .queryParam("guide_type", guideType.name())
                         .contentType(MediaType.APPLICATION_JSON)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.is_success").value(true))
                 .andExpect(jsonPath("$.code").value("200"))
                 .andExpect(jsonPath("$.message").value("유저 가이드 확인 여부 조회에 성공했습니다."))
+                .andExpect(jsonPath("$.payload.is_viewed").value(true))
@@
-                                        .responseFields(
-                                                fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"),
-                                                fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"),
-                                                fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
-                                                fieldWithPath("payload.is_viewed").type(JsonFieldType.BOOLEAN).description("가이드 확인 여부")
-                                        )
+                                        .responseFields(
+                                                fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"),
+                                                fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"),
+                                                fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
+                                                fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"),
+                                                fieldWithPath("payload.is_viewed").type(JsonFieldType.BOOLEAN).description("가이드 확인 여부")
+                                        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0397ff0 and 4b1e54a.

📒 Files selected for processing (6)
  • src/main/java/side/onetime/controller/UserController.java (2 hunks)
  • src/main/java/side/onetime/exception/status/UserErrorStatus.java (1 hunks)
  • src/main/java/side/onetime/global/common/status/SuccessStatus.java (1 hunks)
  • src/main/java/side/onetime/repository/GuideViewStatusRepository.java (1 hunks)
  • src/main/java/side/onetime/service/UserService.java (3 hunks)
  • src/test/java/side/onetime/user/UserControllerTest.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/side/onetime/repository/GuideViewStatusRepository.java
  • src/main/java/side/onetime/exception/status/UserErrorStatus.java
  • src/main/java/side/onetime/controller/UserController.java
  • src/main/java/side/onetime/service/UserService.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: Deploy to Server
🔇 Additional comments (7)
src/test/java/side/onetime/user/UserControllerTest.java (6)

18-27: 가이드 뷰 상태 기능을 위한 import 구성 적절합니다

새로 추가된 GuideType, 요청/응답 DTO, CustomException, UserErrorStatus, parameterWithName 정적 import 모두 아래 테스트들에서 실제로 사용되고 있어 불필요한 의존성 없이 잘 정리된 것 같습니다.


462-500: 가이드 확인 여부 저장 성공 플로우 테스트 구조가 일관적입니다

성공 케이스에서 201 응답, 코드 "201", 메시지와 RestDocs 요청/응답 스펙을 모두 검증하고 있어 기존 User API 테스트들과 일관되고 충분한 수준으로 보입니다.


502-527: 잘못된 GuideType에 대한 400 오류 테스트가 명확합니다

유효하지 않은 enum 값을 직접 JSON 문자열로 구성해서 E_BAD_REQUEST 코드와 상세 메시지까지 검증하고 있어, enum 바인딩 실패 시의 동작을 안정적으로 커버하고 있습니다.


529-559: 이미 조회한 가이드 409 충돌 케이스 테스트도 적절합니다

CustomException(UserErrorStatus._IS_ALREADY_VIEWED_GUIDE)를 모킹해 409, "USER-005", 메시지까지 검증하는 흐름이 명확하고, 에러 응답 포맷까지 잘 확인하고 있습니다.


602-632: 가이드 확인 여부 삭제 성공 케이스 테스트가 간결하게 잘 작성되었습니다

서비스 모킹, 200 응답/코드/메시지 검증, 공통 성공 응답 스키마 문서화까지 깔끔하게 정리되어 있어 추가로 손볼 부분은 없어 보입니다.


634-659: 가이드 미존재 시 404 처리 테스트가 기대 동작을 잘 검증합니다

_NOT_FOUND_GUIDE 예외를 통해 404, "USER-006", 메시지까지 모두 확인하고 있고, 실패 케이스에 대한 RestDocs 스니펫도 추가되어 있어 에러 플로우 커버리지가 충분합니다.

src/main/java/side/onetime/global/common/status/SuccessStatus.java (1)

56-58: 가이드 뷰 상태용 SuccessStatus 상수들이 컨트롤러/테스트와 잘 정합됩니다

_CREATE_GUIDE_VIEW_STATUS(201), _GET_GUIDE_VIEW_STATUS(200), _DELETE_GUIDE_VIEW_STATUS(200)의 HTTP 상태, 코드 문자열, 한글 메시지가 각각 대응하는 컨트롤러 응답 및 테스트에서 기대하는 값과 정확히 일치해 일관성이 좋습니다.

@anxi01 anxi01 changed the title [feat] : 가이드 확인 여부를 조회/저장한다 [feat] : 가이드 확인 여부를 조회/저장/삭제한다 Nov 29, 2025
@bbbang105 bbbang105 added the 🔄 refactor 코드 리팩토링 label Nov 30, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main/java/side/onetime/repository/GuideViewLogRepository.java (1)

10-15: 향후 GuideType 확장 시 findByUser 사용부 설계 점검 필요

지금 구조(GuideType 값이 1개)에서는 findByUser(User user) 로 단일 로그를 조회/삭제해도 문제가 없지만,
엔티티 제약 조건이 (users_id, guide_type) 복합 유니크인 만큼, 추후 GuideType 이 늘어나면 한 유저가 여러 가이드 로그를 가질 수 있게 됩니다.

그 시점에는:

  • 삭제/조회 용도로 findByUserAndGuideType(User user, GuideType guideType) 또는
  • “해당 유저의 모든 가이드 로그 삭제” 용도로 void deleteAllByUser(User user)

와 같이 의도를 더 명확히 드러내는 메서드를 추가하는 쪽이 안전할 것 같습니다. 현재 PR 범위에서는 그대로 두셔도 동작에는 문제가 없고, 확장 시점에 함께 리팩터링하는 것을 추천드립니다.

src/main/java/side/onetime/service/UserService.java (1)

250-264: 삭제 대상 가이드 범위(단일/전체)에 대한 스펙 명확화와 시그니처 개선 여지

현재 deleteGuideViewLog() 는:

  • guideType 파라미터 없이 “해당 유저의 가이드 조회 로그 하나”를 찾기 위해 findByUser(user) 를 사용하고,
  • 없으면 _NOT_FOUND_GUIDE 예외를 던지는 구조입니다.

지금은 GuideType 이 1개뿐이라 동작에는 문제가 없지만, 추후 타입이 늘어나면:

  • “특정 guideType 의 로그를 삭제하는 API” 인지,
  • “해당 유저의 모든 가이드 로그를 일괄 삭제하는 API” 인지

스펙을 다시 정해야 할 것 같습니다.

그에 따라:

  • 전자라면 deleteGuideViewLog(GuideType guideType) + findByUserAndGuideType(user, guideType) 형태로 좁히고,
  • 후자라면 guideViewLogRepository.deleteAllByUser(user) 처럼 “전체 삭제” 를 명확하게 드러내는 쪽이 이해하기 더 쉬울 것 같습니다.

현재 PR 범위에서는 그대로 두셔도 되지만, GuideType 확장 시점에 함께 리팩터링하는 것을 권장드립니다.

src/test/java/side/onetime/user/UserControllerTest.java (1)

561-600: getGuideViewLog 테스트에서 payload 내용 및 문서 스키마를 조금 더 엄밀히 검증하면 좋겠습니다

현재 테스트는 상태 코드·공통 필드만 검증하고 있어, 실제 비즈니스 값인 payload.is_viewed에 대한 보장이 없습니다. 또한 RestDocs 에서는 payload.is_viewed만 정의하고 payload 객체 자체는 누락되어 있어 기존 문서들과 약간 불균형해 보입니다.

아래처럼 작은 수정 제안드립니다.

         mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/guides/view-log")
                         .queryParam("guide_type", guideType.name())
                         .contentType(MediaType.APPLICATION_JSON)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.is_success").value(true))
                 .andExpect(jsonPath("$.code").value("200"))
                 .andExpect(jsonPath("$.message").value("유저 가이드 조회 로그 조회에 성공했습니다."))
+                .andExpect(jsonPath("$.payload.is_viewed").value(true))
                 .andDo(MockMvcRestDocumentationWrapper.document("user/get-guide-view-log",
@@
-                                        .responseFields(
+                                        .responseFields(
                                                 fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"),
                                                 fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"),
                                                 fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
+                                                fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"),
                                                 fieldWithPath("payload.is_viewed").type(JsonFieldType.BOOLEAN).description("가이드 조회 여부")
                                         )

이렇게 하면 실제 반환 모델과 테스트/문서가 더 강하게 동기화될 것 같습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b1e54a and b7b2f1c.

📒 Files selected for processing (9)
  • src/main/java/side/onetime/controller/UserController.java (2 hunks)
  • src/main/java/side/onetime/domain/GuideViewLog.java (1 hunks)
  • src/main/java/side/onetime/dto/user/request/CreateGuideViewLogRequest.java (1 hunks)
  • src/main/java/side/onetime/dto/user/response/GetGuideViewLogResponse.java (1 hunks)
  • src/main/java/side/onetime/global/common/status/SuccessStatus.java (1 hunks)
  • src/main/java/side/onetime/repository/GuideViewLogRepository.java (1 hunks)
  • src/main/java/side/onetime/repository/custom/UserRepositoryImpl.java (2 hunks)
  • src/main/java/side/onetime/service/UserService.java (3 hunks)
  • src/test/java/side/onetime/user/UserControllerTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/side/onetime/domain/GuideViewLog.java (3)
src/main/java/side/onetime/domain/User.java (1)
  • Entity (17-122)
src/main/java/side/onetime/global/common/dao/BaseEntity.java (1)
  • EntityListeners (15-28)
src/main/java/side/onetime/global/config/JpaConfig.java (1)
  • Configuration (6-9)
src/main/java/side/onetime/service/UserService.java (1)
src/main/java/side/onetime/util/UserAuthorizationUtil.java (1)
  • UserAuthorizationUtil (9-32)
src/main/java/side/onetime/global/common/status/SuccessStatus.java (2)
src/main/java/side/onetime/global/common/status/ErrorStatus.java (1)
  • Getter (9-45)
src/main/java/side/onetime/domain/enums/Status.java (1)
  • Status (3-6)
⏰ 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 and Push to ECR
🔇 Additional comments (13)
src/main/java/side/onetime/repository/custom/UserRepositoryImpl.java (1)

25-25: 탈퇴 시 GuideViewLog 삭제 로직 일관성 좋습니다

withdraw 안에서 guideViewLog 를 다른 엔티티들과 동일한 패턴으로 정리해 주셔서, 유저 탈퇴 시 가이드 조회 로그가 남지 않는 구조가 명확합니다. 조건도 guideViewLog.user.eq(activeUser) 로 잘 잡혀 있습니다.

Also applies to: 100-102

src/main/java/side/onetime/dto/user/response/GetGuideViewLogResponse.java (1)

7-14: 가이드 조회 여부 응답 DTO 설계 적절합니다

isViewed 하나만을 노출하는 레코드 + SnakeCase 설정으로, 클라이언트 입장에서 is_viewed 플래그만 보면 되는 단순한 응답이 잘 정의되어 있습니다. 정적 팩토리 from 도 다른 DTO들과 일관된 패턴이라 유지보수에 좋습니다.

src/main/java/side/onetime/dto/user/request/CreateGuideViewLogRequest.java (1)

9-14: 가이드 타입 요청 DTO와 검증 설정이 명확합니다

guide_type 를 enum GuideType 으로 받고 @NotNull 로 강제하는 구조라, 서비스 레이어에서 null 체크 분기가 필요 없고 API 계약도 분명합니다. Jackson 설정도 다른 User 관련 DTO들과 일관되어 보입니다.

src/main/java/side/onetime/global/common/status/SuccessStatus.java (1)

56-58: 가이드 조회 로그용 성공 상태 코드 정의 적절합니다

CREATE/GET/DELETE 각각에 대해 HTTP status(201/200)와 한글 메시지가 역할에 맞게 잘 분리되어 있고, User 도메인의 다른 성공 코드들과도 naming/pattern 이 일관적입니다.

src/main/java/side/onetime/service/UserService.java (1)

29-31: 가이드 조회 여부를 “행 존재 여부”로 모델링한 서비스 로직이 요구사항에 잘 맞습니다

  • createGuideViewLog 에서 로그인 유저 조회 → (user, guideType) 기준 중복 여부 체크 → 중복 시 도메인 에러, 아니면 새 로그를 생성하는 플로우가 자연스럽습니다.
  • getGuideViewLog 는 단순히 existsByUserAndGuideType 결과를 GetGuideViewLogResponse.from(isViewed) 로 감싸 주기만 해서, “가이드 확인 여부 조회/저장” 이라는 요구사항을 과하지 않게 구현한 형태입니다.
  • 별도의 isViewed 컬럼 없이, 레코드 존재 여부 = 조회 여부 로 처리하는 방향도 지금 도메인(조회 시점만 기록하면 되는 로그 성격)에는 잘 어울립니다.

현재 범위 내에서는 비즈니스 로직과 트랜잭션 설정 모두 무난해 보입니다.

Also applies to: 206-231, 241-248

src/main/java/side/onetime/controller/UserController.java (1)

158-191: 가이드 조회 로그 생성/조회 엔드포인트 설계가 서비스 로직과 잘 맞습니다

  • POST /guides/view-log@Valid CreateGuideViewLogRequest 를 받아 별도 응답 바디 없이 상태 코드만 내려주는 패턴으로, 다른 User API들과 일관적입니다.
  • GET /guides/view-log 에서 @RequestParam("guide_type") GuideType guideType 으로 타입을 받는 부분도 DTO/enum 의 SnakeCase 설정(guide_type)과 잘 맞고, GetGuideViewLogResponse 를 그대로 내려줘서 클라이언트가 is_viewed 만 보면 되도록 단순하게 정리되어 있습니다.

컨트롤러 레이어에서는 특별한 이슈 없이 무난해 보입니다.

src/main/java/side/onetime/domain/GuideViewLog.java (1)

12-44: 엔티티와 응답 구조 간 혼동 – 실제 불일치 없음

이 엔티티는 일관되게 guide_view_logs 테이블로 매핑되어 있으며, 응답 DTO의 is_viewed 필드는 서비스 레이어에서 조회 여부를 boolean 값으로 계산하여 전달하는 것입니다. 엔티티에 isViewed 컬럼이 필요하지 않으며, "행이 존재하면 조회한 상태"라는 설계는 적절합니다.

현재 코드베이스에서는 guide_view_status 테이블이나 대체 DDL에 대한 참조를 찾을 수 없으며, 엔티티 매핑과 실제 사용 구조 간 불일치는 확인되지 않습니다.

src/test/java/side/onetime/user/UserControllerTest.java (6)

18-27: 가이드 뷰 로그 테스트를 위한 import 추가 적절함

추가된 GuideType, DTO wildcard, CustomException, UserErrorStatus, parameterWithName import 들이 아래 신규 테스트에서 모두 사용되고 있어 불필요한 의존성 없이 잘 정리된 상태입니다.


462-500: 가이드 조회 로그 생성 성공 케이스 테스트/문서화가 일관되게 잘 추가되었습니다

정상 플로우에서 201 응답, 공통 응답 랩퍼 필드(is_success, code, message)를 모두 검증하고, RestDocs 로 request/response 필드까지 문서화한 부분이 기존 User API 테스트들과 스타일도 잘 맞습니다.


502-527: 잘못된 GuideType 입력에 대한 400 케이스 테스트가 기대 스펙을 잘 커버합니다

enum 바인딩 실패 시 E_BAD_REQUEST 코드와 상세 메시지 포맷까지 검증해서, 글로벌 예외 핸들러 동작을 안정적으로 보장해 줄 수 있을 것 같습니다. JSON 문자열을 직접 구성한 것도 enum 직렬화 로직 변화에 독립적이라 괜찮은 선택입니다.


529-559: 이미 조회한 가이드(409) 예외 플로우 테스트 적절함

CustomException(UserErrorStatus._IS_ALREADY_VIEWED_GUIDE)를 직접 던지도록 모킹해서 409 + USER-005 + 한글 메시지를 모두 검증하고 있어, 서비스/예외 매핑이 깨질 경우 바로 감지할 수 있을 것 같습니다.


602-632: 가이드 조회 로그 삭제 성공 케이스 테스트/문서화도 기존 패턴과 잘 맞습니다

userService.deleteGuideViewLog() 모킹 후 200 응답과 공통 응답 필드를 검증하고, CommonSuccessResponse 스키마로 문서화한 부분이 다른 API들과 통일되어 있어 유지보수에 좋아 보입니다.


634-659: 가이드 조회 로그 없음(404) 예외 플로우 테스트가 적절히 추가되었습니다

_NOT_FOUND_GUIDE 예외를 모킹해 404 + USER-006 + 한글 메시지를 검증하고 있어, 삭제 API의 에러 응답 계약을 충분히 커버하고 있습니다. 실패 케이스 RestDocs 스니펫까지 포함된 점도 좋습니다.

@bbbang105 bbbang105 merged commit 429a7fb into develop Nov 30, 2025
4 checks passed
@bbbang105 bbbang105 deleted the feature/#299/guide-view branch November 30, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 🚨 fix 버그 수정 / 에러 해결 🔄 refactor 코드 리팩토링 😉 seongmin 성민 PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] : 가이드 확인 여부를 조회/저장/삭제한다

3 participants