Skip to content

Conversation

@move-hoon
Copy link
Member

@move-hoon move-hoon commented Aug 8, 2025

🔗 관련 이슈

📘 작업 유형

  • ✨ Feature (기능 추가)
  • 🐞 Bugfix (버그 수정)
  • 🔧 Refactor (코드 리팩토링)
  • ⚙️ Chore (환경 설정)
  • 📝 Docs (문서 작성 및 수정)
  • ✅ Test (기능 테스트)
  • 🎨 style (코드 스타일 수정)

📙 작업 내역

  • DisableSwaggerSecurity 어노테이션으로 Swagger 내 로그인과 토큰 갱신 API 보안 설정을 비활성화하는 기능 추가했습니다.
  • 추가 개선작업 진행했습니다.
    • 애플 리프레쉬 토큰이 Null or 빈 문자열일 경우 핸들링 하는 로직 추가(두 번째 요청부터는 해당 정보를 내려주지 않기 때문)
    • 스웨거 명세를 전반적으로 리팩토링 했습니다. (볼륨 큼)

🧪 테스트 내역

  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

🎨 스크린샷 또는 시연 영상 (선택)

image
  • 확인완료!

✅ PR 체크리스트

  • 커밋 메시지가 명확합니다
  • PR 제목이 컨벤션에 맞습니다
  • 관련 이슈 번호를 작성했습니다
  • 기능이 정상적으로 작동합니다
  • 불필요한 코드를 제거했습니다

💬 추가 설명 or 리뷰 포인트 (선택)

  • ..

Summary by CodeRabbit

  • 신규 기능

    • 특정 인증 엔드포인트에서 Swagger 문서의 보안 요구사항이 비활성화되었습니다.
    • Swagger 문서에서 보안 스킴을 비활성화할 수 있는 새로운 어노테이션이 추가되었습니다.
    • 서비스 클래스에 적용하는 새로운 통합 애플리케이션 서비스 어노테이션이 도입되었습니다.
  • 개선 사항

    • 인증 및 권한 관련 HTTP 보안 규칙이 단순화되어, 일부 경로의 접근 제어가 변경되었습니다.
    • 도서 검색 및 상세 응답에 API 문서화용 스키마 설명과 예시가 추가되었습니다.
    • 사용자 도서 관련 응답 및 요청 DTO에 상세한 Swagger 스키마 주석이 강화되었습니다.
    • 애플 리프레시 토큰 저장 요청 DTO가 개선되어 불필요한 속성이 제거되고 검증이 강화되었습니다.
    • 일부 서비스 메서드에서 파라미터 및 반환 타입이 변경되어 기능이 개선되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 8, 2025

Walkthrough

Swagger UI에서 특정 API의 보안 설정을 비활성화할 수 있도록 @DisableSwaggerSecurity 커스텀 어노테이션이 도입되었습니다. 해당 어노테이션이 적용된 엔드포인트는 Swagger 문서에서 보안 요구 사항이 제거되며, 관련 Swagger 설정과 어노테이션, 적용 예시가 추가되었습니다. 게이트웨이 보안 설정에는 별도의 역할 기반 경로 규칙 정리가 포함되었습니다.

Changes

Cohort / File(s) Change Summary
DisableSwaggerSecurity 어노테이션 도입 및 적용
global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt, apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt
@DisableSwaggerSecurity 커스텀 어노테이션을 새로 생성하고, signIn, refreshToken 메서드에 적용
Swagger 보안 커스터마이저 추가
apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt
Swagger 설정에 OperationCustomizer 빈 추가, 어노테이션이 붙은 엔드포인트의 보안 요구 사항을 Swagger 문서에서 제거
게이트웨이 보안 설정 정리
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt
HTTP 보안 설정에서 일부 경로별 권한 규칙을 제거하고, 화이트리스트 및 /api/v1/admin/** 경로만 별도 처리하도록 단순화
ApplicationService 커스텀 어노테이션 도입 및 서비스 클래스 적용
global-utils/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt, apis/src/main/kotlin/org/yapp/apis/**/service/*.kt (여러 서비스 클래스)
Spring의 @Service@Validated를 조합한 @ApplicationService 커스텀 어노테이션 추가 및 기존 서비스 클래스에 적용
컴포넌트 및 패키지 경로 변경
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInStrategyResolver.kt, apis/src/main/kotlin/org/yapp/apis/book/util/*, apis/src/main/kotlin/org/yapp/apis/book/dto/response/*
@Service@Component로 변경, 유틸리티 패키지 경로 변경 및 import 수정
Swagger API 문서용 @Schema 어노테이션 추가 및 수정
apis/src/main/kotlin/org/yapp/apis/book/dto/response/*.kt, apis/src/main/kotlin/org/yapp/apis/book/dto/request/*.kt, apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/*.kt, apis/src/main/kotlin/org/yapp/apis/user/dto/request/*.kt
여러 DTO 클래스에 Swagger @Schema 어노테이션 추가 및 속성 변경(예: titlename), API 문서화 강화
UserAccountService 및 관련 도메인 서비스 메서드 시그니처 및 로직 일부 변경
apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt, domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt, apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt
updateAppleRefreshToken 메서드 파라미터 변경 및 반환 타입 변경, DTO 프로퍼티 및 메서드 변경에 따른 호출부 수정
UserSignInService 내부 로직 일부 변경
apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt
processSignIn 메서드 내 변수명 변경 및 appleRefreshToken 처리 로직 수정 (객체 생성 방식 변경)
AuthUseCase 메서드 내부 변수 추가
apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt
fetchAppleRefreshTokenIfNeeded 메서드 내 중간 변수 추가, 로직 및 반환 값은 동일
Swagger 스키마 어노테이션의 속성 순서 및 형식 변경
일부 DTO 클래스
Swagger 어노테이션 속성 순서 변경 및 멀티라인 형식 적용 등 문서화 목적 변경

Sequence Diagram(s)

sequenceDiagram
    participant SwaggerUI
    participant SwaggerConfig
    participant Controller

    SwaggerUI->>SwaggerConfig: API 문서 생성 요청
    SwaggerConfig->>Controller: 엔드포인트 메서드 검사
    alt @DisableSwaggerSecurity 어노테이션 있음
        SwaggerConfig-->>SwaggerUI: 해당 엔드포인트 보안 요구 사항 제거
    else 어노테이션 없음
        SwaggerConfig-->>SwaggerUI: 기본 보안 요구 사항 적용
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Assessment against linked issues

Objective Addressed Explanation
@DisableSwaggerSecurity 커스텀 어노테이션을 추가하여 Swagger UI에서 특정 API의 보안 설정을 비활성화할 수 있도록 구현 (#25)

Assessment against linked issues: Out-of-scope changes

해당 변경사항 내에서 명확히 범위를 벗어난 기능적 코드 변경은 발견되지 않았습니다.

Possibly related issues

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc46056 and a6516db.

📒 Files selected for processing (1)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-validation
🔇 Additional comments (4)
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (4)

7-10: 클래스 레벨 @Schema 메타데이터 적절함

스키마 이름/설명이 명확합니다. DTO 문서화에 도움이 됩니다.


20-23: 중첩 클래스 레벨 @Schema 메타데이터 적절함

SeedCategoryStats의 스키마 명/설명이 명확합니다.


26-29: name 필드 문서화 적절

설명/예시가 자연스럽고 일관적입니다.


32-36: count 필드의 최소값 제약(minimum=0) 반영 좋습니다

비음수 제약이 스키마에 명확히 드러납니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-90-chore/#25

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.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai 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:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai 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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

Documentation and Community

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

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: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73a1dff and 6aace86.

📒 Files selected for processing (4)
  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt (2 hunks)
  • gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (0 hunks)
  • global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-06T08:18:36.255Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt
🔇 Additional comments (1)
global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt (1)

3-5: 요구사항 충족: 함수 타깃 + RUNTIME 보존 설정이 적절합니다

Swagger OperationCustomizer에서 리플렉션으로 판별하기 위한 최소 조건을 정확히 갖췄습니다. 👍

import org.yapp.apis.auth.dto.request.SocialLoginRequest
import org.yapp.apis.auth.dto.request.TokenRefreshRequest
import org.yapp.apis.auth.dto.response.AuthResponse
import org.yapp.globalutils.annotation.DisableSwaggerSecurity
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

인터페이스에 @DisableSwaggerSecurity 부착은 의도에 부합하지만, 현 Customizer 로직으로는 미감지 위험

  • 본 파일처럼 “인터페이스 메서드”에 어노테이션을 부착하는 패턴은 좋습니다.
  • 다만 현재 SwaggerConfig.securityOperationCustomizer()handlerMethod.getMethodAnnotation(...)로만 검사하면, 구현 클래스의 메서드에 어노테이션이 없을 때(=인터페이스에만 있을 때) 감지가 누락될 수 있습니다.
  • 제안한 대로 AnnotatedElementUtils.findMergedAnnotation 기반 + 인터페이스 메서드 탐색으로 보강하면 안정적으로 동작합니다. 관련 수정 제안은 SwaggerConfig.kt 코멘트를 참고하세요.

빠른 점검:

Also applies to: 25-25, 52-52


🏁 Script executed:

#!/bin/bash
# 인터페이스에만 어노테이션이 달려있는지, 구현체에는 없는지 확인
rg -n "@DisableSwaggerSecurity" -S

Length of output: 240


다음 스크립트를 실행해 SwaggerConfig.ktsecurityOperationCustomizer 구현이 handlerMethod.getMethodAnnotation만 사용하고 있는지 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# SwaggerConfig 파일 경로 찾기 및 securityOperationCustomizer 내용 확인
file=$(rg -l "class SwaggerConfig" -S)
echo "SwaggerConfig file: $file"
rg -n -A5 -B5 "securityOperationCustomizer" "$file"

Length of output: 784


securityOperationCustomizer 로직 보강 필요: 인터페이스 메서드 어노테이션 감지 누락

현재 SwaggerConfig.ktsecurityOperationCustomizer는 구현 클래스의 메서드에만 붙은 어노테이션을 handlerMethod.getMethodAnnotation(...)로 조회하기 때문에, 인터페이스에만 선언된 @DisableSwaggerSecurity를 놓칠 수 있습니다. 아래 위치를 수정해 주세요.

  • 대상 파일:
    apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt
  • 메서드:
    fun securityOperationCustomizer (라인 57~63)

현 구현 예시:

val disableSwaggerSecurity: DisableSwaggerSecurity? =
    handlerMethod.getMethodAnnotation(DisableSwaggerSecurity::class.java)

제안된 수정 방법 (인터페이스 메서드까지 탐색):

import org.springframework.core.annotation.AnnotatedElementUtils

val disableSwaggerSecurity: DisableSwaggerSecurity? =
    AnnotatedElementUtils.findMergedAnnotation(
        handlerMethod.method, 
        DisableSwaggerSecurity::class.java
    ) ?: AnnotatedElementUtils.findMergedAnnotation(
        handlerMethod.beanType.getMethod(
            handlerMethod.method.name,
            *handlerMethod.method.parameterTypes
        ),
        DisableSwaggerSecurity::class.java
    )

위와 같이 변경하면 인터페이스에 선언된 @DisableSwaggerSecurity도 안정적으로 감지됩니다.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt around lines 57 to
63, the current securityOperationCustomizer method only checks for the
@DisableSwaggerSecurity annotation on the implementation method using
handlerMethod.getMethodAnnotation, which misses annotations declared on
interface methods. To fix this, replace the annotation retrieval with
AnnotatedElementUtils.findMergedAnnotation to first check the implementation
method and if not found, check the corresponding interface method by name and
parameter types. This ensures that @DisableSwaggerSecurity annotations declared
on interface methods are also detected correctly.

Comment on lines 3 to 5
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class DisableSwaggerSecurity()
Copy link

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

KDoc와 문서화 어노테이션 추가, Kotlin 스타일 닛픽

  • 사용 의도를 KDoc에 남기고, API 문서 생성 시 노출되도록 @MustBeDocumented를 권장합니다.
  • 빈 생성자 괄호는 불필요하므로 제거를 권장합니다.

적용 예시:

 @Target(AnnotationTarget.FUNCTION)
 @Retention(AnnotationRetention.RUNTIME)
-annotation class DisableSwaggerSecurity()
+@MustBeDocumented
+annotation class DisableSwaggerSecurity

간단한 KDoc도 추가하면 더 좋습니다:

/**
 * Swagger(OpenAPI) 문서에서 해당 Operation의 보안 요구 사항을 제거합니다.
 * - 실제 런타임 보안에는 영향 없음 (문서 전용)
 * - 로그인/토큰 갱신 등 공개 엔드포인트 명시용
 */
🤖 Prompt for AI Agents
In
global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt
around lines 3 to 5, add a KDoc comment above the annotation class explaining
its purpose as removing security requirements from Swagger documentation only,
and annotate the class with @MustBeDocumented to ensure it appears in generated
API docs. Also, remove the unnecessary empty parentheses from the annotation
class declaration to follow Kotlin style conventions.

Copy link
Member

Choose a reason for hiding this comment

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

docs 써주면 추후에 도움이 많이 될 것 같아요!

Copy link

Choose a reason for hiding this comment

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

@minwoo1999 네, 맞습니다! 문서화는 팀원들이 어노테이션의 목적과 사용법을 빠르게 이해하는 데 큰 도움이 됩니다. 특히 @DisableSwaggerSecurity처럼 특정 목적을 가진 커스텀 어노테이션의 경우, KDoc을 통해 명확한 의도를 전달하는 것이 중요합니다.

제안드린 KDoc과 @MustBeDocumented 어노테이션을 추가하시면 IDE에서도 바로 문서를 확인할 수 있어 개발 효율성이 높아질 것입니다. 👍

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다.
코멘트 남긴 부분만 확인 한번 부탁드릴게요~

Comment on lines 3 to 5
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class DisableSwaggerSecurity()
Copy link
Member

Choose a reason for hiding this comment

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

docs 써주면 추후에 도움이 많이 될 것 같아요!

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: 9

♻️ Duplicate comments (6)
global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt (1)

1-6: KDoc 추가 제안 (문서 의도 명확화 및 IDE 툴팁 개선)

의도(문서 전용, 런타임 보안 영향 없음)를 KDoc으로 명시하면 사용자가 혼동하지 않습니다. 과거 코멘트와 동일 제안입니다.

아래처럼 KDoc을 추가해 주세요:

 package org.yapp.globalutils.annotation

+/**
+ * Swagger(OpenAPI) 문서에서 해당 Operation의 보안 요구사항(securitySchemes)을 제거합니다.
+ * - 런타임 보안(Spring Security 등)에는 영향이 없습니다. (문서 전용)
+ * - 로그인/토큰 갱신 등 공개 엔드포인트 명시 용도
+ */
 @Target(AnnotationTarget.FUNCTION)
 @Retention(AnnotationRetention.RUNTIME)
 @MustBeDocumented
 annotation class DisableSwaggerSecurity
apis/src/main/kotlin/org/yapp/apis/auth/service/AuthTokenService.kt (1)

11-14: @ApplicationService 도입 LGTM (상동 검증 참고)

주입/검증 로직 변경 없음 확인. ApplicationService 메타 구성(@service, @validated, Retention/Target)은 앞 파일 코멘트를 참고해 동일 기준으로만 확인해 주세요.

apis/src/main/kotlin/org/yapp/apis/auth/service/AppleAuthService.kt (1)

6-10: @ApplicationService 도입 LGTM (상동 검증 참고)

클래스 어노테이션 통합 외 로직 변화 없음. 메타 어노테이션 구성은 상동 기준으로 검증 부탁드립니다.

apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt (1)

7-10: @ApplicationService 도입 LGTM (상동 검증 참고)

서비스 어노테이션 통합으로 일관성 향상. Validation AOP 동작 지속 여부는 상동 기준으로 확인해 주세요.

apis/src/main/kotlin/org/yapp/apis/auth/service/RefreshTokenService.kt (1)

9-12: @ApplicationService 도입 LGTM (상동 검증 참고)

도메인 서비스 위임만 수행하므로 이번 변경의 동작 영향은 매우 낮습니다. 메타 어노테이션 구성은 상동 기준으로만 재확인 부탁드립니다.

apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt (1)

9-9: 동일: @ApplicationService 적용 시 프록시/검증 동작 유지 점검

위 파일에 남긴 스크립트로 일괄 점검 가능합니다.

Also applies to: 12-12

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6aace86 and 40c0bbf.

📒 Files selected for processing (21)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/AppleAuthService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/AuthTokenService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/RefreshTokenService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserWithdrawalService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInStrategyResolver.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/util/AuthorExtractor.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/util/IsbnConverter.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/home/service/HomeService.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordTagService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserService.kt (1 hunks)
  • global-utils/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt (1 hunks)
  • global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-06T08:18:36.255Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserWithdrawalService.kt
  • apis/src/main/kotlin/org/yapp/apis/auth/service/AppleAuthService.kt
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInStrategyResolver.kt
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserService.kt
⏰ 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-validation
🔇 Additional comments (17)
apis/src/main/kotlin/org/yapp/apis/book/util/IsbnConverter.kt (1)

1-1: 레거시 참조 없음 확인됨
org.yapp.apis.util.IsbnConverter에 대한 참조가 코드베이스에서 모두 제거되었으며, 새 패키지 선언(org.yapp.apis.book.util)도 정상 적용되었습니다.
변경 사항 문제 없습니다.

apis/src/main/kotlin/org/yapp/apis/book/util/AuthorExtractor.kt (1)

1-1: 패키지 경로 및 레거시 참조 확인 완료
AuthorExtractororg.yapp.apis.book.util로 이동되었으며, 기존 org.yapp.apis.util.AuthorExtractor 임포트/참조는 코드 전반에 남아 있지 않음을 확인했습니다.
문제 없습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (1)

3-4: 구 임포트 경로 잔존 여부 확인 완료

아래 검색 결과로 org.yapp.apis.util.AuthorExtractororg.yapp.apis.util.IsbnConverter 경로를 사용하는 import 문은 더 이상 존재하지 않습니다.
추가 DTO/서비스에서도 구 임포트가 모두 제거된 것을 확인했습니다.

  • rg -n --fixed-strings 'org.yapp.apis.util.AuthorExtractor' → 검색 결과 없음
  • rg -n --fixed-strings 'org.yapp.apis.util.IsbnConverter' → 검색 결과 없음
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1)

3-3: 레거시 util 패키지 참조 없음 확인 완료
org.yapp.apis.util.AuthorExtractororg.yapp.apis.util.IsbnConverter를 참조하는 import 구문이 Kotlin 파일 전체에서 검색되지 않습니다. 남아 있는 레거시 코드가 없어 컴파일 오류를 일으킬 우려가 없습니다.

global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt (2)

3-6: 어노테이션 기본 설정 적절합니다

  • Target: FUNCTION, Retention: RUNTIME, 문서화: MustBeDocumented → Swagger 문서용 커스텀 어노테이션에 적합합니다.

1-6: Swagger 보안 제거 어노테이션 적용 확인 완료

  • AuthControllerApi 인터페이스의 소셜 로그인(카카오/애플 로그인) 및 토큰 갱신 API에 @DisableSwaggerSecurity 어노테이션이 적용되어 있습니다.
  • SwaggerConfig.ktsecurityOperationCustomizer()에서 해당 어노테이션이 붙은 메서드 또는 인터페이스를 감지하여
    operation.security = emptyList()로 보안 요구사항을 제거하는 로직이 정상 동작함을 확인했습니다.

위 사항으로 의도한 대로 Swagger 문서에서 보안 요구사항이 제거되고 있으므로 추가 조치가 필요하지 않습니다.

global-utils/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt (1)

6-11: 레거시 @service 사용 발견 — @ApplicationService로 전환 필요

스크립트 검증 결과, 아래 클래스에서 여전히 @Service가 남아 있습니다. 해당 어노테이션을 @ApplicationService로 교체해 주세요.

• gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenServiceImpl.kt (라인 10)

예시 변경:

- @Service
+ @ApplicationService

Likely an incorrect or invalid review comment.

apis/src/main/kotlin/org/yapp/apis/auth/service/UserWithdrawalService.kt (1)

6-10: 애노테이션 교체 LGTM

@ApplicationService 적용으로 서비스/검증 설정이 간결해졌습니다. 추가 조치 필요 없어 보입니다.

apis/src/main/kotlin/org/yapp/apis/home/service/HomeService.kt (1)

6-10: 애노테이션 교체 LGTM

@ApplicationService 적용이 적절합니다.

apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (1)

9-12: 애노테이션 교체 LGTM

서비스 계층 통합 및 @Validated 포함 효과를 잘 활용했습니다.

apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt (1)

11-15: 검증 완료: ApplicationService 메타 어노테이션 및 설정 확인됨

  • global-utils/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt
    @service, @validated 포함
    @target(AnnotationTarget.CLASS), @retention(AnnotationRetention.RUNTIME) 선언
    • @MustBeDocumented 처리
  • 따라서 이 어노테이션이 적용된 서비스 클래스의 메서드 파라미터에 대한 @Valid 검증은 기존과 동일하게 Spring Method Validation AOP로 동작합니다.

추가 조치 불필요합니다.

apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt (1)

7-7: 임포트 변경(정렬/재배치) 이상 없음

기능 영향 없음으로 보입니다.

apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt (2)

11-11: @ApplicationService 적용 관련 공통 점검 사항

  • @Service/@validated 메타 포함, RUNTIME 보존, all-open 등록 여부 확인을 권장합니다.
  • Swagger 보안 비활성화 기능(본 PR 주제)과는 무관한 변경이므로, PR 스코프 병합 의도도 함께 확인 부탁드립니다.

위 파일들에 공통으로 올려둔 점검 스크립트로 일괄 확인 가능합니다.

Also applies to: 20-20


10-10: 패키지 경로 변경 검증 완료
org.yapp.apis.book.util.IsbnConverter로 이동되었으며, 이전 경로(org.yapp.apis.util.IsbnConverter)에 대한 잔존 참조가 없음을 확인했습니다.

apis/src/main/kotlin/org/yapp/apis/user/service/UserService.kt (1)

6-6: DTO 임포트 정리 OK

기능 영향 없음으로 보입니다.

Also applies to: 8-8

apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt (2)

9-9: Import 적정성 확인

OperationCustomizer import 추가 적절합니다. 별도 이슈 없음.


77-79: OpenAPI 사양상 올바른 보안 비활성화 처리

operation.security = emptyList()는 전역 보안 요구사항을 해당 Operation에서 무효화하는 올바른 방법입니다. 의도대로 Swagger에서 인증 요구가 표시되지 않습니다.

import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBookDomainService
import org.yapp.domain.userbook.UserBookSortType
import org.yapp.globalutils.annotation.ApplicationService
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@ApplicationService 적용 시 Method Validation 의존 강함 → 반드시 @validated 포함 확인

  • 본 서비스는 메서드 파라미터에 @Valid를 적극 사용하고 있습니다. 클래스 레벨 @validated가 빠지면 메서드 검증이 동작하지 않습니다.
  • 따라서 @ApplicationService 메타 어노테이션에 @validated 포함 여부와 all-open 등록 여부를 반드시 확인해주세요.

위 서비스에서 검증이 실제로 동작하는지 점검하려면, 다음과 같이 컴파일 타임이 아닌 리포지토리 내 선언을 확인하세요.

Also applies to: 18-18


🏁 Script executed:

#!/bin/bash
set -e

echo "1) @ApplicationService 내 @Validated 포함 여부"
fd ApplicationService.kt | xargs rg -n "@Validated"

echo
echo "2) @UserBookService가 프록시 대상인지(클래스 final 여부 간접 확인)"
# kotlin-spring(all-open) 사용 시, 커스텀 어노테이션은 별도 등록 필요
fd -a "build.gradle.kts" -X rg -n "allOpen\\s*\\{|ApplicationService"

Length of output: 352


클래스 레벨 메서드 검증 및 프록시 처리 설정 확인 및 수정 필요

검증 결과:

  • apis/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt 내에 이미 @Validated가 선언되어 있어 메서드 검증 어노테이션은 포함되어 있습니다.
  • ❌ 그러나 build.gradle.kts에서 Kotlin All-Open 플러그인에 ApplicationService 어노테이션이 등록되어 있지 않아, 해당 어노테이션이 달린 클래스가 final 상태로 남아 스프링 프록시가 적용되지 않습니다.

수정 제안:

  • build.gradle.kts에 Kotlin All-Open 설정에 아래 코드를 추가하세요.
    plugins {
      kotlin("jvm") version "1.x.x"
      kotlin("plugin.spring") version "1.x.x"
      kotlin("plugin.allopen") version "1.x.x"
    }
    
    allOpen {
      annotation("org.yapp.globalutils.annotation.ApplicationService")
    }

이렇게 하면 @ApplicationService가 붙은 클래스에 자동으로 스프링 프록시(AOP)가 적용되어 메서드 레벨 유효성 검증(@Valid, @Validated)이 정상 동작합니다.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt at line
15, the @ApplicationService annotation is correctly imported but the Kotlin
All-Open plugin configuration in build.gradle.kts is missing the registration of
this annotation. To fix this, update build.gradle.kts by adding the
kotlin("plugin.allopen") plugin and include the allOpen block with
annotation("org.yapp.globalutils.annotation.ApplicationService") so that classes
annotated with @ApplicationService are not final and can be proxied by Spring,
enabling method-level validation to work properly.

import org.springframework.stereotype.Service
import org.yapp.apis.readingrecord.dto.response.SeedStatsResponse
import org.yapp.domain.readingrecordtag.ReadingRecordTagDomainService
import org.yapp.globalutils.annotation.ApplicationService
Copy link

@coderabbitai coderabbitai bot Aug 9, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@ApplicationService로의 대체 자체는 타당. 단, all-open 설정과 메서드 검증(@validated) 유지 여부 확인 필요

  • Kotlin에서 커스텀 메타 어노테이션을 사용할 때, Spring 프록시가 필요한 경우(예: 메서드 검증/트랜잭션) all-open 플러그인에 해당 어노테이션을 등록하지 않으면 클래스가 final로 남아 프록시 생성에 문제가 생길 수 있습니다.
  • @ApplicationService 안에 아래가 포함되어 있는지 확인 부탁드립니다:
  • 본 PR 목적(DisableSwaggerSecurity)과는 직접 관련이 없는 리팩토링이므로, 스코프 분리 의도(또는 동시 반영 의도)도 한번 확인 부탁드립니다.

다음 스크립트로 @ApplicationService 정의와 all-open 설정 포함 여부를 빠르게 점검할 수 있습니다.

all-open 설정 예시 (Gradle Kotlin DSL):

plugins {
  kotlin("plugin.spring")
  kotlin("plugin.allopen")
}

allOpen {
  annotation("org.yapp.globalutils.annotation.ApplicationService")
}

Also applies to: 9-9


🏁 Script executed:

#!/bin/bash
set -e

echo "1) @ApplicationService 정의 위치/내용 확인"
fd ApplicationService.kt | xargs -I{} sh -c 'echo "==> {}"; sed -n "1,200p" "{}" | sed -n "1,120p"'

echo
echo "2) all-open 플러그인에 @ApplicationService가 등록되어 있는지 확인"
fd -a "build.gradle.kts" -X rg -n "allOpen\\s*\\{|annotation\\(\"org\\.yapp\\.globalutils\\.annotation\\.ApplicationService\"\\)"
fd -a "build.gradle" -X rg -n "allOpen|ApplicationService"

echo
echo "3) @Validated를 @ApplicationService가 포함하는지 확인"
fd ApplicationService.kt | xargs rg -n "@Validated"

Length of output: 810


all-open 설정에 @ApplicationService 등록이 필요합니다

ApplicationService 자체에는

  • @Service
  • @Validated
  • @Target(AnnotationTarget.CLASS)
  • @Retention(AnnotationRetention.RUNTIME)
    가 포함되어 있어 Bean 등록 및 메서드 검증 요건은 충족됩니다.
    다만, build.gradle(.kts) 내 all-open 플러그인 설정에서 org.yapp.globalutils.annotation.ApplicationService를 열어주는 부분이 누락된 상태입니다. 이대로 두면 Spring AOP 프록시 생성 시 클래스가 final 상태여서 프록시 생성에 실패할 수 있습니다.

수정 제안:

  • build.gradle.kts 혹은 build.gradle의 allOpen 블록에 아래를 추가해주세요.
    allOpen {
      annotation("org.yapp.globalutils.annotation.ApplicationService")
    }
  • 이 리팩토링이 본 PR 목표(DisableSwaggerSecurity)와 별도 Scope인지, 동시 반영 의도인지도 한번 검토 부탁드립니다.
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordTagService.kt
at line 5, the @ApplicationService annotation requires registration in the
all-open plugin configuration to allow Spring AOP proxy creation by making the
class non-final. To fix this, add the line
annotation("org.yapp.globalutils.annotation.ApplicationService") inside the
allOpen block in your build.gradle.kts or build.gradle file. Also, review
whether this change fits the current PR scope or should be handled separately.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai

#87 (comment)

이 의견 한번 봐줘

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the PR comment using GitHub CLI
gh api repos/YAPP-Github/Reed-Server/pulls/87/comments | jq '.[] | select(.id == 2264602988)'

Length of output: 6764


@hoonyworld, 네, 확인했습니다. 제가 UserService.kt에 대해 남긴 의견과 동일한 이슈입니다.

프로젝트 전체적으로 동일한 문제가 있습니다:

  • 이번 PR에서 여러 Service 클래스들이 @Service + @Validated에서 @ApplicationService로 변경되었는데
  • Gradle에 kotlin-spring 또는 all-open 플러그인 설정이 없어서 이 클래스들이 final로 컴파일됩니다
  • 결과적으로 Spring AOP 프록시 생성이 실패하여 @Validated가 작동하지 않을 수 있습니다

해결 방법:

// build.gradle.kts에 추가
plugins {
    kotlin("plugin.spring") version "버전"
    // 또는
    kotlin("plugin.allopen") version "버전"
}

allOpen {
    annotation("org.yapp.globalutils.annotation.ApplicationService")
}

이 설정을 추가하시면 @ApplicationService가 적용된 모든 Service 클래스들이 정상적으로 open으로 컴파일되어 Spring AOP가 작동하게 됩니다.

import org.yapp.apis.user.dto.response.UserAuthInfoResponse
import org.yapp.apis.user.dto.response.UserProfileResponse
import org.yapp.domain.user.UserDomainService
import org.yapp.globalutils.annotation.ApplicationService
Copy link

@coderabbitai coderabbitai bot Aug 9, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@ApplicationService 적용 시 프록시/검증/스코프 확인

  • @ApplicationService에 @Service/@validated 포함, RUNTIME 보존, all-open 등록 여부 확인 요청.
  • 아키텍처적으로, 외부 API 오케스트레이션(트랜잭션 없음)과 DB 변경 실행자(트랜잭션 적용) 역할을 분리하는 팀 합의가 있는 것으로 알고 있습니다. @ApplicationService를 “실행자(Service)” 계층에만 적용하고, 오케스트레이터(UseCase)는 별도 스테레오타입을 유지하는지 검토해 주세요.

필요 시, gradle 설정과 @ApplicationService 정의 확인 스크립트를 재사용하세요.

Also applies to: 14-14


아래 스크립트를 실행해 @ApplicationService 어노테이션 정의·메타정보, all-open 설정 등록 여부, 그리고 사용 위치를 확인해주세요.


🏁 Script executed:

#!/bin/bash
set -e
# 1) ApplicationService 어노테이션 정의 및 메타 어노테이션 확인
rg -n "annotation class ApplicationService" -n .
# (출력된 경로를 복사해 다음 명령에 적용)
awk '1,/^\)/' $(rg --files-with-matches "annotation class ApplicationService" -l)

# 2) Gradle all-open 플러그인 설정에 ApplicationService 포함 여부 확인
rg -C2 "allOpen" -n .

# 3) @ApplicationService 사용 위치(UseCase vs Service) 확인
rg -n "@ApplicationService" -n apis/src

Length of output: 665


아래 스크립트를 실행해 @ApplicationService 사용 위치와 Gradle 플러그인 설정(kotlin-spring/all-open) 적용 여부를 다시 확인해주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1) @ApplicationService 사용 위치 확인
echo "---- @ApplicationService 사용 위치 ----"
rg -n "@ApplicationService" -n apis/src

# 2) Gradle 빌드스크립트에서 kotlin-spring 또는 all-open 플러그인 설정 확인
echo "---- Gradle 플러그인 설정(kotlin-spring/all-open) ----"
find . -type f \( -name "*.gradle" -o -name "*.gradle.kts" \) -print0 \
  | xargs -0 grep -n -E "kotlin-spring|allOpen|all-open|kotlin-allopen" || echo "플러그인 설정 없음"

Length of output: 1629


@ApplicationService 메타정보 및 사용 위치 확인 결과

  • ApplicationService 어노테이션은
    @service, @validated 포함
    • RUNTIME 보존으로 잘 정의되어 있습니다.
  • 그러나 Gradle 스크립트에서 kotlin-spring/all-open 플러그인이 적용되어 있지 않아
    ApplicationService가 붙은 클래스가 기본적으로 final로 컴파일됩니다.
    이로 인해 Spring AOP 프록시(@validated 등) 및 메소드 레벨 검증이 동작하지 않을 수 있습니다.
  • 서비스 계층(apis/src/**/service)에는 @ApplicationService만 적용되어 있고
    UseCase(오케스트레이터) 레이어에는 별도 스테레오타입이 유지되고 있어
    기존 아키텍처 가이드라인에 부합합니다.

조치 요청
build.gradle(.kts)에 kotlin-spring 또는 all-open 플러그인 설정을 추가하여
ApplicationService 어노테이션 대상 클래스를 open 처리하거나,
직접 open 키워드 선언 방안 검토 후 반영해 주세요.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/service/UserService.kt at line 11,
the @ApplicationService annotation is used but the kotlin-spring or all-open
plugin is not applied in the Gradle build script, causing classes annotated with
@ApplicationService to be compiled as final and preventing Spring AOP proxies
from working properly. To fix this, update the build.gradle(.kts) file to
include the kotlin-spring or all-open plugin configuration so that classes with
@ApplicationService are compiled as open, or alternatively, explicitly declare
the service classes as open in the Kotlin code.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai

plugins.withId(Plugins.Kotlin.ALLOPEN) {
    extensions.configure<org.jetbrains.kotlin.allopen.gradle.AllOpenExtension> {
        annotation("jakarta.persistence.Entity")
        annotation("jakarta.persistence.MappedSuperclass")
        annotation("jakarta.persistence.Embeddable")
    }
}

이미 루트 build.gradle에 allOpen이 되어있는데, 이러면 주의할 점이 없지?

Copy link

Choose a reason for hiding this comment

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

@hoonyworld 현재 allOpen 설정은 JPA 엔티티 관련 어노테이션에만 적용되어 있어, @ApplicationService가 붙은 서비스 클래스들은 여전히 final로 컴파일됩니다.

Spring AOP 프록시(@validated, @transactional 등)가 제대로 동작하려면 다음과 같이 설정을 추가해야 합니다:

plugins.withId(Plugins.Kotlin.ALLOPEN) {
    extensions.configure<org.jetbrains.kotlin.allopen.gradle.AllOpenExtension> {
        annotation("jakarta.persistence.Entity")
        annotation("jakarta.persistence.MappedSuperclass")
        annotation("jakarta.persistence.Embeddable")
        // ApplicationService 추가
        annotation("org.yapp.globalutils.annotation.ApplicationService")
    }
}

또는 kotlin-spring 플러그인을 적용하면 @service를 포함한 Spring 스테레오타입 어노테이션들이 자동으로 open 처리됩니다:

plugins {
    kotlin("plugin.spring") version "버전"
}

현재 상태로는 @validated가 메소드 레벨에서 동작하지 않을 수 있으니 꼭 설정 추가가 필요합니다.


🧠 Learnings used
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Copy link
Member Author

Choose a reason for hiding this comment

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

package org.yapp.globalutils.annotation

import org.springframework.stereotype.Service
import org.springframework.validation.annotation.Validated

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Service
@Validated
annotation class ApplicationService
package org.yapp.globalutils.annotation

import org.springframework.stereotype.Component

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component
annotation class DomainService

근데 커스텀 어노테이션에도 스프링 프리셋의 어노테이션들이 사용되고 잇는데 이러면 같이 allOpen의 대상으로 되는거 아닌가?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai

의견줘

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if kotlin-spring plugin is applied anywhere in the project
echo "=== Checking for kotlin-spring plugin ==="
find . -type f \( -name "*.gradle" -o -name "*.gradle.kts" \) -print0 | xargs -0 grep -l "kotlin.*spring" || echo "kotlin-spring plugin not found"

# Check current allOpen configuration
echo -e "\n=== Current allOpen configuration ==="
find . -type f \( -name "*.gradle" -o -name "*.gradle.kts" \) -print0 | xargs -0 grep -A5 -B2 "allOpen\|AllOpen"

# Check if there are any open class declarations in service files
echo -e "\n=== Checking for 'open' keyword in service classes ==="
rg "open class.*Service" apis/src --type kotlin || echo "No open service classes found"

# Find all classes using @ApplicationService or @DomainService
echo -e "\n=== Classes using custom annotations ==="
rg "@(ApplicationService|DomainService)" apis/src --type kotlin -A1

Length of output: 3848


🌐 Web query:

Does Kotlin allOpen plugin automatically apply to custom annotations that have Spring stereotype meta-annotations like @Service or @Component?

💡 Result:

Yes, the Kotlin allOpen compiler plugin can be configured to automatically apply to custom annotations that are meta-annotated with Spring's stereotype annotations, such as @Service or @Component. This is achieved through meta-annotation support, which allows the allOpen plugin to recognize and process annotations that are themselves annotated with other annotations.

How It Works:

  1. Meta-Annotation Support: In Kotlin, you can create a custom annotation and annotate it with existing annotations. For example:

    @Component
    annotation class MyCustomAnnotation

    In this case, @MyCustomAnnotation is meta-annotated with @Component.

  2. Configuring the allOpen Plugin: To ensure that classes annotated with @MyCustomAnnotation are treated as open by the Kotlin compiler, you can configure the allOpen plugin to recognize @MyCustomAnnotation as an annotation that should make classes open.

    In your build.gradle file, you can add:

    allOpen {
        annotation("com.example.MyCustomAnnotation")
    }

    This configuration tells the allOpen plugin to treat any class annotated with @MyCustomAnnotation as open, even though @MyCustomAnnotation is not directly listed among the annotations that typically trigger the allOpen behavior.

Example Usage:

@Component
annotation class MyCustomAnnotation

@MyCustomAnnotation
class MyService {
    // Class members
}

In this example, MyService is annotated with @MyCustomAnnotation, which is meta-annotated with @Component. By configuring the allOpen plugin as shown above, the MyService class and its members will be treated as open by the Kotlin compiler, allowing Spring to proxy them as needed.

References:

By leveraging meta-annotation support with the allOpen plugin, you can effectively manage the openness of classes in Kotlin, especially when working with custom annotations that are meta-annotated with Spring's stereotype annotations.


@hoonyworld 좋은 지적입니다! 하지만 allOpen 플러그인은 메타 어노테이션을 자동으로 인식하지 않습니다.

현재 상황:

  • @ApplicationService@Service를 메타 어노테이션으로 가지고 있어도
  • allOpen 설정에 JPA 어노테이션만 등록되어 있어서
  • 서비스 클래스들이 여전히 final로 컴파일됩니다

해결 방법:

plugins.withId(Plugins.Kotlin.ALLOPEN) {
    extensions.configure<org.jetbrains.kotlin.allopen.gradle.AllOpenExtension> {
        annotation("jakarta.persistence.Entity")
        annotation("jakarta.persistence.MappedSuperclass")
        annotation("jakarta.persistence.Embeddable")
        // 커스텀 어노테이션 명시적 추가 필요
        annotation("org.yapp.globalutils.annotation.ApplicationService")
        annotation("org.yapp.globalutils.annotation.DomainService")
    }
}

또는 kotlin-spring 플러그인을 적용하면 @Service, @Component 메타 어노테이션을 자동 인식합니다.

현재 상태로는 @Validated의 메소드 레벨 검증이 제대로 동작하지 않을 수 있으니, allOpen 설정 추가를 권장드립니다.


🧠 Learnings used
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Copy link
Member Author

@move-hoon move-hoon Aug 9, 2025

Choose a reason for hiding this comment

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

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.stereotype.Service
import org.yapp.globalutils.annotation.ApplicationService
import java.lang.reflect.Modifier

// 테스트를 위한 대상 클래스들
@ApplicationService
class TestApplicationService {
    fun someMethod() {}
}

@Service
class TestStandardService {
    fun someMethod() {}
}

class NormalFinalClass {
    fun someMethod() {}
}


class AllOpenVerificationTest {

    @Test
    @DisplayName("커스텀 어노테이션(@ApplicationService)이 붙은 클래스는 open 상태여야 한다")
    fun customAnnotationShouldBeOpen() {
        // given
        val customServiceClass = TestApplicationService::class.java

        // when: 클래스의 수정자(modifiers)에서 final 여부를 확인
        val isFinal = Modifier.isFinal(customServiceClass.modifiers)

        // then: all-open이 적용되었다면 final이 아니어야 함 (테스트 통과)
        assertThat(isFinal).isFalse()
    }

    @Test
    @DisplayName("표준 Spring 어노테이션(@Service)이 붙은 클래스는 open 상태여야 한다")
    fun standardSpringAnnotationShouldBeOpen() {
        // given
        val standardServiceClass = TestStandardService::class.java

        // when
        val isFinal = Modifier.isFinal(standardServiceClass.modifiers)

        // then: 'kotlin-spring' 프리셋에 의해 final이 아니어야 함 (테스트 통과)
        assertThat(isFinal).isFalse()
    }

    @Test
    @DisplayName("아무 어노테이션도 없는 일반 클래스는 final 상태여야 한다")
    fun normalClassShouldBeFinal() {
        // given
        val normalClass = NormalFinalClass::class.java

        // when
        val isFinal = Modifier.isFinal(normalClass.modifiers)

        // then: Kotlin의 기본 동작에 따라 final이어야 함 (테스트 통과)
        assertThat(isFinal).isTrue()
    }
}

나는 위 3개의 테스트를 실행했고 모두 성공으로 나왔는데 의견줄래? @coderabbitai

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

@hoonyworld 테스트 결과를 확인했습니다. 제가 잘못 분석했네요!

테스트가 모두 통과한 것으로 보아:

  • @ApplicationService가 붙은 클래스들이 정상적으로 open으로 컴파일되고 있습니다
  • Spring AOP 프록시(@validated 등)가 정상 동작할 것입니다
  • 현재 설정에 문제가 없습니다

kotlin-spring 플러그인이 이미 적용되어 있거나, 다른 곳에 allOpen 설정이 추가로 있어서 @Service 메타 어노테이션이 인식되고 있는 것으로 보입니다.

추가 설정이 필요 없으며, 현재 상태로 안전하게 사용 가능합니다. 실증적인 테스트로 검증해주셔서 감사합니다! 👍


🧠 Learnings used
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

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: 22

🔭 Outside diff range comments (4)
apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt (1)

19-19: ‘!!’ 사용은 NPE를 유발할 수 있습니다.

검증 이전에 호출되면 런타임 NPE가 발생합니다. 필드를 non-null로 전환해 !!를 제거하세요.

아래는 전체 권장 형태의 예시입니다(파일 외부 변경을 수반):

@Schema(
    name = "TermsAgreementRequest",
    description = "Request to update terms agreement status"
)
data class TermsAgreementRequest(
    @Schema(
        description = "Whether the user agrees to the terms of service",
        example = "true",
        requiredMode = Schema.RequiredMode.REQUIRED
    )
    val termsAgreed: Boolean
) {
    fun validTermsAgreed(): Boolean = termsAgreed
}
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt (2)

23-27: pageNumber 유효성 검증 누락(NPE 가능성) — @NotNull 추가 필요

pageNumberInt?인데 @Min/@Max만 적용되어 있어 null일 때 제약이 무시됩니다. 이후 validPageNumber()에서 !!로 NPE가 발생할 수 있습니다. 최소 수정으로 @NotNull을 추가해 주세요.

적용 제안(diff):

 import io.swagger.v3.oas.annotations.media.Schema
 import jakarta.validation.constraints.Max
 import jakarta.validation.constraints.Min
+import jakarta.validation.constraints.NotNull
 import jakarta.validation.constraints.NotBlank
 import jakarta.validation.constraints.Size
@@
-    @field:Min(1, message = "페이지 번호는 1 이상이어야 합니다.")
-    @field:Max(9999, message = "페이지 번호는 9999 이하여야 합니다.")
+    @field:NotNull(message = "페이지 번호는 필수입니다.")
+    @field:Min(1, message = "페이지 번호는 1 이상이어야 합니다.")
+    @field:Max(9999, message = "페이지 번호는 9999 이하여야 합니다.")
     @Schema(description = "현재 읽은 페이지 번호", example = "42", required = true)
     val pageNumber: Int? = null,

추가 개선안:

  • 더 견고하게 하려면 타입을 Int(non-null)로 바꾸고 기본값 제거, !! 제거를 고려해 주세요(역직렬화 단계에서 바로 검출).

42-45: Non-null 강제(!! ) 제거 권장

검증 미적용 경로에서 !!는 즉시 NPE를 유발합니다. 아래 중 하나로 개선을 권장합니다.

  • 검증을 강제할 수 없다면 requireNotNull(pageNumber) { "페이지 번호는 필수입니다." }처럼 명시적 체크로 교체.
  • 필드 타입을 non-null로 전환하여(예: Int, String) !! 자체를 제거.
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1)

111-127: Null 안전성 위반으로 컴파일 불가

title 파라미터는 String? 타입인데, 아래에서 non-null 타입으로 그대로 전달하고 있어 컴파일이 실패합니다.

- require(!title.isNullOrBlank()) { "Title is required" }
- return BookSummary(
-     ...
-     title = title,
+ val nonNullTitle = title ?: throw IllegalArgumentException("Title is required")
+ return BookSummary(
+     ...
+     title = nonNullTitle,

require만으로는 스마트 캐스트가 일어나지 않으므로 위와 같이 명시적으로 널을 해소해 주세요.

♻️ Duplicate comments (2)
apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt (2)

15-15: @ApplicationService 메타 구성 재확인

위 UserSignInService 코멘트와 동일하게, @ApplicationService가 스캔/검증 메타 어노테이션을 포함하는지 점검 부탁드립니다. (중복 호출 방지를 위해 한 곳에서만 수정하면 됩니다.)


13-13: 와일드카드 import 정리 제안 (이전 의견과 동일)

팀 컨벤션에 따라 명시적 import 권장합니다.

-import java.util.*
+import java.util.UUID
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40c0bbf and ce96fb3.

📒 Files selected for processing (20)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (4 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookPageResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/home/dto/response/UserHomeResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt (2 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-06T08:18:36.255Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt
  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt
📚 Learning: 2025-07-08T14:24:44.340Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#31
File: apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt:13-13
Timestamp: 2025-07-08T14:24:44.340Z
Learning: IntelliJ IDEA에서 와일드카드 임포트를 방지하는 설정 방법: File > Settings > Editor > Code Style > Java > Imports 탭에서 "Class count to use import with '*'" 값을 높게 설정(예: 500)하면 해당 임계값 이하에서는 와일드카드 임포트가 자동으로 사용되지 않음. 와일드카드 임포트 관련 리뷰 시 이 IDE 설정 방법도 함께 안내하면 좋음.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt
📚 Learning: 2025-07-08T14:24:44.340Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#31
File: apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt:13-13
Timestamp: 2025-07-08T14:24:44.340Z
Learning: hoonyworld가 와일드카드 임포트 관련하여 IDE 설정을 통한 자동 최적화 방법을 추천했습니다. 향후 임포트 관련 리뷰 시 IDE 설정 방법도 함께 안내하는 것이 좋겠습니다.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt
⏰ 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-validation
🔇 Additional comments (12)
apis/src/main/kotlin/org/yapp/apis/home/dto/response/UserHomeResponse.kt (2)

9-10: 주석 파라미터 순서 조정은 기능 영향 없음, 문서 메타데이터 일관성 개선으로 LGTM

스키마 메타데이터 가독성/일관성 개선으로 보이며, 기능/런타임 영향은 없습니다.


9-10: 스키마 이름 중복 없음 확인

현재 리포지토리 내에서 @Schema(name = "UserHomeResponse") 선언은 해당 파일 한 곳에서만 사용되고 있어, 전역 충돌 우려가 없습니다.
추가 조치 필요 없습니다.

apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt (2)

9-20: 스키마 이름 변경 승인 및 중복 검증 권고

@Schema(name = "CreateReadingRecordRequest") 변경 자체는 API 문서 일관성에 부합하므로 승인합니다. 다만 ripgrep의 PCRE 미지원 등으로 인해 앞서 실행한 스크립트가 중복을 제대로 탐지하지 못했을 가능성이 있으니, 전역 중복 여부를 수동 또는 아래 업데이트된 스크립트로 한 번 더 확인해 주세요.

• 검증 대상 파일
– apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt 및 기타 DTO 파일 전체

• 업데이트된 중복 탐지 스크립트 예시

#!/bin/bash
# 모든 Kotlin 파일에서 @Schema(name="…") 값을 추출해 중복 확인
fd -e kt . | xargs rg -o '@Schema\([^)]*name\s*=\s*"[^"]+"\)' \
  | grep -o 'name\s*=\s*"[^"]\+"' \
  | sed -E 's/name\s*=\s*"([^"]+)"/\1/' \
  | sort \
  | uniq -c \
  | awk '$1>1{print "DUPLICATE:", $2, "(count="$1")"}'

• 수동 검증
– IDE나 에디터에서 @Schema(name = 검색 후 중복 이름이 없는지 최종 확인


25-36: SpringDoc/OpenAPI requiredMode 지원 여부 확인 요청
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) 사용 전, 프로젝트에서 사용하는 SpringDoc/OpenAPI 및 Swagger Core 의존성 버전이 해당 속성을 지원하는지 확인해 주세요.

점검 대상 파일:

  • build.gradle / build.gradle.kts (루트 및 하위 모듈)
  • pom.xml (Maven 프로젝트인 경우)

지원 버전 예시:

implementation "org.springdoc:springdoc-openapi-ui:1.6.0" // 최소 1.6.0 이상

확인이 완료되면 다음과 같이 변경 가능합니다:

-    @Schema(description = "현재 읽은 페이지 번호", example = "42", required = true)
+    @Schema(description = "현재 읽은 페이지 번호", example = "42", requiredMode = Schema.RequiredMode.REQUIRED)
@@
-    @Schema(description = "기억에 남는 문장", example = "이것은 기억에 남는 문장입니다.", required = true)
+    @Schema(description = "기억에 남는 문장", example = "이것은 기억에 남는 문장입니다.", requiredMode = Schema.RequiredMode.REQUIRED)
@@
-    @Schema(description = "감상평", example = "이 책은 매우 인상적이었습니다.", required = true)
+    @Schema(description = "감상평", example = "이 책은 매우 인상적이었습니다.", requiredMode = Schema.RequiredMode.REQUIRED)
apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (1)

12-12: 확인 완료: @ApplicationService 메타 어노테이션에 @service@validated 포함됨

global-utils/src/main/kotlin/org/yapp/globalutils/annotation/ApplicationService.kt (9~10행)에서
@service@validated가 선언된 것이 확인되었습니다.
@transactional 및 Bean Validation이 정상 작동하므로 추가 조치는 필요 없습니다.

apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (1)

7-10: 스키마 네이밍/설명 추가 LGTM

모델 이름과 설명이 명확합니다. 변경 문제 없습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (1)

10-13: 클래스 수준 스키마 메타데이터 추가 LGTM

모델 네이밍/설명 표준화에 부합합니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt (1)

9-10: @Schema title → name 변경 LGTM

스키마 이름 표준화에 적합합니다. 동일 디렉터리 내 DTO에도 동일 규칙 적용됐는지 최종 확인 부탁드립니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt (1)

9-10: @Schema title → name 변경 LGTM

문서화 메타데이터 표준화에 부합합니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (1)

9-10: @Schema title → name 변경 LGTM

일관된 스키마 네이밍 규칙으로 정리된 점 좋습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt (2)

14-16: Schema.name 변경 의도 확인 및 title 속성 병행 제안

@Schema(name=…)가 Swagger 컴포넌트 식별명으로 사용되므로, 문서 표시용 제목은 별도의 title 속성으로 병행 설정하는 것이 안전합니다.

확인 포인트:

  • name 속성의 사용 의도(컴포넌트 식별용 vs. 화면 표시용)
  • 프로젝트 내 다른 DTO에서 title 속성 사용 여부

제안하는 변경 예시:

 @Schema(
-    name = "UpsertUserBookRequest",
+    name  = "UpsertUserBookRequest",
+    title = "UpsertUserBookRequest",
     description = "사용자 서재에 도서를 생성하거나 기존 도서 정보를 수정하는 내부 API 요청 (주로 내부 서비스에서 사용)"
 )

14-16: DisableSwaggerSecurity 어노테이션 적용 확인됨

AuthControllerApi.kt의 Swagger 로그인/토큰 갱신 엔드포인트에 이미 @DisableSwaggerSecurity가 선언되어 있어, 본 PR에서 별도 추가 조치는 필요하지 않습니다.

  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt:25 – 로그인 API 메서드
  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt:52 – 토큰 갱신 API 메서드

Comment on lines 21 to 31
val initialUserResponse = userAccountService.findOrCreateUser(request)

appleRefreshToken?.let {
userAccountService.updateAppleRefreshToken(createUserResponse.id, it)
}

return createUserResponse
return appleRefreshToken?.let { token ->
userAccountService.updateAppleRefreshToken(
SaveAppleRefreshTokenRequest.of(
initialUserResponse,
token
)
)
} ?: initialUserResponse
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

빈 문자열 토큰 방지 및 트리밍 권장

빈 문자열이 전달되면 Validation 에러를 내는 대신 아예 업데이트 호출을 생략하면 불필요한 예외와 로깅을 줄일 수 있습니다.

-        return appleRefreshToken?.let { token ->
+        return appleRefreshToken
+            ?.trim()
+            ?.takeIf { it.isNotEmpty() }
+            ?.let { token ->
             userAccountService.updateAppleRefreshToken(
                 SaveAppleRefreshTokenRequest.of(
                     initialUserResponse,
                     token
                 )
             )
-        } ?: initialUserResponse
+        } ?: initialUserResponse
📝 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
val initialUserResponse = userAccountService.findOrCreateUser(request)
appleRefreshToken?.let {
userAccountService.updateAppleRefreshToken(createUserResponse.id, it)
}
return createUserResponse
return appleRefreshToken?.let { token ->
userAccountService.updateAppleRefreshToken(
SaveAppleRefreshTokenRequest.of(
initialUserResponse,
token
)
)
} ?: initialUserResponse
}
val initialUserResponse = userAccountService.findOrCreateUser(request)
return appleRefreshToken
?.trim()
?.takeIf { it.isNotEmpty() }
?.let { token ->
userAccountService.updateAppleRefreshToken(
SaveAppleRefreshTokenRequest.of(
initialUserResponse,
token
)
)
} ?: initialUserResponse
}
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt around
lines 21 to 31, the code currently attempts to update the Apple refresh token
even if the token is an empty string, which can cause unnecessary validation
errors and logging. Modify the logic to first trim the appleRefreshToken and
check if it is not blank before calling
userAccountService.updateAppleRefreshToken. If the token is blank or empty after
trimming, skip the update call and directly return the initialUserResponse.

Comment on lines 11 to 15
@Schema(
description = "Whether the user agrees to the terms of service",
example = "true",
required = true
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

OpenAPI ‘required’ 표기는 최신 springdoc/openapi에선 requiredMode로 명시하는 것을 권장합니다.

호환성을 위해 required = true 대신 requiredMode = Schema.RequiredMode.REQUIRED로 변경을 권장합니다.

적용 diff:

 @Schema(
     description = "Whether the user agrees to the terms of service",
     example = "true",
-    required = true
+    requiredMode = Schema.RequiredMode.REQUIRED
 )
📝 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
@Schema(
description = "Whether the user agrees to the terms of service",
example = "true",
required = true
)
@Schema(
description = "Whether the user agrees to the terms of service",
example = "true",
requiredMode = Schema.RequiredMode.REQUIRED
)
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt
between lines 11 and 15, replace the deprecated 'required = true' attribute in
the @Schema annotation with 'requiredMode = Schema.RequiredMode.REQUIRED' to
comply with the latest springdoc/openapi standards and ensure compatibility.

💡 Verification agent

🧩 Analysis chain

문서/메시지 언어 일관성 확인 요청

프로젝트 전반이 ko-KR을 주로 사용한다면, description과 Validation 메시지(현재 영어)를 한국어 또는 메시지 키로 통일하는 것을 고려해 주세요.


프로젝트 내 다른 @Schema(description = …) 에서 사용된 언어(한글/영어) 현황을 파악하기 위해 아래 스크립트를 실행해주세요.
이 결과를 바탕으로 일관된 언어 사용 여부를 검토하겠습니다.


🏁 Script executed:

#!/bin/bash
# 모든 @Schema 어노테이션의 description 필드와 그 앞뒤 2줄을 검색합니다.
rg '@Schema' -n -C2 apis/src/main/kotlin

Length of output: 93880


문서 언어 일관성 검토 필요
프로젝트 전반에 @Schema(description = "") 필드에 한국어와 영어가 혼재되어 있습니다. 주요 English description DTO는 아래와 같으니, 한국어 또는 메시지 키로 통일하는 방안을 검토해주세요.

• apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserAuthInfoResponse.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserProfileResponse.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/response/CreateUserResponse.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindUserIdentityRequest.kt
• apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindOrCreateUserRequest.kt
• apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt
…등 다수의 DTO

  • description 항목과 Validation 메시지를 모두 한국어로 작성하거나, 메시지 키 참조 방식으로 일괄 관리하세요.
  • 전체 DTO를 대상으로 언어를 통일한 후 스웨거 문서에도 반영되어 있는지 확인 바랍니다.
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt
around lines 11 to 15, the @Schema description uses English while other DTOs
have mixed languages. Review all DTOs listed and unify the language used in the
@Schema description and validation messages to either Korean or message key
references. After updating, verify that the Swagger documentation reflects these
consistent language changes across the project.

Comment on lines 16 to 17
@field:NotNull(message = "termsAgreed must not be null")
val termsAgreed: Boolean? = null
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Nullable 타입(Boolean?)과 @NotNull 혼용은 일관되지 않습니다.

필드를 non-null로 선언하면 컴파일 타임/스키마/역직렬화 단계에서 더 일관되게 강제할 수 있습니다. 현재 선언은 유효성 검사 전에는 null이 가능하여 호출부에서 안전하지 않습니다.

권장 변경:

  • val termsAgreed: Boolean으로 non-null로 지정
  • 기본값 = null 제거
  • @field:NotNull 제거(이미 non-null 타입이므로 중복)
  • Swagger에는 위 코멘트의 requiredMode = REQUIRED로 필수 표기 유지
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt
at lines 16-17, the field termsAgreed is declared as a nullable Boolean? with a
@NotNull annotation, which is inconsistent. Change the field to a non-nullable
Boolean by removing the question mark and the default null value, eliminate the
@field:NotNull annotation since the type is now inherently non-null, and ensure
Swagger marks this field as required using requiredMode = REQUIRED.

Comment on lines +48 to 55
fun updateAppleRefreshToken(@Valid saveAppleRefreshTokenRequest: SaveAppleRefreshTokenRequest): CreateUserResponse {
val userAuthVO = userDomainService.updateAppleRefreshToken(
saveAppleRefreshTokenRequest.validUserId(),
saveAppleRefreshTokenRequest.validAppleRefreshToken()
)

return CreateUserResponse.from(userAuthVO)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

DTO 캡슐화 기반 리팩터링 LGTM + NPE 가능성 제거를 위한 타입 강화 제안

  • @Valid SaveAppleRefreshTokenRequest로 파라미터를 캡슐화한 리팩터링은 👍
  • 다만 DTO 내부에서 !! 접근을 쓰지 않도록, 해당 필드들을 non-null 타입으로 전환하면 더 안전합니다(아래 DTO 코멘트 참고). 그러면 여기서도 valid*() 대신 프로퍼티 직접 접근으로 단순화할 수 있습니다.
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/service/UserAccountService.kt around
lines 48 to 55, the method updateAppleRefreshToken uses non-null asserted calls
(!!) inside the DTO's valid*() methods which can cause NPEs. Refactor the
SaveAppleRefreshTokenRequest DTO to declare its fields as non-nullable types,
removing the need for !! assertions. Then simplify this method by accessing the
DTO properties directly instead of calling valid*() methods.

Comment on lines +87 to 94
fun updateAppleRefreshToken(userId: UUID, refreshToken: String): UserAuthVO {
val user = userRepository.findById(userId)
?: throw UserNotFoundException(UserErrorCode.USER_NOT_FOUND)

val updatedUser = userRepository.save(user.updateAppleRefreshToken(refreshToken))

return UserIdentityVO.newInstance(updatedUser)
return UserAuthVO.newInstance(updatedUser)
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

도메인 불변성 보강 및 불필요한 저장 방지 제안

  • 반환 타입을 UserAuthVO로 통일한 변경은 👍
  • 다만 refresh token은 도메인 규칙(비어있지 않음 등)을 엔티티/도메인에서도 보장하는 편이 안전합니다. 애플리케이션 레이어에서 이미 검증하더라도, 도메인 계층에서 이너 밸리데이션을 갖추면 추후 재사용 시 안전합니다.
  • 동일 값으로 업데이트 시 불필요한 DB write를 건너뛰는 최적화도 고려해 보세요(가능하면).

도메인이 현재 토큰 접근자(e.g., user.appleRefreshToken)를 노출한다면, 동일 값이면 save를 생략하는 형태로 미세 최적화가 가능합니다.


다음 스크립트로 도메인 엔티티에 구현된 updateAppleRefreshToken 메서드를 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# updateAppleRefreshToken 메서드 정의 및 호출부 조회
rg -n "updateAppleRefreshToken" -A3 -B3

Length of output: 3505


아래 스크립트로 User 엔티티의 정의 및 생성자 유효성 검증 로직을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
sed -n '1,150p' domain/src/main/kotlin/org/yapp/domain/user/User.kt

Length of output: 4495


도메인 유효성 검증 및 불필요한 저장 최적화 필요

현재 updateAppleRefreshToken는 아무 값이나 그대로 복사하고, 매번 저장을 호출하기 때문에

  1. 빈 문자열(또는 null 아님)을 허용하지 않는 도메인 규칙 보강
  2. 동일 값일 때 불필요한 DB write 방지

를 적용하는 것이 좋습니다.

수정 제안:

  • domain/src/main/kotlin/org/yapp/domain/user/User.kt
    • updateAppleRefreshToken(token: String) 내에
      require(token.isNotBlank()) { "Apple refresh token은 빈 값일 수 없습니다." }
      if (token == this.appleRefreshToken) return this
      등을 추가하여 불변성과 유효성을 강화
  • domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt
    • userRepository.save(...) 호출 전 기존 토큰과 비교하여 실제로 변경된 경우에만 save하도록 최적화

이렇게 수정하면 도메인 계층에서 안전하게 유효성을 보장하고, 동일 값 갱신 시 불필요한 DB 부하를 줄일 수 있습니다.

🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt lines 87-94,
the updateAppleRefreshToken function lacks domain validation and always saves
the user even if the token is unchanged. To fix this, first enhance the
updateAppleRefreshToken method in
domain/src/main/kotlin/org/yapp/domain/user/User.kt by adding a check that the
token is not blank and return the current user instance if the token is the same
as the existing one. Then, in UserDomainService.kt, before calling
userRepository.save, compare the new token with the existing one and only call
save if the token has actually changed to avoid unnecessary database writes.

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: 26

🔭 Outside diff range comments (11)
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbn13sRequest.kt (1)

20-25: 배열 필드의 스키마는 @ArraySchema로 명확화하고, 항목 유효성(ISBN-13)도 표현하는 것을 권장합니다.

현재 @Schema(example="[...]")로도 동작하나, 배열 메타데이터(minItems/uniqueItems)와 항목 패턴(13자리 숫자)을 명시하면 문서 품질과 클라이언트 생성을 개선할 수 있습니다.

옵션 A(권장): @ArraySchema로 전환 + 각 항목의 패턴 검증 추가

-    @field:Schema(
-        description = "도서 ISBN 목록",
-        example = "[\"9788966262429\", \"9791190412351\"]"
-    )
+    @field:ArraySchema(
+        arraySchema = Schema(description = "도서 ISBN-13 목록"),
+        minItems = 1,
+        uniqueItems = true,
+        schema = Schema(description = "ISBN-13", example = "9788966262429", pattern = "^\\d{13}$")
+    )
     @field:NotEmpty(message = "isbn13 리스트는 비어있을 수 없습니다.")
-    val isbn13s: List<String>? = null
+    val isbn13s: List<@Pattern(regexp = "^\\d{13}$", message = "각 항목은 13자리 숫자 문자열이어야 합니다.") String>? = null

추가 import(라인 범위 밖):

import io.swagger.v3.oas.annotations.media.ArraySchema
import jakarta.validation.constraints.Pattern

옵션 B(변경 최소화): example 및 설명만 보강

-    @field:Schema(
-        description = "도서 ISBN 목록",
-        example = "[\"9788966262429\", \"9791190412351\"]"
-    )
+    @field:Schema(
+        description = "도서 ISBN-13 목록",
+        example = "[\"9788966262429\", \"9791190412351\"]"
+    )
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt (2)

11-17: Kotlin 필드 타겟 지정은 적절. 추가로 requiredMode 사용 + nullable 제거를 권장합니다.

  • @field:Schema(required = true) 대신 requiredMode = Schema.RequiredMode.REQUIRED 사용을 권장합니다(최신 스펙 반영, 가독성 향상).
  • val refreshToken: String? + !! 보다는 val refreshToken: String으로 명세를 강화하면 NPE 가능성을 제거하고 의도를 명확히 할 수 있습니다. Bean Validation과 Jackson 바인딩이 있다면 비어있거나 누락된 값에 대해 400을 반환하게 됩니다.

적용 예시(diff):

-    @field:Schema(
-        description = "Valid refresh token issued during previous authentication",
-        example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
-        required = true
-    )
+    @field:Schema(
+        description = "Valid refresh token issued during previous authentication",
+        example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+        requiredMode = Schema.RequiredMode.REQUIRED,
+    )
     @field:NotBlank(message = "Refresh token is required")
-    val refreshToken: String? = null
+    val refreshToken: String

그리고 아래 메서드는 단순히 반환하도록 정리 가능합니다(참고):

fun validRefreshToken(): String = refreshToken

11-17: TokenRefreshRequest DTO 역직렬화 장애: private 생성자 제거 및 Jackson Kotlin 모듈 검토 필요
TokenRefreshRequest 의 주 생성자가 private으로 선언되어 있어, Spring의 Jackson/Kotlin 모듈이 인스턴스를 생성하지 못합니다. 실제 컨트롤러의 @RequestBody TokenRefreshRequest 바인딩 시 400/500 에러가 발생할 수 있으니, 아래 조치를 부탁드립니다.

  • 대상 파일:
    apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt
  • 변경 제안:
-data class TokenRefreshRequest private constructor(
+data class TokenRefreshRequest(
  • 추가 검토:
    build.gradle.ktscom.fasterxml.jackson.module:jackson-module-kotlin 의존성이 포함되어 있는지 확인하고, 없다면 추가해주세요.
apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindUserIdentityRequest.kt (1)

13-18: 필드 타겟 지정은 적절. requiredMode 사용 + non-null 타입으로의 명세 강화 제안

  • required 대신 requiredMode = Schema.RequiredMode.REQUIRED 사용을 권장합니다.
  • val userId: UUID? 대신 val userId: UUID로 명세를 강화하면 validUserId()!!가 불필요해지고 NPE 위험을 제거할 수 있습니다.

적용 예시(diff):

-    @field:Schema(
-        description = "User ID (UUID format)",
-        example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
-    )
-    @field:NotNull(message = "userId must not be null")
-    val userId: UUID? = null
+    @field:Schema(
+        description = "User ID (UUID format)",
+        example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0",
+        requiredMode = Schema.RequiredMode.REQUIRED,
+    )
+    @field:NotNull(message = "userId must not be null")
+    val userId: UUID

validUserId()fun validUserId(): UUID = userId로 단순화 가능합니다.

apis/src/main/kotlin/org/yapp/apis/user/dto/response/CreateUserResponse.kt (1)

13-30: 문서 품질 향상을 위한 소소한 보완 제안(선택)

  • id, role는 응답 전용 의미가 강하므로 accessMode = Schema.AccessMode.READ_ONLY를 고려해볼 수 있습니다.
  • appleRefreshToken는 선택 필드이므로 응답에서 null 표시를 숨기려면 클래스 레벨에 @JsonInclude(JsonInclude.Include.NON_NULL)를 적용하는 방법이 있습니다(전역 설정이 없다면).

예시:

-@Schema(
+@Schema(
     name = "CreateUserResponse",
     description = "Response DTO returned after successful user registration"
 )
+@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
 data class CreateUserResponse private constructor(
@@
-    @field:Schema(
+    @field:Schema(
         description = "사용자 ID",
         example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
-    )
+    , accessMode = Schema.AccessMode.READ_ONLY)
     val id: UUID,
@@
-    @field:Schema(
+    @field:Schema(
         description = "사용자 역할",
         example = "USER"
-    )
+    , accessMode = Schema.AccessMode.READ_ONLY)
     val role: Role,
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt (1)

12-25: nullability와 required 표기의 일관성 검토

스키마에는 required=true지만 Kotlin 타입은 nullable(String?)입니다. 현재 @notblank로 런타임 검증은 되지만, 문서-타입 간 불일치를 줄이려면 비nullable로의 전환을 고려해 주세요.

예:

data class BookDetailRequest private constructor(
    @field:NotBlank
    @field:Pattern(regexp = RegexUtils.ISBN13_PATTERN)
    @field:Schema(
        description = "조회할 책의 13자리 ISBN 코드",
        example = "9788932473901",
        minLength = 13, maxLength = 13
    )
    val isbn13: String
)
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt (2)

91-99: enum allowableValues 중복 정의 제거 권장

BookStatus enum은 타입 자체로 값이 문서화되므로 enumAsRef=true만으로 충분합니다. allowableValues는 중복/드리프트 위험이 있어 제거를 권장합니다.

     @field:Schema(
         description = "사용자의 도서 읽기 상태",
         example = "READING",
-        required = true,
-        allowableValues = ["BEFORE_REGISTRATION", "BEFORE_READING", "READING", "COMPLETED"],
-        enumAsRef = true
+        required = true,
+        enumAsRef = true
     )

18-99: nullable + NotNull/NotBlank 혼용 최소화(타입 안정성 개선)

생성 경로가 companion of(...)로 제한되고 필수값이면, 필드를 비nullable로 전환하고 validX()의 !! 제거를 권장합니다. NPE 위험 감소 및 타입 시그널 명확화.

예시(요지):

data class UpsertUserBookRequest private constructor(
    @field:NotNull @field:Schema(..., format = "uuid")
    val userId: UUID,
    @field:NotNull @field:Schema(..., format = "uuid")
    val bookId: UUID,
    @field:NotBlank @field:Pattern(...) @field:Schema(..., minLength = 13, maxLength = 13)
    val isbn13: String,
    @field:NotBlank @field:Size(max = 500) @field:Schema(..., maxLength = 500)
    val bookTitle: String,
    @field:NotBlank @field:Size(max = 200) @field:Schema(..., maxLength = 200)
    val bookAuthor: String,
    @field:NotBlank @field:Size(max = 200) @field:Schema(..., maxLength = 200)
    val bookPublisher: String,
    @field:NotBlank @field:Size(max = 2048) @field:Schema(..., maxLength = 2048, format = "uri")
    val bookCoverImageUrl: String,
    @field:NotNull @field:Schema(..., enumAsRef = true)
    val status: BookStatus
) {
    // validX() 헬퍼 제거 가능
}
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (2)

18-26: required 플래그 대신 requiredMode 사용 권장

springdoc 최신 버전에선 requiredMode = Schema.RequiredMode.REQUIRED 사용을 권장합니다.
동일 속성들이 다수 DTO에 반복되니 일괄 교체를 검토해 주세요.

Also applies to: 28-36


13-26: Nullable 프로퍼티와 @NotBlank 혼용 – 타입 일관성 필요

val isbn13: String? 이 nullable인데 @NotBlank가 붙어 있습니다.
null 허용이 필요 없다면 String 으로 변경하고 기본값·!! 제거가 더 안전합니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt (1)

20-27: requiredMode 사용 및 Nullable 불일치

  • required = truerequiredMode = REQUIRED 권장
  • isbn13, bookStatus 모두 nullable로 선언돼 있으나 @NotBlank / @NotNull 사용 중입니다.
    필요 없다면 non-null 타입으로 변경해 NPE 리스크를 줄여 주세요.

Also applies to: 30-37

♻️ Duplicate comments (17)
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (1)

13-29: UUID/ISBN/URI format 및 길이 제약 보강 권장

응답 스키마 신뢰도를 위해 다음 보완을 추천합니다. 이전 리뷰와 동일 제안입니다.

-    @field:Schema(description = "등록된 책의 UUID", example = "123e4567-e89b-12d3-a456-426614174000")
+    @field:Schema(description = "등록된 책의 UUID", example = "123e4567-e89b-12d3-a456-426614174000", format = "uuid")
     val bookId: UUID,

-    @field:Schema(description = "ISBN-13 번호", example = "9791164053353")
+    @field:Schema(description = "ISBN-13 번호", example = "9791164053353", minLength = 13, maxLength = 13)
     val isbn13: String,
...
-    @field:Schema(description = "책 표지 이미지 URL", example = "https://image.aladin.co.kr/product/36801/75/coversum/k692030806_1.jpg")
+    @field:Schema(description = "책 표지 이미지 URL", example = "https://image.aladin.co.kr/product/36801/75/coversum/k692030806_1.jpg", format = "uri")
     val coverImageUrl: String

추가로, title/author/publisher에도 합리적 maxLength(예: 500/200/200) 명시를 검토해 주세요.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookPageResponse.kt (1)

12-13: Page의 OpenAPI 표현 개선 필요

현재 Page 타입은 Swagger에서 의도와 다르게 렌더링될 수 있습니다. 이전 리뷰와 동일 제안 재강조합니다.

옵션 A(저비용): 배열 스키마 명시

-    @field:Schema(description = "페이징된 책 목록")
-    val books: Page<UserBookResponse>,
+    @field:ArraySchema(
+        arraySchema = Schema(description = "페이징된 책 목록"),
+        schema = Schema(implementation = UserBookResponse::class)
+    )
+    val books: Page<UserBookResponse>,

옵션 B(권장 리팩터): DTO를 명시적 페이징 구조로 변환하여 직렬화/문서 명확성 향상

data class UserBookPageResponse(
    @field:Schema(description = "페이징된 책 목록")
    val books: List<UserBookResponse>,
    @field:Schema(description = "현재 페이지 번호", example = "0") val pageNumber: Int,
    @field:Schema(description = "페이지 크기", example = "20") val pageSize: Int,
    @field:Schema(description = "전체 페이지 수", example = "42") val totalPages: Int,
    @field:Schema(description = "전체 요소 수", example = "834") val totalElements: Long,
    @field:Schema(description = "읽기 전 상태의 책 개수", example = "5", minimum = "0") val beforeReadingCount: Long,
    @field:Schema(description = "현재 읽고 있는 책 개수", example = "3", minimum = "0") val readingCount: Long,
    @field:Schema(description = "완독한 책 개수", example = "10", minimum = "0") val completedCount: Long,
    @field:Schema(description = "전체 책 개수", example = "18", minimum = "0") val totalCount: Long,
)
apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (4)

16-20: UUID 필드 format="uuid" 명시 권장

스키마 명확성을 위해 UUID 필드에 format을 추가해 주세요. (이전 리뷰와 동일)

-    @field:Schema(description = "사용자 책 고유 ID(UUID)", example = "123e4567-e89b-12d3-a456-426614174000")
+    @field:Schema(description = "사용자 책 고유 ID(UUID)", example = "123e4567-e89b-12d3-a456-426614174000", format = "uuid")
     val userBookId: UUID,

-    @field:Schema(description = "사용자 고유 ID(UUID)", example = "987e6543-e21b-34d3-a456-426614174999")
+    @field:Schema(description = "사용자 고유 ID(UUID)", example = "987e6543-e21b-34d3-a456-426614174999", format = "uuid")
     val userId: UUID,

22-23: ISBN-13 길이 제약 명시

13자리 고정 제약을 추가해 주세요. (이전 리뷰와 동일)

-    @field:Schema(description = "ISBN-13 번호", example = "9791164053353")
+    @field:Schema(description = "ISBN-13 번호", example = "9791164053353", minLength = 13, maxLength = 13)
     val isbn13: String,

34-38: 표지 URL 스키마에 URI 형식 명시

URI 형식을 명시해 주세요. (이전 리뷰와 동일)

-    @field:Schema(
-        description = "책 표지 이미지 URL",
-        example = "https://image.aladin.co.kr/product/36801/75/coversum/k692030806_1.jpg"
-    )
+    @field:Schema(
+        description = "책 표지 이미지 URL",
+        example = "https://image.aladin.co.kr/product/36801/75/coversum/k692030806_1.jpg",
+        format = "uri"
+    )
     val coverImageUrl: String,

43-47: 시간 필드에 date-time 형식 명시 (+타임존 고려 제안)

스키마 형식을 명시하고, 가능하면 OffsetDateTime/Instant 사용으로 타임존 포함을 검토해 주세요. (형식 추가는 이전 리뷰 동일)

-    @field:Schema(description = "등록 일시 (ISO_LOCAL_DATE_TIME 형식)", example = "2025-08-09T15:30:00")
+    @field:Schema(description = "등록 일시 (ISO_LOCAL_DATE_TIME 형식)", example = "2025-08-09T15:30:00", format = "date-time")
     val createdAt: String,

-    @field:Schema(description = "수정 일시 (ISO_LOCAL_DATE_TIME 형식)", example = "2025-08-10T10:15:30")
+    @field:Schema(description = "수정 일시 (ISO_LOCAL_DATE_TIME 형식)", example = "2025-08-10T10:15:30", format = "date-time")
     val updatedAt: String,

선택: DTO 타입을 String → OffsetDateTime/Instant로 바꾸고 Jackson 설정(ISO 8601)으로 문서/직렬화를 일관화하는 것을 권장합니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (1)

21-25: 예제 URL의 &amp; HTML 엔티티 제거 필요
Swagger 예제에 &amp; 가 그대로 노출되어 가독성이 떨어집니다. & 로 수정해 주세요.

apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt (1)

10-17: private 생성자 + Nullable Boolean 문제 재확인 필요

지난 리뷰에서 지적된 역직렬화 실패 및 타입 일관성 문제가 그대로 남아 있습니다.

  • private constructor 제거
  • termsAgreed: Boolean (non-null) 로 변경
  • requiredMode = REQUIRED 사용

조치가 필요합니다.

apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (1)

22-29: Apple 토큰 공백 제거 로직 누락

isNullOrBlank() 체크는 했지만 trim() 을 적용하지 않아 앞뒤 공백이 포함된 토큰이 그대로 저장될 수 있습니다.

return appleRefreshToken
    ?.trim()
    ?.takeIf { it.isNotEmpty() }
    ?.let { token -> /* 그대로 */ }
    ?: initialUserResponse
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (1)

13-16: List 필드는 @ArraySchema로 명확히 표기 권장 (재제안)

List 아이템 타입이 명확히 드러나도록 @field:ArraySchema 사용을 권장합니다. Swagger UI 표기가 더 정확해집니다.

+import io.swagger.v3.oas.annotations.media.ArraySchema
 import io.swagger.v3.oas.annotations.media.Schema
@@
-    @field:Schema(
-        description = "각 감정 카테고리의 통계 리스트",
-        example = "[{\"name\":\"따뜻함\",\"count\":3},{\"name\":\"즐거움\",\"count\":1}]"
-    )
+    @field:ArraySchema(
+        arraySchema = Schema(
+            description = "각 감정 카테고리의 통계 리스트",
+            example = "[{\"name\":\"따뜻함\",\"count\":3},{\"name\":\"즐거움\",\"count\":1}]"
+        ),
+        schema = Schema(implementation = SeedCategoryStats::class)
+    )
     val categories: List<SeedCategoryStats>
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt (1)

9-12: 클래스 스키마에 title 병행 표기 제안 (재제안)

문서 UI 표시 품질을 위해 title을 병행해 주세요.

 @Schema(
     name = "ReadingRecordResponse",
+    title = "ReadingRecordResponse",
     description = "독서 기록 응답"
 )
apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt (5)

7-7: 와일드카드 import 제거 (재제안)

UUID만 필요하므로 명시적 import로 교체해 주세요. 기존 러닝을 반영해 IDE 설정 팁도 공유드립니다.

-import java.util.*
+import java.util.UUID

참고: IntelliJ IDEA에서 File > Settings > Editor > Code Style > Java > Imports의 "Class count to use import with '*'" 값을 크게(예: 500) 올리면 와일드카드 임포트 자동 사용을 방지할 수 있습니다. (과거 러닝 참고)


18-19: nullable + @NotBlank/@NotNull 불일치 및 NPE 위험 제거 (재제안)

@NotBlank가 붙은 appleRefreshToken이 nullable이며(String?), !! 사용으로 NPE 위험이 있습니다. 두 필드를 non-null로 전환하고 !!를 제거해 주세요. userId 역시 @NotNull 대신 non-null 타입으로 일치시키는 편이 안전합니다.

-    @field:NotNull(message = "userId must not be null")
-    val userId: UUID? = null,
+    val userId: UUID,
@@
-    @field:Schema(
-        description = "Apple refresh token, nullable if not issued yet",
-        example = "apple-refresh-token-example"
-    )
-    @field:NotBlank(message = "appleRefreshToken must not be blank")
-    val appleRefreshToken: String? = null
+    @field:Schema(
+        description = "Apple refresh token",
+        example = "apple-refresh-token-example"
+    )
+    @field:NotBlank(message = "appleRefreshToken must not be blank")
+    val appleRefreshToken: String
 ) {
-    fun validUserId(): UUID = userId!!
-    fun validAppleRefreshToken(): String = appleRefreshToken!!
+    fun validUserId(): UUID = userId
+    fun validAppleRefreshToken(): String = appleRefreshToken

Also applies to: 26-26, 28-29


21-26: 스키마 설명/nullable 표기 정합성 유지 (재제안)

프로퍼티 설명의 nullable if not issued yet 문구는 현재 제약과 상충합니다. nullable이 아니므로 문구를 제거하고 필요 시 nullable = false를 명시하세요.

-    @field:Schema(
-        description = "Apple refresh token, nullable if not issued yet",
-        example = "apple-refresh-token-example"
-    )
+    @field:Schema(
+        description = "Apple refresh token",
+        example = "apple-refresh-token-example",
+        nullable = false
+    )

31-39: 빌더 of에서 사전 정규화(트리밍) 적용 (재제안)

입력 토큰을 트리밍하고 빈 문자열이면 생성 자체를 막아 Validation 실패를 줄이는 방식을 권장합니다.

         fun of(
             userResponse: CreateUserResponse,
             appleRefreshToken: String
         ): SaveAppleRefreshTokenRequest {
-            return SaveAppleRefreshTokenRequest(
-                userId = userResponse.id,
-                appleRefreshToken = appleRefreshToken
-            )
+            val normalized = appleRefreshToken.trim()
+            require(normalized.isNotEmpty()) { "appleRefreshToken must not be blank" }
+            return SaveAppleRefreshTokenRequest(
+                userId = userResponse.id,
+                appleRefreshToken = normalized
+            )
         }

9-12: 클래스 설명이 현재 필드와 불일치

설명에 authorization code가 남아 있어 문서 혼선을 유발합니다. 실제 필드에 맞게 수정해 주세요.

 @Schema(
     name = "SaveAppleRefreshTokenRequest",
-    description = "Request DTO for saving Apple refresh token with user ID and authorization code"
+    description = "Request DTO for saving Apple refresh token with user ID"
 )
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1)

85-89: 링크 예제에 HTML 엔티티 제거 (재제안)

Swagger UI에서 실제 URL 형태로 보이도록 &amp; 대신 &를 사용해 주세요.

-        @field:Schema(
-            description = "검색 결과 상세 페이지 링크",
-            example = "https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=367872613&amp;partner=openAPI&amp;start=api"
-        )
+        @field:Schema(
+            description = "검색 결과 상세 페이지 링크",
+            example = "https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=367872613&partner=openAPI&start=api"
+        )
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce96fb3 and dc46056.

📒 Files selected for processing (35)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/DeleteTokenRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenGenerateRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/WithdrawStrategyRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/AuthResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/RefreshTokenResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/TokenPairResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserCreateInfoResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserIdResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (8 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt (3 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt (8 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt (3 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbn13sRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (4 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookPageResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/home/dto/response/UserHomeResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/SeedStatsResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindOrCreateUserRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindUserIdentityRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/TermsAgreementRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/response/CreateUserResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserAuthInfoResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserProfileResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/user/dto/response/WithdrawTargetUserResponse.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-06T08:18:36.255Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#76
File: apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt:33-46
Timestamp: 2025-08-06T08:18:36.255Z
Learning: 로그인 작업에서는 외부 API 호출(소셜 인증, Apple 토큰 발급)과 DB 작업을 분리하여, AuthUseCase는 트랜잭션 없이 외부 호출을 먼저 처리하는 오케스트레이터 역할을 하고, UserSignInService는 Transactional로 DB 작업만 처리하는 실행자 역할을 한다. 이는 DB 커넥션 점유 시간 최소화와 시스템 안정성 향상을 위한 패턴이다.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt
📚 Learning: 2025-07-08T14:24:44.340Z
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#31
File: apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt:13-13
Timestamp: 2025-07-08T14:24:44.340Z
Learning: IntelliJ IDEA에서 와일드카드 임포트를 방지하는 설정 방법: File > Settings > Editor > Code Style > Java > Imports 탭에서 "Class count to use import with '*'" 값을 높게 설정(예: 500)하면 해당 임계값 이하에서는 와일드카드 임포트가 자동으로 사용되지 않음. 와일드카드 임포트 관련 리뷰 시 이 IDE 설정 방법도 함께 안내하면 좋음.

Applied to files:

  • apis/src/main/kotlin/org/yapp/apis/user/dto/request/SaveAppleRefreshTokenRequest.kt
⏰ 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-validation
🔇 Additional comments (19)
apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserIdResponse.kt (1)

12-12: 아노테이션 타깃 변경 확인 완료

@Schema@field:Schema 변경이 올바르게 적용되었습니다. 추가 의견 없습니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/WithdrawStrategyRequest.kt (1)

13-38: 아노테이션 타깃 변경 문제없음

@field:Schema 로 일괄 교체된 부분 확인했습니다. 특별한 문제 없습니다.

apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserAuthInfoResponse.kt (1)

13-23: 스키마 메타데이터 적용 OK

필드 수준으로 아노테이션을 맞춘 점 확인했습니다. 👍

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/RefreshTokenResponse.kt (1)

11-12: @field:Schema로의 타겟 정정, 일관성 있게 잘 반영되었습니다.

스키마 생성 정확도 향상에 도움이 됩니다. 추가 조치 필요 없습니다.

apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindUserIdentityRequest.kt (1)

13-18: 해당 DTO는 역직렬화 바인딩 대상이 아닙니다.

조사 결과, FindUserIdentityRequest는 컨트롤러의 @RequestBody/@ModelAttribute로 바인딩되지 않고 오직 companion objectfrom(...) 메서드로만 생성되고 있습니다. 따라서 private constructor로 인한 역직렬화 오류 위험이 없습니다.

apis/src/main/kotlin/org/yapp/apis/home/dto/response/UserHomeResponse.kt (1)

13-16: 필드 레벨 @Schema 적용 일관성 확보, 좋습니다.

리스트 필드에도 정확히 반영되어 OpenAPI 스키마 생성 시 필드 설명이 누락되지 않습니다.

apis/src/main/kotlin/org/yapp/apis/user/dto/response/CreateUserResponse.kt (1)

13-30: 필드 레벨 @Schema 타겟 정정, 모두 적절합니다.

스키마 반영 누락 가능성을 줄여줍니다. 현재 변경에는 기능적 리스크 없습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt (5)

9-11: 클래스 스키마 name 사용 확인

class-level @Schema에서 titlename 전환은 문제 없습니다. 다만 스키마 컴포넌트 명으로 의도하신 게 맞는지(예: 중복/충돌 없이 원하는 이름으로 노출되는지) 한 번 확인 부탁드립니다.


14-19: 필드 타겟 전환 적절함

@field:Schema로의 전환이 올바릅니다. required = true 지정도 일치합니다. LGTM.


39-46: 제약/스키마 일관성 양호

Bean Validation(@Min/@Max)과 스키마의 minimum/maximum/defaultValue가 일관적입니다. LGTM.


49-55: 페이징 파라미터 스키마 적절

start에 대한 최소값/기본값과 설명이 명확합니다. LGTM.


65-70: 카테고리 ID 기본값 명시 적절

기본값과 의미(0: 전체)가 명확합니다. LGTM.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserCreateInfoResponse.kt (1)

11-16: Kotlin 필드 타겟으로의 전환 적절

모든 속성에 @field:Schema 적용이 올바릅니다. Swagger 필드 레벨 문서화가 정확해집니다. LGTM.

Also applies to: 18-22, 24-28, 30-34, 36-40

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/AuthResponse.kt (1)

11-15: 필드 스키마 타겟 변경 OK

@field:Schema로의 전환이 문서 정확도를 높입니다. 변경 범위 내 문제 없습니다. LGTM.

Also applies to: 17-21

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/TokenPairResponse.kt (1)

11-15: 일관성 있는 스키마 적용

TokenPairResponse@field:Schema로 일관되게 정리되었습니다. LGTM.

Also applies to: 17-21

apis/src/main/kotlin/org/yapp/apis/user/dto/response/WithdrawTargetUserResponse.kt (1)

14-21: @field: 타깃 전환 적절

응답 DTO의 필드에 @field:Schema 적용으로 문서 일관성이 좋아졌습니다. 추가 변경 없이 LGTM입니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (1)

17-23: Swagger 보안 제거 어노테이션 적용 및 로직 확인 완료

  • global-utils/src/main/kotlin/org/yapp/globalutils/annotation/DisableSwaggerSecurity.kt 에서 어노테이션 정의 확인
  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt
    • 25행: 소셜 로그인 엔드포인트에 @DisableSwaggerSecurity 적용
    • 52행: 토큰 갱신 엔드포인트에 @DisableSwaggerSecurity 적용
  • apis/src/main/kotlin/org/yapp/apis/config/SwaggerConfig.kt (securityOperationCustomizer – 61~68행)
    DisableSwaggerSecurity 어노테이션 감지 후 해당 메서드의 보안 요구사항을 제거하는 로직이 구현되어 있음

위 검증 결과, 로그인 및 토큰 갱신 API에 대한 Swagger 보안 비활성화 처리가 정상적으로 적용되어 있으므로 추가 수정은 필요하지 않습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (1)

7-10: 클래스 스키마 메타데이터 추가 좋습니다

클래스 레벨 @Schema(name/description) 정합성 좋고, 문서 가독성 향상에 도움이 됩니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt (1)

18-25: 필드 타깃 전환 및 제약 명시 적절합니다

@field:Schema 전환과 13자리 제약, 예시/required 표기는 적절합니다. 실사용 검증은 Bean Validation(@NotBlank/@pattern)으로 커버되어 일관적입니다.

Comment on lines +13 to 14
@field:Schema(description = "Refresh token to be deleted", example = "eyJhbGciOiJIUz...")
val refreshToken: String? = null
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

String? + @NotBlank 조합

refreshToken 을 nullable 로 두면서 @NotBlank 를 적용하면 스키마 상으로는 null 허용으로 보입니다. 일관성을 위해 non-null 로 선언하거나 validation 어노테이션을 조정해 주세요.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/DeleteTokenRequest.kt at
lines 13-14, the refreshToken property is declared as nullable String with
@NotBlank annotation, causing inconsistency between nullability and validation.
To fix this, either change the type to non-nullable String or remove/adjust the
@NotBlank annotation to align with the nullable declaration, ensuring consistent
schema and validation behavior.

Comment on lines +15 to 21
@field:Schema(
description = "User ID",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
)
@field:NotNull(message = "userId must not be null")
val userId: UUID? = null,

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

검증과 문서의 required 불일치

@NotNull로 서버 검증은 필수이지만, Kotlin 타입이 nullable이라 OpenAPI에선 선택(optional)로 보일 수 있습니다. 문서-런타임 불일치를 해소하세요.

다음 중 하나를 권장합니다:

  • 간단: @field:Schema(required = true) 추가
  • 근본: 타입을 nullable → non-null로 전환하고 @NotNull 제거

간단안 적용 diff 예시:

 @Schema(
   name = "GenerateTokenPairRequest",
   description = "Request DTO to generate a new pair of access and refresh tokens"
 )
 data class GenerateTokenPairRequest private constructor(
-    @field:Schema(
+    @field:Schema(
+        required = true,
         description = "User ID",
         example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
     )
     @field:NotNull(message = "userId must not be null")
     val userId: UUID? = null,

-    @field:Schema(
+    @field:Schema(
+        required = true,
         description = "User role",
         example = "USER"
     )
     @field:NotNull(message = "role must not be null")
     val role: Role? = null
 )

Also applies to: 22-27

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt
around lines 15 to 21 and similarly at lines 22 to 27, the userId field is
annotated with @NotNull but declared as a nullable UUID?, causing a mismatch
between validation and OpenAPI documentation. To fix this, either add
@field:Schema(required = true) to explicitly mark the field as required in the
schema or change the Kotlin type to non-nullable UUID and remove the @NotNull
annotation to align validation and documentation consistently.

Comment on lines +17 to 23
@field:Schema(
description = "Type of social login provider",
example = "KAKAO",
required = true
)
@field:NotBlank(message = "Provider type is required")
val providerType: String? = null,
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

providerType 값 제약 강화(선택) — 잘못된 값 조기 차단

현재 String 수신 후 enum 변환 실패 시 예외 처리하고 있습니다. 요청 단계에서 허용 값(예: KAKAO|APPLE)만 통과하도록 정규식 검증을 추가하면 불필요한 예외 흐름을 줄일 수 있습니다.

정규식 검증 추가 예시:

 @field:Schema(
   description = "Type of social login provider",
   example = "KAKAO",
   required = true
 )
-@field:NotBlank(message = "Provider type is required")
+@field:NotBlank(message = "Provider type is required")
+@field:jakarta.validation.constraints.Pattern(
+  regexp = "KAKAO|APPLE",
+  message = "Provider type must be one of [KAKAO, APPLE]"
+)
 val providerType: String? = null,

파일 상단 import 추가:

import jakarta.validation.constraints.Pattern
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt
around lines 17 to 23, the providerType field currently accepts any string and
relies on enum conversion failure to handle invalid values. To strengthen
validation and prevent invalid inputs early, add a @field:Pattern annotation
with a regex that only allows valid provider types like "KAKAO" or "APPLE".
Also, import jakarta.validation.constraints.Pattern at the top of the file. This
change will reduce unnecessary exception handling by validating input format
upfront.

Comment on lines +25 to 31
@field:Schema(
description = "OAuth token issued by the social provider",
example = "eyJ...",
required = true
)
@field:NotBlank(message = "OAuth token is required")
val oauthToken: String? = null,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

토큰/코드 필드 Swagger 마스킹 및 WRITE_ONLY 권장

요청 DTO에 포함된 oauthToken, authorizationCode는 민감정보입니다. Swagger UI 입력 시 마스킹하고, 문서 상에서도 요청 전용(WRITE_ONLY)으로 표시하는 것을 권장합니다.

해당 라인 범위 내 적용 예시:

 @field:Schema(
   description = "OAuth token issued by the social provider",
   example = "eyJ...",
-  required = true
+  required = true,
+  format = "password",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY
 )
 @field:NotBlank(message = "OAuth token is required")
 val oauthToken: String? = null,

 @field:Schema(
   description = "Authorization code used to issue Apple access/refresh tokens (required only for Apple login)",
   example = "c322a426...",
-  required = false
+  required = false,
+  format = "password",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY
 )
 val authorizationCode: String? = null

Also applies to: 33-38

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt
around lines 25 to 31 and 33 to 38, the fields oauthToken and authorizationCode
contain sensitive information and should be masked in Swagger UI and marked as
WRITE_ONLY in the schema. To fix this, add the property writeOnly = true to the
@field:Schema annotation for these fields and configure Swagger to mask the
input for these fields to protect sensitive data during API documentation and
testing.

Comment on lines 13 to 23
@field:NotNull(message = "userId must not be null")
@Schema(description = "User ID", example = "f6b7d490-1b1a-4b9f-8e8e-27f8e3a5dafa")
@field:Schema(description = "User ID", example = "f6b7d490-1b1a-4b9f-8e8e-27f8e3a5dafa")
val userId: UUID? = null,

@field:NotBlank(message = "refreshToken must not be blank")
@Schema(description = "Generated refresh token", example = "eyJhbGciOiJIUzI1NiIsInR...")
@field:Schema(description = "Generated refresh token", example = "eyJhbGciOiJIUzI1NiIsInR...")
val refreshToken: String? = null,

@field:NotNull(message = "expiration must not be null")
@Schema(description = "Refresh token expiration time (in seconds)", example = "2592000")
@field:Schema(description = "Refresh token expiration time (in seconds)", example = "2592000")
val expiration: Long? = null
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Nullable 선언과 Bean Validation 제약 조건의 불일치

필드가 UUID?, String?, Long? 로 nullable 로 선언되어 있지만 @NotNull/@NotBlank 제약을 걸어 두었습니다.
Swagger UI 와 코틀린 타입 시스템이 전달하는 메시지가 달라져 혼란을 줄 수 있으니, 가능하면 타입을 non-null 로 선언하고 기본 생성자를 별도로 제공하는 방식을 고려해 주세요.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenGenerateRequest.kt
around lines 13 to 23, the fields userId, refreshToken, and expiration are
declared as nullable types but have @NotNull/@NotBlank validation annotations,
causing inconsistency. Change these fields to non-nullable types by removing the
'?' and provide a separate default constructor if needed to maintain
compatibility. This aligns Kotlin's type system with the validation constraints
and avoids confusion in Swagger UI.

Comment on lines +32 to 36
@field:Schema(description = "생성 일시", example = "2023-01-01T12:00:00")
val createdAt: String,

@Schema(description = "수정 일시", example = "2023-01-01T12:00:00")
@field:Schema(description = "수정 일시", example = "2023-01-01T12:00:00")
val updatedAt: String,
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

날짜/시간 필드에 format="date-time" 지정 권장

OpenAPI 표준에 맞춰 createdAt, updatedAtformat = "date-time"을 추가해 주세요.

-    @field:Schema(description = "생성 일시", example = "2023-01-01T12:00:00")
+    @field:Schema(description = "생성 일시", example = "2023-01-01T12:00:00", format = "date-time")
     val createdAt: String,
@@
-    @field:Schema(description = "수정 일시", example = "2023-01-01T12:00:00")
+    @field:Schema(description = "수정 일시", example = "2023-01-01T12:00:00", format = "date-time")
     val updatedAt: String,
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt
around lines 32 to 36, the createdAt and updatedAt fields lack the OpenAPI
standard format specification. Add format = "date-time" to the @field:Schema
annotations for both fields to comply with OpenAPI standards and improve API
documentation accuracy.

Comment on lines +38 to 49
@field:Schema(description = "도서 제목", example = "클린 코드")
val bookTitle: String?,

@Schema(description = "출판사", example = "인사이트")
@field:Schema(description = "출판사", example = "인사이트")
val bookPublisher: String?,

@Schema(description = "도서 썸네일 URL", example = "https://example.com/book-cover.jpg")
@field:Schema(description = "도서 썸네일 URL", example = "https://example.com/book-cover.jpg")
val bookCoverImageUrl: String?,

@Schema(description = "저자", example = "로버트 C. 마틴")
@field:Schema(description = "저자", example = "로버트 C. 마틴")
val author: String?
) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

nullable 필드에 nullable=true 명시

선택 필드(bookTitle, bookPublisher, bookCoverImageUrl, author)는 nullable = true를 명시하면 스키마 일관성이 좋아집니다.

-    @field:Schema(description = "도서 제목", example = "클린 코드")
+    @field:Schema(description = "도서 제목", example = "클린 코드", nullable = true)
     val bookTitle: String?,
@@
-    @field:Schema(description = "출판사", example = "인사이트")
+    @field:Schema(description = "출판사", example = "인사이트", nullable = true)
     val bookPublisher: String?,
@@
-    @field:Schema(description = "도서 썸네일 URL", example = "https://example.com/book-cover.jpg")
+    @field:Schema(description = "도서 썸네일 URL", example = "https://example.com/book-cover.jpg", nullable = true)
     val bookCoverImageUrl: String?,
@@
-    @field:Schema(description = "저자", example = "로버트 C. 마틴")
+    @field:Schema(description = "저자", example = "로버트 C. 마틴", nullable = true)
     val author: String?
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt
between lines 38 and 49, the nullable fields bookTitle, bookPublisher,
bookCoverImageUrl, and author lack the explicit nullable=true attribute in their
@field:Schema annotations. To improve schema consistency, add nullable=true to
each of these @field:Schema annotations for the nullable fields.

Comment on lines +15 to 18
@field:Schema(
description = "사용자 이메일",
example = "[email protected]", nullable = true)
val email: String? = null,
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@field: 타깃 전환 적절 — 이메일 검증/Null 안정성 보완 제안

  • 변경 취지(@field: 타깃)는 적절합니다. springdoc + Bean Validation 처리 일관성에 도움됩니다.
  • 이메일에는 형식 검증을 추가하는 편이 안전합니다. 또한 해당 DTO는 컨트롤러에서 반드시 @Valid가 적용되어야 validProviderType()/validProviderId()!!가 NPE를 유발하지 않습니다.

제안:

  • email에 @Email 추가
  • 컨트롤러 메서드 파라미터에 @Valid 적용 여부 확인
  • 선택 사항: providerType/providerId를 비즈니스상 필수라면 Kotlin non-null로 선언하고 기본값 제거(문서상의 required와 실제 모델 일치) — 다만 Jackson 바인딩 전략에 따라 영향이 있으니 팀 합의 필요

다음 스크립트로 이 DTO를 파라미터로 받는 지점에 @Valid가 붙어있는지 확인해 주세요.

이메일 형식 검증 추가(해당 라인 범위 내 diff):

 @field:Schema(
   description = "사용자 이메일",
   example = "[email protected]", nullable = true)
+@field:jakarta.validation.constraints.Email(message = "올바른 이메일 형식이어야 합니다.")
 val email: String? = null,

파일 상단 import 추가:

import jakarta.validation.constraints.Email

Also applies to: 20-25, 27-33, 34-39, 41-46


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== FindOrCreateUserRequest 사용처 탐색"
rg -n --hidden --glob '!*build*' 'FindOrCreateUserRequest' -A 3

echo
echo "== @Valid와 함께 선언된 파라미터 탐색"
rg -n --hidden --glob '!*build*' '@Valid\s+.*FindOrCreateUserRequest'

Length of output: 5856


이메일 형식 검증 및 @Valid 적용 강화 필요

요약:

  • 이메일 필드에 @Email 검증 추가
  • HTTP 요청 바인딩 단계에서 DTO 검증을 보장하기 위해 컨트롤러 레이어의 @RequestBody 파라미터에도 @Valid 적용 여부 점검/추가

수정 제안:

  1. FindOrCreateUserRequest.kt (15–18행)

        @field:Schema(
            description = "사용자 이메일",
            example = "[email protected]", nullable = true)
    +   @field:jakarta.validation.constraints.Email(message = "올바른 이메일 형식이어야 합니다.")
        val email: String? = null,

    파일 상단에 import 추가:

    import jakarta.validation.constraints.Email
  2. 컨트롤러 레이어 확인

    • 이 DTO를 @RequestBody 로 받는 컨트롤러 메서드 시그니처에 @Valid 가 누락되었는지 확인하고, 없으면 아래처럼 추가해주세요.
      @PostMapping("/users")
      fun signIn(
        @Valid @RequestBody request: FindOrCreateUserRequest
      ): ResponseEntity<…> { … }
    • (현재 서비스 계층에는 이미 fun findOrCreateUser(@Valid …) 형태로 적용되어 있으나, Spring MVC 요청 바인딩 단계에서 동작하려면 컨트롤러에 @Valid 가 필요합니다.)
  3. 선택 고려사항

    • providerType/providerId 가 비즈니스상 필수라면 nullable 기본값(? = null)을 제거하고 non-null로 전환하여 문서(required)와 실제 모델을 일치시키는 것도 검토해주세요.
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindOrCreateUserRequest.kt
around lines 15 to 18, add the import statement for
jakarta.validation.constraints.Email at the top of the file. Annotate the email
field with @Email to enforce email format validation. Then, check the controller
method that receives this DTO via @RequestBody and ensure it is also annotated
with @Valid to trigger validation during HTTP request binding. Optionally,
review providerType and providerId fields to remove nullable defaults if they
are required by business logic, aligning the model with API documentation.

Comment on lines +14 to 19
@field:Schema(
description = "Unique identifier of the user",
example = "c4d46ff7-9f1b-4c5f-8262-9fa2f982a7f4"
)
val id: UUID,

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

응답 필드 accessMode=READ_ONLY 명시(선택)

@field:Schema 전환은 적절합니다. 응답 전용 DTO이므로 문서 명확성을 위해 READ_ONLY를 추가하는 것을 권장합니다(요청 모델과 혼동 방지).

예시(각 필드에 accessMode 추가):

 @field:Schema(
   description = "Unique identifier of the user",
-  example = "c4d46ff7-9f1b-4c5f-8262-9fa2f982a7f4"
+  example = "c4d46ff7-9f1b-4c5f-8262-9fa2f982a7f4",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY
 )
 val id: UUID,

 @field:Schema(
   description = "User email address",
-  example = "[email protected]"
+  example = "[email protected]",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY
 )
 val email: String,

 @field:Schema(
   description = "User nickname",
-  example = "HappyPanda"
+  example = "HappyPanda",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY
 )
 val nickname: String,

 @field:Schema(
   description = "Social login provider type",
-  example = "KAKAO"
+  example = "KAKAO",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY
 )
 val provider: ProviderType,

 @field:Schema(
   description = "Whether the user has agreed to the terms of service",
-  example = "false"
+  example = "false",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY
 )
 val termsAgreed: Boolean

Also applies to: 20-25, 26-31, 32-37, 38-43

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserProfileResponse.kt
around lines 14 to 19 and also lines 20-25, 26-31, 32-37, 38-43, add the
attribute accessMode = Schema.AccessMode.READ_ONLY to each @field:Schema
annotation. This explicitly marks these fields as read-only in the API
documentation, clarifying that these are response-only DTO fields and preventing
confusion with request models.

Comment on lines +23 to 24
@field:Schema(description = "Apple Refresh Token (애플 회원 탈퇴 시 필요, 카카오는 null)")
val appleRefreshToken: String? = null
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

민감정보(appleRefreshToken) 응답 노출 재검토 권장

Apple Refresh Token은 고위험 민감정보입니다. 클라이언트로 전달해야 하는 합당한 사유가 없으면 응답에서 배제하는 편이 안전합니다. Swagger 문서에도 노출되지 않도록 WRITE_ONLY 또는 hidden 처리 검토를 권장합니다.

대안:

  • 서버-서버 통신으로만 사용하고 응답에서 제거
  • Swagger에서 비노출: hidden = true 또는 accessMode = Schema.AccessMode.WRITE_ONLY 지정

Swagger 문서 비노출 예시(해당 라인 범위 내 diff):

-@field:Schema(description = "Apple Refresh Token (애플 회원 탈퇴 시 필요, 카카오는 null)")
+@field:Schema(
+  description = "Apple Refresh Token (애플 회원 탈퇴 시 필요, 카카오는 null)",
+  accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY
+  // 또는 hidden = true
+)
 val appleRefreshToken: String? = null

가능하면 토큰은 서버 단에서 직접 폐기/탈퇴 처리하세요. 클라이언트로의 재전달은 회수 불가 노출 위험을 키웁니다.

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/user/dto/response/WithdrawTargetUserResponse.kt
at lines 23 to 24, the appleRefreshToken field exposes sensitive information in
the API response and Swagger documentation. To fix this, remove
appleRefreshToken from the response DTO or mark it with Schema annotation
properties such as hidden = true or accessMode = Schema.AccessMode.WRITE_ONLY to
prevent it from appearing in the API response and Swagger docs. Prefer handling
the token only on the server side without sending it to clients.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Aug 9, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
17.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

LGTM

@move-hoon move-hoon merged commit f6a9f48 into develop Aug 9, 2025
3 of 4 checks passed
@move-hoon move-hoon deleted the BOOK-90-chore/#25 branch August 12, 2025 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-90/chore] 커스텀 어노테이션으로 Swagger UI에서 특정 API의 보안 설정 비활성화

3 participants