import { DBOS } from "@dbos-inc/dbos-sdk"; import { eq } from "drizzle-orm"; import Elysia, { t } from "elysia"; import { betterAuthElysia } from "../auth"; import { db } from "../db"; import { party, partyMember } from "../db/schema"; import { getMemberRecord } from "../party-data"; import type { QuizState } from "../party-types"; import { QuizWorkflow, quizQueue } from "../workflows/quiz"; import { pubsub } from "./party-socket"; const quizWf = new QuizWorkflow(); export const quizRoutes = new Elysia() .use(betterAuthElysia) .group("/party/:partyId/quiz", (app) => app .post( "/start", async ({ user, set, params }) => { const membership = await getMemberRecord(db, user.id); if (!membership || membership.partyId !== params.partyId) { set.status = 403; return { error: "Not a member of this party" }; } const existingQuiz = await db .select({ status: party.status }) .from(party) .where(eq(party.id, params.partyId)) .limit(1) .then((rows) => rows[0]); if (existingQuiz?.status === "started") { set.status = 409; return { error: "Quiz already running" }; } const handle = await DBOS.startWorkflow(quizWf.startQuiz, { queueName: quizQueue.name, enqueueOptions: { queuePartitionKey: params.partyId }, })(params.partyId); await db .update(party) .set({ status: "started", lastUpdated: new Date(), }) .where(eq(party.id, params.partyId)); const members = await db .select({ id: partyMember.id, userId: partyMember.userId, }) .from(partyMember) .where(eq(partyMember.partyId, params.partyId)); pubsub.publish( `party:${params.partyId}`, JSON.stringify({ type: "party_status", party: { status: "started" }, members, }), ); return { message: "Quiz started", workflowId: handle.workflowID, }; }, { auth: true }, ) .post( "/response", async ({ user, body, params, set }) => { const existingQuiz = await db .select({ data: party.data }) .from(party) .where(eq(party.id, params.partyId)) .limit(1) .then((rows) => rows[0]); if (!existingQuiz) { set.status = 404; return { error: "Party not found" }; } const quizData = ( (existingQuiz.data ?? {}) as Record ).quiz as QuizState | undefined; if (!quizData || quizData.status !== "running") { set.status = 400; return { error: "Quiz not running" }; } if (!quizData.workflowId) { set.status = 500; return { error: "Workflow ID not found" }; } await DBOS.send( quizData.workflowId, { playerId: user.id, selected: body.selected }, "quiz_responses", ); const updatedParty = await db .select({ data: party.data }) .from(party) .where(eq(party.id, params.partyId)) .limit(1) .then((rows) => rows[0]); const updatedQuizData = ( (updatedParty?.data ?? {}) as Record ).quiz as QuizState | undefined; if (updatedQuizData) { pubsub.publish( `party:${params.partyId}`, JSON.stringify({ type: "quiz_state", quiz: updatedQuizData, }), ); } return { message: "Response recorded" }; }, { auth: true, body: t.Object({ selected: t.Integer(), }), }, ) .get( "/status", async ({ params, set }) => { const existingQuiz = await db .select({ data: party.data }) .from(party) .where(eq(party.id, params.partyId)) .limit(1) .then((rows) => rows[0]); if (!existingQuiz) { set.status = 404; return { error: "Party not found" }; } const quizData = ( (existingQuiz.data ?? {}) as Record ).quiz as QuizState | undefined; return { quiz: quizData, }; }, { auth: true }, ), );