๐Ÿ“– API Sync & Upload Documentation

Offline-First Architecture โ€ข PHP 8.1+ โ€ข JWT โ€ข AES-256-GCM โ€ข UUID-Based Upload โ€ข SSE

๐ŸŒ Base URL & Global Rules

Base URL: https://android.urice.id

Content-Type: application/json (kecuali SSE: text/event-stream, Upload: multipart/form-data)

๐Ÿ“ฆ Standard Response Format:
{"status": int, "message": string, "data": mixed}
Error codes mengikuti standar HTTP (400, 401, 403, 404, 409, 500).
โฐ Timezone Alignment: Pastikan PHP (date_default_timezone_set('Asia/Jakarta')) dan MySQL (SET time_zone = '+07:00') menggunakan zona waktu yang sama. Ketidakcocokan akan menyebabkan conflict detection ngaco karena strtotime() & NOW() menghasilkan nilai berbeda.

๐Ÿ“ฆ Schema & Common Sync Columns

Setiap tabel operasional memiliki 4 kolom wajib yang menangani state offline-first & conflict resolution.

Column Type Trigger Update Kegunaan Utama
createdAt TIMESTAMP Saat record pertama dibuat Audit trail, sorting berdasarkan waktu pembuatan
updatedAt TIMESTAMP Setiap insert/update (lokal atau server) Conflict resolution, parameter sinceTimestamp saat pull
syncStatus ENUM Proses push/pull & conflict detection State queue: PENDING โ†’ SYNCED โ†’ CONFLICT โ†’ FAILED
lastSyncedAt TIMESTAMP Hanya saat push/pull berhasil dikonfirmasi server Marker keberhasilan sync, optimasi pull berikutnya
๐Ÿ’ก Photo Columns: attendance.photo & users.photoName menyimpan 1 path relatif. task_evidence.photo menyimpan JSON array string ["path1.jpg","path2.jpg"].

๐Ÿ“ Field Tambahan (Terbaru)

Tabel Column Type Keterangan
projects supervisorServerId CHAR(36) FK ke users.userServer. Penanggung jawab proyek
projects location VARCHAR(255) Alamat/lokasi pelaksanaan proyek
project_participants assignmentDate DATETIME Tanggal & waktu pelaksanaan penugasan
โœ… Semua kolom di atas otomatis ter-sync via push/pull. Tidak perlu ubah logic PHP.

๐Ÿ”„ Sync Architecture & Flow

Sistem menggunakan pendekatan Upload-First โ†’ Sync-Second. File media harus sukses terupload ke server sebelum record database dikirim.

๐Ÿ“ Step-by-Step Flow

1
Operasi OfflineUser input data & ambil foto. Local DB simpan: updatedAt=NOW(), syncStatus='PENDING', photoLocalPath='/storage/...'
2
Upload PhaseDevice online โ†’ Query PENDING records โ†’ Upload foto ke POST /api/upload โ†’ Server return path relatif.
3
Sync PhaseClient replace photoLocalPath โ†’ server path. Kirim payload ke POST /api/sync?action=push.
4
Conflict ResolutionServer bandingkan updatedAt. Client lebih baru โ†’ UPDATE โ†’ SYNCED. Server lebih baru โ†’ SKIP โ†’ CONFLICT โ†’ return UUID ke client.
5
FinalizationClient parse response โ†’ update syncStatus & lastSyncedAt โ†’ hapus file lokal jika SYNCED. Tampilkan UI merge jika CONFLICT.

๐Ÿ” State Machine: syncStatus

Status Pemicu Aksi Selanjutnya
PENDING Data diubah/dibuat saat offline Akan di-upload & dipush saat jaringan tersedia
SYNCED Push/Pull berhasil dikonfirmasi server Hapus cache lokal, siap operasi offline baru
CONFLICT Server menolak karena updatedAt lebih lama UI menampilkan prompt resolve. Setelah pilih versi โ†’ set PENDING โ†’ push ulang
FAILED Network error / server 5xx / FK violation Tetap PENDING, retry otomatis dengan exponential backoff

๐Ÿ“ค File Upload System

๐Ÿ“ Struktur Folder Berbasis UUID

Setiap upload disimpan dalam folder unik berdasarkan {table}/{uuid}/. Client wajib generate UUID v4 di lokal sebelum upload agar server bisa menyimpan file dengan struktur yang rapi & predictable.

uploads/
โ”œโ”€โ”€ .htaccess (Block PHP & Indexing)
โ”œโ”€โ”€ attendance/
โ”‚   โ””โ”€โ”€ 550e8400-e29b-41d4-a716-446655440011/
โ”‚       โ””โ”€โ”€ 1715500000_a1b2c3d4.jpg      โ† Single file
โ”œโ”€โ”€ users/
โ”‚   โ””โ”€โ”€ ffef-bce3-0e8a-4c87-80e2/
โ”‚       โ””โ”€โ”€ 1715500100_e5f6g7h8.png      โ† Profile photo
โ””โ”€โ”€ task_evidence/
    โ””โ”€โ”€ 550e8400-.../
        โ”œโ”€โ”€ img1_1715500200.jpg          โ† Multiple files
        โ”œโ”€โ”€ img2_1715500201.jpg
        โ””โ”€โ”€ vid3_1715500202.mp4

๐Ÿ“ก Endpoint: POST /api/upload

Parameter Type Required Desc
type string Yes Nama tabel: attendance, users, task_evidence
uuid string Yes UUID v4 dari record lokal (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
photos[] file[] Yes Array binary files. Support multiple upload sekaligus. Max 5MB/file, ext: jpg,jpeg,png,webp

๐Ÿ“ฅ Contoh Request (Multiple Files - Task Evidence)

Gunakan multipart/form-data. Perhatikan penamaan field photos[] (dengan kurung siku) agar PHP mengenali sebagai array.

POST /api/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="type"

task_evidence
------WebKitFormBoundary
Content-Disposition: form-data; name="uuid"

550e8400-e29b-41d4-a716-446655440099
------WebKitFormBoundary
Content-Disposition: form-data; name="photos[]"; filename="bukti1.jpg"
Content-Type: image/jpeg

(binary data...)
------WebKitFormBoundary
Content-Disposition: form-data; name="photos[]"; filename="bukti2.jpg"
Content-Type: image/jpeg

(binary data...)
------WebKitFormBoundary--

๐Ÿ“ค Response Success (200)

{
  "status": 200,
  "message": "Upload completed",
  "data": {
    "files": [
      { "status": "success", "filename": "1715500200_a1b2.jpg", "path": "uploads/task_evidence/550e8400-.../1715500200_a1b2.jpg" },
      { "status": "success", "filename": "1715500201_c3d4.jpg", "path": "uploads/task_evidence/550e8400-.../1715500201_c3d4.jpg" }
    ]
  }
}
๐Ÿ’ก Cara Simpan di Database:
โ€ข attendance.photo: Simpan string path tunggal.
โ€ข task_evidence.photo: Simpan JSON Array dari response.
["uploads/task_evidence/.../img1.jpg", "uploads/task_evidence/.../img2.jpg"]
Saat PULL, client cukup JSON.parse() kolom tersebut untuk mendapatkan list foto.

๐Ÿ” Authentication & Session Management

๐Ÿ”„ Refresh Token Strategy

Strategi Cara Kerja Rekomendasi
โœ… Proactive Cek exp di JWT lokal. Jika sisa < 7 hari, otomatis call /auth/refresh WAJIB. Hindari 401 saat user bekerja.
๐Ÿ” Reactive Interceptor tangkap 401 โ†’ call /refresh โ†’ retry request awal BACKUP. Handle clock skew atau app lama idle.
โš ๏ธ Deactivation Server balikin 403 saat isActive=false Client harus clear session & redirect login. JANGAN retry.
๐Ÿ”’ Stateless Warning: JWT tidak bisa di-revoke instan. Mekanisme /auth/verify & /auth/refresh yang mengecek DB isActive adalah satu-satunya cara aman memutus sesi.

POST /api/auth/login

๐Ÿ“ฅ Body: {"username":"superadmin","password":"123456"}

๐Ÿ“ค โœ… (200): {"data":{"token":"eyJ...","userServer":"uuid","roleId":"R001","isActive":true}}

POST /api/auth/refresh & GET /api/auth/verify

๐Ÿ“ฅ Headers: Authorization: Bearer <token>

๐Ÿ“ค โœ… Refresh (200): {"data":{"token":"eyJ...","expiresIn":2592000}}

๐Ÿ“ค โœ… Verify (200): {"data":{"userServer":"uuid","roleId":"R001","isActive":true,"expiresIn":2500000}}

โŒ Error: 401 (invalid/expired), 403 (akun dinonaktifkan)

๐Ÿ“ก API Endpoints

POST /api/sync?action=push Bearer Required

Mengirim data PENDING dari database lokal device ke server. Endpoint ini menangani insert baru, update, soft delete, dan conflict resolution secara otomatis dalam satu transaksi ACID.

๐Ÿ“ฅ Request Structure

Field Type Required Deskripsi
table string Yes Nama tabel target. Harus terdaftar di SYNCABLE_TABLES (whitelist). Tabel master seperti users, roles, jabatan ditolak otomatis demi keamanan.
records array Yes Array objek data lokal. Maksimal 100 record per request untuk menghindari timeout server.

๐Ÿ“ Field Rules per Record

  • โœ… Primary Key wajib ada (UUID v4 valid).
  • โœ… Foreign Keys harus sudah ada di server DB. Jika tidak โ†’ 409 FK Violation.
  • โœ… updatedAt wajib lebih baru dari versi server agar diterima. Jika lebih lama/sama โ†’ masuk conflicts.
  • ๐Ÿ“ธ File Fields: Gunakan path relatif hasil upload. Jangan kirim path lokal device.

โš™๏ธ Server Processing Logic

1
Validasi Cek whitelist tabel & payload valid.
2
Auto-Code Jika tabel kwitansi & kwitansiCode kosong, server generate otomatis [SEQ]/KWT/BCL/[MM]/[YY].
3
Conflict Detection Bandingkan client.updatedAt vs server.updatedAt. Jika client <= server โ†’ tandai CONFLICT.
4
INSERT / UPDATE Jika lolos: INSERT atau UPDATE + set syncStatus='SYNCED', lastSyncedAt=NOW().
5
Transaction Commit Seluruh batch dibungkus BEGIN TRANSACTION. Error โ†’ ROLLBACK total.

๐Ÿ“ค Response Format

โœ… Success (200):

{
  "status": 200,
  "message": "Push processed",
  "data": {
    "synced": ["550e8400-e29b-41d4-a716-446655440050"],
    "conflicts": [{ "uuid": "550e8400-...", "serverData": { "updatedAt": "...", "deletedAt": null } }]
  }
}
๐Ÿ’ก Client Best Practices: Batch 20-50 records/request. Handle conflicts via UI (jangan auto-retry). Hapus file lokal jika masuk synced[].

GET /api/sync?action=pull Bearer Required

Tarik data server yang berubah sejak timestamp terakhir. Tidak menyertakan record soft-deleted.

๐Ÿ“ฅ Query: ?sinceTimestamp=2026-05-01 00:00:00

๐Ÿ“ค โœ… (200): {"data":{"records":{"attendance":[...],"kwitansi":[...]},"nextPullTimestamp":"..."}}

๐Ÿ’ก Filter: Server otomatis menambahkan AND deletedAt IS NULL untuk tabel soft-delete. Tabel kwitansi dikecualikan karena menggunakan hard-delete. Tabel master (users, roles, jabatan) tidak dipull via endpoint ini.

Master Data POST / GET / PUT / DELETE /api/users

CRUD User Management (SuperAdmin Only). TIDAK termasuk dalam sync push/pull. Password otomatis didekripsi saat ambil detail.

๐Ÿ“ฅ List Users (Ringan)

GET /api/users?page=1&limit=20&search=budi&active=true

๐Ÿ“ค โœ… (200): {"data":[{"userServer":"uuid","nama":"Budi","isActive":true}], "pagination":{...}}

๐Ÿ“ฅ Detail User + Decrypted Password

GET /api/users?id=<uuid>

๐Ÿ“ค โœ… (200):

{
            "status": 200,
            "message": "User detail retrieved successfully",
            "data": {
                "userServer": "550e8400-...",
                "nik": "3271009999999999",
                "username": "karyawan.baru",
                "password": "123456",        โ† โœ… Plain text (auto-decrypt)
                "passwordHash": "U2FsdGVkX1+...",  โ† Tetap ada untuk sync
                "roleId": "R004",
                "jabatanId": "J004",
                "nama": "Ahmad Fauzi",
                "handphone": "081299998888",
                "email": "ahmad@test.com",
                "dailySalary": 150000,
                "isActive": true,
                "createdAt": "2026-05-20 10:00:00",
                "updatedAt": "2026-05-20 10:00:00"
            }
            }

๐Ÿ“ฅ Create / Update / Delete

  • POST /api/users โ†’ Create user baru (password dienkripsi otomatis)
  • PUT/PATCH /api/users?id=<uuid> โ†’ Update partial field
  • DELETE /api/users?id=<uuid>[&type=hard] โ†’ Soft delete (default) atau hard delete
๐Ÿ”’ Security & Audit:
โ€ข Hanya role R001 yang bisa akses endpoint ini.
โ€ข Setiap view detail (termasuk password) tercatat di logs/sync_*.log.
โ€ข Pastikan ENCRYPTION_KEY di .env sama persis dengan saat password dienkripsi.
โ€ข Selalu gunakan HTTPS di production.

๐Ÿงพ Kwitansi: Client-Side Generation & No-Delete Policy

Tabel kwitansi tidak akan dihapus (no soft/hard delete). Semua penomoran ditangani sepenuhnya di sisi aplikasi.

๐Ÿ“ Format Kode

[SEQ]/KWT/BCL/[MM]/[YY]

Contoh: 001/KWT/BCL/05/26, 002/KWT/BCL/05/26...

๐Ÿ“ฑ Aturan Generate di Client

  • โœ… App wajib query MAX(sequence) bulan berjalan di local DB sebelum generate.
  • โœ… Sequence di-reset otomatis saat pergantian bulan (logic ada di format string, bukan DB).
  • โœ… Server tidak auto-generate. Hanya menerima & menyimpan payload dari client.
  • โš ๏ธ Jika terjadi 409 Duplicate saat push, client harus increment sequence & retry.

๐Ÿ“ฅ Contoh Payload

{
  "table": "kwitansi",
  "records": [{
    "kwitansiServer": "550e8400-...",
    "milestoneServer": "550e8400-...",
    "kwitansiCode": "001/KWT/BCL/05/26",  โ† Generated by App
    "tanggal": "2026-05-20",
    "nominal": 5000000,
    "isLunas": false,
    "syncStatus": "PENDING",
    "updatedAt": "2026-05-20 10:00:00"
  }]
}

๐Ÿ”Œ Live Server Time & SSE Status

Menghubungkan... Endpoint: /api/sse/timezone.php
Server Timezone:Memuat...
Server Time:Memuat...
Status Koneksi:Initializing...
Live Log

๐Ÿš€ Deployment & Security Checklist

  • โœ… PHP 8.1+ aktif di hPanel
  • โœ… composer install --no-dev --optimize-autoloader selesai
  • โœ… .env dibuat manual (JANGAN commit). Kunci: JWT_SECRET, ENCRYPTION_KEY
  • โœ… Folder logs/ & uploads/ writable (chmod 755)
  • โœ… .htaccess aktif: blokir .env, logs/, force clean URL
  • โœ… DB schema import + dummy data roles, jabatan, users terisi
  • โœ… Timezone PHP & MySQL disamakan (Asia/Jakarta atau UTC)
  • โœ… Upload security: .htaccess di uploads/ blokir eksekusi PHP & directory listing
๐Ÿ›ก๏ธ Production Tips: Selalu gunakan HTTPS. Rotate JWT_SECRET & ENCRYPTION_KEY berkala. Monitoring logs/ untuk anomali login atau sync conflict massal. Gunakan WorkManager di Android untuk trigger sync background.