-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 파일 업로드 기능 구현 #87
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
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
6c61ddf
chore: `.pem` ignore 처리
yechan-kim ae47688
chore: oci object storage 관련 의존성 추가
yechan-kim 33dcfda
chore: oci object storage 관련 profile 추가
yechan-kim 7906330
feat: oci object storage 설정 추가
yechan-kim 6a95069
feat: Pre-Authenticated Url 응답 DTO 추가
yechan-kim 6b6e5d5
feat: Pre-Authenticated Url 생성 기능 구현
yechan-kim cee2430
feat: oci object storage 의 object 주소를 반환하는 기능 추가
yechan-kim b06b1db
feat: oci object storage Rest 컨트롤러 추가
yechan-kim b3aad05
feat: oci object storage API 보안 설정 추가
yechan-kim 2343a16
delete: 불필요한 의존성 삭제
yechan-kim d20c197
test: Pre-Authenticated Url 생성 기능에 대한 테스트 코드 작성
yechan-kim 12b8a5e
delete: 미사용 코드 삭제
yechan-kim 607cf83
fix: 오탈자 수정
yechan-kim dae92e8
feat: 파라미터 검증 로직 추가
yechan-kim f770890
refactor: 표현 단순화
yechan-kim 4bb8573
fix: 오탈자 수정
yechan-kim 57fdf9c
refactor: 상수를 top-level로 이동
yechan-kim 777feb7
feat: 지원서 상태 조회 API 및 DTO 구현
jwnnoh 697d922
feat: 지원서 상태 조회 기능 구현
jwnnoh 04047c1
test: 지원서 상태 조회 기능 테스트 구현
jwnnoh da6f028
fix: 비즈니스 로직에 따른 인터뷰 일정 및 장소 전달 방식 수정
jwnnoh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,3 +46,6 @@ src/test/generated/ | |
|
|
||
| ### env ### | ||
| .env | ||
|
|
||
| ### key ### | ||
| *.pem | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/main/kotlin/land/leets/domain/application/presentation/dto/ApplicationStatusResponse.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package land.leets.domain.application.presentation.dto | ||
|
|
||
| import land.leets.domain.application.domain.Application | ||
| import land.leets.domain.application.type.ApplicationStatus | ||
| import land.leets.domain.interview.domain.Interview | ||
| import land.leets.domain.interview.type.HasInterview | ||
| import java.time.LocalDateTime | ||
|
|
||
| data class ApplicationStatusResponse( | ||
| val id: Long, | ||
| val status: ApplicationStatus, | ||
| val hasInterview: HasInterview?, | ||
| val interviewDate: LocalDateTime?, | ||
| val interviewPlace: String?, | ||
| ) { | ||
| companion object { | ||
| fun of( | ||
| application: Application, | ||
| interview: Interview?, | ||
| ): ApplicationStatusResponse { | ||
| if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { | ||
| return ApplicationStatusResponse( | ||
| id = application.id!!, | ||
| status = application.applicationStatus, | ||
| hasInterview = null, | ||
| interviewDate = null, | ||
| interviewPlace = null, | ||
| ) | ||
| } | ||
| return ApplicationStatusResponse( | ||
| id = application.id!!, | ||
| status = application.applicationStatus, | ||
| hasInterview = interview!!.hasInterview, | ||
| interviewDate = interview.fixedInterviewDate, | ||
| interviewPlace = interview.place, | ||
| ) | ||
| } | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatus.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package land.leets.domain.application.usecase | ||
|
|
||
| import land.leets.domain.application.presentation.dto.ApplicationStatusResponse | ||
| import java.util.UUID | ||
|
|
||
| interface GetApplicationStatus { | ||
| fun execute(uid: UUID): ApplicationStatusResponse | ||
| } |
24 changes: 24 additions & 0 deletions
24
src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImpl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package land.leets.domain.application.usecase | ||
|
|
||
| import land.leets.domain.application.domain.repository.ApplicationRepository | ||
| import land.leets.domain.application.exception.ApplicationNotFoundException | ||
| import land.leets.domain.application.presentation.dto.ApplicationStatusResponse | ||
| import land.leets.domain.interview.domain.repository.InterviewRepository | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Transactional | ||
| import java.util.UUID | ||
|
|
||
| @Service | ||
| @Transactional(readOnly = true) | ||
| class GetApplicationStatusImpl( | ||
| private val applicationRepository: ApplicationRepository, | ||
| private val interviewRepository: InterviewRepository, | ||
| ) : GetApplicationStatus { | ||
| override fun execute(uid: UUID): ApplicationStatusResponse { | ||
| val application = applicationRepository.findByUser_Id(uid) | ||
| ?: throw ApplicationNotFoundException() | ||
| val interview = interviewRepository.findByApplication(application) | ||
|
|
||
| return ApplicationStatusResponse.of(application, interview) | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
src/main/kotlin/land/leets/domain/storage/presentation/StorageController.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package land.leets.domain.storage.presentation | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation | ||
| import io.swagger.v3.oas.annotations.Parameter | ||
| import io.swagger.v3.oas.annotations.media.Content | ||
| import io.swagger.v3.oas.annotations.media.Schema | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses | ||
| import jakarta.validation.constraints.NotBlank | ||
| import land.leets.domain.storage.presentation.dto.PreAuthenticatedUrlResponse | ||
| import land.leets.domain.storage.usecase.GetPreAuthenticatedUrl | ||
| import land.leets.global.error.ErrorResponse | ||
| 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.RequestParam | ||
| import org.springframework.web.bind.annotation.RestController | ||
|
|
||
| @RestController | ||
| @RequestMapping("/storages") | ||
| class StorageController( | ||
| private val getPreAuthenticatedUrl: GetPreAuthenticatedUrl | ||
| ) { | ||
|
|
||
| @Operation(summary = "Pre-Authenticated Url 발급", description = "OCI Object Storage에 파일 업로드를 위한 Pre-Authenticated Url을 발급합니다.") | ||
| @ApiResponses( | ||
| ApiResponse(responseCode = "200"), | ||
| ApiResponse(responseCode = "400", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), | ||
| ApiResponse(responseCode = "403", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), | ||
| ApiResponse(responseCode = "404", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), | ||
| ApiResponse(responseCode = "500", content = [Content(schema = Schema(implementation = ErrorResponse::class))]) | ||
| ) | ||
| @PostMapping("/pre-authenticated-url") | ||
| fun getPreAuthenticatedUrl( | ||
| @NotBlank | ||
| @Parameter(description = "버킷에 업로드할 파일 이름 및 경로", example = "profile/{username}.jpg") | ||
| @RequestParam fileName: String | ||
| ): ResponseEntity<PreAuthenticatedUrlResponse> { | ||
| return ResponseEntity.ok(getPreAuthenticatedUrl.execute(fileName)) | ||
| } | ||
| } | ||
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/land/leets/domain/storage/presentation/dto/PreAuthenticatedUrlResponse.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package land.leets.domain.storage.presentation.dto | ||
|
|
||
| data class PreAuthenticatedUrlResponse( | ||
| val url: String, | ||
| ) |
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/land/leets/domain/storage/usecase/GetObjectUrl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package land.leets.domain.storage.usecase | ||
|
|
||
| interface GetObjectUrl { | ||
| fun execute(fileName: String): String | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/kotlin/land/leets/domain/storage/usecase/GetObjectUrlImpl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package land.leets.domain.storage.usecase | ||
|
|
||
| import com.oracle.bmc.objectstorage.ObjectStorage | ||
| import org.springframework.beans.factory.annotation.Value | ||
|
|
||
|
|
||
yechan-kim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| class GetObjectUrlImpl( | ||
| private val objectStorage: ObjectStorage, | ||
| @Value("\${oci.bucket.name}") private val bucketName: String, | ||
| @Value("\${oci.bucket.namespace}") private val bucketNamespace: String, | ||
| ) : GetObjectUrl { | ||
|
|
||
| override fun execute(fileName: String): String = | ||
| "${objectStorage.endpoint}/n/$bucketNamespace/b/$bucketName/o/$fileName" | ||
yechan-kim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
7 changes: 7 additions & 0 deletions
7
src/main/kotlin/land/leets/domain/storage/usecase/GetPreAuthenticatedUrl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package land.leets.domain.storage.usecase | ||
|
|
||
| import land.leets.domain.storage.presentation.dto.PreAuthenticatedUrlResponse | ||
|
|
||
| interface GetPreAuthenticatedUrl { | ||
| fun execute(fileName: String): PreAuthenticatedUrlResponse | ||
| } |
71 changes: 71 additions & 0 deletions
71
src/main/kotlin/land/leets/domain/storage/usecase/GetPreAuthenticatedUrlImpl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package land.leets.domain.storage.usecase | ||
|
|
||
| import com.oracle.bmc.objectstorage.ObjectStorage | ||
| import com.oracle.bmc.objectstorage.model.CreatePreauthenticatedRequestDetails | ||
| import com.oracle.bmc.objectstorage.requests.CreatePreauthenticatedRequestRequest | ||
| import land.leets.domain.storage.presentation.dto.PreAuthenticatedUrlResponse | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.stereotype.Service | ||
| import java.time.Instant | ||
| import java.time.temporal.ChronoUnit | ||
| import java.util.* | ||
|
|
||
| private const val PRESIGNED_URL_EXPIRATION_MINUTES = 15L | ||
| private const val PRESIGNED_REQUEST_NAME_PREFIX = "PAR_Request_" | ||
|
|
||
| @Service | ||
| class GetPreAuthenticatedUrlImpl( | ||
| private val objectStorage: ObjectStorage, | ||
| @Value("\${oci.bucket.name}") private val bucketName: String, | ||
| @Value("\${oci.bucket.namespace}") private val bucketNamespace: String, | ||
| ) : GetPreAuthenticatedUrl { | ||
|
|
||
| override fun execute(fileName: String): PreAuthenticatedUrlResponse { | ||
| val uniqueFileName = generateUniqueFileName(fileName) | ||
| val expirationTime = calculateExpirationTime() | ||
|
|
||
| val details = buildPreAuthenticatedRequestDetails(uniqueFileName, expirationTime) | ||
| val request = buildPreAuthenticatedRequest(details) | ||
|
|
||
| val response = objectStorage.createPreauthenticatedRequest(request) | ||
| val fullUrl = buildFullUrl(response.preauthenticatedRequest.accessUri) | ||
|
|
||
| return PreAuthenticatedUrlResponse(fullUrl) | ||
| } | ||
|
|
||
| private fun generateUniqueFileName(fileName: String): String { | ||
| val uuid = UUID.randomUUID() | ||
|
|
||
| return if ('/' in fileName) { | ||
| "${fileName.substringBeforeLast('/')}/${uuid}_${fileName.substringAfterLast('/')}" | ||
| } else { | ||
| "${uuid}_$fileName" | ||
| } | ||
| } | ||
|
|
||
| private fun calculateExpirationTime(): Date = | ||
| Date.from(Instant.now().plus(PRESIGNED_URL_EXPIRATION_MINUTES, ChronoUnit.MINUTES)) | ||
|
|
||
| private fun buildPreAuthenticatedRequestDetails( | ||
| fileName: String, | ||
| expirationTime: Date | ||
| ): CreatePreauthenticatedRequestDetails = | ||
| CreatePreauthenticatedRequestDetails.builder().apply { | ||
| name("$PRESIGNED_REQUEST_NAME_PREFIX${UUID.randomUUID()}") | ||
| objectName(fileName) | ||
| accessType(CreatePreauthenticatedRequestDetails.AccessType.ObjectWrite) | ||
| timeExpires(expirationTime) | ||
| }.build() | ||
|
|
||
| private fun buildPreAuthenticatedRequest( | ||
| details: CreatePreauthenticatedRequestDetails | ||
| ): CreatePreauthenticatedRequestRequest = | ||
| CreatePreauthenticatedRequestRequest.builder().apply { | ||
| namespaceName(bucketNamespace) | ||
| bucketName(bucketName) | ||
| createPreauthenticatedRequestDetails(details) | ||
| }.build() | ||
|
|
||
| private fun buildFullUrl(accessUri: String): String = | ||
| "${objectStorage.endpoint}$accessUri" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package land.leets.global.config | ||
|
|
||
| import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider | ||
| import com.oracle.bmc.objectstorage.ObjectStorage | ||
| import com.oracle.bmc.objectstorage.ObjectStorageClient | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.context.annotation.Bean | ||
| import org.springframework.context.annotation.Configuration | ||
| import java.io.FileInputStream | ||
| import java.io.InputStream | ||
| import java.util.function.Supplier | ||
|
|
||
| @Configuration | ||
| class OciConfig( | ||
| @Value("\${oci.tenant-id}") | ||
| private val tenantId: String, | ||
| @Value("\${oci.user-id}") | ||
| private val userId: String, | ||
| @Value("\${oci.fingerprint}") | ||
| private val fingerprint: String, | ||
| @Value("\${oci.private-key-path}") | ||
| private val privateKeyPath: String, | ||
| @Value("\${oci.region}") | ||
| private val region: String, | ||
| @Value("\${oci.bucket.namespace}") | ||
| private val bucketNamespace: String | ||
| ) { | ||
|
|
||
| @Bean | ||
| fun objectStorageClient(): ObjectStorage = | ||
| createStorageClient(createAuthenticationProvider()) | ||
| .also { client -> | ||
| client.endpoint = "https://$bucketNamespace.objectstorage.$region.oci.customer-oci.com" | ||
| } | ||
|
|
||
| private fun createAuthenticationProvider(): SimpleAuthenticationDetailsProvider = | ||
| SimpleAuthenticationDetailsProvider.builder().apply { | ||
| tenantId(tenantId) | ||
| userId(userId) | ||
| fingerprint(fingerprint) | ||
| privateKeySupplier(createPrivateKeySupplier()) | ||
| }.build() | ||
|
|
||
| private fun createPrivateKeySupplier(): Supplier<InputStream> = | ||
| Supplier { FileInputStream(privateKeyPath) } | ||
yechan-kim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private fun createStorageClient(provider: SimpleAuthenticationDetailsProvider): ObjectStorage = | ||
| ObjectStorageClient.builder().build(provider) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.