Skip to content

fix: resolve issues in API endpoints and dashboard components#11

Open
AradhyaX wants to merge 9 commits intoJavaScript-Mastery-Pro:mainfrom
AradhyaX:fix/bughunt-aradhya
Open

fix: resolve issues in API endpoints and dashboard components#11
AradhyaX wants to merge 9 commits intoJavaScript-Mastery-Pro:mainfrom
AradhyaX:fix/bughunt-aradhya

Conversation

@AradhyaX
Copy link
Copy Markdown

@AradhyaX AradhyaX commented Apr 18, 2026

Email - soularadhya@gmail.com
→ Your GitHub PR link https://github.com/AradhyaX/discord-challenge/pull/new/fix/bughunt-aradhya

→ Number of issues fixed (self-reported) 12

→ Short summary of your changes

Secured API Authorization: Fixed authorization loopholes on all PUT/DELETE API routes by enforcing teacherId: userId checks to ensure only resource owners can modify or delete items.
Strict Data Validation: Replaced weak parameter whitelisting with strict Zod schema validation across all endpoints (announcements, assignments, attendance, grades, and students).
React Performance Fixes: Resolved React hooks cascading render bugs in the dashboard components (Navbar and OverviewClient).

Summary by CodeRabbit

  • Bug Fixes

    • Fixed authorization checks to ensure users can only modify their own assignments, announcements, grades, and students.
    • Corrected deadline field formatting in assignment edit form.
    • Improved error handling for invalid requests with clearer error messages.
    • Enhanced grade calculations and validation.
  • New Features

    • Added separate tracking of active assignments in the dashboard.
    • Improved input validation across all endpoints with stricter field requirements.
  • Improvements

    • Dashboard now normalizes data more defensively to handle edge cases.
    • Updated grade point mappings for consistency.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

Warning

Rate limit exceeded

@AradhyaX has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 10 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 10 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f44a8c9-d9b6-413a-9556-01d82d003190

📥 Commits

Reviewing files that changed from the base of the PR and between 3a781c2 and ebcdcb8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • app/api/announcements/[id]/route.ts
  • app/api/assignments/[id]/route.ts
  • app/api/assignments/route.ts
  • app/api/attendance/route.ts
  • app/api/grades/[id]/route.ts
  • app/api/grades/route.ts
  • app/api/profile/route.ts
  • app/api/students/[id]/route.ts
  • app/api/students/route.ts
  • app/dashboard/OverviewClient.tsx
  • app/dashboard/assignments/AssignmentsClient.tsx
📝 Walkthrough

Walkthrough

This PR introduces schema-based validation via Zod across multiple API routes, replacing manual field sanitization. It adds authorization scoping to ensure teachers can only modify their own resources, introduces ObjectId validation for MongoDB IDs, improves error handling with precise HTTP status codes, and updates the dashboard to fetch data with pagination and defensive normalization.

Changes

Cohort / File(s) Summary
API Validation & Authorization (PUT/DELETE)
app/api/announcements/[id]/route.ts, app/api/assignments/[id]/route.ts, app/api/grades/[id]/route.ts, app/api/students/[id]/route.ts
Replaced field-sanitization with Zod schema validation; validate request bodies and reject on invalid shapes or when no updatable fields present (400). Added ownership enforcement: PUT and DELETE queries now require both _id: id and teacherId: userId to scope operations to authenticated user.
API Route POST/GET Enhancement
app/api/assignments/route.ts, app/api/grades/route.ts, app/api/students/route.ts, app/api/attendance/route.ts
Introduced StudentIdSchema for MongoDB ObjectId validation; tightened deadline validation with Date.parse() checks. Enabled Mongoose validators during updates and improved error responses (removed stack traces). In POST, now uses validated parsed.data instead of raw request body.
Profile & Error Handling
app/api/profile/route.ts
Changed GET to derive userId exclusively from auth context rather than query parameters; added 401 check when creating missing Teacher records.
Dashboard Data Layer
app/dashboard/OverviewClient.tsx, app/dashboard/assignments/AssignmentsClient.tsx
Introduced pagination (limit parameters) and defensive JSON parsing via readJson helper. Normalized fetched data shapes with fallbacks to empty arrays. Updated deadline formatting by slicing to date portion. Fixed assignment move error handling. Adjusted CGPA grade mapping (D: 4 → 5).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 With Zod's validation and scopes held tight,
Authorization keeps each record in sight,
No loose fields dance where they shouldn't go,
Ownership bound from API below,
The dashboard pirouettes with data so clean,
A security-first API scene!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and overly broad. It uses the generic phrase 'resolve issues' without specifying what issues were resolved, making it unclear what the primary change is. Use a more specific title that describes the main change, e.g., 'fix: add Zod validation and enforce authorization scoping in API endpoints' or 'fix: strengthen API authorization and validation across endpoints'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AradhyaX AradhyaX force-pushed the fix/bughunt-aradhya branch from 3a781c2 to ebcdcb8 Compare April 18, 2026 15:16
@AradhyaX
Copy link
Copy Markdown
Author

AradhyaX commented Apr 18, 2026

Email - [soularadhya@gmail.com)
→ Your GitHub PR link https://github.com/AradhyaX/discord-challenge/pull/11

→ Number of issues fixed (self-reported) 9

Summary of changes:

Fixed authentication scoping issues in API routes
Added proper validation using safeParse
Implemented ObjectId validation for routes
Improved error handling to prevent internal leaks
Corrected logic inconsistencies in grade and assignment flows
Fixed UI calculation and data handling issues

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
app/dashboard/OverviewClient.tsx (1)

333-352: Upcoming deadlines are derived from a paginated slice.

upcomingDeadlines is computed from the 100 most-recently-created assignments (sorted desc by createdAt server-side). Long-lived active assignments with imminent deadlines but older createdAt will be silently dropped once the teacher has >100 assignments. For a dashboard that highlights deadlines, consider querying the API with sort-by-deadline/active filter, or bumping ?limit= high enough to cover realistic usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/dashboard/OverviewClient.tsx` around lines 333 - 352, upcomingDeadlines
is computed from the client-side variable assignments which currently contains
only the 100 most-recently-created items, so imminent active assignments older
than the 100 newest createdAt entries are omitted; update the data fetching
logic instead of post-filtering here: change the API request that populates
assignments to include an active status filter and sort by deadline (or increase
the ?limit= to a safe maximum), then use the resulting assignments array in
OverviewClient.upcomingDeadlines so the top 5 nearest-deadline active
assignments are always included (refer to the upcomingDeadlines computation and
whatever fetch/populate function that sets assignments).
app/api/grades/[id]/route.ts (1)

21-30: Duplicate calcGrade — extract to a shared helper.

Identical to the calcGrade in app/api/grades/route.ts (per relevant context). Any future change to the grade scale (e.g., the A+ cutoff inconsistency where > 90 is A+ but >= 80 is A, so exactly 90 falls into A) must be made in two places. Extract into something like lib/grade.ts and import from both routes.

♻️ Suggested shared helper
// lib/grade.ts
export 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'
}

Then in both routes:

-function calcGrade(marks: number, max: number): string {
-  const pct = (marks / max) * 100
-  if (pct > 90) return 'A+'
-  ...
-}
+import { calcGrade } from '@/lib/grade'

While you're there, please confirm whether > 90 vs >= 90 for A+ is deliberate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/grades/`[id]/route.ts around lines 21 - 30, Extract the duplicated
calcGrade function into a shared helper (e.g., export function calcGrade in
lib/grade.ts) and replace the local calcGrade implementations in both routes
with an import from that helper; ensure the helper uses the normalized cutoff
(change the first condition to pct >= 90 for 'A+'), export it, and update all
call sites to import { calcGrade } from the new module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/assignments/`[id]/route.ts:
- Around line 13-15: The current Zod schema for the deadline field uses
Date.parse which is too permissive; update the deadline schema (the Zod field
named deadline in the route handlers) to use Zod's stricter validators — e.g.,
replace the refine(Date.parse) approach with z.string().datetime() if you expect
full ISO datetimes or z.string().regex(/^\d{4}-\d{2}-\d{2}$/) (or
z.string().regex for a stricter ISO date) for date-only values, keeping
.optional() as needed; apply the same change to the deadline field in both the
update route schema (the [id] route) and the create route schema so validation
is consistent.

In `@app/api/assignments/route.ts`:
- Around line 12-14: The 'deadline' Zod schema field currently uses Date.parse
in a refine which accepts malformed dates; change the validator on the deadline
field to use Zod's ISO datetime check (e.g., replace the Date.parse-based refine
with z.string().datetime(...) on the deadline field) so only strict ISO
datetimes are accepted; if you need date-only values instead, validate with a
strict YYYY-MM-DD pattern (regex) on the same 'deadline' schema entry.

In `@app/api/attendance/route.ts`:
- Around line 3-13: Replace the loose ObjectId check in StudentIdSchema with a
strict validator (use mongoose.isObjectIdOrHexString(value) or a 24-hex regex)
so only 24-hex strings are accepted; then reuse that StudentIdSchema in
AttendanceSchema and in the GET query validation (the GET guard that currently
duplicates validation) to avoid divergent checks—update the refine predicate on
StudentIdSchema and replace the duplicated GET validation logic to reference
StudentIdSchema.

In `@app/api/grades/`[id]/route.ts:
- Around line 60-82: The code has a TOCTOU between Grade.findOne and
Grade.findOneAndUpdate: compute merged marks/maxMarks from parsed.data and
existing then validate, but a concurrent update can violate marks <= maxMarks
before findOneAndUpdate runs; either add a schema-level validator on the Grade
model that enforces marks <= maxMarks so runValidators: true on
Grade.findOneAndUpdate will catch violations, or make the update atomic by
moving the validation into the findOneAndUpdate filter (use a compound query or
$expr to require (parsed.data.marks ?? existing.marks) <= (parsed.data.maxMarks
?? existing.maxMarks) in the query), and ensure calcGrade is computed from the
values in the same atomic update; update the code paths that call Grade.findOne,
Grade.findOneAndUpdate and any reliance on the pre('save') hook (which does not
run for findOneAndUpdate) to use the chosen approach.

In `@app/api/grades/route.ts`:
- Around line 3-13: The StudentIdSchema currently uses
mongoose.Types.ObjectId.isValid which is too permissive; update the refine
predicate to use mongoose.isObjectIdOrHexString instead to ensure only ObjectId
instances or 24-character hex strings pass, and make the same replacement where
studentId validation occurs later in this file (the other validate calls around
the GradeSchema / any validation at lines ~48-50); keep the same error message
('Invalid studentId') and schema names (StudentIdSchema, GradeSchema) so only
the predicate changes.

In `@app/dashboard/assignments/AssignmentsClient.tsx`:
- Line 347: The code currently sets deadline using the raw ISO substring
"deadline: a.deadline.slice(0, 10)" which uses the UTC calendar date and will
shift for non-UTC users; replace that slice with a timezone-aware local-date
formatter by parsing the ISO into a Date (new Date(a.deadline)) and building a
YYYY-MM-DD string from the Date's local getters (getFullYear(), getMonth()+1,
getDate()) so the <input type="date"> is prefilled with the user's local date;
alternatively, if you intend deadlines to be UTC end-to-end, make that explicit
here and on submit by normalizing to UTC rather than converting to local time.

---

Nitpick comments:
In `@app/api/grades/`[id]/route.ts:
- Around line 21-30: Extract the duplicated calcGrade function into a shared
helper (e.g., export function calcGrade in lib/grade.ts) and replace the local
calcGrade implementations in both routes with an import from that helper; ensure
the helper uses the normalized cutoff (change the first condition to pct >= 90
for 'A+'), export it, and update all call sites to import { calcGrade } from the
new module.

In `@app/dashboard/OverviewClient.tsx`:
- Around line 333-352: upcomingDeadlines is computed from the client-side
variable assignments which currently contains only the 100 most-recently-created
items, so imminent active assignments older than the 100 newest createdAt
entries are omitted; update the data fetching logic instead of post-filtering
here: change the API request that populates assignments to include an active
status filter and sort by deadline (or increase the ?limit= to a safe maximum),
then use the resulting assignments array in OverviewClient.upcomingDeadlines so
the top 5 nearest-deadline active assignments are always included (refer to the
upcomingDeadlines computation and whatever fetch/populate function that sets
assignments).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 88f45c36-b957-40e3-89c5-a7c0ad890148

📥 Commits

Reviewing files that changed from the base of the PR and between c71cbbf and 3a781c2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • app/api/announcements/[id]/route.ts
  • app/api/assignments/[id]/route.ts
  • app/api/assignments/route.ts
  • app/api/attendance/route.ts
  • app/api/grades/[id]/route.ts
  • app/api/grades/route.ts
  • app/api/profile/route.ts
  • app/api/students/[id]/route.ts
  • app/api/students/route.ts
  • app/dashboard/OverviewClient.tsx
  • app/dashboard/assignments/AssignmentsClient.tsx

Comment on lines +13 to +15
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}).optional(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
for (const value of ['2026-02-31', '2026-13-01', '1', '2026-04-18', '2026-04-18T00:00:00.000Z']) {
  const ms = Date.parse(value)
  console.log(value, Number.isNaN(ms) ? 'invalid' : new Date(ms).toISOString())
}
NODE

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 251


🏁 Script executed:

# Check if the file exists and read the relevant section
fd -t f "route.ts" | grep "assignments\[id\]" | head -5

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 66


🏁 Script executed:

# Find package.json and check Zod version
fd -t f "package.json" | head -1 | xargs cat | jq '.dependencies, .devDependencies' 2>/dev/null || cat package.json | grep -i zod || echo "Checking for package.lock files..."

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 512


🏁 Script executed:

# List potential package lock files to check versions
fd -t f "package-lock.json|pnpm-lock.yaml|yarn.lock" | head -1

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 101


🏁 Script executed:

# Search more broadly for the route file
find . -name "route.ts" -o -name "route.js" 2>/dev/null | grep -i assignment

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 150


🏁 Script executed:

# Check Zod 4.3.6 API for date validation - web search will help with this
# But first let's see if the file exists anywhere
fd -type f "route.ts" 2>/dev/null | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 66


🏁 Script executed:

# Check app/api directory structure
find app -type d 2>/dev/null | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 483


🏁 Script executed:

# Read the actual file to verify the code and schema
cat -n app/api/assignments/\[id\]/route.ts | head -60

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 2613


🏁 Script executed:

# Check Zod 4.3.6 documentation for correct date validation API
# Since it's Zod 4.3.6, let's verify what date validators are available
# Let's also look for how zod is imported and used in the file
grep -n "import.*zod\|z\." app/api/assignments/\[id\]/route.ts | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 583


🌐 Web query:

Zod 4.3.6 date validation api z.iso.date() z.iso.datetime()

💡 Result:

In Zod 4.3.6, the date validation API includes z.iso.date for validating ISO 8601 date strings in YYYY-MM-DD format and z.iso.datetime for ISO 8601 datetime strings. Usage examples: import { z } from "zod"; const dateSchema = z.iso.date; dateSchema.parse("2020-01-01"); // ✅ valid dateSchema.parse("2020-1-1"); // ❌ invalid const datetimeSchema = z.iso.datetime; datetimeSchema.parse("2020-01-01T06:15:00Z"); // ✅ valid datetimeSchema.parse("2020-01-01T06:15:00.123Z"); // ✅ valid (with milliseconds) These are string validators using regex-based checks for ISO formats. They were introduced in Zod v4 (stable release announced recently, with v4.3.6 being a patch release). Ensure standard import import { z } from "zod"; and Zod v4+ installation. TypeScript recognizes these in v4.3.6 when properly installed. Note: z.date validates Date objects, while z.iso.date and z.iso.datetime validate strings. For Date coercion from strings, use z.coerce.date or codecs like z.codec(z.iso.datetime, z.date).

Citations:


🏁 Script executed:

# Let's also check if there's a create route that uses similar validation
cat -n app/api/assignments/route.ts | head -40

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 1777


Tighten deadline validation beyond Date.parse.

Date.parse accepts permissive inputs: date rollover (e.g., '2026-02-31' → Mar 3), numeric strings ('1' → 2001-01-01), and other ambiguous formats. Use Zod's stricter ISO validators.

🛡️ Proposed fix
-  deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
-    message: 'Invalid deadline',
-  }).optional(),
+  deadline: z.iso.date().optional(),

If this endpoint intentionally accepts full datetimes too:

-  deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
-    message: 'Invalid deadline',
-  }).optional(),
+  deadline: z.union([z.iso.date(), z.iso.datetime()]).optional(),

This also applies to the create route at app/api/assignments/route.ts (lines 12–14).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}).optional(),
deadline: z.string().datetime().optional(),
Suggested change
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}).optional(),
deadline: z.union([
z.string().date(),
z.string().datetime(),
]).optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/assignments/`[id]/route.ts around lines 13 - 15, The current Zod
schema for the deadline field uses Date.parse which is too permissive; update
the deadline schema (the Zod field named deadline in the route handlers) to use
Zod's stricter validators — e.g., replace the refine(Date.parse) approach with
z.string().datetime() if you expect full ISO datetimes or
z.string().regex(/^\d{4}-\d{2}-\d{2}$/) (or z.string().regex for a stricter ISO
date) for date-only values, keeping .optional() as needed; apply the same change
to the deadline field in both the update route schema (the [id] route) and the
create route schema so validation is consistent.

Comment on lines +12 to +14
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
for (const value of ['2026-02-31', '2026-13-01', '1', '2026-04-18', '2026-04-18T00:00:00.000Z']) {
  const ms = Date.parse(value)
  console.log(value, Number.isNaN(ms) ? 'invalid' : new Date(ms).toISOString())
}
NODE

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 251


🏁 Script executed:

cat package.json | grep -A 2 -B 2 "zod"

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 191


🏁 Script executed:

find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) -exec grep -l "z\\..*date" {} \; | head -10

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 113


🏁 Script executed:

cat app/api/assignments/route.ts

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 3209


🏁 Script executed:

cat app/api/attendance/route.ts

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 5226


🌐 Web query:

Zod 4.3.6 ISO date datetime validation API

💡 Result:

In Zod version 4.3.6, the ISO date and datetime validation API is available via the z.iso namespace on the main z object. Key methods: ISO Date (validates YYYY-MM-DD format): import { z } from 'zod'; const dateSchema = z.iso.date; dateSchema.parse('2020-01-01'); // ✅ valid dateSchema.parse('2020-1-1'); // ❌ invalid (must be padded) dateSchema.parse('2020-01-32'); // ❌ invalid day ISO Datetime (validates ISO 8601 format, default no timezone offsets except Z): const datetimeSchema = z.iso.datetime; datetimeSchema.parse('2020-01-01T06:15:00Z'); // ✅ valid datetimeSchema.parse('2020-01-01T06:15:00.123Z'); // ✅ valid (subseconds) datetimeSchema.parse('2020-01-01T06:15:00+02:00'); // ❌ invalid (offsets not allowed by default) Other related: - z.iso.time: HH:MM[:SS[.s+]] (no offsets) - z.iso.duration: ISO 8601 duration These are regex-based string validations (not semantic date checks like min/max dates; use .refine with date libraries for that). For Date objects: use z.date. To convert ISO string to Date: const stringToDate = z.codec( z.iso.datetime, z.date, { decode: (isoString) => new Date(isoString), encode: (date) => date.toISOString, } ); stringToDate.parse('2024-01-15T10:30:00.000Z'); // returns Date object Official docs: https://zod.dev/ (Zod 4 stable since 2025). Version 4.3.6 released ~Jan 2026 per npm.

Citations:


Use strict ISO validation for deadline.

Date.parse is permissive and accepts malformed dates. For example, '2026-02-31' parses successfully (rolling over to March 3rd) and '1' parses as year 2001. Use Zod's ISO datetime validator to reject invalid deadlines before persistence.

🛡️ Proposed fix
-  deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
-    message: 'Invalid deadline',
-  }),
+  deadline: z.iso.datetime(),

If dates without time components are acceptable:

-  deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
-    message: 'Invalid deadline',
-  }),
+  deadline: z.union([z.iso.date(), z.iso.datetime()]),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}),
deadline: z.string().datetime(),
Suggested change
deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), {
message: 'Invalid deadline',
}),
deadline: z.union([z.string().date(), z.string().datetime()]),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/assignments/route.ts` around lines 12 - 14, The 'deadline' Zod schema
field currently uses Date.parse in a refine which accepts malformed dates;
change the validator on the deadline field to use Zod's ISO datetime check
(e.g., replace the Date.parse-based refine with z.string().datetime(...) on the
deadline field) so only strict ISO datetimes are accepted; if you need date-only
values instead, validate with a strict YYYY-MM-DD pattern (regex) on the same
'deadline' schema entry.

Comment on lines +3 to +13
import mongoose from 'mongoose'
import { connectDB } from '@/lib/mongodb'
import { Attendance } from '@/models/Attendance'
import { z } from 'zod'

const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), {
message: 'Invalid studentId',
})

const AttendanceSchema = z.object({
studentId: z.string().min(1),
studentId: StudentIdSchema,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Mongoose 9, does mongoose.Types.ObjectId.isValid accept 12-character non-hex strings, and is mongoose.isObjectIdOrHexString the recommended stricter validator?

💡 Result:

Yes, in Mongoose 9, mongoose.Types.ObjectId.isValid accepts 12-character non-hex strings as valid, consistent with BSON ObjectId behavior where any 12-byte string is technically valid. For example, mongoose.Types.ObjectId.isValid("zzzzzzzzzzzz") returns true. This has been the case across versions, including Mongoose 9, as no breaking changes to this behavior appear in the v9 changelog or migration guide (though v9 makes isValidObjectId stricter for numbers). Yes, mongoose.isObjectIdOrHexString is the recommended stricter validator. It returns true only if the value is a Mongoose ObjectId instance or a 24-character hex string (matching /^[0-9a-fA-F]{24}$/), and false for 12-char non-hex strings like "zzzzzzzzzzzz" or numbers. It was introduced in Mongoose 6.2.5 to address common use cases needing hex validation, and maintainers like @vkarpov15 recommend it over isValid for verifying 24-hex strings (e.g., in issues #15510, #14200, #4083). The official docs note that by default any 12-char string is valid for ObjectId casting, and isObjectIdOrHexString provides the stricter check.

Citations:


🏁 Script executed:

# Check if the file exists and examine the relevant sections
cat -n app/api/attendance/route.ts | head -50

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 2098


Use strict ObjectId validation for studentId.

mongoose.Types.ObjectId.isValid accepts any 12-byte string as valid, including 12-character non-hex strings, which can be persisted as bogus attendance studentIds. Use mongoose.isObjectIdOrHexString (stricter, 24-hex only) or a regex pattern instead. Reuse the schema in both the POST validation and GET query guard.

🛡️ Proposed fix
-import mongoose from 'mongoose'
 import { connectDB } from '@/lib/mongodb'
 import { Attendance } from '@/models/Attendance'
 import { z } from 'zod'
 
-const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), {
+const StudentIdSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, {
   message: 'Invalid studentId',
 })
-    if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) {
+    if (studentId && !StudentIdSchema.safeParse(studentId).success) {
       return NextResponse.json(
         { error: "Invalid studentId" },
         { status: 400 },
       );

Also applies to lines 38–43 (GET validation).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import mongoose from 'mongoose'
import { connectDB } from '@/lib/mongodb'
import { Attendance } from '@/models/Attendance'
import { z } from 'zod'
const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), {
message: 'Invalid studentId',
})
const AttendanceSchema = z.object({
studentId: z.string().min(1),
studentId: StudentIdSchema,
import { connectDB } from '@/lib/mongodb'
import { Attendance } from '@/models/Attendance'
import { z } from 'zod'
const StudentIdSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, {
message: 'Invalid studentId',
})
const AttendanceSchema = z.object({
studentId: StudentIdSchema,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/attendance/route.ts` around lines 3 - 13, Replace the loose ObjectId
check in StudentIdSchema with a strict validator (use
mongoose.isObjectIdOrHexString(value) or a 24-hex regex) so only 24-hex strings
are accepted; then reuse that StudentIdSchema in AttendanceSchema and in the GET
query validation (the GET guard that currently duplicates validation) to avoid
divergent checks—update the refine predicate on StudentIdSchema and replace the
duplicated GET validation logic to reference StudentIdSchema.

Comment on lines 60 to 82
await connectDB()
const existing = await Grade.findOne({ _id: id, teacherId: userId })
if (!existing) return NextResponse.json({ error: 'Not found' }, { status: 404 })

const marks = parsed.data.marks ?? existing.marks
const maxMarks = parsed.data.maxMarks ?? existing.maxMarks
if (marks > maxMarks) {
return NextResponse.json(
{ error: { fieldErrors: { marks: ['marks must be less than or equal to maxMarks'] } } },
{ status: 400 },
)
}

const grade = await Grade.findOneAndUpdate(
{ _id: id },
sanitizedBody,
{ new: true }
{ _id: id, teacherId: userId },
{
$set: {
...parsed.data,
grade: calcGrade(marks, maxMarks),
},
},
{ new: true, runValidators: true, context: 'query' }
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor TOCTOU between findOne and findOneAndUpdate.

The marks/maxMarks validation loads the existing doc (line 61), computes the merged values (64-65), validates (66-71), then issues findOneAndUpdate (73-82). A concurrent update landing in between could violate marks <= maxMarks on the final document. The model's pre('save') hook doesn't fire for findOneAndUpdate, so runValidators: true is your only safety net here, and the Grade schema (per relevant context) relies on the save hook for that invariant.

If concurrent grade edits are plausible, either (a) add a schema-level Mongoose validator that compares this.marks and this.maxMarks (so runValidators catches it), or (b) do a single atomic update using $expr / findOneAndUpdate with a compound filter that re-asserts the invariant. Otherwise this is an acceptable, narrow race.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/grades/`[id]/route.ts around lines 60 - 82, The code has a TOCTOU
between Grade.findOne and Grade.findOneAndUpdate: compute merged marks/maxMarks
from parsed.data and existing then validate, but a concurrent update can violate
marks <= maxMarks before findOneAndUpdate runs; either add a schema-level
validator on the Grade model that enforces marks <= maxMarks so runValidators:
true on Grade.findOneAndUpdate will catch violations, or make the update atomic
by moving the validation into the findOneAndUpdate filter (use a compound query
or $expr to require (parsed.data.marks ?? existing.marks) <=
(parsed.data.maxMarks ?? existing.maxMarks) in the query), and ensure calcGrade
is computed from the values in the same atomic update; update the code paths
that call Grade.findOne, Grade.findOneAndUpdate and any reliance on the
pre('save') hook (which does not run for findOneAndUpdate) to use the chosen
approach.

Comment thread app/api/grades/route.ts
Comment on lines +3 to +13
import mongoose from 'mongoose'
import { connectDB } from '@/lib/mongodb'
import { Grade } from '@/models/Grade'
import { z } from 'zod'

const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), {
message: 'Invalid studentId',
})

const GradeSchema = z.object({
studentId: z.string().min(1),
studentId: StudentIdSchema,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Mongoose 9, does mongoose.Types.ObjectId.isValid accept 12-character non-hex strings, and is mongoose.isObjectIdOrHexString the recommended stricter validator?

💡 Result:

Yes, in Mongoose 9, mongoose.Types.ObjectId.isValid accepts 12-character non-hex strings as valid, because it follows BSON ObjectId semantics where any 12-byte sequence (including 12-char strings) is valid. It returns true for strings like "zzzzzzzzzzzz" or "testtesttest". Yes, mongoose.isObjectIdOrHexString is the recommended stricter validator. It returns true only for ObjectId instances or exactly 24-character hex strings (matching /^[0-9a-fA-F]{24}$/), and false for 12-char strings, numbers, or other values. This is explicitly recommended by Mongoose maintainers in multiple GitHub issues for cases needing hex string validation, and the feature was added in Mongoose 6.2.5 with no breaking changes noted in Mongoose 9 changelogs.

Citations:


Replace mongoose.Types.ObjectId.isValid with mongoose.isObjectIdOrHexString for strict student ID validation.

mongoose.Types.ObjectId.isValid is too permissive and accepts 12-character non-hex strings (e.g., "zzzzzzzzzzzz"), which would pass validation and create grade records for malformed student IDs. mongoose.isObjectIdOrHexString is the recommended strict validator from Mongoose maintainers and only accepts ObjectId instances or exactly 24-character hex strings.

🛡️ Proposed fix
const StudentIdSchema = z.string().refine((value) => mongoose.isObjectIdOrHexString(value), {
   message: 'Invalid studentId',
 })
-    if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) {
+    if (studentId && !mongoose.isObjectIdOrHexString(studentId)) {
       return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 })
     }

Also applies to: 48-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/grades/route.ts` around lines 3 - 13, The StudentIdSchema currently
uses mongoose.Types.ObjectId.isValid which is too permissive; update the refine
predicate to use mongoose.isObjectIdOrHexString instead to ensure only ObjectId
instances or 24-character hex strings pass, and make the same replacement where
studentId validation occurs later in this file (the other validate calls around
the GradeSchema / any validation at lines ~48-50); keep the same error message
('Invalid studentId') and schema names (StudentIdSchema, GradeSchema) so only
the predicate changes.

subject: a.subject,
class: a.class,
deadline: a.deadline,
deadline: a.deadline.slice(0, 10),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Timezone shift when prefilling deadline from ISO string.

The API returns deadline as a full ISO 8601 UTC string (per models/Assignment.ts deadline: Date serialized via .lean()), so a.deadline.slice(0, 10) returns the UTC calendar date, not the user's local date. For users not on UTC, a deadline created locally as e.g. 2024-01-15 can be stored as 2024-01-14T18:30:00.000Z (IST) or 2024-01-15T05:00:00.000Z (EST), and prefilling the <input type="date"> via slice will show a day-off value, which then round-trips back to the server and silently shifts the deadline on every edit.

Use a timezone-aware format instead:

🛠️ Proposed fix
-      deadline: a.deadline.slice(0, 10),
+      deadline: (() => {
+        const d = new Date(a.deadline);
+        const y = d.getFullYear();
+        const m = String(d.getMonth() + 1).padStart(2, '0');
+        const day = String(d.getDate()).padStart(2, '0');
+        return `${y}-${m}-${day}`;
+      })(),

Or, if the intent is strictly to treat deadlines as UTC dates end-to-end, make that explicit both here (slicing OK) and when submitting from the modal (by normalizing to UTC on the server).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deadline: a.deadline.slice(0, 10),
deadline: (() => {
const d = new Date(a.deadline);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
})(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/dashboard/assignments/AssignmentsClient.tsx` at line 347, The code
currently sets deadline using the raw ISO substring "deadline:
a.deadline.slice(0, 10)" which uses the UTC calendar date and will shift for
non-UTC users; replace that slice with a timezone-aware local-date formatter by
parsing the ISO into a Date (new Date(a.deadline)) and building a YYYY-MM-DD
string from the Date's local getters (getFullYear(), getMonth()+1, getDate()) so
the <input type="date"> is prefilled with the user's local date; alternatively,
if you intend deadlines to be UTC end-to-end, make that explicit here and on
submit by normalizing to UTC rather than converting to local time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant