import type { FastifyInstance } from "fastify"; import type { Db } from "@parking/db"; import { requirePermission, bumpPermsCache } from "../auth.js"; import { listRecycleBin, purge, restore, restoreBlockedReason, retentionDays, RESOURCE_KINDS, type ResourceKind, } from "../recycle-bin.js"; // Recycle bin API — view / restore / purge soft-deleted master data. The actual // soft-delete STAMP happens in each resource's own DELETE route (users/roles/ // subscriptions/plans/tariffs); this is the way back. Admin-grade (recyclebin:*). // See recycle-bin.ts, wiki/concepts/soft-delete.md. function isKind(s: string): s is ResourceKind { return (RESOURCE_KINDS as string[]).includes(s); } export async function recycleBinRoutes(app: FastifyInstance, db: Db): Promise { // List everything in the bin (+ the retention window so the UI can warn how long // items survive before auto-purge). app.get( "/api/recycle-bin", { preHandler: requirePermission("recyclebin:read") }, async () => ({ items: listRecycleBin(db), retentionDays: retentionDays() }), ); // Restore a soft-deleted item (clear the stamps → it reappears in its catalog). // Blocked with a 409 when a live row would collide (e.g. the username was reused). app.post<{ Params: { kind: string; id: string } }>( "/api/recycle-bin/:kind/:id/restore", { preHandler: requirePermission("recyclebin:update") }, async (req, reply) => { const { kind, id } = req.params; if (!isKind(kind)) return reply.code(400).send({ error: `unknown resource kind: ${kind}` }); const blocked = restoreBlockedReason(db, kind, id); if (blocked) return reply.code(409).send({ error: `cannot restore: ${blocked}` }); const ok = restore(db, kind, id); if (!ok) return reply.code(404).send({ error: "no deleted item to restore" }); // A restored role/user changes the authz picture — drop the permission cache. if (kind === "role" || kind === "user") bumpPermsCache(); app.log.info(`recycle-bin: restored ${kind} ${id}`); return { kind, id, restored: true }; }, ); // Purge (permanently delete) a soft-deleted item + its children. Irreversible. app.delete<{ Params: { kind: string; id: string } }>( "/api/recycle-bin/:kind/:id", { preHandler: requirePermission("recyclebin:delete") }, async (req, reply) => { const { kind, id } = req.params; if (!isKind(kind)) return reply.code(400).send({ error: `unknown resource kind: ${kind}` }); const ok = purge(db, kind, id); if (!ok) return reply.code(404).send({ error: "no deleted item to purge" }); if (kind === "role" || kind === "user") bumpPermsCache(); app.log.warn(`recycle-bin: PURGED ${kind} ${id} (permanent)`); return reply.code(204).send(); }, ); }