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 (localStoragekompu.mockdb.v2). Restart dev server setelah mengubah.env.- Dev server frontend berjalan di
http://localhost:5173(dipatok divite.config.ts). Origin ini harus ada diCORS_ALLOWED_ORIGINSbackend (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.
- Kegiatan (
2.1 Autentikasi
GETpublik.POST/PUT/DELETEwajib headerAuthorization: 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.
- Tanpa/format salah →
- Tidak ada cookie; frontend tidak mengirim
credentials. - Frontend (mode nyata): dialog SSO meminta token akses sso-balai, disimpan di
localStorage
kompu.sso.tokendan 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 }
}
pagedefault1;page_sizedefault20, maks.100.- Halaman di luar rentang tidak di-clamp:
datakosong. total_pages=0bilatotal=0.
Gagal (4xx/5xx) — code adalah status HTTP (angka):
{ "error": { "code": 400, "message": "validation failed: title is required; …" } }
| Status | Arti | Kode yang dipakai UI (ApiError.code) |
|---|---|---|
| 400 | Validasi gagal (semua masalah digabung dengan ; ) | VALIDATION_ERROR |
| 401 | Token tidak ada/tidak valid | UNAUTHENTICATED |
| 403 | Akun tidak diizinkan | FORBIDDEN |
| 404 | Resource tidak ada | NOT_FOUND |
| 413 | File melebihi batas | PAYLOAD_TOO_LARGE |
| 415 | Tipe file tidak diizinkan | UNSUPPORTED_MEDIA_TYPE |
| 429 | Batas caption AI (10/menit/pengguna) | RATE_LIMITED |
| 502/503 | AI/SSO tidak tersedia | SERVICE_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, harusobject_keydari presign untuk modul yang sama),name,content_type,size.urldiabaikan. - Respons:
urladalah 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"
}
| Method | Path | Auth | Catatan |
|---|---|---|---|
GET | /jobs | publik | Query: search, date_from, date_to, page, page_size. |
GET | /jobs/:id | publik | 404 bila tidak ada. |
POST | /jobs | Bearer | Respons 201 + job. |
PUT | /jobs/:id | Bearer | Mengganti seluruh field. 200 + job. |
DELETE | /jobs/:id | Bearer | Soft delete. Query opsional cleanup_files=true. 200. |
GET /jobs
search: substring (tidak peka huruf besar/kecil) padatitle,objective,location,participants; maks. 100 karakter.date_from/date_to: filter inklusif padaactivity_date;date_from>date_to→400.- Urutan:
activity_dateturun (terbaru dulu), lalucreated_atturun.
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,keyvalid untukjobs).
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: platforminstagram|facebook|x|tiktok|youtube|website→posted|not_posted. Respons selalu berisi keenam platform; platform yang tidak dikirim dianggapnot_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).
| Method | Path | Auth | Catatan |
|---|---|---|---|
GET | /releases | publik | Query: search, status, date_from, date_to, page, page_size. |
GET | /releases/:id | publik | 404 bila tidak ada. |
POST | /releases | Bearer | Respons 201 + release. |
PUT | /releases/:id | Bearer | Mengganti seluruh field. 200 + release. |
DELETE | /releases/:id | Bearer | Soft delete; lampiran tetap di storage. 200 { "id", "deleted_at" }. |
GET /releases
search: substring padatitle,caption.status: filterprogress_status(nilai di luar daftar →400).date_from/date_to: filter inklusif padastart_date.- Urutan:
start_dateturun (terbaru dulu), lalucreated_atturun.
POST /releases, PUT /releases/:id
- Wajib:
start_date,title(≤ 200),progress_status. - Opsional:
social_media_status,sharepoint_folder_url,attachment_urls(≤ 20,keyvalid untukreleases),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
}
resourcebukanjobs/releasesatausize≤ 0 →400.size> batas (jobs10 MB,releases50 MB) →413.content_typetidak diizinkan →415.- Klien lalu mengirim berkas mentah ke
upload_urldenganmethoddanheadersdari respons; ukuran badan harus sama dengansize(ditandatangani). - Simpan
object_keysebagaikeydievidence_files/attachment_urls. - Mode mock:
upload_urlberupa 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" }
noteswajib (≤ 20000).platformopsional, defaultinstagram(salah satu platform di §5).titleopsional (≤ 200).- Caption selalu dalam Bahasa Indonesia; panduan gaya dari
skills.mdbackend.
Respons 200:
{ "caption": "…caption siap tempel…", "platform": "instagram", "guideline_applied": true }
- AI belum dikonfigurasi (
AI_BASE_URLkosong) →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_status4 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_status6 platform ×posted/not_posted,sharepoint_folder_url,attachment_urls); objek tunggal tanpa envelope; daftar{ data, pagination }denganpage_size/total_pages; querysearch/date_from/date_to; error{ code: <status HTTP>, message }, validasi400; delete200dengan badan; authAuthorization: Bearer;POST /uploads/presigned-urldanPOST /releases/generate-caption. Dihapus:/sharepoint/folders, filterlocationdanchannel,languagepada caption.