add poverty
This commit is contained in:
+161
-13
@@ -5,15 +5,17 @@ const TEMPAT_IBADAH_TYPES = ["Gereja Katolik", "GPIB", "GKI", "HKBP", "Pentakost
|
||||
|
||||
const createTempatIbadah = `
|
||||
CREATE TABLE IF NOT EXISTS tempat_ibadah (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(150) NOT NULL,
|
||||
jenis ENUM('Gereja Katolik','GPIB','GKI','HKBP','Pentakosta') NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
kelurahan VARCHAR(100) NOT NULL,
|
||||
lat DOUBLE NOT NULL,
|
||||
lng DOUBLE NOT NULL,
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(150) NOT NULL,
|
||||
jenis ENUM('Gereja Katolik','GPIB','GKI','HKBP','Pentakosta') NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
kelurahan VARCHAR(100) NOT NULL,
|
||||
lat DOUBLE NOT NULL,
|
||||
lng DOUBLE NOT NULL,
|
||||
kapasitas_bantuan INT NOT NULL DEFAULT 0,
|
||||
keterangan TEXT,
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`;
|
||||
|
||||
@@ -26,6 +28,12 @@ async function initTempatIbadahTable() {
|
||||
if (!columns.includes("created_at")) {
|
||||
await db.execute("ALTER TABLE tempat_ibadah ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
|
||||
}
|
||||
if (!columns.includes("kapasitas_bantuan")) {
|
||||
await db.execute("ALTER TABLE tempat_ibadah ADD COLUMN kapasitas_bantuan INT NOT NULL DEFAULT 0");
|
||||
}
|
||||
if (!columns.includes("keterangan")) {
|
||||
await db.execute("ALTER TABLE tempat_ibadah ADD COLUMN keterangan TEXT");
|
||||
}
|
||||
await db.execute(`
|
||||
UPDATE tempat_ibadah
|
||||
SET jenis = 'Gereja Katolik'
|
||||
@@ -291,6 +299,24 @@ async function ensureTables() {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`);
|
||||
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS pengajuan_bantuan (
|
||||
id_pengajuan INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_pemohon VARCHAR(160) NOT NULL,
|
||||
nik VARCHAR(20) NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
wilayah VARCHAR(120) NOT NULL DEFAULT 'Belum Dipetakan',
|
||||
alasan TEXT NOT NULL,
|
||||
status ENUM('menunggu','diverifikasi','disetujui','ditolak','selesai') NOT NULL DEFAULT 'menunggu',
|
||||
catatan_admin TEXT,
|
||||
id_keluarga INT,
|
||||
tanggal_pengajuan DATE NOT NULL,
|
||||
tanggal_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_pengajuan_keluarga FOREIGN KEY (id_keluarga) REFERENCES keluarga(id_keluarga) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`);
|
||||
|
||||
await seedDefaults();
|
||||
await calculateSaw();
|
||||
tablesReady = true;
|
||||
@@ -560,9 +586,33 @@ function validateIbadah(body) {
|
||||
if (!Number.isFinite(Number(body.lat)) || !Number.isFinite(Number(body.lng))) {
|
||||
return "Latitude dan longitude wajib valid.";
|
||||
}
|
||||
if (body.kapasitas_bantuan !== undefined && !Number.isFinite(Number(body.kapasitas_bantuan))) {
|
||||
return "Kapasitas bantuan harus berupa angka.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validatePengajuan(body) {
|
||||
const required = ["nama_pemohon", "nik", "alamat", "alasan", "tanggal_pengajuan"];
|
||||
for (const key of required) {
|
||||
if (!body[key] || String(body[key]).trim() === "") {
|
||||
return `${key} wajib diisi.`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Haversine distance in meters
|
||||
function haversineMeters(lat1, lng1, lat2, lng2) {
|
||||
const R = 6371000;
|
||||
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
||||
const dLng = ((lng2 - lng1) * Math.PI) / 180;
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLng / 2) ** 2;
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
}
|
||||
|
||||
router.use(async (req, res, next) => {
|
||||
try {
|
||||
await ensureTables();
|
||||
@@ -1000,8 +1050,8 @@ router.post("/ibadah", async (req, res) => {
|
||||
const body = req.body;
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO tempat_ibadah (nama, jenis, alamat, kelurahan, lat, lng) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
[body.nama, body.jenis, body.alamat, body.kelurahan, Number(body.lat), Number(body.lng)],
|
||||
"INSERT INTO tempat_ibadah (nama, jenis, alamat, kelurahan, lat, lng, kapasitas_bantuan, keterangan) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
[body.nama, body.jenis, body.alamat, body.kelurahan, Number(body.lat), Number(body.lng), toNumber(body.kapasitas_bantuan), body.keterangan || null],
|
||||
);
|
||||
res.status(201).json({ success: true, id: result.insertId, message: "Tempat ibadah berhasil ditambahkan." });
|
||||
} catch (err) {
|
||||
@@ -1015,8 +1065,8 @@ router.put("/ibadah/:id", async (req, res) => {
|
||||
const body = req.body;
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE tempat_ibadah SET nama=?, jenis=?, alamat=?, kelurahan=?, lat=?, lng=? WHERE id=? AND aktif = 1",
|
||||
[body.nama, body.jenis, body.alamat, body.kelurahan, Number(body.lat), Number(body.lng), req.params.id],
|
||||
"UPDATE tempat_ibadah SET nama=?, jenis=?, alamat=?, kelurahan=?, lat=?, lng=?, kapasitas_bantuan=?, keterangan=? WHERE id=? AND aktif = 1",
|
||||
[body.nama, body.jenis, body.alamat, body.kelurahan, Number(body.lat), Number(body.lng), toNumber(body.kapasitas_bantuan), body.keterangan || null, req.params.id],
|
||||
);
|
||||
if (!result.affectedRows) return res.status(404).json({ success: false, message: "Tempat ibadah tidak ditemukan." });
|
||||
res.json({ success: true, message: "Tempat ibadah berhasil diperbarui." });
|
||||
@@ -1025,6 +1075,104 @@ router.put("/ibadah/:id", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Nearest Church ─────────────────────────────────────────────────────────
|
||||
router.get("/nearest-church", async (req, res) => {
|
||||
try {
|
||||
const lat = Number(req.query.lat);
|
||||
const lng = Number(req.query.lng);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
|
||||
return res.status(400).json({ success: false, message: "lat dan lng wajib valid." });
|
||||
}
|
||||
const [rows] = await db.execute("SELECT * FROM tempat_ibadah WHERE aktif = 1 ORDER BY nama ASC");
|
||||
if (!rows.length) return res.json({ success: true, data: null });
|
||||
let nearest = null;
|
||||
let minDist = Infinity;
|
||||
for (const item of rows) {
|
||||
const dist = haversineMeters(lat, lng, Number(item.lat), Number(item.lng));
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
nearest = { ...item, jarak_meter: Math.round(dist) };
|
||||
}
|
||||
}
|
||||
// Also return all churches sorted by distance
|
||||
const sorted = rows
|
||||
.map((item) => ({ ...item, jarak_meter: Math.round(haversineMeters(lat, lng, Number(item.lat), Number(item.lng))) }))
|
||||
.sort((a, b) => a.jarak_meter - b.jarak_meter);
|
||||
res.json({ success: true, data: { nearest, all: sorted } });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Pengajuan Bantuan ───────────────────────────────────────────────────────
|
||||
router.get("/pengajuan", async (req, res) => {
|
||||
try {
|
||||
const status = req.query.status || "";
|
||||
const sql = status
|
||||
? "SELECT * FROM pengajuan_bantuan WHERE status = ? ORDER BY created_at DESC"
|
||||
: "SELECT * FROM pengajuan_bantuan ORDER BY created_at DESC";
|
||||
const params = status ? [status] : [];
|
||||
const [rows] = await db.execute(sql, params);
|
||||
res.json({ success: true, data: rows });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/pengajuan/:id", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute("SELECT * FROM pengajuan_bantuan WHERE id_pengajuan = ?", [req.params.id]);
|
||||
if (!rows.length) return res.status(404).json({ success: false, message: "Pengajuan tidak ditemukan." });
|
||||
res.json({ success: true, data: rows[0] });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/pengajuan", async (req, res) => {
|
||||
const error = validatePengajuan(req.body);
|
||||
if (error) return res.status(400).json({ success: false, message: error });
|
||||
const body = req.body;
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO pengajuan_bantuan (nama_pemohon, nik, alamat, wilayah, alasan, tanggal_pengajuan)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[body.nama_pemohon.trim(), body.nik.trim(), body.alamat.trim(), body.wilayah || "Belum Dipetakan", body.alasan.trim(), body.tanggal_pengajuan],
|
||||
);
|
||||
res.status(201).json({ success: true, id: result.insertId, message: "Pengajuan berhasil dikirim. Kami akan segera menghubungi Anda." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/pengajuan/:id/status", async (req, res) => {
|
||||
const { status, catatan_admin, id_keluarga } = req.body;
|
||||
const validStatuses = ["menunggu", "diverifikasi", "disetujui", "ditolak", "selesai"];
|
||||
if (!validStatuses.includes(status)) {
|
||||
return res.status(400).json({ success: false, message: "Status tidak valid." });
|
||||
}
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE pengajuan_bantuan SET status=?, catatan_admin=?, id_keluarga=? WHERE id_pengajuan=?",
|
||||
[status, catatan_admin || null, id_keluarga || null, req.params.id],
|
||||
);
|
||||
if (!result.affectedRows) return res.status(404).json({ success: false, message: "Pengajuan tidak ditemukan." });
|
||||
res.json({ success: true, message: `Status pengajuan diperbarui menjadi '${status}'.` });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/pengajuan/:id", async (req, res) => {
|
||||
try {
|
||||
const [result] = await db.execute("DELETE FROM pengajuan_bantuan WHERE id_pengajuan = ?", [req.params.id]);
|
||||
if (!result.affectedRows) return res.status(404).json({ success: false, message: "Pengajuan tidak ditemukan." });
|
||||
res.json({ success: true, message: "Pengajuan berhasil dihapus." });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/ibadah/:id", async (req, res) => {
|
||||
try {
|
||||
const [result] = await db.execute("UPDATE tempat_ibadah SET aktif = 0 WHERE id = ?", [req.params.id]);
|
||||
|
||||
Reference in New Issue
Block a user