Files
Learn_System/backend/src/controllers/notificationController.js
T
Maxim Dolgolyov be4d43105e LearnSpace: full-stack educational whiteboard platform
Node.js/Express backend + vanilla JS frontend.
Features: real-time collaborative whiteboard (SSE), multi-page support,
LaTeX formulas, shapes/connectors, coordinate systems, number lines,
compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with
rotation & resize controls, minimap navigation overlay, auto-measurements,
multi-page thumbnails sidebar, PNG export, page templates.
Student/teacher workflows: classes, assignments, library, dashboard.
Mobile responsive. SQLite (better-sqlite3).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 10:10:37 +03:00

67 lines
2.6 KiB
JavaScript

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 };