Spaces:
Paused
Paused
File size: 4,543 Bytes
1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 aa41b23 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 1dd35a9 ef3b247 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import { Router } from 'express';
import db from './db.js';
const router = Router();
const MEMBER_SQL = `SELECT m.*, g.name AS group_name, g.color AS group_color
FROM members m LEFT JOIN talkgroups g ON g.id = m.group_id`;
const SOS_SQL = `SELECT a.*, m.name AS member_name FROM sos_alerts a
JOIN members m ON m.id = a.member_id`;
const hub = (req) => req.app.get('hub');
router.get('/groups', (req, res) => {
res.json(db.prepare('SELECT * FROM talkgroups ORDER BY id').all());
});
router.post('/groups', (req, res) => {
const { name, color = '#3b82f6' } = req.body || {};
if (!name) return res.status(400).json({ error: 'name required' });
try {
const info = db.prepare('INSERT INTO talkgroups (name, color) VALUES (?, ?)').run(name, color);
hub(req)?.broadcastGroups();
res.json(db.prepare('SELECT * FROM talkgroups WHERE id = ?').get(info.lastInsertRowid));
} catch {
res.status(409).json({ error: 'group name already exists' });
}
});
router.get('/members', (req, res) => {
res.json(db.prepare(`${MEMBER_SQL} ORDER BY m.id`).all());
});
router.post('/members', (req, res) => {
const { name, callsign, device_id, group_id } = req.body || {};
if (!name) return res.status(400).json({ error: 'name required' });
if (!device_id) return res.status(400).json({ error: 'device_id required' });
try {
const info = db.prepare('INSERT INTO members (name, callsign, device_id, group_id) VALUES (?, ?, ?, ?)')
.run(name, callsign || null, device_id, group_id ?? null);
hub(req)?.broadcastMembers();
res.json(db.prepare(`${MEMBER_SQL} WHERE m.id = ?`).get(info.lastInsertRowid));
} catch {
res.status(409).json({ error: 'device_id already exists' });
}
});
router.put('/members/:id', (req, res) => {
const m = db.prepare('SELECT * FROM members WHERE id = ?').get(req.params.id);
if (!m) return res.status(404).json({ error: 'member not found' });
const { name = m.name, callsign = m.callsign, group_id = m.group_id } = req.body || {};
db.prepare('UPDATE members SET name = ?, callsign = ?, group_id = ? WHERE id = ?')
.run(name, callsign, group_id, m.id);
hub(req)?.broadcastMembers();
res.json(db.prepare(`${MEMBER_SQL} WHERE m.id = ?`).get(m.id));
});
router.delete('/members/:id', (req, res) => {
const m = db.prepare('SELECT id FROM members WHERE id = ?').get(req.params.id);
if (!m) return res.status(404).json({ error: 'member not found' });
// remove history first: FK references would otherwise make this fail
db.prepare('DELETE FROM gps_pings WHERE member_id = ?').run(m.id);
db.prepare('DELETE FROM sos_alerts WHERE member_id = ?').run(m.id);
db.prepare('DELETE FROM call_log WHERE member_id = ?').run(m.id);
db.prepare('DELETE FROM members WHERE id = ?').run(m.id);
hub(req)?.broadcastMembers();
res.json({ ok: true });
});
router.get('/sos', (req, res) => {
const rows = req.query.state
? db.prepare(`${SOS_SQL} WHERE a.state = ? ORDER BY a.created_at DESC LIMIT 200`).all(req.query.state)
: db.prepare(`${SOS_SQL} ORDER BY a.created_at DESC LIMIT 200`).all();
res.json(rows);
});
router.post('/sos/:id/ack', (req, res) => {
const info = db.prepare(`UPDATE sos_alerts SET state = 'ack', ack_by = ?, updated_at = ? WHERE id = ? AND state = 'open'`)
.run(req.user.username, Date.now(), req.params.id);
if (!info.changes) return res.status(409).json({ error: 'alert not open' });
const row = db.prepare(`${SOS_SQL} WHERE a.id = ?`).get(req.params.id);
hub(req)?.broadcast({ type: 'sos_update', alert: row });
res.json(row);
});
router.post('/sos/:id/resolve', (req, res) => {
const info = db.prepare(`UPDATE sos_alerts SET state = 'resolved', ack_by = COALESCE(ack_by, ?), updated_at = ? WHERE id = ? AND state != 'resolved'`)
.run(req.user.username, Date.now(), req.params.id);
if (!info.changes) return res.status(409).json({ error: 'alert already resolved' });
const row = db.prepare(`${SOS_SQL} WHERE a.id = ?`).get(req.params.id);
hub(req)?.broadcast({ type: 'sos_update', alert: row });
res.json(row);
});
router.get('/members/:id/trail', (req, res) => {
const since = Number(req.query.since) || 0;
res.json(db.prepare('SELECT lat, lng, speed, ts FROM gps_pings WHERE member_id = ? AND ts > ? ORDER BY ts LIMIT 5000')
.all(req.params.id, since));
});
router.get('/calls', (req, res) => {
res.json(db.prepare(`SELECT c.*, m.name AS member_name FROM call_log c
LEFT JOIN members m ON m.id = c.member_id ORDER BY c.ts DESC LIMIT 100`).all());
});
export default router; |