-
Notifications
You must be signed in to change notification settings - Fork 23
Fix/bughunt konstanty #14
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
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,32 +7,40 @@ export async function GET(req: NextRequest) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { searchParams } = new URL(req.url) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryUserId = searchParams.get('userId') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let userId: string | null = queryUserId | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. Identificazione utente | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const session = await auth() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const userId = queryUserId || session?.userId | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const session = await auth() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId = session.userId | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+15
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. Do not let Line 12 trusts 🔒 Proposed fix- const session = await auth()
- const userId = queryUserId || session?.userId
+ const { userId: sessionUserId } = await auth()
- if (!userId) {
+ if (!sessionUserId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
+
+ if (queryUserId && queryUserId !== sessionUserId) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
+ const userId = queryUserId ?? sessionUserId📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!clerkUser) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'User data not found' }, { status: 404 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newTeacher = await Teacher.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clerkId: userId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: clerkUser?.fullName ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: clerkUser?.emailAddresses[0]?.emailAddress ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: `${clerkUser.firstName ?? ''} ${clerkUser.lastName ?? ''}`.trim() || 'Anonymous', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: clerkUser.emailAddresses[0]?.emailAddress ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| department: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subjects: [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teacher = created.toObject() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teacher = JSON.parse(JSON.stringify(newTeacher)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
21
to
+38
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. Make first-profile creation atomic. The Prefer an atomic upsert or handle duplicate-key retry: 🛠️ Proposed direction- const newTeacher = await Teacher.create({
- clerkId: userId,
- name: `${clerkUser.firstName ?? ''} ${clerkUser.lastName ?? ''}`.trim() || 'Anonymous',
- email: clerkUser.emailAddresses[0]?.emailAddress ?? '',
- department: '',
- subjects: [],
- })
-
- teacher = JSON.parse(JSON.stringify(newTeacher))
+ teacher = await Teacher.findOneAndUpdate(
+ { clerkId: userId },
+ {
+ $setOnInsert: {
+ clerkId: userId,
+ name: `${clerkUser.firstName ?? ''} ${clerkUser.lastName ?? ''}`.trim() || 'Anonymous',
+ email: clerkUser.emailAddresses[0]?.emailAddress ?? '',
+ department: '',
+ subjects: [],
+ },
+ },
+ { new: true, upsert: true, setDefaultsOnInsert: true },
+ ).lean()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json(teacher) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('GET /api/profile error:', error instanceof Error ? error.message : error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('GET /api/profile error:', error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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
🌐 Web query:
Zod ZodError toJSON vs flatten() vs format() recommended error response shape💡 Result:
For ZodError error handling in Zod (stable v4 as of 2026), use these methods to shape responses: - error.toJSON: Serializes the ZodError to JSON, producing {name: 'ZodError', issues: [...]}, where issues is the raw array of ZodIssue objects (code, path, message, etc.). Suitable for simple API responses but retains full structured details. Note: JSON.stringify(error) uses this via the Error prototype's message getter, which stringifies issues. - error.flatten or z.flattenError(error): Recommended for flat schemas (most common, e.g., forms, top-level API objects). Returns {formErrors: string[], fieldErrors: {[key: string]: string[]}}, grouping top-level errors and per-field error arrays. Ideal for client-side form error display or simple API field-error responses. - error.format or z.formatError(error): Deprecated; use z.treeifyError(error) instead. treeifyError produces a nested object mirroring the schema structure: {errors: string[], properties: {...nested errors...}}. Best for deeply nested schemas. Recommended error response shape depends on use case: - For API responses (e.g., HTTP 400), use flattened shape for simplicity: {formErrors: [...], fieldErrors: {field1: [...], ...}}. Easy to parse, matches common patterns (React Hook Form, etc.), avoids deep nesting. - Raw error.toJSON if full issue details needed (codes, expected types). - treeifyError only for complex nested data. Official docs emphasize flatten for flat schemas (majority) and treeify for nested. Examples show flatten for form handling. GitHub issue confirms JSON serialization via issues array, with caveats for client reconstruction (use new ZodError(issues)). Examples: const flattened = error.flatten; // {formErrors: ['...'], fieldErrors: {username: ['...']}} res.status(400).json(flattened); // API response JSON.stringify(error); // '{"name":"ZodError","issues":[...]}'
Citations:
flatten()broken/useless when doingnew z.ZodError()with a JSON stringified/parsed ZodError coming from the server colinhacks/zod#2351🏁 Script executed:
Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 903
🏁 Script executed:
Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 121
🏁 Script executed:
rg "NextResponse.json.*error" app/api/grades/route.ts -A 1Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 629
🏁 Script executed:
rg "GradeSchema" app/api/grades/route.ts -B 5 -A 10Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 1257
🏁 Script executed:
Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 103
🏁 Script executed:
Repository: JavaScript-Mastery-Pro/discord-challenge
Length of output: 66
Validation error response uses raw ZodError instead of flattened shape.
Line 70 returns
parsed.errordirectly, which serializes to{name: 'ZodError', issues: [...]}and exposes Zod internals. This breaks consistency: every other error in this file uses a plain string ({ error: 'string' }), and this ZodError shape will confuse clients expecting a uniform error contract.Since
GradeSchemais flat (no nested objects), use.flatten()to produce{formErrors: string[], fieldErrors: {field: string[]}}, which is both client-friendly and the documented Zod best practice for flat schemas.♻️ Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents