Skip to content

Conversation

@codrin2
Copy link
Member

@codrin2 codrin2 commented Sep 25, 2025

Issue Number

#43

As-Is

  • AI 기반 가상 피팅 기능의 부재로 인한 사용자 이용 불가능 및 서비스 확장성의 한계
  • 이미지 업로드, 관리, AI 피팅 생성 등 관련 백엔드 로직의 미구현
  • 외부 API 호출에 대한 사용량 제한 시스템의 부재로 인한 무분별한 API 사용 및 비용 발생 가능성

To-Be

  • Google Nano Banana AI 기반 가상 피팅 이미지 생성 기능 구현
  • 이미지 업로드 및 관리 API 추가, Gemini API 연동
  • 서비스 안정성과 지속 가능성을 위한 Redis 기반 크레딧 시스템 도입으로 AI 피팅 기능 사용량 제한
  • 도메인 서비스, 퍼사드, 레포지토리 등 기능별 계층 분리를 통한 코드 응집도 및 유지보수성 향상

✅ Check List

  • Have all tests passed?
  • Have all commits been pushed?
  • Did you verify the target branch for the merge?
  • Did you assign the appropriate assignee(s)?
  • Did you set the correct label(s)?

📸 Test Screenshot

Additional Description

@codrin2 codrin2 self-assigned this Sep 25, 2025
@codrin2 codrin2 linked an issue Sep 25, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Walkthrough

S3 업로드 및 AI 피팅 기능을 추가했다. AWS S3 presigned URL 발급, Gemini 이미지 생성 연동, Redis 기반 토큰 버킷 크레딧 검사/차감(Lua 스크립트 + Redisson), 관련 도메인/서비스/레포지토리/컨트롤러/DTO/Swagger를 새로 도입했고 serializer 패키지 재배치, 설정·CI 환경변수 확장도 포함한다.

Changes

Cohort / File(s) Summary
CI 배포 워크플로 환경변수 확장
.github/workflows/deploy-dev.yaml, .github/workflows/deploy-prod.yaml
Docker run에 AWS 자격증명·S3 버킷/리전·GEMINI_API_KEY 환경변수 추가.
빌드 의존성
gradle/core.gradle
AWS SDK BOM(2.27.24) 및 S3 모듈 의존성 추가.
Core: 패키지/직렬화 이동 및 정리
src/main/kotlin/com/dh/baro/core/serialization/*, src/main/kotlin/com/dh/baro/core/GlobalExceptionHandler.kt, src/main/kotlin/com/dh/baro/core/Cursor.kt
LongToStringSerializer 등 serialization 패키지로 이동, GlobalExceptionHandler 패키지 변경 및 import 정리.
에러 메시지 확장
src/main/kotlin/com/dh/baro/core/ErrorMessage.kt
AI/피팅/Gemini/Redis 관련 에러 상수 다수 추가.
Look: 도메인 엔티티·리포지토리
src/main/kotlin/com/dh/baro/look/domain/FittingSourceImage.kt, .../FittingSourceImageStatus.kt, .../repository/FittingSourceImageRepository.kt, .../repository/CreditRepository.kt
FittingSourceImage JPA 엔티티, 상태 enum, JPA 리포지토리 및 CreditRepository 인터페이스 추가.
Look: 도메인 서비스
src/main/kotlin/com/dh/baro/look/domain/service/FittingSourceImageService.kt, .../CreditService.kt
이미지 생성/완료/조회 로직 및 분산락 기반 크레딧 검증·실행 서비스 추가.
Look: 인프라 (S3 / Gemini / Redis)
.../infra/s3/S3ImageApi.kt, .../infra/s3/S3PresignedUrlInfo.kt, .../infra/gemini/GeminiImageApi.kt, .../infra/gemini/GeminiApiRequest.kt, .../infra/gemini/GeminiApiResponse.kt, .../infra/redis/CreditRepositoryImpl.kt
S3 presigned URL·이미지 URL 생성, Gemini 요청/응답 모델 및 호출, Redisson + Lua 스크립트 연동한 크레딧 체크/차감 구현.
Look: 애플리케이션·프레젠테이션 계층
src/main/kotlin/com/dh/baro/look/application/FitFacade.kt, .../dto/* (AiFittingInfo, FittingSourceImageUploadInfo, 등), src/main/kotlin/com/dh/baro/look/presentation/FitController.kt, .../presentation/swagger/FitSwagger.kt, .../presentation/dto/*
업로드 URL 발급, 업로드 완료, 사용자 이미지 조회, AI 피팅 생성 API 및 DTO·Swagger 추가.
보조: DTO / 패키지 이동 및 import 수정
.../look/application/dto/LookCreateCommand.kt, .../look/application/LookFacade.kt, .../look/domain/service/LookService.kt, .../look/presentation/dto/LookCreateRequest.kt, src/test/.../LookServiceTest.kt
LookCreateCommand 패키지 이동 및 관련 import/사용처 업데이트.
Serializer import 일괄 변경
src/main/kotlin/.../*Response.kt, .../dto/* (cart, identity, look, order, product 관련 여러 파일)
LongToStringSerializer 참조를 com.dh.baro.core.serialization.* 로 일괄 변경.
환경설정 추가
src/main/resources/application-dev.yaml, src/main/resources/application-prod.yaml
cloud.aws.s3.bucket/region, gemini.api.key/url 설정 추가.
Redis Lua 스크립트
src/main/resources/lua/credit_check.lua, src/main/resources/lua/credit_deduct.lua
토큰 버킷 기반 크레딧 확인/차감 Lua 스크립트 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Controller as FitController
  participant Facade as FitFacade
  participant ImgSvc as FittingSourceImageService
  participant S3 as S3ImageApi

  User->>Controller: POST /fit/source-images/upload-url
  Controller->>Facade: createUploadUrl(userId)
  Facade->>ImgSvc: createPendingImage(userId)
  Facade->>S3: generatePresignedUrl(s3Key)
  S3-->>Facade: S3PresignedUrlInfo
  Facade->>ImgSvc: persist s3Key on image
  Facade-->>Controller: FittingSourceImageUploadInfo
  Controller-->>User: 200 OK (upload URL)

  User->>Controller: PUT /fit/source-images/{imageId}
  Controller->>Facade: completeImageUpload(imageId, userId)
  Facade->>ImgSvc: getFittingSourceImage(imageId, userId)
  Facade->>S3: getImageUrl(s3Key)
  Facade->>ImgSvc: completeImageUpload(image, imageUrl)
  Controller-->>User: 204 No Content
Loading
sequenceDiagram
  autonumber
  actor User
  participant Controller as FitController
  participant Facade as FitFacade
  participant Credit as CreditService
  participant Gemini as GeminiImageApi
  participant Redis as Redisson/Lua
  participant Http as External HTTP

  User->>Controller: POST /fit/ai-fitting (sourceUrl, clothingUrl)
  Controller->>Facade: generateAiFitting(userId, sourceUrl, clothingUrl)
  Facade->>Credit: executeWithCreditCheck(userId, action)
  Credit->>Redis: acquire lock (user)
  alt lock acquired
    Credit->>Redis: eval credit_check.lua(now)
    alt credit available
      Facade->>Gemini: generateAiFitting(source, clothing)
      Gemini->>Http: GET source image, GET clothing image
      Http-->>Gemini: 200 images
      Gemini->>Http: POST Gemini generateContent
      Http-->>Gemini: 200 response (candidates)
      Gemini-->>Facade: generated image ByteArray
      Credit->>Redis: eval credit_deduct.lua(now)
      Facade-->>Controller: 200 OK (image/png)
      Controller-->>User: image/png
    else no credit
      Credit-->>Facade: throw rate-limit error
      Controller-->>User: error (429/4xx)
    end
  else lock denied
    Credit-->>Facade: throw lock error
    Controller-->>User: error (429/4xx)
  end
  Credit->>Redis: unlock (finally)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • [FEAT] AI 피팅 기능 구현 #43 — AI 피팅 기능 구현 이슈로, Gemini 연동·S3 업로드·크레딧 시스템 등 본 PR의 목적과 직접적으로 일치함.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 이 PR 제목 “[FEAT] AI 피팅 기능 구현”은 주요 변경사항인 AI 기반 피팅 기능의 구현을 명확하고 간결하게 요약하고 있어 적절합니다.
Description Check ✅ Passed 제공된 설명은 템플릿의 구조(이슈 번호, As-Is, To-Be, 체크리스트, 스크린샷 섹션, 추가 설명)를 모두 포함하고 있어 요구 사항을 충족합니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat#43

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codrin2 codrin2 changed the title [FEAT] 피팅 기능 구현 [FEAT] AI 피팅 기능 구현 Sep 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/resources/application-prod.yaml (1)

60-81: 필수 환경 변수 명 불일치로 부팅 실패 발생

${AWS_S3_BUCKET}/${AWS_REGION} 플레이스홀더는 컨테이너에 주입되지 않아 Spring 부팅 단계에서 Could not resolve placeholder 예외가 발생합니다. 현재 배포 워크플로우는 CLOUD_AWS_S3_BUCKET/CLOUD_AWS_REGION_STATIC만 전달하므로, 설정 키와 환경 변수 명을 일치시켜 주세요.

-      bucket: ${AWS_S3_BUCKET}
+      bucket: ${CLOUD_AWS_S3_BUCKET}
 ...
-      static: ${AWS_REGION}
+      static: ${CLOUD_AWS_REGION_STATIC}
🧹 Nitpick comments (22)
src/main/kotlin/com/dh/baro/look/application/dto/AiFittingInfo.kt (1)

3-7: ByteArray 가변성으로 인한 동등성/해시 불안정 가능성 — 방어적 복사 또는 사용 제약 명시 권장

generatedImageData는 가변(ByteArray)이라 외부에서 내용 변경 시 equals/hashCode 결과가 달라질 수 있어, 컬렉션 키/Set 원소로 사용 시 심각한 버그가 발생할 수 있습니다. 방어적 복사(생성 시 copyOf) 또는 팩토리 메서드로 복사 생성, 최소한 Javadoc/KDoc에 “외부에서 변형 금지”를 명시하는 것을 권장합니다. toString()에서 바이트 내용을 노출하지 않도록 마스킹도 고려해 주세요.

가능한 toString() 보강 예시(파일 내 임의 위치 추가):

override fun toString(): String =
    "AiFittingInfo(sourceImageUrl=$sourceImageUrl, clothingImageUrl=$clothingImageUrl, generatedImageData=<${generatedImageData.size} bytes>)"
src/main/kotlin/com/dh/baro/look/domain/repository/FittingSourceImageRepository.kt (1)

8-8: 페이징/상한 없는 전체 조회 — Pageable/limit 메서드 추가 권장 및 인덱스 확인

OrderByIdDesc로 전체 List 반환은 사용자별 데이터가 많아질 경우 메모리/성능 이슈가 있습니다. Pageable 버전 또는 findFirstBy…OrderByIdDesc 추가를 권장합니다. 또한 (user_id, upload_status) 복합 인덱스를 DDL에 보장해 주세요.

예시:

fun findByUserIdAndUploadStatusOrderByIdDesc(
    userId: Long,
    uploadStatus: FittingSourceImageStatus,
    pageable: Pageable
): Page<FittingSourceImage>

fun findFirstByUserIdAndUploadStatusOrderByIdDesc(
    userId: Long,
    uploadStatus: FittingSourceImageStatus
): FittingSourceImage?
src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiResponse.kt (1)

3-17: 외부 API 스키마 변화 내성 확보 — 알 수 없는 필드 무시 설정 권장

응답 필드가 가변적인 특성상 @JsonIgnoreProperties(ignoreUnknown = true)를 부여해 방어적으로 역직렬화하는 것을 권장합니다.

적용 diff(주요 선언부에 어노테이션 추가):

 data class GeminiApiResponse(
-    val candidates: List<GeminiCandidate>?,
-    val usageMetadata: GeminiUsageMetadata?,
-)
+    val candidates: List<GeminiCandidate>?,
+    val usageMetadata: GeminiUsageMetadata?,
+) 
@@
-data class GeminiCandidate(
+@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
+data class GeminiCandidate(
     val content: GeminiContent?,
     val finishReason: String?,
 )
@@
-data class GeminiUsageMetadata(
+@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
+data class GeminiUsageMetadata(
     val promptTokenCount: Int?,
     val candidatesTokenCount: Int?,
     val totalTokenCount: Int?,
 )

추가로 파일 상단에 import를 두고 싶다면:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
src/main/kotlin/com/dh/baro/look/domain/service/CreditService.kt (1)

29-38: 신용 차감 타이밍과 사용자 경험

성공 후 차감(현재 방식)은 실패 시 “작업은 됐는데 차감 실패로 에러 반환” 가능성이 있습니다. 반대로 선차감은 실패 시 보상(환급) 로직이 필요합니다. 요구사항에 따라:

  • 워치독 기반으로 전체 구간을 단일 임계영역으로 유지(현재 권장 수정과 조합)하여 체크→실행→차감의 원자성을 최대화
  • 또는 RRateLimiter/원자적 Lua(체크+차감)로 ‘선차감-실패 시 롤백’ 패턴 구축
src/main/kotlin/com/dh/baro/look/domain/service/FittingSourceImageService.kt (2)

19-26: 조회 가독성 개선: findByIdOrNull 사용 제안

Optional.orElse(null) 패턴 대신 Kotlin 확장인 findByIdOrNull이 더 간결합니다.

-    val image = fittingSourceImageRepository.findById(imageId).orElse(null)
-        ?: throw IllegalArgumentException(ErrorMessage.FITTING_SOURCE_IMAGE_NOT_FOUND.format(imageId))
+    import org.springframework.data.repository.findByIdOrNull
+    val image = fittingSourceImageRepository.findByIdOrNull(imageId)
+        ?: throw IllegalArgumentException(ErrorMessage.FITTING_SOURCE_IMAGE_NOT_FOUND.format(imageId))

22-24: 예외 타입 일관성

require/requireNotNull는 IllegalArgumentException을 던집니다. 상위 계층에서 도메인 예외 구분이 필요하다면 도메인 전용 예외(또는 ErrorMessage 기반 예외)로 맞추는 편이 좋습니다.

src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt (2)

50-57: 이미지 MIME 타입 하드코딩

입력 이미지 타입을 모두 image/jpeg로 고정하고 있습니다. 실제 타입(PNG/WebP 등)에 맞춰 MIME을 세팅하거나 최소한 "image/*"로 포괄 지정하는 편이 안전합니다.

-                        GeminiImagePart(GeminiInlineData("image/jpeg", clothingImageData)),
-                        GeminiImagePart(GeminiInlineData("image/jpeg", sourceImageData)),
+                        GeminiImagePart(GeminiInlineData("image/*", clothingImageData)),
+                        GeminiImagePart(GeminiInlineData("image/*", sourceImageData)),

또는 호출부에서 실제 Content-Type을 받아와 반영하는 API로 확장하는 방안을 고려해주세요.


16-16: RestClient 구성 일원화 및 헤더 기본값 설정 제안

API 키 헤더와 타임아웃을 RestClient 빌더로 한 번만 구성하면 중복/누락을 줄일 수 있습니다.

예: RestClient.builder().baseUrl(geminiApiUrl).defaultHeader("x-goog-api-key", geminiApiKey) ... 로 생성한 빈을 주입.

src/main/kotlin/com/dh/baro/look/domain/FittingSourceImage.kt (1)

41-48: 세터에 입력값 검증 추가 제안

s3Key는 빈 문자열/공백을 거부하도록 최소 검증을 추가하면 데이터 정합성이 좋아집니다.

-    fun setS3Key(s3Key: String) {
-        this.s3Key = s3Key
-    }
+    fun setS3Key(s3Key: String) {
+        require(s3Key.isNotBlank()) { "s3Key must not be blank" }
+        this.s3Key = s3Key
+    }
src/main/kotlin/com/dh/baro/look/infra/redis/CreditRepositoryImpl.kt (1)

44-61: READ_ONLY 모드 가능 여부 검토

checkCreditAvailability가 쓰기 부작용이 없다면 RScript.Mode.READ_ONLY로 내리는 편이 안전합니다.

스크립트가 TTL 갱신 등 쓰기를 수행한다면 현 상태 유지가 맞습니다. 확인 부탁드립니다.

src/main/kotlin/com/dh/baro/look/infra/s3/S3ImageApi.kt (3)

26-43: S3Presigner 자원 안전하게 닫기

예외 시 close가 보장되지 않습니다. use 블록으로 자원 관리를 명확히 하세요.

-        val s3Presigner = S3Presigner.builder()
-            .region(Region.of(region))
-            .build()
+        val presignedRequest = S3Presigner.builder()
+            .region(Region.of(region))
+            .build()
+            .use { s3Presigner ->
+                val putObjectRequest = PutObjectRequest.builder()
+                    .bucket(bucketName)
+                    .key(s3Key)
+                    .build()
+
+                val presignRequest = PutObjectPresignRequest.builder()
+                    .putObjectRequest(putObjectRequest)
+                    .signatureDuration(duration)
+                    .build()
+
+                s3Presigner.presignPutObject(presignRequest)
+            }
-
-        val putObjectRequest = PutObjectRequest.builder()
-            .bucket(bucketName)
-            .key(s3Key)
-            .build()
-
-        val presignRequest = PutObjectPresignRequest.builder()
-            .putObjectRequest(putObjectRequest)
-            .signatureDuration(duration)
-            .build()
-
-        val presignedRequest = s3Presigner.presignPutObject(presignRequest)
-
-        s3Presigner.close()

54-58: 키 확장자(.jpg) 고정 vs 허용 MIME 목록 불일치

S3 키를 .jpg로 고정하면서 allowedTypes엔 png/webp가 포함되어 혼선을 유발합니다. 실제 업로드 타입과 키 확장자를 정렬하거나, 확장자를 제거/범용화하는 것이 좋습니다.

  • 모든 업로드가 JPEG임을 보장합니까? 아니라면 generatePresignedUrl에 contentType(또는 확장자) 인자를 추가하고 PutObjectRequest에 .contentType(...)을 지정해 강제하는 방안을 제안합니다.
  • 아니면 allowedTypes를 JPEG로만 제한하세요.

예시(시그니처 변경 필요):

-    fun generatePresignedUrl(imageId: Long): S3PresignedUrlInfo {
+    fun generatePresignedUrl(imageId: Long, contentType: String): S3PresignedUrlInfo {
-        val s3Key = generateS3Key(imageId)
+        val s3Key = generateS3Key(imageId, contentType)
@@
-        val putObjectRequest = PutObjectRequest.builder()
+        val putObjectRequest = PutObjectRequest.builder()
             .bucket(bucketName)
             .key(s3Key)
+            .contentType(contentType)
             .build()
@@
-    private fun generateS3Key(imageId: Long): String {
+    private fun generateS3Key(imageId: Long, contentType: String): String {
         val timestamp = System.currentTimeMillis()
         val uuid = UUID.randomUUID().toString().substring(0, 8)
-        return "fitting-source-images/$imageId/$timestamp-$uuid.jpg"
+        val ext = when (contentType.lowercase()) {
+            "image/jpeg", "image/jpg" -> "jpg"
+            "image/png" -> "png"
+            "image/webp" -> "webp"
+            else -> "bin"
+        }
+        return "fitting-source-images/$imageId/$timestamp-$uuid.$ext"
     }

Also applies to: 64-73


44-52: 콘텐츠 타입·사이즈 정책 집행 한계 안내

PUT presign은 파일 크기 제한을 강제하지 못합니다. 서버 측 업로드 완료 후 S3 객체 메타데이터(Content-Type/Content-Length) 검증과 초과 시 삭제 로직을 운영에 추가하세요.

src/main/kotlin/com/dh/baro/look/application/FitFacade.kt (2)

26-27: Kotlin 스타일 속성 접근 일관화(get/setS3Key → 속성 접근) 검토

도메인이 Kotlin 클래스라면 image.s3Key/image.s3Key = ... 형태의 속성 접근이 가독성과 일관성에 더 좋습니다. 이전 PR에서 선호하신 “불변 id는 게터 대신 필드 접근” 컨벤션과도 부합합니다.

현재 FittingSourceImage가 Kotlin 속성을 노출한다면 아래처럼 정리해 주세요.

  • pendingImage.setS3Key(s3Info.s3Key)pendingImage.s3Key = s3Info.s3Key
  • image.getS3Key()image.s3Key

Also applies to: 40-40


52-54: 외부 호출의 안정성(타임아웃/차단 시간) 점검

크레딧 체크 임계 구간에서 외부(GenAI) 호출을 동기 블록으로 수행하면 지연 시 크레딧 리소스 점유가 길어질 수 있습니다. HTTP 타임아웃, 재시도/백오프, 서킷브레이커 적용 여부를 확인해 주세요.

  • geminiImageApi 내부 HTTP 클라이언트에 연결/읽기/전체 타임아웃 설정
  • 재시도는 멱등성 보장되는 범위에서만 적용
  • 실패 시 크레딧 복구/롤백 시맨틱 확인(예약→성공 시 확정, 실패 시 자동 복구)
src/main/kotlin/com/dh/baro/look/presentation/FitController.kt (4)

44-45: 응답 콘텐츠 타입을 매핑에 선언(produces)해 문서/호환성 개선

이미지 바이트를 반환하므로 produces를 명시해 주세요.

-    @PostMapping("/ai-fitting")
+    @PostMapping("/ai-fitting", produces = [MediaType.IMAGE_PNG_VALUE])

44-49: Swagger 인터페이스와 시그니처 정합성 확보(override 추가)

FitSwagger에 AI 피팅 API가 누락되어 문서화에서 빠집니다. Swagger 인터페이스에 메서드를 추가하고 컨트롤러에서 override로 구현하세요. 우선 컨트롤러 메서드에 override를 붙여 정합성을 맞춰 주세요.

-    @PostMapping("/ai-fitting", produces = [MediaType.IMAGE_PNG_VALUE])
-    fun generateAiFitting(
+    @PostMapping("/ai-fitting", produces = [MediaType.IMAGE_PNG_VALUE])
+    override fun generateAiFitting(
         @CurrentUser userId: Long,
         @Valid @RequestBody request: AiFittingRequest,
     ): ResponseEntity<ByteArray> {

주의: FitSwagger.kt에 해당 시그니처와 문서가 추가되어야 합니다(아래 코멘트 참고).


45-53: 사용자 입력 URL의 출처 제한(SSRF/임의 외부 URL 사용) 검증

sourceImageUrl/clothingImageUrl가 임의 외부 URL이면 SSRF·라이선스·콘텐츠 안전성 문제가 생길 수 있습니다. 우리 S3 버킷(또는 허용 도메인)로만 제한하는 검증을 DTO/Validator에서 적용하세요.

  • AiFittingRequest에 허용 도메인 화이트리스트(예: 정규식으로 our‑bucket.s3..amazonaws.com) 검증
  • 필요 시 서버가 직접 바이트를 읽지 않도록(외부 다운로드 방지) 설계 확인

55-57: 대용량 이미지 대비 스트리밍 응답 고려

메모리에 통째로 적재하는 ByteArray 대신 ByteArrayResource/InputStreamResource 사용을 검토하세요.

예시:

return ResponseEntity.ok()
    .contentType(MediaType.IMAGE_PNG)
    .body(ByteArrayResource(fittingResult.generatedImageData))
src/main/kotlin/com/dh/baro/look/presentation/swagger/FitSwagger.kt (2)

266-269: AI 피팅 엔드포인트 문서화 누락 추가

컨트롤러에 존재하는 /fit/ai-fitting API가 문서에서 빠져 있습니다. 아래처럼 메서드를 추가해 주세요.

     fun getUserFittingSourceImages(
         @Parameter(hidden = true) userId: Long
     ): FittingSourceImageListResponse
-}
+    
+    /* ───────────────────────────── AI 피팅 생성 ───────────────────────────── */
+    @Operation(
+        summary = "AI 피팅 이미지 생성",
+        description = """
+            업로드된 소스 이미지와 의류 이미지를 사용해 AI 피팅 이미지를 생성합니다.
+            요청은 JSON으로 받고, 응답은 image/png 바이너리입니다.
+        """,
+        responses = [
+            ApiResponse(
+                responseCode = "200", description = "생성 성공",
+                content = [Content(
+                    mediaType = "image/png",
+                    schema = Schema(type = "string", format = "binary")
+                )]
+            ),
+            ApiResponse(
+                responseCode = "401",
+                description = "미인증",
+                content = [Content(mediaType = APPLICATION_JSON_VALUE, schema = Schema(implementation = ErrorResponse::class))]
+            ),
+            ApiResponse(
+                responseCode = "429",
+                description = "크레딧 부족 또는 요금제 제한",
+                content = [Content(mediaType = APPLICATION_JSON_VALUE, schema = Schema(implementation = ErrorResponse::class))]
+            )
+        ]
+    )
+    fun generateAiFitting(
+        @Parameter(hidden = true) userId: Long,
+        @io.swagger.v3.oas.annotations.parameters.RequestBody(
+            required = true,
+            content = [Content(schema = Schema(implementation = AiFittingRequest::class), mediaType = APPLICATION_JSON_VALUE)]
+        )
+        request: AiFittingRequest
+    ): ByteArray
+}

15-21: 문서 정의 방식 일관성 확인(인터페이스에 매핑 부재)

LookSwagger는 인터페이스에 @PostMapping 등 매핑을 포함하는 반면, FitSwagger는 매핑이 컨트롤러에만 있습니다. springdoc 설정에 따라 인터페이스의 @operation만으로는 병합이 안 될 수 있습니다. 두 가지 중 하나로 통일해 주세요.

옵션:

  • 인터페이스에 @RequestMapping("/fit") 및 각 메서드에 @PostMapping/@PutMapping/@GetMapping 추가
  • 혹은 컨트롤러 메서드에 @operation을 직접 선언해 인터페이스 의존 제거
src/main/resources/lua/credit_deduct.lua (1)

2-2: 사용하지 않는 인자 정리 제안
ARGV[1]에서 변환한 current_time이 이후 로직에서 전혀 쓰이지 않아 혼동을 줄이도록 제거하거나 사용할 로직을 추가하는 편이 좋겠습니다.

스크립트 호출부에서 인자를 계속 전달하더라도 아래처럼 불필요한 로컬 변수를 제거할 수 있습니다.

-local current_time = tonumber(ARGV[1])
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66af226 and 80be275.

📒 Files selected for processing (51)
  • .github/workflows/deploy-dev.yaml (1 hunks)
  • .github/workflows/deploy-prod.yaml (1 hunks)
  • gradle/core.gradle (1 hunks)
  • src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/Cursor.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/ErrorMessage.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/GlobalExceptionHandler.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/serialization/EventSerializer.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/serialization/JacksonEventSerializer.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/core/serialization/LongToStringSerializer.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/application/FitFacade.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/application/LookFacade.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/application/dto/AiFittingInfo.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/application/dto/FittingSourceImageUploadInfo.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/application/dto/LookCreateCommand.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/FittingSourceImage.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/FittingSourceImageStatus.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/repository/CreditRepository.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/repository/FittingSourceImageRepository.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/service/CreditService.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/service/FittingSourceImageService.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/service/LookService.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiRequest.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/redis/CreditRepositoryImpl.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/s3/S3ImageApi.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/s3/S3PresignedUrlInfo.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/FitController.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/AiFittingRequest.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageDto.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageListResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageUploadUrlResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/presentation/swagger/FitSwagger.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1 hunks)
  • src/main/resources/application-dev.yaml (2 hunks)
  • src/main/resources/application-prod.yaml (2 hunks)
  • src/main/resources/lua/credit_check.lua (1 hunks)
  • src/main/resources/lua/credit_deduct.lua (1 hunks)
  • src/test/kotlin/com/dh/baro/look/domain/LookServiceTest.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: codrin2
PR: S-BARO/server#17
File: src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt:38-59
Timestamp: 2025-08-12T13:52:29.449Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
🧬 Code graph analysis (16)
src/main/kotlin/com/dh/baro/core/Cursor.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (1)
  • serialize (7-15)
src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/kotlin/com/dh/baro/look/domain/FittingSourceImageStatus.kt (3)
src/main/kotlin/com/dh/baro/identity/domain/StoreStatus.kt (1)
  • DRAFT (3-10)
src/main/kotlin/com/dh/baro/order/domain/OrderStatus.kt (1)
  • ORDERED (3-10)
src/main/kotlin/com/dh/baro/core/outbox/MessageStatus.kt (1)
  • INIT (3-8)
src/main/kotlin/com/dh/baro/look/domain/service/LookService.kt (1)
src/main/kotlin/com/dh/baro/look/application/LookCreateCommand.kt (1)
  • creatorId (3-10)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/kotlin/com/dh/baro/look/presentation/dto/AiFittingRequest.kt (2)
src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt (2)
  • message (10-28)
  • message (20-26)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
  • min (7-46)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/kotlin/com/dh/baro/core/GlobalExceptionHandler.kt (2)
src/main/kotlin/com/dh/baro/core/advice/GlobalExceptionHandler.kt (8)
  • logger (25-159)
  • HttpStatus (153-158)
  • HttpStatus (146-151)
  • HttpStatus (116-121)
  • HttpStatus (79-84)
  • HttpStatus (95-100)
  • HttpStatus (131-136)
  • HttpStatus (109-114)
src/main/kotlin/com/dh/baro/core/ErrorResponse.kt (2)
  • Include (9-75)
  • field (36-51)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt (1)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
  • serialize (7-15)
  • serialize (8-14)
src/main/resources/lua/credit_deduct.lua (1)
src/main/kotlin/com/dh/baro/product/infra/redis/InventoryRedisRepository.kt (2)
  • redissonClient (13-136)
  • loadLuaScripts (22-27)
src/main/kotlin/com/dh/baro/look/infra/redis/CreditRepositoryImpl.kt (1)
src/main/kotlin/com/dh/baro/product/infra/redis/InventoryRedisRepository.kt (8)
  • redissonClient (13-136)
  • loadLuaScripts (22-27)
  • rollbackStocks (45-56)
  • it (115-115)
  • it (72-72)
  • it (89-89)
  • executeScript (65-107)
  • deductStocks (29-43)
src/main/kotlin/com/dh/baro/look/presentation/swagger/FitSwagger.kt (1)
src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt (6)
  • name (16-215)
  • summary (24-60)
  • summary (168-206)
  • summary (63-96)
  • summary (120-165)
  • summary (99-117)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (1)
src/main/kotlin/com/dh/baro/look/application/LookCreateCommand.kt (1)
  • creatorId (3-10)
src/main/kotlin/com/dh/baro/look/infra/s3/S3PresignedUrlInfo.kt (1)
src/main/kotlin/com/dh/baro/look/application/LookCreateCommand.kt (1)
  • creatorId (3-10)
🪛 detekt (1.23.8)
src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt

[warning] 38-38: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 72-72: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ 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: CI-CD
🔇 Additional comments (37)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt (1)

3-3: 패키지 이동 반영 적절합니다
LongToStringSerializer의 신규 위치를 잘 따라가고 있어서 직렬화 동작에 문제 없을 듯합니다.

src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt (1)

3-3: 직렬화 유틸 패키지 정리 확인
패키지 구조 개편에 맞춰 import만 조정된 상태라 다른 영향은 없어 보입니다.

src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)

3-3: Serializer 경로 업데이트 문제없습니다
Look/상품 DTO 전반과 동일하게 패키지 이동을 반영했네요. 기존 동작 그대로 유지됩니다.

src/main/kotlin/com/dh/baro/look/application/LookFacade.kt (1)

5-27: LookCreateCommand 경로 변경 검증 완료
새 dto 패키지로 이동한 LookCreateCommand를 사용하는 부분만 조정되었고, createLook 흐름에는 추가 영향이 없습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt (1)

3-3: Serializer import 일관성 유지
다른 DTO와 맞춰 패키지 경로만 변경된 것으로 확인했습니다.

src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt (1)

3-3: 인입된 패키지 경로 정리 OK
LongToStringSerializer 패키지 이동 반영만 있어서 기능 영향 없습니다.

src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt (1)

4-4: 패키지 리팩토링 반영 OK
LongToStringSerializer의 신규 패키지 경로를 정확히 따라가서 직렬화 설정이 계속 정상 동작하겠습니다.

src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (1)

3-3: 패키지 이동 처리 확인
새 패키지 경로로 직렬화기를 교체해 DTO 직렬화가 유지됩니다.

src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt (1)

3-3: 일관된 직렬화 패키지 적용
다른 DTO들과 동일하게 새 serialization 네임스페이스를 사용하여 혼선을 줄였습니다.

src/main/kotlin/com/dh/baro/look/domain/service/LookService.kt (1)

4-4: LookCreateCommand 위치 변경 반영 OK
DTO 패키지로 이동한 커맨드 객체를 정상 참조하고 있어 서비스 로직에 영향 없습니다.

src/main/kotlin/com/dh/baro/look/domain/FittingSourceImageStatus.kt (1)

1-6: 새 상태 enum 정의 확인
PENDING/COMPLETED 두 상태가 명확하게 선언되어 후속 도메인 로직에서 활용하기 좋습니다.

src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)

3-3: 직렬화기 경로 업데이트 완료
새 패키지 경로로 수정되어 다른 상품 DTO들과 일관성을 유지합니다.

src/main/kotlin/com/dh/baro/core/serialization/JacksonEventSerializer.kt (1)

1-1: 직렬화 모듈 패키지 통합 적용
직렬화 관련 컴포넌트를 동일 패키지로 정리해 모듈 구조가 더 명확해졌습니다.

src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1)

3-3: 패키지 경로 정리 완료
CategoryResponse에서도 새 LongToStringSerializer 위치를 사용해 전역 리팩토링이 일관됩니다.

src/main/kotlin/com/dh/baro/look/domain/repository/CreditRepository.kt (1)

3-5: 인터페이스 설계 적절합니다
Redis 구현과 자연스럽게 맞물릴 수 있는 최소 계약만 노출되어 있어 확장하기 좋아 보입니다.

src/main/kotlin/com/dh/baro/look/application/dto/FittingSourceImageUploadInfo.kt (1)

5-11: DTO 구성 깔끔합니다
사전 서명 URL 흐름에 필요한 정보가 모두 포함돼 있어 상위 계층 매핑이 수월할 것 같습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (1)

3-30: 패키지 정리 잘 반영됐습니다
LookCreateCommand 이동에 맞춰 표현 계층에서도 일관되게 참조하도록 정리된 점 좋습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageDto.kt (1)

6-18: 매퍼 책임 분리가 명확합니다
도메인 엔티티를 표현용 DTO로 변환하는 역할이 컴패니언으로 깔끔히 정리돼 있어 재사용에 용이해 보여요.

src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageListResponse.kt (1)

5-12: 리스트 응답 구조 적합합니다
단일 DTO 매핑을 재활용해 일관된 응답 형태를 보장하는 점이 좋습니다.

src/main/kotlin/com/dh/baro/look/application/dto/AiFittingInfo.kt (1)

8-26: LGTM — 배열 콘텐츠 기준 동등성/해시 구현 적절

contentEquals/contentHashCode 사용으로 ByteArray 참조 동등성 문제를 올바르게 회피했습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/FittingSourceImageUploadUrlResponse.kt (1)

6-22: LGTM — 계층 간 DTO 변환 단순·명확

Instant 직렬화 포맷(ISO-8601)과 allowedTypes가 프론트 기대치와 합치하는지 최종 확인만 부탁드립니다.

src/main/kotlin/com/dh/baro/core/ErrorMessage.kt (1)

54-65: LGTM — 에러 메시지 체계 확장 적절

도메인별로 구체적인 메시지가 추가되어 예외 매핑 및 관측성 향상에 도움이 됩니다.

src/main/kotlin/com/dh/baro/look/infra/redis/CreditRepositoryImpl.kt (1)

25-42: 루아 스크립트 리소스 포함 확인 src/main/resources/lua 경로에 credit_check.lua 및 credit_deduct.lua가 존재하여 패키징에 포함됩니다.

src/main/kotlin/com/dh/baro/core/Cursor.kt (1)

3-3: Serializer 패키지 이동 반영 확인
LongToStringSerializer import 경로가 새 패키지 구조에 맞게 정리되어 일관성이 유지됩니다.

.github/workflows/deploy-dev.yaml (1)

117-121: 배포 환경 변수 확장 확인
S3·Gemini 관련 시크릿을 컨테이너 환경에 주입하도록 추가되어, 애플리케이션 설정과 잘 맞춰졌습니다.

src/main/kotlin/com/dh/baro/core/serialization/EventSerializer.kt (1)

1-5: 패키지 정리 문제 없음
직렬화 관련 타입을 동일 패키지로 모아둔 구조가 분명해져, 추가 조정 없이도 의존성이 깔끔합니다.

.github/workflows/deploy-prod.yaml (1)

111-115: 프로덕션 환경 변수 주입 확인
Prod 배포에도 동일한 AWS/Gemini 시크릿이 전달되어 환경별 구성 차이를 최소화했습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/AiFittingRequest.kt (1)

5-11: 요청 DTO 검증 구성 적절
필수 URL 필드에 @NotBlank를 적용해 기본 입력 검증을 확보한 구성이 적절합니다.

src/main/kotlin/com/dh/baro/core/GlobalExceptionHandler.kt (1)

1-24: 패키지 이동 후 의존성 정상
전역 예외 핸들러를 루트 패키지로 옮겨 ErrorResponse 등과 같은 공간에서 관리하게 되어 추가 import 없이도 동작이 명확합니다.

src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)

3-3: Serializer 경로 업데이트 확인
LongToStringSerializer의 새 위치를 반영해 직렬화가 계속 정상 동작할 수 있도록 정리된 점 좋습니다.

src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (1)

3-3: 패키지 변경 반영 깔끔합니다.
LongToStringSerializer 새 위치로의 import 교체가 정확히 적용되어 직렬화 동작에 영향이 없음을 확인했습니다.

gradle/core.gradle (1)

3-4: AWS SDK BOM 도입 적절합니다.
BOM과 S3 모듈을 함께 추가해 버전 정합성이 확보된 점 확인했습니다.

src/main/kotlin/com/dh/baro/core/serialization/LongToStringSerializer.kt (1)

1-1: 직렬화 유틸의 패키지 이동 확인했습니다.
패키지 선언만 교체되어 기존 시리얼라이저 동작은 그대로 유지됩니다.

src/test/kotlin/com/dh/baro/look/domain/LookServiceTest.kt (1)

4-4: 테스트 import 정정 완료되었습니다.
LookCreateCommand의 새 위치를 테스트에서도 일관되게 참조하고 있습니다.

src/main/resources/application-dev.yaml (1)

60-81: 새 인프라 설정 추가가 일관됩니다.
S3 버킷/리전 및 Gemini API 설정이 환경 변수 기반으로 안전하게 연결되었습니다.

src/main/kotlin/com/dh/baro/look/application/dto/LookCreateCommand.kt (1)

1-10: DTO 패키지 정리 확인했습니다.
도메인 커맨드 객체가 dto 패키지로 이동했지만 구조는 유지되어 호환성 문제가 없습니다.

src/main/kotlin/com/dh/baro/look/infra/s3/S3PresignedUrlInfo.kt (1)

1-12: 프리사인 URL 응답 모델 구성 적절합니다.
필요 필드를 모두 포함한 DTO로 추가된 점 확인했습니다.

@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/main/kotlin/com/dh/baro/look/domain/service/CreditService.kt (1)

19-23: 인터럽트 플래그 복구 필요

InterruptedException을 잡은 뒤 플래그를 복구하지 않으면 상위 호출자가 인터럽트 사실을 감지하지 못해 후속 처리(정리, 중단 등)에 실패할 수 있습니다. IllegalStateException을 던지기 전에 Thread.currentThread().interrupt()를 호출해 인터럽트 신호를 보존해 주세요.

         val acquired = try {
             lock.tryLock(LOCK_WAIT_TIME, TimeUnit.SECONDS)
         } catch (e: InterruptedException) {
+            Thread.currentThread().interrupt()
             throw IllegalStateException(ErrorMessage.AI_FITTING_TOKEN_BUCKET_ERROR.message)
         }
src/main/kotlin/com/dh/baro/look/domain/FittingSourceImage.kt (1)

40-43: 업로드 완료 상태 전환 시 중복 호출 방지 장치가 필요합니다.

markAsUploaded가 이미 COMPLETED인 엔티티에서도 다시 호출될 수 있어, 재시도나 중복 요청이 들어오면 마지막 호출이 imageUrl을 덮어써 버립니다. 업로드 완료 이후 상태를 비가역적으로 유지하도록 가드 로직을 두는 편이 안전합니다.

     fun markAsUploaded(imageUrl: String) {
-        this.imageUrl = imageUrl
-        this.uploadStatus = FittingSourceImageStatus.COMPLETED
+        if (uploadStatus == FittingSourceImageStatus.COMPLETED) {
+            throw IllegalStateException("이미 업로드가 완료된 이미지입니다.")
+        }
+
+        this.imageUrl = imageUrl
+        this.uploadStatus = FittingSourceImageStatus.COMPLETED
     }
src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt (2)

28-40: 예외 원인 보존을 위해 cause 전달 필요

현재 IllegalArgumentException으로 감싸며 원래 예외를 버리고 있어 스택 추적이 유실됩니다. 운영 장애 분석이 어려워지니 cause를 함께 넘겨 주세요.

-    } catch (e: Exception) {
-        throw IllegalArgumentException(ErrorMessage.IMAGE_DOWNLOAD_FAILED.format(e.message ?: "Unknown error"))
+    } catch (e: Exception) {
+        throw IllegalArgumentException(
+            ErrorMessage.IMAGE_DOWNLOAD_FAILED.format(e.message ?: "Unknown error"),
+            e,
+        )

63-74: API 호출 실패 시 원인 추적이 어렵습니다

여기도 동일하게 cause를 전달하지 않아 예외 체인이 끊깁니다. 동일한 방식으로 수정 부탁드립니다.

-    } catch (e: Exception) {
-        throw IllegalArgumentException(ErrorMessage.GEMINI_API_REQUEST_FAILED.format(e.message ?: "Unknown error"))
+    } catch (e: Exception) {
+        throw IllegalArgumentException(
+            ErrorMessage.GEMINI_API_REQUEST_FAILED.format(e.message ?: "Unknown error"),
+            e,
+        )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80be275 and 0a5283f.

📒 Files selected for processing (9)
  • src/main/kotlin/com/dh/baro/look/application/FitFacade.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/FittingSourceImage.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/service/CreditService.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/domain/service/FittingSourceImageService.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiRequest.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiResponse.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt (1 hunks)
  • src/main/kotlin/com/dh/baro/look/infra/s3/S3ImageApi.kt (1 hunks)
  • src/main/resources/lua/credit_check.lua (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/resources/lua/credit_check.lua
  • src/main/kotlin/com/dh/baro/look/application/FitFacade.kt
  • src/main/kotlin/com/dh/baro/look/infra/s3/S3ImageApi.kt
  • src/main/kotlin/com/dh/baro/look/domain/service/FittingSourceImageService.kt
  • src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiResponse.kt
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: codrin2
PR: S-BARO/server#17
File: src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt:38-59
Timestamp: 2025-08-12T13:52:29.449Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
🧬 Code graph analysis (1)
src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiApiRequest.kt (1)
src/main/kotlin/com/dh/baro/identity/infra/KakaoUserInfoResponse.kt (1)
  • ignoreUnknown (6-22)
🪛 detekt (1.23.8)
src/main/kotlin/com/dh/baro/look/infra/gemini/GeminiImageApi.kt

[warning] 38-38: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 72-72: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ 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: CI-CD

@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Sep 25, 2025
@codrin2 codrin2 merged commit 42a718f into develop Sep 25, 2025
2 checks passed
@codrin2 codrin2 deleted the feat#43 branch September 25, 2025 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] AI 피팅 기능 구현

2 participants