import { z } from "zod"; import { eq, and, sql } from "drizzle-orm"; import { router, protectedProcedure, publicProcedure } from "../index"; import { db } from "@pram/db"; import { questionFeedback } from "@pram/db"; import { checkRateLimit } from "../lib/rate-limit"; export const feedbackRouter = router({ getQuestionFeedback: publicProcedure .input(z.object({ questionId: z.string().uuid() })) .query(async ({ ctx, input }) => { const userId = ctx.session?.user.id; const [counts] = await db .select({ up: sql`count(case when ${questionFeedback.type} = 'up' then 1 end)::int`, down: sql`count(case when ${questionFeedback.type} = 'down' then 1 end)::int`, }) .from(questionFeedback) .where(eq(questionFeedback.questionId, input.questionId)); let myFeedback: "up" | "down" | null = null; if (userId) { const [row] = await db .select({ type: questionFeedback.type }) .from(questionFeedback) .where( and( eq(questionFeedback.questionId, input.questionId), eq(questionFeedback.userId, userId), ), ) .limit(1); myFeedback = (row?.type as "up" | "down") ?? null; } return { up: counts?.up ?? 0, down: counts?.down ?? 0, myFeedback }; }), voteQuestion: protectedProcedure .input( z.object({ questionId: z.string().uuid(), type: z.enum(["up", "down"]), note: z.string().optional(), }), ) .mutation(async ({ ctx, input }) => { await checkRateLimit({ key: `vote:${ctx.session.user.id}`, limit: 10, windowMs: 10_000 }); const existing = await db .select() .from(questionFeedback) .where( and( eq(questionFeedback.questionId, input.questionId), eq(questionFeedback.userId, ctx.session.user.id), ), ) .limit(1); const existingFeedback = existing[0]; if (existingFeedback) { if (existingFeedback.type === input.type) { // Toggle off if same type clicked again await db .delete(questionFeedback) .where(eq(questionFeedback.id, existingFeedback.id)); } else { await db .update(questionFeedback) .set({ type: input.type, note: input.note ?? null }) .where(eq(questionFeedback.id, existingFeedback.id)); } } else { await db.insert(questionFeedback).values({ userId: ctx.session.user.id, questionId: input.questionId, type: input.type, note: input.note ?? null, }); } const [counts] = await db .select({ up: sql`count(case when ${questionFeedback.type} = 'up' then 1 end)::int`, down: sql`count(case when ${questionFeedback.type} = 'down' then 1 end)::int`, }) .from(questionFeedback) .where(eq(questionFeedback.questionId, input.questionId)); return { up: counts?.up ?? 0, down: counts?.down ?? 0 }; }), });