mengubah sistem menjadi lebih terfokus ke poverty

This commit is contained in:
Abimanyu Ridho
2026-06-11 01:50:37 +07:00
parent 739e360018
commit 468541b1c8
46 changed files with 5753 additions and 2805 deletions
+4 -2
View File
@@ -6,10 +6,11 @@ mysqli_report(MYSQLI_REPORT_OFF);
if (!function_exists('loadDatabaseConfig')) {
function loadDatabaseConfig(): array {
$config = [
'host' => getenv('DB_HOST') ?: 'localhost',
'host' => getenv('DB_HOST') ?: '127.0.0.1',
'user' => getenv('DB_USER') ?: 'root',
'pass' => getenv('DB_PASS') ?: '',
'name' => getenv('DB_NAME') ?: 'webgis_poverty',
'port' => (int)(getenv('DB_PORT') ?: 3306),
'charset' => getenv('DB_CHARSET') ?: 'utf8mb4',
];
@@ -31,12 +32,13 @@ define('DB_HOST', $dbConfig['host']);
define('DB_USER', $dbConfig['user']);
define('DB_PASS', $dbConfig['pass']);
define('DB_NAME', $dbConfig['name']);
define('DB_PORT', (int)$dbConfig['port']);
define('DB_CHARSET', $dbConfig['charset']);
function getDB(): mysqli {
static $conn = null;
if ($conn === null) {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT);
if ($conn->connect_error) {
error_log('DB connection failed: ' . $conn->connect_error);
http_response_code(500);
+51
View File
@@ -0,0 +1,51 @@
<?php
// api/config/setup_users.php
require_once __DIR__ . '/database.php';
$db = getDB();
// Create users table
$sql = "CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`role` ENUM('admin', 'petugas_lapangan', 'guest') NOT NULL,
`rumah_ibadah_id` INT DEFAULT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
if ($db->query($sql)) {
echo "Tabel 'users' berhasil dibuat atau sudah ada.\n";
} else {
echo "Gagal membuat tabel 'users': " . $db->error . "\n";
exit(1);
}
// Insert default users if they don't exist
$defaultUsers = [
[
'username' => 'admin',
'password' => password_hash('admin123', PASSWORD_BCRYPT),
'role' => 'admin'
]
];
foreach ($defaultUsers as $user) {
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmt->bind_param('s', $user['username']);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$stmtInsert = $db->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmtInsert->bind_param('sss', $user['username'], $user['password'], $user['role']);
if ($stmtInsert->execute()) {
echo "User '" . $user['username'] . "' berhasil ditambahkan.\n";
} else {
echo "Gagal menambahkan user '" . $user['username'] . "': " . $stmtInsert->error . "\n";
}
} else {
echo "User '" . $user['username'] . "' sudah ada.\n";
}
}
echo "Setup database selesai.\n";
+73
View File
@@ -0,0 +1,73 @@
<?php
// api/dashboard_stats.php
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
// CORS and RBAC are already processed globally in response.php
$db = getDB();
$stats = [];
// 1. Penduduk Miskin (Total KK & Total Jiwa)
$resPm = $db->query("SELECT COUNT(*) AS total_kk, IFNULL(SUM(jumlah_anggota), 0) AS total_jiwa FROM penduduk_miskin");
$stats['penduduk_miskin'] = $resPm->fetch_assoc();
// 2. Rumah Ibadah (Total)
$resRi = $db->query("SELECT COUNT(*) AS total FROM rumah_ibadah");
$stats['rumah_ibadah'] = $resRi->fetch_assoc();
// 6. Log Bantuan (Total log & Nilai total bantuan)
$resLb = $db->query("SELECT COUNT(*) AS total_count, IFNULL(SUM(nilai_bantuan), 0) AS total_nilai FROM log_bantuan");
$stats['log_bantuan'] = $resLb->fetch_assoc();
// 7. Proximity (Dalam vs Luar Radius)
$resProx = $db->query("SELECT status_proximity, COUNT(*) AS count FROM penduduk_miskin GROUP BY status_proximity");
$proximity = ['dalam_radius' => 0, 'luar_radius' => 0];
while ($row = $resProx->fetch_assoc()) {
$key = $row['status_proximity'] === 'dalam_radius' ? 'dalam_radius' : 'luar_radius';
$proximity[$key] += (int)$row['count'];
}
$stats['proximity'] = $proximity;
// 8. Tipe Bantuan (Pemberdayaan vs Konsumtif)
$resTipe = $db->query("SELECT tipe_bantuan, COUNT(*) AS count FROM log_bantuan GROUP BY tipe_bantuan");
$tipeBantuan = ['Pemberdayaan' => 0, 'Konsumtif' => 0];
while ($row = $resTipe->fetch_assoc()) {
$key = $row['tipe_bantuan'] === 'Pemberdayaan' ? 'Pemberdayaan' : 'Konsumtif';
$tipeBantuan[$key] = (int)$row['count'];
}
$stats['tipe_bantuan'] = $tipeBantuan;
// 9. Rumah Ibadah Teraktif (Top 5)
$resRiAktif = $db->query("
SELECT ri.nama, COUNT(lb.id) AS count, IFNULL(SUM(lb.nilai_bantuan), 0) AS nilai
FROM log_bantuan lb
JOIN rumah_ibadah ri ON ri.id = lb.rumah_ibadah_id
GROUP BY lb.rumah_ibadah_id
ORDER BY count DESC LIMIT 5
");
$riAktif = [];
while ($row = $resRiAktif->fetch_assoc()) {
$row['count'] = (int)$row['count'];
$row['nilai'] = (float)$row['nilai'];
$riAktif[] = $row;
}
$stats['ri_teraktif'] = $riAktif;
// 10. Bantuan Terbaru (Top 5)
$resBantuanBaru = $db->query("
SELECT lb.id, lb.tipe_bantuan, lb.sub_kategori, lb.keterangan, lb.tanggal_bantuan, lb.nilai_bantuan,
pm.nama AS nama_penduduk, ri.nama AS nama_rumah_ibadah
FROM log_bantuan lb
JOIN penduduk_miskin pm ON pm.id = lb.penduduk_miskin_id
JOIN rumah_ibadah ri ON ri.id = lb.rumah_ibadah_id
ORDER BY lb.tanggal_bantuan DESC, lb.id DESC LIMIT 5
");
$bantuanBaru = [];
while ($row = $resBantuanBaru->fetch_assoc()) {
$row['nilai_bantuan'] = $row['nilai_bantuan'] ? (float)$row['nilai_bantuan'] : 0;
$bantuanBaru[] = $row;
}
$stats['bantuan_terbaru'] = $bantuanBaru;
sendSuccess($stats);
-112
View File
@@ -1,112 +0,0 @@
<?php
// api/data_jalan.php
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
$db = getDB();
$method = getMethod();
requireWriteAuth($method);
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
switch ($method) {
case 'GET':
if ($id > 0) {
$stmt = $db->prepare(
"SELECT id, nama_jalan, status_jalan,
panjang_meter, keterangan,
CAST(geojson AS CHAR) AS geojson,
created_at
FROM data_jalan WHERE id = ?"
);
$stmt->bind_param('i', $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) sendError('Data tidak ditemukan', 404);
// Parse geojson string menjadi object
$row['geojson'] = json_decode($row['geojson']);
$row['panjang_meter'] = (float)$row['panjang_meter'];
sendSuccess($row);
} else {
$rows = [];
$res = $db->query(
"SELECT id, nama_jalan, status_jalan,
panjang_meter, keterangan,
CAST(geojson AS CHAR) AS geojson,
created_at
FROM data_jalan ORDER BY created_at DESC"
);
while ($r = $res->fetch_assoc()) {
$r['geojson'] = json_decode($r['geojson']);
$r['panjang_meter'] = (float)$r['panjang_meter'];
$rows[] = $r;
}
sendSuccess($rows);
}
break;
case 'POST':
$body = getBody();
foreach (['nama_jalan','status_jalan','geojson','panjang_meter'] as $f) {
if (!isset($body[$f])) sendError("Field '$f' wajib diisi");
}
// Validasi GeoJSON LineString
$geo = is_array($body['geojson'])
? $body['geojson']
: json_decode($body['geojson'], true);
if (!$geo || $geo['type'] !== 'LineString') {
sendError('GeoJSON harus bertipe LineString');
}
$geoStr = json_encode($geo);
$panjang = (float)$body['panjang_meter'];
$stmt = $db->prepare(
"INSERT INTO data_jalan
(nama_jalan, status_jalan, panjang_meter, keterangan, geojson)
VALUES (?, ?, ?, ?, ?)"
);
$stmt->bind_param('ssdss',
$body['nama_jalan'], $body['status_jalan'],
$panjang, $body['keterangan'], $geoStr
);
executeOrFail($stmt, 'Gagal menyimpan data jalan');
sendSuccess(['id' => $db->insert_id], 'Data jalan berhasil disimpan', 201);
break;
case 'PUT':
if ($id <= 0) sendError('ID wajib disertakan');
$body = getBody();
$geo = is_array($body['geojson'])
? $body['geojson']
: json_decode($body['geojson'], true);
$geoStr = json_encode($geo);
$panjang = (float)$body['panjang_meter'];
$stmt = $db->prepare(
"UPDATE data_jalan
SET nama_jalan=?, status_jalan=?,
panjang_meter=?, keterangan=?, geojson=?
WHERE id=?"
);
$stmt->bind_param('ssdssi',
$body['nama_jalan'], $body['status_jalan'],
$panjang, $body['keterangan'], $geoStr, $id
);
executeOrFail($stmt, 'Gagal memperbarui data jalan');
sendSuccess(null, 'Data jalan berhasil diperbarui');
break;
case 'DELETE':
if ($id <= 0) sendError('ID wajib disertakan');
$stmt = $db->prepare("DELETE FROM data_jalan WHERE id=?");
$stmt->bind_param('i', $id);
executeOrFail($stmt, 'Gagal menghapus data jalan');
$stmt->affected_rows > 0
? sendSuccess(null, 'Data jalan berhasil dihapus')
: sendError('Data tidak ditemukan', 404);
break;
default:
sendError('Method tidak diizinkan', 405);
}
-112
View File
@@ -1,112 +0,0 @@
<?php
// api/data_parsil.php
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
$db = getDB();
$method = getMethod();
requireWriteAuth($method);
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
switch ($method) {
case 'GET':
if ($id > 0) {
$stmt = $db->prepare(
"SELECT id, nama_pemilik, no_sertifikat, jenis_hak,
luas_m2, keterangan,
CAST(geojson AS CHAR) AS geojson,
created_at
FROM data_parsil WHERE id = ?"
);
$stmt->bind_param('i', $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) sendError('Data tidak ditemukan', 404);
$row['geojson'] = json_decode($row['geojson']);
$row['luas_m2'] = (float)$row['luas_m2'];
sendSuccess($row);
} else {
$rows = [];
$res = $db->query(
"SELECT id, nama_pemilik, no_sertifikat, jenis_hak,
luas_m2, keterangan,
CAST(geojson AS CHAR) AS geojson,
created_at
FROM data_parsil ORDER BY created_at DESC"
);
while ($r = $res->fetch_assoc()) {
$r['geojson'] = json_decode($r['geojson']);
$r['luas_m2'] = (float)$r['luas_m2'];
$rows[] = $r;
}
sendSuccess($rows);
}
break;
case 'POST':
$body = getBody();
foreach (['nama_pemilik','jenis_hak','geojson','luas_m2'] as $f) {
if (!isset($body[$f])) sendError("Field '$f' wajib diisi");
}
$geo = is_array($body['geojson'])
? $body['geojson']
: json_decode($body['geojson'], true);
if (!$geo || $geo['type'] !== 'Polygon') {
sendError('GeoJSON harus bertipe Polygon');
}
$geoStr = json_encode($geo);
$luas = (float)$body['luas_m2'];
$stmt = $db->prepare(
"INSERT INTO data_parsil
(nama_pemilik, no_sertifikat, jenis_hak, luas_m2, keterangan, geojson)
VALUES (?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param('sssdss',
$body['nama_pemilik'], $body['no_sertifikat'],
$body['jenis_hak'], $luas,
$body['keterangan'], $geoStr
);
executeOrFail($stmt, 'Gagal menyimpan data parsil');
sendSuccess(['id' => $db->insert_id], 'Data parsil berhasil disimpan', 201);
break;
case 'PUT':
if ($id <= 0) sendError('ID wajib disertakan');
$body = getBody();
$geo = is_array($body['geojson'])
? $body['geojson']
: json_decode($body['geojson'], true);
$geoStr = json_encode($geo);
$luas = (float)$body['luas_m2'];
$stmt = $db->prepare(
"UPDATE data_parsil
SET nama_pemilik=?, no_sertifikat=?, jenis_hak=?,
luas_m2=?, keterangan=?, geojson=?
WHERE id=?"
);
$stmt->bind_param('sssdssi',
$body['nama_pemilik'], $body['no_sertifikat'],
$body['jenis_hak'], $luas,
$body['keterangan'], $geoStr, $id
);
executeOrFail($stmt, 'Gagal memperbarui data parsil');
sendSuccess(null, 'Data parsil berhasil diperbarui');
break;
case 'DELETE':
if ($id <= 0) sendError('ID wajib disertakan');
$stmt = $db->prepare("DELETE FROM data_parsil WHERE id=?");
$stmt->bind_param('i', $id);
executeOrFail($stmt, 'Gagal menghapus data parsil');
$stmt->affected_rows > 0
? sendSuccess(null, 'Data parsil berhasil dihapus')
: sendError('Data tidak ditemukan', 404);
break;
default:
sendError('Method tidak diizinkan', 405);
}
+179 -20
View File
@@ -49,28 +49,178 @@ function sendError(string $message, int $code = 400): void {
}
function requireWriteAuth(string $method): void {
if (!in_array($method, ['POST', 'PUT', 'DELETE'], true)) {
return;
}
$expected = (string)(getenv('API_WRITE_TOKEN') ?: '');
if ($expected === '') {
return;
}
$provided = (string)($_SERVER['HTTP_X_API_TOKEN'] ?? '');
if ($provided === '') {
$auth = (string)($_SERVER['HTTP_AUTHORIZATION'] ?? '');
if (preg_match('/Bearer\s+(.+)/i', $auth, $m)) {
$provided = trim($m[1]);
}
}
if ($provided === '' || !hash_equals($expected, $provided)) {
sendError('Unauthorized', 401);
}
// Deprecated: Menggunakan checkRBAC() secara global untuk semua request.
}
function checkRBAC(): void {
if (PHP_SAPI === 'cli') {
return;
}
$script = basename($_SERVER['SCRIPT_FILENAME']);
if (in_array($script, ['login.php', 'logout.php', 'me.php'], true)) {
return;
}
if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
session_start();
}
if (!isset($_SESSION['user_id'])) {
sendError('Unauthorized - Silakan login terlebih dahulu', 401);
}
$role = $_SESSION['role'] ?? 'guest';
$method = getMethod();
if ($method === 'GET') {
// Semua role diizinkan membaca data (GET)
return;
}
// Operasi Tulis (POST, PUT, DELETE)
if ($role === 'admin') {
// Admin diizinkan melakukan semua aksi tulis
return;
}
if ($role === 'petugas_lapangan') {
$user_ri_id = isset($_SESSION['rumah_ibadah_id']) ? (int)$_SESSION['rumah_ibadah_id'] : 0;
if ($user_ri_id <= 0) {
sendError('Forbidden - Akun petugas lapangan tidak dikaitkan dengan rumah ibadah manapun', 403);
}
if ($method === 'DELETE') {
sendError('Forbidden - Petugas Lapangan tidak diizinkan menghapus data', 403);
}
if ($script === 'rumah_ibadah.php') {
if ($method === 'PUT') {
$req_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($req_id !== $user_ri_id) {
sendError('Forbidden - Petugas Lapangan hanya dapat mengedit rumah ibadah mereka sendiri', 403);
}
return; // Allowed
}
sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada rumah ibadah', 403);
}
if ($script === 'penduduk_miskin.php') {
if ($method === 'POST') {
if (isset($_GET['action']) && $_GET['action'] === 'recalc_all') {
sendError('Forbidden - Petugas Lapangan tidak dapat memperbarui proximity masal', 403);
}
$body = getBody();
$lat = isset($body['latitude']) ? (float)$body['latitude'] : null;
$lng = isset($body['longitude']) ? (float)$body['longitude'] : null;
if ($lat === null || $lng === null) {
sendError('Field latitude dan longitude wajib diisi');
}
$db = getDB();
$stmt = $db->prepare("SELECT latitude, longitude, radius_meter FROM rumah_ibadah WHERE id = ?");
$stmt->bind_param('i', $user_ri_id);
$stmt->execute();
$ri = $stmt->get_result()->fetch_assoc();
if (!$ri) {
sendError('Rumah ibadah petugas tidak ditemukan', 404);
}
$dist = haversine_distance((float)$ri['latitude'], (float)$ri['longitude'], $lat, $lng);
if ($dist > (int)$ri['radius_meter']) {
sendError('Forbidden - Titik koordinat penduduk miskin di luar radius jangkauan rumah ibadah Anda', 403);
}
return; // Allowed
}
if ($method === 'PUT') {
$req_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($req_id <= 0) {
sendError('ID penduduk miskin wajib disertakan');
}
$db = getDB();
$stmtCheck = $db->prepare("SELECT rumah_ibadah_id FROM penduduk_miskin WHERE id = ?");
$stmtCheck->bind_param('i', $req_id);
$stmtCheck->execute();
$pm = $stmtCheck->get_result()->fetch_assoc();
if (!$pm) {
sendError('Penduduk miskin tidak ditemukan', 404);
}
if ($pm['rumah_ibadah_id'] === null || (int)$pm['rumah_ibadah_id'] !== $user_ri_id) {
sendError('Forbidden - Penduduk miskin ini tidak berada di wilayah rumah ibadah Anda', 403);
}
$body = getBody();
$lat = isset($body['latitude']) ? (float)$body['latitude'] : null;
$lng = isset($body['longitude']) ? (float)$body['longitude'] : null;
if ($lat === null || $lng === null) {
sendError('Field latitude dan longitude wajib diisi');
}
$stmt = $db->prepare("SELECT latitude, longitude, radius_meter FROM rumah_ibadah WHERE id = ?");
$stmt->bind_param('i', $user_ri_id);
$stmt->execute();
$ri = $stmt->get_result()->fetch_assoc();
if (!$ri) {
sendError('Rumah ibadah petugas tidak ditemukan', 404);
}
$dist = haversine_distance((float)$ri['latitude'], (float)$ri['longitude'], $lat, $lng);
if ($dist > (int)$ri['radius_meter']) {
sendError('Forbidden - Titik koordinat baru di luar radius jangkauan rumah ibadah Anda', 403);
}
return; // Allowed
}
sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada penduduk miskin', 403);
}
if ($script === 'log_bantuan.php') {
if ($method === 'POST') {
$body = getBody();
$req_pm_id = isset($body['penduduk_miskin_id']) ? (int)$body['penduduk_miskin_id'] : 0;
$req_ri_id = isset($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : 0;
if ($req_ri_id !== $user_ri_id) {
sendError('Forbidden - Anda hanya dapat mencatat log bantuan dari rumah ibadah Anda sendiri', 403);
}
$db = getDB();
$stmtPm = $db->prepare("SELECT rumah_ibadah_id, status_proximity FROM penduduk_miskin WHERE id = ?");
$stmtPm->bind_param('i', $req_pm_id);
$stmtPm->execute();
$pm = $stmtPm->get_result()->fetch_assoc();
if (!$pm) {
sendError('Penduduk miskin tidak ditemukan', 404);
}
if ($pm['rumah_ibadah_id'] === null || (int)$pm['rumah_ibadah_id'] !== $user_ri_id || $pm['status_proximity'] !== 'dalam_radius') {
sendError('Forbidden - Penduduk miskin tidak berada di dalam radius rumah ibadah Anda', 403);
}
return; // Allowed
}
sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada log bantuan', 403);
}
sendError('Forbidden', 403);
}
if ($role === 'guest') {
sendError('Forbidden - Guest hanya memiliki akses lihat (read-only)', 403);
}
sendError('Forbidden', 403);
}
// Jalankan pemeriksaan CORS dan RBAC secara global saat file di-load
setCORSHeaders();
checkRBAC();
function executeOrFail(mysqli_stmt $stmt, string $defaultMessage = 'Operasi database gagal'): void {
if ($stmt->execute()) {
return;
@@ -97,4 +247,13 @@ function getBody(): array {
function getMethod(): string {
return strtoupper($_SERVER['REQUEST_METHOD']);
}
function haversine_distance(float $lat1, float $lng1, float $lat2, float $lng2): float {
$R = 6371000; // radius bumi dalam meter
$dLat = deg2rad($lat2 - $lat1);
$dLng = deg2rad($lng2 - $lng1);
$a = sin($dLat/2)**2
+ cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2;
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
}
+50
View File
@@ -0,0 +1,50 @@
<?php
// api/login.php
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
$method = getMethod();
if ($method !== 'POST') {
sendError('Method tidak diizinkan', 405);
}
$body = getBody();
$username = trim($body['username'] ?? '');
$password = trim($body['password'] ?? '');
if (empty($username) || empty($password)) {
sendError('Username dan Password wajib diisi');
}
$db = getDB();
$stmt = $db->prepare("SELECT id, username, password, role, rumah_ibadah_id FROM users WHERE username = ?");
if (!$stmt) {
sendError('Gagal menyiapkan query database', 500);
}
$stmt->bind_param('s', $username);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
if (!$user || !password_verify($password, $user['password'])) {
sendError('Username atau Password salah', 401);
}
if (session_status() === PHP_SESSION_NONE) {
// Session settings for security
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
session_start();
}
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
$_SESSION['rumah_ibadah_id'] = $user['rumah_ibadah_id'] ? (int)$user['rumah_ibadah_id'] : null;
sendSuccess([
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role'],
'rumah_ibadah_id' => $user['rumah_ibadah_id'] ? (int)$user['rumah_ibadah_id'] : null
], 'Login berhasil');
+22
View File
@@ -0,0 +1,22 @@
<?php
// api/logout.php
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
sendSuccess(null, 'Logout berhasil');
+122
View File
@@ -0,0 +1,122 @@
<?php
// api/manajemen_akun.php
// Endpoint khusus admin untuk mendapatkan data manajemen akun rumah ibadah
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
// CORS dan RBAC sudah diproses secara global di response.php
// Hanya admin yang boleh mengakses
if ($_SESSION['role'] !== 'admin') {
sendError('Forbidden - Hanya admin yang dapat mengakses halaman ini', 403);
}
$db = getDB();
$method = getMethod();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
switch ($method) {
// GET: Ambil semua rumah ibadah beserta statistik dan info akun
case 'GET':
if ($id > 0) {
// Detail satu rumah ibadah
$stmt = $db->prepare("
SELECT ri.id, ri.nama, ri.jenis, ri.alamat, ri.no_wa,
ri.latitude, ri.longitude, ri.radius_meter, ri.created_at,
u.id AS user_id, u.username, u.password_plain, u.created_at AS akun_dibuat,
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id AND pm.status_proximity = 'dalam_radius') AS pm_dalam_radius,
(SELECT COUNT(*) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS total_penyaluran,
(SELECT COUNT(*) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id AND lb.tipe_bantuan = 'Pemberdayaan') AS bantuan_pemberdayaan,
(SELECT COUNT(*) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id AND lb.tipe_bantuan = 'Konsumtif') AS bantuan_konsumtif,
(SELECT IFNULL(SUM(lb.nilai_bantuan), 0) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS total_nilai_bantuan,
(SELECT COUNT(DISTINCT lb.penduduk_miskin_id) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS jumlah_penerima_unik,
(SELECT lb.tanggal_bantuan FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id ORDER BY lb.tanggal_bantuan DESC LIMIT 1) AS tanggal_bantuan_terakhir
FROM rumah_ibadah ri
LEFT JOIN users u ON u.rumah_ibadah_id = ri.id
WHERE ri.id = ?
");
$stmt->bind_param('i', $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) sendError('Data tidak ditemukan', 404);
// Cast types
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['radius_meter'] = (int)$row['radius_meter'];
$row['pm_dalam_radius'] = (int)$row['pm_dalam_radius'];
$row['total_penyaluran'] = (int)$row['total_penyaluran'];
$row['bantuan_pemberdayaan'] = (int)$row['bantuan_pemberdayaan'];
$row['bantuan_konsumtif'] = (int)$row['bantuan_konsumtif'];
$row['total_nilai_bantuan'] = (float)$row['total_nilai_bantuan'];
$row['jumlah_penerima_unik'] = (int)$row['jumlah_penerima_unik'];
// password_plain: null if no account
if ($row['password_plain'] === null) $row['password_plain'] = null;
// Ambil 5 riwayat bantuan terbaru
$stmtLog = $db->prepare("
SELECT lb.tipe_bantuan, lb.sub_kategori, lb.tanggal_bantuan, lb.nilai_bantuan,
pm.nama AS nama_penerima
FROM log_bantuan lb
JOIN penduduk_miskin pm ON pm.id = lb.penduduk_miskin_id
WHERE lb.rumah_ibadah_id = ?
ORDER BY lb.tanggal_bantuan DESC, lb.id DESC
LIMIT 10
");
$stmtLog->bind_param('i', $id);
$stmtLog->execute();
$logs = [];
$resLog = $stmtLog->get_result();
while ($l = $resLog->fetch_assoc()) {
$l['nilai_bantuan'] = $l['nilai_bantuan'] ? (float)$l['nilai_bantuan'] : null;
$logs[] = $l;
}
$row['riwayat_bantuan'] = $logs;
sendSuccess($row);
} else {
// Semua rumah ibadah dengan ringkasan statistik
$sql = "
SELECT ri.id, ri.nama, ri.jenis, ri.alamat, ri.no_wa,
ri.latitude, ri.longitude, ri.radius_meter, ri.created_at,
u.id AS user_id, u.username,
(SELECT COUNT(*) FROM penduduk_miskin pm WHERE pm.rumah_ibadah_id = ri.id AND pm.status_proximity = 'dalam_radius') AS pm_dalam_radius,
(SELECT COUNT(*) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS total_penyaluran,
(SELECT IFNULL(SUM(lb.nilai_bantuan), 0) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS total_nilai_bantuan,
(SELECT COUNT(DISTINCT lb.penduduk_miskin_id) FROM log_bantuan lb WHERE lb.rumah_ibadah_id = ri.id) AS jumlah_penerima_unik
FROM rumah_ibadah ri
LEFT JOIN users u ON u.rumah_ibadah_id = ri.id
ORDER BY ri.created_at DESC
";
$result = $db->query($sql);
$rows = [];
while ($r = $result->fetch_assoc()) {
$r['latitude'] = (float)$r['latitude'];
$r['longitude'] = (float)$r['longitude'];
$r['radius_meter'] = (int)$r['radius_meter'];
$r['pm_dalam_radius'] = (int)$r['pm_dalam_radius'];
$r['total_penyaluran'] = (int)$r['total_penyaluran'];
$r['total_nilai_bantuan'] = (float)$r['total_nilai_bantuan'];
$r['jumlah_penerima_unik'] = (int)$r['jumlah_penerima_unik'];
$rows[] = $r;
}
sendSuccess($rows);
}
break;
// DELETE: Hapus akun user rumah ibadah (tidak hapus rumah ibadahnya)
case 'DELETE':
if ($id <= 0) sendError('ID wajib disertakan');
$stmt = $db->prepare("DELETE FROM users WHERE rumah_ibadah_id = ?");
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
sendSuccess(null, $stmt->affected_rows > 0 ? 'Akun berhasil dihapus' : 'Tidak ada akun untuk dihapus');
} else {
sendError('Gagal menghapus akun');
}
break;
default:
sendError('Method tidak diizinkan', 405);
}
+21
View File
@@ -0,0 +1,21 @@
<?php
// api/me.php
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
session_start();
}
if (!isset($_SESSION['user_id'])) {
sendError('Unauthorized', 401);
}
sendSuccess([
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'role' => $_SESSION['role'],
'rumah_ibadah_id' => isset($_SESSION['rumah_ibadah_id']) ? (int)$_SESSION['rumah_ibadah_id'] : null
]);
+139 -41
View File
@@ -15,10 +15,12 @@ switch ($method) {
case 'GET':
if ($id > 0) {
$stmt = $db->prepare(
"SELECT id, nama, jenis, alamat, no_wa,
latitude, longitude, radius_meter,
created_at
FROM rumah_ibadah WHERE id = ?"
"SELECT ri.id, ri.nama, ri.jenis, ri.alamat, ri.no_wa,
ri.latitude, ri.longitude, ri.radius_meter,
ri.created_at, u.username
FROM rumah_ibadah ri
LEFT JOIN users u ON u.rumah_ibadah_id = ri.id
WHERE ri.id = ?"
);
$stmt->bind_param('i', $id);
$stmt->execute();
@@ -30,10 +32,12 @@ switch ($method) {
}
$row ? sendSuccess($row) : sendError('Data tidak ditemukan', 404);
} else {
$sql = "SELECT id, nama, jenis, alamat, no_wa,
latitude, longitude, radius_meter,
created_at
FROM rumah_ibadah ORDER BY created_at DESC";
$sql = "SELECT ri.id, ri.nama, ri.jenis, ri.alamat, ri.no_wa,
ri.latitude, ri.longitude, ri.radius_meter,
ri.created_at, u.username
FROM rumah_ibadah ri
LEFT JOIN users u ON u.rumah_ibadah_id = ri.id
ORDER BY ri.created_at DESC";
$result = $db->query($sql);
$rows = [];
while ($r = $result->fetch_assoc()) {
@@ -55,45 +59,139 @@ switch ($method) {
sendError("Field '$field' wajib diisi");
}
}
$stmt = $db->prepare(
"INSERT INTO rumah_ibadah
(nama, jenis, alamat, no_wa, latitude, longitude, radius_meter)
VALUES (?, ?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param(
'ssssddi',
$body['nama'],
$body['jenis'],
$body['alamat'],
$body['no_wa'],
$body['latitude'],
$body['longitude'],
$body['radius_meter']
);
executeOrFail($stmt, 'Gagal menyimpan rumah ibadah');
$newId = $db->insert_id;
sendSuccess(['id' => $newId], 'Rumah Ibadah berhasil disimpan', 201);
$db->begin_transaction();
try {
$stmt = $db->prepare(
"INSERT INTO rumah_ibadah
(nama, jenis, alamat, no_wa, latitude, longitude, radius_meter)
VALUES (?, ?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param(
'ssssddi',
$body['nama'],
$body['jenis'],
$body['alamat'],
$body['no_wa'],
$body['latitude'],
$body['longitude'],
$body['radius_meter']
);
executeOrFail($stmt, 'Gagal menyimpan rumah ibadah');
$newId = $db->insert_id;
// Handle user account creation
$username = isset($body['username']) ? trim($body['username']) : '';
$password = isset($body['password']) ? trim($body['password']) : '';
if ($username !== '') {
// Check if username already exists
$stmtUserCheck = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmtUserCheck->bind_param('s', $username);
$stmtUserCheck->execute();
if ($stmtUserCheck->get_result()->num_rows > 0) {
throw new Exception('Username sudah digunakan');
}
if ($password === '') {
throw new Exception('Password wajib diisi jika username ditentukan');
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmtUser = $db->prepare(
"INSERT INTO users (username, password, password_plain, role, rumah_ibadah_id) VALUES (?, ?, ?, 'petugas_lapangan', ?)"
);
$stmtUser->bind_param('sssi', $username, $hashedPassword, $password, $newId);
executeOrFail($stmtUser, 'Gagal membuat akun petugas lapangan');
}
$db->commit();
sendSuccess(['id' => $newId], 'Rumah Ibadah dan Akun Petugas berhasil disimpan', 201);
} catch (Exception $e) {
$db->rollback();
sendError($e->getMessage());
}
break;
// ── PUT: update data ──────────────────────────────────────────────
case 'PUT':
if ($id <= 0) sendError('ID wajib disertakan');
$body = getBody();
$stmt = $db->prepare(
"UPDATE rumah_ibadah
SET nama=?, jenis=?, alamat=?, no_wa=?,
latitude=?, longitude=?, radius_meter=?
WHERE id=?"
);
$stmt->bind_param(
'ssssddii',
$body['nama'], $body['jenis'],
$body['alamat'], $body['no_wa'],
$body['latitude'], $body['longitude'],
$body['radius_meter'], $id
);
executeOrFail($stmt, 'Gagal memperbarui rumah ibadah');
sendSuccess(null, 'Rumah Ibadah berhasil diperbarui');
$db->begin_transaction();
try {
$stmt = $db->prepare(
"UPDATE rumah_ibadah
SET nama=?, jenis=?, alamat=?, no_wa=?,
latitude=?, longitude=?, radius_meter=?
WHERE id=?"
);
$stmt->bind_param(
'ssssddii',
$body['nama'], $body['jenis'],
$body['alamat'], $body['no_wa'],
$body['latitude'], $body['longitude'],
$body['radius_meter'], $id
);
executeOrFail($stmt, 'Gagal memperbarui rumah ibadah');
// Handle user account creation/update (only if user is admin)
if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
$username = isset($body['username']) ? trim($body['username']) : '';
$password = isset($body['password']) ? trim($body['password']) : '';
if ($username !== '') {
// Check if username belongs to another user
$stmtUserCheck = $db->prepare("SELECT id, rumah_ibadah_id FROM users WHERE username = ?");
$stmtUserCheck->bind_param('s', $username);
$stmtUserCheck->execute();
$resUserCheck = $stmtUserCheck->get_result()->fetch_assoc();
if ($resUserCheck && (int)$resUserCheck['rumah_ibadah_id'] !== $id) {
throw new Exception('Username sudah digunakan oleh akun lain');
}
// Check if account already exists for this rumah_ibadah
$stmtExisting = $db->prepare("SELECT id FROM users WHERE rumah_ibadah_id = ?");
$stmtExisting->bind_param('i', $id);
$stmtExisting->execute();
$existing = $stmtExisting->get_result()->fetch_assoc();
if ($existing) {
// Update existing user
if ($password !== '') {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmtUpdate = $db->prepare("UPDATE users SET username = ?, password = ?, password_plain = ? WHERE id = ?");
$stmtUpdate->bind_param('sssi', $username, $hashedPassword, $password, $existing['id']);
} else {
$stmtUpdate = $db->prepare("UPDATE users SET username = ? WHERE id = ?");
$stmtUpdate->bind_param('si', $username, $existing['id']);
}
executeOrFail($stmtUpdate, 'Gagal memperbarui akun petugas lapangan');
} else {
// Create new user for this rumah ibadah
if ($password === '') {
throw new Exception('Password wajib diisi untuk pembuatan akun baru');
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmtUser = $db->prepare(
"INSERT INTO users (username, password, password_plain, role, rumah_ibadah_id) VALUES (?, ?, ?, 'petugas_lapangan', ?)"
);
$stmtUser->bind_param('sssi', $username, $hashedPassword, $password, $id);
executeOrFail($stmtUser, 'Gagal membuat akun petugas lapangan');
}
} else {
// Delete the associated user account if username is cleared
$stmtDeleteUser = $db->prepare("DELETE FROM users WHERE rumah_ibadah_id = ?");
$stmtDeleteUser->bind_param('i', $id);
$stmtDeleteUser->execute();
}
}
$db->commit();
sendSuccess(null, 'Rumah Ibadah berhasil diperbarui');
} catch (Exception $e) {
$db->rollback();
sendError($e->getMessage());
}
break;
// ── DELETE: hapus data ────────────────────────────────────────────
-105
View File
@@ -1,105 +0,0 @@
<?php
// api/spbu.php
require_once __DIR__ . '/config/database.php';
require_once __DIR__ . '/helpers/response.php';
setCORSHeaders();
$db = getDB();
$method = getMethod();
requireWriteAuth($method);
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
switch ($method) {
case 'GET':
if ($id > 0) {
$stmt = $db->prepare(
"SELECT id, nama, no_wa, alamat, buka_24jam,
latitude, longitude, created_at
FROM spbu WHERE id = ?"
);
$stmt->bind_param('i', $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) sendError('Data tidak ditemukan', 404);
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['buka_24jam']= (bool)$row['buka_24jam'];
sendSuccess($row);
} else {
$rows = [];
if (isset($_GET['buka_24jam'])) {
$val = (int)$_GET['buka_24jam'];
$stmt = $db->prepare(
"SELECT id, nama, no_wa, alamat, buka_24jam,
latitude, longitude, created_at
FROM spbu WHERE buka_24jam = ? ORDER BY nama"
);
$stmt->bind_param('i', $val);
$stmt->execute();
$res = $stmt->get_result();
} else {
$res = $db->query(
"SELECT id, nama, no_wa, alamat, buka_24jam,
latitude, longitude, created_at
FROM spbu ORDER BY nama"
);
}
while ($r = $res->fetch_assoc()) {
$r['latitude'] = (float)$r['latitude'];
$r['longitude'] = (float)$r['longitude'];
$r['buka_24jam']= (bool)$r['buka_24jam'];
$rows[] = $r;
}
sendSuccess($rows);
}
break;
case 'POST':
$body = getBody();
foreach (['nama','latitude','longitude'] as $f) {
if (empty($body[$f]) && $body[$f] !== 0) sendError("Field '$f' wajib diisi");
}
$buka = isset($body['buka_24jam']) ? (int)(bool)$body['buka_24jam'] : 0;
$stmt = $db->prepare(
"INSERT INTO spbu (nama, no_wa, alamat, buka_24jam, latitude, longitude)
VALUES (?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param('sssidd',
$body['nama'], $body['no_wa'], $body['alamat'],
$buka, $body['latitude'], $body['longitude']
);
executeOrFail($stmt, 'Gagal menyimpan data SPBU');
sendSuccess(['id' => $db->insert_id], 'SPBU berhasil disimpan', 201);
break;
case 'PUT':
if ($id <= 0) sendError('ID wajib disertakan');
$body = getBody();
$buka = isset($body['buka_24jam']) ? (int)(bool)$body['buka_24jam'] : 0;
$stmt = $db->prepare(
"UPDATE spbu
SET nama=?, no_wa=?, alamat=?, buka_24jam=?, latitude=?, longitude=?
WHERE id=?"
);
$stmt->bind_param('sssiddi',
$body['nama'], $body['no_wa'], $body['alamat'],
$buka, $body['latitude'], $body['longitude'], $id
);
executeOrFail($stmt, 'Gagal memperbarui data SPBU');
sendSuccess(null, 'SPBU berhasil diperbarui');
break;
case 'DELETE':
if ($id <= 0) sendError('ID wajib disertakan');
$stmt = $db->prepare("DELETE FROM spbu WHERE id=?");
$stmt->bind_param('i', $id);
executeOrFail($stmt, 'Gagal menghapus data SPBU');
$stmt->affected_rows > 0
? sendSuccess(null, 'SPBU berhasil dihapus')
: sendError('Data tidak ditemukan', 404);
break;
default:
sendError('Method tidak diizinkan', 405);
}