-
Notifications
You must be signed in to change notification settings - Fork 0
upload file #54
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
upload file #54
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
업로드 예외가 삼켜져 성공으로 처리됨
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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