// Next.js API route support: https://nextjs.org/docs/api-routes/introduction import { Module } from "@/interfaces"; import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam"; import { createApprovalWorkflowOnExamCreation } from "@/lib/createWorkflowsOnExamCreation"; import client from "@/lib/mongodb"; import { sessionOptions } from "@/lib/session"; import { mapBy } from "@/utils"; import { requestUser } from "@/utils/api"; import { getApprovalWorkflowsByExamId, updateApprovalWorkflows } from "@/utils/approval.workflows.be"; import { generateExamDifferences } from "@/utils/exam.differences"; import { getExams } from "@/utils/exams.be"; import { isAdmin } from "@/utils/users"; import { access } from "fs"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; const db = client.db(process.env.MONGODB_DB); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "GET") return await GET(req, res); if (req.method === "POST") return await POST(req, res); res.status(404).json({ ok: false }); } async function GET(req: NextApiRequest, res: NextApiResponse) { if (!req.session.user) { res.status(401).json({ ok: false }); return; } const { module, avoidRepeated, variant, instructorGender } = req.query as { module: Module; avoidRepeated: string; variant?: Variant; instructorGender?: InstructorGender; }; const exams: Exam[] = await getExams(db, module, avoidRepeated, req.session.user.id, variant, instructorGender); res.status(200).json(exams); } async function POST(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); const { module } = req.query as { module: string }; const session = client.startSession(); const entities = isAdmin(user) ? [] : mapBy(user.entities, "id"); try { const exam = { access: "public", // default access is public ...req.body, module: module, entities, createdBy: user.id, createdAt: new Date().toISOString(), }; let responseStatus: number; let responseMessage: string; await session.withTransaction(async () => { const docSnap = await db.collection(module).findOne({ id: req.body.id }, { session }); // Check whether the id of the exam matches another exam with different // owners, throw exception if there is, else allow editing const existingExamOwners = docSnap?.owners ?? []; const newExamOwners = exam.owners ?? []; const ownersSet = new Set(existingExamOwners); if (docSnap !== null && (existingExamOwners.length !== newExamOwners.length || !newExamOwners.every((e: string) => ownersSet.has(e)))) { throw new Error("Name already exists"); } if (isAdmin(user)) { exam.isDiagnostic = false; } await db.collection(module).updateOne( { id: req.body.id }, { $set: { id: req.body.id, ...exam } }, { upsert: true, session, } ); // if it doesn't enter the next if condition it means the exam was updated and not created, so we can send this response. responseStatus = 200; responseMessage = `Successfully updated exam with ID: "${exam.id}"`; // create workflow only if exam is being created for the first time if (docSnap === null) { try { if (exam.requiresApproval === false) { responseStatus = 200; responseMessage = `Successfully created exam "${exam.id}" and skipped Approval Workflow due to user request.`; } else if (isAdmin(user)) { responseStatus = 200; responseMessage = `Successfully created exam "${exam.id}" and skipped Approval Workflow due to admin rights.`; } else { const { successCount, totalCount } = await createApprovalWorkflowOnExamCreation(exam.createdBy, exam.entities, exam.id, module); if (successCount === totalCount) { responseStatus = 200; responseMessage = `Successfully created exam "${exam.id}" and started its Approval Workflow.`; } else if (successCount > 0) { responseStatus = 207; responseMessage = `Successfully created exam with ID: "${exam.id}" but was not able to start/find an Approval Workflow for all the author's entities.`; } else { responseStatus = 207; responseMessage = `Successfully created exam with ID: "${exam.id}" but skipping approval process because no approval workflow was found configured for the exam author.`; } } } catch (error) { console.error("Workflow creation error:", error); responseStatus = 207; responseMessage = `Successfully created exam with ID: "${exam.id}" but something went wrong while creating the Approval Workflow(s).`; } } else { // if exam was updated, log the updates const approvalWorkflows = await getApprovalWorkflowsByExamId(exam.id); if (approvalWorkflows) { const differences = generateExamDifferences(docSnap as Exam, exam as Exam); if (differences) { approvalWorkflows.forEach((workflow) => { const currentStepIndex = workflow.steps.findIndex((step) => !step.completed || step.rejected); if (workflow.steps[currentStepIndex].examChanges === undefined) { workflow.steps[currentStepIndex].examChanges = [...differences]; } else { workflow.steps[currentStepIndex].examChanges!.push(...differences); } }); await updateApprovalWorkflows("active-workflows", approvalWorkflows); } } } res.status(responseStatus).json({ message: responseMessage, }); }); } catch (error) { console.error("Transaction failed: ", error); res.status(500).json({ ok: false, error: (error as any).message }); } finally { session.endSession(); } }