Coba Commit

This commit is contained in:
naukyy
2026-06-12 22:46:13 +07:00
parent f167f233b7
commit 66bb4c4c7c
126 changed files with 1613 additions and 866 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* jalan.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Jalan (LineString).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* kavling.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Kavling (Polygon).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,5 +1,5 @@
<?php
require_once '../config/db.php';
<?php
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+9 -1
View File
@@ -1,4 +1,12 @@
export const BASE_URL = '';
const getBaseUrl = () => {
const segments = window.location.pathname.split('/');
const targetIndex = segments.findIndex(seg => ['01', '02', '03', 'versi'].includes(seg));
if (targetIndex !== -1) {
return segments.slice(0, targetIndex).join('/');
}
return '';
};
export const BASE_URL = getBaseUrl();
const createService = (endpoint) => ({
getAll: async () => {
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* jalan.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Jalan (LineString).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* kavling.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Kavling (Polygon).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* rumah_ibadah.php
* Tanggung Jawab: Operasi CRUD Rumah Ibadah + Endpoint analisis jangkauan Haversine.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
require_once '../utils/geo_helper.php';
+2 -2
View File
@@ -1,5 +1,5 @@
<?php
require_once '../config/db.php';
<?php
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* warga_miskin.php
* Tanggung Jawab: Operasi CRUD untuk Warga Miskin.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+9 -1
View File
@@ -1,4 +1,12 @@
export const BASE_URL = '';
const getBaseUrl = () => {
const segments = window.location.pathname.split('/');
const targetIndex = segments.findIndex(seg => ['01', '02', '03', 'versi'].includes(seg));
if (targetIndex !== -1) {
return segments.slice(0, targetIndex).join('/');
}
return '';
};
export const BASE_URL = getBaseUrl();
const createService = (endpoint) => ({
getAll: async () => {
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* jalan.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Jalan (LineString).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* kavling.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Kavling (Polygon).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* rumah_ibadah.php
* Tanggung Jawab: Operasi CRUD Rumah Ibadah + Endpoint analisis jangkauan Haversine.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
require_once '../utils/geo_helper.php';
+2 -2
View File
@@ -1,5 +1,5 @@
<?php
require_once '../config/db.php';
<?php
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,11 +1,11 @@
<?php
<?php
/**
* statistik.php
* Tanggung Jawab: Menyediakan data analisis spasial menggunakan Point in Polygon (ST_Contains).
* Menghitung kepadatan warga miskin dalam setiap kavling.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
<?php
/**
* warga_miskin.php
* Tanggung Jawab: Operasi CRUD untuk Warga Miskin.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
+9 -1
View File
@@ -1,4 +1,12 @@
export const BASE_URL = '';
const getBaseUrl = () => {
const segments = window.location.pathname.split('/');
const targetIndex = segments.findIndex(seg => ['01', '02', '03', 'versi'].includes(seg));
if (targetIndex !== -1) {
return segments.slice(0, targetIndex).join('/');
}
return '';
};
export const BASE_URL = getBaseUrl();
const createService = (endpoint) => ({
getAll: async () => {
+38 -23
View File
@@ -1,18 +1,19 @@
# WebGIS Poverty Map
# D1041231071 — WebGIS Smart City Project
**Naufal Zaky R | UAS WebGIS**
Aplikasi WebGIS PHP + MySQL untuk pemetaan data kemiskinan, fasilitas, laporan warga, dan analisis blank spot bantuan.
Aplikasi WebGIS berbasis PHP + MySQL untuk pemetaan data kemiskinan, fasilitas umum, laporan warga, dan analisis blank spot bantuan sosial di Kota Pontianak.
## Stack
## Stack Teknologi
- PHP 8.2 Apache
- MySQL/MariaDB
- Leaflet
- PDO MySQL
- MySQL / MariaDB + PostGIS
- Leaflet.js (peta interaktif)
- PDO MySQL (koneksi database)
## Deploy Coolify
## Cara Deploy (Coolify)
1. Buat database MySQL/MariaDB di Coolify.
2. Import `database/webgis_db.sql` ke database tersebut.
2. Import `db_scripts/webgis_naufal_zaky.sql` ke database tersebut.
3. Buat aplikasi baru dari repo ini.
4. Pilih build menggunakan `Dockerfile` di root repository.
5. Set environment variable:
@@ -28,9 +29,7 @@ DB_USERNAME=user_database
DB_PASSWORD=password_database
```
Root aplikasi menampilkan halaman pemilih progres dari `index.html`. Versi final berjalan di `/project_final`, jadi gunakan `APP_BASE_PATH=/project_final` untuk deploy Coolify ini.
`CORS_ALLOWED_ORIGIN` boleh dikosongkan untuk same-origin deployment. Isi dengan domain aplikasi jika API memang perlu diakses dari origin lain.
Aplikasi final berjalan di `/webgis_app`. Gunakan `APP_BASE_PATH=/webgis_app` untuk deploy.
## Akun Awal
@@ -39,23 +38,38 @@ Setelah import SQL:
- Admin: `admin / admin123`
- Pengguna: `pengguna / user123`
Ganti password akun demo setelah deploy jika aplikasi dibuka publik.
> Ganti password akun demo setelah deploy jika aplikasi dibuka publik.
## Struktur Penting
## Struktur Project
- `index.html` - halaman awal untuk memilih progres.
- `01/`, `02/`, `03/`, `final/` - progres pertemuan.
- `project_final/` - aplikasi final yang berjalan di `/project_final`.
- `database/webgis_db.sql` - struktur dan seed database lengkap.
- `Dockerfile` - image PHP Apache untuk Coolify.
- `.env.example` - contoh environment variable deploy.
```
D1041231071_NaufalZakyR_UAS_WebGISProject/
├── beranda.html ← Halaman pemilih progres
├── webgis_app/ ← Aplikasi final (main app)
│ ├── panel_admin/ ← Halaman & fitur admin
│ ├── panel_user/ ← Halaman & fitur pengguna
│ ├── endpoints/ ← API endpoint PHP
│ ├── core_config/ ← Konfigurasi (DB, session, auth)
│ └── public_assets/ ← CSS & JS (global, panel, map)
├── db_scripts/ ← SQL scripts database
│ ├── buat_tabel.sql
│ ├── data_awal_wilayah.sql
│ ├── data_awal_pengguna.sql
│ └── webgis_naufal_zaky.sql
├── version_01/ ← Progres pertemuan 1
├── version_02/ ← Progres pertemuan 2
├── version_03/ ← Progres pertemuan 3
├── legacy_version/ ← Versi lama (referensi)
├── Dockerfile ← Docker image untuk Coolify
└── .env ← Konfigurasi environment
```
## Local XAMPP
## Jalankan di XAMPP Lokal
Untuk menjalankan dari XAMPP seperti struktur lama, set base path:
Set konfigurasi di `.env`:
```env
APP_BASE_PATH=/project/project_final
APP_BASE_PATH=/D1041231071_NaufalZakyR_UAS_WebGISProject/webgis_app
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=webgis_db
@@ -63,4 +77,5 @@ DB_USERNAME=root
DB_PASSWORD=
```
Import `database/webgis_db.sql`, lalu buka `/project/project_final/login.php`.
Import `db_scripts/webgis_naufal_zaky.sql` ke phpMyAdmin, lalu buka:
`http://localhost/D1041231071_NaufalZakyR_UAS_WebGISProject/webgis_app/login.php`
+2 -2
View File
@@ -8,7 +8,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
:root {
/* Matching project_final/main.css */
/* Matching webgis_app/main.css */
--primary: #2563EB;
--primary-light: #3B82F6;
--primary-dark: #1D4ED8;
@@ -302,7 +302,7 @@
</a>
<!-- Final (UAS) -->
<a href="project_final/" class="card final" style="animation-delay: 0.25s;">
<a href="webgis_app/" class="card final" style="animation-delay: 0.25s;">
<div class="card-icon-wrap">
<i class="fas fa-laptop-code"></i>
</div>
-66
View File
@@ -1,66 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection();
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
sendError('Method not allowed', 405);
}
// Warga miskin yang berada di luar semua radius bantuan rumah ibadah.
$sql = "
SELECT
w.id,
w.nama_kk,
w.penghasilan,
ST_AsGeoJSON(w.geom) AS geojson,
ri.nama AS rumah_ibadah_terdekat,
ST_Distance_Sphere(ST_SRID(w.geom, 4326), ST_SRID(ri.geom, 4326)) AS jarak_meter,
ri.radius_bantuan_meter,
(ST_Distance_Sphere(ST_SRID(w.geom, 4326), ST_SRID(ri.geom, 4326)) - ri.radius_bantuan_meter) AS selisih_meter
FROM warga_miskin w
JOIN rumah_ibadah ri
JOIN (
SELECT
nearest_distance.warga_id,
MIN(nearest_distance.jarak_meter) AS min_jarak_meter
FROM (
SELECT
w2.id AS warga_id,
ST_Distance_Sphere(ST_SRID(w2.geom, 4326), ST_SRID(ri2.geom, 4326)) AS jarak_meter
FROM warga_miskin w2
CROSS JOIN rumah_ibadah ri2
) nearest_distance
GROUP BY nearest_distance.warga_id
) nearest
ON nearest.warga_id = w.id
AND ABS(ST_Distance_Sphere(ST_SRID(w.geom, 4326), ST_SRID(ri.geom, 4326)) - nearest.min_jarak_meter) < 0.001
WHERE NOT EXISTS (
SELECT 1
FROM rumah_ibadah ri_in_radius
WHERE ST_Distance_Sphere(ST_SRID(w.geom, 4326), ST_SRID(ri_in_radius.geom, 4326)) <= ri_in_radius.radius_bantuan_meter
)
GROUP BY w.id
ORDER BY selisih_meter DESC
";
$stmt = $pdo->query($sql);
$features = [];
while ($r = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($r['geojson']),
'properties' => [
'id' => (int)$r['id'],
'nama_kk' => $r['nama_kk'],
'jarak_km' => round(((float)$r['jarak_meter']) / 1000, 2),
'radius_km' => round(((int)$r['radius_bantuan_meter']) / 1000, 2),
'selisih_km' => round(((float)$r['selisih_meter']) / 1000, 2),
'rumah_ibadah_terdekat' => $r['rumah_ibadah_terdekat']
]
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Blank Spot');
-14
View File
@@ -1,14 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
try {
$pdo = Database::getConnection();
$stmt = $pdo->query("SELECT id, ST_SRID(geom) as srid FROM warga_miskin LIMIT 5");
echo "warga_miskin SRIDs: \n";
while ($row = $stmt->fetch()) echo $row['id'] . " => " . $row['srid'] . "\n";
$stmt = $pdo->query("SELECT id, ST_SRID(geom) as srid FROM rumah_ibadah LIMIT 5");
echo "rumah_ibadah SRIDs: \n";
while ($row = $stmt->fetch()) echo $row['id'] . " => " . $row['srid'] . "\n";
} catch (Exception $e) {
echo "ERROR: " . $e->getMessage();
}
-12
View File
@@ -1,12 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
try {
$pdo = Database::getConnection();
$tables = ['spbu', 'rumah_ibadah', 'jalan', 'kavling', 'kawasan_kumuh', 'warga_miskin'];
foreach ($tables as $t) {
$pdo->exec("UPDATE $t SET geom = ST_SRID(geom, 4326) WHERE ST_SRID(geom) != 4326");
}
echo "SRID FIXED";
} catch (Exception $e) {
echo "ERROR: " . $e->getMessage();
}
-52
View File
@@ -1,52 +0,0 @@
<?php
require_once __DIR__ . '/../config/session.php';
/**
* Shared response helper for all API endpoints
*/
header('Content-Type: application/json');
$allowedOrigin = getenv('CORS_ALLOWED_ORIGIN');
if ($allowedOrigin !== false && $allowedOrigin !== '') {
header('Access-Control-Allow-Origin: ' . $allowedOrigin);
header('Vary: Origin');
header('Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
function sendSuccess($data, string $message = 'OK', int $code = 200): void {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit;
}
function sendError(string $message = 'Bad Request', int $code = 400): void {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message, 'data' => null]);
exit;
}
function getInput(): array {
return json_decode(file_get_contents('php://input'), true) ?? [];
}
function requireApiRole(string $role): void {
if (session_status() === PHP_SESSION_NONE) {
startAppSession();
}
if (!isset($_SESSION['user_id'], $_SESSION['role'])) {
sendError('Unauthorized', 401);
}
if ($_SESSION['role'] !== $role) {
sendError('Forbidden', 403);
}
}
function requireAdminForMutation(): void {
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
requireApiRole('admin');
}
}
-22
View File
@@ -1,22 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection(); $method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id,nama,jenis_jalan,created_at,ST_AsGeoJSON(geom) as geojson FROM jalan ORDER BY id DESC");
$features=[];
while($r=$stmt->fetch()) $features[]=['type'=>'Feature','geometry'=>json_decode($r['geojson']),'properties'=>['id'=>$r['id'],'nama'=>$r['nama'],'jenis_jalan'=>$r['jenis_jalan'],'created_at'=>$r['created_at']]];
sendSuccess(['type'=>'FeatureCollection','features'=>$features],'Data Jalan'); break;
case 'POST':
$d=getInput(); if(empty($d['nama'])||empty($d['geometry'])) sendError('Nama & geometri wajib');
$pdo->prepare("INSERT INTO jalan(nama,jenis_jalan,geom) VALUES(?,?,ST_GeomFromGeoJSON(?))")->execute([$d['nama'],$d['jenis_jalan']??'Lokal',json_encode($d['geometry'])]);
sendSuccess(['id'=>$pdo->lastInsertId()],'Jalan disimpan',201); break;
case 'PUT':
$d=getInput(); $id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("UPDATE jalan SET nama=?,jenis_jalan=? WHERE id=?")->execute([$d['nama'],$d['jenis_jalan'],$id]);
sendSuccess(null,'Jalan diperbarui'); break;
case 'DELETE':
$id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("DELETE FROM jalan WHERE id=?")->execute([$id]); sendSuccess(null,'Jalan dihapus'); break;
default: sendError('Method not allowed',405);
}
-26
View File
@@ -1,26 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection(); $method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id,nama_pemilik,status_kepemilikan,luas,created_at,ST_AsGeoJSON(geom) as geojson FROM kavling ORDER BY id DESC");
$features=[];
while($r=$stmt->fetch()) $features[]=['type'=>'Feature','geometry'=>json_decode($r['geojson']),'properties'=>['id'=>$r['id'],'nama_pemilik'=>$r['nama_pemilik'],'status_kepemilikan'=>$r['status_kepemilikan'],'luas'=>$r['luas'],'created_at'=>$r['created_at']]];
sendSuccess(['type'=>'FeatureCollection','features'=>$features],'Data Kavling'); break;
case 'POST':
$d=getInput(); if(empty($d['nama_pemilik'])||empty($d['status_kepemilikan'])||empty($d['geometry'])) sendError('Data tidak lengkap');
$pdo->prepare("INSERT INTO kavling(nama_pemilik,status_kepemilikan,luas,geom) VALUES(?,?,?,ST_GeomFromGeoJSON(?))")->execute([$d['nama_pemilik'],$d['status_kepemilikan'],$d['luas']??0,json_encode($d['geometry'])]);
sendSuccess(['id'=>$pdo->lastInsertId()],'Kavling disimpan',201); break;
case 'PUT':
$d=getInput(); $id=$_GET['id']??null; if(!$id) sendError('ID wajib');
if(!empty($d['geometry'])){
$pdo->prepare("UPDATE kavling SET geom=ST_GeomFromGeoJSON(?) WHERE id=?")->execute([json_encode($d['geometry']),$id]);
} else {
$pdo->prepare("UPDATE kavling SET nama_pemilik=?,status_kepemilikan=?,luas=? WHERE id=?")->execute([$d['nama_pemilik'],$d['status_kepemilikan'],$d['luas']??0,$id]);
}
sendSuccess(null,'Kavling diperbarui'); break;
case 'DELETE':
$id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("DELETE FROM kavling WHERE id=?")->execute([$id]); sendSuccess(null,'Kavling dihapus'); break;
default: sendError('Method not allowed',405);
}
-22
View File
@@ -1,22 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection(); $method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id,nama_kawasan,created_at,ST_AsGeoJSON(geom) as geojson FROM kawasan_kumuh ORDER BY id DESC");
$features=[];
while($r=$stmt->fetch()) $features[]=['type'=>'Feature','geometry'=>json_decode($r['geojson']),'properties'=>['id'=>$r['id'],'nama_kawasan'=>$r['nama_kawasan'],'created_at'=>$r['created_at']]];
sendSuccess(['type'=>'FeatureCollection','features'=>$features],'Data Kawasan Kumuh'); break;
case 'POST':
$d=getInput(); if(empty($d['nama_kawasan'])||empty($d['geometry'])) sendError('Data tidak lengkap');
$pdo->prepare("INSERT INTO kawasan_kumuh(nama_kawasan,geom) VALUES(?,ST_GeomFromGeoJSON(?))")->execute([$d['nama_kawasan'],json_encode($d['geometry'])]);
sendSuccess(['id'=>$pdo->lastInsertId()],'Kawasan disimpan',201); break;
case 'PUT':
$d=getInput(); $id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("UPDATE kawasan_kumuh SET nama_kawasan=? WHERE id=?")->execute([$d['nama_kawasan'],$id]);
sendSuccess(null,'Kawasan diperbarui'); break;
case 'DELETE':
$id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("DELETE FROM kawasan_kumuh WHERE id=?")->execute([$id]); sendSuccess(null,'Kawasan dihapus'); break;
default: sendError('Method not allowed',405);
}
-104
View File
@@ -1,104 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/session.php';
startAppSession();
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit;
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
try {
if ($method === 'GET') {
// Jika parameter my_reports=1, hanya laporan milik user yang login
// Jika tidak dan role=admin, ambil semua.
$my_reports = $_GET['my_reports'] ?? 0;
$sql = "SELECT l.*, ST_X(l.geometry) as lng, ST_Y(l.geometry) as lat, u.nama_lengkap, u.username
FROM laporan_warga l
JOIN users u ON l.user_id = u.id ";
if ($my_reports || $_SESSION['role'] !== 'admin') {
// User biasa hanya boleh melihat laporannya sendiri di tabel dashboard,
// tapi mungkin butuh lihat semua laporan di peta publik?
// Untuk peta public (semua laporan disetujui atau diproses), tambahkan parameter public=1
if (isset($_GET['public']) && $_GET['public'] == 1) {
$sql .= " WHERE l.status IN ('diproses', 'selesai') ";
} else {
$sql .= " WHERE l.user_id = " . (int)$_SESSION['user_id'];
}
}
$sql .= " ORDER BY l.id DESC";
$rows = $pdo->query($sql)->fetchAll();
$features = [];
foreach ($rows as $row) {
$features[] = [
'type' => 'Feature',
'properties' => [
'id' => $row['id'],
'user_id' => $row['user_id'],
'pelapor' => $row['nama_lengkap'] ?: $row['username'],
'kategori' => $row['kategori'],
'deskripsi' => $row['deskripsi'],
'status' => $row['status'],
'created_at' => $row['created_at']
],
'geometry' => [
'type' => 'Point',
'coordinates' => [(float)$row['lng'], (float)$row['lat']]
]
];
}
echo json_encode([
'type' => 'FeatureCollection',
'features' => $features
]);
}
elseif ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['kategori'], $input['deskripsi'], $input['geometry'])) {
throw new Exception("Data tidak lengkap.");
}
$coords = $input['geometry']['coordinates'];
$stmt = $pdo->prepare("INSERT INTO laporan_warga (user_id, kategori, deskripsi, geometry) VALUES (?, ?, ?, ST_GeomFromText(?))");
$stmt->execute([
$_SESSION['user_id'],
$input['kategori'],
$input['deskripsi'],
"POINT({$coords[0]} {$coords[1]})"
]);
echo json_encode(['status' => 'success', 'message' => 'Laporan berhasil dibuat.']);
}
elseif ($method === 'PUT') {
if ($_SESSION['role'] !== 'admin') throw new Exception("Unauthorized");
$id = $_GET['id'] ?? 0;
$input = json_decode(file_get_contents('php://input'), true);
$status = $input['status'] ?? 'menunggu';
$stmt = $pdo->prepare("UPDATE laporan_warga SET status = ? WHERE id = ?");
$stmt->execute([$status, $id]);
echo json_encode(['status' => 'success', 'message' => 'Status laporan diperbarui.']);
}
elseif ($method === 'DELETE') {
$id = $_GET['id'] ?? 0;
// User bisa hapus kalau status menunggu. Admin bisa hapus semua.
if ($_SESSION['role'] === 'admin') {
$stmt = $pdo->prepare("DELETE FROM laporan_warga WHERE id = ?");
$stmt->execute([$id]);
} else {
$stmt = $pdo->prepare("DELETE FROM laporan_warga WHERE id = ? AND user_id = ? AND status = 'menunggu'");
$stmt->execute([$id, $_SESSION['user_id']]);
}
echo json_encode(['status' => 'success', 'message' => 'Laporan dihapus.']);
}
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
-102
View File
@@ -1,102 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
function normalizeRadius($value): int {
$radius = (int)($value ?? 1000);
if ($radius < 100) return 100;
if ($radius > 10000) return 10000;
return $radius;
}
switch ($method) {
case 'GET':
$stmt = $pdo->query("
SELECT
r.id,
r.nama,
r.agama,
r.radius_bantuan_meter,
r.created_at,
ST_AsGeoJSON(r.geom) AS geojson,
IFNULL(AVG(u.rating), 0) AS avg_rating,
COUNT(u.id) AS total_ulasan
FROM rumah_ibadah r
LEFT JOIN ulasan_fasilitas u
ON u.fasilitas_id = r.id
AND u.fasilitas_tipe = 'rumah_ibadah'
GROUP BY r.id
ORDER BY r.id DESC
");
$features = [];
while ($r = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($r['geojson']),
'properties' => [
'id' => (int)$r['id'],
'nama' => $r['nama'],
'agama' => $r['agama'],
'radius_bantuan_meter' => (int)$r['radius_bantuan_meter'],
'radius_bantuan_km' => round(((int)$r['radius_bantuan_meter']) / 1000, 2),
'created_at' => $r['created_at'],
'avg_rating' => round((float)$r['avg_rating'], 1),
'total_ulasan' => (int)$r['total_ulasan']
]
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Rumah Ibadah');
break;
case 'POST':
$d = getInput();
if (empty($d['nama']) || empty($d['agama']) || empty($d['geometry'])) {
sendError('Data tidak lengkap');
}
$radius = normalizeRadius($d['radius_bantuan_meter'] ?? null);
$pdo->prepare("
INSERT INTO rumah_ibadah (nama, agama, radius_bantuan_meter, geom)
VALUES (?, ?, ?, ST_GeomFromGeoJSON(?))
")->execute([$d['nama'], $d['agama'], $radius, json_encode($d['geometry'])]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Rumah Ibadah disimpan', 201);
break;
case 'PUT':
$d = getInput();
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID wajib');
if (!empty($d['geometry'])) {
$pdo->prepare("UPDATE rumah_ibadah SET geom = ST_GeomFromGeoJSON(?) WHERE id = ?")
->execute([json_encode($d['geometry']), $id]);
} else {
$radius = normalizeRadius($d['radius_bantuan_meter'] ?? null);
$pdo->prepare("
UPDATE rumah_ibadah
SET nama = ?, agama = ?, radius_bantuan_meter = ?
WHERE id = ?
")->execute([$d['nama'], $d['agama'], $radius, $id]);
}
sendSuccess(null, 'Rumah Ibadah diperbarui');
break;
case 'DELETE':
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID wajib');
$pdo->prepare("DELETE FROM rumah_ibadah WHERE id = ?")->execute([$id]);
sendSuccess(null, 'Rumah Ibadah dihapus');
break;
default:
sendError('Method not allowed', 405);
}
-57
View File
@@ -1,57 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT s.id, s.nama, s.deskripsi, s.buka_24_jam, s.created_at, ST_AsGeoJSON(s.geom) as geojson,
IFNULL(AVG(u.rating), 0) as avg_rating, COUNT(u.id) as total_ulasan
FROM spbu s
LEFT JOIN ulasan_fasilitas u ON u.fasilitas_id = s.id AND u.fasilitas_tipe = 'spbu'
GROUP BY s.id
ORDER BY s.id DESC");
$features = [];
while ($r = $stmt->fetch()) {
$features[] = [
'type'=>'Feature',
'geometry'=>json_decode($r['geojson']),
'properties'=>[
'id'=>$r['id'],
'nama'=>$r['nama'],
'deskripsi'=>$r['deskripsi'],
'buka_24_jam'=>(int)$r['buka_24_jam'],
'created_at'=>$r['created_at'],
'avg_rating'=>round($r['avg_rating'], 1),
'total_ulasan'=>$r['total_ulasan']
]
];
}
sendSuccess(['type'=>'FeatureCollection','features'=>$features], 'Data SPBU');
break;
case 'POST':
$d = getInput();
if (empty($d['nama']) || empty($d['geometry'])) sendError('Nama dan geometri wajib diisi');
$stmt = $pdo->prepare("INSERT INTO spbu (nama, deskripsi, buka_24_jam, geom) VALUES (?,?,?,ST_GeomFromGeoJSON(?))");
$stmt->execute([$d['nama'], $d['deskripsi']??'', (int)($d['buka_24_jam']??0), json_encode($d['geometry'])]);
sendSuccess(['id'=>$pdo->lastInsertId()], 'SPBU disimpan', 201);
break;
case 'PUT':
$d = getInput(); $id = $_GET['id'] ?? null;
if (!$id) sendError('ID wajib disertakan');
if (!empty($d['geometry'])) {
$stmt = $pdo->prepare("UPDATE spbu SET geom=ST_GeomFromGeoJSON(?) WHERE id=?");
$stmt->execute([json_encode($d['geometry']), $id]);
} else {
$stmt = $pdo->prepare("UPDATE spbu SET nama=?, deskripsi=?, buka_24_jam=? WHERE id=?");
$stmt->execute([$d['nama'], $d['deskripsi']??'', (int)($d['buka_24_jam']??0), $id]);
}
sendSuccess(null, 'SPBU diperbarui');
break;
case 'DELETE':
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID wajib disertakan');
$pdo->prepare("DELETE FROM spbu WHERE id=?")->execute([$id]);
sendSuccess(null, 'SPBU dihapus');
break;
default: sendError('Method not allowed', 405);
}
-29
View File
@@ -1,29 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/helpers.php';
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
sendError('Method not allowed', 405);
}
$pdo = Database::getConnection();
date_default_timezone_set('Asia/Jakarta');
$row = $pdo->query("
SELECT
COUNT(*) AS total_spbu,
SUM(CASE WHEN buka_24_jam = 1 THEN 1 ELSE 0 END) AS spbu_buka
FROM spbu
")->fetch();
$total = (int)($row['total_spbu'] ?? 0);
$open = (int)($row['spbu_buka'] ?? 0);
sendSuccess([
'total_spbu' => $total,
'spbu_buka' => $open,
'spbu_tutup' => max(0, $total - $open),
'basis_perhitungan' => 'buka_24_jam',
'server_time' => date('Y-m-d H:i:s'),
'timezone' => date_default_timezone_get()
], 'Status SPBU');
-13
View File
@@ -1,13 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection();
if ($_SERVER['REQUEST_METHOD'] !== 'GET') sendError('Method not allowed', 405);
$lat = (float)($_GET['lat'] ?? 0); $lng = (float)($_GET['lng'] ?? 0);
if (!$lat || !$lng) sendError('lat dan lng wajib diisi');
$wkt = sprintf('POINT(%F %F)', $lng, $lat);
$stmt = $pdo->prepare("SELECT id, nama, buka_24_jam, ST_AsGeoJSON(geom) as geojson,
ST_Distance_Sphere(ST_SRID(geom, 4326), ST_SRID(ST_GeomFromText(?), 4326)) / 1000 as jarak_km
FROM spbu ORDER BY jarak_km ASC LIMIT 1");
$stmt->execute([$wkt]);
$r = $stmt->fetch();
if (!$r) sendError('Tidak ada SPBU di database', 404);
sendSuccess(['type'=>'Feature','geometry'=>json_decode($r['geojson']),'properties'=>['id'=>$r['id'],'nama'=>$r['nama'],'buka_24_jam'=>(int)$r['buka_24_jam'],'jarak_km'=>round($r['jarak_km'],2)]], 'SPBU terdekat');
-53
View File
@@ -1,53 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection();
if ($_SERVER['REQUEST_METHOD'] !== 'GET') sendError('Method not allowed', 405);
// Summary counts
$counts = $pdo->query("SELECT
(SELECT COUNT(*) FROM spbu) as total_spbu,
(SELECT COUNT(*) FROM jalan) as total_jalan,
(SELECT COUNT(*) FROM kavling) as total_kavling,
(SELECT COUNT(*) FROM rumah_ibadah) as total_rumah_ibadah,
(SELECT COUNT(*) FROM warga_miskin) as total_warga_miskin,
(SELECT COUNT(*) FROM kawasan_kumuh) as total_kawasan_kumuh,
(SELECT COUNT(*) FROM spbu WHERE buka_24_jam=1) as spbu_24jam
")->fetch();
// Choropleth: kawasan kumuh + jumlah warga di dalamnya
$sql = "SELECT k.id, k.nama_kawasan, ST_AsGeoJSON(k.geom) as geojson,
COUNT(w.id) as jumlah_warga,
COALESCE(SUM(w.jumlah_tanggungan),0) as total_tanggungan
FROM kawasan_kumuh k
LEFT JOIN warga_miskin w ON ST_Contains(k.geom, w.geom)
GROUP BY k.id";
$stmt = $pdo->query($sql);
$features = [];
$merahCount = 0;
while ($r = $stmt->fetch()) {
$jumlah = (int)$r['jumlah_warga'];
if ($jumlah > 3) $merahCount++;
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($r['geojson']),
'properties' => [
'id' => $r['id'],
'nama_kawasan' => $r['nama_kawasan'],
'jumlah_warga' => $jumlah,
'total_tanggungan' => (int)$r['total_tanggungan'],
]
];
}
// Rumah ibadah by agama
$byAgama = $pdo->query("SELECT agama, COUNT(*) as total FROM rumah_ibadah GROUP BY agama")->fetchAll();
// Kavling by status
$byStatus = $pdo->query("SELECT status_kepemilikan, COUNT(*) as total FROM kavling GROUP BY status_kepemilikan")->fetchAll();
sendSuccess([
'counts' => $counts,
'kawasan_merah' => $merahCount,
'choropleth' => ['type' => 'FeatureCollection', 'features' => $features],
'rumah_by_agama' => $byAgama,
'kavling_by_status' => $byStatus,
], 'Statistik OK');
-50
View File
@@ -1,50 +0,0 @@
<?php
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/session.php';
startAppSession();
header('Content-Type: application/json');
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'user') {
http_response_code(401);
echo json_encode(['status' => 'error', 'message' => 'Hanya pengguna terdaftar yang dapat memberikan ulasan']);
exit;
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
try {
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['fasilitas_tipe'], $input['fasilitas_id'], $input['rating'])) {
throw new Exception("Data ulasan tidak lengkap.");
}
$tipe = $input['fasilitas_tipe'];
if (!in_array($tipe, ['spbu', 'rumah_ibadah'])) throw new Exception("Tipe fasilitas tidak valid");
$fasilitas_id = (int)$input['fasilitas_id'];
$rating = (int)$input['rating'];
$komentar = trim($input['komentar'] ?? '');
if ($rating < 1 || $rating > 5) throw new Exception("Rating harus antara 1 sampai 5");
// Cek apakah sudah pernah mengulas
$check = $pdo->prepare("SELECT id FROM ulasan_fasilitas WHERE user_id = ? AND fasilitas_tipe = ? AND fasilitas_id = ?");
$check->execute([$_SESSION['user_id'], $tipe, $fasilitas_id]);
if ($check->fetch()) {
throw new Exception("Anda sudah memberikan ulasan untuk fasilitas ini.");
}
$stmt = $pdo->prepare("INSERT INTO ulasan_fasilitas (user_id, fasilitas_tipe, fasilitas_id, rating, komentar) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$_SESSION['user_id'], $tipe, $fasilitas_id, $rating, $komentar]);
echo json_encode(['status' => 'success', 'message' => 'Ulasan berhasil disimpan! Terima kasih atas partisipasi Anda.']);
} else {
throw new Exception("Method not allowed");
}
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
-31
View File
@@ -1,31 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection(); $method = $_SERVER['REQUEST_METHOD'];
requireApiRole('admin');
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id,username,role,nama_lengkap,created_at FROM users ORDER BY id ASC");
sendSuccess($stmt->fetchAll(), 'Data Users'); break;
case 'POST':
$d=getInput();
if(empty($d['username'])||empty($d['password'])||empty($d['role'])) sendError('Data tidak lengkap');
$hash = password_hash($d['password'], PASSWORD_BCRYPT);
try {
$pdo->prepare("INSERT INTO users(username,password,role,nama_lengkap) VALUES(?,?,?,?)")->execute([$d['username'],$hash,$d['role'],$d['nama_lengkap']??'']);
sendSuccess(['id'=>$pdo->lastInsertId()],'User disimpan',201);
} catch(Exception $e) { sendError('Username sudah digunakan'); }
break;
case 'PUT':
$d=getInput(); $id=$_GET['id']??null; if(!$id) sendError('ID wajib');
if(!empty($d['password'])) {
$hash=password_hash($d['password'],PASSWORD_BCRYPT);
$pdo->prepare("UPDATE users SET username=?,password=?,role=?,nama_lengkap=? WHERE id=?")->execute([$d['username'],$hash,$d['role'],$d['nama_lengkap']??'',$id]);
} else {
$pdo->prepare("UPDATE users SET username=?,role=?,nama_lengkap=? WHERE id=?")->execute([$d['username'],$d['role'],$d['nama_lengkap']??'',$id]);
}
sendSuccess(null,'User diperbarui'); break;
case 'DELETE':
$id=$_GET['id']??null; if(!$id) sendError('ID wajib');
if($id==1) sendError('Admin utama tidak dapat dihapus');
$pdo->prepare("DELETE FROM users WHERE id=?")->execute([$id]); sendSuccess(null,'User dihapus'); break;
default: sendError('Method not allowed',405);
}
-26
View File
@@ -1,26 +0,0 @@
<?php require_once __DIR__ . '/../config/db.php'; require_once __DIR__ . '/helpers.php';
$pdo = Database::getConnection(); $method = $_SERVER['REQUEST_METHOD'];
requireAdminForMutation();
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id,nama_kk,penghasilan,jumlah_tanggungan,created_at,ST_AsGeoJSON(geom) as geojson FROM warga_miskin ORDER BY id DESC");
$features=[];
while($r=$stmt->fetch()) $features[]=['type'=>'Feature','geometry'=>json_decode($r['geojson']),'properties'=>['id'=>$r['id'],'nama_kk'=>$r['nama_kk'],'penghasilan'=>$r['penghasilan'],'jumlah_tanggungan'=>$r['jumlah_tanggungan'],'created_at'=>$r['created_at']]];
sendSuccess(['type'=>'FeatureCollection','features'=>$features],'Data Warga Miskin'); break;
case 'POST':
$d=getInput(); if(empty($d['nama_kk'])||empty($d['geometry'])) sendError('Data tidak lengkap');
$pdo->prepare("INSERT INTO warga_miskin(nama_kk,penghasilan,jumlah_tanggungan,geom) VALUES(?,?,?,ST_GeomFromGeoJSON(?))")->execute([$d['nama_kk'],$d['penghasilan']??0,$d['jumlah_tanggungan']??0,json_encode($d['geometry'])]);
sendSuccess(['id'=>$pdo->lastInsertId()],'Warga disimpan',201); break;
case 'PUT':
$d=getInput(); $id=$_GET['id']??null; if(!$id) sendError('ID wajib');
if(!empty($d['geometry'])){
$pdo->prepare("UPDATE warga_miskin SET geom=ST_GeomFromGeoJSON(?) WHERE id=?")->execute([json_encode($d['geometry']),$id]);
} else {
$pdo->prepare("UPDATE warga_miskin SET nama_kk=?,penghasilan=?,jumlah_tanggungan=? WHERE id=?")->execute([$d['nama_kk'],$d['penghasilan']??0,$d['jumlah_tanggungan']??0,$id]);
}
sendSuccess(null,'Warga diperbarui'); break;
case 'DELETE':
$id=$_GET['id']??null; if(!$id) sendError('ID wajib');
$pdo->prepare("DELETE FROM warga_miskin WHERE id=?")->execute([$id]); sendSuccess(null,'Warga dihapus'); break;
default: sendError('Method not allowed',405);
}
-13
View File
@@ -1,13 +0,0 @@
<?php
require_once __DIR__ . '/config/session.php';
startAppSession();
if (isset($_SESSION['user_id'])) {
if ($_SESSION['role'] === 'admin') {
header('Location: admin/index.php');
} else {
header('Location: user/index.php');
}
exit;
}
header('Location: login.php');
exit;
@@ -1,10 +1,10 @@
<?php
<?php
/**
* jalan.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Jalan (LineString).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
@@ -1,10 +1,10 @@
<?php
<?php
/**
* kavling.php
* Tanggung Jawab: Menangani operasi CRUD untuk entitas Kavling (Polygon).
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
@@ -1,10 +1,10 @@
<?php
<?php
/**
* rumah_ibadah.php
* Tanggung Jawab: Operasi CRUD Rumah Ibadah + Endpoint analisis jangkauan Haversine.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
require_once '../utils/geo_helper.php';
@@ -1,5 +1,5 @@
<?php
require_once '../config/db.php';
<?php
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
@@ -1,11 +1,11 @@
<?php
<?php
/**
* statistik.php
* Tanggung Jawab: Menyediakan data analisis spasial menggunakan Point in Polygon (ST_Contains).
* Menghitung kepadatan warga miskin dalam setiap kavling.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
@@ -1,10 +1,10 @@
<?php
<?php
/**
* warga_miskin.php
* Tanggung Jawab: Operasi CRUD untuk Warga Miskin.
*/
require_once '../config/db.php';
require_once '../core_config/database.php';
require_once '../utils/response_helper.php';
$pdo = Database::getConnection();
@@ -1,4 +1,12 @@
export const BASE_URL = '';
const getBaseUrl = () => {
const segments = window.location.pathname.split('/');
const targetIndex = segments.findIndex(seg => ['01', '02', '03', 'versi'].includes(seg));
if (targetIndex !== -1) {
return segments.slice(0, targetIndex).join('/');
}
return '';
};
export const BASE_URL = getBaseUrl();
const createService = (endpoint) => ({
getAll: async () => {
+13
View File
@@ -0,0 +1,13 @@
RewriteEngine On
# Specific file mappings from old API names to new refactored endpoint names
RewriteRule ^api/kawasan_kumuh\.php$ endpoints/api_kawasan.php [L,QSA]
RewriteRule ^api/rumah_ibadah\.php$ endpoints/api_fasilitas.php [L,QSA]
RewriteRule ^api/users\.php$ endpoints/api_pengguna.php [L,QSA]
RewriteRule ^api/spbu_terdekat\.php$ endpoints/api_spbu_nearby.php [L,QSA]
RewriteRule ^api/blank_spot\.php$ endpoints/area_blank.php [L,QSA]
RewriteRule ^api/check_srid\.php$ endpoints/cek_srid.php [L,QSA]
RewriteRule ^api/fix_srid\.php$ endpoints/perbaiki_srid.php [L,QSA]
# General pattern mapping: api/filename.php -> endpoints/api_filename.php
RewriteRule ^api/([a-zA-Z0-9_]+)\.php$ endpoints/api_$1.php [L,QSA]
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/area_blank.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/cek_srid.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/perbaiki_srid.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_jalan.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_kavling.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_kawasan.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_laporan.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_fasilitas.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_spbu.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_spbu_status.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_spbu_nearby.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_statistik.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_ulasan.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_pengguna.php';
+2
View File
@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../endpoints/api_warga_miskin.php';
@@ -1,10 +1,11 @@
<?php
<?php
/**
* Singleton PDO connection.
*
* Coolify: set DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, and DB_PASSWORD
* from the database service credentials.
*/
require_once __DIR__ . '/env_loader.php';
class Database {
private static $conn = null;
@@ -45,7 +46,7 @@ class Database {
// Auto-initialize database if empty
$stmt = self::$conn->query("SHOW TABLES LIKE 'users'");
if ($stmt->rowCount() == 0) {
$sqlFile = __DIR__ . '/../../database/webgis_db.sql';
$sqlFile = __DIR__ . '/../../db_scripts/webgis_naufal_zaky.sql';
if (file_exists($sqlFile)) {
$sql = file_get_contents($sqlFile);
self::$conn->exec($sql);
+56
View File
@@ -0,0 +1,56 @@
<?php
/**
* Simple .env loader.
* Reads the .env file from the repository root and sets environment variables
* via putenv() so getenv() calls throughout the app work correctly.
* Only loads once per request.
*/
function loadEnv(): void {
static $loaded = false;
if ($loaded) return;
// Walk up from webgis_app/config to the repository root
$envFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . '.env';
if (!file_exists($envFile)) {
$loaded = true;
return;
}
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!empty($lines)) {
if (substr($lines[0], 0, 3) === "\xEF\xBB\xBF") {
$lines[0] = substr($lines[0], 3);
}
}
foreach ($lines as $line) {
$line = trim($line);
// Skip comments
if ($line === '' || $line[0] === '#') continue;
if (strpos($line, '=') === false) continue;
[$key, $value] = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// Remove surrounding quotes if present
if (strlen($value) >= 2 &&
(($value[0] === '"' && $value[-1] === '"') ||
($value[0] === "'" && $value[-1] === "'"))) {
$value = substr($value, 1, -1);
}
// Only set if not already defined in the environment (server env wins)
if (getenv($key) === false) {
putenv("{$key}={$value}");
$_ENV[$key] = $value;
$_SERVER[$key] = $value;
}
}
$loaded = true;
}
// Auto-load on include
loadEnv();
@@ -1,10 +1,10 @@
<?php
/**
* auth_check.php Middleware: pastikan user sudah login dan role sesuai.
* Usage: require_once __DIR__ . '/../config/auth_check.php';
* Usage: require_once __DIR__ . '/../core_config/middleware_auth.php';
* requireRole('admin'); // atau 'user'
*/
require_once __DIR__ . '/session.php';
require_once __DIR__ . '/session_mgr.php';
if (session_status() === PHP_SESSION_NONE) {
startAppSession();
@@ -27,9 +27,9 @@ function requireRole(string $role, ?string $redirect = null): void {
if ($_SESSION['role'] !== $role) {
// Redirect ke dashboard yang sesuai role-nya
if ($_SESSION['role'] === 'admin') {
header('Location: ' . app_url('admin/index.php'));
header('Location: ' . app_url('panel_admin/index.php'));
} else {
header('Location: ' . app_url('user/index.php'));
header('Location: ' . app_url('panel_user/index.php'));
}
exit;
}
@@ -43,3 +43,24 @@ function currentUser(): array {
'nama_lengkap'=> $_SESSION['nama_lengkap']?? '',
];
}
/**
* API-safe auth helpers: return JSON 401/403 instead of redirecting.
* Use these in endpoints called via fetch/AJAX.
*/
function requireApiLogin(): void {
if (!isLoggedIn()) {
http_response_code(401);
echo json_encode(['status' => 'error', 'message' => 'Autentikasi diperlukan']);
exit;
}
}
function requireApiRole(string $role): void {
requireApiLogin();
if ($_SESSION['role'] !== $role) {
http_response_code(403);
echo json_encode(['status' => 'error', 'message' => 'Akses ditolak: role tidak sesuai']);
exit;
}
}
@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/app.php';
require_once __DIR__ . '/settings.php';
function startAppSession(): void {
if (session_status() === PHP_SESSION_ACTIVE) {
@@ -2,13 +2,14 @@
/**
* Application path helpers.
*
* Set APP_BASE_PATH to the subdirectory that serves project_final.
* In this repository's Coolify image the final app runs at /project_final.
* Set APP_BASE_PATH to the subdirectory that serves webgis_app.
* In this repository's Coolify image the final app runs at /webgis_app.
*/
require_once __DIR__ . '/env_loader.php';
function app_base_path(): string {
$basePath = getenv('APP_BASE_PATH');
if ($basePath === false) {
$basePath = '/project/project_final';
$basePath = '/project/webgis_app';
}
$basePath = trim($basePath);
@@ -20,6 +21,9 @@ function app_base_path(): string {
}
function app_url(string $path = ''): string {
if ($path === '') {
return app_base_path();
}
$path = '/' . ltrim($path, '/');
return app_base_path() . $path;
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* api_fasilitas.php
* Endpoint: CRUD untuk layer Rumah Ibadah (Point).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama, agama, radius_bantuan_meter, created_at,
ST_AsGeoJSON(geom) AS geojson FROM rumah_ibadah"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama' => $row['nama'],
'agama' => $row['agama'],
'radius_bantuan_meter' => $row['radius_bantuan_meter'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Rumah Ibadah berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Rumah Ibadah: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama']) || !isset($input['agama']) || !isset($input['geometry'])) {
sendError('Nama, agama, dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO rumah_ibadah (nama, agama, radius_bantuan_meter, geom)
VALUES (:nama, :agama, :radius_bantuan_meter, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama' => $input['nama'],
':agama' => $input['agama'],
':radius_bantuan_meter' => $input['radius_bantuan_meter'] ?? 1000,
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Rumah Ibadah berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Rumah Ibadah: ' . $e->getMessage(), 500);
}
break;
case 'PUT':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Rumah Ibadah wajib disertakan');
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) sendError('Data tidak valid');
try {
$stmt = $pdo->prepare(
"UPDATE rumah_ibadah SET nama = :nama, agama = :agama, radius_bantuan_meter = :radius_bantuan_meter WHERE id = :id"
);
$stmt->execute([
':nama' => $input['nama'],
':agama' => $input['agama'],
':radius_bantuan_meter' => $input['radius_bantuan_meter'] ?? 1000,
':id' => $id,
]);
sendSuccess(null, 'Data Rumah Ibadah berhasil diperbarui');
} catch (PDOException $e) {
sendError('Gagal memperbarui Rumah Ibadah: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Rumah Ibadah wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM rumah_ibadah WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Rumah Ibadah berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Rumah Ibadah: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* api_jalan.php
* Endpoint: CRUD untuk layer Jalan (LineString).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama, jenis_jalan, created_at, ST_AsGeoJSON(geom) AS geojson FROM jalan"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama' => $row['nama'],
'jenis_jalan' => $row['jenis_jalan'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Jalan berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Jalan: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama']) || !isset($input['geometry'])) {
sendError('Nama dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO jalan (nama, jenis_jalan, geom)
VALUES (:nama, :jenis_jalan, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama' => $input['nama'],
':jenis_jalan' => $input['jenis_jalan'] ?? 'Lokal',
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Jalan berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Jalan: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Jalan wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM jalan WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Jalan berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Jalan: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+91
View File
@@ -0,0 +1,91 @@
<?php
/**
* api_kavling.php
* Endpoint: CRUD untuk layer Kavling (Polygon).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama_pemilik, status_kepemilikan, luas, created_at,
ST_AsGeoJSON(geom) AS geojson FROM kavling"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama_pemilik' => $row['nama_pemilik'],
'status_kepemilikan' => $row['status_kepemilikan'],
'luas' => $row['luas'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Kavling berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Kavling: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama_pemilik']) || !isset($input['geometry'])) {
sendError('Nama pemilik dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO kavling (nama_pemilik, status_kepemilikan, luas, geom)
VALUES (:nama_pemilik, :status_kepemilikan, :luas, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama_pemilik' => $input['nama_pemilik'],
':status_kepemilikan' => $input['status_kepemilikan'] ?? 'SHM',
':luas' => $input['luas'] ?? 0,
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Kavling berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Kavling: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Kavling wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM kavling WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Kavling berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Kavling: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/**
* api_kawasan.php
* Endpoint: CRUD untuk layer Kawasan Kumuh (Polygon).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama_kawasan, created_at, ST_AsGeoJSON(geom) AS geojson FROM kawasan_kumuh"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama_kawasan' => $row['nama_kawasan'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Kawasan Kumuh berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Kawasan Kumuh: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama_kawasan']) || !isset($input['geometry'])) {
sendError('Nama kawasan dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO kawasan_kumuh (nama_kawasan, geom)
VALUES (:nama_kawasan, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama_kawasan' => $input['nama_kawasan'],
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Kawasan Kumuh berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Kawasan Kumuh: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Kawasan Kumuh wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM kawasan_kumuh WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Kawasan Kumuh berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Kawasan Kumuh: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+117
View File
@@ -0,0 +1,117 @@
<?php
/**
* api_laporan.php
* Endpoint: CRUD untuk Laporan Warga.
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
require_once __DIR__ . '/../core_config/session_mgr.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
startAppSession();
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
requireApiLogin();
try {
$userId = $_SESSION['user_id'] ?? null;
$role = $_SESSION['role'] ?? 'user';
if ($role === 'admin') {
$stmt = $pdo->query(
"SELECT lw.id, lw.user_id, u.username, lw.kategori, lw.deskripsi,
lw.status, lw.created_at, ST_AsGeoJSON(lw.geometry) AS geojson
FROM laporan_warga lw
JOIN users u ON u.id = lw.user_id
ORDER BY lw.created_at DESC"
);
} else {
$stmt = $pdo->prepare(
"SELECT id, user_id, kategori, deskripsi, status, created_at,
ST_AsGeoJSON(geometry) AS geojson
FROM laporan_warga WHERE user_id = :uid ORDER BY created_at DESC"
);
$stmt->execute([':uid' => $userId]);
}
$rows = $stmt->fetchAll();
foreach ($rows as &$r) {
$r['geojson'] = json_decode($r['geojson']);
}
sendSuccess($rows, 'Data Laporan berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil laporan: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiLogin();
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['kategori']) || !isset($input['deskripsi']) || !isset($input['geometry'])) {
sendError('Kategori, deskripsi, dan geometri wajib diisi');
}
$userId = $_SESSION['user_id'];
try {
$stmt = $pdo->prepare(
"INSERT INTO laporan_warga (user_id, kategori, deskripsi, geometry)
VALUES (:user_id, :kategori, :deskripsi, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':user_id' => $userId,
':kategori' => $input['kategori'],
':deskripsi'=> $input['deskripsi'],
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Laporan berhasil dikirim', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan laporan: ' . $e->getMessage(), 500);
}
break;
case 'PUT':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
$id = $_GET['id'] ?? null;
if (!$id || !isset($input['status'])) sendError('ID dan status wajib disertakan');
$allowed = ['menunggu', 'diproses', 'selesai', 'ditolak'];
if (!in_array($input['status'], $allowed)) sendError('Status tidak valid');
try {
$stmt = $pdo->prepare("UPDATE laporan_warga SET status = :status WHERE id = :id");
$stmt->execute([':status' => $input['status'], ':id' => $id]);
sendSuccess(null, 'Status laporan berhasil diperbarui');
} catch (PDOException $e) {
sendError('Gagal memperbarui laporan: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID laporan wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM laporan_warga WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Laporan berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus laporan: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+77
View File
@@ -0,0 +1,77 @@
<?php
/**
* api_pengguna.php
* Endpoint: Manajemen data Pengguna (hanya Admin).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
requireApiRole('admin');
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, username, role, nama_lengkap, created_at FROM users ORDER BY created_at DESC"
);
sendSuccess($stmt->fetchAll(), 'Data Pengguna berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data pengguna: ' . $e->getMessage(), 500);
}
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['username'], $input['password'])) {
sendError('Username dan password wajib diisi');
}
try {
$hash = password_hash($input['password'], PASSWORD_BCRYPT);
$stmt = $pdo->prepare(
"INSERT INTO users (username, password, role, nama_lengkap)
VALUES (:username, :password, :role, :nama_lengkap)"
);
$stmt->execute([
':username' => $input['username'],
':password' => $hash,
':role' => $input['role'] ?? 'user',
':nama_lengkap'=> $input['nama_lengkap'] ?? null,
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Pengguna berhasil dibuat', 201);
} catch (PDOException $e) {
sendError('Gagal membuat pengguna: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID pengguna wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Pengguna berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus pengguna: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+91
View File
@@ -0,0 +1,91 @@
<?php
/**
* api_spbu.php
* Endpoint: CRUD untuk layer SPBU (Point).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama, deskripsi, buka_24_jam, created_at,
ST_AsGeoJSON(geom) AS geojson FROM spbu"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama' => $row['nama'],
'deskripsi' => $row['deskripsi'],
'buka_24_jam' => (bool) $row['buka_24_jam'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data SPBU berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data SPBU: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama']) || !isset($input['geometry'])) {
sendError('Nama dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO spbu (nama, deskripsi, buka_24_jam, geom)
VALUES (:nama, :deskripsi, :buka_24_jam, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama' => $input['nama'],
':deskripsi' => $input['deskripsi'] ?? '',
':buka_24_jam' => isset($input['buka_24_jam']) && $input['buka_24_jam'] ? 1 : 0,
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data SPBU berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan SPBU: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID SPBU wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM spbu WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data SPBU berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus SPBU: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* api_spbu_nearby.php
* Endpoint: Mencari SPBU terdekat dari titik koordinat pengguna.
* Query params: lat, lng, radius (meter, default 5000)
*/
require_once __DIR__ . '/../core_config/database.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'GET') { sendError('Method not allowed', 405); }
$lat = isset($_GET['lat']) ? (float) $_GET['lat'] : null;
$lng = isset($_GET['lng']) ? (float) $_GET['lng'] : null;
$radius = isset($_GET['radius']) ? (int) $_GET['radius'] : 5000;
if ($lat === null || $lng === null) { sendError('Parameter lat dan lng wajib disertakan'); }
try {
$stmt = $pdo->prepare(
"SELECT id, nama, deskripsi, buka_24_jam, created_at,
ST_AsGeoJSON(geom) AS geojson,
ST_Distance_Sphere(geom, ST_GeomFromText(:point)) AS jarak_meter
FROM spbu
WHERE ST_Distance_Sphere(geom, ST_GeomFromText(:point)) <= :radius
ORDER BY jarak_meter ASC"
);
$stmt->execute([':point' => "POINT({$lng} {$lat})", ':radius' => $radius]);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama' => $row['nama'],
'deskripsi' => $row['deskripsi'],
'buka_24_jam' => (bool) $row['buka_24_jam'],
'jarak_meter' => round($row['jarak_meter'], 2),
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'SPBU Terdekat berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil SPBU Terdekat: ' . $e->getMessage(), 500);
}
+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* api_spbu_status.php
* Endpoint: Mengambil ringkasan status SPBU (berapa 24 jam, berapa tidak).
*/
require_once __DIR__ . '/../core_config/database.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'GET') {
sendError('Method not allowed', 405);
}
try {
$stmt = $pdo->query(
"SELECT
COUNT(*) AS total,
SUM(buka_24_jam = 1) AS buka_24_jam,
SUM(buka_24_jam = 0) AS tidak_24_jam
FROM spbu"
);
$summary = $stmt->fetch();
sendSuccess($summary, 'Status SPBU berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil status SPBU: ' . $e->getMessage(), 500);
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* api_statistik.php
* Endpoint: Mengembalikan ringkasan statistik keseluruhan data WebGIS.
*/
require_once __DIR__ . '/../core_config/database.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'GET') { sendError('Method not allowed', 405); }
try {
$counts = [];
$tables = [
'spbu' => 'total_spbu',
'rumah_ibadah' => 'total_rumah_ibadah',
'jalan' => 'total_jalan',
'kavling' => 'total_kavling',
'kawasan_kumuh' => 'total_kawasan_kumuh',
'warga_miskin' => 'total_warga_miskin',
'laporan_warga' => 'total_laporan',
'users' => 'total_users',
];
foreach ($tables as $table => $key) {
$row = $pdo->query("SELECT COUNT(*) AS cnt FROM `{$table}`")->fetch();
$counts[$key] = (int) $row['cnt'];
}
// Additional SPBU breakdown
$spbuStatus = $pdo->query(
"SELECT SUM(buka_24_jam=1) AS buka_24_jam, SUM(buka_24_jam=0) AS tidak_24_jam FROM spbu"
)->fetch();
$counts['spbu_buka_24_jam'] = (int) $spbuStatus['buka_24_jam'];
$counts['spbu_tidak_24_jam'] = (int) $spbuStatus['tidak_24_jam'];
// Laporan by status
$laporanStatus = $pdo->query(
"SELECT status, COUNT(*) AS cnt FROM laporan_warga GROUP BY status"
)->fetchAll();
$counts['laporan_by_status'] = [];
foreach ($laporanStatus as $row) {
$counts['laporan_by_status'][$row['status']] = (int) $row['cnt'];
}
sendSuccess($counts, 'Statistik berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil statistik: ' . $e->getMessage(), 500);
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* api_ulasan.php
* Endpoint: CRUD untuk Ulasan Fasilitas (SPBU / Rumah Ibadah).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
require_once __DIR__ . '/../core_config/session_mgr.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
startAppSession();
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
$tipe = $_GET['tipe'] ?? null;
$fid = $_GET['fasilitas_id'] ?? null;
try {
if ($tipe && $fid) {
$stmt = $pdo->prepare(
"SELECT uf.id, uf.user_id, u.username, uf.fasilitas_tipe,
uf.fasilitas_id, uf.rating, uf.komentar, uf.created_at
FROM ulasan_fasilitas uf
JOIN users u ON u.id = uf.user_id
WHERE uf.fasilitas_tipe = :tipe AND uf.fasilitas_id = :fid
ORDER BY uf.created_at DESC"
);
$stmt->execute([':tipe' => $tipe, ':fid' => $fid]);
} else {
$stmt = $pdo->query(
"SELECT uf.id, uf.user_id, u.username, uf.fasilitas_tipe,
uf.fasilitas_id, uf.rating, uf.komentar, uf.created_at
FROM ulasan_fasilitas uf
JOIN users u ON u.id = uf.user_id
ORDER BY uf.created_at DESC"
);
}
sendSuccess($stmt->fetchAll(), 'Data Ulasan berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil ulasan: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiLogin();
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['fasilitas_tipe'], $input['fasilitas_id'], $input['rating'])) {
sendError('fasilitas_tipe, fasilitas_id, dan rating wajib diisi');
}
$allowed = ['spbu', 'rumah_ibadah'];
if (!in_array($input['fasilitas_tipe'], $allowed)) sendError('fasilitas_tipe tidak valid');
if ($input['rating'] < 1 || $input['rating'] > 5) sendError('Rating harus antara 1-5');
try {
$stmt = $pdo->prepare(
"INSERT INTO ulasan_fasilitas (user_id, fasilitas_tipe, fasilitas_id, rating, komentar)
VALUES (:user_id, :fasilitas_tipe, :fasilitas_id, :rating, :komentar)"
);
$stmt->execute([
':user_id' => $_SESSION['user_id'],
':fasilitas_tipe'=> $input['fasilitas_tipe'],
':fasilitas_id' => $input['fasilitas_id'],
':rating' => $input['rating'],
':komentar' => $input['komentar'] ?? null,
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Ulasan berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan ulasan: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID ulasan wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM ulasan_fasilitas WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Ulasan berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus ulasan: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+91
View File
@@ -0,0 +1,91 @@
<?php
/**
* api_warga_miskin.php
* Endpoint: CRUD untuk layer Warga Miskin (Point).
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query(
"SELECT id, nama_kk, penghasilan, jumlah_tanggungan, created_at,
ST_AsGeoJSON(geom) AS geojson FROM warga_miskin"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama_kk' => $row['nama_kk'],
'penghasilan' => $row['penghasilan'],
'jumlah_tanggungan' => $row['jumlah_tanggungan'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Warga Miskin berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Warga Miskin: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama_kk']) || !isset($input['geometry'])) {
sendError('Nama KK dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO warga_miskin (nama_kk, penghasilan, jumlah_tanggungan, geom)
VALUES (:nama_kk, :penghasilan, :jumlah_tanggungan, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama_kk' => $input['nama_kk'],
':penghasilan' => $input['penghasilan'] ?? 0,
':jumlah_tanggungan' => $input['jumlah_tanggungan'] ?? 0,
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Warga Miskin berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Warga Miskin: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Warga Miskin wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM warga_miskin WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Warga Miskin berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Warga Miskin: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
* area_blank.php
* Endpoint: Blank Spot — area tanpa sinyal seluler.
* Menggunakan tabel kawasan_kumuh sebagai proxy (tidak ada tabel blank_spot tersendiri).
* Mengembalikan FeatureCollection kosong jika tabel belum ada.
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
function sendSuccess($data = null, $message = 'Success', $code = 200) {
http_response_code($code);
echo json_encode(['status' => 'success', 'message' => $message, 'data' => $data]);
exit();
}
function sendError($message = 'Error', $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'error', 'message' => $message]);
exit();
}
$pdo = Database::getConnection();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
try {
// Check if blank_spot table exists, otherwise return empty collection
$check = $pdo->query("SHOW TABLES LIKE 'blank_spot'")->rowCount();
if ($check === 0) {
sendSuccess(
['type' => 'FeatureCollection', 'features' => []],
'Tabel blank_spot belum tersedia'
);
}
$stmt = $pdo->query(
"SELECT id, nama, keterangan, created_at, ST_AsGeoJSON(geom) AS geojson FROM blank_spot"
);
$features = [];
while ($row = $stmt->fetch()) {
$features[] = [
'type' => 'Feature',
'geometry' => json_decode($row['geojson']),
'properties' => [
'id' => $row['id'],
'nama' => $row['nama'],
'keterangan' => $row['keterangan'],
'created_at' => $row['created_at'],
],
];
}
sendSuccess(['type' => 'FeatureCollection', 'features' => $features], 'Data Blank Spot berhasil diambil');
} catch (PDOException $e) {
sendError('Gagal mengambil data Blank Spot: ' . $e->getMessage(), 500);
}
break;
case 'POST':
requireApiRole('admin');
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['nama']) || !isset($input['geometry'])) {
sendError('Nama dan geometri wajib diisi');
}
try {
$stmt = $pdo->prepare(
"INSERT INTO blank_spot (nama, keterangan, geom)
VALUES (:nama, :keterangan, ST_GeomFromGeoJSON(:geometry))"
);
$stmt->execute([
':nama' => $input['nama'],
':keterangan' => $input['keterangan'] ?? '',
':geometry' => json_encode($input['geometry']),
]);
sendSuccess(['id' => $pdo->lastInsertId()], 'Data Blank Spot berhasil disimpan', 201);
} catch (PDOException $e) {
sendError('Gagal menyimpan Blank Spot: ' . $e->getMessage(), 500);
}
break;
case 'DELETE':
requireApiRole('admin');
$id = $_GET['id'] ?? null;
if (!$id) sendError('ID Blank Spot wajib disertakan');
try {
$stmt = $pdo->prepare("DELETE FROM blank_spot WHERE id = :id");
$stmt->execute([':id' => $id]);
sendSuccess(null, 'Data Blank Spot berhasil dihapus');
} catch (PDOException $e) {
sendError('Gagal menghapus Blank Spot: ' . $e->getMessage(), 500);
}
break;
default:
sendError('Method not allowed', 405);
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/**
* cek_srid.php
* Utility: Cek SRID (Spatial Reference ID) dari semua kolom geometri.
* Hanya untuk Admin / debugging.
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
requireApiRole('admin');
$pdo = Database::getConnection();
try {
$tables = ['spbu', 'rumah_ibadah', 'jalan', 'kavling', 'kawasan_kumuh', 'warga_miskin'];
$results = [];
foreach ($tables as $table) {
$stmt = $pdo->query(
"SELECT ST_SRID(geom) AS srid, COUNT(*) AS jumlah
FROM `{$table}`
GROUP BY ST_SRID(geom)"
);
$results[$table] = $stmt->fetchAll();
}
http_response_code(200);
echo json_encode(['status' => 'success', 'data' => $results]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
+33
View File
@@ -0,0 +1,33 @@
<?php
/**
* perbaiki_srid.php
* Utility: Perbaiki SRID semua geometri ke SRID 0 (tidak terdefinisi, kompatibel dengan MySQL).
* Hanya untuk Admin / debugging.
*/
require_once __DIR__ . '/../core_config/database.php';
require_once __DIR__ . '/../core_config/middleware_auth.php';
header('Content-Type: application/json; charset=utf-8');
requireApiRole('admin');
$pdo = Database::getConnection();
try {
$tables = ['spbu', 'rumah_ibadah', 'jalan', 'kavling', 'kawasan_kumuh', 'warga_miskin'];
$updated = [];
foreach ($tables as $table) {
$affected = $pdo->exec(
"UPDATE `{$table}` SET geom = ST_GeomFromWKB(ST_AsWKB(geom), 0) WHERE ST_SRID(geom) != 0"
);
$updated[$table] = ['rows_updated' => $affected];
}
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => 'SRID diperbaiki', 'data' => $updated]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
@@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/config/app.php';
<?php
require_once __DIR__ . '/core_config/settings.php';
$host = getenv('DB_HOST') ?: 'localhost';
$port = getenv('DB_PORT') ?: 3306;

Some files were not shown because too many files have changed in this diff Show More