Conversation
- ImageUploadRequest에 contentType 파라미터 추가 (기본값 image/jpeg) - PreSignUrlProvider에 MIME -> 확장자 허용 목록 적용 (jpg, png, webp, mp4) - GeneratePresignedUrlRequest로 Content-Type 강제 적용 - 허용되지 않은 Content-Type 요청 시 UNSUPPORTED_CONTENT_TYPE 예외 처리 - contentType별 확장자 검증 단위 테스트 추가 - product-api, admin-api RestDocs에 contentType 파라미터 문서화 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
PreSigned URL 발급 시 contentType을 받아 S3 오브젝트 확장자 및 Presigned URL의 Content-Type을 동적으로 처리하고, 허용되지 않은 Content-Type은 400으로 차단하도록 변경합니다. 또한 product-api/admin-api RestDocs 및 관련 테스트를 업데이트합니다.
Changes:
contentType기반 확장자 매핑(허용 목록) 및 미지원 Content-Type 예외(400) 추가- Presigned URL 생성 시
GeneratePresignedUrlRequest.withContentType(contentType)적용 - product/admin RestDocs 및 단위 테스트에서
contentType파라미터 반영
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| plan/presigned-url-content-type.md | Content-Type 기반 Presigned URL 설계/변경점 계획 문서 추가 |
| plan/banner-media-type.md | 배너의 미디어 타입 분리 작업 계획 문서 추가(관련 문서 링크) |
| git.environment-variables | 서브모듈 커밋 포인터 업데이트 |
| bottlenote-product-api/src/test/java/app/docs/upload/RestImageUploadControllerTest.java | product-api RestDocs에 contentType 쿼리 파라미터 문서화 |
| bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/fixture/FakeAmazonS3.java | 테스트용 presigned URL 생성 로직을 S3 형태로 변경 |
| bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/ImageUploadServiceTest.java | contentType별 확장자 및 예외 케이스 단위 테스트 추가/갱신 |
| bottlenote-mono/src/test/java/app/bottlenote/common/file/ImageUploadUnitTest.java | mono 단위 테스트에서 신규 request 시그니처 반영 |
| bottlenote-mono/src/main/java/app/bottlenote/common/file/service/ImageUploadService.java | presigned URL 생성에 contentType 전달 및 request 빌더 사용 |
| bottlenote-mono/src/main/java/app/bottlenote/common/file/exception/FileExceptionCode.java | 미지원 Content-Type 예외 코드 추가 |
| bottlenote-mono/src/main/java/app/bottlenote/common/file/dto/request/ImageUploadRequest.java | contentType 필드 추가 및 기본값(image/jpeg) 적용 |
| bottlenote-mono/src/main/java/app/bottlenote/common/file/PreSignUrlProvider.java | 허용 Content-Type→확장자 매핑 및 키 생성 시 검증/적용 |
| bottlenote-admin-api/src/test/kotlin/app/docs/file/AdminImageUploadControllerDocsTest.kt | admin-api RestDocs에 contentType 쿼리 파라미터 문서화 |
| String bucketName = generatePresignedUrlRequest.getBucketName(); | ||
| String key = generatePresignedUrlRequest.getKey(); | ||
| url = new URL("https", bucketName + ".s3.amazonaws.com", "/" + key); | ||
| System.out.println("Fake url 생성 : " + url); |
There was a problem hiding this comment.
System.out.println은 테스트 출력 노이즈를 만들고 CI 로그를 불필요하게 오염시킬 수 있습니다. 필요하다면 테스트 로거를 사용해 debug 레벨로 남기거나, 단순히 출력 라인을 제거하는 방식으로 정리하는 것이 좋습니다.
| * @return 생성된 오브젝트 키 | ||
| */ | ||
| default String getImageKey(String rootPath, Long index) { | ||
| default String getImageKey(String rootPath, Long index, String contentType) { |
There was a problem hiding this comment.
MIME media type은 규격상 대소문자/공백에 대해 관대하게 처리되는 경우가 많습니다(예: "Image/JPEG", "image/jpeg; charset=utf-8", 앞뒤 공백). 현재는 Map key와 완전 일치만 허용해서 유효한 입력도 400으로 거절될 수 있으니, lookup 전에 trim() + 소문자 정규화 및 필요 시 파라미터(예: ; charset=) 제거 또는 Spring MediaType 파싱 기반 비교로 허용 여부를 판단하도록 개선하는 것을 권장합니다.
| String extension = ALLOWED_CONTENT_TYPES.get(contentType); | ||
| if (extension == null) { | ||
| throw new FileException(UNSUPPORTED_CONTENT_TYPE); | ||
| } |
There was a problem hiding this comment.
MIME media type은 규격상 대소문자/공백에 대해 관대하게 처리되는 경우가 많습니다(예: "Image/JPEG", "image/jpeg; charset=utf-8", 앞뒤 공백). 현재는 Map key와 완전 일치만 허용해서 유효한 입력도 400으로 거절될 수 있으니, lookup 전에 trim() + 소문자 정규화 및 필요 시 파라미터(예: ; charset=) 제거 또는 Spring MediaType 파싱 기반 비교로 허용 여부를 판단하도록 개선하는 것을 권장합니다.
| parameterWithName("uploadSize").description("업로드할 이미지의 사이즈 ( 이미지당 1개 )"), | ||
| parameterWithName("contentType") | ||
| .description( | ||
| "업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4)") |
There was a problem hiding this comment.
Presigned PUT URL에 withContentType(contentType)을 포함하면, 실제 업로드 요청에서도 동일한 Content-Type 헤더를 반드시 설정해야 서명 검증이 통과합니다. 현재 설명에는 이 제약이 드러나지 않으므로, 문서(description)에 “업로드(PUT) 시 동일한 Content-Type 헤더를 포함해야 함”을 명시하는 것이 필요합니다.
| "업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4)") | |
| "업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4). " | |
| + "Presigned PUT URL 발급 시 지정한 값과 동일한 Content-Type 헤더를 업로드(PUT) 요청에도 반드시 포함해야 합니다.") |
| parameterWithName("rootPath").description("업로드 경로 (예: admin/banner, admin/alcohol)"), | ||
| parameterWithName("uploadSize").description("발급할 URL 개수") | ||
| parameterWithName("uploadSize").description("발급할 URL 개수"), | ||
| parameterWithName("contentType").description("업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4)").optional() |
There was a problem hiding this comment.
product-api 문서와 동일하게, presigned URL로 PUT 업로드 시 Content-Type 헤더가 서명에 포함되는 제약을 사용자가 이해할 수 있도록 description에 “업로드 요청 시 동일한 Content-Type 헤더 필요”를 명시해 주세요.
| parameterWithName("contentType").description("업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4)").optional() | |
| parameterWithName("contentType").description("업로드 파일의 Content-Type (image/jpeg, image/png, image/webp, video/mp4), 업로드 요청 시 동일한 Content-Type 헤더 필요").optional() |
| EXPIRY_TIME_RANGE_INVALID(HttpStatus.BAD_REQUEST, "만료 기간의 범위가 적절하지 않습니다.( 최소 1분 ,최대 10분) "), | ||
| UNSUPPORTED_CONTENT_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 Content-Type입니다."); |
There was a problem hiding this comment.
EXPIRY_TIME_RANGE_INVALID 메시지에 불필요한 공백(예: "( 최소 1분 ,최대 10분) ")이 포함되어 가독성이 떨어집니다. 또한 UNSUPPORTED_CONTENT_TYPE는 어떤 값이 거절되었는지/무엇이 허용되는지 정보가 없어 디버깅이 어려우니, 가능하다면 거절된 contentType과 허용 목록을 메시지/에러 상세(예: exception detail)로 포함시키는 개선을 권장합니다.
| EXPIRY_TIME_RANGE_INVALID(HttpStatus.BAD_REQUEST, "만료 기간의 범위가 적절하지 않습니다.( 최소 1분 ,최대 10분) "), | |
| UNSUPPORTED_CONTENT_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 Content-Type입니다."); | |
| EXPIRY_TIME_RANGE_INVALID(HttpStatus.BAD_REQUEST, "만료 기간의 범위가 적절하지 않습니다.(최소 1분, 최대 10분)"), | |
| UNSUPPORTED_CONTENT_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 Content-Type입니다. 요청하신 Content-Type과 허용되는 목록을 확인해주세요."); |
- admin 통합 테스트 업로드 시 Content-Type을 image/jpeg로 변경 (서명 불일치 해소) - MIME 타입 lookup 전 trim/소문자 정규화 및 파라미터 제거 처리 추가 - RestDocs 설명에 PUT 요청 시 동일 Content-Type 헤더 필요 명시 - FakeAmazonS3에서 불필요한 System.out.println 제거 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
contentType파라미터를 입력받아 확장자와 Content-Type을 동적으로 처리image/jpeg,image/png,image/webp,video/mp4contentType파라미터 반영Related: bottle-note/workspace#205
Test plan
🤖 Generated with Claude Code