itpdp/api/src/routes/quiz.ts
2026-05-01 20:53:24 +02:00

166 lines
4 KiB
TypeScript

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<string, unknown>
).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<string, unknown>
).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<string, unknown>
).quiz as QuizState | undefined;
return {
quiz: quizData,
};
},
{ auth: true },
),
);