Files
fm_be/docs/file-manager-upload.md
2026-07-16 22:16:45 +07:00

5.8 KiB

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
{
  "data": [
    {
      "type": "file_manager_file_upload_intent",
      "attributes": {
        "name": "image.png",
        "content_type": "image/png",
        "size_bytes": 123
      }
    }
  ]
}
  • Bulk File
{
  "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:

{
  "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:

  await fetch(upload_url, {
    method: method, // "PUT"
    headers: { "Content-Type": file.type },
    body: file,
  });

atau pseudo

  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

{
  "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

{
  "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

{
  "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:

{
  "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
  }
}