Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Walkthrough사진 업로드 및 조회를 위한 파일 처리 기능을 도메인/인프라 전반에 추가. S3 연동(설정/어댑터), 업로드/URL 생성 포트 정의, 컨트롤러 및 유스케이스 구현, 이미지 확장자 검증/변환 유틸리티, 파일 관련 예외 계층 도입, DTO/엔티티/리포지토리 확장 및 의존성 추가. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant C as FileController
participant Conv as ImageFileConverter
participant UC as FileUploadUseCase
participant Sec as SecurityAdapter
participant Repo as PhotoJpaRepository
participant S3 as AwsS3Adapter
U->>C: POST /photo (Multipart image)
C->>Conv: transferTo(multipartFile)
alt 유효 확장자
Conv-->>C: File
C->>UC: execute(file)
UC->>Sec: currentUserId()
Sec-->>UC: userId
UC->>S3: upload(file, PathList.PHOTO)
S3-->>UC: s3Url
UC->>Repo: findByUserId(userId)
Repo-->>UC: PhotoJpaEntity?
UC->>Repo: save(new/updated PhotoJpaEntity)
UC-->>C: s3Url
C-->>U: 200 {"photo_url": s3Url}
else 잘못된 확장자
Conv-->>C: throws InvalidExtension(400)
C-->>U: 400 Error
end
sequenceDiagram
autonumber
actor U as User
participant AQ as ApplicationQueryUseCase
participant Sec as SecurityAdapter
participant Repo as PhotoJpaRepository
participant URL as GenerateFileUrlPort
U->>AQ: getApplicationById(...)
AQ->>Sec: currentUser()
Sec-->>AQ: user
AQ->>Repo: findByUserId(user.id)
Repo-->>AQ: PhotoJpaEntity?
alt 사진 있음
AQ->>URL: generateFileUrl(fileName=photo, path=PathList.PHOTO)
URL-->>AQ: presignedUrl
AQ-->>U: ApplicationDetail(photoPath=presignedUrl)
else 없음
AQ-->>U: ApplicationDetail(photoPath=null)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (11)
casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/spi/UploadFilePort.kt (1)
3-7: 도메인 포트에서 java.io.File 의존 — 스트리밍 기반 서명으로 전환 권장대용량 처리/메모리 효율/테스트 용이성을 위해 File 대신 InputStream(+메타데이터)로 받는 것이 바람직합니다. 도메인 계층의 로컬 파일시스템 의존도도 줄일 수 있습니다.
다음과 같이 서명 변경을 제안합니다:
-import java.io.File +import java.io.InputStream interface UploadFilePort { - fun upload(file: File, path: String): String + /** + * 파일 스트림과 메타데이터를 업로드하고 '객체 키'를 반환합니다. + */ + fun upload( + input: InputStream, + path: String, + filename: String, + contentType: String? = null, + ): String }casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/usecase/FileUploadUseCase.kt (3)
18-22: 외부 I/O(S3 업로드)를 트랜잭션 안에서 수행 — 트랜잭션 경계 분리 권장업로드는 네트워크 I/O이며 지연/재시도/실패 가능성이 큽니다. DB 트랜잭션을 불필요하게 오래 잡아두지 않도록 업로드를 트랜잭션 밖에서 수행하고, DB 갱신만 트랜잭션으로 묶는 구조가 안전합니다.
아래처럼 @transactional을 제거하고, DB 갱신 전용 메서드를 분리하는 방식을 권장합니다:
- @Transactional fun execute(file: File): String { val userId = securityAdapter.getCurrentUserId() - val photoUrl = uploadFilePort.upload(file, PathList.PHOTO) + val photoKey = uploadFilePort.upload(file, PathList.PHOTO)클래스 내부에 다음 메서드를 추가:
import org.springframework.transaction.annotation.Transactional import java.util.UUID @Transactional private fun upsertPhoto(userId: UUID, photoKey: String) { photoJpaRepository.findByUserId(userId)?.apply { photo = photoKey // 더티 체킹 } ?: photoJpaRepository.save( PhotoJpaEntity( userId = userId, photo = photoKey ) ) }그리고 execute 내에서 업로드 후 upsertPhoto 호출로 마무리:
upsertPhoto(userId, photoKey) return photoKey
21-31: 저장값/변수명 불일치 가능성(photoUrl) — ‘키’ 저장으로 정렬 및 불필요한 save 제거photoUrl이라는 이름은 URL 저장으로 오해될 수 있습니다. 엔티티 컬럼명이 photo_path라면 ‘키’를 저장하는 것이 자연스럽습니다. 또한 JPA 트랜잭션 내에서는 필드 변경만으로 더티 체킹이 적용되므로 기존 엔티티에 대해 save 호출은 불필요합니다.
아래처럼 변수명을 photoKey로, 더티 체킹을 활용하도록 제안합니다:
- val photoUrl = uploadFilePort.upload(file, PathList.PHOTO) + val photoKey = uploadFilePort.upload(file, PathList.PHOTO) - photoJpaRepository.findByUserId(userId)?.apply { - photo = photoUrl - photoJpaRepository.save(this) - } ?: photoJpaRepository.save( + photoJpaRepository.findByUserId(userId)?.apply { + photo = photoKey + } ?: photoJpaRepository.save( PhotoJpaEntity( userId = userId, - photo = photoUrl + photo = photoKey ) ) - return photoUrl + return photoKey추가 확인 요청:
- UploadFilePort.upload가 실제로 ‘URL’이 아닌 ‘키’를 반환하는지 확인해 주세요. URL을 반환한다면, 키 저장으로 전환해야 이후 GenerateFileUrlPort를 통한 URL 생성 흐름과 합치됩니다.
Also applies to: 33-34
23-31: 교체 업로드 시 기존 S3 객체 정리 누락 — 스토리지 누수 방지사용자 사진 교체 시 기존 객체 삭제가 없으면 스토리지 누수가 발생합니다. DeleteFilePort(또는 어댑터의 삭제 기능)를 추가해, 키가 변경될 때 이전 키를 제거하는 처리를 권장합니다.
원칙:
- 새 키 업로드 성공 → DB 갱신 성공 시 이전 키 삭제(또는 반대로: DB 실패 시 새 키 롤백 삭제) 처리를 포함한 보상 로직 고려.
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsCredentialsProperties.kt (1)
5-9: 정적 액세스 키 사용 지양 + 값 검증 추가운영환경에서는 IAM Role/Default Credentials Provider 사용을 우선하세요. 불가피하게 정적 자격증명을 쓰는 경우 유효성 검증을 추가하고, 로깅/노출에 각별히 주의해야 합니다.
아래처럼 @validated와 제약을 추가하세요:
import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.validation.annotation.Validated +import jakarta.validation.constraints.NotBlank -@ConfigurationProperties("cloud.aws.credentials") -class AwsCredentialsProperties( - val accessKey: String, - val secretKey: String, -) +@ConfigurationProperties("cloud.aws.credentials") +@Validated +class AwsCredentialsProperties( + @field:NotBlank val accessKey: String, + @field:NotBlank val secretKey: String, +)추가 조언:
- SDK 기본 자격증명 체인(IAM Role, 환경변수, 프로파일 등) 우선 적용을 검토하고, 장기적으로는 AWS SDK v2로의 이전을 고려하세요. Based on learnings
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/domain/entity/PhotoJpaEntity.kt (1)
11-17: photo_path 컬럼에 length 지정 추가
URL/키 길이를 고려해 @column에 length=2048 지정 권장- @Column(name = "photo_path", nullable = false) + @Column(name = "photo_path", nullable = false, length = 2048)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/presentation/FileController.kt (1)
3-11: multipart 업로드 컨트롤 협의: consumes 지정 및 빈 파일 가드 추가 권장
- consumes 미지정 시 일부 클라이언트/프록시에서 컨텐츠 협상 문제가 날 수 있습니다.
- 업로드 파일이 비어있는 경우 즉시 400 처리하는 가드가 필요합니다.
아래처럼 개선을 제안합니다.
import hs.kr.entrydsm.application.domain.application.usecase.FileUploadUseCase import hs.kr.entrydsm.application.domain.file.presentation.converter.ImageFileConverter +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile @RequestMapping("/photo") @RestController class FileController( private val fileUploadUseCase: FileUploadUseCase ) { - @PostMapping + @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) fun uploadPhoto(@RequestPart(name = "image") file: MultipartFile): ResponseEntity<Map<String, String>> { + require(!file.isEmpty) { "빈 파일은 업로드할 수 없습니다." } val photoUrl = fileUploadUseCase.execute( file.let(ImageFileConverter::transferTo) ) return ResponseEntity.ok(mapOf("photo_url" to photoUrl)) } }Also applies to: 17-23
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsProperties.kt (1)
5-9: 프로퍼티 유효성 추가 권장bucket/region은 비어있으면 안 됩니다. 프로퍼티 검증을 추가해 잘못된 설정을 조기에 차단하세요. 예: @field:NotBlank 사용(+ Boot 3이면 jakarta.validation).
예시:
- class에 @ConfigurationProperties 유지
- 필드에 @field:NotBlank 추가
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/config/AwsS3Config.kt (1)
5-7: 구현 클래스 캐스팅 제거: AmazonS3 인터페이스로 노출AmazonS3ClientBuilder.build()는 AmazonS3를 반환합니다. 구현 클래스( AmazonS3Client )로 캐스팅하면 교체/업데이트 시 런타임 위험이 있습니다. 빈/주입 모두 AmazonS3 인터페이스를 사용하세요.
-import com.amazonaws.services.s3.AmazonS3Client +import com.amazonaws.services.s3.AmazonS3 import com.amazonaws.services.s3.AmazonS3ClientBuilder ... @Bean - fun amazonS3Client(): AmazonS3Client { + fun amazonS3Client(): AmazonS3 { val credentials = BasicAWSCredentials(awsCredentialsProperties.accessKey, awsCredentialsProperties.secretKey) return AmazonS3ClientBuilder.standard() .withRegion(awsProperties.region) .withCredentials(AWSStaticCredentialsProvider(credentials)) - .build() as AmazonS3Client + .build() }Also applies to: 20-28
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/exception/WebFileExceptions.kt (1)
9-13: HTTP 상태 코드 재검토(확장자 오류는 415 고려)InvalidExtension를 400으로 매핑했는데, 미디어 타입/확장자 문제는 415 Unsupported Media Type도 옵션입니다. 에러 스펙에 맞춰 상수/코드를 재검토하세요.
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt (1)
59-69: 접근 제어 정책 불일치: PublicRead vs Presigned URL객체를 PublicRead로 업로드하면서 동시에 presigned URL을 생성하고 있습니다. 정책을 하나로 통일하세요.
- 사설 접근이 목표라면: 업로드 ACL 제거(기본 private), presign 사용.
- 공개 접근이 목표라면: presign 제거, getUrl 반환만 사용.
현 로직과 API 사용처에 맞춰 한 가지 전략으로 정리하시길 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to data retention organization setting
📒 Files selected for processing (20)
casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/object/PathList.kt(1 hunks)casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/spi/GenerateFileUrlPort.kt(1 hunks)casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/spi/UploadFilePort.kt(1 hunks)casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/WebException.kt(1 hunks)casper-application-infrastructure/build.gradle.kts(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/domain/entity/ApplicationJpaEntity.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/domain/entity/PhotoJpaEntity.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/domain/repository/PhotoJpaRepository.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/presentation/FileController.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/presentation/dto/response/ApplicationDetailResponse.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/usecase/ApplicationQueryUseCase.kt(3 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/usecase/FileUploadUseCase.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/FileConverter.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/FileExtensions.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/ImageFileConverter.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/exception/WebFileExceptions.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/config/AwsS3Config.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsCredentialsProperties.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsProperties.kt(1 hunks)casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt(1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt
[warning] 50-50: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (9)
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/domain/entity/ApplicationJpaEntity.kt (1)
55-55: 사진 경로 갱신을 위한 가변 필드 전환 확인 완료사진 경로가 업로드 이후 갱신되어야 하므로
photoPath를var로 바꾼 선택이 타당합니다. 추가 우려 사항은 없습니다.casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/WebException.kt (1)
3-6: 기반 예외 타입 정의 적절HTTP 상태 코드와 메시지를 묶어 전달하려는 의도가 명확하며, 구현상 문제 없습니다.
casper-application-infrastructure/build.gradle.kts (1)
138-139: AWS SDK 버전 고정 추가 확인S3 연동을 위해 필요한 의존성이 명확히 추가되었습니다. 다른 충돌 요소는 보이지 않습니다.
casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/object/PathList.kt (1)
3-4: 경로 상수 추출 적절사진 경로 prefix를 상수로 분리한 덕분에 재사용과 유지보수가 좋아졌습니다.
casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/spi/GenerateFileUrlPort.kt (1)
3-4: 포트 정의 명확파일 URL 생성을 도메인 포트로 분리한 설계가 깔끔합니다.
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/FileExtensions.kt (1)
3-8: 확장자 상수화 문제 없음이미지 확장자를 상수로 중앙화한 접근이 합리적이며, 다른 우려는 없습니다.
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/application/presentation/dto/response/ApplicationDetailResponse.kt (1)
25-25: 응답 DTO 확장 확인
photoPath필드 추가로 응답 스키마가 요구사항을 충족하게 되었으며 다른 영향은 없어 보입니다.casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/file/spi/UploadFilePort.kt (1)
5-7: 반환값을 S3 객체 키로 명확히 명시
인터페이스 반환값이 URL인지 객체 키인지 모호하므로, URL 생성은 GenerateFileUrlPort에서 담당하고 이 포트는 “객체 키” 반환으로 고정해 KDoc으로 설명을 추가하세요.interface UploadFilePort { - fun upload(file: File, path: String): String + /** + * 파일을 업로드하고 S3 스토리지의 객체 키(object key)를 반환합니다. + * (접근 가능한 URL이 아닙니다.) + */ + fun upload(file: File, path: String): String }casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt (1)
42-49: S3 오브젝트 키 구성 일관성 확인 필요
upload()는path를 그대로 S3 키로 사용,generateFileUrl()는${path}${fileName}형태로 키 구성- DB에 저장되는
photo필드가 파일명인지, 전체 키인지, URL인지 명확히 정의하고 키 생성 로직을 일관되게 정리upload()/generateFileUrl() 호출부와 Photo 엔티티의
photo필드 저장 형태를 코드베이스에서 직접 확인하세요.
...kotlin/hs/kr/entrydsm/application/domain/application/domain/repository/PhotoJpaRepository.kt
Outdated
Show resolved
Hide resolved
...main/kotlin/hs/kr/entrydsm/application/domain/application/usecase/ApplicationQueryUseCase.kt
Show resolved
Hide resolved
...e/src/main/kotlin/hs/kr/entrydsm/application/domain/application/usecase/FileUploadUseCase.kt
Show resolved
Hide resolved
...c/main/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/FileConverter.kt
Show resolved
Hide resolved
...n/kotlin/hs/kr/entrydsm/application/domain/file/presentation/converter/ImageFileConverter.kt
Show resolved
Hide resolved
| override fun upload(file: File, path: String): String { | ||
| runCatching { inputS3(file, path) } | ||
| .also { file.delete() } | ||
|
|
||
| return getS3Url(path) | ||
| } | ||
|
|
There was a problem hiding this comment.
업로드 예외가 삼켜져 성공으로 처리됨
runCatching 결과를 무시하고 항상 URL을 반환합니다. 업로드 실패 시 실패를 반환/전파해야 합니다. 또한 파일 삭제는 finally에서 보장하세요.
- override fun upload(file: File, path: String): String {
- runCatching { inputS3(file, path) }
- .also { file.delete() }
-
- return getS3Url(path)
- }
+ override fun upload(file: File, path: String): String {
+ return try {
+ inputS3(file, path)
+ getS3Url(path)
+ } catch (e: Exception) {
+ throw IllegalStateException("S3 업로드 실패: ${e.message}", e)
+ } finally {
+ file.delete()
+ }
+ }📝 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.
| override fun upload(file: File, path: String): String { | |
| runCatching { inputS3(file, path) } | |
| .also { file.delete() } | |
| return getS3Url(path) | |
| } | |
| override fun upload(file: File, path: String): String { | |
| return try { | |
| inputS3(file, path) | |
| getS3Url(path) | |
| } catch (e: Exception) { | |
| throw IllegalStateException("S3 업로드 실패: ${e.message}", e) | |
| } finally { | |
| file.delete() | |
| } | |
| } |
🤖 Prompt for AI Agents
In
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt
around lines 27-33, the current implementation swallows upload exceptions by
ignoring the runCatching result and always returning the S3 URL while deleting
the file unconditionally; change it to attempt the upload and propagate any
failure (e.g., use runCatching(...).getOrThrow() or a try/catch that rethrows
the caught exception) so callers receive the error instead of a false success,
and move the file.delete() into a finally block (or use Kotlin's use/try/finally
pattern) so the file is always cleaned up regardless of success or failure.
| private fun inputS3(file: File, path: String) { | ||
| try { | ||
| val inputStream = file.inputStream() | ||
| val objectMetadata = ObjectMetadata().apply { | ||
| contentLength = file.length() | ||
| contentType = Mimetypes.getInstance().getMimetype(file) | ||
| } | ||
|
|
There was a problem hiding this comment.
InputStream 누수 가능성과 예외 타입 구체화
- file.inputStream()을 use로 닫아주세요.
- IOException만 포착하면 AWS SDK 예외(AmazonServiceException/SdkClientException)를 놓칩니다. 보다 넓게 포착 후 의미 있는 도메인 예외로 감싸세요.
- private fun inputS3(file: File, path: String) {
- try {
- val inputStream = file.inputStream()
- val objectMetadata = ObjectMetadata().apply {
- contentLength = file.length()
- contentType = Mimetypes.getInstance().getMimetype(file)
- }
+ private fun inputS3(file: File, path: String) {
+ try {
+ file.inputStream().use { inputStream ->
+ val objectMetadata = ObjectMetadata().apply {
+ contentLength = file.length()
+ contentType = Mimetypes.getInstance().getMimetype(file)
+ }
- amazonS3Client.putObject(
- PutObjectRequest(
- awsProperties.bucket,
- path,
- inputStream,
- objectMetadata
- ).withCannedAcl(CannedAccessControlList.PublicRead)
- )
- } catch (e: IOException) {
- throw IllegalArgumentException("File Exception")
+ amazonS3Client.putObject(
+ PutObjectRequest(
+ awsProperties.bucket,
+ path,
+ inputStream,
+ objectMetadata
+ ).withCannedAcl(CannedAccessControlList.PublicRead)
+ )
+ }
+ } catch (e: Exception) {
+ throw IllegalStateException("S3 업로드 처리 중 예외 발생: ${e.message}", e)
}
}📝 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.
| private fun inputS3(file: File, path: String) { | |
| try { | |
| val inputStream = file.inputStream() | |
| val objectMetadata = ObjectMetadata().apply { | |
| contentLength = file.length() | |
| contentType = Mimetypes.getInstance().getMimetype(file) | |
| } | |
| private fun inputS3(file: File, path: String) { | |
| try { | |
| file.inputStream().use { inputStream -> | |
| val objectMetadata = ObjectMetadata().apply { | |
| contentLength = file.length() | |
| contentType = Mimetypes.getInstance().getMimetype(file) | |
| } | |
| amazonS3Client.putObject( | |
| PutObjectRequest( | |
| awsProperties.bucket, | |
| path, | |
| inputStream, | |
| objectMetadata | |
| ).withCannedAcl(CannedAccessControlList.PublicRead) | |
| ) | |
| } | |
| } catch (e: Exception) { | |
| throw IllegalStateException("S3 업로드 처리 중 예외 발생: ${e.message}", e) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/storage/AwsS3Adapter.kt
around lines 34 to 41, the File InputStream is opened without being closed and
only IOException is caught; change to use file.inputStream().use { inputStream
-> ... } so the stream is always closed, and broaden the catch to include AWS
SDK exceptions (AmazonServiceException, SdkClientException) or a general
Exception, then wrap the caught exception in a meaningful domain-specific
exception (or a clear runtime exception) with context about the S3 operation
before rethrowing.
Summary by CodeRabbit
New Features
Error Handling
Infrastructure
Chores