feat: auth foundation, similar tools, costs, redundancy, anonymous voting

This commit is contained in:
root
2026-07-29 21:59:29 +02:00
parent 8b3b7c9955
commit 59badeaa48
22 changed files with 793 additions and 15 deletions
+22 -1
View File
@@ -1,5 +1,5 @@
import { Router, type IRouter } from "express";
import { eq } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { db, toolsTable, ratingsTable } from "@workspace/db";
import {
ListToolRatingsParams,
@@ -7,9 +7,16 @@ import {
CreateRatingBody,
} from "@workspace/api-zod";
import { requireAuth } from "../middleware/auth";
import crypto from "crypto";
const router: IRouter = Router();
const VOTER_SECRET = process.env.VOTER_SECRET || "dev-voter-secret-change-in-production";
function computeVoterToken(userId: string): string {
return crypto.createHmac("sha256", VOTER_SECRET).update(userId).digest("hex");
}
router.get("/tools/:id/ratings", async (req, res): Promise<void> => {
const params = ListToolRatingsParams.safeParse(req.params);
if (!params.success) {
@@ -51,12 +58,26 @@ router.post("/tools/:id/ratings", requireAuth, async (req, res): Promise<void> =
return;
}
const token = computeVoterToken(req.session.user!.sub);
const [existing] = await db
.select()
.from(ratingsTable)
.where(and(eq(ratingsTable.toolId, params.data.id), eq(ratingsTable.voterToken, token)))
.limit(1);
if (existing) {
res.status(409).json({ error: "You have already reviewed this tool." });
return;
}
const [rating] = await db.insert(ratingsTable).values({
toolId: params.data.id,
usefulness: parsed.data.usefulness,
usability: parsed.data.usability,
comment: parsed.data.comment ?? null,
reviewerName: parsed.data.reviewerName ?? null,
voterToken: token,
}).returning();
res.status(201).json(rating);