first commit

This commit is contained in:
tistopandita
2026-06-10 20:41:25 +07:00
commit 08b84fea98
9 changed files with 5337 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
backend/.env
*.log
+87
View File
@@ -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.
+26
View File
@@ -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;
+1381
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -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"
}
}
+679
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1407
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -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;