Skip to content

Fix auth, validation, and dashboard stats bugs#3

Open
notedwin-dev wants to merge 25 commits intoJavaScript-Mastery-Pro:mainfrom
notedwin-dev:fix/bughunt-notedwin-dev
Open

Fix auth, validation, and dashboard stats bugs#3
notedwin-dev wants to merge 25 commits intoJavaScript-Mastery-Pro:mainfrom
notedwin-dev:fix/bughunt-notedwin-dev

Conversation

@notedwin-dev
Copy link
Copy Markdown

@notedwin-dev notedwin-dev commented Apr 18, 2026

Summary

  • Enforced ownership checks for mutation routes so teachers can only modify their own records.
  • Required authenticated profile reads and removed insecure profile lookup behavior.
  • Fixed student-create validation so invalid payloads correctly return 400.
  • Repaired grade create/update flow to await writes, apply defaults, and recalculate grades safely.
  • Removed internal stack-trace leakage from API responses.
  • Deferred MongoDB env validation to runtime connection to avoid eager import/build failures.
  • Migrated to Proxy for Next 16 compatibility and stabilized navbar theme initialization.
  • Aligned route update whitelists with schemas (removed non-schema fields).
  • Fixed grading threshold so 90% is correctly treated as A+.

Verification

  • npm run lint
  • npx tsc --noEmit
  • npm run build

Summary by CodeRabbit

  • Bug Fixes

    • Error responses now return consistent messages instead of exposing internal details
    • Assignment and grade updates now properly validate request status
    • Empty bulk attendance payloads are rejected with appropriate error messaging
  • New Features

    • Grade calculations now computed server-side for improved accuracy
    • Stricter ownership validation for content updates and deletions
  • Improvements

    • CGPA calculation adjusted—grade D now contributes 4 points instead of 5
    • Dashboard statistics now rely on server-provided totals for better accuracy
    • Enhanced input validation across assignments, grades, and student management

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

The changes introduce authorization scoping to API endpoints, restrict field-level updates by removing sensitive or computed fields from whitelists, implement server-side grade calculation logic, standardize error handling to exclude stack traces, add input validation for bulk operations, and adjust grade-to-point weightings in CGPA calculations.

Changes

Cohort / File(s) Summary
API Authorization & Field Restrictions
app/api/announcements/[id]/route.ts, app/api/assignments/[id]/route.ts, app/api/students/[id]/route.ts
Added teacherId: userId ownership checks to PUT and DELETE operations. Removed sensitive/computed fields (body, expiresAt, dueDate, grade) from update whitelists to prevent client-side modification.
Grade Calculation & Validation
app/api/grades/[id]/route.ts, app/api/grades/route.ts
Added server-side calcGrade() function computing grades from marks/maxMarks. Removed grade from allowed update fields; adjusted schema to default maxMarks to 100; changed grade boundary (90% → A+). Added strict validation for numeric coercion and marks/maxMarks relationships.
API Error Handling & Input Validation
app/api/assignments/route.ts, app/api/attendance/route.ts, app/api/students/route.ts, app/api/profile/route.ts
Standardized 500 error responses to return fixed { error: 'Internal server error' } instead of exposing error stacks. Added validation for empty bulk attendance payloads (400 error) and POST student payloads (schema validation with error flattening). Tightened profile authorization to reject mismatched userId access (403).
Dashboard Stats & UI Adjustments
app/dashboard/OverviewClient.tsx, app/dashboard/assignments/AssignmentsClient.tsx, app/dashboard/grades/GradesClient.tsx, app/dashboard/students/StudentsClient.tsx
Changed dashboard stats calculation to prefer server-provided total fields over client-side filtering. Adjusted date/maxMarks normalization in assignment and grade forms. Updated grade D point value from 5 to 4 in CGPA calculations. Added HTTP status validation on assignment move operation.
Dark Mode & Configuration
components/dashboard/Navbar.tsx, lib/mongodb.ts, next.config.ts, proxy.ts
Refactored dark mode initialization to use lazy state from existing <html> class attribute instead of localStorage/matchMedia. Extracted MongoDB URI validation into getMongoUri() helper. Updated next.config.ts quote style to double quotes. Fixed missing newline in proxy.ts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 With whiskers twitched and paws held high,
We now own what's truly nigh,
Grades compute on server's call,
And D's worth less, yet guides us all!
Authorization guards the day—
No secrets leak, hooray, hooray! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix auth, validation, and dashboard stats bugs' accurately reflects the main objectives: it addresses authentication enforcement, validation improvements, and dashboard statistics fixes across multiple endpoints and components.

✏️ 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.

## Summary
- Enforce ownership and auth checks for updates/deletes and profile access; validate student creation and grade updates.
- Harden grade creation/update defaults (term/maxMarks), prevent manual grade overrides, and align CGPA point mapping.
- Improve UX and data integrity: dashboard totals, assignment move rollback, edit date normalization, maxMarks form handling, and attendance bulk validation.
- Remove stack-trace leaks from API error responses.

## Testing
- Not run (not requested)
@notedwin-dev notedwin-dev changed the title Default empty grade term Fix auth, validation, and dashboard stats bugs Apr 18, 2026
@notedwin-dev
Copy link
Copy Markdown
Author

@CodeRabbit please try again...

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

@notedwin-dev Sure! I'll go ahead and review the PR again now.

✅ Actions performed

Full review triggered.

@notedwin-dev
Copy link
Copy Markdown
Author

@CodeRabbit please do a full code review and try to find potential security issues as well as uncaught potential bugs

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

@notedwin-dev Sure! I'll perform a full code review with a focus on security vulnerabilities and potential bugs right away.

✅ Actions performed

Full review triggered.

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: 3

🧹 Nitpick comments (1)
app/api/grades/[id]/route.ts (1)

7-18: Extract calcGrade into a shared util to avoid drift.

calcGrade is now defined identically here and in app/api/grades/route.ts. If thresholds change again (as they did in this PR for A+), keeping both in sync is error-prone. Consider extracting to e.g. lib/grading.ts and importing from both routes.

♻️ Proposed extraction
// lib/grading.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:

import { calcGrade } from '@/lib/grading'
🤖 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 7 - 18, The calcGrade function is
duplicated and should be extracted to a shared utility to avoid drift; create a
new module (e.g., lib/grading.ts) that exports calcGrade(marks: number, max:
number): string using the existing threshold logic, then replace the local
calcGrade definition in this file and the other route that duplicates it by
importing calcGrade (e.g., import { calcGrade } from '@/lib/grading') so both
routes use the single implementation.
🤖 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/grades/route.ts`:
- Around line 72-81: The code currently defaults an empty term to "Term 1" which
lets upserts (Grade.findOneAndUpdate keyed by teacherId, studentId, subject,
term) silently collide; instead make term required in the request validation
(change the Zod schema that produces parsed.data so term is z.string().min(1)),
stop applying a silent default (remove the `term = data.term?.trim() || 'Term
1'` fallback), and return a 400 Bad Request when parsed.data.term is
missing/empty before calling Grade.findOneAndUpdate; this ensures clients must
supply term and prevents accidental overwrites of existing records.

In `@app/dashboard/OverviewClient.tsx`:
- Around line 198-224: The upcomingDeadlines calculation currently uses the
paginated assignments list from assignmentsData (fetched via
fetch("/api/assignments") which defaults to limit=20) and can miss active items
beyond page 1; change the data source so deadlines are computed from the active
set: either modify the initial fetch to request the active set with a
sufficiently large limit (e.g.,
fetch("/api/assignments?status=active&limit=100")) or reuse
assignmentsActiveRes/assignmentsActiveData and derive upcomingDeadlines from
assignmentsActiveData.assignments (or perform an additional fetch for active
assignments) so that upcomingDeadlines uses the full active list rather than the
mixed paginated assignments array.

In `@components/dashboard/Navbar.tsx`:
- Around line 11-14: The current lazy useState initializer for dark reads
document during initial render causing SSR/CSR mismatch; change the initial
state to false (useState(false)), add a mounted flag (e.g., const [mounted,
setMounted] = useState(false)) and in useEffect on mount call
setDark(document.documentElement.classList.contains("dark")) then
setMounted(true); finally, guard the theme-dependent UI (the icon/title
rendering that reads dark) to only render when mounted (or render a neutral
placeholder) so SSR markup matches the first client paint; update references to
dark and setDark accordingly.

---

Nitpick comments:
In `@app/api/grades/`[id]/route.ts:
- Around line 7-18: The calcGrade function is duplicated and should be extracted
to a shared utility to avoid drift; create a new module (e.g., lib/grading.ts)
that exports calcGrade(marks: number, max: number): string using the existing
threshold logic, then replace the local calcGrade definition in this file and
the other route that duplicates it by importing calcGrade (e.g., import {
calcGrade } from '@/lib/grading') so both routes use the single implementation.
🪄 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: 97e89224-a866-477d-a120-59593b564979

📥 Commits

Reviewing files that changed from the base of the PR and between c71cbbf and 0201d54.

📒 Files selected for processing (17)
  • 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
  • app/dashboard/grades/GradesClient.tsx
  • app/dashboard/students/StudentsClient.tsx
  • components/dashboard/Navbar.tsx
  • lib/mongodb.ts
  • next.config.ts
  • proxy.ts

Comment thread app/api/grades/route.ts
Comment on lines 72 to 81
const data = parsed.data
const max = data.maxMarks!
const term = data.term ?? 'Term 1'
const maxMarks = data.maxMarks
const term = data.term?.trim() || '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 }
{ $set: { ...data, term, maxMarks, teacherId: userId, grade: calcGrade(data.marks, maxMarks) } },
{ upsert: true, new: true, runValidators: true, context: 'query' }
)
return NextResponse.json(grade, { status: 201 })
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

Empty-term default + upsert can silently overwrite a prior record.

With term defaulted to "Term 1" and the upsert keyed on {teacherId, studentId, subject, term}, two submissions without a term for the same (student, subject) will collide and the second one will overwrite the first under Term 1. If clients commonly omit the term (the client already defaults term to "Term 1" in openAdd, so this is usually fine, but direct API users may not), consider either:

  • Requiring term explicitly at the schema level (z.string().min(1)) and returning 400 if missing, or
  • Returning 200 vs 201 based on whether the upsert created a new document (via rawResult: true / includeResultMetadata: true) so clients can distinguish create from update.

Minor: the response always returns 201 Created even when the upsert merely updates an existing document.

🤖 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 72 - 81, The code currently defaults an
empty term to "Term 1" which lets upserts (Grade.findOneAndUpdate keyed by
teacherId, studentId, subject, term) silently collide; instead make term
required in the request validation (change the Zod schema that produces
parsed.data so term is z.string().min(1)), stop applying a silent default
(remove the `term = data.term?.trim() || 'Term 1'` fallback), and return a 400
Bad Request when parsed.data.term is missing/empty before calling
Grade.findOneAndUpdate; this ensures clients must supply term and prevents
accidental overwrites of existing records.

Comment on lines 198 to +224
fetch("/api/assignments"),
fetch("/api/assignments?status=active&limit=1"),
fetch("/api/attendance"),
fetch("/api/grades"),
fetch("/api/announcements?limit=5"),
]);

const [students, assignmentsData, attendance, grades, announcements] =
await Promise.all([
studentsRes.json(),
assignmentsRes.json(),
attendanceRes.json(),
gradesRes.json(),
announcementsRes.json(),
]);
const [
students,
assignmentsData,
assignmentsActiveData,
attendance,
grades,
announcements,
] = await Promise.all([
studentsRes.json(),
assignmentsRes.json(),
assignmentsActiveRes.json(),
attendanceRes.json(),
gradesRes.json(),
announcementsRes.json(),
]);

const assignments = assignmentsData.assignments ?? assignmentsData;
const assignmentsTotal = assignmentsData.total ?? assignments.length;
const pendingAssignments = assignmentsActiveData.total ??
(assignmentsActiveData.assignments ?? assignmentsActiveData ?? []).length;
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

LGTM on the count fix — but upcomingDeadlines still relies on the paginated list.

assignmentsTotal and pendingAssignments are now correctly derived from server-side total. However, the same assignmentsData.assignments array (line 221) is later used at lines 310–328 to compute upcomingDeadlines, and fetch('/api/assignments') (line 198) uses the server default of limit=20. For teachers whose newest 20 records include closed assignments, active items beyond page 1 will silently disappear from the "Upcoming Deadlines" panel.

Consider either fetching with status=active (and a larger limit) specifically for the deadlines panel, or reusing assignmentsActiveRes with an appropriate limit:

🔧 Suggested adjustment
-        fetch("/api/assignments"),
-        fetch("/api/assignments?status=active&limit=1"),
+        fetch("/api/assignments?limit=100"),
+        fetch("/api/assignments?status=active&limit=100"),

Then derive upcomingDeadlines from assignmentsActiveData.assignments instead of the mixed list.

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

In `@app/dashboard/OverviewClient.tsx` around lines 198 - 224, The
upcomingDeadlines calculation currently uses the paginated assignments list from
assignmentsData (fetched via fetch("/api/assignments") which defaults to
limit=20) and can miss active items beyond page 1; change the data source so
deadlines are computed from the active set: either modify the initial fetch to
request the active set with a sufficiently large limit (e.g.,
fetch("/api/assignments?status=active&limit=100")) or reuse
assignmentsActiveRes/assignmentsActiveData and derive upcomingDeadlines from
assignmentsActiveData.assignments (or perform an additional fetch for active
assignments) so that upcomingDeadlines uses the full active list rather than the
mixed paginated assignments array.

Comment on lines +11 to +14
const [dark, setDark] = useState(() => {
if (typeof document === "undefined") return false;
return document.documentElement.classList.contains("dark");
});
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

Potential hydration mismatch from reading document during initial render.

The lazy initializer runs on both server and client: on the server it returns false, while on the client it reflects the dark class already set by the layout's theme init script. When a user's stored/system preference is dark, the server-rendered markup (icon at lines 64–92, title at line 62) will differ from the first client render, triggering a React hydration warning and a brief visual flip.

Consider initializing to false and deriving the real value in a mount effect, or gating the toggle UI behind a mounted flag so SSR and first client paint agree.

♻️ Suggested pattern
-  const [dark, setDark] = useState(() => {
-    if (typeof document === "undefined") return false;
-    return document.documentElement.classList.contains("dark");
-  });
+  const [dark, setDark] = useState(false);
+  const [mounted, setMounted] = useState(false);
+
+  useEffect(() => {
+    setDark(document.documentElement.classList.contains("dark"));
+    setMounted(true);
+  }, []);

Then guard the icon render on mounted (or render a neutral placeholder) to avoid the mismatch.

📝 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
const [dark, setDark] = useState(() => {
if (typeof document === "undefined") return false;
return document.documentElement.classList.contains("dark");
});
const [dark, setDark] = useState(false);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setDark(document.documentElement.classList.contains("dark"));
setMounted(true);
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/dashboard/Navbar.tsx` around lines 11 - 14, The current lazy
useState initializer for dark reads document during initial render causing
SSR/CSR mismatch; change the initial state to false (useState(false)), add a
mounted flag (e.g., const [mounted, setMounted] = useState(false)) and in
useEffect on mount call
setDark(document.documentElement.classList.contains("dark")) then
setMounted(true); finally, guard the theme-dependent UI (the icon/title
rendering that reads dark) to only render when mounted (or render a neutral
placeholder) so SSR markup matches the first client paint; update references to
dark and setDark accordingly.

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