Clear Past Notifications
Goal: Provide superadmins (and optionally admins) a way to delete notifications that have already been sent.
Background
- The
scheduled_notifications table stores both pending (sent_at IS NULL) and sent (sent_at IS NOT NULL) notifications.
- The Danger Zone reset (
POST /superadmin/reset-hackathon with reset_notifications: true) already bulk-deletes all notifications (sent + pending) via Hackathon.Reset().
- The existing individual delete (
DELETE /superadmin/notifications/{notificationID}) is blocked for sent notifications by AND sent_at IS NULL in the store query, returning 409.
- The frontend "Sent" tab in the notifications page is read-only — no delete buttons.
Solution
Remove the AND sent_at IS NULL guard from the individual delete path so superadmins can delete any single notification regardless of its sent status. Add delete buttons to the "Sent" tab in the UI.
Changes
Backend
internal/store/scheduled_notifications.go
Remove AND sent_at IS NULL from the Delete() query:
- DELETE FROM scheduled_notifications WHERE id = $1 AND sent_at IS NULL
+ DELETE FROM scheduled_notifications WHERE id = $1
This eliminates the ErrConflict path (which was only reachable when the row existed but had sent_at IS NOT NULL). The only error cases after this change are ErrNotFound (no row with that ID) or a database error.
cmd/api/scheduled_notifications.go
Remove the 409 conflict case from deleteScheduledNotificationHandler:
if err := app.store.ScheduledNotifications.Delete(r.Context(), id); err != nil {
switch {
case errors.Is(err, store.ErrNotFound):
app.notFoundResponse(w, r, errors.New("notification not found"))
return
- case errors.Is(err, store.ErrConflict):
- app.conflictResponse(w, r, errors.New("notification already sent"))
- return
}
app.internalServerError(w, r, err)
return
}
cmd/api/scheduled_notifications_test.go
- Remove the test case
"returns 409 when already sent" from TestDeleteScheduledNotification.
- Add a test case
"deletes a sent notification" that asserts 204.
Frontend
client/web/src/pages/superadmin/notifications/components/NotificationsTable.tsx
- Add an "Actions" column header to the sent tab's
<TableHeader>.
- Add a delete button (
Trash2 icon) to each sent row, reusing the existing setDeleteTarget / deleteTarget state and the same confirmation AlertDialog.
- Update the confirmation dialog description text from "Already-sent notifications cannot be deleted" to something generic like "This action cannot be undone."
No changes needed to the store, API module, or types — the existing remove action and deleteScheduledNotification API call already work for any notification ID.
Files Modified
| File |
Change |
internal/store/scheduled_notifications.go |
Remove AND sent_at IS NULL from Delete() |
cmd/api/scheduled_notifications.go |
Remove 409 conflict case from handler |
cmd/api/scheduled_notifications_test.go |
Update tests |
client/web/src/pages/superadmin/notifications/components/NotificationsTable.tsx |
Add delete buttons to sent rows, update dialog text |
Not in Scope
- Bulk "clear all sent" button — the Danger Zone reset already covers this.
- Admin role access — the existing route is superadmin-only. Extending to admins would require a separate route group and middleware change.
- Schema migration — no changes needed.
Clear Past Notifications
Goal: Provide superadmins (and optionally admins) a way to delete notifications that have already been sent.
Background
scheduled_notificationstable stores both pending (sent_at IS NULL) and sent (sent_at IS NOT NULL) notifications.POST /superadmin/reset-hackathonwithreset_notifications: true) already bulk-deletes all notifications (sent + pending) viaHackathon.Reset().DELETE /superadmin/notifications/{notificationID}) is blocked for sent notifications byAND sent_at IS NULLin the store query, returning 409.Solution
Remove the
AND sent_at IS NULLguard from the individual delete path so superadmins can delete any single notification regardless of its sent status. Add delete buttons to the "Sent" tab in the UI.Changes
Backend
internal/store/scheduled_notifications.goRemove
AND sent_at IS NULLfrom theDelete()query:This eliminates the
ErrConflictpath (which was only reachable when the row existed but hadsent_at IS NOT NULL). The only error cases after this change areErrNotFound(no row with that ID) or a database error.cmd/api/scheduled_notifications.goRemove the 409 conflict case from
deleteScheduledNotificationHandler:if err := app.store.ScheduledNotifications.Delete(r.Context(), id); err != nil { switch { case errors.Is(err, store.ErrNotFound): app.notFoundResponse(w, r, errors.New("notification not found")) return - case errors.Is(err, store.ErrConflict): - app.conflictResponse(w, r, errors.New("notification already sent")) - return } app.internalServerError(w, r, err) return }cmd/api/scheduled_notifications_test.go"returns 409 when already sent"fromTestDeleteScheduledNotification."deletes a sent notification"that asserts 204.Frontend
client/web/src/pages/superadmin/notifications/components/NotificationsTable.tsx<TableHeader>.Trash2icon) to each sent row, reusing the existingsetDeleteTarget/deleteTargetstate and the same confirmationAlertDialog.No changes needed to the store, API module, or types — the existing
removeaction anddeleteScheduledNotificationAPI call already work for any notification ID.Files Modified
internal/store/scheduled_notifications.goAND sent_at IS NULLfromDelete()cmd/api/scheduled_notifications.gocmd/api/scheduled_notifications_test.goclient/web/src/pages/superadmin/notifications/components/NotificationsTable.tsxNot in Scope