secutorpro commited on
Commit
d1ce9f2
·
verified ·
1 Parent(s): 08df792

SQLite schema and first-run seeding

Browse files
Files changed (1) hide show
  1. server/src/db.js +83 -0
server/src/db.js ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Database from 'better-sqlite3';
2
+ import bcrypt from 'bcryptjs';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'data');
7
+ fs.mkdirSync(DATA_DIR, { recursive: true });
8
+
9
+ const db = new Database(path.join(DATA_DIR, 'dispatch.db'));
10
+ db.pragma('journal_mode = WAL');
11
+ db.pragma('foreign_keys = ON');
12
+
13
+ db.exec(`
14
+ CREATE TABLE IF NOT EXISTS users (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ username TEXT UNIQUE NOT NULL,
17
+ password_hash TEXT NOT NULL,
18
+ role TEXT NOT NULL DEFAULT 'dispatcher',
19
+ created_at INTEGER NOT NULL
20
+ );
21
+ CREATE TABLE IF NOT EXISTS talkgroups (
22
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
23
+ name TEXT UNIQUE NOT NULL,
24
+ color TEXT NOT NULL DEFAULT '#3b82f6'
25
+ );
26
+ CREATE TABLE IF NOT EXISTS members (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ name TEXT NOT NULL,
29
+ callsign TEXT,
30
+ device_id TEXT UNIQUE,
31
+ group_id INTEGER REFERENCES talkgroups(id),
32
+ status TEXT NOT NULL DEFAULT 'offline',
33
+ battery INTEGER,
34
+ lat REAL,
35
+ lng REAL,
36
+ last_seen INTEGER
37
+ );
38
+ CREATE TABLE IF NOT EXISTS gps_pings (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ member_id INTEGER NOT NULL REFERENCES members(id) ON DELETE CASCADE,
41
+ lat REAL NOT NULL,
42
+ lng REAL NOT NULL,
43
+ speed REAL,
44
+ ts INTEGER NOT NULL
45
+ );
46
+ CREATE INDEX IF NOT EXISTS idx_pings_member_ts ON gps_pings(member_id, ts);
47
+ CREATE TABLE IF NOT EXISTS sos_alerts (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ member_id INTEGER NOT NULL REFERENCES members(id),
50
+ lat REAL,
51
+ lng REAL,
52
+ state TEXT NOT NULL DEFAULT 'open', -- open | ack | resolved
53
+ ack_by TEXT,
54
+ created_at INTEGER NOT NULL,
55
+ updated_at INTEGER
56
+ );
57
+ CREATE TABLE IF NOT EXISTS call_log (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ group_id INTEGER NOT NULL REFERENCES talkgroups(id),
60
+ member_id INTEGER,
61
+ kind TEXT NOT NULL, -- ptt_start | ptt_stop
62
+ ts INTEGER NOT NULL
63
+ );
64
+ `);
65
+
66
+ // ---- first-run seed -------------------------------------------------------
67
+ const hasAdmin = db.prepare('SELECT 1 FROM users WHERE username = ?').get('admin');
68
+ if (!hasAdmin) {
69
+ db.prepare('INSERT INTO users (username, password_hash, role, created_at) VALUES (?, ?, ?, ?)')
70
+ .run('admin', bcrypt.hashSync(process.env.ADMIN_PASSWORD || 'admin', 10), 'admin', Date.now());
71
+ console.log('[db] created default dispatcher "admin" (password: admin) — change it');
72
+ }
73
+
74
+ const groupCount = db.prepare('SELECT COUNT(*) AS n FROM talkgroups').get().n;
75
+ if (groupCount === 0) {
76
+ const ins = db.prepare('INSERT INTO talkgroups (name, color) VALUES (?, ?)');
77
+ ins.run('Alpha', '#3b82f6');
78
+ ins.run('Bravo', '#f59e0b');
79
+ ins.run('Echo', '#10b981');
80
+ console.log('[db] seeded default talkgroups: Alpha, Bravo, Echo');
81
+ }
82
+
83
+ export default db;