diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6e43f79 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.env +*.log diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..edd46b9 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +DB_HOST=localhost +DB_PORT=3306 +DB_DATABASE=webgis_spbu +DB_USERNAME=root +DB_PASSWORD= + +# Alternatif nama env yang juga didukung: +# MYSQL_HOST=localhost +# MYSQL_PORT=3306 +# MYSQL_DATABASE=webgis_spbu +# MYSQL_USER=root +# MYSQL_PASSWORD= diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..39afedb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM php:8.2-apache + +RUN docker-php-ext-install mysqli + +COPY . /var/www/html/ + +RUN chown -R www-data:www-data /var/www/html + +EXPOSE 80 diff --git a/README.md b/README.md index 5ff0fce..5a6ae49 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,17 @@ Aplikasi dibuat menggunakan PHP native, MySQL, JavaScript, Leaflet.js, Leaflet D ### 1. WebGIS Geometri -Lokasi: `geometri/index.php` +Lokasi publik: `geometri/index.php` +Lokasi admin: `geometri/admin.php` Modul ini digunakan untuk latihan dan pengelolaan data geometri dasar. -Fitur: +Fitur publik: + +- Menampilkan data point, jalan/polyline, dan polygon/parsil. +- Melihat detail data melalui popup peta. + +Fitur admin: - Menampilkan data point, jalan/polyline, dan polygon/parsil. - Menambah point dengan nama dan kode point. @@ -33,12 +39,20 @@ Fitur: ### 2. WebGIS SPBU -Lokasi: `spbu/index.php` +Lokasi publik: `spbu/index.php` +Lokasi admin: `spbu/admin.php` Modul ini digunakan untuk memetakan lokasi SPBU di Pontianak. -Fitur: +Fitur publik: +- Menampilkan titik SPBU. +- Membedakan SPBU 24 jam dan tidak 24 jam. +- Toggle layer untuk menampilkan/menyembunyikan SPBU berdasarkan status. + +Fitur admin: + +- Login admin. - Menampilkan titik SPBU. - Membedakan SPBU 24 jam dan tidak 24 jam. - Menambah titik SPBU dari peta. @@ -148,77 +162,110 @@ Pastikan perangkat sudah memiliki: 3. Jalankan Apache dan MySQL dari XAMPP Control Panel. -4. Buat database MySQL, contoh: +4. Siapkan konfigurasi database melalui environment variable. Contoh variabel tersedia di `.env.example`. + + ```text + DB_HOST=localhost + DB_PORT=3306 + DB_DATABASE=webgis_spbu + DB_USERNAME=root + DB_PASSWORD= + ``` + + Pada Coolify, isi variabel tersebut dari menu Environment Variables project. Jangan menulis password database langsung di `php/db.php`. + +5. Jalankan migration database yang sudah disediakan. ```sql - CREATE DATABASE webgis_spbu; + SOURCE migrations/001_init_database.sql; ``` -5. Sesuaikan konfigurasi koneksi database pada file `php/db.php`. + File migration tersebut akan membuat database `webgis_spbu`, seluruh tabel aplikasi, dan akun default. - ```php - $conn = new mysqli("localhost", "root", "password_database", "webgis_spbu"); - ``` - -6. Buat tabel yang dibutuhkan. Jika belum memiliki file `.sql`, gunakan struktur dasar berikut sebagai acuan. - - ```sql - CREATE TABLE spbu ( - id INT AUTO_INCREMENT PRIMARY KEY, - nama VARCHAR(150) NOT NULL, - no_spbu VARCHAR(100) NOT NULL, - status VARCHAR(50) NOT NULL, - latitude DOUBLE NOT NULL, - longitude DOUBLE NOT NULL - ); - - CREATE TABLE jalan ( - id INT AUTO_INCREMENT PRIMARY KEY, - nama VARCHAR(150) NOT NULL, - status VARCHAR(50) NOT NULL, - panjang DOUBLE DEFAULT 0, - geom LONGTEXT NOT NULL - ); - - CREATE TABLE parsil ( - id INT AUTO_INCREMENT PRIMARY KEY, - nama VARCHAR(150) NOT NULL, - status VARCHAR(50) NOT NULL, - luas DOUBLE DEFAULT 0, - geom LONGTEXT NOT NULL - ); - - CREATE TABLE masjid ( - id INT AUTO_INCREMENT PRIMARY KEY, - nama VARCHAR(150) NOT NULL, - pic VARCHAR(150) NOT NULL, - radius DOUBLE DEFAULT 500, - latitude DOUBLE NOT NULL, - longitude DOUBLE NOT NULL, - radius_layanan DOUBLE DEFAULT 500 - ); - - CREATE TABLE penduduk_miskin ( - id INT AUTO_INCREMENT PRIMARY KEY, - nama_kk VARCHAR(150) NOT NULL, - jumlah_anggota INT DEFAULT 1, - alamat TEXT NOT NULL, - latitude DOUBLE NOT NULL, - longitude DOUBLE NOT NULL, - penghasilan VARCHAR(50), - kondisi_rumah VARCHAR(50), - sumber_air VARCHAR(50), - skor_kerentanan DOUBLE DEFAULT 0, - jenis_bantuan VARCHAR(100) - ); - ``` - -7. Buka aplikasi melalui browser: +6. Buka aplikasi melalui browser: ```text http://localhost/WebGIS2026/ ``` +## Catatan Deploy Coolify + +Untuk deploy di Coolify, project ini dapat dijalankan sebagai aplikasi PHP native. Pastikan project memiliki service MySQL/MariaDB, lalu set environment variable berikut pada aplikasi: + +```text +DB_HOST= +DB_PORT=3306 +DB_DATABASE=webgis_spbu +DB_USERNAME= +DB_PASSWORD= +``` + +Jika service database di Coolify memakai nama variabel `MYSQL_*`, aplikasi juga mendukung: + +```text +MYSQL_HOST= +MYSQL_PORT=3306 +MYSQL_DATABASE=webgis_spbu +MYSQL_USER= +MYSQL_PASSWORD= +``` + +Docker tidak wajib jika Coolify/Nixpacks berhasil mendeteksi aplikasi PHP dan menyediakan web server PHP. Namun repository ini sudah menyediakan `Dockerfile`, sehingga mode deployment paling stabil di Coolify adalah memilih build dari Dockerfile. Dockerfile tersebut memakai `php:8.2-apache` dan memasang ekstensi `mysqli`. + +## Menjalankan Dengan Docker Compose + +Repository ini juga menyediakan `docker-compose.yml` untuk menjalankan seluruh service secara lokal: + +- `app`: aplikasi PHP Apache +- `db`: MySQL 8.4 +- `phpmyadmin`: panel database + +Jalankan: + +```bash +docker compose up -d --build +``` + +URL akses: + +```text +Aplikasi: http://localhost:8080 +phpMyAdmin: http://localhost:8081 +``` + +Kredensial database default Docker Compose: + +```text +Host: db +Port internal: 3306 +Port host/lokal: 3307 +Database: webgis_spbu +Username: webgis_user +Password: webgis_password +Root password: root_password +``` + +File migration di folder `migrations/` akan otomatis dijalankan oleh MySQL saat volume database pertama kali dibuat. File `001_init_database.sql` membuat struktur tabel dan akun login, sedangkan `002_seed_sample_data.sql` mengisi data contoh untuk Docker Compose. + +Data contoh yang disediakan: + +- 3 point geometri +- 3 polyline jalan +- 3 polygon/parsil +- 7 SPBU, terdiri dari 5 SPBU 24 jam dan 2 tidak 24 jam +- 3 masjid dengan radius layanan +- 14 data penduduk miskin, termasuk minimal 1 blind spot +- akun `admin / password` dan `publik / password` + +Jika migration atau seed berubah dan ingin mengulang dari awal, hentikan container lalu hapus volume database: + +```bash +docker compose down -v +docker compose up -d --build +``` + +Catatan: perintah `down -v` akan menghapus data database container. + ## Cara Penggunaan ### Membuka Dashboard Utama @@ -254,20 +301,28 @@ Pastikan perangkat sudah memiliki: 6. Gunakan tombol layer `24 Jam` dan `Tidak 24 Jam` untuk memfilter tampilan. 7. Klik marker SPBU untuk melihat detail atau menghapus data. -### Login Admin Modul Sosial +### Login Admin -1. Buka `http://localhost/WebGIS2026/sosial/`. +1. Buka salah satu dashboard publik: + - `http://localhost/WebGIS2026/geometri/` + - `http://localhost/WebGIS2026/spbu/` + - `http://localhost/WebGIS2026/sosial/` 2. Klik tombol `Admin Login`. -3. Masukkan akun default: +3. Masukkan salah satu akun default: ```text + Role Admin Username: admin - Password: admin123 + Password: password + + Role Publik + Username: publik + Password: password ``` -4. Setelah berhasil login, sistem akan membuka panel admin di `sosial/admin.php`. +4. Akun `admin` akan diarahkan ke panel admin sesuai project yang sedang dibuka. Akun `publik` diarahkan ke dashboard publik. Dashboard publik juga dapat diakses tanpa login. -Catatan: ubah kredensial default di `sosial/login.php` sebelum proyek digunakan di lingkungan publik. +Catatan: akun default berasal dari tabel `users`. Ubah atau tambah akun melalui database, bukan dari `sosial/login.php`. ### Menambah Data Masjid @@ -332,7 +387,8 @@ Beberapa endpoint utama yang digunakan aplikasi: | `php/show_geometri.php` | Mengambil data point, jalan, dan parsil | | `php/show_spbu.php` | Mengambil data SPBU | | `php/show_sosial.php` | Mengambil data masjid dan penduduk miskin | -| `php/insert.php` | Menambah data SPBU/point | +| `php/insert_point.php` | Menambah data point geometri | +| `php/insert.php` | Menambah data SPBU | | `php/insert_line.php` | Menambah data jalan | | `php/insert_polygon.php` | Menambah data parsil | | `php/insert_masjid.php` | Menambah data masjid | @@ -355,7 +411,7 @@ Beberapa endpoint utama yang digunakan aplikasi: - Import/export data GeoJSON. - Filter data berdasarkan kecamatan atau kelurahan. -- Role user untuk admin, petugas, dan viewer. +- Pemisahan akses admin dan publik untuk seluruh modul WebGIS. - Upload dokumentasi/foto penduduk atau masjid. - Dashboard laporan bantuan sosial. - Integrasi data batas administrasi. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ba851d9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: webgis_app + ports: + - "8080:80" + environment: + DB_HOST: db + DB_PORT: 3306 + DB_DATABASE: webgis_spbu + DB_USERNAME: webgis_user + DB_PASSWORD: webgis_password + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: mysql:8.4 + container_name: webgis_db + environment: + MYSQL_DATABASE: webgis_spbu + MYSQL_USER: webgis_user + MYSQL_PASSWORD: webgis_password + MYSQL_ROOT_PASSWORD: root_password + ports: + - "3307:3306" + volumes: + - webgis_db_data:/var/lib/mysql + - ./migrations:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot_password"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + phpmyadmin: + image: phpmyadmin:5.2 + container_name: webgis_phpmyadmin + ports: + - "8081:80" + environment: + PMA_HOST: db + PMA_PORT: 3306 + depends_on: + db: + condition: service_healthy + restart: unless-stopped + +volumes: + webgis_db_data: diff --git a/geometri/admin.php b/geometri/admin.php new file mode 100644 index 0000000..211b520 --- /dev/null +++ b/geometri/admin.php @@ -0,0 +1,51 @@ + + + + + + + Panel Admin - WebGIS Geometri + + + + + + + + + + +
+ + +
+ Kelola Point, Polyline, Polygon + +
+
Point
+
Polyline/Jalan
+
Polygon/Parsil
+
+ +
+
+ Keluar +
+
+ +
+ + + + + + + + + diff --git a/geometri/index.php b/geometri/index.php index a226996..83e012e 100644 --- a/geometri/index.php +++ b/geometri/index.php @@ -9,7 +9,6 @@ - @@ -23,7 +22,13 @@
Point, Polyline, Polygon -
+ + +
+ +
Point
@@ -39,9 +44,8 @@
- - - + + diff --git a/geometri/login.php b/geometri/login.php new file mode 100644 index 0000000..2159500 --- /dev/null +++ b/geometri/login.php @@ -0,0 +1,92 @@ +prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1"); + if (!$stmt) { + $error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.'; + } else { + $stmt->bind_param("s", $user); + $stmt->execute(); + $result = $stmt->get_result(); + $account = $result ? $result->fetch_assoc() : null; + + if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) { + session_regenerate_id(true); + $_SESSION['user_id'] = intval($account['id']); + $_SESSION['username'] = $account['username']; + $_SESSION['role'] = $account['role']; + $_SESSION['full_name'] = $account['full_name']; + $_SESSION['is_admin'] = $account['role'] === 'admin'; + + header('Location: ' . ($account['role'] === 'admin' ? 'admin.php' : 'index.php')); + exit; + } + + $error = 'Username atau password salah.'; + $stmt->close(); + } +} + +if (isset($conn)) { + $conn->close(); +} +?> + + + + + + Login Admin - WebGIS Geometri + + + + + + + + diff --git a/geometri/logout.php b/geometri/logout.php new file mode 100644 index 0000000..e5ab4b6 --- /dev/null +++ b/geometri/logout.php @@ -0,0 +1,6 @@ + diff --git a/js/draw_geometri.js b/js/draw_geometri.js index 5ed1537..cfa3d60 100644 --- a/js/draw_geometri.js +++ b/js/draw_geometri.js @@ -13,20 +13,23 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { var drawnItems = new L.FeatureGroup().addTo(map); var layerGroupPoint = L.featureGroup().addTo(map); var currentLayer = null; +var isAdmin = window.IS_ADMIN === true; -var drawControl = new L.Control.Draw({ - position: 'topleft', - edit: { featureGroup: drawnItems }, - draw: { - marker: true, - polyline: { shapeOptions: { color: '#a855f7', weight: 3 } }, - polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } }, - rectangle: false, - circle: false, - circlemarker: false - } -}); -map.addControl(drawControl); +if (isAdmin) { + var drawControl = new L.Control.Draw({ + position: 'topleft', + edit: { featureGroup: drawnItems }, + draw: { + marker: true, + polyline: { shapeOptions: { color: '#a855f7', weight: 3 } }, + polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } }, + rectangle: false, + circle: false, + circlemarker: false + } + }); + map.addControl(drawControl); +} function getLineColor(status) { return status === 'Nasional' ? '#ef4444' : status === 'Provinsi' ? '#38bdf8' : '#a855f7'; @@ -88,9 +91,11 @@ function buildInfoPopup(d, type) {
Luas${fmtNum(d.luas)} m²
`; } + var deleteButton = isAdmin ? `` : ''; + return `
${icons[type]} ${titles[type]}
-
${rows}
+
${rows}${deleteButton}
`; } @@ -142,22 +147,24 @@ function hapusData(id, type) { }); } -map.on(L.Draw.Event.CREATED, function(e) { - var layer = e.layer; +if (isAdmin) { + map.on(L.Draw.Event.CREATED, function(e) { + var layer = e.layer; - if (e.layerType === 'marker') { - drawnItems.addLayer(layer); - handlePoint(layer); - } - if (e.layerType === 'polyline') { - drawnItems.addLayer(layer); - handleLine(layer); - } - if (e.layerType === 'polygon') { - drawnItems.addLayer(layer); - handlePolygon(layer); - } -}); + if (e.layerType === 'marker') { + drawnItems.addLayer(layer); + handlePoint(layer); + } + if (e.layerType === 'polyline') { + drawnItems.addLayer(layer); + handleLine(layer); + } + if (e.layerType === 'polygon') { + drawnItems.addLayer(layer); + handlePolygon(layer); + } + }); +} function handlePoint(layer) { var ll = layer.getLatLng(); @@ -180,7 +187,7 @@ function savePoint(lat, lng) { alert('Nama dan kode point wajib diisi!'); return; } - fetch('../php/insert.php', { + fetch('../php/insert_point.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nama, no, status, lat, lng }) @@ -294,38 +301,40 @@ function savePolygon() { }); } -map.on('draw:edited', function(e) { - e.layers.eachLayer(layer => { - if (!layer._db_id) return; - if (layer._type === 'line') { - fetch('../php/update_line.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - id: layer._db_id, - geom: JSON.stringify(layer.getLatLngs()), - panjang: getLength(layer.getLatLngs()) - }) - }); - } - if (layer._type === 'polygon') { - fetch('../php/update_polygon.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - id: layer._db_id, - geom: JSON.stringify(layer.getLatLngs()), - luas: L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]) - }) - }); - } +if (isAdmin) { + map.on('draw:edited', function(e) { + e.layers.eachLayer(layer => { + if (!layer._db_id) return; + if (layer._type === 'line') { + fetch('../php/update_line.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: layer._db_id, + geom: JSON.stringify(layer.getLatLngs()), + panjang: getLength(layer.getLatLngs()) + }) + }); + } + if (layer._type === 'polygon') { + fetch('../php/update_polygon.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: layer._db_id, + geom: JSON.stringify(layer.getLatLngs()), + luas: L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]) + }) + }); + } + }); }); -}); -map.on('draw:deleted', function(e) { - e.layers.eachLayer(layer => { - if (layer._db_id && layer._type) { - fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`); - } + map.on('draw:deleted', function(e) { + e.layers.eachLayer(layer => { + if (layer._db_id && layer._type) { + fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`); + } + }); }); -}); +} diff --git a/js/draw_spbu_point.js b/js/draw_spbu_point.js index c71db2e..889d575 100644 --- a/js/draw_spbu_point.js +++ b/js/draw_spbu_point.js @@ -15,6 +15,7 @@ var layerGroup24 = L.featureGroup().addTo(map); var layerGroupTidak = L.featureGroup().addTo(map); var show24 = true; var showTidak = true; +var isAdmin = window.IS_ADMIN === true; function toggleLayer(type) { if (type === '24jam') { @@ -28,19 +29,21 @@ function toggleLayer(type) { } } -var drawControl = new L.Control.Draw({ - position: 'topleft', - edit: { featureGroup: drawnItems }, - draw: { - marker: true, - polyline: false, - polygon: false, - rectangle: false, - circle: false, - circlemarker: false - } -}); -map.addControl(drawControl); +if (isAdmin) { + var drawControl = new L.Control.Draw({ + position: 'topleft', + edit: { featureGroup: drawnItems }, + draw: { + marker: true, + polyline: false, + polygon: false, + rectangle: false, + circle: false, + circlemarker: false + } + }); + map.addControl(drawControl); +} function makeIcon(status) { var c = status === '24 Jam' ? '#4ade80' : '#f87171'; @@ -60,13 +63,14 @@ function statusBadge(status) { } function buildInfoPopup(d) { + var deleteButton = isAdmin ? `` : ''; return `
⛽ SPBU - ${d.nama}
No. SPBU${d.no_spbu}
Status${statusBadge(d.status)}
Koordinat${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}
- + ${deleteButton}
`; } @@ -76,7 +80,7 @@ fetch('../php/show_spbu.php') .then(data => { data.forEach(d => { var layer = L.marker([d.latitude, d.longitude], { icon: makeIcon(d.status) }); - layer._type = 'point'; + layer._type = 'spbu'; layer._db_id = d.id; layer.bindPopup(buildInfoPopup(d), { maxWidth: 260 }); d.status === '24 Jam' ? layerGroup24.addLayer(layer) : layerGroupTidak.addLayer(layer); @@ -94,12 +98,14 @@ function hapusData(id, type) { }); } -map.on(L.Draw.Event.CREATED, function(e) { - if (e.layerType !== 'marker') return; - var layer = e.layer; - drawnItems.addLayer(layer); - handlePoint(layer); -}); +if (isAdmin) { + map.on(L.Draw.Event.CREATED, function(e) { + if (e.layerType !== 'marker') return; + var layer = e.layer; + drawnItems.addLayer(layer); + handlePoint(layer); + }); +} function handlePoint(layer) { var ll = layer.getLatLng(); diff --git a/migrations/001_init_database.sql b/migrations/001_init_database.sql new file mode 100644 index 0000000..2c79b6a --- /dev/null +++ b/migrations/001_init_database.sql @@ -0,0 +1,88 @@ +CREATE DATABASE IF NOT EXISTS webgis_spbu + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +USE webgis_spbu; + +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + role ENUM('admin', 'publik') NOT NULL DEFAULT 'publik', + full_name VARCHAR(150) NOT NULL, + is_active TINYINT(1) NOT NULL DEFAULT 1, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS spbu ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(150) NOT NULL, + no_spbu VARCHAR(100) NOT NULL, + status VARCHAR(50) NOT NULL, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS points ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(150) NOT NULL, + kode_point VARCHAR(100) NOT NULL, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS jalan ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(150) NOT NULL, + status VARCHAR(50) NOT NULL, + panjang DOUBLE DEFAULT 0, + geom LONGTEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS parsil ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(150) NOT NULL, + status VARCHAR(50) NOT NULL, + luas DOUBLE DEFAULT 0, + geom LONGTEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS masjid ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama VARCHAR(150) NOT NULL, + pic VARCHAR(150) NOT NULL, + radius DOUBLE DEFAULT 500, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + radius_layanan DOUBLE DEFAULT 500, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS penduduk_miskin ( + id INT AUTO_INCREMENT PRIMARY KEY, + nama_kk VARCHAR(150) NOT NULL, + jumlah_anggota INT DEFAULT 1, + alamat TEXT NOT NULL, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + penghasilan VARCHAR(50), + kondisi_rumah VARCHAR(50), + sumber_air VARCHAR(50), + skor_kerentanan DOUBLE DEFAULT 0, + jenis_bantuan VARCHAR(100), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT INTO users (username, password_hash, role, full_name, is_active) VALUES +('admin', '$2y$10$X/2pN5ag6A/SmFlm/bZJq.V1Ul41mm3lX1PNDY0ZBncinKhEnQvpO', 'admin', 'Administrator WebGIS', 1), +('publik', '$2y$10$X/2pN5ag6A/SmFlm/bZJq.V1Ul41mm3lX1PNDY0ZBncinKhEnQvpO', 'publik', 'Pengguna Publik', 1) +ON DUPLICATE KEY UPDATE + password_hash = VALUES(password_hash), + role = VALUES(role), + full_name = VALUES(full_name), + is_active = VALUES(is_active); diff --git a/migrations/002_seed_sample_data.sql b/migrations/002_seed_sample_data.sql new file mode 100644 index 0000000..989a864 --- /dev/null +++ b/migrations/002_seed_sample_data.sql @@ -0,0 +1,73 @@ +USE webgis_spbu; + +INSERT INTO points (nama, kode_point, latitude, longitude) +SELECT * +FROM ( + SELECT 'Titik Survei Alun Kapuas' AS nama, 'P-001' AS kode_point, -0.023700 AS latitude, 109.342900 AS longitude + UNION ALL SELECT 'Titik Survei Akcaya', 'P-002', -0.051200, 109.339000 + UNION ALL SELECT 'Titik Survei Siantan', 'P-003', -0.009500, 109.339500 +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM points LIMIT 1); + +INSERT INTO spbu (nama, no_spbu, status, latitude, longitude) +SELECT * +FROM ( + SELECT 'SPBU Ahmad Yani Pontianak' AS nama, '64.781.01' AS no_spbu, '24 Jam' AS status, -0.045050 AS latitude, 109.345900 AS longitude + UNION ALL SELECT 'SPBU Imam Bonjol', '64.781.02', '24 Jam', -0.036500, 109.333100 + UNION ALL SELECT 'SPBU Tanjungpura', '64.781.03', '24 Jam', -0.028950, 109.337800 + UNION ALL SELECT 'SPBU Sungai Jawi', '64.781.04', '24 Jam', -0.040100, 109.305400 + UNION ALL SELECT 'SPBU Siantan', '64.781.05', '24 Jam', -0.006700, 109.340800 + UNION ALL SELECT 'SPBU Paris II', '64.781.06', 'Tidak', -0.061800, 109.351500 + UNION ALL SELECT 'SPBU Tanjung Hulu', '64.781.07', 'Tidak', -0.023800, 109.364400 +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM spbu LIMIT 1); + +INSERT INTO jalan (nama, status, panjang, geom) +SELECT * +FROM ( + SELECT 'Koridor Jalan Ahmad Yani' AS nama, 'Nasional' AS status, 1430.50 AS panjang, '[{"lat":-0.0565,"lng":109.3418},{"lat":-0.0493,"lng":109.3443},{"lat":-0.0402,"lng":109.3475}]' AS geom + UNION ALL SELECT 'Koridor Jalan Tanjungpura', 'Provinsi', 1210.25, '[{"lat":-0.0339,"lng":109.3323},{"lat":-0.0304,"lng":109.3372},{"lat":-0.0275,"lng":109.3420}]' + UNION ALL SELECT 'Koridor Jalan Kom Yos Sudarso', 'Kabupaten', 1655.75, '[{"lat":-0.0128,"lng":109.3142},{"lat":-0.0187,"lng":109.3237},{"lat":-0.0239,"lng":109.3338}]' +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM jalan LIMIT 1); + +INSERT INTO parsil (nama, status, luas, geom) +SELECT * +FROM ( + SELECT 'Parsil Kawasan GOR Pangsuma' AS nama, 'SHM' AS status, 24500.75 AS luas, '[[{"lat":-0.0493,"lng":109.3419},{"lat":-0.0470,"lng":109.3470},{"lat":-0.0514,"lng":109.3490},{"lat":-0.0535,"lng":109.3440}]]' AS geom + UNION ALL SELECT 'Parsil Area Sungai Bangkong', 'HGB', 18250.40, '[[{"lat":-0.0340,"lng":109.3090},{"lat":-0.0308,"lng":109.3134},{"lat":-0.0346,"lng":109.3168},{"lat":-0.0381,"lng":109.3120}]]' + UNION ALL SELECT 'Parsil Area Tanjung Hulu', 'HP', 21780.10, '[[{"lat":-0.0211,"lng":109.3634},{"lat":-0.0176,"lng":109.3673},{"lat":-0.0219,"lng":109.3712},{"lat":-0.0251,"lng":109.3669}]]' +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM parsil LIMIT 1); + +INSERT INTO masjid (nama, pic, radius, latitude, longitude, radius_layanan) +SELECT * +FROM ( + SELECT 'Masjid Raya Mujahidin' AS nama, 'H. Sulaiman Arif' AS pic, 900 AS radius, -0.032500 AS latitude, 109.338200 AS longitude, 900 AS radius_layanan + UNION ALL SELECT 'Masjid Jami Sultan Syarif Abdurrahman', 'Ust. Rahman Hakim', 700, -0.020600, 109.338900, 700 + UNION ALL SELECT 'Masjid Al-Falah Sungai Jawi', 'H. Darmawan Yusuf', 820, -0.041900, 109.310500, 820 +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM masjid LIMIT 1); + +INSERT INTO penduduk_miskin ( + nama_kk, jumlah_anggota, alamat, latitude, longitude, + penghasilan, kondisi_rumah, sumber_air, skor_kerentanan, jenis_bantuan +) +SELECT * +FROM ( + SELECT 'Rahmat Hidayat' AS nama_kk, 5 AS jumlah_anggota, 'Gang Karya Baru, Sungai Bangkong' AS alamat, -0.037800 AS latitude, 109.314100 AS longitude, '< 500rb' AS penghasilan, 'Kayu' AS kondisi_rumah, 'Sumur' AS sumber_air, 75.00 AS skor_kerentanan, 'BLT / Modal Usaha' AS jenis_bantuan + UNION ALL SELECT 'Siti Aminah', 4, 'Jl. Tanjungpura Dalam', -0.030500, 109.337600, '500rb - 1jt', 'Kayu', 'PDAM', 42.00, 'BLT / Modal Usaha' + UNION ALL SELECT 'Mulyadi Saputra', 6, 'Gang Sepakat, Benua Melayu Darat', -0.034300, 109.343600, '< 500rb', 'Bambu/Tepas', 'Sumur', 90.00, 'Bedah Rumah' + UNION ALL SELECT 'Nuraini Hasanah', 3, 'Jl. Gajah Mada Gang Kecil', -0.026900, 109.335900, '500rb - 1jt', 'Tembok', 'PDAM', 30.00, 'Sembako' + UNION ALL SELECT 'Agus Salim', 5, 'Kelurahan Tambelan Sampit', -0.019900, 109.340100, '< 500rb', 'Kayu', 'Sungai', 85.00, 'Air Bersih / PDAM' + UNION ALL SELECT 'Yuliana Putri', 4, 'Sungai Jawi Dalam', -0.041200, 109.309900, '500rb - 1jt', 'Kayu', 'Sumur', 50.00, 'Sembako' + UNION ALL SELECT 'Bambang Irawan', 7, 'Gang Bersama, Mariana', -0.028000, 109.326800, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Bedah Rumah' + UNION ALL SELECT 'Kartini Rahma', 2, 'Jl. Dr. Sutomo Dalam', -0.033800, 109.331800, '> 1jt', 'Kayu', 'PDAM', 28.00, 'Beasiswa Pendidikan' + UNION ALL SELECT 'Firdaus Ahmad', 5, 'Siantan Hilir Gang Masjid', -0.018400, 109.337600, '500rb - 1jt', 'Kayu', 'Sungai', 60.00, 'Air Bersih / PDAM' + UNION ALL SELECT 'Mariam Abdullah', 4, 'Tanjung Hulu Gang Keluarga', -0.022300, 109.362800, '< 500rb', 'Kayu', 'Sumur', 75.00, 'BLT / Modal Usaha' + UNION ALL SELECT 'Rudi Hartono', 6, 'Parit Mayor Dalam', -0.043800, 109.372900, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Bedah Rumah' + UNION ALL SELECT 'Desi Wulandari', 3, 'Akcaya Gang Damai', -0.052200, 109.339600, '500rb - 1jt', 'Tembok', 'PDAM', 30.00, 'Beasiswa Pendidikan' + UNION ALL SELECT 'Hendra Gunawan', 5, 'Sungai Raya Dalam, ujung permukiman', -0.079500, 109.395200, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Air Bersih / PDAM' + UNION ALL SELECT 'Rosnawati Idris', 4, 'Pal Lima Gang Mandiri', -0.048800, 109.300500, '500rb - 1jt', 'Kayu', 'Sumur', 50.00, 'Sembako' +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM penduduk_miskin LIMIT 1); diff --git a/php/auth.php b/php/auth.php new file mode 100644 index 0000000..6de619f --- /dev/null +++ b/php/auth.php @@ -0,0 +1,45 @@ + "Akses ditolak. Silakan login dengan akun yang berwenang." + ]); + exit; + } +} + +function require_write_access_json() +{ + require_roles_json(['admin']); +} + +function require_roles_page(array $allowed_roles, $login_path = 'login.php') +{ + if (!user_has_role($allowed_roles)) { + header('Location: ' . $login_path); + exit; + } +} +?> diff --git a/php/db.php b/php/db.php index 8a4ee47..253a7f4 100644 --- a/php/db.php +++ b/php/db.php @@ -1,7 +1,25 @@ connect_error) { die(json_encode(["error" => "Koneksi gagal: " . $conn->connect_error])); } -$conn->set_charset("utf8"); +$conn->set_charset("utf8mb4"); ?> diff --git a/php/delete.php b/php/delete.php index 1a40537..b8014d4 100644 --- a/php/delete.php +++ b/php/delete.php @@ -1,4 +1,6 @@ execute()) { } else { echo json_encode(["message" => "Gagal hapus", "error" => $stmt->error]); } -?> \ No newline at end of file +?> diff --git a/php/insert.php b/php/insert.php index a53b795..153ab66 100644 --- a/php/insert.php +++ b/php/insert.php @@ -1,4 +1,6 @@ execute()) { echo json_encode(["message"=>"Gagal", "error"=>$stmt->error]); } $stmt->close(); $conn->close(); -?> \ No newline at end of file +?> diff --git a/php/insert_penduduk.php b/php/insert_penduduk.php index 46c3c6a..628b20e 100644 --- a/php/insert_penduduk.php +++ b/php/insert_penduduk.php @@ -1,4 +1,6 @@ execute()) { echo json_encode(["message"=>"Gagal", "error"=>$stmt->error]); } $stmt->close(); $conn->close(); -?> \ No newline at end of file +?> diff --git a/php/insert_point.php b/php/insert_point.php new file mode 100644 index 0000000..139f3f5 --- /dev/null +++ b/php/insert_point.php @@ -0,0 +1,35 @@ + "Data tidak diterima"]); + exit; +} + +$nama = $data['nama'] ?? ''; +$kode = $data['no'] ?? ''; +$lat = $data['lat'] ?? ''; +$lng = $data['lng'] ?? ''; + +if ($nama === '' || $kode === '' || $lat === '' || $lng === '') { + echo json_encode(["message" => "Field tidak lengkap"]); + exit; +} + +$stmt = $conn->prepare("INSERT INTO points (nama, kode_point, latitude, longitude) VALUES (?, ?, ?, ?)"); +$stmt->bind_param("ssdd", $nama, $kode, $lat, $lng); + +if ($stmt->execute()) { + echo json_encode(["message" => "Data berhasil disimpan", "id" => $stmt->insert_id]); +} else { + echo json_encode(["message" => "Gagal simpan", "error" => $stmt->error]); +} + +$stmt->close(); +$conn->close(); +?> diff --git a/php/insert_polygon.php b/php/insert_polygon.php index c607130..1a53eb8 100644 --- a/php/insert_polygon.php +++ b/php/insert_polygon.php @@ -1,4 +1,6 @@ query("SELECT * FROM spbu"); +$res = $conn->query("SELECT id, nama, kode_point AS no_spbu, latitude, longitude FROM points"); if ($res) { while ($r = $res->fetch_assoc()) { $r['type'] = 'point'; diff --git a/php/update_line.php b/php/update_line.php index 2a8a5cf..b4d995a 100644 --- a/php/update_line.php +++ b/php/update_line.php @@ -1,4 +1,6 @@ diff --git a/sosial/index.php b/sosial/index.php index a4107ff..b4ec5ae 100644 --- a/sosial/index.php +++ b/sosial/index.php @@ -87,13 +87,5 @@
- - - - diff --git a/sosial/login.php b/sosial/login.php index 061d35b..ea902a8 100644 --- a/sosial/login.php +++ b/sosial/login.php @@ -1,23 +1,47 @@ prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1"); + if (!$stmt) { + $error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.'; } else { + $stmt->bind_param("s", $user); + $stmt->execute(); + $result = $stmt->get_result(); + $account = $result ? $result->fetch_assoc() : null; + + if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) { + session_regenerate_id(true); + $_SESSION['user_id'] = intval($account['id']); + $_SESSION['username'] = $account['username']; + $_SESSION['role'] = $account['role']; + $_SESSION['full_name'] = $account['full_name']; + $_SESSION['is_admin'] = $account['role'] === 'admin'; + + if ($account['role'] === 'admin') { + header('Location: admin.php'); + } else { + header('Location: index.php'); + } + exit; + } + $error = 'Username atau password salah.'; + $stmt->close(); } } + +if (isset($conn)) { + $conn->close(); +} ?> @@ -182,12 +206,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- +
-

🔐 Masuk sebagai Admin

- +

🔐 Masuk Pengguna

+
⚠️
@@ -200,7 +224,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { - + ← Kembali ke Dashboard Publik diff --git a/spbu/admin.php b/spbu/admin.php new file mode 100644 index 0000000..3bcd71e --- /dev/null +++ b/spbu/admin.php @@ -0,0 +1,59 @@ + + + + + + + Panel Admin - WebGIS SPBU + + + + + + + + + + +
+ + +
+ Kelola Titik SPBU Pontianak + +
+
SPBU 24 Jam
+
Tidak 24 Jam
+
Titik SPBU
+
+ +
+
+ + +
+ Keluar +
+
+ +
+ + + + + + + + diff --git a/spbu/index.php b/spbu/index.php index e5a4bf7..4ab6352 100644 --- a/spbu/index.php +++ b/spbu/index.php @@ -11,7 +11,6 @@ - @@ -27,7 +26,22 @@
Pontianak, Kalimantan Barat -
+
+ + +
+ Admin Login +
+ +
+ +
SPBU 24 Jam
@@ -38,30 +52,16 @@
Titik SPBU
- -
- - -
- - -
- - + + diff --git a/spbu/login.php b/spbu/login.php new file mode 100644 index 0000000..0d13b86 --- /dev/null +++ b/spbu/login.php @@ -0,0 +1,92 @@ +prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1"); + if (!$stmt) { + $error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.'; + } else { + $stmt->bind_param("s", $user); + $stmt->execute(); + $result = $stmt->get_result(); + $account = $result ? $result->fetch_assoc() : null; + + if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) { + session_regenerate_id(true); + $_SESSION['user_id'] = intval($account['id']); + $_SESSION['username'] = $account['username']; + $_SESSION['role'] = $account['role']; + $_SESSION['full_name'] = $account['full_name']; + $_SESSION['is_admin'] = $account['role'] === 'admin'; + + header('Location: ' . ($account['role'] === 'admin' ? 'admin.php' : 'index.php')); + exit; + } + + $error = 'Username atau password salah.'; + $stmt->close(); + } +} + +if (isset($conn)) { + $conn->close(); +} +?> + + + + + + Login Admin - WebGIS SPBU + + + + + + + + diff --git a/spbu/logout.php b/spbu/logout.php new file mode 100644 index 0000000..e5ab4b6 --- /dev/null +++ b/spbu/logout.php @@ -0,0 +1,6 @@ +