feat: trash (soft delete) with admin tool management
Build & Push Docker Image / build (push) Successful in 2m15s

- tools: add deletedAt/deletedBy, soft delete via DELETE /tools/:id when
  actor has trash entitlement, else immediate hard delete
- trash endpoints: GET /tools/trash, POST /tools/trash (admin bulk),
  POST /tools/trash/restore, DELETE /tools/trash, POST /tools/trash/empty
- trash feature for premium/enterprise; exclude trashed from all public
  surfaces (browse, categories, features, tags, similar, ratings, costs,
  analytics, redundancy)
- TRASH_RETENTION_DAYS env (0 = keep forever) with hourly purge job
- frontend: /trash page (premium+, restore for all, permanent delete +
  empty for admin), admin Tools tab with multi-select bulk trash,
  sidebar Trash link, tool-detail delete hint
This commit is contained in:
opencode
2026-08-02 02:02:16 +02:00
parent 32df998399
commit 2f20b1e5c2
25 changed files with 1404 additions and 28 deletions
+6 -1
View File
@@ -76,6 +76,7 @@ export default function ToolDetail() {
const { user, isAdmin, hasFeature } = useAuth();
const canManageCosts = hasFeature("costs");
const hasTrash = hasFeature("trash");
const deleteTool = useDeleteTool();
const [similarData, setSimilarData] = useState<{ manual: any[]; auto: any[] } | null>(null);
@@ -860,7 +861,11 @@ export default function ToolDetail() {
<AlertDialogHeader>
<AlertDialogTitle>Delete this tool?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently remove <span className="font-medium">{tool?.name}</span> and all its ratings. This cannot be undone.
{hasTrash ? (
<>This will move <span className="font-medium">{tool?.name}</span> to the trash. It can be restored later.</>
) : (
<>This will permanently remove <span className="font-medium">{tool?.name}</span> and all its ratings. This cannot be undone.</>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>