Kontrak API

Kontrak API

Spesifikasi endpoint, payload, dan struktur respons untuk tim backend.

Aplikasi saat ini berjalan dengan mock API. Untuk terhubung ke backend nyata, atur VITE_USE_MOCK_API=false pada berkas .env dan arahkan VITE_API_BASE_URL ke server backend.

Kompu API Contract — Komunikasi Publik

Versi: v1 · Terakhir diperbarui: integrasi dengan kompu-backend (Go/Fiber)

Dokumen ini adalah kontrak antara frontend Kompu dan kompu-backend. Bentuk di bawah adalah bentuk backend nyata; mock (src/services/mock/*) meniru rute, nama field, validasi, dan kode status yang sama.

1. Konfigurasi

  • VITE_API_BASE_URL=http://localhost:8080/api/v1 — base URL backend.
  • VITE_USE_MOCK_API=false — pakai backend nyata; true — pakai mock (localStorage kompu.mockdb.v2). Restart dev server setelah mengubah .env.
  • Dev server frontend berjalan di http://localhost:5173 (dipatok di vite.config.ts). Origin ini harus ada di CORS_ALLOWED_ORIGINS backend (default backend: http://localhost:3000,http://localhost:5173).

2. Konvensi Umum

  • Semua path relatif terhadap base URL, mis. GET /jobs → GET http://localhost:8080/api/v1/jobs.
  • Nama field: bahasa Inggris, snake_case.
  • Format tanggal: YYYY-MM-DD. Format waktu: ISO 8601 UTC (2026-10-24T03:00:00Z).
  • ID resource: UUID. ID yang bukan UUID → 404.
  • Batas file upload (divalidasi frontend, ditegakkan backend saat presign):
    • Kegiatan (jobs): 10 MB per file; image/jpeg, image/png, image/webp, application/pdf.
    • Siaran Pers (releases): 50 MB per file; tipe di atas + video/mp4.
    • Maks. 20 file per kegiatan/rilis.

2.1 Autentikasi

  • GET publik. POST/PUT/DELETE wajib header Authorization: Bearer <token sso-balai>.
  • Backend memverifikasi token ke sso-balai (GET {SSO_BASE_URL}/api/v1/auth/me).
    • Tanpa/format salah → 401 (missing or malformed bearer token).
    • Token tidak valid/kedaluwarsa → 401.
    • Akun tidak diizinkan → 403.
    • sso-balai tidak terjangkau → 503.
  • Tidak ada cookie; frontend tidak mengirim credentials.
  • Frontend (mode nyata): dialog SSO meminta token akses sso-balai, disimpan di localStorage kompu.sso.token dan dikirim di setiap mutasi. Mode mock tetap memakai sesi tiruan /auth/sso/session, /auth/session (hanya ada di mock).

2.2 Bentuk respons

Satu resource (GET /:id, POST, PUT): objek langsung, tanpa envelope.

{ "id": "…", "title": "…" }

Daftar dengan paginasi:

{
  "data": [{ "...": "item" }],
  "pagination": { "page": 1, "page_size": 20, "total": 137, "total_pages": 7 }
}
  • page default 1; page_size default 20, maks. 100.
  • Halaman di luar rentang tidak di-clamp: data kosong.
  • total_pages = 0 bila total = 0.

Gagal (4xx/5xx) — code adalah status HTTP (angka):

{ "error": { "code": 400, "message": "validation failed: title is required; …" } }
StatusArtiKode yang dipakai UI (ApiError.code)
400Validasi gagal (semua masalah digabung dengan ; )VALIDATION_ERROR
401Token tidak ada/tidak validUNAUTHENTICATED
403Akun tidak diizinkanFORBIDDEN
404Resource tidak adaNOT_FOUND
413File melebihi batasPAYLOAD_TOO_LARGE
415Tipe file tidak diizinkanUNSUPPORTED_MEDIA_TYPE
429Batas caption AI (10/menit/pengguna)RATE_LIMITED
502/503AI/SSO tidak tersediaSERVICE_UNAVAILABLE (503)

src/services/api.ts memetakan status ke nama kode di atas.

2.3 File tersimpan

Kegiatan (evidence_files) dan rilis (attachment_urls) memakai bentuk yang sama:

{
  "key": "jobs/2026/11/0123456789abcdef0123456789abcdef.jpg",
  "name": "dokumentasi.jpg",
  "content_type": "image/jpeg",
  "size": 1048576,
  "url": "https://…signed…"
}
  • Request: kirim key (wajib, harus object_key dari presign untuk modul yang sama), name, content_type, size. url diabaikan.
  • Respons: url adalah URL unduh bertanda tangan yang kedaluwarsa (STORAGE_DOWNLOAD_EXPIRY, default 1 jam); ambil ulang resource untuk URL baru.

3. Health

GET /health → 200 { "status": "ok", "database": "ok", "service", "environment", "time" }.

4. Activity Jobs

Resource job:

{
  "id": "6f1c…",
  "activity_date": "2026-10-25",
  "title": "Rapat koordinasi publikasi",
  "objective": "Menyusun rencana publikasi bulan ini",
  "location": "Ruang Rapat A",
  "participants": "Tim Kompu",
  "evidence_files": [{ "key": "jobs/…", "name": "dokumentasi.jpg", "content_type": "image/jpeg", "size": 1048576, "url": "…" }],
  "created_at": "2026-10-23T21:00:00Z",
  "updated_at": "2026-10-23T21:00:00Z"
}
MethodPathAuthCatatan
GET/jobspublikQuery: search, date_from, date_to, page, page_size.
GET/jobs/:idpublik404 bila tidak ada.
POST/jobsBearerRespons 201 + job.
PUT/jobs/:idBearerMengganti seluruh field. 200 + job.
DELETE/jobs/:idBearerSoft delete. Query opsional cleanup_files=true. 200.

GET /jobs

  • search: substring (tidak peka huruf besar/kecil) pada title, objective, location, participants; maks. 100 karakter.
  • date_from / date_to: filter inklusif pada activity_date; date_from > date_to → 400.
  • Urutan: activity_date turun (terbaru dulu), lalu created_at turun.

POST /jobs, PUT /jobs/:id

Body:

{
  "activity_date": "2026-10-25",
  "title": "Rapat koordinasi publikasi",
  "objective": "",
  "location": "",
  "participants": "",
  "evidence_files": [{ "key": "jobs/…", "name": "a.jpg", "content_type": "image/jpeg", "size": 1024 }]
}
  • Wajib: activity_date (YYYY-MM-DD), title (≤ 200).
  • Opsional: objective (≤ 5000), location (≤ 300), participants (≤ 2000), evidence_files (≤ 20, key valid untuk jobs).

DELETE /jobs/:id

{
  "id": "6f1c…",
  "deleted_at": "2026-10-24T03:00:00Z",
  "cleanup_files": { "requested": false, "deleted": 0, "kept_shared": 0, "failed": 0 }
}

?cleanup_files=true juga menghapus file dari storage, kecuali yang masih dipakai kegiatan lain. UI tidak memakainya.

5. News Releases

Resource release:

{
  "id": "9a2e…",
  "start_date": "2026-10-26",
  "title": "Siaran pers layanan klinik",
  "progress_status": "in_progress",
  "social_media_status": {
    "instagram": "posted",
    "facebook": "not_posted",
    "x": "not_posted",
    "tiktok": "not_posted",
    "youtube": "not_posted",
    "website": "posted"
  },
  "sharepoint_folder_url": "https://…sharepoint.com/sites/…",
  "attachment_urls": [{ "key": "releases/…", "name": "rilis-final.pdf", "content_type": "application/pdf", "size": 52428800, "url": "…" }],
  "caption": "Teks caption siaran pers…",
  "created_at": "2026-10-23T21:00:00Z",
  "updated_at": "2026-10-23T21:00:00Z"
}
  • progress_status: not_started | in_progress | paused | ready_to_release | released | cancelled.
  • social_media_status: platform instagram | facebook | x | tiktok | youtube | website → posted | not_posted. Respons selalu berisi keenam platform; platform yang tidak dikirim dianggap not_posted.
  • sharepoint_folder_url: "" atau URL http(s) (≤ 2048). Ini pengganti "SharePoint folder explorer": UI menampilkan folder dari rilis yang sedang tampil dan membukanya di tab baru (tanpa integrasi Graph).
MethodPathAuthCatatan
GET/releasespublikQuery: search, status, date_from, date_to, page, page_size.
GET/releases/:idpublik404 bila tidak ada.
POST/releasesBearerRespons 201 + release.
PUT/releases/:idBearerMengganti seluruh field. 200 + release.
DELETE/releases/:idBearerSoft delete; lampiran tetap di storage. 200 { "id", "deleted_at" }.

GET /releases

  • search: substring pada title, caption.
  • status: filter progress_status (nilai di luar daftar → 400).
  • date_from / date_to: filter inklusif pada start_date.
  • Urutan: start_date turun (terbaru dulu), lalu created_at turun.

POST /releases, PUT /releases/:id

  • Wajib: start_date, title (≤ 200), progress_status.
  • Opsional: social_media_status, sharepoint_folder_url, attachment_urls (≤ 20, key valid untuk releases), caption (≤ 5000).
  • Platform/status tidak dikenal → 400.

6. Uploads — pre-signed URL

POST /uploads/presigned-url (Bearer)

Request:

{ "resource": "jobs", "content_type": "image/jpeg", "size": 1048576 }

Respons 200 (header Cache-Control: no-store):

{
  "upload_url": "https://…storage…?X-Amz-Signature=…",
  "method": "PUT",
  "headers": { "Content-Type": "image/jpeg" },
  "object_key": "jobs/2026/11/0123456789abcdef0123456789abcdef.jpg",
  "expires_at": "2026-11-12T03:15:00Z",
  "max_bytes": 10485760
}
  • resource bukan jobs/releases atau size ≤ 0 → 400.
  • size > batas (jobs 10 MB, releases 50 MB) → 413.
  • content_type tidak diizinkan → 415.
  • Klien lalu mengirim berkas mentah ke upload_url dengan method dan headers dari respons; ukuran badan harus sama dengan size (ditandatangani).
  • Simpan object_key sebagai key di evidence_files/attachment_urls.
  • Mode mock: upload_url berupa path relatif /uploads/direct/… yang ditangani registry mock.

7. AI Caption Generator

POST /releases/generate-caption (Bearer, maks. 10 permintaan/menit/pengguna)

Request:

{ "notes": "ringkasan Laporan Perjalanan Dinas / catatan", "platform": "instagram", "title": "opsional" }
  • notes wajib (≤ 20000). platform opsional, default instagram (salah satu platform di §5). title opsional (≤ 200).
  • Caption selalu dalam Bahasa Indonesia; panduan gaya dari skills.md backend.

Respons 200:

{ "caption": "…caption siap tempel…", "platform": "instagram", "guideline_applied": true }
  • AI belum dikonfigurasi (AI_BASE_URL kosong) → 503; penyedia AI gagal → 502.
  • Mode mock: caption disusun lokal, guideline_applied: false.

Riwayat perubahan kontrak

  • Hari 1–5 — kontrak mock awal (field berbahasa Indonesia untuk kegiatan, social_status 4 kanal × 4 status, /uploads/presign, /ai/caption, /sharepoint/folders, sesi cookie, envelope { data, meta }).
  • Integrasi backend — kontrak disesuaikan dengan kompu-backend: field bahasa Inggris (title, objective, location, participants, evidence_files); model rilis backend (progress_status, social_media_status 6 platform × posted/not_posted, sharepoint_folder_url, attachment_urls); objek tunggal tanpa envelope; daftar { data, pagination } dengan page_size/total_pages; query search/date_from/date_to; error { code: <status HTTP>, message }, validasi 400; delete 200 dengan badan; auth Authorization: Bearer; POST /uploads/presigned-url dan POST /releases/generate-caption. Dihapus: /sharepoint/folders, filter location dan channel, language pada caption.