Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 1x 16x 3x 3x 13x 1x 8x 8x 7x 12x 12x 6x 1x 1x 1x 4x 4x 3x 3x 3x 3x 2x 1x 1x 1x 4x 4x 3x 2x 1x 1x 1x | /**
* Suggestion notification inbox routes.
*
* GET /notifications -> paginated inbox, newest first, with unreadCount
* PUT /notifications/read-all -> mark all notifications as read
* PUT /notifications/:id/read -> mark single notification as read
*/
const router = require('express').Router();
const { db } = require('../utils/firebase');
const log = require('../utils/log');
function requireAuth(req, res) {
if (!req.auth || !req.auth.uniqueId) {
res.status(401).json({ error: 'Authentication required' });
return true;
}
return false;
}
// ─── GET /notifications ─────────────────────────────────────────
router.get('/notifications', async (req, res) => {
try {
if (requireAuth(req, res)) return;
const snap = await db
.collection('notifications')
.where('uid', '==', req.auth.uniqueId)
.orderBy('createdAt', 'desc')
.get();
const notifications = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
const unreadCount = notifications.filter((n) => !n.isRead).length;
res.json({ notifications, unreadCount, total: notifications.length });
} catch (err) {
log.error('notifications', 'Failed to list', { error: err.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// ─── PUT /notifications/read-all ────────────────────────────────
// NOTE: Must be registered BEFORE /:id/read to avoid "read-all"
// being captured as an :id parameter.
router.put('/notifications/read-all', async (req, res) => {
try {
if (requireAuth(req, res)) return;
const snap = await db
.collection('notifications')
.where('uid', '==', req.auth.uniqueId)
.where('isRead', '==', false)
.get();
const batch = db.batch();
snap.docs.forEach((d) => batch.update(db.doc('notifications/' + d.id), { isRead: true }));
await batch.commit();
res.json({ success: true, updated: snap.size });
} catch (err) {
log.error('notifications', 'Mark all read failed', { error: err.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// ─── PUT /notifications/:id/read ────────────────────────────────
router.put('/notifications/:id/read', async (req, res) => {
try {
if (requireAuth(req, res)) return;
await db.doc('notifications/' + req.params.id).update({ isRead: true });
res.json({ success: true });
} catch (err) {
log.error('notifications', 'Mark read failed', { error: err.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// POST /subscriptions/unsubscribe is handled by subscriptions.js (HMAC-based token verification)
module.exports = router;
|