Revisione/server/api/topics/[id]/progress.post.ts

55 lines
2.1 KiB
TypeScript

import { db } from "../../../db/index";
import { topics, userProgress } from "../../../db/schema";
import { eq, and, sql } from "drizzle-orm";
import { randomUUID } from "crypto";
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id")!;
const topic = await db.query.topics.findFirst({ where: eq(topics.id, id) });
if (!topic) throw createError({ statusCode: 404, message: "Topic not found" });
const body = await readBody(event);
const { lessonComplete, quizScore, tookBranches, branchCount } = body ?? {};
// manual validation — no zod
if (lessonComplete !== undefined && typeof lessonComplete !== "boolean") {
throw createError({ statusCode: 400, message: "lessonComplete must be a boolean" });
}
if (quizScore !== undefined && quizScore !== null) {
if (!Number.isInteger(quizScore)) throw createError({ statusCode: 400, message: "quizScore must be an integer" });
}
if (tookBranches !== undefined && typeof tookBranches !== "boolean") {
throw createError({ statusCode: 400, message: "tookBranches must be a boolean" });
}
if (branchCount !== undefined && !Number.isInteger(branchCount)) {
throw createError({ statusCode: 400, message: "branchCount must be an integer" });
}
// NOTE: this requires a UNIQUE constraint on (course_id, topic_id) in user_progress — the migration adds this
await db
.insert(userProgress)
.values({
id: randomUUID(),
courseId: topic.courseId,
topicId: id,
lessonComplete: lessonComplete ?? false,
quizScore: quizScore ?? null,
tookBranches: tookBranches ?? false,
branchCount: branchCount ?? 0,
updatedAt: sql`datetime('now')`,
})
.onConflictDoUpdate({
target: [userProgress.courseId, userProgress.topicId],
set: {
lessonComplete: lessonComplete ?? sql`excluded.lesson_complete`,
quizScore: quizScore !== undefined ? quizScore : sql`excluded.quiz_score`,
tookBranches: tookBranches ?? sql`excluded.took_branches`,
branchCount: branchCount ?? sql`excluded.branch_count`,
updatedAt: sql`datetime('now')`,
},
});
return { ok: true };
});