fix(tools): OR'ed where clauses now AND'd so search excludes soft-deleted tools; localize delete confirm dialog

This commit is contained in:
opencode
2026-08-03 08:48:08 +02:00
parent 743b177c89
commit 50756044b0
4 changed files with 25 additions and 13 deletions
+8 -6
View File
@@ -1,5 +1,5 @@
import { Router, type IRouter } from "express";
import { eq, desc, asc, sql, and, not, isNull, inArray } from "drizzle-orm";
import { eq, desc, asc, sql, and, not, isNull, inArray, type SQL } from "drizzle-orm";
import { z } from "zod";
import { db, toolsTable, ratingsTable, toolRelationsTable } from "@workspace/db";
import {
@@ -57,21 +57,23 @@ router.get("/tools", async (req, res): Promise<void> => {
const tagList = (tags ?? "").split(",").map((t) => t.trim()).filter(Boolean);
const featureList = (features ?? "").split(",").map((f) => f.trim()).filter(Boolean);
let query = db.select().from(toolsTable).where(isNull(toolsTable.deletedAt)).$dynamic();
const conditions: SQL[] = [isNull(toolsTable.deletedAt)];
if (category) {
query = query.where(eq(toolsTable.category, category));
conditions.push(eq(toolsTable.category, category));
}
if (search) {
const escaped = search.replace(/[%_\\]/g, (m) => `\\${m}`);
query = query.where(sql`${toolsTable.name} ilike ${`%${escaped}%`} escape '\\'`);
conditions.push(sql`${toolsTable.name} ilike ${`%${escaped}%`} escape '\\'`);
}
if (tagList.length > 0) {
query = query.where(sql`${toolsTable.tags} @> ARRAY[${sql.join(tagList.map((t) => sql`${t}`), sql`, `)}]::text[]`);
conditions.push(sql`${toolsTable.tags} @> ARRAY[${sql.join(tagList.map((t) => sql`${t}`), sql`, `)}]::text[]`);
}
if (featureList.length > 0) {
query = query.where(sql`${toolsTable.features} @> ARRAY[${sql.join(featureList.map((f) => sql`${f}`), sql`, `)}]::text[]`);
conditions.push(sql`${toolsTable.features} @> ARRAY[${sql.join(featureList.map((f) => sql`${f}`), sql`, `)}]::text[]`);
}
const query = db.select().from(toolsTable).where(and(...conditions));
const tools = await query.orderBy(desc(toolsTable.createdAt));
const toolIds = tools.map((t) => t.id);
+6 -1
View File
@@ -127,7 +127,12 @@
"relatedTools": "Ähnliche Tools",
"costs": "Kosten",
"addCost": "Kosten hinzufügen",
"recentRatings": "Letzte Bewertungen"
"recentRatings": "Letzte Bewertungen",
"deleteConfirmTitle": "Dieses Tool löschen?",
"deleteToTrash": "Dies verschiebt {{name}} in den Papierkorb. Es kann später wiederhergestellt werden.",
"deletePermanent": "Dies entfernt {{name}} dauerhaft inklusive aller Bewertungen. Das kann nicht rückgängig gemacht werden.",
"deleting": "Löschen…",
"deleteAction": "Löschen"
},
"compare": {
"title": "Tools vergleichen",
+6 -1
View File
@@ -127,7 +127,12 @@
"relatedTools": "Related Tools",
"costs": "Costs",
"addCost": "Add Cost",
"recentRatings": "Recent Ratings"
"recentRatings": "Recent Ratings",
"deleteConfirmTitle": "Delete this tool?",
"deleteToTrash": "This will move {{name}} to the trash. It can be restored later.",
"deletePermanent": "This will permanently remove {{name}} and all its ratings. This cannot be undone.",
"deleting": "Deleting…",
"deleteAction": "Delete"
},
"compare": {
"title": "Compare Tools",
+5 -5
View File
@@ -914,23 +914,23 @@ export default function ToolDetail() {
<AlertDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete this tool?</AlertDialogTitle>
<AlertDialogTitle>{t("detail.deleteConfirmTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{hasTrash ? (
<>This will move <span className="font-medium">{tool?.name}</span> to the trash. It can be restored later.</>
<>{t("detail.deleteToTrash", { name: tool?.name })}</>
) : (
<>This will permanently remove <span className="font-medium">{tool?.name}</span> and all its ratings. This cannot be undone.</>
<>{t("detail.deletePermanent", { name: tool?.name })}</>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={handleDelete}
disabled={deleteTool.isPending}
>
{deleteTool.isPending ? "Deleting" : "Delete"}
{deleteTool.isPending ? t("detail.deleting") : t("detail.deleteAction")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>