diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index 5157716..0d3d16c 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -1,19 +1,35 @@ +import { z } from "zod"; import { success, failure } from "@/lib/utils/response"; import { createPaperRequest } from "@/lib/services/paper"; -import type { CreatePaperInputType } from "@/lib/services/paper"; +import { requestExams } from "@/db/paperRequest"; import { getPostHogClient } from "@/lib/posthog-server"; import { randomUUID } from "crypto"; +const paperRequestInput = z.object({ + subject: z.string().trim().min(1).max(200), + exam: z.enum(requestExams), + slot: z.string().trim().min(1).max(20), + year: z.string().trim().min(1).max(20), +}); + export async function POST(req: Request) { try { - const { subject, exam, slot, year } = - (await req.json()) as CreatePaperInputType; + const body: unknown = await req.json().catch(() => null); + const input = paperRequestInput.safeParse(body); - if (!subject || !exam || !slot || !year) { - return failure("All fields are required.", 400); + if (!input.success) { + return failure( + `send a subject, slot and year, and one of ${requestExams.join(", ")} as exam`, + 400, + ); } - const newRequest = await createPaperRequest({ subject, exam, slot, year }); + const { subject, exam, slot, year } = input.data; + const { request, alreadyRequested } = await createPaperRequest({ subject, exam, slot, year }); + + if (alreadyRequested) { + return success({ message: "this paper has already been requested", request }); + } try { const posthog = getPostHogClient(); @@ -43,7 +59,7 @@ export async function POST(req: Request) { } return success( - { message: "Paper request submitted successfully!", request: newRequest }, + { message: "Paper request submitted successfully!", request }, "Created", 201, ); diff --git a/src/db/paperRequest.ts b/src/db/paperRequest.ts index 501f457..9de5a3d 100644 --- a/src/db/paperRequest.ts +++ b/src/db/paperRequest.ts @@ -1,5 +1,7 @@ import mongoose, { Schema, type Document, type Model } from "mongoose"; +export const requestExams = ["CAT-1", "CAT-2", "FAT", "Model CAT-1", "Model CAT-2", "Model FAT"] as const; + export interface IPaperRequest extends Document { subject: string; exam: string; @@ -10,12 +12,14 @@ export interface IPaperRequest extends Document { const paperRequestSchema = new Schema({ subject: { type: String, required: true }, - exam: { type: String, required: true }, + exam: { type: String, enum: [...requestExams], required: true }, slot: { type: String, required: true }, year: { type: String, required: true }, createdAt: { type: Date, default: Date.now } }); +paperRequestSchema.index({ subject: 1, exam: 1, slot: 1, year: 1 }, { unique: true }); + const PaperRequest: Model = mongoose.models.PaperRequest ?? mongoose.model("PaperRequest", paperRequestSchema); diff --git a/src/lib/services/paper.ts b/src/lib/services/paper.ts index 4eb52f4..e25ed8d 100644 --- a/src/lib/services/paper.ts +++ b/src/lib/services/paper.ts @@ -61,7 +61,15 @@ export async function getCourseCounts(){ export async function createPaperRequest({ subject, exam, slot, year} : CreatePaperInputType){ await connectToDatabase(); - return await PaperRequest.create({ subject, exam, slot, year }); + const result = await PaperRequest.findOneAndUpdate( + { subject, exam, slot, year }, + { $setOnInsert: { subject, exam, slot, year } }, + { upsert: true, new: true, runValidators: true, includeResultMetadata: true }, + ); + return { + request: result.value, + alreadyRequested: result.lastErrorObject?.updatedExisting === true, + }; } export async function getSelectedPapers() {