fix: category cache refresh, API 404, redundancy mapping, cost/relation authz, voterToken exposure
Build & Push Docker Image / build (push) Successful in 8m33s
Build & Push Docker Image / build (push) Successful in 8m33s
- Invalidate categories/features queries after creating/editing tools so new categories appear immediately in search, browse dropdown and tool form - Always refetch categories/features when the combobox/suggestion inputs mount - Return JSON 404 for unmatched /api routes instead of the SPA index.html - Read the manually confirmed 'better tool' from the recommendation notes instead of using the min tool id in the redundancy dashboard - Require admin for cost/relation update+delete endpoints - Stop exposing the voter token in the ratings list response - Fix parseInt type error on user id params (Express 5 params typing)
This commit is contained in:
@@ -60,6 +60,12 @@ app.use(
|
||||
|
||||
app.use("/api", router);
|
||||
|
||||
// Any unmatched /api route should return a JSON 404 instead of falling
|
||||
// through to the SPA catch-all below.
|
||||
app.use("/api", (_req, res) => {
|
||||
res.status(404).json({ error: "Not found" });
|
||||
});
|
||||
|
||||
const staticDir = process.env.STATIC_DIR;
|
||||
if (staticDir && existsSync(staticDir)) {
|
||||
app.use(express.static(staticDir));
|
||||
|
||||
@@ -95,7 +95,8 @@ router.get("/admin/redundancy", requireAuth, async (_req, res): Promise<void> =>
|
||||
const manualEvalMap = new Map<string, number>();
|
||||
for (const e of manualEvals) {
|
||||
const key = [Math.min(e.toolId, e.relatedToolId), Math.max(e.toolId, e.relatedToolId)].join(":");
|
||||
manualEvalMap.set(key, e.toolId);
|
||||
const betterMatch = e.notes?.match(/Recommended:\s*(\d+)/);
|
||||
manualEvalMap.set(key, betterMatch ? Number(betterMatch[1]) : e.toolId);
|
||||
}
|
||||
|
||||
function buildStats(t: typeof toolsTable.$inferSelect) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { db, toolsTable, toolCostsTable } from "@workspace/db";
|
||||
import { requireAuth } from "../middleware/auth";
|
||||
import { requireAuth, requireAdmin } from "../middleware/auth";
|
||||
import { requireFeature } from "../middleware/feature";
|
||||
import { writeAuditLog } from "../lib/audit";
|
||||
|
||||
@@ -47,7 +47,7 @@ router.post("/tools/:id/costs", requireAuth, requireFeature("costs"), async (req
|
||||
res.status(201).json(entry);
|
||||
});
|
||||
|
||||
router.patch("/costs/:id", requireAuth, async (req, res): Promise<void> => {
|
||||
router.patch("/costs/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
const id = Number(req.params.id);
|
||||
if (isNaN(id)) { res.status(400).json({ error: "Invalid id" }); return; }
|
||||
|
||||
@@ -68,7 +68,7 @@ router.patch("/costs/:id", requireAuth, async (req, res): Promise<void> => {
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
router.delete("/costs/:id", requireAuth, async (req, res): Promise<void> => {
|
||||
router.delete("/costs/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
const id = Number(req.params.id);
|
||||
if (isNaN(id)) { res.status(400).json({ error: "Invalid id" }); return; }
|
||||
|
||||
|
||||
@@ -31,7 +31,15 @@ router.get("/tools/:id/ratings", async (req, res): Promise<void> => {
|
||||
}
|
||||
|
||||
const ratings = await db
|
||||
.select()
|
||||
.select({
|
||||
id: ratingsTable.id,
|
||||
toolId: ratingsTable.toolId,
|
||||
usefulness: ratingsTable.usefulness,
|
||||
usability: ratingsTable.usability,
|
||||
comment: ratingsTable.comment,
|
||||
reviewerName: ratingsTable.reviewerName,
|
||||
createdAt: ratingsTable.createdAt,
|
||||
})
|
||||
.from(ratingsTable)
|
||||
.where(eq(ratingsTable.toolId, params.data.id))
|
||||
.orderBy(ratingsTable.createdAt);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
UpdateToolBody,
|
||||
DeleteToolParams,
|
||||
} from "@workspace/api-zod";
|
||||
import { requireAuth } from "../middleware/auth";
|
||||
import { requireAuth, requireAdmin } from "../middleware/auth";
|
||||
import { requireFeature } from "../middleware/feature";
|
||||
import { writeAuditLog } from "../lib/audit";
|
||||
|
||||
@@ -349,7 +349,7 @@ router.post("/tools/:id/relations", requireAuth, requireFeature("similar-tools")
|
||||
res.status(201).json(relation);
|
||||
});
|
||||
|
||||
router.delete("/tools/relations/:id", requireAuth, async (req, res): Promise<void> => {
|
||||
router.delete("/tools/relations/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
const id = Number(req.params.id);
|
||||
if (isNaN(id)) { res.status(400).json({ error: "Invalid id" }); return; }
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ router.post("/users", requireAdmin, async (req, res): Promise<void> => {
|
||||
});
|
||||
|
||||
router.patch("/users/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const id = parseInt(String(req.params.id), 10);
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ error: "Invalid user id" });
|
||||
return;
|
||||
@@ -107,7 +107,7 @@ router.patch("/users/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
});
|
||||
|
||||
router.delete("/users/:id", requireAdmin, async (req, res): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const id = parseInt(String(req.params.id), 10);
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ error: "Invalid user id" });
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user