feat: auth foundation, similar tools, costs, redundancy, anonymous voting
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user