feat(recycle-bin): soft delete + restore for master data
Accidental admin deletes of users/roles/subscriptions/plans/tariffs were hard and unrecoverable. Now they soft-delete into a recycle bin. Schema (migration 0012): nullable deleted_at + deleted_by on users, roles, subscriptions, subscription_plans, tariffs. Additive ADD COLUMN; verified against a copy of the live DB. Backend: each resource's DELETE route STAMPS instead of removing; every catalog list filters deleted_at IS NULL. New recycle-bin module + routes (GET /api/recycle-bin, POST .../restore, DELETE .../:id purge) gated on a new recyclebin:read/update/delete permission. A 6-hourly + startup sweep auto-purges items older than RECYCLE_BIN_RETENTION_DAYS (default 30; 0 = forever). Invariants: soft-deleted users can't log in (login rejects deleted_at; no-lockout counts live admins only); a soft-deleted subscription doesn't open the barrier; plans are versioned so a delete stamps all versions of the plan_id (bin shows one item); username/role-name UNIQUE spans deleted rows so reuse returns a clear 409 pointing at the bin; restore doesn't auto-cascade a dangling role (guard resolves missing role to empty perms). The signed append-only ledger is OUT of scope (no delete path). Web: a Recycle bin tab under Setup (RecycleBin.tsx) with Restore/Purge + purge confirm; api client + i18n (sq + en parity). Tests: recycle-bin.test.ts (9 unit) + recycle-bin-routes.test.ts (4 integration: delete -> can't-login -> restore -> login, purge, gating, 409 reuse). server 103/103; build+lint+test 19/19. Wiki: new concepts/soft-delete.md; local-jwt-auth + index + log updated. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+16
@@ -1354,3 +1354,19 @@ so the booth bundle is untouched. reports.test.ts (10) pins the sums/tz/split/du
|
||||
90/90, build+lint 14/14. Also (earlier same session): a camera "Test ANPR" probe in first-run setup
|
||||
(`POST /api/setup/test-anpr`) — snapshot→vision analyze, fail-soft, shown only when a camera's ANPR
|
||||
opt-in is checked. See [[reporting-analytics]], [[opencv-anpr-service]].
|
||||
|
||||
## [2026-06-22] feat | Soft delete + recycle bin for master data (migration 0012)
|
||||
Accidental admin deletes used to be hard + unrecoverable. Now users/roles/subscriptions/plans/
|
||||
tariffs soft-delete: migration 0012 adds nullable deleted_at + deleted_by; each resource's DELETE
|
||||
route STAMPS instead of removing, and every catalog list filters deleted_at IS NULL. A recycle bin
|
||||
(GET /api/recycle-bin, POST .../restore, DELETE .../:id purge — gated recyclebin:read/update/delete,
|
||||
new resource in the PERMISSIONS grid) lists everything soft-deleted, restores, or purges; a 6-hourly
|
||||
+ startup sweep auto-purges items older than RECYCLE_BIN_RETENTION_DAYS (default 30; 0 = forever).
|
||||
Key invariants: soft-deleted users CAN'T log in (login rejects deleted_at; no-lockout counts live
|
||||
admins only); a soft-deleted subscription doesn't open the barrier; PLANS are versioned so a delete
|
||||
stamps all version rows of the plan_id (bin shows one item); username/role-name UNIQUE spans deleted
|
||||
rows so reuse returns a clear 409 pointing at the bin; restore doesn't auto-cascade a dangling role
|
||||
(guard resolves missing role → empty perms, safe). Signed ledger is OUT of scope (no delete path).
|
||||
Web: a Recycle bin tab under Setup (RecycleBin.tsx). Tests: recycle-bin.test.ts (9 unit) +
|
||||
recycle-bin-routes.test.ts (4 integration: delete→can't-login→restore→login, purge, gating, 409
|
||||
reuse); server 103/103, build+lint 19/19, i18n parity (sq+en). See [[soft-delete]], [[local-jwt-auth]].
|
||||
|
||||
Reference in New Issue
Block a user