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
+9 -1
View File
@@ -22,8 +22,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Skeleton } from "@/components/ui/skeleton";
import { useToast } from "@/hooks/use-toast";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Users, ScrollText, ShieldAlert, Plus, Pencil, Trash2, Clock, AlertTriangle } from "lucide-react";
import { Users, ScrollText, ShieldAlert, Plus, Pencil, Trash2, Clock, AlertTriangle, Wrench } from "lucide-react";
import { format } from "date-fns";
import { AdminToolsTab } from "@/components/admin-tools-tab";
export default function Admin() {
const [, setLocation] = useLocation();
@@ -147,6 +148,9 @@ export default function Admin() {
<TabsTrigger value="users" className="gap-2">
<Users className="w-4 h-4" /> Users
</TabsTrigger>
<TabsTrigger value="tools" className="gap-2">
<Wrench className="w-4 h-4" /> Tools
</TabsTrigger>
<TabsTrigger value="audit" className="gap-2">
<ScrollText className="w-4 h-4" /> Audit Log
</TabsTrigger>
@@ -220,6 +224,10 @@ export default function Admin() {
</Card>
</TabsContent>
<TabsContent value="tools">
<AdminToolsTab />
</TabsContent>
<TabsContent value="audit">
<Card>
<CardHeader>