forked from Team-Neki/Team-Neki-Server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUploadPhotosUseCase.kt
More file actions
97 lines (81 loc) · 3.38 KB
/
Copy pathUploadPhotosUseCase.kt
File metadata and controls
97 lines (81 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.yapp2app.photo.application.usecase
import com.yapp2app.common.annotation.UseCase
import com.yapp2app.common.api.dto.ResultCode
import com.yapp2app.common.exception.BusinessException
import com.yapp2app.common.transaction.TransactionRunner
import com.yapp2app.photo.application.command.UploadPhotoCommand
import com.yapp2app.photo.application.contract.MediaAvailability
import com.yapp2app.photo.application.port.FolderRepositoryPort
import com.yapp2app.photo.application.port.MediaClientPort
import com.yapp2app.photo.application.port.PhotoImageRepositoryPort
import com.yapp2app.photo.domain.entity.PhotoImage
/**
* fileName : BulkUploadPhotoUseCase
* author : koo
* date : 2026. 1. 20.
* description : 다중 사진 업로드 UseCase (최대 10장)
*/
@UseCase
class UploadPhotosUseCase(
private val mediaClient: MediaClientPort,
private val photoImageRepository: PhotoImageRepositoryPort,
private val folderRepository: FolderRepositoryPort,
private val transactionRunner: TransactionRunner,
) {
fun execute(command: UploadPhotoCommand) {
validateNoDuplicateMediaIds(command.uploads)
validateFolderOwnership(command.userId, command.folderId)
val mediaIds = command.uploads.map { it.mediaId }
// 모든 media가 object storage에 정상적으로 저장되었는지 일괄 확인
val availabilities = mediaClient.verifyMediasUploaded(
ownerId = command.userId,
mediaIds = mediaIds,
)
rollbackIfFailed(command.userId, availabilities)
val photos = command.uploads.map { upload ->
PhotoImage(
userId = command.userId,
mediaId = upload.mediaId,
folderId = command.folderId,
memo = upload.memo,
)
}
try {
transactionRunner.run {
photoImageRepository.saveAll(photos)
}
} catch (e: Exception) {
// 보상 트랜잭션: 모든 media 상태를 INITIATED로 롤백
mediaClient.rollbackMediasUploaded(command.userId, mediaIds)
throw e
}
}
private fun validateNoDuplicateMediaIds(uploads: List<UploadPhotoCommand.UploadItem>) {
val mediaIds = uploads.map { it.mediaId }
val duplicates = mediaIds.groupingBy { it }.eachCount().filter { it.value > 1 }.keys
if (duplicates.isNotEmpty()) {
throw BusinessException(ResultCode.INVALID_PARAMETER)
}
}
private fun validateFolderOwnership(userId: Long, folderId: Long?) {
if (folderId != null) {
folderRepository.getOwnedFolder(userId, folderId)
?: throw BusinessException(ResultCode.NOT_FOUND)
}
}
private fun rollbackIfFailed(userId: Long, availabilities: Map<Long, MediaAvailability>) {
val unavailableMediaIds = availabilities
.filter { it.value != MediaAvailability.AVAILABLE }
.keys
if (unavailableMediaIds.isNotEmpty()) {
val successfulMediaIds = availabilities
.filter { it.value == MediaAvailability.AVAILABLE }
.keys
.toList()
if (successfulMediaIds.isNotEmpty()) {
mediaClient.rollbackMediasUploaded(userId, successfulMediaIds)
}
throw BusinessException(ResultCode.UPLOAD_FAILED)
}
}
}