Merge pull request #598 from the-luap/fix/notifications-clear-all-597

fix(notifications): restore /clear-all route the frontend already calls (#597)
This commit is contained in:
Paul Nothaft
2026-06-02 09:16:47 +02:00
committed by GitHub
3 changed files with 23 additions and 56 deletions
+2 -2
View File
@@ -60,9 +60,9 @@ jobs:
# integration/webhookDelivery — supertest fixture
# services/backupService.enhanced — knex mock chain
# routes/__tests__/adminAuth — supertest fixture
# routes/__tests__/adminNotifications — supertest fixture
# (adminNotifications was excluded; #597 fix re-enables it.)
npx jest \
--testPathIgnorePatterns='/node_modules/|adminSettings\.logo\.test|integration/adminPhotos\.reference|integration/webhookDelivery|backupService\.enhanced|routes/__tests__/adminAuth|routes/__tests__/adminNotifications' \
--testPathIgnorePatterns='/node_modules/|adminSettings\.logo\.test|integration/adminPhotos\.reference|integration/webhookDelivery|backupService\.enhanced|routes/__tests__/adminAuth' \
--ci
frontend:
@@ -28,6 +28,13 @@ jest.mock('../../middleware/auth', () => ({
adminAuth: (_req, _res, next) => next(),
}));
// requirePermission is its own module — without this mock the real
// implementation runs, queries role_permissions on the mocked db, and
// 403s before we ever reach the handler.
jest.mock('../../middleware/permissions', () => ({
requirePermission: () => (_req, _res, next) => next(),
}));
const { db } = require('../../database/db');
const notificationsRouter = require('../adminNotifications');
+14 -54
View File
@@ -98,62 +98,22 @@ router.put('/read-all', adminAuth, requirePermission('settings.edit'), async (re
}
});
// Delete old notifications (older than 30 days and read)
router.delete('/clear-old', adminAuth, requirePermission('settings.edit'), async (req, res) => {
// Clear all notifications (#597).
//
// The frontend AdminHeader "Clear All" button hits this — its service
// at `notifications.service.ts` does DELETE /admin/notifications/clear-all.
// The previous /clear-old route was named for an "older than 30 days
// and read" semantic but had a fallback that deleted EVERYTHING when
// nothing matched the date filter, so it was effectively a confusingly
// named Clear All anyway. Drop the rename and the branching, return
// the simple deletedCount the existing test (and frontend toast) expect.
router.delete('/clear-all', adminAuth, requirePermission('settings.edit'), async (req, res) => {
try {
// Use database-agnostic date calculation
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
let deletedCount = 0;
const client = db?.client?.config?.client;
if (client === 'pg') {
const primaryResult = await db.raw(
`
WITH deleted AS (
DELETE FROM activity_logs
WHERE read_at IS NOT NULL OR created_at < ?
RETURNING id
)
SELECT COUNT(*)::int AS count FROM deleted
`,
[thirtyDaysAgo.toISOString()]
);
deletedCount = primaryResult.rows?.[0]?.count || 0;
if (deletedCount === 0) {
const fallbackResult = await db.raw(
`
WITH deleted AS (
DELETE FROM activity_logs
RETURNING id
)
SELECT COUNT(*)::int AS count FROM deleted
`
);
deletedCount = fallbackResult.rows?.[0]?.count || 0;
}
} else {
deletedCount = await db('activity_logs')
.where(function () {
this.whereNotNull('read_at')
.orWhere('created_at', '<', thirtyDaysAgo);
})
.delete();
if (deletedCount === 0) {
deletedCount = await db('activity_logs').delete();
}
}
res.json({
message: deletedCount > 0 ? 'Old notifications cleared' : 'No notifications to clear',
deletedCount
});
const deletedCount = await db('activity_logs').delete();
res.json({ message: 'All notifications cleared', deletedCount });
} catch (error) {
console.error('Clear old notifications error:', error);
res.status(500).json({ error: 'Failed to clear old notifications' });
console.error('Clear notifications error:', error);
res.status(500).json({ error: 'Failed to clear notifications' });
}
});