first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
backend/.env
|
||||
*.log
|
||||
@@ -0,0 +1,87 @@
|
||||
# WebGIS SPBU Pontianak — Backend + Frontend
|
||||
|
||||
## Struktur Project
|
||||
|
||||
```
|
||||
spbu-webgis/
|
||||
├── backend/
|
||||
│ ├── server.js ← Express server + semua API route
|
||||
│ ├── db.js ← Koneksi MySQL (pool)
|
||||
│ ├── .env ← Konfigurasi database (edit ini!)
|
||||
│ └── package.json
|
||||
├── frontend/
|
||||
│ ├── index.html ← Halaman peta utama
|
||||
│ └── admin.html ← Halaman CRUD data SPBU
|
||||
├── schema.sql ← SQL untuk buat tabel manual (opsional)
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cara Menjalankan
|
||||
|
||||
### 1. Pastikan MySQL berjalan dan buat database
|
||||
|
||||
```sql
|
||||
CREATE DATABASE spbu_pontianak CHARACTER SET utf8mb4;
|
||||
```
|
||||
|
||||
### 2. Edit konfigurasi database
|
||||
|
||||
Buka `backend/.env` dan sesuaikan:
|
||||
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=password_anda ← ganti ini
|
||||
DB_NAME=spbu_pontianak
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### 3. Install dependencies
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Jalankan server
|
||||
|
||||
```bash
|
||||
# Mode biasa
|
||||
npm start
|
||||
|
||||
# Mode development (auto-restart saat ada perubahan)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 5. Buka di browser
|
||||
|
||||
| URL | Keterangan |
|
||||
|-----|-----------|
|
||||
| http://localhost:3000/ | Peta WebGIS |
|
||||
| http://localhost:3000/admin.html | CRUD Data SPBU |
|
||||
| http://localhost:3000/api/spbu | JSON API |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint
|
||||
|
||||
| Method | URL | Fungsi |
|
||||
|--------|-----|--------|
|
||||
| `GET` | `/api/spbu` | Ambil semua SPBU aktif |
|
||||
| `GET` | `/api/spbu/:id` | Ambil satu SPBU |
|
||||
| `POST` | `/api/spbu` | Tambah SPBU baru |
|
||||
| `PUT` | `/api/spbu/:id` | Update data SPBU |
|
||||
| `PATCH` | `/api/spbu/:id/override` | Update override status saja |
|
||||
| `DELETE` | `/api/spbu/:id` | Hapus SPBU (soft delete) |
|
||||
|
||||
---
|
||||
|
||||
## Catatan
|
||||
|
||||
- Tabel MySQL dibuat otomatis saat server pertama kali dijalankan.
|
||||
- Data 9 SPBU di-seed otomatis jika tabel masih kosong.
|
||||
- Hapus SPBU bersifat **soft delete** (kolom `aktif = 0`), data tidak hilang dari database.
|
||||
- Override status tersimpan ke database dan bertahan setelah server restart.
|
||||
@@ -0,0 +1,26 @@
|
||||
const mysql = require("mysql2/promise");
|
||||
require("dotenv").config();
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || "localhost",
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER || "root",
|
||||
password: process.env.DB_PASSWORD || "",
|
||||
database: process.env.DB_NAME || "spbu_pontianak",
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
|
||||
// Test koneksi saat pertama kali diload
|
||||
pool.getConnection()
|
||||
.then((conn) => {
|
||||
console.log("✅ Terkoneksi ke MySQL:", process.env.DB_NAME);
|
||||
conn.release();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("❌ Gagal koneksi ke MySQL:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
Generated
+1381
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "spbu-webgis-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API untuk WebGIS SPBU Pontianak",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mysql2": "^3.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
// server.js
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const path = require("path");
|
||||
const db = require("./db");
|
||||
require("dotenv").config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// ─── Middleware ───────────────────────────────────────────────────────────────
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Sajikan folder frontend secara statis
|
||||
app.use(express.static(path.join(__dirname, "../frontend")));
|
||||
|
||||
// ─── Inisialisasi tabel saat server start ────────────────────────────────────
|
||||
async function initDatabase() {
|
||||
const createSpbu = `
|
||||
CREATE TABLE IF NOT EXISTS spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
kode VARCHAR(20) NOT NULL UNIQUE COMMENT 'Kode unik SPBU, mis: 64.781.01',
|
||||
nama VARCHAR(100) NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
lat DOUBLE NOT NULL,
|
||||
lng DOUBLE NOT NULL,
|
||||
jam_buka TIME NOT NULL DEFAULT '06:00:00',
|
||||
jam_tutup TIME NOT NULL DEFAULT '22:00:00',
|
||||
jenis_bbm JSON NOT NULL COMMENT 'Array jenis BBM tersedia',
|
||||
manual_override ENUM('auto','open','closed') NOT NULL DEFAULT 'auto',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`;
|
||||
|
||||
const createJalan = `
|
||||
CREATE TABLE IF NOT EXISTS jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(150) NOT NULL,
|
||||
jenis_jalan ENUM('nasional','provinsi','kota_kabupaten') NOT NULL DEFAULT 'kota_kabupaten',
|
||||
koordinat LONGTEXT NOT NULL COMMENT 'GeoJSON LineString koordinat polyline',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`;
|
||||
|
||||
const createBangunan = `
|
||||
CREATE TABLE IF NOT EXISTS bangunan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_bangunan VARCHAR(150) NOT NULL,
|
||||
jenis_bangunan ENUM('SHM','HGB','HGU','HP','HPL') NOT NULL DEFAULT 'SHM',
|
||||
nama_jalan VARCHAR(150),
|
||||
koordinat LONGTEXT NOT NULL COMMENT 'GeoJSON Polygon koordinat area bangunan',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`;
|
||||
|
||||
try {
|
||||
await db.execute(createSpbu);
|
||||
console.log("✅ Tabel 'spbu' siap.");
|
||||
await db.execute(createJalan);
|
||||
console.log("✅ Tabel 'jalan' siap.");
|
||||
await db.execute(createBangunan);
|
||||
console.log("✅ Tabel 'bangunan' siap.");
|
||||
await seedDataIfEmpty();
|
||||
} catch (err) {
|
||||
console.error("❌ Gagal membuat tabel:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Isi data awal jika tabel kosong
|
||||
async function seedDataIfEmpty() {
|
||||
const [[{ count }]] = await db.execute("SELECT COUNT(*) AS count FROM spbu");
|
||||
if (Number(count) > 0) return;
|
||||
|
||||
const seedData = [
|
||||
[
|
||||
"64.781.01",
|
||||
"SPBU 64.781.01 Ahmad Yani",
|
||||
"Jl. Jend. Ahmad Yani, Bansir Laut, Pontianak Tenggara",
|
||||
-0.0529,
|
||||
109.3484,
|
||||
"06:00",
|
||||
"22:00",
|
||||
["Pertalite", "Pertamax", "Solar", "Dexlite"],
|
||||
],
|
||||
[
|
||||
"64.781.02",
|
||||
"SPBU 64.781.02 Tanjung Raya",
|
||||
"Jl. Tanjung Raya II, Saigon, Pontianak Timur",
|
||||
-0.0334,
|
||||
109.3702,
|
||||
"00:00",
|
||||
"23:59",
|
||||
["Pertalite", "Pertamax", "Pertamina Dex"],
|
||||
],
|
||||
[
|
||||
"64.781.03",
|
||||
"SPBU 64.781.03 Gajah Mada",
|
||||
"Jl. Gajah Mada, Benua Melayu Darat, Pontianak Selatan",
|
||||
-0.0348,
|
||||
109.3368,
|
||||
"05:30",
|
||||
"23:00",
|
||||
["Pertalite", "Pertamax", "Pertamax Turbo", "Solar"],
|
||||
],
|
||||
[
|
||||
"64.781.04",
|
||||
"SPBU 64.781.04 Adi Sucipto",
|
||||
"Jl. Adi Sucipto, Sungai Raya, area Pontianak Selatan",
|
||||
-0.0907,
|
||||
109.3605,
|
||||
"06:00",
|
||||
"21:30",
|
||||
["Pertalite", "Pertamax", "Dexlite", "Solar"],
|
||||
],
|
||||
[
|
||||
"64.781.05",
|
||||
"SPBU 64.781.05 Sultan Hamid II",
|
||||
"Jl. Sultan Hamid II, Dalam Bugis, Pontianak Timur",
|
||||
-0.0125,
|
||||
109.3567,
|
||||
"06:00",
|
||||
"22:30",
|
||||
["Pertalite", "Pertamax", "Solar"],
|
||||
],
|
||||
[
|
||||
"64.781.06",
|
||||
"SPBU 64.781.06 Imam Bonjol",
|
||||
"Jl. Imam Bonjol, Benua Melayu Laut, Pontianak Selatan",
|
||||
-0.0261,
|
||||
109.3449,
|
||||
"00:00",
|
||||
"23:59",
|
||||
["Pertalite", "Pertamax", "Pertamax Turbo", "Pertamina Dex"],
|
||||
],
|
||||
[
|
||||
"64.781.07",
|
||||
"SPBU 64.781.07 Khatulistiwa",
|
||||
"Jl. Khatulistiwa, Siantan Hilir, Pontianak Utara",
|
||||
0.0019,
|
||||
109.3231,
|
||||
"05:00",
|
||||
"22:00",
|
||||
["Pertalite", "Pertamax", "Solar", "Dexlite"],
|
||||
],
|
||||
[
|
||||
"64.781.08",
|
||||
"SPBU 64.781.08 Husein Hamzah",
|
||||
"Jl. Husein Hamzah, Pal Lima, Pontianak Barat",
|
||||
-0.0238,
|
||||
109.3039,
|
||||
"06:00",
|
||||
"23:00",
|
||||
["Pertalite", "Pertamax", "Solar", "Pertamina Dex"],
|
||||
],
|
||||
[
|
||||
"64.781.09",
|
||||
"SPBU 64.781.09 Dr. Wahidin",
|
||||
"Jl. Dr. Wahidin Sudirohusodo, Sungai Jawi, Pontianak Kota",
|
||||
-0.0359,
|
||||
109.3148,
|
||||
"06:00",
|
||||
"21:00",
|
||||
["Pertalite", "Pertamax", "Dexlite"],
|
||||
],
|
||||
];
|
||||
|
||||
for (const [
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
jenis_bbm,
|
||||
] of seedData) {
|
||||
await db.execute(
|
||||
`INSERT INTO spbu (kode, nama, alamat, lat, lng, jam_buka, jam_tutup, jenis_bbm)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
JSON.stringify(jenis_bbm),
|
||||
],
|
||||
);
|
||||
}
|
||||
console.log("✅ Data awal 9 SPBU berhasil di-seed.");
|
||||
}
|
||||
|
||||
// ─── Helper ───────────────────────────────────────────────────────────────────
|
||||
function parseSpbu(row) {
|
||||
return {
|
||||
...row,
|
||||
jenis_bbm:
|
||||
typeof row.jenis_bbm === "string"
|
||||
? JSON.parse(row.jenis_bbm)
|
||||
: row.jenis_bbm,
|
||||
aktif: Boolean(row.aktif),
|
||||
jam_buka: row.jam_buka ? row.jam_buka.slice(0, 5) : null,
|
||||
jam_tutup: row.jam_tutup ? row.jam_tutup.slice(0, 5) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function parseGeo(row) {
|
||||
return {
|
||||
...row,
|
||||
aktif: Boolean(row.aktif),
|
||||
koordinat:
|
||||
typeof row.koordinat === "string"
|
||||
? JSON.parse(row.koordinat)
|
||||
: row.koordinat,
|
||||
};
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// ─── API: SPBU ────────────────────────────────────────────────────────────────
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// GET /api/spbu
|
||||
app.get("/api/spbu", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute(
|
||||
"SELECT * FROM spbu WHERE aktif = 1 ORDER BY kode ASC",
|
||||
);
|
||||
res.json({ success: true, data: rows.map(parseSpbu) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/spbu/:id
|
||||
app.get("/api/spbu/:id", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute("SELECT * FROM spbu WHERE id = ?", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (!rows.length)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "SPBU tidak ditemukan." });
|
||||
res.json({ success: true, data: parseSpbu(rows[0]) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/spbu
|
||||
app.post("/api/spbu", async (req, res) => {
|
||||
const {
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
jenis_bbm,
|
||||
manual_override,
|
||||
} = req.body;
|
||||
if (
|
||||
!kode ||
|
||||
!nama ||
|
||||
!alamat ||
|
||||
lat == null ||
|
||||
lng == null ||
|
||||
!jam_buka ||
|
||||
!jam_tutup ||
|
||||
!jenis_bbm
|
||||
) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, message: "Semua field wajib diisi." });
|
||||
}
|
||||
if (!Array.isArray(jenis_bbm) || jenis_bbm.length === 0) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
message: "Minimal satu jenis BBM harus dipilih.",
|
||||
});
|
||||
}
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO spbu (kode, nama, alamat, lat, lng, jam_buka, jam_tutup, jenis_bbm, manual_override)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
JSON.stringify(jenis_bbm),
|
||||
manual_override || "auto",
|
||||
],
|
||||
);
|
||||
res
|
||||
.status(201)
|
||||
.json({
|
||||
success: true,
|
||||
message: "SPBU berhasil ditambahkan.",
|
||||
id: result.insertId,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === "ER_DUP_ENTRY") {
|
||||
return res
|
||||
.status(409)
|
||||
.json({
|
||||
success: false,
|
||||
message: `Kode SPBU '${kode}' sudah digunakan.`,
|
||||
});
|
||||
}
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/spbu/:id
|
||||
app.put("/api/spbu/:id", async (req, res) => {
|
||||
const {
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
jenis_bbm,
|
||||
manual_override,
|
||||
aktif,
|
||||
} = req.body;
|
||||
if (
|
||||
!kode ||
|
||||
!nama ||
|
||||
!alamat ||
|
||||
lat == null ||
|
||||
lng == null ||
|
||||
!jam_buka ||
|
||||
!jam_tutup ||
|
||||
!jenis_bbm
|
||||
) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, message: "Semua field wajib diisi." });
|
||||
}
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
`UPDATE spbu SET kode=?, nama=?, alamat=?, lat=?, lng=?, jam_buka=?, jam_tutup=?,
|
||||
jenis_bbm=?, manual_override=?, aktif=? WHERE id=?`,
|
||||
[
|
||||
kode,
|
||||
nama,
|
||||
alamat,
|
||||
lat,
|
||||
lng,
|
||||
jam_buka,
|
||||
jam_tutup,
|
||||
JSON.stringify(jenis_bbm),
|
||||
manual_override || "auto",
|
||||
aktif !== undefined ? aktif : 1,
|
||||
req.params.id,
|
||||
],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "SPBU tidak ditemukan." });
|
||||
res.json({ success: true, message: "SPBU berhasil diperbarui." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PATCH /api/spbu/:id/override
|
||||
app.patch("/api/spbu/:id/override", async (req, res) => {
|
||||
const { manual_override } = req.body;
|
||||
const allowed = ["auto", "open", "closed"];
|
||||
if (!allowed.includes(manual_override)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, message: "Nilai override tidak valid." });
|
||||
}
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE spbu SET manual_override = ? WHERE id = ?",
|
||||
[manual_override, req.params.id],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "SPBU tidak ditemukan." });
|
||||
res.json({ success: true, message: "Override berhasil diperbarui." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/spbu/:id
|
||||
app.delete("/api/spbu/:id", async (req, res) => {
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE spbu SET aktif = 0 WHERE id = ?",
|
||||
[req.params.id],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "SPBU tidak ditemukan." });
|
||||
res.json({ success: true, message: "SPBU berhasil dihapus." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// ─── API: JALAN ───────────────────────────────────────────────────────────────
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// GET /api/jalan — ambil semua jalan aktif
|
||||
app.get("/api/jalan", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute(
|
||||
"SELECT * FROM jalan WHERE aktif = 1 ORDER BY nama_jalan ASC",
|
||||
);
|
||||
res.json({ success: true, data: rows.map(parseGeo) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/jalan/:id
|
||||
app.get("/api/jalan/:id", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute("SELECT * FROM jalan WHERE id = ?", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (!rows.length)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Jalan tidak ditemukan." });
|
||||
res.json({ success: true, data: parseGeo(rows[0]) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/jalan — tambah jalan baru
|
||||
app.post("/api/jalan", async (req, res) => {
|
||||
const { nama_jalan, jenis_jalan, koordinat } = req.body;
|
||||
if (!nama_jalan || !jenis_jalan || !koordinat) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
message: "nama_jalan, jenis_jalan, dan koordinat wajib diisi.",
|
||||
});
|
||||
}
|
||||
const jenisValid = ["nasional", "provinsi", "kota_kabupaten"];
|
||||
if (!jenisValid.includes(jenis_jalan)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, message: "jenis_jalan tidak valid." });
|
||||
}
|
||||
try {
|
||||
const koordinatStr =
|
||||
typeof koordinat === "string" ? koordinat : JSON.stringify(koordinat);
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO jalan (nama_jalan, jenis_jalan, koordinat) VALUES (?, ?, ?)",
|
||||
[nama_jalan.trim(), jenis_jalan, koordinatStr],
|
||||
);
|
||||
res
|
||||
.status(201)
|
||||
.json({
|
||||
success: true,
|
||||
message: "Jalan berhasil ditambahkan.",
|
||||
id: result.insertId,
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/jalan/:id — update jalan
|
||||
app.put("/api/jalan/:id", async (req, res) => {
|
||||
const { nama_jalan, jenis_jalan, koordinat } = req.body;
|
||||
if (!nama_jalan || !jenis_jalan || !koordinat) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
message: "nama_jalan, jenis_jalan, dan koordinat wajib diisi.",
|
||||
});
|
||||
}
|
||||
try {
|
||||
const koordinatStr =
|
||||
typeof koordinat === "string" ? koordinat : JSON.stringify(koordinat);
|
||||
const [result] = await db.execute(
|
||||
"UPDATE jalan SET nama_jalan=?, jenis_jalan=?, koordinat=? WHERE id=?",
|
||||
[nama_jalan.trim(), jenis_jalan, koordinatStr, req.params.id],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Jalan tidak ditemukan." });
|
||||
res.json({ success: true, message: "Jalan berhasil diperbarui." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/jalan/:id — soft delete
|
||||
app.delete("/api/jalan/:id", async (req, res) => {
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE jalan SET aktif = 0 WHERE id = ?",
|
||||
[req.params.id],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Jalan tidak ditemukan." });
|
||||
res.json({ success: true, message: "Jalan berhasil dihapus." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// ─── API: BANGUNAN ────────────────────────────────────────────────────────────
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// GET /api/bangunan — ambil semua bangunan aktif
|
||||
app.get("/api/bangunan", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute(
|
||||
"SELECT * FROM bangunan WHERE aktif = 1 ORDER BY nama_bangunan ASC",
|
||||
);
|
||||
res.json({ success: true, data: rows.map(parseGeo) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/bangunan/:id
|
||||
app.get("/api/bangunan/:id", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute("SELECT * FROM bangunan WHERE id = ?", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (!rows.length)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Bangunan tidak ditemukan." });
|
||||
res.json({ success: true, data: parseGeo(rows[0]) });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/bangunan — tambah bangunan baru
|
||||
app.post("/api/bangunan", async (req, res) => {
|
||||
const { nama_bangunan, jenis_bangunan, nama_jalan, koordinat } = req.body;
|
||||
if (!nama_bangunan || !jenis_bangunan || !koordinat) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
message: "nama_bangunan, jenis_bangunan, dan koordinat wajib diisi.",
|
||||
});
|
||||
}
|
||||
const jenisValid = ["SHM", "HGB", "HGU", "HP", "HPL"];
|
||||
if (!jenisValid.includes(jenis_bangunan)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, message: "jenis_bangunan tidak valid." });
|
||||
}
|
||||
try {
|
||||
const koordinatStr =
|
||||
typeof koordinat === "string" ? koordinat : JSON.stringify(koordinat);
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO bangunan (nama_bangunan, jenis_bangunan, nama_jalan, koordinat) VALUES (?, ?, ?, ?)",
|
||||
[
|
||||
nama_bangunan.trim(),
|
||||
jenis_bangunan,
|
||||
nama_jalan ? nama_jalan.trim() : null,
|
||||
koordinatStr,
|
||||
],
|
||||
);
|
||||
res
|
||||
.status(201)
|
||||
.json({
|
||||
success: true,
|
||||
message: "Bangunan berhasil ditambahkan.",
|
||||
id: result.insertId,
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/bangunan/:id — update bangunan
|
||||
app.put("/api/bangunan/:id", async (req, res) => {
|
||||
const { nama_bangunan, jenis_bangunan, nama_jalan, koordinat } = req.body;
|
||||
if (!nama_bangunan || !jenis_bangunan || !koordinat) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
message: "nama_bangunan, jenis_bangunan, dan koordinat wajib diisi.",
|
||||
});
|
||||
}
|
||||
try {
|
||||
const koordinatStr =
|
||||
typeof koordinat === "string" ? koordinat : JSON.stringify(koordinat);
|
||||
const [result] = await db.execute(
|
||||
"UPDATE bangunan SET nama_bangunan=?, jenis_bangunan=?, nama_jalan=?, koordinat=? WHERE id=?",
|
||||
[
|
||||
nama_bangunan.trim(),
|
||||
jenis_bangunan,
|
||||
nama_jalan ? nama_jalan.trim() : null,
|
||||
koordinatStr,
|
||||
req.params.id,
|
||||
],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Bangunan tidak ditemukan." });
|
||||
res.json({ success: true, message: "Bangunan berhasil diperbarui." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/bangunan/:id — soft delete
|
||||
app.delete("/api/bangunan/:id", async (req, res) => {
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
"UPDATE bangunan SET aktif = 0 WHERE id = ?",
|
||||
[req.params.id],
|
||||
);
|
||||
if (result.affectedRows === 0)
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Bangunan tidak ditemukan." });
|
||||
res.json({ success: true, message: "Bangunan berhasil dihapus." });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Fallback: semua route non-API ke index.html ─────────────────────────────
|
||||
app.get("*", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "../frontend/index.html"));
|
||||
});
|
||||
|
||||
// ─── Start ────────────────────────────────────────────────────────────────────
|
||||
initDatabase().then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 Server berjalan di http://localhost:${PORT}`);
|
||||
console.log(` Peta → http://localhost:${PORT}/`);
|
||||
console.log(` Admin → http://localhost:${PORT}/admin.html`);
|
||||
console.log(` API → http://localhost:${PORT}/api/spbu`);
|
||||
console.log(` API → http://localhost:${PORT}/api/jalan`);
|
||||
console.log(` API → http://localhost:${PORT}/api/bangunan\n`);
|
||||
});
|
||||
});
|
||||
+1682
File diff suppressed because it is too large
Load Diff
+1407
File diff suppressed because it is too large
Load Diff
+53
@@ -0,0 +1,53 @@
|
||||
-- ============================================================
|
||||
-- Schema Database WebGIS SPBU Pontianak
|
||||
-- Jalankan sekali untuk membuat database & tabel secara manual
|
||||
-- (Opsional — server.js sudah auto-create tabel saat start)
|
||||
-- ============================================================
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS spbu_pontianak
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE spbu_pontianak;
|
||||
|
||||
-- ── Tabel SPBU ──────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
kode VARCHAR(20) NOT NULL UNIQUE COMMENT 'Kode unik SPBU, mis: 64.781.01',
|
||||
nama VARCHAR(100) NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
lat DOUBLE NOT NULL COMMENT 'Latitude (desimal)',
|
||||
lng DOUBLE NOT NULL COMMENT 'Longitude (desimal)',
|
||||
jam_buka TIME NOT NULL DEFAULT '06:00' COMMENT 'Format HH:MM',
|
||||
jam_tutup TIME NOT NULL DEFAULT '22:00' COMMENT 'Format HH:MM',
|
||||
jenis_bbm JSON NOT NULL COMMENT 'Array jenis BBM, contoh: ["Pertalite","Pertamax"]',
|
||||
manual_override ENUM('auto','open','closed') NOT NULL DEFAULT 'auto',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1=aktif, 0=dihapus (soft delete)',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ── Tabel Jalan ─────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(150) NOT NULL COMMENT 'Nama jalan, mis: Jl. Ahmad Yani',
|
||||
jenis_jalan ENUM('nasional','provinsi','kota_kabupaten') NOT NULL DEFAULT 'kota_kabupaten'
|
||||
COMMENT 'Klasifikasi jalan',
|
||||
koordinat LONGTEXT NOT NULL COMMENT 'GeoJSON LineString koordinat polyline',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1=aktif, 0=dihapus (soft delete)',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ── Tabel Bangunan ──────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS bangunan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_bangunan VARCHAR(150) NOT NULL COMMENT 'Nama bangunan atau kavling',
|
||||
jenis_bangunan ENUM('SHM','HGB','HGU','HP','HPL') NOT NULL DEFAULT 'SHM'
|
||||
COMMENT 'SHM=Sertifikat Hak Milik, HGB=Hak Guna Bangunan, HGU=Hak Guna Usaha, HP=Hak Pakai, HPL=Hak Pengelolaan',
|
||||
nama_jalan VARCHAR(150) COMMENT 'Nama jalan lokasi bangunan',
|
||||
koordinat LONGTEXT NOT NULL COMMENT 'GeoJSON Polygon koordinat area bangunan',
|
||||
aktif TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1=aktif, 0=dihapus (soft delete)',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
Reference in New Issue
Block a user