diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1..aaaabb3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf93008..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a218..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/kotlin/picklab/backend/activity/application/model/ActivityItemWithBookmark.kt b/src/main/kotlin/picklab/backend/activity/application/model/ActivityItemWithBookmark.kt index e041fcc4..20d20816 100644 --- a/src/main/kotlin/picklab/backend/activity/application/model/ActivityItemWithBookmark.kt +++ b/src/main/kotlin/picklab/backend/activity/application/model/ActivityItemWithBookmark.kt @@ -1,19 +1,32 @@ package picklab.backend.activity.application.model +import io.swagger.v3.oas.annotations.media.Schema import picklab.backend.activity.domain.enums.RecruitmentEndType import java.time.LocalDate data class ActivityItemWithBookmark( + @field:Schema(description = "활동 ID") val id: Long, + @field:Schema(description = "활동명") val title: String, + @field:Schema(description = "주최기관/단체명") val organization: String?, + @field:Schema(description = "주최기관 유형") val organizerType: String, + @field:Schema(description = "활동 시작일") val startDate: LocalDate, + @field:Schema(description = "활동 유형 (EXTRACURRICULAR, COMPETITION, SEMINAR, EDUCATION)") val category: String, + @field:Schema(description = "직무 태그 목록") val jobTags: List, + @field:Schema(description = "활동 썸네일 이미지 URL") val thumbnailUrl: String?, + @field:Schema(description = "조회수") val viewCount: Long, + @field:Schema(description = "모집 종료 유형 (FIXED: 날짜 지정, ALWAYS_OPEN: 상시모집, CLOSE_ON_HIRE: 채용시마감)") val recruitmentEndType: RecruitmentEndType, + @field:Schema(description = "모집 마감까지 남은 일수 (상시모집·채용시마감은 null)") val dDay: Long?, + @field:Schema(description = "북마크 여부") val isBookmarked: Boolean, ) diff --git a/src/main/kotlin/picklab/backend/archive/application/ArchiveUseCase.kt b/src/main/kotlin/picklab/backend/archive/application/ArchiveUseCase.kt index 30a75b97..32e611be 100644 --- a/src/main/kotlin/picklab/backend/archive/application/ArchiveUseCase.kt +++ b/src/main/kotlin/picklab/backend/archive/application/ArchiveUseCase.kt @@ -11,7 +11,8 @@ import picklab.backend.archive.domain.service.ArchiveReferenceUrlService import picklab.backend.archive.domain.service.ArchiveService import picklab.backend.archive.domain.service.ArchiveUploadFileUrlService import picklab.backend.archive.entrypoint.request.ArchiveCreateRequest -import picklab.backend.archive.entrypoint.request.ArchiveUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveRecordUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveStatusUpdateRequest import picklab.backend.archive.entrypoint.response.ArchiveActivityResponse import picklab.backend.common.model.MemberPrincipal import picklab.backend.file.application.FileManagementService @@ -53,9 +54,9 @@ class ArchiveUseCase( } @Transactional - fun updateArchive( + fun updateArchiveStatus( archiveId: Long, - request: ArchiveUpdateRequest, + request: ArchiveStatusUpdateRequest, memberPrincipal: MemberPrincipal, ) { val member = memberService.findActiveMember(memberPrincipal.memberId) @@ -69,6 +70,40 @@ class ArchiveUseCase( archiveService.save(archive) } + @Transactional + fun updateArchiveRecord( + archiveId: Long, + request: ArchiveRecordUpdateRequest, + memberPrincipal: MemberPrincipal, + ) { + val member = memberService.findActiveMember(memberPrincipal.memberId) + val archive = archiveService.mustFindByIdAndMember(archiveId, member) + + val finalFileUrls = + fileManagementService.processUpdatedFileUrls( + fileUrls = request.fileUrls, + memberId = member.id, + activityId = archive.activity.id, + category = "archive", + ) + + archive.updateRecord( + activityRecord = request.activityRecord, + role = request.role, + detailRole = request.detailRole, + userStartDate = request.startDate, + userEndDate = request.endDate, + customRole = request.customRole, + ) + archiveService.save(archive) + + archiveReferenceUrlService.deleteByArchive(archive) + archiveUploadFileUrlService.deleteByArchive(archive) + + archiveReferenceUrlService.saveAll(request.referenceUrls.map { url -> ArchiveReferenceUrl(archive, url) }) + archiveUploadFileUrlService.saveAll(finalFileUrls.map { url -> ArchiveUploadFileUrl(archive, url) }) + } + @Transactional(readOnly = true) fun getArchiveList( activityType: ActivityType?, diff --git a/src/main/kotlin/picklab/backend/archive/domain/entity/Archive.kt b/src/main/kotlin/picklab/backend/archive/domain/entity/Archive.kt index 63553f23..4d9ae10c 100644 --- a/src/main/kotlin/picklab/backend/archive/domain/entity/Archive.kt +++ b/src/main/kotlin/picklab/backend/archive/domain/entity/Archive.kt @@ -77,4 +77,21 @@ class Archive( this.activityProgressStatus = activityProgressStatus this.passOrFailStatus = passOrFailStatus } + + fun updateRecord( + activityRecord: String, + role: RoleType, + detailRole: DetailRoleType, + userStartDate: LocalDate, + userEndDate: LocalDate, + customRole: String?, + ) { + this.activityRecord = activityRecord + this.role = role + this.detailRole = detailRole + this.userStartDate = userStartDate + this.userEndDate = userEndDate + this.customRole = customRole + this.writeStatus = WriteStatus.COMPLETED + } } diff --git a/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveReferenceUrlRepository.kt b/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveReferenceUrlRepository.kt index 750babec..cbb82939 100644 --- a/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveReferenceUrlRepository.kt +++ b/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveReferenceUrlRepository.kt @@ -2,7 +2,10 @@ package picklab.backend.archive.domain.repository import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository +import picklab.backend.archive.domain.entity.Archive import picklab.backend.archive.domain.entity.ArchiveReferenceUrl @Repository -interface ArchiveReferenceUrlRepository : JpaRepository +interface ArchiveReferenceUrlRepository : JpaRepository { + fun deleteByArchive(archive: Archive) +} diff --git a/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveUploadFileUrlRepository.kt b/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveUploadFileUrlRepository.kt index 86111bb0..9b4649f0 100644 --- a/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveUploadFileUrlRepository.kt +++ b/src/main/kotlin/picklab/backend/archive/domain/repository/ArchiveUploadFileUrlRepository.kt @@ -2,7 +2,10 @@ package picklab.backend.archive.domain.repository import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository +import picklab.backend.archive.domain.entity.Archive import picklab.backend.archive.domain.entity.ArchiveUploadFileUrl @Repository -interface ArchiveUploadFileUrlRepository : JpaRepository +interface ArchiveUploadFileUrlRepository : JpaRepository { + fun deleteByArchive(archive: Archive) +} diff --git a/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveReferenceUrlService.kt b/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveReferenceUrlService.kt index 7194f381..e16fd02d 100644 --- a/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveReferenceUrlService.kt +++ b/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveReferenceUrlService.kt @@ -1,6 +1,7 @@ package picklab.backend.archive.domain.service import org.springframework.stereotype.Service +import picklab.backend.archive.domain.entity.Archive import picklab.backend.archive.domain.entity.ArchiveReferenceUrl import picklab.backend.archive.domain.repository.ArchiveReferenceUrlRepository @@ -11,4 +12,8 @@ class ArchiveReferenceUrlService( fun saveAll(referenceUrls: Collection) { archiveReferenceUrlRepository.saveAll(referenceUrls) } + + fun deleteByArchive(archive: Archive) { + archiveReferenceUrlRepository.deleteByArchive(archive) + } } diff --git a/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveUploadFileUrlService.kt b/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveUploadFileUrlService.kt index 838a7a67..bc80829a 100644 --- a/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveUploadFileUrlService.kt +++ b/src/main/kotlin/picklab/backend/archive/domain/service/ArchiveUploadFileUrlService.kt @@ -1,6 +1,7 @@ package picklab.backend.archive.domain.service import org.springframework.stereotype.Service +import picklab.backend.archive.domain.entity.Archive import picklab.backend.archive.domain.entity.ArchiveUploadFileUrl import picklab.backend.archive.domain.repository.ArchiveUploadFileUrlRepository @@ -11,4 +12,8 @@ class ArchiveUploadFileUrlService( fun saveAll(uploadedFileUrls: Collection) { archiveUploadFileUrlRepository.saveAll(uploadedFileUrls) } + + fun deleteByArchive(archive: Archive) { + archiveUploadFileUrlRepository.deleteByArchive(archive) + } } diff --git a/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveApi.kt b/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveApi.kt index 57673681..ce23726e 100644 --- a/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveApi.kt +++ b/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveApi.kt @@ -13,7 +13,8 @@ import org.springframework.web.bind.annotation.RequestParam import picklab.backend.activity.domain.enums.ActivityType import picklab.backend.archive.domain.enums.ArchiveSortType import picklab.backend.archive.entrypoint.request.ArchiveCreateRequest -import picklab.backend.archive.entrypoint.request.ArchiveUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveRecordUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveStatusUpdateRequest import picklab.backend.archive.entrypoint.response.ArchiveActivityResponse import picklab.backend.common.model.MemberPrincipal import picklab.backend.common.model.ResponseWrapper @@ -35,19 +36,35 @@ interface ArchiveApi { ): ResponseEntity> @Operation( - summary = "아카이브 정보 수정", - description = "아카이브 정보를 수정 합니다", + summary = "아카이브 상태 수정", + description = "아카이브의 활동 진행 상태 및 합불 여부를 수정합니다", ) @ApiResponses( value = [ - ApiResponse(responseCode = "200", description = "아카이브 수정에 성공했습니다."), + ApiResponse(responseCode = "200", description = "아카이브 상태 수정에 성공했습니다."), ApiResponse(responseCode = "404", description = "아카이브 정보를 찾을 수 없습니다."), ], ) - fun update( + fun updateStatus( @AuthenticationPrincipal member: MemberPrincipal, @PathVariable archiveId: Long, - @RequestBody request: ArchiveUpdateRequest, + @RequestBody request: ArchiveStatusUpdateRequest, + ): ResponseEntity> + + @Operation( + summary = "아카이브 기록 내용 수정", + description = "아카이브의 활동 기록 내용(역할, 파일, 연관 URL 등)을 작성하거나 수정합니다", + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "아카이브 기록 내용 수정에 성공했습니다."), + ApiResponse(responseCode = "404", description = "아카이브 정보를 찾을 수 없습니다."), + ], + ) + fun updateRecord( + @AuthenticationPrincipal member: MemberPrincipal, + @PathVariable archiveId: Long, + @RequestBody request: ArchiveRecordUpdateRequest, ): ResponseEntity> @Operation( diff --git a/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveController.kt b/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveController.kt index c9c3a45e..e023ba52 100644 --- a/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveController.kt +++ b/src/main/kotlin/picklab/backend/archive/entrypoint/ArchiveController.kt @@ -13,7 +13,8 @@ import picklab.backend.activity.domain.enums.ActivityType import picklab.backend.archive.application.ArchiveUseCase import picklab.backend.archive.domain.enums.ArchiveSortType import picklab.backend.archive.entrypoint.request.ArchiveCreateRequest -import picklab.backend.archive.entrypoint.request.ArchiveUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveRecordUpdateRequest +import picklab.backend.archive.entrypoint.request.ArchiveStatusUpdateRequest import picklab.backend.archive.entrypoint.response.ArchiveActivityResponse import picklab.backend.common.model.MemberPrincipal import picklab.backend.common.model.ResponseWrapper @@ -32,16 +33,26 @@ class ArchiveController( return ResponseEntity.ok(ResponseWrapper.success(SuccessCode.CREATE_ARCHIVE_SUCCESS)) } - @PatchMapping("/v1/archive/{archiveId}") - override fun update( + @PatchMapping("/v1/archive/{archiveId}/status") + override fun updateStatus( @AuthenticationPrincipal member: MemberPrincipal, @PathVariable archiveId: Long, - @RequestBody request: ArchiveUpdateRequest, + @RequestBody request: ArchiveStatusUpdateRequest, ): ResponseEntity> { - archiveUseCase.updateArchive(archiveId, request, member) + archiveUseCase.updateArchiveStatus(archiveId, request, member) return ResponseEntity.ok(ResponseWrapper.success(SuccessCode.UPDATE_ARCHIVE_SUCCESS)) } + @PatchMapping("/v1/archive/{archiveId}/record") + override fun updateRecord( + @AuthenticationPrincipal member: MemberPrincipal, + @PathVariable archiveId: Long, + @RequestBody request: ArchiveRecordUpdateRequest, + ): ResponseEntity> { + archiveUseCase.updateArchiveRecord(archiveId, request, member) + return ResponseEntity.ok(ResponseWrapper.success(SuccessCode.UPDATE_ARCHIVE_RECORD_SUCCESS)) + } + @GetMapping("/v1/archive") override fun getList( @AuthenticationPrincipal member: MemberPrincipal, diff --git a/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveRecordUpdateRequest.kt b/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveRecordUpdateRequest.kt new file mode 100644 index 00000000..9dd360da --- /dev/null +++ b/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveRecordUpdateRequest.kt @@ -0,0 +1,25 @@ +package picklab.backend.archive.entrypoint.request + +import io.swagger.v3.oas.annotations.media.Schema +import picklab.backend.archive.domain.enums.DetailRoleType +import picklab.backend.archive.domain.enums.RoleType +import java.time.LocalDate + +class ArchiveRecordUpdateRequest( + @field:Schema(description = "활동 기록") + val activityRecord: String, + @field:Schema(description = "활동 역할") + val role: RoleType, + @field:Schema(description = "상세 역할") + val detailRole: DetailRoleType, + @field:Schema(description = "상세 역할에서 기타를 선택하여 직접 입력한 역할") + val customRole: String?, + @field:Schema(description = "활동 파일 URLs (기존 영구 URL + 신규 임시 URL 혼용 가능)") + val fileUrls: List, + @field:Schema(description = "활동 연관 URLs") + val referenceUrls: List, + @field:Schema(description = "활동 시작일") + val startDate: LocalDate, + @field:Schema(description = "활동 종료일") + val endDate: LocalDate, +) diff --git a/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveUpdateRequest.kt b/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveStatusUpdateRequest.kt similarity index 92% rename from src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveUpdateRequest.kt rename to src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveStatusUpdateRequest.kt index 55c117f6..39b098f4 100644 --- a/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveUpdateRequest.kt +++ b/src/main/kotlin/picklab/backend/archive/entrypoint/request/ArchiveStatusUpdateRequest.kt @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema import picklab.backend.archive.domain.enums.PassOrFailStatus import picklab.backend.archive.domain.enums.ProgressStatus -class ArchiveUpdateRequest( +class ArchiveStatusUpdateRequest( @field:Schema(description = "활동 진행 상태") val activityProgressStatus: ProgressStatus, @field:Schema(description = "합격/불합격 상태") diff --git a/src/main/kotlin/picklab/backend/archive/entrypoint/response/ArchiveActivityResponse.kt b/src/main/kotlin/picklab/backend/archive/entrypoint/response/ArchiveActivityResponse.kt index e1f3dae8..4b49d291 100644 --- a/src/main/kotlin/picklab/backend/archive/entrypoint/response/ArchiveActivityResponse.kt +++ b/src/main/kotlin/picklab/backend/archive/entrypoint/response/ArchiveActivityResponse.kt @@ -8,6 +8,8 @@ import java.time.LocalDate data class ArchiveActivityResponse( @field:Schema(description = "아카이브 ID") val id: Long, + @field:Schema(description = "원본 활동 ID") + val activityId: Long, @field:Schema(description = "활동 썸네일 이미지 URL") val activityThumbnailUrl: String?, @field:Schema(description = "활동 유형") @@ -27,6 +29,7 @@ data class ArchiveActivityResponse( fun from(archive: Archive): ArchiveActivityResponse = ArchiveActivityResponse( id = archive.id, + activityId = archive.activity.id, activityThumbnailUrl = archive.activity.activityThumbnailUrl, activityType = archive.activityType.name, title = archive.activity.title, diff --git a/src/main/kotlin/picklab/backend/common/model/SuccessCode.kt b/src/main/kotlin/picklab/backend/common/model/SuccessCode.kt index 62922eb2..918eed7b 100644 --- a/src/main/kotlin/picklab/backend/common/model/SuccessCode.kt +++ b/src/main/kotlin/picklab/backend/common/model/SuccessCode.kt @@ -38,7 +38,8 @@ enum class SuccessCode( // Archive 관련 CREATE_ARCHIVE_SUCCESS(HttpStatus.OK, "아카이브 생성에 성공했습니다."), - UPDATE_ARCHIVE_SUCCESS(HttpStatus.OK, "아카이브 수정에 성공했습니다."), + UPDATE_ARCHIVE_SUCCESS(HttpStatus.OK, "아카이브 상태 수정에 성공했습니다."), + UPDATE_ARCHIVE_RECORD_SUCCESS(HttpStatus.OK, "아카이브 기록 내용 수정에 성공했습니다."), GET_ARCHIVE_LIST(HttpStatus.OK, "아카이브 목록 조회에 성공했습니다."), // Notification 관련 diff --git a/src/main/kotlin/picklab/backend/file/application/FileManagementService.kt b/src/main/kotlin/picklab/backend/file/application/FileManagementService.kt index 6b0e2a8a..7a69da04 100644 --- a/src/main/kotlin/picklab/backend/file/application/FileManagementService.kt +++ b/src/main/kotlin/picklab/backend/file/application/FileManagementService.kt @@ -28,6 +28,33 @@ class FileManagementService( fileStoragePort.deleteFile(key) } + /** + * 수정 요청의 fileUrls에서 임시 URL은 영구저장소로 이동하고, 기존 영구 URL은 그대로 유지합니다. + * @return 최종 파일 URL 목록 (기존 영구 URL + 신규 이동된 URL) + */ + fun processUpdatedFileUrls( + fileUrls: List, + memberId: Long, + activityId: Long, + category: String, + ): List { + if (fileUrls.isEmpty()) return emptyList() + + val (tempUrls, permanentUrls) = + fileUrls.partition { + fileKeyGenerator.extractFileKeyFromUrl(it).startsWith("temp/") + } + + val movedUrls = + if (tempUrls.isNotEmpty()) { + verifyAndMoveTempFilesToPermanent(tempUrls, memberId, activityId, category) + } else { + emptyList() + } + + return permanentUrls + movedUrls + } + /** * 특정 멤버의 활동에 대한 임시 파일들을 일괄로 검증하고 영구저장소로 이동합니다. * @return 영구 저장소로 이동된 파일들의 public read URL 목록