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
@@ -130,6 +130,10 @@ export interface Tool {
tags?: string[];
createdAt: string;
updatedAt: string;
/** @nullable */
deletedAt?: string | null;
/** @nullable */
deletedBy?: string | null;
}
export interface ToolWithStats {
@@ -182,6 +186,14 @@ export interface ToolUpdate {
tags?: string[];
}
export interface TrashToolsInput {
/**
* @minItems 1
* @maxItems 500
*/
ids: number[];
}
export interface Rating {
id: number;
toolId: number;
@@ -306,6 +318,22 @@ export const ListToolsSort = {
most_reviewed: 'most_reviewed',
} as const;
export type ListTrashedToolsParams = {
search?: string;
};
export type TrashTools200 = {
trashed?: number;
};
export type RestoreTools200 = {
restored?: number;
};
export type EmptyTrash200 = {
deleted?: number;
};
export type GetTopToolsParams = {
limit?: number;
metric?: GetTopToolsMetric;