const jwt = require('jsonwebtoken'); const db = require('../db/db'); const { addClient, removeClient } = require('../sse'); const _stmts = { list: db.prepare('SELECT id, type, message, link, is_read, created_at FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 50'), markOne: db.prepare('UPDATE notifications SET is_read = 1 WHERE id = ? AND user_id = ?'), markAll: db.prepare('UPDATE notifications SET is_read = 1 WHERE user_id = ?'), getUser: db.prepare('SELECT id, token_version, is_banned FROM users WHERE id = ?'), }; /* ── GET /api/notifications ─────────────────────────────────────────────── */ function list(req, res) { const rows = _stmts.list.all(req.user.id); const unread = rows.filter(r => !r.is_read).length; res.json({ notifications: rows, unread }); } /* ── PATCH /api/notifications/:id/read ──────────────────────────────────── */ function markRead(req, res) { _stmts.markOne.run(req.params.id, req.user.id); res.json({ ok: true }); } /* ── POST /api/notifications/read-all ───────────────────────────────────── */ function markAllRead(req, res) { _stmts.markAll.run(req.user.id); res.json({ ok: true }); } /* ── GET /api/notifications/stream ── SSE (auth via ?token=JWT) ─────────── */ function stream(req, res) { const token = req.query.token; if (!token) return res.status(401).end(); let userId; try { const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] }); const fresh = _stmts.getUser.get(payload.id); if (!fresh) return res.status(401).end(); if (fresh.is_banned) return res.status(403).end(); if (fresh.token_version != null && payload.tv !== fresh.token_version) return res.status(401).end(); userId = payload.id; } catch { return res.status(401).end(); } res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); res.setHeader('Referrer-Policy', 'no-referrer'); res.flushHeaders(); addClient(userId, res); res.write(`data: ${JSON.stringify({ type: 'connected' })}\n\n`); const hb = setInterval(() => { try { res.write(':hb\n\n'); } catch {} }, 25_000); req.on('close', () => { clearInterval(hb); removeClient(userId, res); }); } module.exports = { list, markRead, markAllRead, stream };