-
Notifications
You must be signed in to change notification settings - Fork 23
Fix: improve backend validation, security and data integrity across APIs #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
098f990
b5f7a1e
a3d811f
b899a9e
dc2a08a
e1ddfe2
1b2e0be
4fe3ca5
4034a32
6909cbb
563beff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from 'next/server' | |||||||||||||||||||
| import { connectDB } from '@/lib/mongodb' | ||||||||||||||||||||
| import { Grade } from '@/models/Grade' | ||||||||||||||||||||
| import { z } from 'zod' | ||||||||||||||||||||
| import mongoose from 'mongoose' | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const GradeSchema = z.object({ | ||||||||||||||||||||
| studentId: z.string().min(1), | ||||||||||||||||||||
|
|
@@ -41,7 +42,11 @@ export async function GET(req: NextRequest) { | |||||||||||||||||||
| const subject = searchParams.get('subject') | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const query: Record<string, unknown> = { teacherId: userId } | ||||||||||||||||||||
| if (studentId) query.studentId = studentId | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (studentId && mongoose.Types.ObjectId.isValid(studentId)) { | ||||||||||||||||||||
| query.studentId = new mongoose.Types.ObjectId(studentId) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+46
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Invalid When 🔧 Proposed fix- if (studentId && mongoose.Types.ObjectId.isValid(studentId)) {
- query.studentId = new mongoose.Types.ObjectId(studentId)
+ if (studentId) {
+ if (!mongoose.Types.ObjectId.isValid(studentId)) {
+ return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 })
+ }
+ query.studentId = new mongoose.Types.ObjectId(studentId)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| if (subject) query.subject = subject | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const grades = await Grade.find(query).sort({ createdAt: -1 }).lean() | ||||||||||||||||||||
|
|
@@ -58,22 +63,22 @@ export async function POST(req: NextRequest) { | |||||||||||||||||||
|
|
||||||||||||||||||||
| try { | ||||||||||||||||||||
| await connectDB() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let body | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| body = await req.json() | ||||||||||||||||||||
| } catch { | ||||||||||||||||||||
| 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( | ||||||||||||||||||||
|
|
||||||||||||||||||||
| 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 } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,15 @@ const AttendanceSchema = new Schema<IAttendance>( | |
| teacherId: { type: String, required: true, index: true }, | ||
| studentId: { type: Schema.Types.ObjectId, ref: 'Student', required: true }, | ||
| studentName: { type: String, required: true }, | ||
| class: { type: String, required: true }, | ||
| class: { | ||
| type: String, | ||
| required: true, | ||
| trim: true, | ||
| validate: { | ||
| validator: (v: string) => v.trim().length > 0, | ||
| message: 'Class cannot be empty', | ||
| }, | ||
| }, | ||
|
Comment on lines
+20
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm the Zod schema for `class` in the attendance API route still uses min(1) without trim.
rg -nP -C2 '\bclass\s*:\s*z\.string\(\)' --type=tsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1057 Validator is redundant after Since Suggested fixes class: {
type: String,
required: true,
trim: true,
validate: {
- validator: (v: string) => v.trim().length > 0,
+ validator: (v: string) => v.length > 0,
message: 'Class cannot be empty',
},
},And in - class: z.string().min(1),
+ class: z.string().trim().min(1),🤖 Prompt for AI Agents |
||
| date: { type: String, required: true }, | ||
| status: { type: String, enum: ['present', 'absent', 'late'], required: true }, | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 4396
Fix audience default casing inconsistency (
'all'→'All').The Mongoose schema in
models/Announcement.tsdefaultsaudienceto'All'(capital A), and the client UI inAnnouncementsClient.tsxdefaults to'All'and explicitly checksa.audience !== "All"to determine whether to display an audience badge. The API route defaults to lowercase'all', creating a casing mismatch that causes records created via this endpoint to have'all'while the client expects'All', breaking the badge display logic and data consistency.Fix
📝 Committable suggestion
🤖 Prompt for AI Agents