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
+138
View File
@@ -165,6 +165,128 @@ paths:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash:
get:
operationId: listTrashedTools
tags: [tools]
summary: List trashed (soft-deleted) tools
parameters:
- name: search
in: query
required: false
schema:
type: string
responses:
"200":
description: List of trashed tools
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Tool"
"403":
description: Feature "trash" requires a higher tier
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
post:
operationId: trashTools
tags: [tools]
summary: Move tools to trash (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"200":
description: Tools trashed
content:
application/json:
schema:
type: object
properties:
trashed:
type: integer
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
delete:
operationId: deleteTrashedTools
tags: [tools]
summary: Permanently delete trashed tools (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"204":
description: Deleted
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash/restore:
post:
operationId: restoreTools
tags: [tools]
summary: Restore trashed tools
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"200":
description: Tools restored
content:
application/json:
schema:
type: object
properties:
restored:
type: integer
"403":
description: Feature "trash" requires a higher tier
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash/empty:
post:
operationId: emptyTrash
tags: [tools]
summary: Permanently delete all trashed tools (admin)
responses:
"200":
description: Trash emptied
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/{id}/ratings:
get:
operationId: listToolRatings
@@ -658,6 +780,11 @@ components:
updatedAt:
type: string
format: date-time
deletedAt:
type: ["string", "null"]
format: date-time
deletedBy:
type: ["string", "null"]
ToolWithStats:
type: object
@@ -749,6 +876,17 @@ components:
items:
type: string
TrashToolsInput:
type: object
required: [ids]
properties:
ids:
type: array
minItems: 1
maxItems: 500
items:
type: integer
Rating:
type: object
required: [id, toolId, usefulness, usability, createdAt]