Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 0 additions & 82 deletions app/api/debug/profile/[username]/route.ts

This file was deleted.

49 changes: 49 additions & 0 deletions app/api/profile/[username]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';

export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ username: string }> }
) {
try {
const { username } = await params;

Comment on lines +4 to +10
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix Next.js route handler params typing (remove Promise/await).

params is not a Promise in App Router route handlers. Typing it as a Promise and awaiting it is incorrect and unnecessary.

Apply:

-export async function GET(
-  request: NextRequest,
-  { params }: { params: Promise<{ username: string }> }
-) {
+export async function GET(
+  request: NextRequest,
+  { params }: { params: { username: string } }
+) {
   try {
-    const { username } = await params;
+    const { username } = params;
📝 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
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ username: string }> }
) {
try {
const { username } = await params;
export async function GET(
request: NextRequest,
{ params }: { params: { username: string } }
) {
try {
const { username } = params;
// …rest of your handler…
🤖 Prompt for AI Agents
In app/api/profile/[username]/route.ts around lines 4 to 10, the handler types
`params` as a Promise and awaits it which is incorrect for Next.js App Router;
change the function signature to accept params as a plain object (e.g. { params:
{ username: string } }) and remove the await when destructuring so you directly
read const { username } = params; keep the rest of the handler logic unchanged.

if (!username) {
return NextResponse.json(
{ error: 'Username is required' },
{ status: 400 }
);
}
Comment on lines +11 to +16
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add basic username validation (length/charset) to harden the endpoint.

Guard early to avoid noisy queries and enforce your username contract.

Example patch:

   if (!username) {
     return NextResponse.json(
       { error: 'Username is required' },
       { status: 400 }
     );
   }
+  // Basic validation: 3–30 chars, alnum/underscore/dot
+  if (!/^[a-zA-Z0-9._]{3,30}$/.test(username)) {
+    return NextResponse.json(
+      { error: 'Invalid username format' },
+      { status: 400 }
+    );
+  }
📝 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
if (!username) {
return NextResponse.json(
{ error: 'Username is required' },
{ status: 400 }
);
}
if (!username) {
return NextResponse.json(
{ error: 'Username is required' },
{ status: 400 }
);
}
// Basic validation: 3–30 chars, alnum/underscore/dot
if (!/^[a-zA-Z0-9._]{3,30}$/.test(username)) {
return NextResponse.json(
{ error: 'Invalid username format' },
{ status: 400 }
);
}
🤖 Prompt for AI Agents
In app/api/profile/[username]/route.ts around lines 11 to 16, the handler
currently only checks presence of username; add basic validation to reject
usernames that don't meet length and charset rules (e.g. trim input, require
length between 3 and 30 characters and only allow a defined charset such as
lowercase/uppercase letters, digits, underscores and hyphens or your project's
chosen pattern). Implement a simple regex check against that charset and length,
returning NextResponse.json({ error: 'Invalid username' }, { status: 400 }) for
invalid values so the endpoint bails early before running queries.


// Use server-side Supabase client directly
const supabase = await createClient();
const { data: profile, error } = await supabase
.from('profiles')
.select('*')
.eq('username', username)
.eq('is_public', true)
.single();

if (error) {
if (error.code === 'PGRST116') {
return NextResponse.json(
{ error: 'Profile not found or not public' },
{ status: 404 }
);
}
console.error('Error fetching public profile:', error);
return NextResponse.json(
{ error: 'Failed to fetch profile' },
{ status: 500 }
);
}

return NextResponse.json({ profile });
Comment on lines +20 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid PII leakage: don’t select '*' on a public endpoint; whitelist safe public fields.

This route is publicly accessible yet returns the whole row (including email/phone if present). That leaks PII regardless of the UI’s selective rendering. Only return a curated set of public fields.

Apply:

-  const { data: profile, error } = await supabase
-    .from('profiles')
-    .select('*')
-    .eq('username', username)
-    .eq('is_public', true)
-    .single();
+  const { data: profile, error } = await supabase
+    .from('profiles')
+    .select(`
+      id,
+      username,
+      first_name,
+      last_name,
+      bio,
+      location,
+      current_position,
+      company,
+      github_url,
+      linkedin_url,
+      twitter_url,
+      skills,
+      is_public
+    `)
+    .eq('username', username)
+    .eq('is_public', true)
+    .maybeSingle();

And simplify not-found handling:

-  if (error) {
-    if (error.code === 'PGRST116') {
-      return NextResponse.json(
-        { error: 'Profile not found or not public' },
-        { status: 404 }
-      );
-    }
-    console.error('Error fetching public profile:', error);
-    return NextResponse.json(
-      { error: 'Failed to fetch profile' },
-      { status: 500 }
-    );
-  }
+  if (error) {
+    console.error('Error fetching public profile:', error);
+    return NextResponse.json({ error: 'Failed to fetch profile' }, { status: 500 });
+  }
+  if (!profile) {
+    return NextResponse.json({ error: 'Profile not found or not public' }, { status: 404 });
+  }
📝 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 { data: profile, error } = await supabase
.from('profiles')
.select('*')
.eq('username', username)
.eq('is_public', true)
.single();
if (error) {
if (error.code === 'PGRST116') {
return NextResponse.json(
{ error: 'Profile not found or not public' },
{ status: 404 }
);
}
console.error('Error fetching public profile:', error);
return NextResponse.json(
{ error: 'Failed to fetch profile' },
{ status: 500 }
);
}
return NextResponse.json({ profile });
// Fetch only safe public fields for the given username
const { data: profile, error } = await supabase
.from('profiles')
.select(`
id,
username,
first_name,
last_name,
bio,
location,
current_position,
company,
github_url,
linkedin_url,
twitter_url,
skills,
is_public
`)
.eq('username', username)
.eq('is_public', true)
.maybeSingle();
// Handle real errors
if (error) {
console.error('Error fetching public profile:', error);
return NextResponse.json(
{ error: 'Failed to fetch profile' },
{ status: 500 }
);
}
// Handle "not found or not public"
if (!profile) {
return NextResponse.json(
{ error: 'Profile not found or not public' },
{ status: 404 }
);
}
return NextResponse.json({ profile });

} catch (error) {
console.error('Error fetching public profile:', error);
return NextResponse.json(
{ error: 'Failed to fetch profile' },
{ status: 500 }
);
}
}
123 changes: 123 additions & 0 deletions app/api/tests/register/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { NextRequest, NextResponse } from 'next/server'
import { createClient as createServiceClient } from '@supabase/supabase-js'

export async function POST(request: NextRequest) {
try {
const { testId, userId, userEmail, userMetadata } = await request.json()

if (!testId || !userId) {
return NextResponse.json(
{ error: 'Test ID and User ID are required' },
{ status: 400 }
)
}
Comment on lines +4 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate input with a schema and return precise status codes.

Add a Zod (or similar) schema to validate testId, userId, userEmail, userMetadata. Use 422 for validation errors.

+import { z } from 'zod'
+
 export async function POST(request: NextRequest) {
   try {
-    const { testId, userId, userEmail, userMetadata } = await request.json()
+    const body = await request.json()
+    const { testId, userId, userEmail, userMetadata } = z.object({
+      testId: z.string().min(1),
+      userId: z.string().min(1),
+      userEmail: z.string().email().optional(),
+      userMetadata: z.record(z.any()).optional(),
+    }).parse(body)
 
-    if (!testId || !userId) {
-      return NextResponse.json(
-        { error: 'Test ID and User ID are required' },
-        { status: 400 }
-      )
-    }
+    // continue...
📝 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
export async function POST(request: NextRequest) {
try {
const { testId, userId, userEmail, userMetadata } = await request.json()
if (!testId || !userId) {
return NextResponse.json(
{ error: 'Test ID and User ID are required' },
{ status: 400 }
)
}
import { z } from 'zod'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { testId, userId, userEmail, userMetadata } = z.object({
testId: z.string().min(1),
userId: z.string().min(1),
userEmail: z.string().email().optional(),
userMetadata: z.record(z.any()).optional(),
}).parse(body)
// continue...
🤖 Prompt for AI Agents
In app/api/tests/register/route.ts around lines 4 to 13, the endpoint currently
performs ad-hoc checks for testId and userId; replace that with a Zod schema
validating testId, userId, userEmail, and userMetadata (types and any
required/optional rules), parse the incoming JSON using schema.safeParse (or
parseAsync) and, on validation failure, return NextResponse.json with the
validation errors and status 422; on success, use the validated values from the
schema result for the rest of the handler.


// Use service role client to bypass RLS
const serviceSupabase = createServiceClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)

Comment on lines +15 to +20
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard env vars early; fail fast if misconfigured.

Non-null assertions won’t protect at runtime.

-    const serviceSupabase = createServiceClient(
-      process.env.NEXT_PUBLIC_SUPABASE_URL!,
-      process.env.SUPABASE_SERVICE_ROLE_KEY!
-    )
+    const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL
+    const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY
+    if (!SUPABASE_URL || !SERVICE_KEY) {
+      console.error('Supabase env vars missing')
+      return NextResponse.json({ error: 'Server misconfiguration' }, { status: 500 })
+    }
+    const serviceSupabase = createServiceClient(SUPABASE_URL, SERVICE_KEY)
📝 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
// Use service role client to bypass RLS
const serviceSupabase = createServiceClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
// Use service role client to bypass RLS
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL
const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!SUPABASE_URL || !SERVICE_KEY) {
console.error('Supabase env vars missing')
return NextResponse.json({ error: 'Server misconfiguration' }, { status: 500 })
}
const serviceSupabase = createServiceClient(SUPABASE_URL, SERVICE_KEY)
🤖 Prompt for AI Agents
In app/api/tests/register/route.ts around lines 15 to 20, the code uses non-null
assertions on NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY which do
not protect at runtime; add an explicit guard at the top of this module or just
before createServiceClient that checks both environment variables and throws a
clear Error if either is missing (e.g. “Missing SUPABASE env var:
NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY”), and then call
createServiceClient without non-null assertions so the function receives
validated strings.

// Check if already registered
const { data: existingRegistration } = await serviceSupabase
.from('test_registrations')
.select('id')
.eq('test_id', testId)
.eq('user_id', userId)
.single()

if (existingRegistration) {
return NextResponse.json(
{ error: 'User is already registered for this test' },
{ status: 400 }
)
}

// Ensure profile exists to prevent trigger failure
try {
const { error: profileError } = await serviceSupabase
.from('profiles')
.select('id, first_name, last_name, email')
.eq('id', userId)
.single()

if (profileError && profileError.code === 'PGRST116') {
// Profile doesn't exist, create a minimal one
const { error: createError } = await serviceSupabase
.from('profiles')
.insert({
id: userId,
email: userEmail,
first_name: userMetadata?.first_name || userMetadata?.given_name || '',
last_name: userMetadata?.last_name || userMetadata?.family_name || '',
is_public: true,
email_notifications: true,
profile_completion_percentage: 0,
is_admin: false,
username_editable: true,
username_set: false,
profile_complete: false
})

if (createError) {
console.error('Error creating profile:', createError)
// Continue anyway, the trigger might still work
}
}
} catch (profileException) {
console.error('Exception during profile check/creation:', profileException)
// Continue with registration
}

// Prepare registration data with proper fallbacks for the trigger
const fullName = userMetadata?.full_name ||
userMetadata?.name ||
(userMetadata?.first_name && userMetadata?.last_name ?
`${userMetadata.first_name} ${userMetadata.last_name}` :
null);

// Register for the test
const { data, error } = await serviceSupabase
.from('test_registrations')
.insert([{
test_id: testId,
user_id: userId,
status: 'registered',
attempt_count: 0,
registration_date: new Date().toISOString(),
full_name: fullName,
email: userEmail || null,
phone: null,
institution: null,
department: null,
year_of_study: null,
experience_level: null,
registration_data: {
registered_via: 'api_tests_register',
registration_timestamp: new Date().toISOString(),
user_metadata: userMetadata
}
}])
.select()

if (error) {
console.error('Registration error:', error)
return NextResponse.json(
{ error: 'Failed to register for test', details: error.message },
{ status: 500 }
)
}

return NextResponse.json({
success: true,
data: data[0]
})

} catch (error) {
console.error('Error in test registration API:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
Loading
Loading