289 lines
5.8 KiB
Markdown
289 lines
5.8 KiB
Markdown
# File Manager
|
|
|
|
# Uploading New File
|
|
Sebelum hit API, FE ambil data per file:
|
|
- `name` (contoh: `spidey.png`)
|
|
- `content_type` (contoh: `image/png`)
|
|
- `size_bytes` (contoh: `42170`)
|
|
|
|
## Step 1 - FE minta upload intent ke BE, lalu upload langsung ke storage
|
|
|
|
## A. Minta Upload Intent ke Backend
|
|
|
|
### Endpoint
|
|
- `POST /api/v1/file-manager/files/upload`
|
|
|
|
### FE kirim request ini
|
|
- Single File
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"type": "file_manager_file_upload_intent",
|
|
"attributes": {
|
|
"name": "image.png",
|
|
"content_type": "image/png",
|
|
"size_bytes": 123
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
- Bulk File
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"type": "file_manager_file_upload_intent",
|
|
"attributes": {
|
|
"name": "image1.png",
|
|
"content_type": "image/png",
|
|
"size_bytes": 123
|
|
}
|
|
},
|
|
{
|
|
"type": "file_manager_file_upload_intent",
|
|
"attributes": {
|
|
"name": "image2.png",
|
|
"content_type": "image/png",
|
|
"size_bytes": 456
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Backend akan balas per file
|
|
- `upload_intent_uuid`
|
|
- `upload_url`
|
|
- `method` (`PUT`)
|
|
- `required_headers`
|
|
- `object.bucket`
|
|
- `object.key`
|
|
- `expires_at`
|
|
- `expires_in_seconds`
|
|
|
|
Contoh response:
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"index": 0,
|
|
"success": true,
|
|
"resource": {
|
|
"type": "file_manager_file_upload_intent",
|
|
"id": "019d8215-c622-7576-86bd-3ddeecaf4867",
|
|
"attributes": {
|
|
"upload_intent_uuid": "019d8215-c622-7576-86bd-3ddeecaf4867",
|
|
"upload_url": "http://localhost:4566/...",
|
|
"method": "PUT",
|
|
"required_headers": {
|
|
"content-type": "image/png"
|
|
},
|
|
"object": {
|
|
"bucket": "wucher-file-dev",
|
|
"key": "fm/objects/files/2026/04/12/019d8215c6227a1aa20fc21f99f36584.png"
|
|
},
|
|
"file": {
|
|
"name": "spidey.png",
|
|
"mime_type": "image/png",
|
|
"size_bytes": 42170
|
|
},
|
|
"expires_at": "2026-04-12T14:40:58Z",
|
|
"expires_in_seconds": 899
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"meta": {
|
|
"total": 1,
|
|
"success": 1,
|
|
"failed": 0,
|
|
"partial_success": false
|
|
}
|
|
}
|
|
```
|
|
|
|
## B. FE Handle langsung Upload Binary Langsung ke S3
|
|
|
|
Untuk setiap item `success: true` dari step 1:
|
|
1. Ambil `upload_url`
|
|
2. Ambil `method`
|
|
3. Ambil `required_headers` (jika ada)
|
|
4. Lakukan request upload file ke S3
|
|
|
|
Contoh yang akan dilakukan FE:
|
|
|
|
```js
|
|
await fetch(upload_url, {
|
|
method: method, // "PUT"
|
|
headers: { "Content-Type": file.type },
|
|
body: file,
|
|
});
|
|
```
|
|
|
|
atau pseudo
|
|
```js
|
|
const intents = await api.requestUploadIntents(files) // bulk
|
|
const readyToUpload = intents.data.filter(i => i.success)
|
|
|
|
const uploadResults = await parallelLimit(readyToUpload, 4, putToS3) // paralel
|
|
const succeeded = uploadResults.filter(r => r.success)
|
|
|
|
if (succeeded.length > 0) {
|
|
await api.createFilesBulk(succeeded.map(toCreatePayload))
|
|
}
|
|
```
|
|
|
|
|
|
Catatan penting:
|
|
- **1 file = 1 PUT ke S3**
|
|
- boleh paralel (disarankan pakai concurrency limit)
|
|
- file yang gagal PUT **jangan** dikirim ke step create
|
|
|
|
|
|
## Step 2 - Upload Cancel (opsional)
|
|
|
|
### Endpoint
|
|
- `DELETE /api/v1/file-manager/files/cancel-upload`
|
|
|
|
### FE kirim request ini
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"type": "file_manager_file_cancel_upload",
|
|
"attributes": {
|
|
"upload_intent_uuid": "019d8215-c622-7576-86bd-3ddeecaf4867"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Kapan dipakai
|
|
- user sudah request upload intent
|
|
- tapi user batal sebelum create
|
|
- FE bisa cancel upload intent supaya flow lebih bersih
|
|
|
|
### Contoh response
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"index": 0,
|
|
"success": true,
|
|
"resource": {
|
|
"type": "file_manager_file_cancel_upload_result",
|
|
"id": "019d8215-c622-7576-86bd-3ddeecaf4867",
|
|
"attributes": {
|
|
"status": "canceled"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"meta": {
|
|
"total": 1,
|
|
"success": 1,
|
|
"failed": 0,
|
|
"partial_success": false
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 3 - FE Panggil Create ke Backend (Finalisasi)
|
|
|
|
Setelah PUT S3 sukses, FE gunakan:
|
|
|
|
### Endpoint
|
|
|
|
- `POST /api/v1/file-manager/files/create`
|
|
|
|
### FE kirim request ini
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"type": "file_manager_file_create",
|
|
"attributes": {
|
|
"upload_intent_uuid": "019d8215-c622-7576-86bd-3ddeecaf4867",
|
|
"name": "spidey.png",
|
|
"folder_id": null
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Aturan:
|
|
|
|
- `upload_intent_uuid`: wajib
|
|
- `name`: wajib
|
|
- `folder_id`: opsional (kalau mau dimasukkan ke folder)
|
|
- `null` atau tidak dikirim = root
|
|
|
|
## Step 4 - Process Create (BE)
|
|
|
|
Untuk setiap item:
|
|
1. Ambil upload intent dari `upload_intent_uuid`
|
|
2. Verify object benar-benar ada di storage
|
|
3. Verify metadata object (size/content type) sesuai intent
|
|
4. Simpan metadata file ke DB
|
|
5. Simpan outbox durable
|
|
6. Set status awal file menjadi `uploaded`
|
|
|
|
## Step 5 - Proses Async Lanjutan (Backend)
|
|
|
|
Setelah status `uploaded`, worker akan lanjut:
|
|
|
|
- `uploaded -> processing -> validated -> ready`
|
|
- atau `uploaded -> processing -> failed`
|
|
|
|
## Step 6 - FE Handle Response Create
|
|
|
|
Response create adalah bulk per item.
|
|
|
|
- Kalau semua sukses: HTTP `201`
|
|
- Kalau campuran sukses + gagal: HTTP `207` dengan `partial_success: true`
|
|
|
|
Contoh sukses:
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"index": 0,
|
|
"success": true,
|
|
"message": "created",
|
|
"resource": {
|
|
"type": "file_manager_file",
|
|
"id": "019d628a-8d92-7949-b355-95de506da3b7",
|
|
"attributes": {
|
|
"folder_id": "019d4309-4dc8-78be-bb0c-3ebb8f3056ba",
|
|
"name": "spidey01.png",
|
|
"extension": "png",
|
|
"size_bytes": 42170,
|
|
"mime_type": "image/png",
|
|
"status": "uploaded",
|
|
"created_at": "2026-04-06T11:25:40Z",
|
|
"updated_at": "2026-04-06T11:25:40Z"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"meta": {
|
|
"total": 1,
|
|
"success": 1,
|
|
"failed": 0,
|
|
"partial_success": false
|
|
}
|
|
}
|
|
```
|