| name | presigned-url-flow |
| description | Load when working with S3 presigned URL uploads, media/image upload flow, photo registration, or media entity lifecycle management. |
Presigned URL ์ด๋ฏธ์ง ์
๋ก๋ Flow
Actors (์ฐธ์ฌ์)
| Actor | ์ญํ |
|---|
| Client | ๋ชจ๋ฐ์ผ ์ฑ (iOS/Android) |
| API Server | Spring Boot ๋ฐฑ์๋ (Neki) |
| Database | PostgreSQL (TB_MEDIA, TB_PHOTO_IMAGE) |
| S3 | AWS S3 Object Storage |
Sequence Flow
Phase 1: Presigned URL ๋ฐ๊ธ
1. Client -> API Server : POST /api/media/upload (JWT ์ธ์ฆ)
- Body: { items: [{ filename, contentType, mediaType, width, height, size }] }
2. API Server -> Database : Media ์ํฐํฐ ์์ฑ (status = INITIATED)
- storageKey: "{mediaType.prefix}/{UUID}.{extension}"
- ์: "photo-booth/550e8400-...-446655440000.jpg"
3. API Server -> S3 : PutObject Presign ์์ฒญ
- bucket, key(storageKey), contentType
- signatureDuration: 10๋ถ
4. S3 -> API Server : Presigned URL ๋ฐํ
5. API Server -> Client : ์๋ต
- { method: "PUT", expiresIn, items: [{ mediaId, uploadTicket(presigned URL), contentType }] }
Phase 2: S3 ์ง์ ์
๋ก๋
6. Client -> S3 : PUT {presigned URL} (์๋ฒ ๊ฒฝ์ ํ์ง ์์)
- Header: Content-Type: image/jpeg
- Body: [๋ฐ์ด๋๋ฆฌ ์ด๋ฏธ์ง ๋ฐ์ดํฐ]
7. S3 -> Client : 200 OK (์
๋ก๋ ์ฑ๊ณต)
Phase 3: ์
๋ก๋ ํ์ธ ๋ฐ ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
8. Client -> API Server : POST /api/photos (JWT ์ธ์ฆ)
- Body: { folderId, uploads: [{ mediaId, memo }], favorite }
9. API Server -> Database : mediaId๋ก Media ์ํฐํฐ ์กฐํ
- ownerId ๊ฒ์ฆ (๋ณธ์ธ ์์ ํ์ธ)
10. API Server -> S3 : ํ์ผ ์กด์ฌ ์ฌ๋ถ ํ์ธ (HEAD Object)
- key: media.storageKey
11. S3 -> API Server : ์กด์ฌ ํ์ธ ์๋ต
12. API Server -> Database : Media ์ํ ์
๋ฐ์ดํธ (INITIATED -> UPLOADED)
13. API Server -> Database : PhotoImage ์ํฐํฐ ์์ฑ
- { userId, mediaId, folderId, memo }
14. API Server -> Database : (favorite=true์ธ ๊ฒฝ์ฐ) FavoriteImage ์ถ๊ฐ
15. API Server -> Client : 200 OK (์ฑ๊ณต)
State Diagram: Media Status Lifecycle
[INITIATED] ---(S3 ์
๋ก๋ ํ์ธ)---> [UPLOADED]
[INITIATED] ---(๋ง๋ฃ/์คํจ)-------> [FAILED]
[UPLOADED] ---(์ญ์ ์์ฒญ)-------> [DELETE_REQUESTED]
[DELETE_REQUESTED] ---(S3 ์ญ์ ์๋ฃ)---> [DELETED]
Data Model Relationships
TB_MEDIA (๋ฏธ๋์ด ๋ฉํ๋ฐ์ดํฐ)
โโโ id (PK)
โโโ storage_key (S3 object key, unique)
โโโ owner_id (FK -> TB_USER)
โโโ media_type (PHOTO_BOOTH | USER_PROFILE | ATTACHMENT | LOGO | POSE | TEMP)
โโโ status (INITIATED | UPLOADED | FAILED | DELETE_REQUESTED | DELETED)
โโโ content_type (image/jpeg, image/png ๋ฑ)
โโโ width, height, size
โโโ created_at, updated_at
TB_PHOTO_IMAGE (์ฌ์ง-๋ฏธ๋์ด ์ฐ๊ฒฐ)
โโโ id (PK)
โโโ user_id (FK -> TB_USER)
โโโ media_id (FK -> TB_MEDIA)
โโโ folder_id (FK -> TB_FOLDER, nullable)
โโโ memo (nullable)
โโโ deleted_at (soft delete)
S3 Bucket ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ
{bucket}/
โโโ user-profiles/ (ํ๋กํ ์ด๋ฏธ์ง, TTL 24h ์บ์)
โโโ photo-booth/ (ํฌํ ๋ถ์ค ์ฌ์ง, ์บ์ ์์)
โโโ attachments/ (์ฒจ๋ถํ์ผ, ์บ์ ์์)
โโโ logo/ (๋ก๊ณ , TTL 24h ์บ์)
โโโ pose/ (ํฌ์ฆ ์ด๋ฏธ์ง, TTL 24h ์บ์)
โโโ temp/ (์์ ํ์ผ, ์บ์ ์์)
Key Design Decisions
| ์ค๊ณ ๊ฒฐ์ | ์ด์ |
|---|
| ํด๋ผ์ด์ธํธ๊ฐ S3์ ์ง์ ์
๋ก๋ | ์๋ฒ ๋ถํ ์ต์ํ, ๋์ฉ๋ ํ์ผ ์ฒ๋ฆฌ |
| Presigned URL ๋ง๋ฃ์๊ฐ 10๋ถ | ๋ณด์๊ณผ ์ฌ์ฉ์ฑ์ ๊ท ํ |
| 2๋จ๊ณ ํ์ธ (์
๋ก๋ โ ํ์ธ) | ์
๋ก๋ ์คํจ ์ ๊ณ ์ ๋ ์ฝ๋ ๋ฐฉ์ง |
| UUID ๊ธฐ๋ฐ storage key | ํ์ผ๋ช
์ถฉ๋ ๋ฐฉ์ง, URL ์์ธก ๋ถ๊ฐ |
| Media/PhotoImage ๋ถ๋ฆฌ | ๋ฏธ๋์ด ์ฌ์ฌ์ฉ ๊ฐ๋ฅ, ๋๋ฉ์ธ ๋ถ๋ฆฌ |
Component Diagram
โโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโ
โ โ โ API Server โ โ โ
โ โ 1. โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โโโโโ>โ โ MediaController โ->โ GenerateUploadTicketโโโโโ> โ โ
โ โ โ โ POST /api/media โ โ UseCase โ โ 3. โ โ
โ โ<โโโโโ โ /upload โ โ โ<โโโ โ โ
โ โ 5. โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โ โ โ
โ Client โ 6. โ โ โ S3 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> โ โ
โ โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ 7. โ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ 8. โ โ PhotoController โ->โ UploadPhotosUseCaseโ โ โ โ
โ โโโโโ>โ โ POST /api/photos โ โ โโโโโ> โ โ
โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโ โ 10. โ โ
โ โ<โโโโโ โ โ โ โConfirmMedia โโ<โโโ โ โ
โ โ 15. โ โ โ โ โUploadedUseCaseโโ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โโโโโโโโโโโ โ โ โ โโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2, 9, 12, 13, 14
v
โโโโโโโโโโโโโโโโ
โ PostgreSQL โ
โ TB_MEDIA โ
โ TB_PHOTO_IMAGEโ
โโโโโโโโโโโโโโโโ
API Spec Summary
POST /api/media/upload
Request:
{
"items": [
{
"filename": "photo1.jpg",
"contentType": "image/jpeg",
"mediaType": "PHOTO_BOOTH",
"width": 1920,
"height": 1080,
"size": 2048576
}
]
}
Response:
{
"resultCode": "SUCCESS",
"data": {
"method": "PUT",
"expiresIn": "2026-02-25T07:35:00Z",
"items": [
{
"mediaId": 1,
"uploadTicket": "https://{bucket}.s3.{region}.amazonaws.com/{key}?X-Amz-Algorithm=...",
"contentType": "image/jpeg"
}
]
}
}
POST /api/photos
Request:
{
"folderId": 123,
"uploads": [
{
"mediaId": 1,
"memo": "Family photo"
}
],
"favorite": true
}
Response:
{
"resultCode": "SUCCESS",
"data": null
}