From 7a6787a23a7cba2a0b547a5f0757a94eef1076ec Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:29:57 +0300 Subject: [PATCH 01/22] fix: validate student input by checking safeParse result before database insert --- app/api/students/route.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/api/students/route.ts b/app/api/students/route.ts index 8f3dcc2..93d77d4 100644 --- a/app/api/students/route.ts +++ b/app/api/students/route.ts @@ -92,9 +92,10 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: 'Malformed JSON' }, { status: 400 }) } - StudentSchema.safeParse(body) + const parsed = StudentSchema.safeParse(body) + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) - const student = await Student.create({ ...(body as Record), teacherId: userId }) + const student = await Student.create({ ...parsed.data, teacherId: userId }) return NextResponse.json(student, { status: 201 }) } catch (error) { if (error instanceof Error) { From 093f27d3ca9a6fdb7af1c75d5bb87b573f85a987 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:30:20 +0300 Subject: [PATCH 02/22] fix: add missing await on Grade.findOneAndUpdate in POST handler --- app/api/grades/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b9da63d..d37502d 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -73,7 +73,7 @@ export async function POST(req: NextRequest) { const max = data.maxMarks! const term = data.term ?? 'Term 1' - const grade = Grade.findOneAndUpdate( + const grade = await Grade.findOneAndUpdate( { teacherId: userId, studentId: data.studentId, subject: data.subject, term }, { $set: { ...data, term, teacherId: userId, grade: calcGrade(data.marks, max) } }, { upsert: true, new: true } From d05041eedbdbdca3118e2a4603b86930eef74602 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:30:44 +0300 Subject: [PATCH 03/22] fix: default maxMarks to 100 when not provided to prevent NaN grade calculation --- app/api/grades/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index d37502d..b4ce7e5 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -70,7 +70,7 @@ export async function POST(req: NextRequest) { if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) const data = parsed.data - const max = data.maxMarks! + const max = data.maxMarks ?? 100 const term = data.term ?? 'Term 1' const grade = await Grade.findOneAndUpdate( From 367f2dde820cfb66a4dbcf3259d22d1294fd3c33 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:31:35 +0300 Subject: [PATCH 04/22] fix: prevent error stack trace leakage in grades API responses --- app/api/grades/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b4ce7e5..2aa2142 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -48,7 +48,7 @@ export async function GET(req: NextRequest) { return NextResponse.json(grades) } catch (error) { console.error('GET /api/grades error:', error instanceof Error ? error.message : error) - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -83,6 +83,6 @@ export async function POST(req: NextRequest) { if (error instanceof Error) { console.error('POST /api/grades error:', error.message) } - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } From 05ad3684a63020b4b831e135e3dec8f3fd5a693c Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:36:46 +0300 Subject: [PATCH 05/22] fix: prevent error stack trace leakage in assignments API response --- app/api/assignments/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/assignments/route.ts b/app/api/assignments/route.ts index 19021d8..1d88b7e 100644 --- a/app/api/assignments/route.ts +++ b/app/api/assignments/route.ts @@ -52,7 +52,7 @@ export async function GET(req: NextRequest) { if (error instanceof Error) { console.error('GET /api/assignments error:', error.message) } - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } From 684354c5f072b2cff53f78808ad3715ad648ab10 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:37:10 +0300 Subject: [PATCH 06/22] fix: add teacherId filter to students update/delete to prevent unauthorized access --- app/api/students/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 2eaaf93..990037b 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -35,7 +35,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string await connectDB() const student = await Student.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -65,7 +65,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Student.findOneAndDelete({ _id: id }) + const deleted = await Student.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Student not found' }, { status: 404 }) From fbfa136fa5aade3de58f90ee2f0de4acf11721d5 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:37:42 +0300 Subject: [PATCH 07/22] fix: add teacherId filter to announcements update/delete to prevent unauthorized access --- app/api/announcements/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 6c32f47..22d4d0a 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -36,7 +36,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string } const announcement = await Announcement.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true, runValidators: true, context: 'query' } ) @@ -63,7 +63,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Announcement.findOneAndDelete({ _id: id }) + const deleted = await Announcement.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Not found' }, { status: 404 }) From f4e0311554af04b6f5131901d9f2757ba9d1f617 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:38:46 +0300 Subject: [PATCH 08/22] fix: add teacherId filter to assignments update/delete to prevent unauthorized access --- app/api/assignments/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index 21fca1c..b041302 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -36,7 +36,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string } const assignment = await Assignment.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -63,7 +63,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Assignment.findOneAndDelete({ _id: id }) + const deleted = await Assignment.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Not found' }, { status: 404 }) From 4efd6ceb8f02d43a6f4fc9a668827ded26828d11 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:46:08 +0300 Subject: [PATCH 09/22] fix: add teacherId filter and ObjectId validation to grades update/delete --- app/api/grades/[id]/route.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 0141f63..64c8da8 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -35,7 +35,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string await connectDB() const grade = await Grade.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -55,8 +55,14 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str try { const { id } = await ctx.params + + // Validate ObjectId + if (!mongoose.Types.ObjectId.isValid(id)) { + return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + } + await connectDB() - const deleted = await Grade.findOneAndDelete({ _id: id }) + const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Grade not found' }, { status: 404 }) From 84dc18011aa5addc13db8048b29e8bb5fef6e147 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:49:02 +0300 Subject: [PATCH 10/22] fix: use total count from API instead of capped array length for dashboard student count --- app/dashboard/OverviewClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 169795e..b1cd4d0 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -316,7 +316,7 @@ export function OverviewClient() { .slice(0, 5); setStats({ - totalStudents: students.students?.length ?? 0, + totalStudents: students.total ?? students.students?.length ?? 0, totalAssignments: Array.isArray(assignments) ? assignments.length : (assignments.length ?? 0), From 7a181d219351d1fbfce6eb40f511ebb1a4bb7470 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:49:59 +0300 Subject: [PATCH 11/22] fix: correct grade boundary for A+ to use >= 90 instead of > 90 --- app/api/grades/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index 2aa2142..427da6b 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -21,7 +21,7 @@ const GradeSchema = z.object({ function calcGrade(marks: number, max: number): string { const pct = (marks / max) * 100 - if (pct > 90) return 'A+' + if (pct >= 90) return 'A+' if (pct >= 80) return 'A' if (pct >= 70) return 'B+' if (pct >= 60) return 'B' From b6014c00849ab0b28bacb4d893c281846564273b Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 09:52:46 +0300 Subject: [PATCH 12/22] fix: Grade findOneAndUpdate hook now validates marks inside operator --- models/Grade.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/Grade.ts b/models/Grade.ts index 311b757..b5bb549 100644 --- a/models/Grade.ts +++ b/models/Grade.ts @@ -41,8 +41,10 @@ GradeSchema.pre("save", function () { GradeSchema.pre("findOneAndUpdate", function () { const update = this.getUpdate() as Record; if (update && typeof update === "object") { - const marks = update.marks; - const maxMarks = update.maxMarks; + // Support both direct updates and $set operator + const source = (update.$set && typeof update.$set === "object" ? update.$set : update) as Record; + const marks = source.marks; + const maxMarks = source.maxMarks; if ( marks !== undefined && typeof marks === "number" && maxMarks !== undefined && typeof maxMarks === "number" && From 4961a8b4851e1ec4c6ee227b642340cc23cc03b5 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:06:48 +0300 Subject: [PATCH 13/22] fix: remove userId query param bypass in profile GET to prevent unauthorized access --- app/api/profile/route.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index a3d98bf..dbdebdc 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,15 +3,8 @@ import { NextRequest, NextResponse } from 'next/server' import { connectDB } from '@/lib/mongodb' import { Teacher } from '@/models/Teacher' -export async function GET(req: NextRequest) { - const { searchParams } = new URL(req.url) - const queryUserId = searchParams.get('userId') - - let userId: string | null = queryUserId - if (!userId) { - const session = await auth() - userId = session.userId - } +export async function GET() { + const { userId } = await auth() if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) try { From ac3daff842e462b791825f5f36d56e7c2faae41e Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:08:04 +0300 Subject: [PATCH 14/22] fix: correct grade point for D from 4 to 5 in dashboard CGPA calculation --- app/dashboard/OverviewClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index b1cd4d0..278d8a3 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -254,7 +254,7 @@ export function OverviewClient() { "B+": 8, B: 7, C: 6, - D: 4, + D: 5, F: 0, }; const termMap: Record = {}; From f5558d9f6e11d421a4abdecec1af7d8524611a69 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:10:04 +0300 Subject: [PATCH 15/22] fix: Grade updateOne hook now validates marks inside operator --- models/Grade.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/Grade.ts b/models/Grade.ts index b5bb549..a2774b7 100644 --- a/models/Grade.ts +++ b/models/Grade.ts @@ -58,8 +58,10 @@ GradeSchema.pre("findOneAndUpdate", function () { GradeSchema.pre("updateOne", function () { const update = this.getUpdate() as Record; if (update && typeof update === "object") { - const marks = update.marks; - const maxMarks = update.maxMarks; + // Support both direct updates and $set operator + const source = (update.$set && typeof update.$set === "object" ? update.$set : update) as Record; + const marks = source.marks; + const maxMarks = source.maxMarks; if ( marks !== undefined && typeof marks === "number" && maxMarks !== undefined && typeof maxMarks === "number" && From 52182a78495aec4c5fc92f3abe7d1fd343bce24f Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:11:47 +0300 Subject: [PATCH 16/22] fix: check API response status before processing dashboard data to prevent silent failures --- app/dashboard/OverviewClient.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 278d8a3..5c96d47 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -200,6 +200,11 @@ export function OverviewClient() { fetch("/api/announcements?limit=5"), ]); + // Check that all responses are OK before processing + for (const res of [studentsRes, assignmentsRes, attendanceRes, gradesRes, announcementsRes]) { + if (!res.ok) throw new Error(`API error: ${res.url} returned ${res.status}`); + } + const [students, assignmentsData, attendance, grades, announcements] = await Promise.all([ studentsRes.json(), From d8a4c3be871345cda39b7b4035f2de8d8291d0ad Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:13:05 +0300 Subject: [PATCH 17/22] fix: handle negative and zero values in timeAgo to prevent displaying negative timestamps --- app/dashboard/announcements/AnnouncementsClient.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/dashboard/announcements/AnnouncementsClient.tsx b/app/dashboard/announcements/AnnouncementsClient.tsx index 0de73bb..6c85b43 100644 --- a/app/dashboard/announcements/AnnouncementsClient.tsx +++ b/app/dashboard/announcements/AnnouncementsClient.tsx @@ -46,7 +46,9 @@ const CATEGORY_BADGE: Record Date: Sat, 18 Apr 2026 10:15:03 +0300 Subject: [PATCH 18/22] fix: guard against division by zero in grade percentage calculation --- app/dashboard/grades/GradesClient.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/dashboard/grades/GradesClient.tsx b/app/dashboard/grades/GradesClient.tsx index fdaa00b..8bffde1 100644 --- a/app/dashboard/grades/GradesClient.tsx +++ b/app/dashboard/grades/GradesClient.tsx @@ -56,6 +56,7 @@ function sortTerms(terms: string[]) { } function pct(marks: number, max: number) { + if (max <= 0) return 0; return Math.round((marks / max) * 100); } From 91c0486942e31f7e20042002f17ad97ecd2b412e Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:16:39 +0300 Subject: [PATCH 19/22] fix: guard against division by zero in API calcGrade function --- app/api/grades/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index 427da6b..fc4f22b 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -20,6 +20,7 @@ const GradeSchema = z.object({ ) function calcGrade(marks: number, max: number): string { + if (max <= 0) return 'F' const pct = (marks / max) * 100 if (pct >= 90) return 'A+' if (pct >= 80) return 'A' From ef31c4bb9ccff77fef6c2b610d6389c31ce28150 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:22:31 +0300 Subject: [PATCH 20/22] fix: validate studentId format in attendance GET to prevent 500 errors --- app/api/attendance/route.ts | 8 +++++++- app/api/students/[id]/route.ts | 25 ++++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 14b6c4d..0495a46 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -1,5 +1,6 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' +import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Attendance } from '@/models/Attendance' import { z } from 'zod' @@ -76,7 +77,12 @@ export async function GET(req: NextRequest) { query.date = dateRange; } if (cls) query.class = cls; - if (studentId) query.studentId = studentId; + if (studentId) { + if (!mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json({ error: "Invalid studentId" }, { status: 400 }); + } + query.studentId = studentId; + } const records = await Attendance.find(query) .sort({ date: -1, studentName: 1 }) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 990037b..cb7bd14 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Student } from '@/models/Student' +import { z } from 'zod' const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'grade', 'rollNo', 'class', 'phone', 'address', 'parentName', 'parentPhone'] @@ -25,19 +26,25 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string return NextResponse.json({ error: 'Bad Request' }, { status: 400 }) } - // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} - for (const key of ALLOWED_UPDATE_FIELDS) { - if (key in body) { - sanitizedBody[key] = body[key] - } - } + const StudentUpdateSchema = z.object({ + name: z.string().min(1).optional(), + rollNo: z.string().min(1).optional(), + class: z.string().min(1).optional(), + email: z.string().email().optional().or(z.literal('')), + phone: z.string().optional(), + address: z.string().optional(), + parentName: z.string().optional(), + parentPhone: z.string().optional(), + }) + + const parsed = StudentUpdateSchema.safeParse(body) + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) await connectDB() const student = await Student.findOneAndUpdate( { _id: id, teacherId: userId }, - sanitizedBody, - { new: true } + { $set: parsed.data }, + { new: true, runValidators: true } ) if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 }) return NextResponse.json(student) From c024e0277dd734177c220de1347ee06b9e3cad32 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:22:58 +0300 Subject: [PATCH 21/22] fix: validate studentId format in grades GET to prevent 500 errors --- app/api/grades/route.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index fc4f22b..f60a233 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -1,5 +1,6 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' +import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Grade } from '@/models/Grade' import { z } from 'zod' @@ -42,7 +43,12 @@ export async function GET(req: NextRequest) { const subject = searchParams.get('subject') const query: Record = { teacherId: userId } - if (studentId) query.studentId = studentId + if (studentId) { + if (!mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json({ error: "Invalid studentId" }, { status: 400 }); + } + query.studentId = studentId + } if (subject) query.subject = subject const grades = await Grade.find(query).sort({ createdAt: -1 }).lean() From 801f41425349290810c04db0af5d873d37171485 Mon Sep 17 00:00:00 2001 From: Kaleb Tesfaye Ayele Date: Sat, 18 Apr 2026 10:23:31 +0300 Subject: [PATCH 22/22] fix: validate studentId format in attendance POST (single and bulk) to prevent 500 errors --- app/api/attendance/route.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 0495a46..e714bde 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -130,7 +130,15 @@ export async function POST(req: NextRequest) { ); if (isBulk) { - const ops = (parsed.data as z.infer).map((record) => ({ + const data = parsed.data as z.infer; + // Validate all studentIds + for (const record of data) { + if (!mongoose.Types.ObjectId.isValid(record.studentId)) { + return NextResponse.json({ error: `Invalid studentId: ${record.studentId}` }, { status: 400 }); + } + } + + const ops = data.map((record) => ({ updateOne: { filter: { teacherId: userId, studentId: record.studentId, date: record.date }, update: { $set: { ...record, teacherId: userId } }, @@ -140,9 +148,14 @@ export async function POST(req: NextRequest) { await Attendance.bulkWrite(ops) return NextResponse.json({ success: true, count: ops.length }) } else { + const data = parsed.data as z.infer; + if (!mongoose.Types.ObjectId.isValid(data.studentId)) { + return NextResponse.json({ error: "Invalid studentId" }, { status: 400 }); + } + const record = await Attendance.findOneAndUpdate( - { teacherId: userId, studentId: (parsed.data as z.infer).studentId, date: (parsed.data as z.infer).date }, - { $set: { ...(parsed.data as z.infer), teacherId: userId } }, + { teacherId: userId, studentId: data.studentId, date: data.date }, + { $set: { ...data, teacherId: userId } }, { upsert: true, new: true } ) return NextResponse.json(record, { status: 201 })