chore: prepare docker webgis deployment

This commit is contained in:
Andrie
2026-06-11 18:14:21 +07:00
parent d2214ad9c8
commit a90748d9c1
149 changed files with 20844 additions and 5 deletions
@@ -0,0 +1,52 @@
# Business Process SOP - WebGIS Poverty Mapping
## Status Dokumen
Dokumen ini adalah acuan proses bisnis aktif. Untuk detail teknis, lihat `docs/codebase-navigation.md`.
## Aktor
### Administrator
Administrator mengatur master data, user, verifikasi, import, export, dan recalculate proximity. Administrator juga bertanggung jawab memastikan data yang tampil ke publik sudah `Terverifikasi`.
### Operator
Operator mengelola warga, kebutuhan, dan status bantuan dalam rumah ibadah yang ditugaskan. Data warga yang dibuat operator masuk `Pending` sampai Administrator melakukan Verifikasi.
### Publik
Publik dapat melihat peta dan papan kebutuhan tanpa melihat NIK, nama KK, alamat detail, catatan, atau koordinat rumah warga. Publik diperlakukan sebagai `viewer` read-only.
### Donatur
Donatur mengirim kontak dan kategori minat bantuan melalui papan kebutuhan publik. Saat ini flow Donatur masih berupa kontak umum, belum pledge ke kebutuhan spesifik.
## Alur Data Warga
1. Administrator atau Operator menambahkan warga.
2. Sistem menghitung Proximity dari titik warga ke rumah ibadah aktif.
3. Data yang dibuat Administrator langsung `Terverifikasi`.
4. Data yang dibuat Operator masuk `Pending`.
5. Administrator melakukan Verifikasi: approve menjadi `Terverifikasi` atau reject menjadi `Ditolak`.
6. Hanya warga `Terverifikasi` yang dapat masuk workflow bantuan/kebutuhan publik.
## Recalculate Proximity
Recalculate dapat mengubah rumah ibadah terdekat warga. Karena model saat ini `proximity-only`, assignment operator dapat berubah mengikuti hasil spasial terbaru setelah posisi/radius rumah ibadah diubah.
Untuk data kecil-menengah, recalculate langsung masih dapat dipahami dan dirawat. Jika jumlah warga/rumah ibadah menjadi besar, gunakan pendekatan batch atau queue agar proses tidak membuat request admin terasa lama.
## Status Bantuan dan Kebutuhan
Status bantuan warga bergerak dari `Belum Ditangani`, `Dalam Proses`, sampai `Sudah Ditangani`.
Status kebutuhan bergerak dari `Belum Terpenuhi`, `Dalam Proses`, sampai `Terpenuhi`.
## Catatan Audit
Setiap perubahan status bantuan atau kebutuhan sebaiknya memiliki Catatan agar riwayat mudah dipahami. Catatan menjawab pertanyaan sederhana: kenapa status ini berubah.
## Batas Flow Donatur Saat Ini
Donatur belum memilih kebutuhan spesifik. Admin membaca kontak donatur, lalu menindaklanjuti manual di luar sistem. Jika ingin closed-loop, perlu tabel donasi/pledge dan status konfirmasi.
@@ -0,0 +1,303 @@
# Codebase Audit Report - WebGIS Poverty Mapping
> Updated: 2026-06-03. Laporan ini merangkum proses bisnis aktif, wiring backend/frontend, lifecycle data, catatan logika, prioritas fix, dan saran pengembangan berdasarkan traverse codebase dan dokumentasi lokal.
## Ringkasan Eksekutif
Proyek ini adalah aplikasi WebGIS PHP prosedural + MySQLi + Leaflet untuk memetakan penduduk miskin, rumah ibadah, radius layanan, blank spot, status bantuan, kebutuhan bantuan, papan kebutuhan publik, import/export, dan dashboard admin.
Implementasi aktif sudah cukup konsisten dengan dokumentasi teknis di `README.md`, `docs/codebase-navigation.md`, dan `docs/superpowers/plans/2026-06-03-codebase-audit-refresh.md`. Dokumen lama di `Tugas/` dan sebagian plan historis bukan sumber kebenaran implementasi aktif karena masih membawa artifact lama atau rencana fase yang sudah selesai.
Status audit refresh 2026-06-03:
1. Validasi koordinat sudah dipusatkan di `includes/validation.php` dan dipakai oleh endpoint mutasi/import utama.
2. Import admin sudah menghasilkan warga `Terverifikasi` beserta `verified_by` dan `verified_at`.
3. Policy NIK sudah dibuat ketat: NIK dicek terhadap semua record, termasuk arsip/inactive, agar sejalan dengan unique index.
4. Statistik operator sudah memakai scope rumah ibadah operator.
5. Workflow kebutuhan dan status bantuan sudah dikunci untuk warga `Terverifikasi`.
6. Heatmap publik sudah disembunyikan karena koordinat warga publik tetap dimasking.
7. User management dan soft delete rumah ibadah sudah memiliki guard tambahan.
Risiko residual terbesar saat ini:
1. Form kontak donatur publik belum memiliki rate limit/CAPTCHA.
2. Model ownership `proximity-only` bisa mengubah scope operator setelah recalculate; pertimbangkan model hybrid jika dipakai multi-operator serius.
3. Test runner masih ad-hoc; belum ada satu command tunggal untuk semua static guard dan DB smoke test.
4. Dependency CDN/tile provider eksternal perlu strategi mirror lokal untuk deployment jaringan tertutup.
## Triage Review Tambahan 2026-06-03
Review tambahan menyorot beberapa risiko bisnis yang perlu dibedakan antara bug yang sudah jelas, keputusan produk yang perlu dikunci, dan optimasi jangka menengah.
### Valid Dan Perlu Masuk Prioritas
1. Aksi kebutuhan pada warga `Pending` belum dikunci. Endpoint `api/kebutuhan/simpan.php` dan `api/kebutuhan/update_status.php` hanya mengecek warga aktif, tidak mengecek `status_verifikasi`. UI popup juga membangun section kebutuhan untuk user login tanpa membedakan status verifikasi. Jika warga ditolak admin, data kebutuhan yang sudah dibuat tetap tertinggal dan bisa menjadi data tidak relevan.
2. Heatmap publik tidak akan berfungsi dengan data warga saat ini karena `api/penduduk/ambil.php` mengosongkan `lat` dan `lng` untuk non-privileged user, sementara `modules/heatmap.js` membutuhkan koordinat valid. Ini adalah tradeoff privasi vs visualisasi yang perlu diputuskan eksplisit.
3. Import admin memang perlu policy yang lebih jelas. Solusi minimum adalah set hasil import admin ke `Terverifikasi`; solusi lebih matang adalah staging/bulk verify sebelum commit permanen.
4. NIK vs soft delete memang perlu strategi lifecycle. Mengubah NIK saat soft delete adalah opsi cepat, tetapi perlu audit trail jelas agar data historis tidak membingungkan.
5. Refresh data lintas modul sebaiknya distandarkan. Pola event atau `window.refreshAllData()` akan mengurangi state drift antar map, list, stats, dan heatmap.
### Perlu Keputusan Produk
1. Kepemilikan warga operator saat ini ditentukan oleh hasil proximity, bukan input bebas operator. Pada `api/penduduk/simpan.php`, data operator tidak dipindahkan diam-diam ke rumah ibadah lain; request ditolak jika titik tidak masuk rumah ibadah operator. Namun, `ibadah_id` warga dapat berubah setelah admin mengubah posisi/radius rumah ibadah atau menjalankan recalculation. Produk perlu memilih:
- ownership dinamis berbasis proximity, cocok untuk analisis spasial otomatis;
- ownership terkunci berbasis operator input, cocok untuk tanggung jawab operasional;
- hybrid: simpan `assigned_ibadah_id` untuk ownership operasional dan `nearest_ibadah_id` untuk hasil analisis spasial.
2. Spatial fuzzing untuk publik dapat menghidupkan heatmap publik, tetapi titik jitter 50-100 meter tetap perlu aturan konsisten agar tidak menciptakan kesan akurasi palsu. Alternatif yang lebih aman adalah endpoint agregat grid/kelurahan untuk heatmap publik, bukan titik warga individual.
### Keputusan Eksekusi Audit 2026-06-03
1. Model ownership warga untuk batch fix ini tetap `proximity-only`. `penduduk_miskin.ibadah_id` tetap menjadi hasil `calc_proximity()` dan menjadi dasar scope operator. Perubahan schema `assigned_ibadah_id`/`nearest_ibadah_id` tidak dilakukan dalam batch ini agar tidak mengubah kontrak bisnis besar tanpa PRD lanjutan.
2. Strategi heatmap publik untuk batch fix ini adalah `hidden`: kontrol heatmap disembunyikan untuk viewer publik karena koordinat warga publik tetap dimasking. Endpoint agregat grid/kelurahan dapat dirancang sebagai pengembangan lanjutan jika produk membutuhkan visualisasi heatmap publik tanpa membocorkan koordinat rumah warga.
### Optimasi Jangka Menengah
1. Bounding box untuk Haversine bagus untuk pencarian kandidat terdekat, tetapi recalculation saat perubahan rumah ibadah tetap perlu menyentuh semua warga aktif jika sistem ingin memastikan semua assignment global tetap benar. Optimasi yang lebih tepat bisa berupa index lat/lng, batch processing, atau recalculation queue.
2. Closed-loop donatur ke kebutuhan spesifik akan meningkatkan akuntabilitas, tetapi membutuhkan model data tambahan seperti `donasi`, `donasi_kebutuhan`, status pledge/confirmed, dan audit penyelesaian.
### Batas Skala Proximity Saat Ini
`calc_proximity()` melakukan scan semua rumah ibadah aktif untuk setiap warga. Ini sederhana dan benar untuk data kecil-menengah, tetapi recalculate/import dapat melambat jika jumlah warga atau rumah ibadah besar.
Opsi peningkatan:
1. Tambah index `lat` dan `lng` untuk membantu query kandidat.
2. Gunakan bounding box kasar sebelum Haversine.
3. Jalankan recalculate dalam batch.
4. Simpan job recalculate jika proses lebih dari beberapa detik.
Jangan mengganti proximity engine sebelum ada data performa nyata. Algoritma saat ini mudah dipahami dan sudah punya test. Optimasi harus menjaga hasil assignment tetap sama.
## Dokumentasi Yang Berlaku
| File | Status | Catatan |
| --- | --- | --- |
| `README.md` | Aktif | Setup, stack, role, fitur, dan test. |
| `docs/codebase-navigation.md` | Aktif | Referensi teknis paling lengkap untuk struktur, API, modul frontend, DB, RBAC, proximity. |
| `setup_database.sql` | Aktif | Schema otoritatif dan seed admin default. |
| `docs/superpowers/specs/2026-05-24-poverty-mapping-prd.md` | Historis/produk | PRD berguna untuk konteks fitur, tetapi status implementasi perlu diupdate. |
| `docs/superpowers/plans/*.md` | Historis implementasi | Log rencana/fase kerja. |
| `Tugas/*` | Artifact tugas lama | Masih menyebut jalan/parsil/POI; jangan dijadikan acuan implementasi aktif. |
| `DESIGN.md` | Artifact desain lama | Bukan referensi desain aktif WebGIS. |
## Proses Bisnis Aktif
1. Administrator menyiapkan sistem dengan mengatur `config.php` dan menjalankan `setup_database.sql`.
2. Administrator login memakai akun default `admin / Admin1234`, lalu wajib mengganti password.
3. Administrator membuat dan mengelola `rumah_ibadah`: nama, jenis, alamat, kontak, koordinat, dan `radius_m`.
4. Administrator membuat akun operator dan mengaitkannya ke satu rumah ibadah melalui `users.ibadah_id`.
5. Administrator atau operator mendata `penduduk_miskin` berbasis koordinat.
6. Backend menjalankan `calc_proximity()` di `koneksi.php` untuk menentukan rumah ibadah terdekat yang mencakup warga.
7. Jika warga masuk radius salah satu rumah ibadah, sistem menyimpan `ibadah_id`, `jarak_m`, dan `is_blank_spot=0`; jika tidak, `ibadah_id=NULL`, `jarak_m` ke rumah ibadah terdekat, dan `is_blank_spot=1`.
8. Data yang dibuat administrator langsung `Terverifikasi`; data yang dibuat operator masuk `Pending` dan perlu diverifikasi admin.
9. Publik tanpa login diperlakukan sebagai viewer: hanya data `Terverifikasi`, PII dimasking, dan koordinat rumah warga tidak dikirim.
10. Operator mengelola warga dalam scope rumah ibadahnya: status bantuan, riwayat bantuan, dan kebutuhan bantuan.
11. Kebutuhan `Belum Terpenuhi` dari warga terverifikasi diagregasi ke `papan-kebutuhan.php`.
12. Donatur publik mengirim kontak/minat bantuan; admin membaca dan menandai kontak donatur sebagai read.
13. Admin mengakses dashboard, tren, import CSV, export CSV/PDF, arsip, verifikasi, user management, dan recalculation proximity.
## Wiring Backend
Backend tidak memakai framework MVC. File endpoint di `api/` berperan sebagai controller, `koneksi.php` dan `auth/helper.php` sebagai helper/service utama, dan schema di `setup_database.sql` sebagai model data.
### Entry Point Dan Helper
| Area | File | Peran |
| --- | --- | --- |
| Konfigurasi | `config.php` | Konstanta app, session, database, map center. |
| DB/proximity | `koneksi.php` | Koneksi global `$conn`, `haversine_distance()`, `calc_proximity()`. |
| Auth/RBAC | `auth/helper.php` | Session, role hierarchy, `require_auth()`, `has_role()`, CSRF. |
| Peta publik/auth-aware | `index.php` | Shell utama Leaflet dan module loader. |
| Dashboard | `dashboard.php` | Ringkasan admin dan trend dashboard. |
| Halaman admin/operator | `pages/*.php` | UI domain per fitur. |
| API | `api/**/*.php` | Endpoint JSON/CSV/HTML per domain. |
### Endpoint Per Domain
| Domain | Endpoint Utama | Akses |
| --- | --- | --- |
| Auth | `api/auth/login.php`, `change_password.php`, `logout.php` | Login/logout, ganti password, lockout. |
| Rumah ibadah | `api/ibadah/ambil.php`, `simpan.php`, `update.php`, `hapus.php`, `update_posisi.php`, `update_radius.php`, `update_kontak.php`, `recalculate.php` | GET publik untuk data aktif; mutasi admin + CSRF. |
| Penduduk | `api/penduduk/ambil.php`, `simpan.php`, `update_posisi.php`, `update_status.php`, `riwayat.php`, `verifikasi.php`, `hapus.php`, `nonaktifkan.php`, `arsip.php` | Public read masked; operator/admin mutasi scoped; verifikasi/admin. |
| Kebutuhan | `api/kebutuhan/ambil.php`, `simpan.php`, `update_status.php` | Operator/admin scoped + CSRF untuk mutasi. |
| Statistik | `api/stats/ambil.php`, `api/dashboard/trend.php` | Public/operator/admin stats; trend admin. |
| Papan publik | `api/papan/ambil.php`, `kontak_donatur.php`, `donatur.php` | Public needs/donor form; admin donor inbox. |
| Users | `api/users/ambil.php`, `simpan.php`, `update.php`, `reset_password.php`, `toggle_active.php` | Admin + CSRF untuk mutasi. |
| Import/export | `api/import/csv.php`, `template.php`, `api/export/csv.php`, `pdf.php` | Admin. |
### Auth Dan Scope
- Role hierarchy: `viewer < operator < administrator`.
- Unauthenticated map user diperlakukan sebagai viewer.
- `require_auth('operator')` mengizinkan operator dan administrator.
- Mutasi terautentikasi memakai `require_csrf()`.
- Operator dibatasi oleh `get_ibadah_id()` dan `penduduk_miskin.ibadah_id` di endpoint penduduk/kebutuhan/riwayat.
- Public endpoint penduduk hanya mengirim data terverifikasi dan memasking `nik`, `nama_kk`, `alamat`, `catatan`, `lat`, dan `lng`.
## Wiring Frontend
Frontend adalah PHP-rendered pages + vanilla JavaScript global modules. Tidak ada bundler, framework SPA, atau package manager JS.
### Entrypoint
| File | Peran |
| --- | --- |
| `index.html` | Redirect legacy ke `index.php`. |
| `index.php` | Shell peta utama, inject `window.APP_USER`, Leaflet, Chart.js, module loader. |
| `papan-kebutuhan.php` | Papan kebutuhan publik dan form kontak donatur. |
| `auth/login.php` | Login form POST ke API auth. |
| `auth/change_password.php` | Ganti password dengan CSRF. |
| `dashboard.php` | Dashboard admin. |
| `pages/map.php` | Iframe `../index.php?embedded=1` untuk map di shell admin. |
| `pages/*.php` | Halaman domain admin/operator. |
### Modul Map
| Module | State/Entry | API Yang Dipakai |
| --- | --- | --- |
| `modules/ibadah.js` | `window.initIbadah()`, `window._ibadahList` | `api/ibadah/ambil.php`, `simpan.php`, `update_posisi.php`, `update_kontak.php`, `update_radius.php`, `hapus.php`, `recalculate.php`. |
| `modules/penduduk.js` | `window.initPenduduk()`, `window._pendudukList` | `api/penduduk/ambil.php`, `simpan.php`, `update_posisi.php`, `update_status.php`, `riwayat.php`, `hapus.php`, `nonaktifkan.php`, `verifikasi.php`. |
| `modules/kebutuhan.js` | Loaded only for authenticated user | `api/kebutuhan/ambil.php`, `simpan.php`, `update_status.php`. |
| `modules/stats.js` | `window._showDashboard()`, `window._statsUpdate()` | `api/stats/ambil.php`. |
| `modules/heatmap.js` | `window._toggleHeatmap()`, `window._heatmapUpdate()` | Uses `window._pendudukList`; no direct API. |
### Flow Frontend-ke-Frontend
- `index.php` injects `window.APP_USER` for role, login status, assigned `ibadahId`, and CSRF token.
- `window.MAP_MODULES` loads module scripts sequentially; once complete, calls `initIbadah()` then `initPenduduk()`.
- `ibadah.js` fills `window._ibadahList`, then `penduduk.js` uses it for client-side proximity preview.
- `penduduk.js` fills `window._pendudukList`, then `stats.js` and `heatmap.js` read it for modal stats/heatmap.
- `ibadah.js` can call `_pendudukRecalcAll()` after ibadah changes.
- `penduduk.js` calls `_statsUpdate()`, `_heatmapUpdate()`, and blank spot counter update after list changes.
- `pages/map.php` embeds map in iframe, so page state and map state communicate through API/server rather than shared memory.
## Data Lifecycle
### Rumah Ibadah
- Dibuat/diedit oleh admin.
- `radius_m` menentukan cakupan layanan.
- Perubahan posisi/radius/delete memicu recalculation semua penduduk aktif.
- Delete bersifat soft delete dengan `deleted_at`.
### Penduduk Miskin
- Data aktif adalah `is_active=1 AND deleted_at IS NULL`.
- `deleted_at` dipakai untuk koreksi entry salah.
- `is_active=0` dipakai untuk warga pindah/meninggal/arsip real-world.
- `status_verifikasi` mengontrol visibilitas publik: hanya `Terverifikasi` yang muncul ke publik/papan kebutuhan.
- `status_bantuan` mengontrol workflow bantuan: `Belum Ditangani`, `Dalam Proses`, `Sudah Ditangani`.
- `riwayat_bantuan` append-only untuk audit perubahan status bantuan.
### Kebutuhan
- Kebutuhan menempel ke `penduduk_miskin`.
- Status: `Belum Terpenuhi`, `Dalam Proses`, `Terpenuhi`.
- `riwayat_kebutuhan` append-only untuk audit status kebutuhan.
- Papan publik mengagregasi kebutuhan `Belum Terpenuhi` dari warga aktif dan terverifikasi.
### User
- `administrator` mengelola sistem penuh.
- `operator` wajib dikaitkan ke satu rumah ibadah untuk scope kerja.
- `viewer` masih ada di backend sebagai fallback/kompatibilitas, tetapi UI tidak menawarkan pembuatan akun viewer baru.
- Password default/temp memaksa `must_change_password=1`.
### Donatur
- Publik mengirim kontak ke `kontak_donatur`.
- Admin melihat dan menandai semua unread sebagai read.
- Belum ada rate limit/spam mitigation yang terdokumentasi.
## Catatan Logika Dan Risiko
### Prioritas Tinggi Audit Awal - Status Refresh 2026-06-03
| Temuan Audit Awal | Status Saat Ini | Referensi |
| --- | --- | --- |
| Validasi koordinat tidak konsisten. | Selesai: helper `validate_lat_lng()` dipakai di endpoint utama dan import. | `includes/validation.php`, `api/penduduk/simpan.php`, `api/penduduk/update_posisi.php`, `api/ibadah/simpan.php`, `api/ibadah/update.php`, `api/ibadah/update_posisi.php`, `api/import/csv.php` |
| Import admin tidak mengisi `status_verifikasi`. | Selesai: import admin set `Terverifikasi`, `verified_by`, `verified_at`. | `api/import/csv.php` |
| Unique NIK global tidak cocok dengan pengecekan duplikat hanya data aktif. | Selesai dengan policy ketat: semua record dicek, termasuk arsip/inactive. | `api/penduduk/simpan.php`, `api/import/csv.php`, `setup_database.sql` |
| Statistik operator berpotensi global. | Selesai: stats memakai scope operator. | `api/stats/ambil.php` |
| Update status bantuan dari popup map bisa membuat UI stale. | Sebagian besar selesai: update state lokal, refresh list, dan dispatch event data changed. Tetap perlu test manual end-to-end di browser. | `modules/penduduk.js`, `index.php` |
| Kebutuhan dapat dibuat/diubah untuk warga `Pending`. | Selesai: endpoint menolak non-`Terverifikasi`, UI dibuat read-only/filtered. | `api/kebutuhan/simpan.php`, `api/kebutuhan/update_status.php`, `modules/kebutuhan.js`, `pages/kebutuhan.php` |
| Heatmap publik kosong karena koordinat publik dimasking. | Selesai dengan strategi `hidden`: kontrol heatmap hanya untuk operator/admin. | `index.php`, `modules/heatmap.js` |
### Prioritas Menengah
| Temuan | Dampak | Referensi |
| --- | --- | --- |
| Soft delete rumah ibadah tidak memakai `AND deleted_at IS NULL` dan tidak cek `affected_rows`. | Request ke ID deleted/tidak ada bisa tampak sukses. | `api/ibadah/hapus.php:40` |
| Update kontak rumah ibadah tidak mengecek `deleted_at IS NULL`. | Rumah ibadah soft-deleted masih bisa diubah kontaknya. | `api/ibadah/update_kontak.php:23` |
| Client dan server sama-sama punya logic proximity. | Rawan drift jika aturan proximity berubah. | `koneksi.php:28`, `modules/penduduk.js` |
| Heatmap memakai `_pendudukList` yang bisa sudah terfilter/visible. | Heatmap berubah diam-diam saat filter/layer berubah, tidak selalu merepresentasikan dataset penuh. | `modules/heatmap.js` |
| UI status bantuan tidak mengirim `catatan`, padahal backend menyimpan audit note. | Audit trail kurang informatif. | `api/penduduk/update_status.php`, `modules/penduduk.js` |
| Backend masih menerima create/update role `viewer`. | Kebijakan UI dan backend tidak sepenuhnya sinkron. | `api/users/simpan.php`, `api/users/update.php`, `README.md` |
| Admin bisa self-demotion via update user. | Risiko kehilangan akses admin jika tidak ada admin lain. | `api/users/update.php` |
### Prioritas Rendah
| Temuan | Dampak | Referensi |
| --- | --- | --- |
| Sidebar punya dead link `Pengaturan Sistem` dan `Aktivitas & Audit Log`. | Navigasi terlihat belum selesai. | `includes/page-start.php` |
| Naming legacy `searchPoi` dan toast POI masih ada. | Membingungkan karena fitur POI legacy sudah dihapus. | `index.php` |
| Papan publik memakai inline `onclick` untuk string kategori. | Saat ini mitigated oleh enum kategori, tetapi pola rapuh. | `papan-kebutuhan.php` |
| `DESIGN.md` berada di root walau bukan referensi aktif. | Developer baru bisa salah membaca arah desain. | `DESIGN.md` |
## Rekomendasi Fix
### Backend
1. Tambahkan helper validasi koordinat bersama, misalnya `validate_lat_lng($lat, $lng)` dan pakai di semua endpoint penduduk/ibadah/import.
2. Putuskan policy import admin: jika import dianggap aksi admin final, set `status_verifikasi='Terverifikasi'`, `verified_by=get_user_id()`, `verified_at=NOW()` pada insert import.
3. Selaraskan NIK dengan lifecycle soft delete/inactive:
- opsi cepat: cek semua NIK termasuk inactive/deleted dan tampilkan pesan jelas;
- opsi lebih baik: ubah constraint ke unique untuk NIK aktif saja via generated column/partial strategy sesuai DB yang dipakai.
4. Kunci endpoint kebutuhan untuk warga non-`Terverifikasi`, atau set policy eksplisit bahwa kebutuhan hanya boleh dibuat setelah approve admin.
5. Tambahkan operator scope ke `api/stats/ambil.php` bila operator tidak boleh melihat agregat global.
6. Perketat endpoint soft-delete/update dengan `deleted_at IS NULL`, `affected_rows`, dan response 404/error saat data tidak ditemukan.
7. Tambahkan guard user management untuk self-demotion dan admin terakhir.
8. Ekstrak helper response JSON, method guard, CSRF/method utility, coordinate validation, recalc-all, dan operator scope check agar endpoint tidak terus menduplikasi pola.
### Frontend
1. Setelah update status bantuan berhasil, update state lokal lalu panggil refresh list/stat/heatmap.
2. Jadikan backend sebagai sumber kebenaran proximity setelah perubahan posisi/radius rumah ibadah; reload penduduk dari API setelah recalc penting.
3. Pisahkan dataset mentah dan dataset visible, misalnya `_pendudukAll` dan `_pendudukVisible`.
4. Buat helper JS bersama untuk `fetchJson`, `appendCsrf`, escape, render badge, dan status update.
5. Tambahkan modal input `catatan` untuk perubahan status bantuan/kebutuhan, atau hapus ekspektasi catatan dari audit UI jika tidak dipakai.
6. Disable action kebutuhan/status bantuan untuk warga `Pending`/`Ditolak` di UI operator.
7. Pilih strategi heatmap publik: sembunyikan kontrol untuk viewer, gunakan endpoint agregat grid, atau gunakan spatial fuzzing dengan disclaimer akurasi.
8. Ganti inline event string di `papan-kebutuhan.php` dengan `data-*` dan `addEventListener`.
9. Bersihkan dead link dan naming legacy POI.
### Dokumentasi/Operasional
1. Dokumen deployment utama sekarang ada di `docs/deployment-docker.md`; `docs/deployment-xampp.md` dipertahankan sebagai referensi legacy.
2. Tambahkan dokumentasi dependency eksternal: Leaflet, Chart.js, leaflet.heat, Google Fonts, OSM/Esri/Carto tiles, Nominatim.
3. Tambahkan `config.example.php` atau `.env.example` jika proyek akan dipakai lintas mesin.
4. Update PRD dengan status implemented/partial/pending.
5. Pindahkan atau arsipkan `DESIGN.md` ke `docs/archive/` agar root documentation tidak ambigu.
## Saran Pengembangan
- Tambahkan audit log admin yang bisa dicari untuk perubahan user, ibadah, verifikasi, dan import.
- Tambahkan rate limiting atau CAPTCHA ringan untuk `api/papan/kontak_donatur.php`.
- Tambahkan batch job/recalculate queue jika jumlah data penduduk besar, karena update radius/posisi ibadah saat ini loop semua warga aktif.
- Tambahkan export laporan untuk blank spot dan kebutuhan per kategori.
- Tambahkan map bounds/wilayah studi di config agar koordinat di luar Pontianak bisa ditolak lebih awal.
- Tambahkan test runner tunggal untuk menjalankan seluruh `tests/test_*.php` dengan urutan yang jelas.
- Pertimbangkan service/helper layer ringan untuk domain `Penduduk`, `Ibadah`, `Kebutuhan`, dan `User` agar endpoint procedural tetap mudah dirawat.
## Prioritas Eksekusi Disarankan
1. Fix validasi koordinat dan import verification policy.
2. Kunci workflow kebutuhan/status bantuan untuk warga belum `Terverifikasi`.
3. Fix NIK lifecycle/constraint mismatch.
4. Putuskan ownership model: proximity-only, operator-locked, atau hybrid `assigned_ibadah_id` + `nearest_ibadah_id`.
5. Putuskan strategi heatmap publik: hidden, agregat grid, atau spatial fuzzing.
6. Scope operator untuk statistik bila memang data agregat harus terbatas.
7. Fix stale UI status bantuan dan heatmap dataset.
8. Hardening user management dan soft-delete endpoint.
9. Cleanup dead link/legacy naming.
10. Tambah deployment/config docs.
@@ -0,0 +1,347 @@
# Codebase Navigation — WebGIS Poverty Mapping
> Quick reference for navigating the project. Updated: 2026-06-03.
---
## Directory Tree at a Glance
```
WebgisPovertyMapping/
├── index.php ← Main app shell (auth-aware)
├── index.html ← Redirect/legacy entry from older prototype
├── dashboard.php ← Authenticated admin/operator shell
├── papan-kebutuhan.php ← Public needs board page
├── config.php ← All app constants (DB, session, map center)
├── koneksi.php ← DB connection + Haversine + calc_proximity()
├── setup_database.sql ← Full schema (idempotent, run to init DB)
├── auth/
│ ├── helper.php ← RBAC core: is_logged_in(), require_auth(), has_role()
│ ├── login.php ← Login page
│ └── change_password.php ← Forced password-change page
├── pages/
│ ├── map.php ← Dashboard wrapper embedding index.php?embedded=1
│ ├── penduduk.php ← Operator/admin penduduk management
│ ├── kebutuhan.php ← Operator/admin kebutuhan workflow
│ ├── status-bantuan.php ← Operator/admin aid status workflow
│ ├── ibadah.php ← Admin rumah ibadah management
│ ├── users.php ← Admin user management
│ ├── import.php ← Admin CSV import
│ ├── laporan.php ← Admin export/report page
│ └── analisis.php ← Admin analytics/recalculation page
├── modules/ ← Frontend JS modules (loaded by index.php)
│ ├── ibadah.js ← Rumah Ibadah layer: markers, circles, CRUD
│ ├── penduduk.js ← Penduduk layer: proximity coloring, CRUD, filters
│ ├── stats.js ← Dashboard modal: cards + chart + rekap table
│ ├── heatmap.js ← Heatmap overlay (leaflet.heat, weighted by jiwa)
│ └── kebutuhan.js ← Kebutuhan bantuan popup/actions
├── css/
│ └── admin.css ← Shared dashboard/page styling
├── includes/
│ ├── page-start.php ← Authenticated page shell start
│ └── page-end.php ← Authenticated page shell end
├── api/
│ ├── auth/ ← login.php · logout.php · change_password.php
│ ├── users/ ← ambil · simpan · update · reset_password · toggle_active
│ ├── ibadah/ ← ambil · simpan · update · hapus · update_posisi · update_kontak · update_radius · recalculate
│ ├── penduduk/ ← ambil · simpan · verifikasi · hapus · nonaktifkan · update_posisi · update_status · riwayat · arsip
│ ├── stats/ ← ambil.php (aggregations)
│ ├── kebutuhan/ ← ambil · simpan · update_status
│ ├── papan/ ← public board + donor contact endpoints
│ ├── import/ ← csv.php · template.php
│ └── export/ ← csv.php · pdf.php
├── tests/
│ ├── test_db.php ← Schema smoke test
│ ├── test_auth_helper.php ← Auth helper unit tests
│ ├── test_ibadah.php ← Ibadah column + radius tests
│ ├── test_penduduk.php ← Penduduk columns + NIK index tests
│ ├── test_kebutuhan.php ← Kebutuhan schema/query smoke tests
│ ├── test_proximity.php ← Haversine + tie-breaking unit tests
│ ├── test_verifikasi_binding.php ← Verification endpoint bind order guard
│ ├── test_update_posisi_scope.php ← Penduduk move scope guard
│ ├── test_operator_scope_endpoints.php ← Operator endpoint scope checks
│ ├── test_public_verification_filters.php← Public verification filters
│ ├── test_frontend_module_loader.php ← Module loader cache-busting guard
│ ├── test_frontend_reliability_guards.php ← Loader/heatmap/recalc guard checks
│ ├── test_no_legacy_poi.php ← Legacy POI removal guard
│ ├── test_viewer_public_filters.php ← Viewer public-data leakage guard
│ ├── test_operator_scope_strict.php ← Strict operator ibadah scope guard
│ ├── test_penduduk_binding_types.php ← Penduduk/import bind type guard
│ ├── test_csrf_enforcement.php ← CSRF helper/endpoint/frontend wiring guard
│ ├── test_session_cookie_security.php ← Session cookie security guard
│ ├── test_operator_ibadah_highlight.php ← Operator assigned-ibadah highlight guard
│ └── test_viewer_account_ui_hidden.php ← Viewer account creation UI guard
├── Tugas/ ← Assignment docs (SRS, use-case diagram)
└── docs/
├── codebase-navigation.md ← This file
└── superpowers/
├── specs/ ← PRD and design specs
└── plans/ ← Phase implementation plans
```
---
## Essential Files — Start Here
| File | Why You Need It |
|------|-----------------|
| [config.php](../config.php) | All configurable constants — DB credentials, map center (CENTER_LAT/LNG), SESSION_TIMEOUT, MAX_LOGIN_ATTEMPTS |
| [koneksi.php](../koneksi.php) | DB connection + the two core functions: `haversine_distance()` and `calc_proximity()` — the heart of the proximity engine |
| [setup_database.sql](../setup_database.sql) | Authoritative schema — run this to initialize or reset the DB |
| [auth/helper.php](../auth/helper.php) | RBAC foundation used by every endpoint: `require_auth()`, `has_role()`, `is_logged_in()`, session timeout logic |
| [index.php](../index.php) | Main SPA shell — role detection, toolbar rendering, all modals, map init, utility functions (`showToast`, `showDeleteConfirm`, etc.) |
## Related Documentation
| File | Purpose |
|------|---------|
| [codebase-audit-report.md](codebase-audit-report.md) | End-to-end business process, backend/frontend wiring, data lifecycle, current logic concerns, fix priorities, and development suggestions. |
| [business-process-sop.md](business-process-sop.md) | SOP proses bisnis aktif untuk administrator, operator, publik, donatur, verifikasi, proximity, dan catatan audit. |
| [deployment-docker.md](deployment-docker.md) | Docker/Coolify deployment, reverse proxy routing, database container, and troubleshooting. |
| [deployment-xampp.md](deployment-xampp.md) | Legacy XAMPP setup if the app must run without Docker. |
| [superpowers/README.md](superpowers/README.md) | Index PRD, implementation plans, and the latest superpowers audit refresh. |
| [superpowers/plans/2026-06-03-codebase-audit-refresh.md](superpowers/plans/2026-06-03-codebase-audit-refresh.md) | Latest codebase audit snapshot after hardening. |
| [../README.md](../README.md) | Setup, stack, default account, roles, features, and test commands. |
| [../setup_database.sql](../setup_database.sql) | Authoritative database schema and default admin seed. |
---
## Database Tables
### Core v1.0 Tables
**`rumah_ibadah`** — Worship places with service radius
| Key Columns | Notes |
|-------------|-------|
| `id`, `nama`, `jenis` (ENUM 6 types), `lat/lng` | Core identity |
| `radius_m` INT DEFAULT 500 | Validated 1005000 |
| `deleted_at` TIMESTAMP NULL | Soft delete — `WHERE deleted_at IS NULL` for all queries |
**`penduduk_miskin`** — Poor residents (core data)
| Key Columns | Notes |
|-------------|-------|
| `id`, `nama_kk`, `nik` VARCHAR(16) UNIQUE | NIK nullable but unique |
| `lat/lng`, `ibadah_id` FK, `jarak_m`, `is_blank_spot` | Proximity results — computed by `calc_proximity()` |
| `kategori` ENUM 3 levels, `status_bantuan` ENUM 3 states | Classification + aid tracking |
| `status_verifikasi` ENUM('Pending','Terverifikasi','Ditolak') | Public visibility gate |
| `is_active` TINYINT(1) | 0 = deceased/relocated (archived) |
| `deleted_at` TIMESTAMP NULL | Admin error correction (soft delete) |
> **`is_active=0` vs `deleted_at IS NOT NULL`:** Both hide the record from all active queries. `is_active=0` = real person, archived. `deleted_at` = data entry error, logically cancelled.
**`users`** — System accounts
| Key Columns | Notes |
|-------------|-------|
| `role` ENUM('administrator','operator','viewer') | RBAC basis |
| `ibadah_id` FK | Operator's assigned worship place |
| `must_change_password` TINYINT(1) | Forces redirect to change_password.php on login |
| `login_attempts`, `locked_until` | Brute-force protection state |
**`riwayat_bantuan`** — Aid status audit log (append-only)
| Key Columns | Notes |
|-------------|-------|
| `penduduk_id`, `operator_id` FK | Who changed what |
| `status_lama`, `status_baru`, `catatan` | Change record |
| No `deleted_at` | Intentionally — immutable audit trail |
**`kebutuhan`** — Assistance needs per resident
| Key Columns | Notes |
|-------------|-------|
| `penduduk_id`, `kategori`, `deskripsi`, `status` | Need details and fulfillment state |
| `created_by`, `updated_by` | Operator/admin audit references |
**`riwayat_kebutuhan`** — Kebutuhan status audit log (append-only)
| Key Columns | Notes |
|-------------|-------|
| `kebutuhan_id`, `operator_id` FK | Which need changed and who changed it |
| `status_lama`, `status_baru`, `catatan` | Status transition record |
| `created_at` | Append-only timestamp for audit chronology |
**`kontak_donatur`** — Public donor contact submissions
| Key Columns | Notes |
|-------------|-------|
| `nama`, `kontak`, `kategori_minat`, `pesan` | Public contact form data |
| `is_read` | Admin dashboard notification state |
## API Endpoint Map
### Public (no auth required)
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `api/ibadah/ambil.php` | GET | All active ibadah + KK/jiwa counts |
| `api/penduduk/ambil.php` | GET | Active penduduk — PII masked for non-auth |
| `api/stats/ambil.php` | GET | Aggregate stats; rekap_ibadah only for admin |
| `api/papan/ambil.php` | GET | Public verified kebutuhan board aggregation |
| `api/papan/kontak_donatur.php` | POST | Public donor contact submission |
### Operator+ (operator or admin)
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `api/penduduk/simpan.php` | POST | Add penduduk (auto-calls calc_proximity) |
| `api/penduduk/update_posisi.php` | POST | Move marker (recalcs proximity) |
| `api/penduduk/update_status.php` | POST | Change aid status + write audit log |
| `api/penduduk/riwayat.php` | GET | Aid history for a penduduk (scoped to own ibadah for operators) |
| `api/kebutuhan/ambil.php` | GET | List kebutuhan for a scoped penduduk |
| `api/kebutuhan/simpan.php` | POST | Add kebutuhan for a scoped penduduk |
| `api/kebutuhan/update_status.php` | POST | Update kebutuhan status + write audit log |
### Admin only
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `api/ibadah/simpan.php` | POST | Create ibadah |
| `api/ibadah/update.php` | POST | Update ibadah master data from admin page |
| `api/ibadah/hapus.php` | POST | Soft-delete ibadah (warns if operators linked) |
| `api/ibadah/update_posisi.php` | POST | Move ibadah → triggers full recalc |
| `api/ibadah/update_radius.php` | POST | Change radius → triggers full recalc |
| `api/ibadah/update_kontak.php` | POST | Update contact only |
| `api/ibadah/recalculate.php` | POST | Bulk recalculate all proximity |
| `api/penduduk/verifikasi.php` | POST | Approve/reject pending penduduk records |
| `api/penduduk/hapus.php` | POST | Soft-delete penduduk (data entry error) |
| `api/penduduk/nonaktifkan.php` | POST | Set is_active=0 (deceased/relocated) |
| `api/penduduk/arsip.php` | GET | List inactive/deleted warga for audit (up to 200 rows) |
| `api/papan/donatur.php` | GET/POST | GET lists donor contacts; POST marks unread entries as read with CSRF |
| `api/users/ambil.php` | GET | List all accounts |
| `api/users/simpan.php` | POST | Create account (must_change_password=1 forced) |
| `api/users/update.php` | POST | Edit nama/role/ibadah_id |
| `api/users/reset_password.php` | POST | Generate temp password + must_change=1 |
| `api/users/toggle_active.php` | POST | Enable/disable account (cannot self-deactivate) |
| `api/import/csv.php` | POST | CSV import (mode=preview or mode=import) |
| `api/import/template.php` | GET | Download CSV template |
| `api/export/csv.php` | GET | Export warga binaan as CSV (no NIK) |
| `api/export/pdf.php` | GET | Print-ready HTML report (no NIK) |
---
## Frontend Module Map
### `modules/ibadah.js`
- **Entry:** `window.initIbadah()` — called by index.php on load
- **Shared state:** `window._ibadahList[]` — read by penduduk.js for client-side proximity preview
- **Key functions:**
- `addToMap(data)` — creates draggable marker + radius circle
- `buildInfoPopup(data)` — admin: editable kontak/radius/export links; viewer: read-only
- `_ibadahHapus(id)` — two-step: operator-warning check → confirm → soft-delete
- `_recalcAll()` — calls recalculate.php, disables all CRUD buttons during operation
- `window._ibadahToggleLayer(bool)` / `_setBufferVisible(bool)` — layer controls
### `modules/penduduk.js`
- **Entry:** `window.initPenduduk()` — called by index.php on load
- **Shared state:** `window._pendudukList[]` — read by stats.js and heatmap.js
- **Key functions:**
- `buildInfoPopup(data, ibadah)` — proximity section + NIK (auth only) + status buttons (operator) + riwayat
- `findNearestIbadah(lat, lng)` — client-side Haversine mirror of PHP `calc_proximity()`
- `applyFilters()` — AND-logic filter across 4 dimensions + search
- `window._pendudukRecalcAll()` — client-side repaint after ibadah change (no server call)
- `_updateBlankSpotCounter()` / `_filterBlankSpot()` — blank spot counter + click-to-filter
### `modules/stats.js`
- **Entry:** `window._showDashboard()` — opens modal
- `window._statsUpdate()` — re-fetches and re-renders (only if modal visible)
- Destroys and recreates Chart.js instance on each update to avoid memory leaks
### `modules/heatmap.js`
- **Entry:** `window._toggleHeatmap(bool)` — creates/destroys heatmap layer
- Intensity = `0.2 + 0.8 * (jumlah_jiwa / maxJiwa)` — proportional to household size
- `window._heatmapSetKategori(k)` — filter heatmap by category
### `modules/kebutuhan.js`
- **Entry:** loaded for authenticated users through `window.MAP_MODULES`
- Builds kebutuhan controls inside penduduk popups
- Calls `api/kebutuhan/ambil.php`, `api/kebutuhan/simpan.php`, and `api/kebutuhan/update_status.php`
---
## Core Algorithm: Proximity Engine
**Location:** [koneksi.php](../koneksi.php) — `calc_proximity($conn, $lat, $lng)`
**Logic:**
1. Query all `rumah_ibadah WHERE deleted_at IS NULL ORDER BY id ASC`
2. For each, compute Haversine distance
3. Track two candidates: `best_in_radius` (nearest within radius) and `best_overall` (nearest regardless)
4. Winner = `best_in_radius` if exists; else `best_overall` (blank spot)
5. Tie-breaking: `ORDER BY id ASC` + strict `<` comparison → lowest id wins on equal distance
**Triggers:**
- `api/penduduk/simpan.php` — on each new penduduk
- `api/penduduk/update_posisi.php` — when penduduk moved
- `api/ibadah/update_posisi.php` — after ibadah position change (loops ALL penduduk)
- `api/ibadah/update_radius.php` — after radius change (loops ALL penduduk)
- `api/ibadah/hapus.php` — after soft-delete (loops ALL penduduk)
- `api/ibadah/recalculate.php` — manual bulk trigger (admin "Hitung Ulang Semua")
- `api/import/csv.php` — per-row during import
**Client-side mirror:** `modules/penduduk.js``findNearestIbadah()` for instant visual feedback during drag/radius resize (no server call).
---
## RBAC Quick Reference
```
viewer (0) → public/fallback read-only map, aggregated stats, PII masked
operator (1) → + add/move penduduk, update aid status (own ibadah only)
administrator (2) → + everything: CRUD ibadah, manage users, import/export, recalculate
```
Operators are created by administrators in `pages/users.php` and must be linked to one `rumah_ibadah`.
The shared admin shell renders navigation by role: administrators see dashboard/master/import/export/user management, while operators see map, penduduk, kebutuhan, and status bantuan only.
Unauthenticated visitors are treated as `viewer`, so the admin user-management UI does not offer `viewer` for new accounts. The backend role remains for fallback RBAC and existing read-only accounts.
**Session flow:**
1. Unauthenticated → `index.php` renders as `viewer` (no redirect)
2. Login → `api/auth/login.php` → session set → if `must_change_password=1`, redirect to `auth/change_password.php`
3. Every page/API: `auth/helper.php:is_logged_in()` validates session + renews `last_activity`
4. Idle 2hr → session expired → treated as viewer on next request
---
## Legacy Status
The obsolete road/polyline, parcel/polygon, and business-location prototype features have been removed from the active app.
Active core:
- `rumah_ibadah`
- `penduduk_miskin`
- `users`
- `kebutuhan`
- `riwayat_bantuan`
- `riwayat_kebutuhan`
- `kontak_donatur`
---
## Running Tests
Tests are intended to run from CLI/local PHP. Browser access to `/tests/` is blocked by `.htaccess` for deployment safety.
Run selected static/smoke tests from the project root:
```powershell
& 'C:\Program Files\Xampp\php\php.exe' tests\run_all.php
```
Some database smoke tests load `../config.php` or `../koneksi.php` and should be run from the `tests` directory after importing `setup_database.sql`.
---
## Configuration Quick-Start
Edit [config.php](../config.php) to set:
```php
define('CENTER_LAT', -0.0557); // Map center latitude
define('CENTER_LNG', 109.3487); // Map center longitude
define('ZOOM_LEVEL', 15); // Initial zoom
define('DB_HOST', 'localhost');
define('DB_PORT', 3307); // Local configured MySQL port
define('DB_NAME', 'db_webgis');
```
Default admin credentials (must change on first login): `admin` / `Admin1234`
@@ -0,0 +1,105 @@
# Deployment Docker
Panduan ini menjadi referensi utama untuk menjalankan WebGIS Poverty Mapping. XAMPP tidak lagi menjadi jalur deploy utama; gunakan XAMPP hanya sebagai fallback legacy.
## Runtime
- PHP 8.2 Apache dari `Dockerfile` root repository.
- MariaDB 11.4 dari `docker-compose.yml`.
- Database aplikasi final: `db_webgis`.
- Database project kelas `01/`: `db_webgis_01`.
- Aplikasi final tersedia di path `/WebgisPovertyMapping/`.
## Struktur Docker
File Docker berada di root repository `webgis/`:
```text
Dockerfile
docker-compose.yml
docker-compose.local.yml
.env.example
docker/apache-webgis.conf
```
Service:
- `app`: Apache + PHP, melayani root landing page, `01/`, dan `WebgisPovertyMapping/`.
- `db`: MariaDB, private untuk service `app`.
## Local Development
Jalankan dari root repository:
```bash
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.local.yml up --build -d
```
Buka:
```text
http://localhost:8080/
http://localhost:8080/WebgisPovertyMapping/
```
Jika port `8080` sudah dipakai, tambahkan di `.env`:
```env
APP_PORT=8081
```
Lalu jalankan ulang compose lokal.
## Database Init
MariaDB menjalankan file berikut hanya saat volume database masih kosong:
```text
01/setup_database.sql
WebgisPovertyMapping/setup_database.sql
```
Untuk reset database lokal:
```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml down -v
docker compose -f docker-compose.yml -f docker-compose.local.yml up --build -d
```
## Production Di Coolify
Gunakan `docker-compose.yml` saja.
1. Push root repository `webgis/` ke Git provider.
2. Buat resource baru di Coolify dari repository tersebut.
3. Pilih deployment type `Docker Compose`.
4. Set compose file ke `docker-compose.yml`.
5. Assign domain ke service `app`, misalnya `https://ifuntanhub.dev`.
6. Set environment variable:
```env
MARIADB_ROOT_PASSWORD=isi-password-kuat
```
`docker-compose.yml` production hanya memakai `expose: 80`. Jangan tambahkan `ports:` untuk production, karena routing publik harus lewat reverse proxy Coolify.
## Reverse Proxy
Coolify/reverse proxy menerima traffic publik dari domain dan meneruskannya ke container `app` port `80`.
Alur request:
```text
https://ifuntanhub.dev -> Coolify reverse proxy -> app:80 -> /WebgisPovertyMapping/
```
Header `X-Forwarded-Proto: https` dipakai oleh aplikasi untuk mengenali HTTPS saat menentukan secure session cookie.
## Troubleshooting
- App tidak bisa dibuka lokal: pastikan memakai `docker-compose.local.yml`, bukan hanya `docker-compose.yml`.
- DB gagal terhubung: cek env `MARIADB_ROOT_PASSWORD`, service `db`, dan healthcheck MariaDB.
- SQL init tidak berubah setelah edit schema: reset volume database; MariaDB hanya menjalankan init script saat volume kosong.
- Domain Coolify tidak masuk ke app: pastikan domain diassign ke service `app` dan container port `80`.
- Peta/chart/font gagal load: cek akses internet ke CDN dan tile provider eksternal.
@@ -0,0 +1,101 @@
# Deployment XAMPP Legacy
Panduan ini hanya referensi legacy jika aplikasi harus dijalankan tanpa Docker. Jalur utama saat ini adalah Docker Compose dan Coolify; lihat `deployment-docker.md`.
## Minimum Runtime
- Apache `>= 2.4`.
- PHP `>= 7.4`; pengujian lokal berjalan di PHP `8.2`.
- MySQL `>= 5.7` atau MariaDB `>= 10.5`.
## Port Dan Konfigurasi
- Contoh URL lokal: `http://localhost:8081/webgis/WebgisPovertyMapping/`.
- Apache dapat berjalan di `80`, `8080`, atau `8081`; sesuaikan URL dengan port XAMPP.
- MySQL/MariaDB default proyek memakai port `3307`.
- Jika MySQL berjalan di `3306`, ubah `DB_PORT` di `config.php`.
- Konfigurasi koneksi utama ada di `config.php`: `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_NAME`, dan `DB_PORT`.
## Import Database
1. Buat database dari schema:
```powershell
mysql -u root -P 3307 < setup_database.sql
```
2. Alternatif: buka phpMyAdmin, pilih tab SQL, lalu jalankan isi `setup_database.sql`.
3. Pastikan nama database sesuai `DB_NAME` di `config.php`.
## Akun Default
- Username: `admin`
- Password awal: `Admin1234`
- Setelah login pertama, admin wajib mengganti password.
## Proteksi Folder Non-Publik
Blokir folder tests dan tmp dari akses browser.
Kenapa: file di tests hanya untuk pengecekan developer. Beberapa test dapat menulis data ke database. Folder tmp dapat berisi session atau file sementara.
Pastikan `.htaccess` aktif dan Apache mengizinkan override:
```apache
AllowOverride All
```
Smoke test manual:
```text
http://localhost:8081/webgis/WebgisPovertyMapping/tests/test_proximity.php
```
Expected: `403 Forbidden`.
## Dependency Eksternal
- Leaflet CSS/JS dari `unpkg.com`.
- Chart.js dari `cdn.jsdelivr.net`.
- `leaflet.heat@0.2.0` dari `cdn.jsdelivr.net`.
- Google Fonts `Inter`.
- OpenStreetMap tiles dari `tile.openstreetmap.org`.
- Esri imagery dari `server.arcgisonline.com`.
- Carto dark tiles dari `basemaps.cartocdn.com`.
- Nominatim reverse geocoding dari `nominatim.openstreetmap.org`.
Jika CDN atau tile server tidak dapat diakses, peta dasar, chart, heatmap, font, atau reverse geocoding bisa terganggu walaupun data aplikasi tetap tersedia.
## Strategi Mirror Lokal
Jika aplikasi dipakai di jaringan tertutup, siapkan salinan lokal untuk:
- Leaflet CSS/JS.
- leaflet.heat.
- Chart.js.
- Lucide icons.
- Google Fonts Inter.
Tile peta juga perlu strategi sendiri:
- Gunakan tile server internal; atau
- Batasi penggunaan ke jaringan yang boleh mengakses OpenStreetMap, Esri, dan Carto.
## HTTPS Behind Reverse Proxy
Session cookie `Secure` aktif berdasarkan deteksi HTTPS di PHP. Jika Apache berada di belakang reverse proxy, pastikan header proxy HTTPS diteruskan dan server PHP mengenali request sebagai HTTPS.
## Abuse Control Publik
Form kontak donatur memiliki rate limit session sederhana: satu submit per 60 detik per browser session.
Untuk deploy publik serius, pertimbangkan CAPTCHA atau rate limit IP di web server.
## Troubleshooting
- DB gagal terhubung: cek service MySQL/MariaDB XAMPP, `DB_PORT`, username, password, dan nama database.
- Port Apache berbeda: gunakan URL sesuai port aktif XAMPP, misalnya `8081`.
- Import SQL gagal: pastikan MySQL/MariaDB aktif dan user punya izin membuat database/tabel.
- CDN tidak bisa diakses: siapkan mirror lokal untuk Leaflet, Chart.js, leaflet.heat, dan font bila deployment berada di jaringan tertutup.
- Tile peta lambat/gagal: cek akses ke OpenStreetMap, Esri, dan Carto.
- Nominatim gagal: reverse geocoding akan kosong, tetapi input koordinat tetap bisa dipakai.
- session timeout: nilai `SESSION_TIMEOUT` di `config.php` mengatur durasi sesi login.