From 58148e0bab2e7dcd0bd3a0cc59c842fd3768a78f Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:16:00 +0530 Subject: [PATCH 01/23] fix: Refactor MongoDB connection logic and enhance seed script diagnostics --- lib/mongodb.ts | 41 ++++++++++++++----------- scripts/seed.mjs | 79 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/lib/mongodb.ts b/lib/mongodb.ts index 01dddf9..1593889 100644 --- a/lib/mongodb.ts +++ b/lib/mongodb.ts @@ -1,40 +1,47 @@ -import mongoose from 'mongoose' +import mongoose from "mongoose"; -const MONGODB_URI = process.env.MONGODB_URI! +const MONGODB_URI = process.env.MONGODB_URI!; if (!MONGODB_URI) { - throw new Error('Please define the MONGODB_URI environment variable') + throw new Error("Please define the MONGODB_URI environment variable"); } interface MongooseCache { - conn: typeof mongoose | null - promise: Promise | null + conn: typeof mongoose | null; + promise: Promise | null; } declare global { - var mongooseCache: MongooseCache | undefined + var mongooseCache: MongooseCache | undefined; } -const cached: MongooseCache = global.mongooseCache ?? { conn: null, promise: null } -global.mongooseCache = cached +const cached: MongooseCache = global.mongooseCache ?? { + conn: null, + promise: null, +}; +global.mongooseCache = cached; export async function connectDB(): Promise { - if (cached.conn) return cached.conn + if (cached.conn) return cached.conn; if (!cached.promise) { cached.promise = mongoose - .connect(MONGODB_URI, { bufferCommands: false }) - .catch((error) => { - cached.promise = null - throw error + .connect(MONGODB_URI, { + bufferCommands: false, + tls: true, + serverSelectionTimeoutMS: 10000, }) + .catch((error) => { + cached.promise = null; + throw error; + }); } try { - cached.conn = await cached.promise - return cached.conn + cached.conn = await cached.promise; + return cached.conn; } catch (error) { - cached.promise = null - throw error + cached.promise = null; + throw error; } } diff --git a/scripts/seed.mjs b/scripts/seed.mjs index 24d6dc3..39eaffd 100644 --- a/scripts/seed.mjs +++ b/scripts/seed.mjs @@ -225,9 +225,45 @@ const ANNOUNCEMENTS_DATA = [ // ── Main ─────────────────────────────────────────────────────────────────────── async function main() { - console.log('🔗 Connecting to MongoDB…') - await mongoose.connect(MONGODB_URI, { bufferCommands: false }) - console.log('✅ Connected.\n') + const isAtlas = MONGODB_URI.includes('mongodb+srv://') + const hostMatch = MONGODB_URI.match(/@([^\/?]+)/) + const host = hostMatch ? hostMatch[1] : 'local or unknown host' + + console.log(`🔗 Connecting to MongoDB...`) + if (process.env.DEBUG_DB === 'true') { + console.log(`[DEBUG] Target Host: ${host}`) + console.log(`[DEBUG] Connecting with TLS and 10s timeout...`) + } + + try { + await mongoose.connect(MONGODB_URI, { + bufferCommands: false, + tls: true, + serverSelectionTimeoutMS: 10000, + }) + console.log('✅ Connected to MongoDB.\n') + } catch (err) { + console.error('\n❌ MongoDB Connection Failed!') + console.error(` Error: ${err.message}`) + + if (err.message.includes('tls') || err.message.includes('SSL') || err.message.includes('tlsv1 alert')) { + console.error('\n🔍 DIAGNOSTIC (TLS/SSL Issue):') + console.error(' - Check if your connection permits outbound TLS over port 27017.') + console.error(' - Could be an anti-virus or proxy intercepting the SSL handshake.') + } else if (err.message.includes('timeout') || err.name === 'MongooseServerSelectionError') { + console.error('\n🔍 DIAGNOSTIC (Timeout Issue):') + if (isAtlas) { + console.error(' - You are using MongoDB Atlas.') + console.error(' - LIKELY CAUSE: Your IP address is not whitelisted.') + console.error(' - FIX: Go to Atlas > Network Access > Add IP Address > "0.0.0.0/0" for testing.') + } else { + console.error(' - Check if the database is running and accessible at the network address.') + } + } else if (err.message.includes('bad auth')) { + console.error('\n🔍 DIAGNOSTIC (Auth Issue): Check if your password contains unencoded special characters.') + } + process.exit(1) + } try { // ── Resolve teacher ────────────────────────────────────────────────────────── @@ -350,16 +386,41 @@ async function main() { ) console.log(` ✔ ${announcements.length} announcements created.`) + // ── Verification ───────────────────────────────────────────────────────────── + console.log('\n🔎 Verifying inserted data…') + const verifyCounts = { + students: await Student.countDocuments({ teacherId }), + attendance: await Attendance.countDocuments({ teacherId }), + assignments: await Assignment.countDocuments({ teacherId }), + grades: await Grade.countDocuments({ teacherId }), + announcements: await Announcement.countDocuments({ teacherId }), + } + + if (process.env.DEBUG_DB === 'true') { + console.log('[DEBUG] DB Counts:', verifyCounts) + } + + if (!verifyCounts.students || !verifyCounts.attendance || !verifyCounts.assignments || !verifyCounts.grades || !verifyCounts.announcements) { + throw new Error(`Verification failed! Zeros detected in one or more collections.\nCounts: ${JSON.stringify(verifyCounts)}`) + } + console.log(' ✔ Data verified successfully.') + // ── Summary ─────────────────────────────────────────────────────────────────── console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') - console.log('🌱 Seed complete!') + console.log('🌱 Data seeded successfully!') console.log(` Teacher profile: updated`) - console.log(` Students: ${students.length}`) - console.log(` Attendance: ${attendance.length} (14 days)`) - console.log(` Assignments: ${assignments.length} (todo / in_progress / submitted)`) - console.log(` Grades: ${grades.length} (Term 1 & Term 2)`) - console.log(` Announcements: ${announcements.length} (academic / events / admin / general)`) + console.log(` Students: ${verifyCounts.students}`) + console.log(` Attendance: ${verifyCounts.attendance} (14 days)`) + console.log(` Assignments: ${verifyCounts.assignments} (todo / in_progress / submitted)`) + console.log(` Grades: ${verifyCounts.grades} (Term 1 & Term 2)`) + console.log(` Announcements: ${verifyCounts.announcements} (academic / events / admin / general)`) console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') + + process.exit(0) + } catch (err) { + console.error('\n❌ Seed operations failed:') + console.error(` ${err.message ?? err}`) + throw err } finally { await mongoose.disconnect() } From f61c3ba7d70279985723862e8a5203988c5c4aa8 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:18:12 +0530 Subject: [PATCH 02/23] fix: Update background gradient class for sign-in and sign-up pages --- app/(auth)/sign-in/[[...sign-in]]/page.tsx | 2 +- app/(auth)/sign-up/[[...sign-up]]/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx index f491a20..1a579d1 100644 --- a/app/(auth)/sign-in/[[...sign-in]]/page.tsx +++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { export default function SignInPage() { return ( -
+

EduDesk

diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx index de96598..f5bbe7b 100644 --- a/app/(auth)/sign-up/[[...sign-up]]/page.tsx +++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx @@ -2,7 +2,7 @@ import { SignUp } from '@clerk/nextjs' export default function SignUpPage() { return ( -
+

EduDesk

From a6ae5103f6e5e702d62d47cae09218e005d60b41 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:24:03 +0530 Subject: [PATCH 03/23] fix: Enhance PUT and DELETE endpoints for announcements with improved error handling and authorization checks --- app/api/announcements/[id]/route.ts | 105 ++++++++++++++++++---------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 6c32f47..9e4e5ae 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -1,79 +1,108 @@ -import { auth } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import mongoose from 'mongoose' -import { connectDB } from '@/lib/mongodb' -import { Announcement } from '@/models/Announcement' +import { auth } from "@clerk/nextjs/server"; +import { NextRequest, NextResponse } from "next/server"; +import mongoose from "mongoose"; +import { connectDB } from "@/lib/mongodb"; +import { Announcement } from "@/models/Announcement"; -const ALLOWED_FIELDS = ['title', 'content', 'body', 'audience', 'category', 'pinned', 'expiresAt'] +const ALLOWED_FIELDS = [ + "title", + "content", + "body", + "audience", + "category", + "pinned", + "expiresAt", +]; -export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function PUT( + req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + return NextResponse.json({ error: "Invalid id" }, { status: 400 }); } - await connectDB() - - let body + await connectDB(); + + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Invalid JSON request body' }, { status: 400 }) + return NextResponse.json( + { error: "Invalid JSON request body" }, + { status: 400 }, + ); } // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} + const sanitizedBody: Record = {}; for (const key of ALLOWED_FIELDS) { if (key in body) { - sanitizedBody[key] = body[key] + sanitizedBody[key] = body[key]; } } const announcement = await Announcement.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, { $set: sanitizedBody }, - { new: true, runValidators: true, context: 'query' } - ) - if (!announcement) return NextResponse.json({ error: 'Not found' }, { status: 404 }) - return NextResponse.json(announcement) + { new: true, runValidators: true, context: "query" }, + ); + if (!announcement) + return NextResponse.json({ error: "Not found" }, { status: 404 }); + return NextResponse.json(announcement); } catch (error) { if (error instanceof Error) { - console.error('PUT /api/announcements/[id] error:', error.message) + console.error("PUT /api/announcements/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } -export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function DELETE( + _req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + return NextResponse.json({ error: "Invalid id" }, { status: 400 }); } - await connectDB() - const deleted = await Announcement.findOneAndDelete({ _id: id }) - + await connectDB(); + const deleted = await Announcement.findOneAndDelete({ + _id: id, + teacherId: userId, + }); + if (!deleted) { - return NextResponse.json({ error: 'Not found' }, { status: 404 }) + return NextResponse.json({ error: "Not found" }, { status: 404 }); } - - return NextResponse.json({ success: true }) + + return NextResponse.json({ success: true }); } catch (error) { if (error instanceof Error) { - console.error('DELETE /api/announcements/[id] error:', error.message) + console.error("DELETE /api/announcements/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } From 936e24c015f77b4c0e7f963b311741cb05da002e Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:26:09 +0530 Subject: [PATCH 04/23] fix: Improve error handling and authorization checks in PUT and DELETE endpoints for assignments --- app/api/assignments/[id]/route.ts | 107 +++++++++++++++++++----------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index 21fca1c..e81112f 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -1,79 +1,110 @@ -import { auth } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import mongoose from 'mongoose' -import { connectDB } from '@/lib/mongodb' -import { Assignment } from '@/models/Assignment' +import { auth } from "@clerk/nextjs/server"; +import { NextRequest, NextResponse } from "next/server"; +import mongoose from "mongoose"; +import { connectDB } from "@/lib/mongodb"; +import { Assignment } from "@/models/Assignment"; -const ALLOWED_UPDATE_FIELDS = ['title', 'description', 'dueDate', 'deadline', 'subject', 'class', 'status', 'kanbanStatus', 'maxMarks'] +const ALLOWED_UPDATE_FIELDS = [ + "title", + "description", + "dueDate", + "deadline", + "subject", + "class", + "status", + "kanbanStatus", + "maxMarks", +]; -export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function PUT( + req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + return NextResponse.json({ error: "Invalid id" }, { status: 400 }); } - await connectDB() - - let body + await connectDB(); + + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); } // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} + const sanitizedBody: Record = {}; for (const key of ALLOWED_UPDATE_FIELDS) { if (key in body) { - sanitizedBody[key] = body[key] + sanitizedBody[key] = body[key]; } } const assignment = await Assignment.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, - { new: true } - ) - if (!assignment) return NextResponse.json({ error: 'Not found' }, { status: 404 }) - return NextResponse.json(assignment) + { new: true }, + ); + if (!assignment) + return NextResponse.json({ error: "Not found" }, { status: 404 }); + return NextResponse.json(assignment); } catch (error) { if (error instanceof Error) { - console.error('PUT /api/assignments/[id] error:', error.message) + console.error("PUT /api/assignments/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } -export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function DELETE( + _req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + return NextResponse.json({ error: "Invalid id" }, { status: 400 }); } - await connectDB() - const deleted = await Assignment.findOneAndDelete({ _id: id }) - + await connectDB(); + const deleted = await Assignment.findOneAndDelete({ + _id: id, + teacherId: userId, + }); + if (!deleted) { - return NextResponse.json({ error: 'Not found' }, { status: 404 }) + return NextResponse.json({ error: "Not found" }, { status: 404 }); } - - return NextResponse.json({ success: true }) + + return NextResponse.json({ success: true }); } catch (error) { if (error instanceof Error) { - console.error('DELETE /api/assignments/[id] error:', error.message) + console.error("DELETE /api/assignments/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } From 511f19a171807cf2fd53d71dc94612fa4893efb0 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:27:27 +0530 Subject: [PATCH 05/23] fix: Refactor and enhance error handling in GET and POST endpoints for grades --- app/api/grades/route.ts | 153 ++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 62 deletions(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b9da63d..eaba3cf 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -1,88 +1,117 @@ -import { auth } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import { connectDB } from '@/lib/mongodb' -import { Grade } from '@/models/Grade' -import { z } from 'zod' +import { auth } from "@clerk/nextjs/server"; +import { NextRequest, NextResponse } from "next/server"; +import { connectDB } from "@/lib/mongodb"; +import { Grade } from "@/models/Grade"; +import { z } from "zod"; -const GradeSchema = z.object({ - studentId: z.string().min(1), - studentName: z.string().min(1), - subject: z.string().min(1), - marks: z.number().min(0), - maxMarks: z.number().min(1).optional(), - term: z.string().optional(), -}).refine( - (data) => !data.maxMarks || data.marks <= data.maxMarks, - { - message: 'marks must be less than or equal to maxMarks', - path: ['marks'], - } -) +const GradeSchema = z + .object({ + studentId: z.string().min(1), + studentName: z.string().min(1), + subject: z.string().min(1), + marks: z.number().min(0), + maxMarks: z.number().min(1).optional(), + term: z.string().optional(), + }) + .refine((data) => !data.maxMarks || data.marks <= data.maxMarks, { + message: "marks must be less than or equal to maxMarks", + path: ["marks"], + }); function calcGrade(marks: number, max: number): string { - const pct = (marks / max) * 100 - if (pct > 90) return 'A+' - if (pct >= 80) return 'A' - if (pct >= 70) return 'B+' - if (pct >= 60) return 'B' - if (pct >= 50) return 'C' - if (pct >= 40) return 'D' - return 'F' + const pct = (marks / max) * 100; + if (pct > 90) return "A+"; + if (pct >= 80) return "A"; + if (pct >= 70) return "B+"; + if (pct >= 60) return "B"; + if (pct >= 50) return "C"; + if (pct >= 40) return "D"; + return "F"; } export async function GET(req: NextRequest) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - await connectDB() - const { searchParams } = new URL(req.url) - const studentId = searchParams.get('studentId') - const subject = searchParams.get('subject') + await connectDB(); + const { searchParams } = new URL(req.url); + const studentId = searchParams.get("studentId"); + const subject = searchParams.get("subject"); - const query: Record = { teacherId: userId } - if (studentId) query.studentId = studentId - if (subject) query.subject = subject + const query: Record = { teacherId: userId }; + if (studentId) query.studentId = studentId; + if (subject) query.subject = subject; - const grades = await Grade.find(query).sort({ createdAt: -1 }).lean() - return NextResponse.json(grades) + const grades = await Grade.find(query).sort({ createdAt: -1 }).lean(); + 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 }) + console.error( + "GET /api/grades error:", + error instanceof Error ? error.message : error, + ); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } export async function POST(req: NextRequest) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - await connectDB() - - let body + await connectDB(); + + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); } - - const parsed = GradeSchema.safeParse(body) - if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) - const data = parsed.data - const max = data.maxMarks! - const term = data.term ?? 'Term 1' - - const grade = 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 } - ) - return NextResponse.json(grade, { status: 201 }) + const parsed = GradeSchema.safeParse(body); + if (!parsed.success) + return NextResponse.json( + { error: parsed.error.flatten() }, + { status: 400 }, + ); + + const data = parsed.data; + const max = data.maxMarks ?? 100; + const term = data.term ?? "Term 1"; + + 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 }, + ); + return NextResponse.json(grade, { status: 201 }); } catch (error) { if (error instanceof Error) { - console.error('POST /api/grades error:', error.message) + 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 eefe3a98e2c6bbcaefd2dd1efb6df56eb8cc4c3d Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:27:42 +0530 Subject: [PATCH 06/23] fix: Enhance error handling and validation in PUT and DELETE endpoints for grades --- app/api/grades/[id]/route.ts | 98 +++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 0141f63..8cb6c56 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -1,72 +1,98 @@ -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 { 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"; -const ALLOWED_UPDATE_FIELDS = ['marks', 'maxMarks', 'grade'] +const ALLOWED_UPDATE_FIELDS = ["marks", "maxMarks", "grade"]; -export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function PUT( + req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Not found' }, { status: 404 }) + return NextResponse.json({ error: "Not found" }, { status: 404 }); } - let body + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); } // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} + const sanitizedBody: Record = {}; for (const key of ALLOWED_UPDATE_FIELDS) { if (key in body) { - sanitizedBody[key] = body[key] + sanitizedBody[key] = body[key]; } } - await connectDB() + await connectDB(); const grade = await Grade.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, - { new: true } - ) - if (!grade) return NextResponse.json({ error: 'Not found' }, { status: 404 }) - return NextResponse.json(grade) + { new: true }, + ); + if (!grade) + return NextResponse.json({ error: "Not found" }, { status: 404 }); + return NextResponse.json(grade); } catch (error) { if (error instanceof Error) { - console.error('PUT /api/grades/[id] error:', error.message) + console.error("PUT /api/grades/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } -export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function DELETE( + _req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params - await connectDB() - const deleted = await Grade.findOneAndDelete({ _id: id }) - + const { id } = await ctx.params; + + if (!mongoose.Types.ObjectId.isValid(id)) { + return NextResponse.json({ error: "Invalid id" }, { status: 400 }); + } + + await connectDB(); + const deleted = await Grade.findOneAndDelete({ + _id: id, + teacherId: userId, + }); + if (!deleted) { - return NextResponse.json({ error: 'Grade not found' }, { status: 404 }) + return NextResponse.json({ error: "Grade not found" }, { status: 404 }); } - - return NextResponse.json({ success: true }) + + return NextResponse.json({ success: true }); } catch (error) { if (error instanceof Error) { - console.error('DELETE /api/grades/[id] error:', error.message) + console.error("DELETE /api/grades/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } From 316f41f224cdf64be46508e6f27d49b576b7c95c Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:28:32 +0530 Subject: [PATCH 07/23] fix: Refactor GET and PUT endpoints in profile API with improved error handling and validation --- app/api/profile/route.ts | 149 +++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 60 deletions(-) diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index a3d98bf..4e45237 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -1,73 +1,95 @@ -import { auth, currentUser } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import { connectDB } from '@/lib/mongodb' -import { Teacher } from '@/models/Teacher' +import { auth, currentUser } from "@clerk/nextjs/server"; +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 - } - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - await connectDB() - let teacher = await Teacher.findOne({ clerkId: userId }).lean() + await connectDB(); + let teacher = await Teacher.findOne({ clerkId: userId }).lean(); if (!teacher) { - const clerkUser = await currentUser() + const clerkUser = await currentUser(); const created = await Teacher.create({ clerkId: userId, - name: clerkUser?.fullName ?? '', - email: clerkUser?.emailAddresses[0]?.emailAddress ?? '', - department: '', + name: clerkUser?.fullName ?? "", + email: clerkUser?.emailAddresses[0]?.emailAddress ?? "", + department: "", subjects: [], - }) - teacher = created.toObject() + }); + teacher = created.toObject(); } - return NextResponse.json(teacher) + return NextResponse.json(teacher); } catch (error) { - console.error('GET /api/profile error:', error instanceof Error ? error.message : error) - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + console.error( + "GET /api/profile error:", + error instanceof Error ? error.message : error, + ); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } export async function PUT(req: NextRequest) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - await connectDB() - - let body + await connectDB(); + + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); } - - const { name, department, subjects, phone, bio, academicHistory } = body + + const { name, department, subjects, phone, bio, academicHistory } = body; // Validate input - if (typeof name !== 'string' || !name.trim()) { - return NextResponse.json({ error: 'name must be a non-empty string' }, { status: 400 }) + if (typeof name !== "string" || !name.trim()) { + return NextResponse.json( + { error: "name must be a non-empty string" }, + { status: 400 }, + ); } - if (department !== undefined && typeof department !== 'string') { - return NextResponse.json({ error: 'department must be a string' }, { status: 400 }) + if (department !== undefined && typeof department !== "string") { + return NextResponse.json( + { error: "department must be a string" }, + { status: 400 }, + ); } - if (!Array.isArray(subjects) || !subjects.every((s) => typeof s === 'string')) { - return NextResponse.json({ error: 'subjects must be an array of strings' }, { status: 400 }) + if ( + !Array.isArray(subjects) || + !subjects.every((s) => typeof s === "string") + ) { + return NextResponse.json( + { error: "subjects must be an array of strings" }, + { status: 400 }, + ); } - if (phone !== undefined && typeof phone !== 'string') { - return NextResponse.json({ error: 'phone must be a string' }, { status: 400 }) + if (phone !== undefined && typeof phone !== "string") { + return NextResponse.json( + { error: "phone must be a string" }, + { status: 400 }, + ); } - if (bio !== undefined && typeof bio !== 'string') { - return NextResponse.json({ error: 'bio must be a string' }, { status: 400 }) + if (bio !== undefined && typeof bio !== "string") { + return NextResponse.json( + { error: "bio must be a string" }, + { status: 400 }, + ); } if (academicHistory !== undefined) { if ( @@ -76,39 +98,46 @@ export async function PUT(req: NextRequest) { !academicHistory.every( (entry: unknown) => entry !== null && - typeof entry === 'object' && - typeof (entry as Record).year === 'string' && - typeof (entry as Record).title === 'string', + typeof entry === "object" && + typeof (entry as Record).year === "string" && + typeof (entry as Record).title === "string", ) ) { return NextResponse.json( - { error: 'academicHistory must be an array of objects with string year and title (max 20 items)' }, + { + error: + "academicHistory must be an array of objects with string year and title (max 20 items)", + }, { status: 400 }, - ) + ); } } - const updatePayload: Record = { name, subjects } - if (department !== undefined) updatePayload.department = department - if (phone !== undefined) updatePayload.phone = phone - if (bio !== undefined) updatePayload.bio = bio - if (academicHistory !== undefined) updatePayload.academicHistory = academicHistory + const updatePayload: Record = { name, subjects }; + if (department !== undefined) updatePayload.department = department; + if (phone !== undefined) updatePayload.phone = phone; + if (bio !== undefined) updatePayload.bio = bio; + if (academicHistory !== undefined) + updatePayload.academicHistory = academicHistory; const teacher = await Teacher.findOneAndUpdate( { clerkId: userId }, { $set: updatePayload }, - { new: true } - ) - + { new: true }, + ); + if (!teacher) { - return NextResponse.json({ error: 'Teacher not found' }, { status: 404 }) + return NextResponse.json({ error: "Teacher not found" }, { status: 404 }); } - return NextResponse.json(teacher) + return NextResponse.json(teacher); } catch (error) { if (error instanceof Error) { - console.error('PUT /api/profile error:', error.message) + console.error("PUT /api/profile error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } From 7ed61ef0b533e1d18d081cf128f87228ae816a71 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:29:43 +0530 Subject: [PATCH 08/23] fix: Improve formatting and error handling in GET and POST endpoints for students --- app/api/students/route.ts | 70 ++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/app/api/students/route.ts b/app/api/students/route.ts index 8f3dcc2..f3d2bb1 100644 --- a/app/api/students/route.ts +++ b/app/api/students/route.ts @@ -1,32 +1,33 @@ -import { auth } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import { connectDB } from '@/lib/mongodb' -import { Student } from '@/models/Student' -import { z } from 'zod' +import { auth } from "@clerk/nextjs/server"; +import { NextRequest, NextResponse } from "next/server"; +import { connectDB } from "@/lib/mongodb"; +import { Student } from "@/models/Student"; +import { z } from "zod"; const StudentSchema = z.object({ name: z.string().min(1), rollNo: z.string().min(1), class: z.string().min(1), - email: z.string().email().optional().or(z.literal('')), + email: z.string().email().optional().or(z.literal("")), phone: z.string().optional(), address: z.string().optional(), parentName: z.string().optional(), parentPhone: z.string().optional(), -}) +}); function escapeRegex(str: string): string { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } export async function GET(req: NextRequest) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { await connectDB(); const { searchParams } = new URL(req.url); - const search = (searchParams.get("search") ?? "").replace(/\s+/g, ' '); + const search = (searchParams.get("search") ?? "").replace(/\s+/g, " "); const classFilter = searchParams.get("class") ?? ""; // Parse and validate pagination @@ -72,37 +73,52 @@ export async function GET(req: NextRequest) { }); } catch (error) { if (error instanceof Error) { - console.error('GET /api/students error:', error.message) + console.error("GET /api/students error:", error.message); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } export async function POST(req: NextRequest) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - await connectDB() - - let body + await connectDB(); + + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Malformed JSON' }, { status: 400 }) + return NextResponse.json({ error: "Malformed JSON" }, { status: 400 }); } - - StudentSchema.safeParse(body) - const student = await Student.create({ ...(body as Record), teacherId: userId }) - return NextResponse.json(student, { status: 201 }) + const parsed = StudentSchema.safeParse(body); + if (!parsed.success) + return NextResponse.json( + { error: parsed.error.flatten() }, + { status: 400 }, + ); + + const student = await Student.create({ ...parsed.data, teacherId: userId }); + return NextResponse.json(student, { status: 201 }); } catch (error) { if (error instanceof Error) { - console.error('POST /api/students error:', error.message) + console.error("POST /api/students error:", error.message); } if ((error as { code?: number }).code === 11000) { - return NextResponse.json({ error: 'A student with this roll number already exists' }, { status: 409 }) + return NextResponse.json( + { error: "A student with this roll number already exists" }, + { status: 409 }, + ); } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } From c9b35304e7cde98270f4525fa5210f03c0c08c42 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:29:59 +0530 Subject: [PATCH 09/23] fix: Enhance error handling and authorization checks in PUT and DELETE endpoints for students --- app/api/students/[id]/route.ts | 107 +++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 2eaaf93..11122e7 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -1,81 +1,112 @@ -import { auth } from '@clerk/nextjs/server' -import { NextRequest, NextResponse } from 'next/server' -import mongoose from 'mongoose' -import { connectDB } from '@/lib/mongodb' -import { Student } from '@/models/Student' +import { auth } from "@clerk/nextjs/server"; +import { NextRequest, NextResponse } from "next/server"; +import mongoose from "mongoose"; +import { connectDB } from "@/lib/mongodb"; +import { Student } from "@/models/Student"; -const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'grade', 'rollNo', 'class', 'phone', 'address', 'parentName', 'parentPhone'] +const ALLOWED_UPDATE_FIELDS = [ + "name", + "email", + "grade", + "rollNo", + "class", + "phone", + "address", + "parentName", + "parentPhone", +]; -export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function PUT( + req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Bad Request' }, { status: 400 }) + return NextResponse.json({ error: "Bad Request" }, { status: 400 }); } - let body + let body; try { - body = await req.json() + body = await req.json(); } catch { - return NextResponse.json({ error: 'Bad Request' }, { status: 400 }) + return NextResponse.json({ error: "Bad Request" }, { status: 400 }); } // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} + const sanitizedBody: Record = {}; for (const key of ALLOWED_UPDATE_FIELDS) { if (key in body) { - sanitizedBody[key] = body[key] + sanitizedBody[key] = body[key]; } } - await connectDB() + await connectDB(); const student = await Student.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, - { new: true } - ) - if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 }) - return NextResponse.json(student) + { new: true }, + ); + if (!student) + return NextResponse.json({ error: "Not found" }, { status: 404 }); + return NextResponse.json(student); } catch (error) { if (error instanceof Error) { - console.error('PUT /api/students/[id] error:', error.message) + console.error("PUT /api/students/[id] error:", error.message); } if ((error as { code?: number }).code === 11000) { - return NextResponse.json({ error: 'A student with this roll number already exists' }, { status: 409 }) + return NextResponse.json( + { error: "A student with this roll number already exists" }, + { status: 409 }, + ); } - return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 }, + ); } } -export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { userId } = await auth() - if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) +export async function DELETE( + _req: NextRequest, + ctx: { params: Promise<{ id: string }> }, +) { + const { userId } = await auth(); + if (!userId) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { - const { id } = await ctx.params + const { id } = await ctx.params; // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Bad Request' }, { status: 400 }) + return NextResponse.json({ error: "Bad Request" }, { status: 400 }); } - await connectDB() - const deleted = await Student.findOneAndDelete({ _id: id }) - + await connectDB(); + const deleted = await Student.findOneAndDelete({ + _id: id, + teacherId: userId, + }); + if (!deleted) { - return NextResponse.json({ error: 'Student not found' }, { status: 404 }) + return NextResponse.json({ error: "Student not found" }, { status: 404 }); } - - return NextResponse.json({ success: true }) + + return NextResponse.json({ success: true }); } catch (error) { if (error instanceof Error) { - console.error('DELETE /api/students/[id] error:', error.message) + console.error("DELETE /api/students/[id] error:", error.message); } - return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }) + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 }, + ); } } From 78b16e0d5d3c8f6cbbd1ad27e93e4f951871cbe5 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:56:41 +0530 Subject: [PATCH 10/23] fix: Simplify error response in GET assignments endpoint --- 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 10e65c40516930939d4305d7d6e702a8743cf1ad Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:56:56 +0530 Subject: [PATCH 11/23] fix: Validate studentId format in Attendance schema and query --- app/api/attendance/route.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 14b6c4d..70d4b73 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -1,11 +1,14 @@ 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' const AttendanceSchema = z.object({ - studentId: z.string().min(1), + studentId: z.string().refine((id) => mongoose.Types.ObjectId.isValid(id), { + message: 'Invalid studentId', + }), studentName: z.string().min(1), class: z.string().min(1), date: z.string().min(1), @@ -76,6 +79,9 @@ export async function GET(req: NextRequest) { query.date = dateRange; } if (cls) query.class = cls; + if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 }); + } if (studentId) query.studentId = studentId; const records = await Attendance.find(query) From b16a97e2098b44354581f12cf9e5d8020c5935b7 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:57:21 +0530 Subject: [PATCH 12/23] fix: Validate studentId format in Grade schema and handle invalid cases in GET endpoint --- 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 eaba3cf..19e4247 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -1,12 +1,15 @@ 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"; const GradeSchema = z .object({ - studentId: z.string().min(1), + studentId: z.string().refine((id) => mongoose.Types.ObjectId.isValid(id), { + message: "Invalid studentId", + }), studentName: z.string().min(1), subject: z.string().min(1), marks: z.number().min(0), @@ -41,6 +44,9 @@ export async function GET(req: NextRequest) { const subject = searchParams.get("subject"); const query: Record = { teacherId: userId }; + if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json({ error: "Invalid studentId" }, { status: 400 }); + } if (studentId) query.studentId = studentId; if (subject) query.subject = subject; From d7afc42a79d9a53272df633a4f25472e9db65960 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:57:36 +0530 Subject: [PATCH 13/23] fix: Improve error handling for dashboard data loading and adjust total calculations for students and assignments --- app/dashboard/OverviewClient.tsx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 169795e..a326503 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -200,6 +200,16 @@ export function OverviewClient() { fetch("/api/announcements?limit=5"), ]); + if ( + !studentsRes.ok || + !assignmentsRes.ok || + !attendanceRes.ok || + !gradesRes.ok || + !announcementsRes.ok + ) { + throw new Error("Failed to load dashboard data"); + } + const [students, assignmentsData, attendance, grades, announcements] = await Promise.all([ studentsRes.json(), @@ -254,7 +264,7 @@ export function OverviewClient() { "B+": 8, B: 7, C: 6, - D: 4, + D: 5, F: 0, }; const termMap: Record = {}; @@ -316,10 +326,10 @@ export function OverviewClient() { .slice(0, 5); setStats({ - totalStudents: students.students?.length ?? 0, - totalAssignments: Array.isArray(assignments) - ? assignments.length - : (assignments.length ?? 0), + totalStudents: students.total ?? students.students?.length ?? 0, + totalAssignments: + assignmentsData.total ?? + (Array.isArray(assignments) ? assignments.length : 0), pendingAssignments: assignments.filter( (a: { status: string }) => a.status === "active", ).length, From 2eece6f31f9db872327de497231e6dab141d33a2 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:57:47 +0530 Subject: [PATCH 14/23] fix: Format GRADE_POINT for improved readability --- app/dashboard/grades/GradesClient.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/dashboard/grades/GradesClient.tsx b/app/dashboard/grades/GradesClient.tsx index fdaa00b..adfe7fd 100644 --- a/app/dashboard/grades/GradesClient.tsx +++ b/app/dashboard/grades/GradesClient.tsx @@ -60,7 +60,13 @@ function pct(marks: number, max: number) { } const GRADE_POINT: Record = { - 'A+': 10, A: 9, 'B+': 8, B: 7, C: 6, D: 5, F: 0, + 'A+': 10, + A: 9, + 'B+': 8, + B: 7, + C: 6, + D: 5, + F: 0, } function cgpaFromGrades(gradeList: Grade[]) { From 8d95d842806903ac7dbb9b91305a860eb7b07523 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 18 Apr 2026 13:58:01 +0530 Subject: [PATCH 15/23] fix: Format GRADE_POINT object for improved readability --- app/dashboard/students/StudentsClient.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/dashboard/students/StudentsClient.tsx b/app/dashboard/students/StudentsClient.tsx index 82a1d96..28b90f4 100644 --- a/app/dashboard/students/StudentsClient.tsx +++ b/app/dashboard/students/StudentsClient.tsx @@ -211,7 +211,15 @@ function StudentDrawer({ }, [grades]); const cgpa = useMemo(() => { - const GRADE_POINT: Record = { 'A+': 10, A: 9, 'B+': 8, B: 7, C: 6, D: 5, F: 0 } + const GRADE_POINT: Record = { + 'A+': 10, + A: 9, + 'B+': 8, + B: 7, + C: 6, + D: 5, + F: 0, + } if (!grades.length) return null const sum = grades.reduce((s, g) => s + (GRADE_POINT[g.grade] ?? 0), 0) return (sum / grades.length).toFixed(2) From bf49f6f1f7efcd00a019cb3081a8d832ba58b07d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:53:47 +0000 Subject: [PATCH 16/23] fix: apply PR review thread feedback across APIs and dashboard Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/announcements/[id]/route.ts | 6 +++ app/api/assignments/[id]/route.ts | 2 +- app/api/attendance/route.ts | 37 +++++++++++++++-- app/api/grades/[id]/route.ts | 2 +- app/api/grades/route.ts | 19 +++++---- app/api/profile/route.ts | 62 ++++++++++++++++------------- app/api/students/[id]/route.ts | 3 +- app/dashboard/OverviewClient.tsx | 17 ++++++-- models/Teacher.ts | 6 +-- scripts/seed.mjs | 2 +- 10 files changed, 104 insertions(+), 52 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 9e4e5ae..c2d8515 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -41,6 +41,12 @@ export async function PUT( { status: 400 }, ); } + if (body === null || typeof body !== "object" || Array.isArray(body)) { + return NextResponse.json( + { error: "Invalid JSON request body" }, + { status: 400 }, + ); + } // Sanitize: only allow whitelisted fields const sanitizedBody: Record = {}; diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index e81112f..e8194b8 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -55,7 +55,7 @@ export async function PUT( const assignment = await Assignment.findOneAndUpdate( { _id: id, teacherId: userId }, sanitizedBody, - { new: true }, + { new: true, runValidators: true, context: "query" }, ); if (!assignment) return NextResponse.json({ error: "Not found" }, { status: 404 }); diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 70d4b73..9cb288a 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -3,10 +3,11 @@ import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Attendance } from '@/models/Attendance' +import { Student } from '@/models/Student' import { z } from 'zod' const AttendanceSchema = z.object({ - studentId: z.string().refine((id) => mongoose.Types.ObjectId.isValid(id), { + studentId: z.string().refine((id) => mongoose.isObjectIdOrHexString(id), { message: 'Invalid studentId', }), studentName: z.string().min(1), @@ -79,7 +80,7 @@ export async function GET(req: NextRequest) { query.date = dateRange; } if (cls) query.class = cls; - if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) { + if (studentId && !mongoose.isObjectIdOrHexString(studentId)) { return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 }); } if (studentId) query.studentId = studentId; @@ -130,6 +131,21 @@ export async function POST(req: NextRequest) { ); if (isBulk) { + const studentIds = [ + ...new Set( + (parsed.data as z.infer).map( + (record) => record.studentId, + ), + ), + ]; + const ownedCount = await Student.countDocuments({ + _id: { $in: studentIds }, + teacherId: userId, + }); + if (ownedCount !== studentIds.length) { + return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 }); + } + const ops = (parsed.data as z.infer).map((record) => ({ updateOne: { filter: { teacherId: userId, studentId: record.studentId, date: record.date }, @@ -140,9 +156,22 @@ export async function POST(req: NextRequest) { await Attendance.bulkWrite(ops) return NextResponse.json({ success: true, count: ops.length }) } else { + const parsedRecord = parsed.data as z.infer; + const student = await Student.exists({ + _id: parsedRecord.studentId, + teacherId: userId, + }); + if (!student) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); + } + 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: parsedRecord.studentId, + date: parsedRecord.date, + }, + { $set: { ...parsedRecord, teacherId: userId } }, { upsert: true, new: true } ) return NextResponse.json(record, { status: 201 }) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 8cb6c56..7b9598e 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -44,7 +44,7 @@ export async function PUT( const grade = await Grade.findOneAndUpdate( { _id: id, teacherId: userId }, sanitizedBody, - { new: true }, + { new: true, runValidators: true, context: "query" }, ); if (!grade) return NextResponse.json({ error: "Not found" }, { status: 404 }); diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index 19e4247..58788de 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -23,7 +23,7 @@ const GradeSchema = z 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"; @@ -90,19 +90,22 @@ export async function POST(req: NextRequest) { ); const data = parsed.data; - const max = data.maxMarks ?? 100; const term = data.term ?? "Term 1"; + const gradeFilter = { + teacherId: userId, + studentId: data.studentId, + subject: data.subject, + term, + }; + const existing = await Grade.findOne(gradeFilter).select("maxMarks").lean(); + const max = data.maxMarks ?? existing?.maxMarks ?? 100; const grade = await Grade.findOneAndUpdate( - { - teacherId: userId, - studentId: data.studentId, - subject: data.subject, - term, - }, + gradeFilter, { $set: { ...data, + maxMarks: max, term, teacherId: userId, grade: calcGrade(data.marks, max), diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index 4e45237..0d3494d 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,26 +3,27 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -export async function GET(req: NextRequest) { +export async function GET() { const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); try { await connectDB(); - let teacher = await Teacher.findOne({ clerkId: userId }).lean(); - - if (!teacher) { - const clerkUser = await currentUser(); - const created = await Teacher.create({ - clerkId: userId, - name: clerkUser?.fullName ?? "", - email: clerkUser?.emailAddresses[0]?.emailAddress ?? "", - department: "", - subjects: [], - }); - teacher = created.toObject(); - } + const clerkUser = await currentUser(); + const teacher = await Teacher.findOneAndUpdate( + { clerkId: userId }, + { + $setOnInsert: { + clerkId: userId, + name: clerkUser?.fullName ?? "", + email: clerkUser?.emailAddresses?.[0]?.emailAddress ?? "", + department: "", + subjects: [], + }, + }, + { upsert: true, new: true, setDefaultsOnInsert: true, lean: true }, + ); return NextResponse.json(teacher); } catch (error) { @@ -95,18 +96,27 @@ export async function PUT(req: NextRequest) { if ( !Array.isArray(academicHistory) || academicHistory.length > 20 || - !academicHistory.every( - (entry: unknown) => - entry !== null && - typeof entry === "object" && - typeof (entry as Record).year === "string" && - typeof (entry as Record).title === "string", - ) + !academicHistory.every((entry: unknown) => { + if (entry === null || typeof entry !== "object") return false; + const record = entry as Record; + if (typeof record.year !== "string" || record.year.length > 10) + return false; + if (typeof record.title !== "string" || record.title.length > 200) + return false; + if ( + record.description !== undefined && + (typeof record.description !== "string" || + record.description.length > 2000) + ) { + return false; + } + return true; + }) ) { return NextResponse.json( { error: - "academicHistory must be an array of objects with string year and title (max 20 items)", + "academicHistory must be an array of objects with year/title strings (year<=10, title<=200, optional description<=2000, max 20 items)", }, { status: 400 }, ); @@ -122,14 +132,10 @@ export async function PUT(req: NextRequest) { const teacher = await Teacher.findOneAndUpdate( { clerkId: userId }, - { $set: updatePayload }, - { new: true }, + { $set: updatePayload, $setOnInsert: { clerkId: userId } }, + { new: true, upsert: true, setDefaultsOnInsert: true }, ); - if (!teacher) { - return NextResponse.json({ error: "Teacher not found" }, { status: 404 }); - } - return NextResponse.json(teacher); } catch (error) { if (error instanceof Error) { diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 11122e7..fec3278 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -7,7 +7,6 @@ import { Student } from "@/models/Student"; const ALLOWED_UPDATE_FIELDS = [ "name", "email", - "grade", "rollNo", "class", "phone", @@ -51,7 +50,7 @@ export async function PUT( const student = await Student.findOneAndUpdate( { _id: id, teacherId: userId }, sanitizedBody, - { new: true }, + { new: true, runValidators: true, context: "query" }, ); if (!student) return NextResponse.json({ error: "Not found" }, { status: 404 }); diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index a326503..b7e93b1 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -189,12 +189,14 @@ export function OverviewClient() { const [ studentsRes, assignmentsRes, + activeAssignmentsRes, attendanceRes, gradesRes, announcementsRes, ] = await Promise.all([ fetch("/api/students?limit=5"), fetch("/api/assignments"), + fetch("/api/assignments?status=active&limit=1"), fetch("/api/attendance"), fetch("/api/grades"), fetch("/api/announcements?limit=5"), @@ -203,6 +205,7 @@ export function OverviewClient() { if ( !studentsRes.ok || !assignmentsRes.ok || + !activeAssignmentsRes.ok || !attendanceRes.ok || !gradesRes.ok || !announcementsRes.ok @@ -210,10 +213,18 @@ export function OverviewClient() { throw new Error("Failed to load dashboard data"); } - const [students, assignmentsData, attendance, grades, announcements] = + const [ + students, + assignmentsData, + activeAssignmentsData, + attendance, + grades, + announcements, + ] = await Promise.all([ studentsRes.json(), assignmentsRes.json(), + activeAssignmentsRes.json(), attendanceRes.json(), gradesRes.json(), announcementsRes.json(), @@ -330,9 +341,7 @@ export function OverviewClient() { totalAssignments: assignmentsData.total ?? (Array.isArray(assignments) ? assignments.length : 0), - pendingAssignments: assignments.filter( - (a: { status: string }) => a.status === "active", - ).length, + pendingAssignments: activeAssignmentsData.total ?? 0, attendancePct, attendanceBreakdown: { present: totalPresent, diff --git a/models/Teacher.ts b/models/Teacher.ts index 837bbc8..69898ec 100644 --- a/models/Teacher.ts +++ b/models/Teacher.ts @@ -32,9 +32,9 @@ const TeacherSchema = new Schema( academicHistory: { type: [ { - year: { type: String, required: true }, - title: { type: String, required: true }, - description: { type: String }, + year: { type: String, required: true, maxlength: 10 }, + title: { type: String, required: true, maxlength: 200 }, + description: { type: String, maxlength: 2000 }, }, ], default: [], diff --git a/scripts/seed.mjs b/scripts/seed.mjs index 39eaffd..4f7a59c 100644 --- a/scripts/seed.mjs +++ b/scripts/seed.mjs @@ -416,7 +416,7 @@ async function main() { console.log(` Announcements: ${verifyCounts.announcements} (academic / events / admin / general)`) console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') - process.exit(0) + return } catch (err) { console.error('\n❌ Seed operations failed:') console.error(` ${err.message ?? err}`) From 9b0e5362177becb509a6cc62adf16353f0e0595a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:55:14 +0000 Subject: [PATCH 17/23] chore: finalize validation feedback Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/announcements/[id]/route.ts | 1 + app/api/profile/route.ts | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index c2d8515..d0b7022 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -42,6 +42,7 @@ export async function PUT( ); } if (body === null || typeof body !== "object" || Array.isArray(body)) { + // Valid JSON can still be a primitive/null/array, but this route requires an object payload. return NextResponse.json( { error: "Invalid JSON request body" }, { status: 400 }, diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index 0d3494d..9675c25 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -10,20 +10,23 @@ export async function GET() { try { await connectDB(); - const clerkUser = await currentUser(); - const teacher = await Teacher.findOneAndUpdate( - { clerkId: userId }, - { - $setOnInsert: { - clerkId: userId, - name: clerkUser?.fullName ?? "", - email: clerkUser?.emailAddresses?.[0]?.emailAddress ?? "", - department: "", - subjects: [], + let teacher = await Teacher.findOne({ clerkId: userId }).lean(); + if (!teacher) { + const clerkUser = await currentUser(); + teacher = await Teacher.findOneAndUpdate( + { clerkId: userId }, + { + $setOnInsert: { + clerkId: userId, + name: clerkUser?.fullName ?? "", + email: clerkUser?.emailAddresses?.[0]?.emailAddress ?? "", + department: "", + subjects: [], + }, }, - }, - { upsert: true, new: true, setDefaultsOnInsert: true, lean: true }, - ); + { upsert: true, new: true, setDefaultsOnInsert: true, lean: true }, + ); + } return NextResponse.json(teacher); } catch (error) { From 3c45899ff7bb57884af840bf57e6887c9de5b60a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:55:38 +0000 Subject: [PATCH 18/23] fix: keep Next.js route signature while avoiding unused warning Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/profile/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index 9675c25..a9eb02d 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,7 +3,8 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -export async function GET() { +export async function GET(req: NextRequest) { + void req; const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); From 4cc36a8d2ffdcb11ab44817ef0836cc8595a93e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:57:29 +0000 Subject: [PATCH 19/23] chore: close remaining code review nits Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/attendance/route.ts | 7 ++++--- app/api/profile/route.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 9cb288a..3a665ff 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -1,13 +1,14 @@ 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 { Student } from '@/models/Student' import { z } from 'zod' +const isCanonicalObjectId = (id: string) => /^[0-9a-fA-F]{24}$/.test(id) + const AttendanceSchema = z.object({ - studentId: z.string().refine((id) => mongoose.isObjectIdOrHexString(id), { + studentId: z.string().refine((id) => isCanonicalObjectId(id), { message: 'Invalid studentId', }), studentName: z.string().min(1), @@ -80,7 +81,7 @@ export async function GET(req: NextRequest) { query.date = dateRange; } if (cls) query.class = cls; - if (studentId && !mongoose.isObjectIdOrHexString(studentId)) { + if (studentId && !isCanonicalObjectId(studentId)) { return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 }); } if (studentId) query.studentId = studentId; diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index a9eb02d..f580a7b 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,8 +3,8 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -export async function GET(req: NextRequest) { - void req; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function GET(_req: NextRequest) { const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); From d2fb70592b572624307d589864aa391074a5b55e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:57:58 +0000 Subject: [PATCH 20/23] fix: resolve remaining validation nits in profile and attendance routes Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/attendance/route.ts | 5 ++++- app/api/profile/route.ts | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 3a665ff..be18cfa 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -163,7 +163,10 @@ export async function POST(req: NextRequest) { teacherId: userId, }); if (!student) { - return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); + return NextResponse.json( + { error: 'Student not found or access denied' }, + { status: 403 }, + ); } const record = await Attendance.findOneAndUpdate( diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index f580a7b..9675c25 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,8 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export async function GET(_req: NextRequest) { +export async function GET() { const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); From 16f84675be6b6c6dc55bea2ebd62f0114073564a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:59:09 +0000 Subject: [PATCH 21/23] chore: document strict objectid validation and keep request signature explicit Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/attendance/route.ts | 1 + app/api/profile/route.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index be18cfa..b6d52cb 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -5,6 +5,7 @@ import { Attendance } from '@/models/Attendance' import { Student } from '@/models/Student' import { z } from 'zod' +// Use strict 24-hex ObjectId checks to reject 12-byte non-hex strings. const isCanonicalObjectId = (id: string) => /^[0-9a-fA-F]{24}$/.test(id) const AttendanceSchema = z.object({ diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index 9675c25..5a1576b 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,7 +3,8 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -export async function GET() { +export async function GET(req: NextRequest) { + void req.nextUrl; const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); From dbcc99bf254a6b0ff940575ae90b1b9aa2dd89d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 09:00:22 +0000 Subject: [PATCH 22/23] fix: remove redundant upsert fields and avoid unnecessary grade lookup Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/fad4ba3f-07a4-4318-99ef-0b604087422f Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/grades/route.ts | 7 +++++-- app/api/profile/route.ts | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index 58788de..18a132f 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -97,8 +97,11 @@ export async function POST(req: NextRequest) { subject: data.subject, term, }; - const existing = await Grade.findOne(gradeFilter).select("maxMarks").lean(); - const max = data.maxMarks ?? existing?.maxMarks ?? 100; + let max = data.maxMarks; + if (max === undefined) { + const existing = await Grade.findOne(gradeFilter).select("maxMarks").lean(); + max = existing?.maxMarks ?? 100; + } const grade = await Grade.findOneAndUpdate( gradeFilter, diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index 5a1576b..ce45e53 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,8 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; import { connectDB } from "@/lib/mongodb"; import { Teacher } from "@/models/Teacher"; -export async function GET(req: NextRequest) { - void req.nextUrl; +export async function GET() { const { userId } = await auth(); if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); @@ -136,7 +135,7 @@ export async function PUT(req: NextRequest) { const teacher = await Teacher.findOneAndUpdate( { clerkId: userId }, - { $set: updatePayload, $setOnInsert: { clerkId: userId } }, + { $set: updatePayload }, { new: true, upsert: true, setDefaultsOnInsert: true }, ); From e1063a7fc1c6439ef6a54b75b560760efb5a5009 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 09:11:11 +0000 Subject: [PATCH 23/23] fix: guard non-object payloads and enforce effective maxMarks validation Agent-Logs-Url: https://github.com/ArhanAnsari/discord-challenge/sessions/f0bcc66c-11ab-4988-8980-c82e8dced445 Co-authored-by: ArhanAnsari <109061685+ArhanAnsari@users.noreply.github.com> --- app/api/assignments/[id]/route.ts | 6 ++++++ app/api/grades/[id]/route.ts | 6 ++++++ app/api/grades/route.ts | 20 +++++++++++++++++--- app/api/students/[id]/route.ts | 11 ++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index e8194b8..b6428fb 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -43,6 +43,12 @@ export async function PUT( { status: 400 }, ); } + if (body === null || typeof body !== "object" || Array.isArray(body)) { + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); + } // Sanitize: only allow whitelisted fields const sanitizedBody: Record = {}; diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 7b9598e..b4429ba 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -31,6 +31,12 @@ export async function PUT( { status: 400 }, ); } + if (body === null || typeof body !== "object" || Array.isArray(body)) { + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); + } // Sanitize: only allow whitelisted fields const sanitizedBody: Record = {}; diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index 18a132f..ba8ba32 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -97,10 +97,24 @@ export async function POST(req: NextRequest) { subject: data.subject, term, }; - let max = data.maxMarks; - if (max === undefined) { + let max: number; + if (data.maxMarks === undefined) { const existing = await Grade.findOne(gradeFilter).select("maxMarks").lean(); max = existing?.maxMarks ?? 100; + } else { + max = data.maxMarks; + } + if (!Number.isFinite(data.marks)) { + return NextResponse.json( + { error: "marks must be a valid number" }, + { status: 400 }, + ); + } + if (data.marks > max) { + return NextResponse.json( + { error: "marks must be less than or equal to maxMarks" }, + { status: 400 }, + ); } const grade = await Grade.findOneAndUpdate( @@ -114,7 +128,7 @@ export async function POST(req: NextRequest) { grade: calcGrade(data.marks, max), }, }, - { upsert: true, new: true }, + { upsert: true, new: true, runValidators: true, context: "query" }, ); return NextResponse.json(grade, { status: 201 }); } catch (error) { diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index fec3278..b056a0b 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -35,7 +35,16 @@ export async function PUT( try { body = await req.json(); } catch { - return NextResponse.json({ error: "Bad Request" }, { status: 400 }); + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); + } + if (body === null || typeof body !== "object" || Array.isArray(body)) { + return NextResponse.json( + { error: "Invalid JSON in request body" }, + { status: 400 }, + ); } // Sanitize: only allow whitelisted fields