generated from MuhammadIqbalMaulana/UAS-SIG
Initial commit
This commit is contained in:
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
Reference in New Issue
Block a user