From bc2cd18e21268c6b615b5f39b8d15e5bb9aa9c52 Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 19 Nov 2025 12:30:53 +0530 Subject: [PATCH 1/2] feat(registrations): Simplify CSV export columns and improve date formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary columns (Institution, Department, Year of Study, Experience Level, Registration Date) from CSV exports for both events and hackathons - Consolidate date columns into single "Registered On" field with human-readable format (e.g., "Nov 19 2025") - Format payment amounts with currency symbol (₹) and convert from cents to rupees - Display "N/A" for missing payment amounts instead of empty values - Update hackathons export endpoint to use service role client for bypassing RLS on master_registrations table - Separate authentication check (server client) from data querying (service role client) in hackathons export - Streamline CSV output to focus on essential registration information for better readability and usability --- .../[slug]/registrations/export/route.ts | 25 +++++---- .../[id]/registrations/export/route.ts | 53 ++++++++++++------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/app/api/events/[slug]/registrations/export/route.ts b/app/api/events/[slug]/registrations/export/route.ts index 314f88bd..5edc493b 100644 --- a/app/api/events/[slug]/registrations/export/route.ts +++ b/app/api/events/[slug]/registrations/export/route.ts @@ -73,34 +73,33 @@ export async function GET( 'Full Name', 'Email', 'Phone', - 'Institution', - 'Department', - 'Year of Study', - 'Experience Level', 'Status', 'Payment Status', 'Payment Amount', - 'Registration Date', - 'Created At' + 'Registered On' ]; const csvRows = [headers.join(',')]; registrations?.forEach(reg => { + // Format date to be more readable (e.g., "Nov 19 2025") + const registeredDate = reg.created_at + ? new Date(reg.created_at).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }).replace(/,/g, '') + : ''; + const row = [ reg.id, `"${reg.full_name || ''}"`, `"${reg.email || ''}"`, `"${reg.phone || ''}"`, - `"${reg.institution || ''}"`, - `"${reg.department || ''}"`, - `"${reg.year_of_study || ''}"`, - `"${reg.experience_level || ''}"`, reg.status, reg.payment_status, - reg.payment_amount || '', - reg.registration_date, - reg.created_at + reg.payment_amount ? `₹${reg.payment_amount / 100}` : 'N/A', + `"${registeredDate}"` ]; csvRows.push(row.join(',')); }); diff --git a/app/api/hackathons/[id]/registrations/export/route.ts b/app/api/hackathons/[id]/registrations/export/route.ts index 5292b37b..4408ec22 100644 --- a/app/api/hackathons/[id]/registrations/export/route.ts +++ b/app/api/hackathons/[id]/registrations/export/route.ts @@ -1,8 +1,23 @@ import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; +import { createClient as createServerClient } from '@/lib/supabase/server'; +import { createClient } from '@supabase/supabase-js'; export const runtime = 'nodejs'; +// Create Supabase client with service role key to bypass RLS for master_registrations +const getServiceRoleClient = () => { + return createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY!, + { + auth: { + autoRefreshToken: false, + persistSession: false + } + } + ); +}; + // GET: Export registrations as CSV export async function GET( request: NextRequest, @@ -10,10 +25,10 @@ export async function GET( ) { try { const { id } = await params; - const supabase = await createClient(); - - // Get the current user - const { data: { user }, error: authError } = await supabase.auth.getUser(); + + // Use server client for authentication + const serverClient = await createServerClient(); + const { data: { user }, error: authError } = await serverClient.auth.getUser(); if (authError || !user) { return NextResponse.json( @@ -22,6 +37,9 @@ export async function GET( ); } + // Use service role client for querying (bypasses RLS) + const supabase = getServiceRoleClient(); + // Get the hackathon by slug const { data: hackathon, error: hackathonError } = await supabase .from('hackathons') @@ -73,34 +91,33 @@ export async function GET( 'Full Name', 'Email', 'Phone', - 'Institution', - 'Department', - 'Year of Study', - 'Experience Level', 'Status', 'Payment Status', 'Payment Amount', - 'Registration Date', - 'Created At' + 'Registered On' ]; const csvRows = [headers.join(',')]; registrations?.forEach(reg => { + // Format date to be more readable (e.g., "Nov 19 2025") + const registeredDate = reg.created_at + ? new Date(reg.created_at).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }).replace(/,/g, '') + : ''; + const row = [ reg.id, `"${reg.full_name || ''}"`, `"${reg.email || ''}"`, `"${reg.phone || ''}"`, - `"${reg.institution || ''}"`, - `"${reg.department || ''}"`, - `"${reg.year_of_study || ''}"`, - `"${reg.experience_level || ''}"`, reg.status, reg.payment_status, - reg.payment_amount || '', - reg.registration_date, - reg.created_at + reg.payment_amount ? `₹${reg.payment_amount / 100}` : 'N/A', + `"${registeredDate}"` ]; csvRows.push(row.join(',')); }); From 0288ccb49772b07ae1205262e18e5917c56e9f74 Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 19 Nov 2025 12:49:32 +0530 Subject: [PATCH 2/2] fix(events): Move search filter to post-enrichment stage for accurate results - Remove search filter from initial database query before data enrichment - Implement search filtering after enriching registrations with profile data - Add support for searching by profile_name in addition to full_name, email, and phone - Update total count calculation to reflect filtered results when search is applied - Improve search accuracy by performing case-insensitive matching on enriched data --- app/api/events/[slug]/registrations/route.ts | 26 ++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/app/api/events/[slug]/registrations/route.ts b/app/api/events/[slug]/registrations/route.ts index 28adb264..9c02001d 100644 --- a/app/api/events/[slug]/registrations/route.ts +++ b/app/api/events/[slug]/registrations/route.ts @@ -94,11 +94,6 @@ export async function GET( query = query.eq('payment_status', paymentStatus); } - // Apply search (search in full_name, email, phone) - if (search) { - query = query.or(`full_name.ilike.%${search}%,email.ilike.%${search}%,phone.ilike.%${search}%`); - } - // Apply pagination query = query.range(offset, offset + limit - 1); @@ -155,9 +150,26 @@ export async function GET( }; }); + // Apply search filter after enrichment (search in profile_name, full_name, email, phone) + let filteredRegistrations = enrichedRegistrations || []; + if (search) { + const searchLower = search.toLowerCase(); + filteredRegistrations = filteredRegistrations.filter(reg => { + const profileName = reg.profile_name?.toLowerCase() || ''; + const fullName = reg.full_name?.toLowerCase() || ''; + const email = reg.email?.toLowerCase() || ''; + const phone = reg.phone?.toLowerCase() || ''; + + return profileName.includes(searchLower) || + fullName.includes(searchLower) || + email.includes(searchLower) || + phone.includes(searchLower); + }); + } + return NextResponse.json({ - registrations: enrichedRegistrations || [], - total: count || 0, + registrations: filteredRegistrations, + total: search ? filteredRegistrations.length : (count || 0), event: { id: event.id, title: event.title,