Initial commit

This commit is contained in:
2026-06-11 02:31:09 +00:00
commit 9a2dc3c2cf
6706 changed files with 440580 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
const db = require('../config/database');
// Menarik seluruh data warga mustahik untuk keperluan dasbor manajemen admin
const ambil_semua_mustahik = (req, res) => {
const kueri_ambil = `SELECT id, nik, nama_kk, no_kk, jumlah_tanggungan, status_validasi FROM MUSTAHIK ORDER BY id DESC`;
db.all(kueri_ambil, [], (error_sql, baris_data) => {
if (error_sql) {
console.error('Gagal fetch data tabel MUSTAHIK:', error_sql.message);
return res.status(500).json({ error: 'Terjadi kegagalan sistem internal server.' });
}
res.status(200).json(baris_data);
});
};
// Menarik rekaman informasi detail warga beserta indikator kemiskinan BPS secara spesifik
const ambil_detail_mustahik = (req, res) => {
const id_warga = req.params.id;
const kueri_detail = `
SELECT m.*, i.luas_lantai_per_kapita, i.material_lantai, i.material_dinding,
i.fasilitas_sanitasi, i.sumber_air_minum, i.sumber_penerangan,
i.pekerjaan_utama, i.pendapatan_bulanan, i.frekuensi_makan, i.kepemilikan_aset
FROM MUSTAHIK m
LEFT JOIN INDIKATOR_BPS i ON m.id = i.id_mustahik
WHERE m.id = ?
`;
/* Baris kompleks: Kita menggunakan kueri SQL 'LEFT JOIN' untuk menyatukan baris data dari
tabel MUSTAHIK dan tabel INDIKATOR_BPS berdasarkan kecocokan Foreign Key (id_mustahik).
Metode 'db.get' dipilih karena pencarian ini bersifat unik dan hanya mengembalikan
satu baris data objek tunggal sesuai parameter ID warga. */
db.get(kueri_detail, [id_warga], (error_sql, baris_data) => {
if (error_sql) {
console.error('Gagal memproses query detail:', error_sql.message);
return res.status(500).json({ error: 'Terjadi kesalahan internal pada database peladen.' });
}
if (!baris_data) {
return res.status(404).json({ error: 'Rekaman data detail warga tidak ditemukan.' });
}
res.status(200).json(baris_data);
});
};
// Melakukan mutasi pembaruan status validasi secara aman parsial
const perbarui_status_validasi = (req, res) => {
const id_warga = req.params.id;
const { status_validasi } = req.body;
if (!status_validasi || !['Verified', 'Rejected', 'Pending'].includes(status_validasi)) {
return res.status(400).json({ error: 'Parameter status validasi tidak sah.' });
}
const kueri_update = `UPDATE MUSTAHIK SET status_validasi = ? WHERE id = ?`;
db.run(kueri_update, [status_validasi, id_warga], function(error_sql) {
if (error_sql) {
console.error('Gagal memproses DML UPDATE status:', error_sql.message);
return res.status(500).json({ error: 'Gagal memperbarui status verifikasi di basis data.' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Rekaman data warga tidak ditemukan.' });
}
res.status(200).json({ message: 'Status verifikasi data warga berhasil diperbarui.' });
});
};
module.exports = {
ambil_semua_mustahik,
ambil_detail_mustahik,
perbarui_status_validasi
};
+93
View File
@@ -0,0 +1,93 @@
const bcrypt = require('bcrypt');
const db = require('../config/database');
// Memvalidasi kredensial login petugas lapangan menggunakan pencarian SQLite
const proses_autentikasi_petugas = (req, res) => {
const { username, password } = req.body;
// Praktik keamanan: Memblokir eksekusi lebih lanjut jika muatan data kosong untuk menghindari error sistem
if (!username || !password) {
return res.status(400).json({ error: 'Username dan kata sandi petugas wajib diisi.' });
}
const kueri_petugas = `SELECT * FROM PETUGAS WHERE username = ?`;
db.get(kueri_petugas, [username], async (error_sql, baris_petugas) => {
if (error_sql) {
console.error('Gagal query tabel PETUGAS:', error_sql.message);
return res.status(500).json({ error: 'Terjadi kegagalan sistem internal server.' });
}
// Praktik keamanan: Memberikan pesan error generik agar peretas tidak mengetahui apakah username tersebut ada di database atau tidak
if (!baris_petugas) {
return res.status(401).json({ error: 'Kredensial login tidak valid.' });
}
try {
/* Baris kompleks: Melakukan verifikasi asinkron untuk mencocokkan kata sandi teks murni dari klien
dengan nilai hash terenkripsi di database menggunakan bcrypt.compare, yang secara inheren kebal
terhadap serangan timing-attack. */
const validasi_password = await bcrypt.compare(password, baris_petugas.password);
if (!validasi_password) {
return res.status(401).json({ error: 'Kredensial login tidak valid.' });
}
res.status(200).json({
message: 'Autentikasi petugas berhasil',
token: `simulasi_jwt_token_untuk_petugas_${baris_petugas.username}`
});
} catch (error_kripto) {
console.error('Error komputasi hash:', error_kripto.message);
res.status(500).json({ error: 'Gagal memproses verifikasi keamanan.' });
}
});
};
// Memvalidasi kredensial login administrator utama menggunakan pencarian SQLite
const proses_autentikasi_admin = (req, res) => {
const { username, password } = req.body;
// Praktik keamanan: Memblokir eksekusi lebih lanjut jika muatan data kosong
if (!username || !password) {
return res.status(400).json({ error: 'Username dan kata sandi admin wajib diisi.' });
}
const kueri_admin = `SELECT * FROM ADMIN WHERE username = ?`;
// Baris kompleks: Eksekusi pencarian data dengan parameterized query (?) memastikan nilai input diperlakukan murni sebagai string data, menutup celah eksploitasi SQL Injection secara absolut.
db.get(kueri_admin, [username], async (error_sql, baris_admin) => {
if (error_sql) {
console.error('Gagal query tabel ADMIN:', error_sql.message);
return res.status(500).json({ error: 'Terjadi kegagalan sistem internal server.' });
}
if (!baris_admin) {
return res.status(401).json({ error: 'Kredensial login admin tidak valid.' });
}
try {
const validasi_password = await bcrypt.compare(password, baris_admin.password);
if (!validasi_password) {
return res.status(401).json({ error: 'Kredensial login admin tidak valid.' });
}
res.status(200).json({
message: 'Autentikasi admin berhasil',
token: `simulasi_jwt_token_untuk_admin_${baris_admin.username}`
});
} catch (error_kripto) {
console.error('Error komputasi hash admin:', error_kripto.message);
res.status(500).json({ error: 'Gagal memproses verifikasi keamanan admin.' });
}
});
};
// Mengekspor semua fungsi agar dapat diakses oleh file router
module.exports = {
proses_autentikasi_petugas,
proses_autentikasi_admin
};
+65
View File
@@ -0,0 +1,65 @@
const fs = require('fs');
const csv = require('csv-parser');
const db = require('../config/database');
// Memproses unggahan berkas CSV dan memasukkan datanya ke tabel MUSTAHIK
const import_data_bps = (req, res) => {
try {
// Praktik keamanan: Memastikan ada berkas yang dikirimkan oleh klien sebelum sistem mencoba membacanya
if (!req.file) {
return res.status(400).json({ error: 'Tidak ada berkas CSV yang diunggah' });
}
const path_berkas = req.file.path;
const hasil_parsing = [];
// Baris kompleks: Kita menggunakan fs.createReadStream untuk membaca berkas secara bertahap (streaming) agar memori peladen tidak kelebihan beban jika ukuran CSV sangat besar, lalu mengalirkannya (pipe) ke modul csv-parser untuk diubah menjadi baris objek JavaScript secara asinkron.
fs.createReadStream(path_berkas)
.pipe(csv())
.on('data', (data) => hasil_parsing.push(data))
.on('end', () => {
db.serialize(() => {
db.run('BEGIN TRANSACTION');
// Data dari BPS langsung ditandai 'Verified' karena sudah merupakan data resmi
const perintah_insert = db.prepare(`INSERT INTO MUSTAHIK (nik, nama_kk, lat, lng, status_validasi) VALUES (?, ?, ?, ?, 'Verified')`);
hasil_parsing.forEach((baris) => {
// Menyesuaikan dengan standar header kolom CSV: NIK, Nama_KK, Latitude, Longitude
perintah_insert.run([baris.NIK, baris.Nama_KK, baris.Latitude, baris.Longitude], (err) => {
if (err) {
console.error('Gagal mengimpor baris data:', err.message);
}
});
});
perintah_insert.finalize();
db.run('COMMIT', (err) => {
if (err) {
db.run('ROLLBACK');
return res.status(500).json({ error: 'Gagal menyimpan data impor ke basis data' });
}
// Praktik keamanan dan kebersihan: Menghapus berkas CSV sementara dari peladen secara otomatis setelah seluruh datanya berhasil diekstrak ke dalam tabel
fs.unlinkSync(path_berkas);
res.status(200).json({ message: `Berhasil mengimpor ${hasil_parsing.length} data baseline BPS` });
});
});
})
.on('error', (error) => {
// Penanganan kesalahan otomatis saat file CSV korup
fs.unlinkSync(path_berkas);
res.status(500).json({ error: 'Kesalahan saat membaca struktur baris CSV' });
});
} catch (error) {
console.error('Kesalahan sistem impor:', error);
res.status(500).json({ error: 'Kegagalan pemrosesan internal server' });
}
};
module.exports = {
import_data_bps
};
+79
View File
@@ -0,0 +1,79 @@
const db = require('../config/database');
// Mengambil data titik warga dan mengubahnya ke format GeoJSON
const dapatkan_data_publik = async (req, res) => {
try {
// Praktik keamanan: Query secara eksplisit hanya memilih data dengan status 'Verified' agar draf data lapangan tidak bocor ke publik
const query_publik = `SELECT lat, lng FROM MUSTAHIK WHERE status_validasi = 'Verified'`;
db.all(query_publik, [], (err, rows) => {
if (err) {
console.error('Gagal mengambil data peta:', err.message);
return res.status(500).json({ error: 'Kegagalan mengambil titik koordinat' });
}
// Baris kompleks: Melakukan pemetaan array objek dari baris basis data menjadi struktur hierarki GeoJSON standar. Format FeatureCollection ini adalah spesifikasi wajib yang dipahami oleh pustaka LeafletJS untuk merender penanda di atas peta.
const geojson_data = {
type: 'FeatureCollection',
features: rows.map(row => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [row.lng, row.lat]
},
properties: {
keterangan: 'Lokasi Terverifikasi'
}
}))
};
res.status(200).json(geojson_data);
});
} catch (error) {
console.error('Kesalahan sistem pemetaan:', error);
res.status(500).json({ error: 'Kegagalan pemrosesan internal server' });
}
};
// Mengambil data rumah ibadah dan mengubahnya ke format GeoJSON
const dapatkan_rumah_ibadah = async (req, res) => {
try {
const query_rumah_ibadah = `SELECT nama_tempat, jenis_agama, lat, lng, alamat FROM RUMAH_IBADAH`;
db.all(query_rumah_ibadah, [], (err, rows) => {
if (err) {
console.error('Gagal mengambil data rumah ibadah:', err.message);
return res.status(500).json({ error: 'Kegagalan mengambil titik koordinat rumah ibadah' });
}
// Baris kompleks: Selain koordinat, kita menyisipkan atribut 'properties' yang berisi nama tempat dan jenis agama agar nantinya antarmuka LeafletJS bisa menampilkan popup informasi (tooltip) saat marker diklik oleh pengguna.
const geojson_data = {
type: 'FeatureCollection',
features: rows.map(row => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [row.lng, row.lat]
},
properties: {
nama_tempat: row.nama_tempat,
jenis_agama: row.jenis_agama,
alamat: row.alamat
}
}))
};
res.status(200).json(geojson_data);
});
} catch (error) {
console.error('Kesalahan sistem pemetaan rumah ibadah:', error);
res.status(500).json({ error: 'Kegagalan pemrosesan internal server' });
}
};
module.exports = {
dapatkan_data_publik,
dapatkan_rumah_ibadah
};
+166
View File
@@ -0,0 +1,166 @@
const db = require('../config/database');
// Mengambil data status bantuan warga beserta lokasi pangkalan ibadah
const cek_status_nik = (req, res) => {
const nik_warga = req.params.nik;
// Praktik keamanan: Memastikan input NIK murni berupa 16 digit angka untuk mencegah manipulasi query
if (!/^\d{16}$/.test(nik_warga)) {
return res.status(400).json({ error: 'Format NIK tidak valid.' });
}
// Baris kompleks: Operasi LEFT JOIN menghubungkan tabel MUSTAHIK dengan RUMAH_IBADAH berdasarkan Foreign Key id_rumah_ibadah untuk menarik koordinat pangkalan distribusi secara simultan dalam satu siklus I/O.
const kueri_pencarian = `
SELECT m.nik, m.nama_kk, m.status_validasi, m.lat, m.lng, m.agama,
r.nama_tempat AS rumah_ibadah_nama,
r.lat AS rumah_ibadah_lat,
r.lng AS rumah_ibadah_lng
FROM MUSTAHIK m
LEFT JOIN RUMAH_IBADAH r ON m.id_rumah_ibadah = r.id
WHERE m.nik = ?
`;
db.get(kueri_pencarian, [nik_warga], (error_sistem, baris_data) => {
if (error_sistem) {
console.error('Sistem gagal membaca tabel gabungan:', error_sistem.message);
return res.status(500).json({ error: 'Terjadi kegagalan server internal.' });
}
if (!baris_data) {
return res.status(404).json({ error: 'Data tidak ditemukan.' });
}
res.status(200).json(baris_data);
});
};
// Mengontrol alur penerimaan data survei lapangan beserta penentuan pangkalan bantuan
const submit_survey_data = async (req, res) => {
try {
const {
nik,
nama_kk,
no_kk,
jumlah_tanggungan,
lat,
lng,
agama,
indikator_bps
} = req.body;
// Praktik keamanan: Validasi input dasar untuk mencegah insersi data kosong (null pointer exception)
if (!nik || !nama_kk || !lat || !lng || !agama || !indikator_bps) {
return res.status(400).json({ error: 'Payload data survei tidak lengkap dari klien.' });
}
// Memulai transaksi basis data untuk menjaga integritas penyimpanan parsial
db.serialize(() => {
db.run('BEGIN TRANSACTION');
// Baris kompleks: Memanfaatkan db.serialize untuk memastikan kueri SELECT pencarian pangkalan ibadah selesai dieksekusi sebelum beralih ke perintah INSERT transaksional berikutnya guna menghindari balapan data (race condition).
const kueri_cari_pangkalan = `SELECT id FROM RUMAH_IBADAH WHERE agama = ? LIMIT 1`;
db.get(kueri_cari_pangkalan, [agama], (error_cari, baris_pangkalan) => {
if (error_cari) {
db.run('ROLLBACK');
console.error('Gagal mencocokkan rumah ibadah:', error_cari.message);
return res.status(500).json({ error: 'Gagal memproses penentuan lokasi bantuan.' });
}
const id_pangkalan_terpilih = baris_pangkalan ? baris_pangkalan.id : null;
// 1. Menyimpan data identitas dan relasi pangkalan ke tabel MUSTAHIK
const kueri_mustahik = `
INSERT INTO MUSTAHIK (nik, nama_kk, no_kk, jumlah_tanggungan, lat, lng, agama, id_rumah_ibadah)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
db.run(kueri_mustahik, [nik, nama_kk, no_kk, jumlah_tanggungan, lat, lng, agama, id_pangkalan_terpilih], function(error_mustahik) {
if (error_mustahik) {
db.run('ROLLBACK');
console.error('Gagal menyimpan DML MUSTAHIK:', error_mustahik.message);
return res.status(500).json({ error: 'Gagal menyimpan data identitas warga, NIK mungkin sudah terdaftar.' });
}
const id_mustahik_baru = this.lastID;
const {
luas_lantai_per_kapita,
material_lantai,
material_dinding,
fasilitas_sanitasi,
sumber_air_minum,
sumber_penerangan,
pekerjaan_utama,
pendapatan_bulanan,
frekuensi_makan,
kepemilikan_aset
} = indikator_bps;
// 2. Menyimpan 10 parameter kondisi ke tabel INDIKATOR_BPS
const kueri_indikator = `
INSERT INTO INDIKATOR_BPS (
id_mustahik, luas_lantai_per_kapita, material_lantai, material_dinding,
fasilitas_sanitasi, sumber_air_minum, sumber_penerangan,
pekerjaan_utama, pendapatan_bulanan, frekuensi_makan, kepemilikan_aset
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
db.run(kueri_indikator, [
id_mustahik_baru,
luas_lantai_per_kapita,
material_lantai,
material_dinding,
fasilitas_sanitasi,
sumber_air_minum,
sumber_penerangan,
pekerjaan_utama,
pendapatan_bulanan,
frekuensi_makan,
kepemilikan_aset
], function(error_indikator) {
if (error_indikator) {
db.run('ROLLBACK');
console.error('Gagal menyimpan DML INDIKATOR_BPS:', error_indikator.message);
return res.status(500).json({ error: 'Gagal merekam indikator ekonomi BPS.' });
}
// Secara permanen menerapkan semua kueri yang tertunda ke dalam file SQLite
db.run('COMMIT');
res.status(201).json({
message: 'Data survei kriteria BPS dan pangkalan bantuan berhasil disimpan.',
id_mustahik: id_mustahik_baru
});
});
});
});
});
} catch (error_sistem) {
console.error('Kesalahan eksekusi sistem:', error_sistem);
res.status(500).json({ error: 'Kegagalan pemrosesan internal server.' });
}
};
// Menarik agregasi data statistik untuk ditampilkan di halaman publik
const ambil_statistik_publik = (req, res) => {
const kueri_statistik = `
SELECT
COUNT(CASE WHEN status_validasi = 'Verified' THEN 1 END) AS terverifikasi,
COUNT(CASE WHEN status_validasi = 'Pending' THEN 1 END) AS diproses
FROM MUSTAHIK
`;
db.get(kueri_statistik, [], (error_sql, baris_data) => {
if (error_sql) {
console.error('Gagal menarik agregasi statistik:', error_sql.message);
return res.status(500).json({ error: 'Gagal memuat statistik.' });
}
res.status(200).json(baris_data);
});
};
module.exports = {
cek_status_nik,
submit_survey_data,
ambil_statistik_publik
};