-
Notifications
You must be signed in to change notification settings - Fork 0
Deploy / Merge to Main #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… and consultation controllers
… upload endpoints
…n admin and CMS member responses
Feature / s3 / image path
Walkthrough이 PR은 세 가지 주요 변경사항을 도입합니다: (1) Jakarta Bean Validation 애노테이션( Sequence Diagram(s)sequenceDiagram
participant Client as 클라이언트
participant Controller as 컨트롤러
participant Service as 서비스
participant Resolver as ImageUrlResolver
participant Storage as ImageStorageService
participant S3 as S3Manager
participant DB as 데이터베이스
Client->>Controller: 프로필 이미지 업로드<br/>(multipart/form-data)
Controller->>Service: uploadProfileImage()
rect rgb(200, 220, 255)
Note over Service,Resolver: 새 이미지 경로 해석
Service->>Resolver: toStoredPath(newImageUrl)
Resolver-->>Service: 변환된 저장 경로
end
rect rgb(255, 220, 200)
Note over Service,Storage: 기존 이미지 삭제 (필요시)
Service->>Service: deleteOldProfileImageUrlIfChanged()
Service->>Resolver: toS3Key(oldImageUrl)
Resolver-->>Service: S3 키
Service->>Storage: deleteImage(S3 키)
Storage->>S3: deleteObject()
S3-->>Storage: 삭제 완료
end
rect rgb(220, 255, 220)
Note over Service,DB: 프로필 정보 업데이트
Service->>DB: 새 이미지 경로로 엔티티 업데이트
DB-->>Service: 업데이트 완료
end
rect rgb(255, 240, 200)
Note over Service,Resolver: 응답 생성 (CDN URL 포함)
Service->>Resolver: toPublicUrl(storedPath)
Resolver-->>Service: 공개 CDN URL
Service-->>Controller: 응답 (CDN URL 포함)
Controller-->>Client: HTTP 200 + 응답
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 주의가 필요한 영역:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/main/java/redot/redot_server/domain/cms/site/style/controller/docs/StyleInfoControllerDocs.java (1)
27-31: 검증 실패 시 400 응답 문서화를 고려하세요.
@Valid애노테이션을 사용하면 검증 실패 시 자동으로 400 Bad Request 응답이 반환됩니다. API 문서의 완성도를 높이기 위해@ApiResponse를 추가하는 것을 고려해보세요.예시:
@ApiResponse(responseCode = "400", description = "잘못된 요청 (검증 실패)")src/main/java/redot/redot_server/domain/auth/controller/docs/RedotMemberAuthControllerDocs.java (1)
55-58: 소셜 로그인 URL 조회 메소드의 파라미터 검증을 고려해 보세요.
getSocialLoginUrl메소드의 String 파라미터들(provider,redirectUri,failureUri)에도 검증 애노테이션을 추가하면 더 견고한 API가 됩니다.예를 들어:
provider:@NotBlank추가redirectUri,failureUri:@NotBlank및@Pattern(URL 형식 검증) 추가적용 예시:
ResponseEntity<SocialLoginUrlResponse> getSocialLoginUrl( - @Parameter(description = "소셜 로그인 제공자", example = "google") String provider, - @Parameter(description = "인가 완료 후 리다이렉트될 URI", example = "https://app.redot.com/auth/callback") String redirectUri, - @Parameter(description = "실패 시 리다이렉트될 URI", example = "https://app.redot.com/auth/fail") String failureUri); + @Parameter(description = "소셜 로그인 제공자", example = "google") @NotBlank String provider, + @Parameter(description = "인가 완료 후 리다이렉트될 URI", example = "https://app.redot.com/auth/callback") @NotBlank String redirectUri, + @Parameter(description = "실패 시 리다이렉트될 URI", example = "https://app.redot.com/auth/fail") @NotBlank String failureUri);필요한 경우 다음 import도 추가하세요:
import jakarta.validation.constraints.NotBlank;src/main/java/redot/redot_server/global/s3/service/ImageStorageService.java (1)
34-40: 삭제 로직 개선: 방어적 검사 추가됨Line 36의
s3Manager.exists(key)검사는 존재하지 않는 파일 삭제를 시도하여 발생하는 오류를 방지합니다. 이는 추가적인 S3 API 호출 비용이 발생하지만, 견고성을 위한 합리적인 트레이드오프입니다.빈 키 검사(
!StringUtils.hasText(key))와 함께 사용되어 안전한 삭제 작업을 보장합니다.성능이 중요한 경우, S3 삭제 작업이 존재하지 않는 키에 대해 idempotent하다면
exists()검사를 제거하고 삭제 실패를 무시하도록 고려할 수 있습니다:public void delete(String imageUrl) throws ImageUploadException { String key = imageUrlResolver.toS3Key(imageUrl); - if (!StringUtils.hasText(key) || !s3Manager.exists(key)) { + if (!StringUtils.hasText(key)) { return; } - s3Manager.deleteFile(key); + try { + s3Manager.deleteFile(key); + } catch (Exception e) { + // Log and ignore if file doesn't exist + } }src/main/java/redot/redot_server/global/s3/util/ImageUrlResolver.java (1)
78-80: isExternalUrl 메서드의 URL 스킴 범위를 확인하세요.현재는
http://와https://만 외부 URL로 인식합니다. 이미지 URL 용도로는 충분하지만, 향후 다른 스킴(예:data:,blob:)을 지원해야 할 가능성이 있다면 고려해주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
src/main/java/redot/redot_server/domain/admin/controller/AdminController.java(2 hunks)src/main/java/redot/redot_server/domain/admin/controller/docs/AdminConsultationControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/admin/controller/docs/AdminControllerDocs.java(4 hunks)src/main/java/redot/redot_server/domain/admin/controller/docs/AdminRedotAppControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/admin/dto/response/AdminResponse.java(2 hunks)src/main/java/redot/redot_server/domain/admin/service/AdminService.java(5 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/AdminAuthControllerDocs.java(3 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/CMSAuthControllerDocs.java(3 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/EmailVerificationControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/RedotMemberAuthControllerDocs.java(3 hunks)src/main/java/redot/redot_server/domain/auth/service/AdminAuthService.java(3 hunks)src/main/java/redot/redot_server/domain/auth/service/CMSAuthService.java(3 hunks)src/main/java/redot/redot_server/domain/auth/service/RedotMemberAuthService.java(4 hunks)src/main/java/redot/redot_server/domain/cms/inquiry/controller/docs/RedotAppInquiryControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/cms/member/controller/CMSMemberController.java(2 hunks)src/main/java/redot/redot_server/domain/cms/member/controller/docs/CMSMemberControllerDocs.java(5 hunks)src/main/java/redot/redot_server/domain/cms/member/dto/response/CMSMemberResponse.java(2 hunks)src/main/java/redot/redot_server/domain/cms/member/service/CMSMemberService.java(5 hunks)src/main/java/redot/redot_server/domain/cms/site/page/controller/docs/CMSSitePageControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/cms/site/setting/controller/SiteSettingController.java(2 hunks)src/main/java/redot/redot_server/domain/cms/site/setting/controller/docs/SiteSettingControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/cms/site/setting/dto/response/SiteSettingResponse.java(2 hunks)src/main/java/redot/redot_server/domain/cms/site/setting/service/SiteSettingService.java(4 hunks)src/main/java/redot/redot_server/domain/cms/site/style/controller/docs/StyleInfoControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/redot/app/controller/docs/RedotAppControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/redot/app/service/RedotAppCreationService.java(3 hunks)src/main/java/redot/redot_server/domain/redot/app/service/RedotAppService.java(4 hunks)src/main/java/redot/redot_server/domain/redot/consultation/controller/docs/ConsultationControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/redot/member/controller/RedotMemberController.java(2 hunks)src/main/java/redot/redot_server/domain/redot/member/controller/docs/RedotMemberControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/redot/member/dto/response/RedotMemberResponse.java(2 hunks)src/main/java/redot/redot_server/domain/redot/member/service/RedotMemberService.java(3 hunks)src/main/java/redot/redot_server/domain/site/domain/controller/docs/DomainControllerDocs.java(2 hunks)src/main/java/redot/redot_server/global/s3/service/ImageStorageService.java(2 hunks)src/main/java/redot/redot_server/global/s3/util/ImageUrlResolver.java(1 hunks)src/main/java/redot/redot_server/global/s3/util/S3Manager.java(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/redot/redot_server/global/s3/service/ImageStorageService.java (1)
src/main/java/redot/redot_server/global/s3/util/ImagePathGenerator.java (1)
ImagePathGenerator(8-35)
🔇 Additional comments (55)
src/main/java/redot/redot_server/domain/admin/controller/docs/AdminConsultationControllerDocs.java (2)
9-9:@Validimport 추가는 적절합니다.
컨트롤러 전반에 검증을 확장하려는 의도와 일관됩니다.
36-37: Docs 인터페이스에 붙인@Valid가 “실제 검증”까지 보장되는지 확인 필요.
Spring MVC가 구현 컨트롤러 핸들러 메서드에서 인터페이스의 파라미터 애노테이션을 항상 반영하는지는 케이스가 갈릴 수 있어, 구현 메서드에도@Valid(및 필요 시 클래스/메서드 레벨@Validated)가 적용돼 있는지와ConsultationUpdateRequest에 실제 제약 애노테이션이 존재하는지 확인 부탁드립니다.src/main/java/redot/redot_server/domain/site/domain/controller/docs/DomainControllerDocs.java (1)
8-8: Bean Validation이 올바르게 구현되었습니다.구현 컨트롤러(DomainController)의 메서드 파라미터에
@Valid애노테이션이 올바르게 적용되어 있으며,SubdomainLookupRequestDTO에는@NotBlank제약조건이 정의되어 있습니다. 인터페이스와 구현 클래스 모두에서 검증이 일관되게 설정되어 있어 런타임 검증이 정상적으로 동작할 것입니다.src/main/java/redot/redot_server/domain/admin/controller/docs/AdminRedotAppControllerDocs.java (2)
8-8: LGTM! 검증 지원을 위한 임포트 추가Jakarta Bean Validation을 활성화하기 위해
@Valid임포트가 올바르게 추가되었습니다.
19-19: LGTM! 요청 검증 강화
@Valid애노테이션을 통해RedotAppCreateRequest에 대한 빈 검증이 활성화되었습니다. 표준 Jakarta Bean Validation 패턴을 올바르게 따르고 있습니다.src/main/java/redot/redot_server/domain/redot/app/controller/docs/RedotAppControllerDocs.java (3)
10-10: LGTM! 검증 지원 임포트 추가Jakarta Bean Validation을 위한
@Valid임포트가 올바르게 추가되었습니다.
40-41: LGTM! 앱 생성 요청 검증 추가
createRedotApp메서드의 요청 파라미터에@Valid애노테이션이 추가되어 빈 검증이 활성화되었습니다. 표준 패턴을 올바르게 따르고 있습니다.
46-47: LGTM! 매니저 생성 요청 검증 추가
createManager메서드의 요청 파라미터에도@Valid애노테이션이 추가되어 일관된 검증 패턴이 적용되었습니다.src/main/java/redot/redot_server/domain/redot/consultation/controller/docs/ConsultationControllerDocs.java (2)
8-8: LGTM: 검증 애노테이션 import가 올바르게 추가되었습니다.Jakarta Bean Validation의
@Validimport가 적절하게 추가되어 요청 검증을 지원합니다.
19-19: @Valid 애노테이션이 올바르게 적용되었습니다.
ConsultationCreateRequest에는@NotBlank,@Size,@NotNull등의 검증 애노테이션이 완벽하게 정의되어 있습니다.- 실제 컨트롤러 구현(
ConsultationController)에도@Valid애노테이션이 이미 적용되어 있습니다.- 문서화 인터페이스에
@Valid를 추가하여 API 계약을 명확히 하는 것은 모범 사례입니다.src/main/java/redot/redot_server/domain/auth/controller/docs/CMSAuthControllerDocs.java (1)
11-11: 인증 엔드포인트 검증 강화 승인요청에
@Valid애노테이션을 추가한 것은 입력 검증을 강화하는 우수한 구현입니다. 특히 로그인 및 비밀번호 재설정과 같은 보안에 민감한 엔드포인트에 적합합니다.검증 인프라가 이미 적절히 구성되어 있음을 확인했습니다:
SignInRequest와PasswordResetConfirmRequestDTO에@NotBlank,GlobalExceptionHandler에서@ExceptionHandler(MethodArgumentNotValidException.class)로 검증 실패 시 통일된 에러 응답(INVALID_INPUT_VALUE)을 반환하고 있습니다.src/main/java/redot/redot_server/domain/auth/controller/docs/EmailVerificationControllerDocs.java (1)
8-8: 요청 검증이 올바르게 구현되었습니다.
@Valid애노테이션 추가를 통해 이메일 인증 요청에 대한 검증을 활성화한 것은 좋은 변경사항입니다.확인 결과:
EmailVerificationSendRequest와EmailVerificationVerifyRequest모두@NotBlank,@NotNull등의 검증 제약사항이 정의되어 있습니다.GlobalExceptionHandler에서MethodArgumentNotValidException과BindException을 처리하는 예외 핸들러가 구현되어 있습니다.spring-boot-starter-validation의존성이build.gradle에 포함되어 있습니다.모든 필수 구성 요소가 정상적으로 갖춰져 있으므로 검증 기능이 예상대로 작동할 것입니다.
src/main/java/redot/redot_server/domain/auth/controller/docs/AdminAuthControllerDocs.java (1)
10-10: 입력 검증 강화 구현 완료 확인됨요청 DTO에
@Valid애노테이션을 추가하여 입력 검증을 강화한 변경사항이 완전하게 구현되었습니다:
- ✓ 실제 컨트롤러 구현체(
AdminAuthController)의 모든 세 메서드(signIn,createAdmin,confirmPasswordReset)에@Valid애노테이션이 적용됨- ✓ 모든 요청 DTO에 적절한 검증 제약조건 정의됨:
SignInRequest:@NotBlank,AdminCreateRequest:@NotBlank,PasswordResetConfirmRequest:@NotBlank,- ✓ 글로벌 예외 핸들러(
GlobalExceptionHandler)에서MethodArgumentNotValidException및BindException처리 설정 완료실서버 배포에 적합한 상태입니다.
src/main/java/redot/redot_server/domain/redot/member/controller/docs/RedotMemberControllerDocs.java (1)
9-9: 좋습니다!
@NotNull애노테이션을 사용하기 위한 import 추가가 적절합니다.src/main/java/redot/redot_server/domain/cms/site/style/controller/docs/StyleInfoControllerDocs.java (1)
10-10: LGTM! 검증 애노테이션 추가가 올바릅니다.Jakarta Bean Validation 임포트와
@Valid애노테이션이 올바르게 추가되어 요청 검증이 활성화됩니다.Also applies to: 31-31
src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.java (1)
10-10: 입력 검증 지원을 위한 올바른 import 추가Jakarta Bean Validation의 표준
@Validimport가 올바르게 추가되었습니다.src/main/java/redot/redot_server/global/s3/util/S3Manager.java (3)
9-9: LGTM!입력 검증을 위한
StringUtils임포트 추가가 적절합니다.
86-88: LGTM!
exists메서드에 null/empty 검증 추가가 적절합니다.deleteFile의 검증 패턴과 일관성 있게 구현되었습니다.
59-62:uploadFile과deleteFile간의 경로 형식 불일치 가능성을 검증하세요.
uploadFile메서드가 선행 슬래시가 포함된 경로를 반환하고deleteFile이 정규화 과정 없이 key를 직접 사용하는 경우,uploadFile의 반환값을deleteFile에 직접 전달하는 호출 지점에서 S3 키 불일치가 발생할 수 있습니다. 두 메서드 간의 경로 형식 일관성과 실제 호출 지점에서의 사용 패턴을 확인하세요.src/main/java/redot/redot_server/domain/cms/inquiry/controller/docs/RedotAppInquiryControllerDocs.java (2)
10-10: LGTM! 입력 검증 강화요청 본문에 대한
@Valid애노테이션 추가는 Jakarta Bean Validation을 활용한 적절한 입력 검증 방식입니다. 이를 통해RedotAppInquiryCreateRequest에 정의된 제약 조건이 자동으로 검증됩니다.Also applies to: 28-29
44-47: 이 주석은 부정확합니다. 수정이 필요하지 않습니다.
RedotAppInquirySearchCondition에는 검증 애노테이션이 정의되어 있지 않으므로,@Valid를 추가할 필요가 없습니다. 또한createInquiry의@Valid는 요청 본문(request body)에 적용되지만,getAllInquiries의searchCondition은 쿼리 파라미터이므로 다른 용도입니다. 코드베이스 전체에서 검색 조건 파라미터는@ParameterObject를 사용하며@Valid를 적용하지 않는 일관된 패턴을 따르고 있습니다.src/main/java/redot/redot_server/domain/redot/member/controller/RedotMemberController.java (1)
5-5: LGTM: 명시적인 콘텐츠 타입 지정멀티파트 업로드 엔드포인트에
consumes = MediaType.MULTIPART_FORM_DATA_VALUE를 명시적으로 지정한 것은 API 계약을 명확히 하고 더 나은 클라이언트 오류 메시지를 제공하는 좋은 관행입니다.Also applies to: 42-42
src/main/java/redot/redot_server/domain/redot/member/service/RedotMemberService.java (3)
20-20: ImageUrlResolver 의존성 추가 확인새로운
ImageUrlResolver가 서비스에 추가되어 URL 변환을 담당합니다. 이는 저장 경로와 공개 URL 간의 일관된 변환을 제공하는 아키텍처 개선입니다.Also applies to: 32-32
84-89: 삭제 로직 메서드 시그니처 개선됨메서드가 이제 전체 요청 객체 대신 변환된 저장 경로를 직접 받도록 변경되어 책임이 더 명확해졌습니다. Line 86의 비교 로직은 저장 경로끼리 비교하므로 정확합니다.
75-82: 데이터베이스에 저장된 URL 형식 일관성 검증 필요Line 75에서 요청의 공개 URL을 저장 경로로 변환하여 데이터베이스에 저장하고, Line 86에서 이전 URL과 비교합니다.
문제는
imageUrlResolver.toStoredPath()의 동작에 있습니다:
- CDN URL (예:
https://cdn.example.com/redot/member/profile/1/image.jpg)을 받으면 CDN 부분을 제거하여/redot/member/profile/1/image.jpg형식으로 변환합니다.- 하지만 이전 데이터가 업로드 API에서 반환된 공개 URL 형식으로 저장되어 있다면, 형식 불일치로 인해 비교가 실패하여 불필요한 이미지 삭제 이벤트가 발생할 수 있습니다.
특히 uploadProfileImage가 반환하는 공개 URL (ImageStorageService.upload의 toPublicUrl 결과)을 다시 updateRedotMemberInfo에 전달할 때 이 문제가 발생합니다. 같은 이미지에 대한 업데이트인데도 형식 차이로 인해 삭제 로직이 작동하게 됩니다.
CMSMemberService도 동일한 패턴을 사용하므로 같은 문제가 존재합니다.
src/main/java/redot/redot_server/domain/cms/site/setting/controller/SiteSettingController.java (1)
6-6: LGTM: 멀티파트 엔드포인트에 명시적 콘텐츠 타입 지정로고 업로드 엔드포인트에
consumes = MediaType.MULTIPART_FORM_DATA_VALUE를 명시하여 API 명세를 명확히 했습니다. 다른 업로드 엔드포인트들과 일관된 패턴입니다.Also applies to: 42-42
src/main/java/redot/redot_server/domain/admin/dto/response/AdminResponse.java (1)
14-22: LGTM: 응답 DTO에서 공개 URL 변환
AdminResponse팩토리 메서드가ImageUrlResolver를 받아 저장 경로를 공개 URL로 변환합니다. 이는 저장소에는 경로를 저장하고 클라이언트에는 공개 URL을 노출하는 깔끔한 분리를 제공합니다.src/main/java/redot/redot_server/domain/auth/service/AdminAuthService.java (1)
17-17: LGTM: ImageUrlResolver 통합
AdminAuthService에ImageUrlResolver가 추가되어 응답 생성 시 이미지 URL 변환을 수행합니다. 다른 인증 서비스들과 일관된 패턴입니다.Also applies to: 33-33, 73-73
src/main/java/redot/redot_server/global/s3/service/ImageStorageService.java (1)
14-14: LGTM: 이미지 업로드 시 URL 변환 추가
ImageStorageService가 업로드 후 저장 경로를 공개 URL로 변환하여 반환합니다. 이는 클라이언트가 즉시 사용 가능한 CDN URL을 받을 수 있도록 합니다.Also applies to: 24-24, 30-31
src/main/java/redot/redot_server/domain/auth/service/RedotMemberAuthService.java (1)
18-18: LGTM: RedotMember 응답에 ImageUrlResolver 통합회원가입 및 현재 회원 정보 조회 시
ImageUrlResolver를 응답 생성에 전달하여 프로필 이미지 URL 변환을 일관되게 처리합니다.AdminAuthService와 동일한 패턴을 따릅니다.Also applies to: 34-34, 58-58, 112-112
src/main/java/redot/redot_server/domain/admin/controller/AdminController.java (1)
9-9: LGTM: 관리자 프로필 이미지 업로드 엔드포인트 개선
consumes = MediaType.MULTIPART_FORM_DATA_VALUE를 명시하여 멀티파트 요청만 허용하도록 설정했습니다.RedotMemberController,SiteSettingController와 일관된 패턴으로 코드베이스 전체의 업로드 엔드포인트가 통일되었습니다.Also applies to: 95-95
src/main/java/redot/redot_server/domain/cms/member/controller/CMSMemberController.java (1)
119-127: multipart consumes 명시는 👍, 다만 파라미터 검증(@NotNull) 실제 적용 여부 확인 필요
consumes = MediaType.MULTIPART_FORM_DATA_VALUE추가는 좋습니다.@RequestPart에 붙은@NotNull이 런타임에 기대대로 400으로 떨어지려면 컨트롤러/전역의 method validation 설정(@validated 등)이 갖춰져 있는지 확인해 주세요.src/main/java/redot/redot_server/domain/redot/app/service/RedotAppCreationService.java (1)
41-92:ImageUrlResolver빈 등록/Null-safe 계약만 확실히 하면 흐름은 깔끔합니다
초기화 응답 생성 경로에서 resolver를 일관되게 주입하는 변경은 좋아 보입니다. 다만toPublicUrl(null)같은 입력 계약이 명확하지 않으면 여기서도 초기화 API가 터질 수 있어 resolver 정책(또는 호출부 방어)을 확정해 주세요.src/main/java/redot/redot_server/domain/auth/service/CMSAuthService.java (1)
23-82: 응답 생성 위임은 좋고,toPublicUrl입력 계약(null 포함)만 재확인하면 됩니다
CMSMemberResponse.fromEntity(..., imageUrlResolver)로 일원화한 건 좋습니다.cmsMember.getProfileImageUrl()이 null일 수 있는지만(그리고 그때 resolver가 안전한지) 확인해 주세요.src/main/java/redot/redot_server/domain/cms/site/setting/controller/docs/SiteSettingControllerDocs.java (1)
10-35: [Your rewritten review comment text here]
[Exactly ONE classification tag]src/main/java/redot/redot_server/domain/admin/controller/docs/AdminControllerDocs.java (1)
10-69: Docs 인터페이스의 검증 애노테이션과 실제 구현의 일치성 확인 필요
AdminControllerDocs에 적용된@Valid와@NotNull애노테이션이 실제AdminController구현체에도 동일하게 적용되어 있고, 클래스 또는 전역 수준의@Validated설정을 통해 런타임에 강제되는지 확인하세요. 특히uploadProfileImage메서드의MultipartFile image파라미터에 대한@NotNull검증이 실제로 유효하지 않은 요청에 400 에러를 반환하는지 확인해 주세요.src/main/java/redot/redot_server/domain/cms/site/setting/dto/response/SiteSettingResponse.java (1)
14-22: 원본 리뷰 코멘트 불필요 -ImageUrlResolver.toPublicUrl()은 이미 null/blank 입력을 안전하게 처리함
ImageUrlResolver.toPublicUrl()메서드는 31-33줄에서!StringUtils.hasText(pathOrUrl)조건으로 null/blank 입력을 명시적으로 처리하고 null을 반환합니다. 따라서siteSetting.getLogoUrl()이 null이거나 빈 값이어도 예외가 발생하지 않으며, 현재 구현이 이미 안전합니다. 제안된 방어 코드는 불필요합니다.src/main/java/redot/redot_server/domain/cms/member/dto/response/CMSMemberResponse.java (1)
17-27: 이미 null 안전성이 보장됨
ImageUrlResolver.toPublicUrl()은 이미 null을 안전하게 처리합니다. 메서드 내에서StringUtils.hasText(pathOrUrl)을 통해 null이나 공백 문자열을 체크한 후 null을 반환하므로,cmsMember.getProfileImageUrl()이 null이어도 예외가 발생하지 않습니다. 추가 방어 코드는 불필요합니다.src/main/java/redot/redot_server/domain/redot/member/dto/response/RedotMemberResponse.java (1)
14-26: 조건부 null 검사는 불필요합니다.ImageUrlResolver.toPublicUrl()는 명시적인 null-안전 계약을 가지고 있으며, null 입력에 대해 null을 반환합니다(예외 발생 없음). 동일한 패턴이CMSMemberResponse,SiteSettingResponse,AdminResponse등 코드베이스 전반에서 일관되게 사용되고 있습니다.Likely an incorrect or invalid review comment.
src/main/java/redot/redot_server/domain/cms/site/setting/service/SiteSettingService.java (3)
24-35: ImageUrlResolver 의존성 주입이 올바르게 적용되었습니다.@requiredargsconstructor를 통한 생성자 주입 패턴이 정상적으로 적용되었습니다.
60-66: 삭제 로직이 올바르게 개선되었습니다.변환된 URL을 직접 비교하도록 메서드 시그니처가 개선되었으며, null 체크와 이벤트 기반 삭제가 적절하게 구현되었습니다.
48-58: null 처리는 의도된 설계입니다.
imageUrlResolver.toStoredPath()가 null을 반환할 수 있으며,siteSetting.updateLogoUrl()이 이를 받아 저장하는 것은 정상입니다. SiteSetting의 logoUrl 필드는 선택적 필드이고, null 값은 로고가 없는 상태를 나타내는 올바른 설계입니다.deleteOldLogoUrlIfChanged()메서드에서도 null을 안전하게 처리하고 있으므로 추가 조치가 필요하지 않습니다.src/main/java/redot/redot_server/domain/redot/app/service/RedotAppService.java (2)
41-58: ImageUrlResolver 의존성이 올바르게 추가되었습니다.표준 의존성 주입 패턴이 적용되었습니다.
78-80: 응답 생성 시 ImageUrlResolver 전달이 일관성 있게 적용되었습니다.
getRedotAppInfo와getRedotAppList모두에서 resolver를 응답 빌더에 전달하는 패턴이 일관되게 적용되었습니다.src/main/java/redot/redot_server/domain/cms/member/controller/docs/CMSMemberControllerDocs.java (2)
11-12: Jakarta Bean Validation 의존성이 올바르게 추가되었습니다.표준 검증 프레임워크를 사용하는 것은 적절한 선택입니다.
35-96: 입력 검증 애노테이션이 적절하게 추가되었습니다.요청 DTO에
@Valid, 파일 파라미터에@NotNull을 추가하여 입력 검증을 강화했습니다. DTO 클래스들이 다음과 같이 적절한 검증 제약조건을 정의하고 있습니다:
CMSMemberCreateRequest:@NotBlank(name, password),@Email @NotNull(email),@NotNull(role)CMSMemberRoleRequest:@NotNull(role)CMSMemberUpdateRequest:@NotBlank(name)모든 검증 애노테이션이 올바르게 적용되어 있습니다.
src/main/java/redot/redot_server/global/s3/util/ImageUrlResolver.java (4)
16-25: CDN 베이스 URL 초기화 로직이 안전하게 구현되었습니다.trailing slash 제거와 trim 처리가 적절하며, hasCdnBaseUrl 플래그를 통한 최적화도 잘 적용되었습니다.
30-44: toPublicUrl 메서드가 올바르게 구현되었습니다.외부 URL과 내부 경로를 적절히 구분하고, CDN 베이스 URL을 안전하게 추가합니다. normalizePath 후 재검증 로직(lines 40-42)이 방어적 코딩을 강화합니다.
49-58: toStoredPath 메서드가 올바르게 구현되었습니다.CDN URL을 내부 저장 경로로 변환하는 로직이 멱등성(idempotent)을 보장하며, 외부 URL은 그대로 보존합니다.
63-69: toS3Key 메서드가 올바르게 구현되었습니다.S3 키 형식에 맞게 선행 슬래시를 제거하고, 외부 URL을 적절히 필터링합니다.
src/main/java/redot/redot_server/domain/admin/service/AdminService.java (3)
24-36: ImageUrlResolver 의존성이 올바르게 추가되었습니다.표준 의존성 주입 패턴이 적용되었습니다.
84-100: 관리자 업데이트 로직이 올바르게 개선되었습니다.URL 변환 후 삭제 체크와 업데이트를 수행하는 일관된 패턴이 적용되었으며, 메서드 시그니처도 명확하게 개선되었습니다.
47-59: 프로필 이미지 URL 변환 로직이 올바르게 적용되었습니다.
imageUrlResolver.toStoredPath()의 반환값이Admin.create()에 전달되며, Admin 엔티티의profileImageUrl필드는 nullable하게 설계되어 있어 null 값을 안전하게 처리합니다.create()메서드는 전달된 값을 builder 패턴으로 그대로 설정하기 때문에 null 처리에 문제가 없습니다.src/main/java/redot/redot_server/domain/cms/member/service/CMSMemberService.java (2)
28-42: ImageUrlResolver 의존성이 올바르게 추가되었습니다.표준 의존성 주입 패턴이 적용되었습니다.
53-107: ImageUrlResolver 통합이 일관성 있게 적용되었습니다.모든 응답 생성 메서드와 업데이트 플로우에서 resolver를 올바르게 사용하고 있으며, AdminService와 동일한 패턴을 따르고 있습니다.
| ResponseEntity<TokenResponse> impersonateAsCMSAdmin(@Parameter(hidden = true) HttpServletRequest request, | ||
| CMSAdminImpersonationRequest cmsAdminImpersonationRequest, | ||
| @Valid CMSAdminImpersonationRequest cmsAdminImpersonationRequest, | ||
| @Parameter(hidden = true) JwtPrincipal jwtPrincipal); |
There was a problem hiding this comment.
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
# Step 1: Find the implementing controller for AdminImpersonationControllerDocs
echo "=== Finding implementing controller ==="
fd -e java | xargs rg -l "implements AdminImpersonationControllerDocs" | head -5
# Step 2: Check CMSAdminImpersonationRequest for validation constraints
echo -e "\n=== Checking CMSAdminImpersonationRequest DTO ==="
fd -e java | xargs rg -l "CMSAdminImpersonationRequest" | head -5Repository: redotlabs/server
Length of output: 604
🏁 Script executed:
#!/bin/bash
# Step 1: Examine the implementing controller's impersonateAsCMSAdmin method
echo "=== AdminImpersonationController - impersonateAsCMSAdmin method ==="
rg -B 2 -A 10 "impersonateAsCMSAdmin" src/main/java/redot/redot_server/domain/auth/controller/AdminImpersonationController.java
# Step 2: Examine CMSAdminImpersonationRequest DTO structure and validation constraints
echo -e "\n=== CMSAdminImpersonationRequest DTO ==="
cat src/main/java/redot/redot_server/domain/auth/dto/request/CMSAdminImpersonationRequest.javaRepository: redotlabs/server
Length of output: 1104
🏁 Script executed:
#!/bin/bash
# Verify the exact state of AdminImpersonationControllerDocs.java around lines 22-24
echo "=== AdminImpersonationControllerDocs.java (lines 20-26) ==="
sed -n '20,26p' src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.javaRepository: redotlabs/server
Length of output: 613
문서화 인터페이스에 @RequestBody 애노테이션도 추가 필요
@Valid 애노테이션 추가는 적절하나, 실제 구현 컨트롤러와 일치시키기 위해 @RequestBody 애노테이션도 함께 추가되어야 합니다.
현재 문서화 인터페이스:
@Valid CMSAdminImpersonationRequest cmsAdminImpersonationRequest실제 구현 컨트롤러:
@Valid @RequestBody CMSAdminImpersonationRequest cmsAdminImpersonationRequest문서화 인터페이스에 @RequestBody를 추가하여 실제 구현과 일치시켜야 합니다. CMSAdminImpersonationRequest DTO는 @NotNull 검증 제약조건이 적절하게 정의되어 있습니다.
🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.java
around lines 22 to 24, the documented method parameter uses only @Valid for the
CMSAdminImpersonationRequest but the real controller declares @Valid
@RequestBody; update the docs interface to add the @RequestBody annotation to
the cmsAdminImpersonationRequest parameter so it matches the implementation
(retain @Valid) and ensures the generated API docs reflect request body binding
and validation.
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.validation.Valid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/redot/redot_server/domain/auth/controller/docs/RedotMemberAuthControllerDocs.javaRepository: redotlabs/server
Length of output: 4189
🏁 Script executed:
# Find the actual controller implementation
find . -path ./target -prune -o -name "*MemberAuthController.java" -type f -print | grep -v docs | head -5Repository: redotlabs/server
Length of output: 148
🏁 Script executed:
cat -n ./src/main/java/redot/redot_server/domain/auth/controller/RedotMemberAuthController.javaRepository: redotlabs/server
Length of output: 9253
🏁 Script executed:
# Check DTOs for constraint annotations
echo "=== RedotMemberCreateRequest ==="
rg -n "class RedotMemberCreateRequest" -A 30
echo -e "\n=== RedotMemberSignInRequest ==="
rg -n "class RedotMemberSignInRequest" -A 20
echo -e "\n=== PasswordResetConfirmRequest ==="
rg -n "class PasswordResetConfirmRequest" -A 20Repository: redotlabs/server
Length of output: 163
🏁 Script executed:
# Find DTO files
echo "=== Finding DTOs ==="
fd "RedotMemberCreateRequest.java" -o "RedotMemberSignInRequest.java" -o "PasswordResetConfirmRequest.java"Repository: redotlabs/server
Length of output: 275
🏁 Script executed:
# Find DTO files
echo "=== Finding RedotMemberCreateRequest ==="
fd "RedotMemberCreateRequest\.java"
echo -e "\n=== Finding RedotMemberSignInRequest ==="
fd "RedotMemberSignInRequest\.java"
echo -e "\n=== Finding PasswordResetConfirmRequest ==="
fd "PasswordResetConfirmRequest\.java"Repository: redotlabs/server
Length of output: 459
🏁 Script executed:
echo "=== RedotMemberCreateRequest ==="
cat -n src/main/java/redot/redot_server/domain/redot/member/dto/request/RedotMemberCreateRequest.java
echo -e "\n=== RedotMemberSignInRequest ==="
cat -n src/main/java/redot/redot_server/domain/auth/dto/request/RedotMemberSignInRequest.java
echo -e "\n=== PasswordResetConfirmRequest ==="
cat -n src/main/java/redot/redot_server/domain/auth/dto/request/PasswordResetConfirmRequest.javaRepository: redotlabs/server
Length of output: 1847
🏁 Script executed:
# Verify if @Validated is used anywhere in the controller or if it's a standard pattern
echo "=== Checking for @Validated in similar controller classes ==="
fd "Controller.java" -x grep -l "@Validated" | head -5Repository: redotlabs/server
Length of output: 218
🏁 Script executed:
# Check how @Validated is used in that controller
grep -n "@Validated\|@RestController\|class.*Controller" ./src/main/java/redot/redot_server/domain/cms/site/setting/controller/SiteSettingController.java | head -10Repository: redotlabs/server
Length of output: 169
컨트롤러 클래스에 @validated 애노테이션이 누락되었습니다.
@Valid 애노테이션을 추가한 것은 좋은 개선이지만, 이를 실제로 동작하게 하려면 RedotMemberAuthController 클래스에 @Validated 애노테이션이 필요합니다. 현재 상태에서는 Spring이 메소드 레벨의 검증을 수행하지 않습니다.
필요한 변경:
RedotMemberAuthController클래스에@Validated애노테이션 추가 (라인 37-39 부근)- 예:
@RestController다음 줄에@Validated추가
확인된 사항:
- 세 개의 요청 DTO (
RedotMemberCreateRequest,RedotMemberSignInRequest,PasswordResetConfirmRequest)는 모두@Email,@NotBlank등의 제약 애노테이션이 올바르게 정의되어 있습니다. ✓
추가 제안 (선택사항):
getSocialLoginUrl메소드의provider,redirectUri,failureUri파라미터에@NotBlank제약을 추가하면 더욱 견고한 검증이 가능합니다.
🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/auth/controller/docs/RedotMemberAuthControllerDocs.java
around lines 37 to 39, the controller class is missing the @Validated annotation
so method-level @Valid checks are not triggered; add @Validated directly below
the @RestController declaration in the RedotMemberAuthController class to enable
Spring validation on request DTOs, and optionally annotate the getSocialLoginUrl
method parameters (provider, redirectUri, failureUri) with @NotBlank to
strengthen validation.
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# 1) Find and examine AppVersionCreateRequest
fd -t f "AppVersionCreateRequest.java" src/main/javaRepository: redotlabs/server
Length of output: 154
🏁 Script executed:
# 2) Search for AppVersionCreateRequest usage and validation constraints
rg -n "AppVersionCreateRequest" src/main/java --type java -A 5Repository: redotlabs/server
Length of output: 7681
🏁 Script executed:
# 3) Find the actual controller implementation for createVersion
rg -n "createVersion" src/main/java --type java -B 2 -A 5Repository: redotlabs/server
Length of output: 4206
🏁 Script executed:
# 4) Check for @RequestBody, @ModelAttribute, @Valid patterns in the cms.site.page.controller area
rg -n "@RequestBody|@ModelAttribute|@Valid" src/main/java/redot/redot_server/domain/cms/site/page --type javaRepository: redotlabs/server
Length of output: 777
Docs 인터페이스에 @RequestBody 추가 필요 - 실제 컨트롤러 구현과 일치시키기
실제 컨트롤러 구현(CMSSitePageController.java:51)은 @Valid @RequestBody AppVersionCreateRequest request로 올바르게 구성되어 있으나, Docs 인터페이스(CMSSitePageControllerDocs.java:48)는 현재 @Valid AppVersionCreateRequest request만 있습니다. 구현과 문서 시그니처를 일치시키기 위해 @RequestBody 애노테이션을 추가하세요. 참고로 AppVersionCreateRequest DTO는 이미 @NotNull, @Valid 등의 제약 조건이 적절히 설정되어 있습니다.
🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/cms/site/page/controller/docs/CMSSitePageControllerDocs.java
around lines 10 and 48, the Docs interface method parameter is missing
@RequestBody and thus does not match the actual controller signature; update the
method parameter to add @RequestBody before the @Valid AppVersionCreateRequest
request and add the corresponding import for
org.springframework.web.bind.annotation.RequestBody at the top of the file so
the Docs interface and controller signatures are identical.
| content = @Content(schema = @Schema(implementation = StyleInfoResponse.class))) | ||
| ResponseEntity<StyleInfoResponse> updateStyleInfo(@Parameter(hidden = true) Long redotAppId, | ||
| StyleInfoUpdateRequest request); | ||
| @Valid StyleInfoUpdateRequest request); |
There was a problem hiding this comment.
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
# Find StyleInfoController implementation (not the Docs interface)
echo "=== Finding StyleInfoController implementation ==="
find . -type f -name "*StyleInfoController.java" ! -name "*Docs.java" | head -20
# Find StyleInfoUpdateRequest
echo ""
echo "=== Finding StyleInfoUpdateRequest ==="
find . -type f -name "*StyleInfoUpdateRequest.java"Repository: redotlabs/server
Length of output: 340
🏁 Script executed:
#!/bin/bash
# Search for updateStyleInfo method in actual controller
echo "=== Checking for updateStyleInfo method in controller implementation ==="
rg -n -B 5 -A 5 'updateStyleInfo.*StyleInfoUpdateRequest' --type java -g '!*Docs.java' | head -50
# Search for @Valid annotations on request parameters
echo ""
echo "=== Looking for @Valid annotations on StyleInfoUpdateRequest ==="
rg -n '@Valid.*StyleInfoUpdateRequest' --type java | head -20Repository: redotlabs/server
Length of output: 3433
🏁 Script executed:
cat src/main/java/redot/redot_server/domain/cms/site/style/dto/request/StyleInfoUpdateRequest.javaRepository: redotlabs/server
Length of output: 290
StyleInfoUpdateRequest DTO에 검증 제약 조건이 정의되지 않았습니다.
구현 컨트롤러에는 @Valid 애노테이션이 올바르게 적용되어 있으나, StyleInfoUpdateRequest 레코드에는 검증 제약 조건(@NotNull, @NotBlank 등)이 전혀 없습니다. 따라서 현재 @Valid 애노테이션은 실질적인 검증 효과를 가지지 못합니다. DTO의 필드들에 적절한 검증 애노테이션을 추가하세요. 예를 들어:
public record StyleInfoUpdateRequest(
@NotBlank String color,
@NotBlank String font,
@NotNull Theme theme
) {
}🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/cms/site/style/controller/docs/StyleInfoControllerDocs.java
around line 31, the controller uses @Valid on StyleInfoUpdateRequest but the DTO
has no validation annotations; add javax.validation annotations to the
StyleInfoUpdateRequest record fields (e.g., @NotBlank, @NotNull, @Size where
appropriate) to enforce non-null/format constraints, import the validation
annotations, and ensure any enums or nested objects are validated (use @Valid on
nested types) so @Valid on the controller method actually triggers validation.
| content = @Content(schema = @Schema(implementation = UploadedImageUrlResponse.class))) | ||
| ResponseEntity<UploadedImageUrlResponse> uploadProfileImage(@Parameter(hidden = true) JwtPrincipal jwtPrincipal, | ||
| MultipartFile image); | ||
| @NotNull MultipartFile image); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "RedotMemberControllerDocs.java" -type fRepository: redotlabs/server
Length of output: 161
🏁 Script executed:
find . -name "RedotMemberController.java" -type fRepository: redotlabs/server
Length of output: 152
🏁 Script executed:
cat -n ./src/main/java/redot/redot_server/domain/redot/member/controller/docs/RedotMemberControllerDocs.javaRepository: redotlabs/server
Length of output: 2359
🏁 Script executed:
cat -n ./src/main/java/redot/redot_server/domain/redot/member/controller/RedotMemberController.javaRepository: redotlabs/server
Length of output: 3054
🏁 Script executed:
rg -n "@Validated" --type java | head -20Repository: redotlabs/server
Length of output: 261
🏁 Script executed:
find . -name "*Config.java" -o -name "*Configuration.java" | xargs grep -l "Validation" 2>/dev/null | head -10Repository: redotlabs/server
Length of output: 42
RedotMemberController에 @validated 애노테이션 추가 필요
RedotMemberController 클래스에 @validated 애노테이션이 누락되어 있습니다. @NotNull 애노테이션이 uploadProfileImage 메서드의 image 파라미터에 올바르게 추가되었으나, @validated 없이는 런타임에 Bean Validation이 실제로 작동하지 않습니다.
같은 프로젝트의 SiteSettingController에서는 @validated를 사용하고 있으므로, 일관성을 위해 RedotMemberController 클래스 정의 위에 @validated 애노테이션을 추가하세요:
@RestController
@Validated
@RequiredArgsConstructor
@RequestMapping("/api/v1/redot/members")
public class RedotMemberController implements RedotMemberControllerDocs {🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/redot/member/controller/docs/RedotMemberControllerDocs.java
around line 30, the controller is missing the @Validated annotation so @NotNull
on the uploadProfileImage parameter won't trigger Bean Validation at runtime;
add @Validated to the RedotMemberController class definition (above the class
declaration, alongside @RestController and @RequiredArgsConstructor) to enable
method-level validation and keep it consistent with SiteSettingController.
|
|
||
| /** | ||
| * 지정된 경로의 파일 삭제 | ||
| * @param filePath 삭제할 파일 경로 (ex: /app/1/logo/image.png) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JavaDoc 파라미터 문서가 실제 코드와 불일치합니다.
JavaDoc에 @param filePath로 표기되어 있지만 실제 파라미터 이름은 key입니다. 문서를 업데이트해야 합니다.
다음 diff를 적용하여 문서를 수정하세요:
/**
* 지정된 경로의 파일 삭제
- * @param filePath 삭제할 파일 경로 (ex: /app/1/logo/image.png)
+ * @param key 삭제할 S3 key (ex: app/1/logo/image.png)
*/📝 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.
| * @param filePath 삭제할 파일 경로 (ex: /app/1/logo/image.png) | |
| * @param key 삭제할 S3 key (ex: app/1/logo/image.png) |
🤖 Prompt for AI Agents
In src/main/java/redot/redot_server/global/s3/util/S3Manager.java around line
57, the JavaDoc param name is incorrect: it documents @param filePath but the
actual method parameter is named key; update the JavaDoc to use @param key and
adjust the description (e.g., "@param key 삭제할 파일 경로 (ex:
/app/1/logo/image.png)") so the doc matches the code.
Overview
Related Issue
PR Checklist
Additional Information