dispatch-console / server /src /routes.js
secutorpro's picture
fix: delete member cascades to sos/pings/call log
aa41b23 verified
Raw History Blame Contribute Delete
4.54 kB
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;