Overview
The Admin API provides authenticated endpoints for managing and analyzing event registrations. All endpoints require authentication with an @nj.sgadi.us domain email address.
All admin endpoints verify user authentication and domain. Requests without proper credentials will return 401 (Unauthorized) or 403 (Forbidden) responses.
Authentication
Every admin endpoint follows this authentication pattern:
import { createClient } from "@/utils/supabase/server"
import { isAdminDomainUser } from "@/lib/admin-auth"
const supabase = await createClient ()
const { data : { user } } = await supabase . auth . getUser ()
if ( ! user ) {
return NextResponse . json (
{ error: "Unauthorized" , message: "Sign in required" },
{ status: 401 }
)
}
if ( ! isAdminDomainUser ( user )) {
return NextResponse . json (
{ error: "Forbidden" , message: "Admin domain (@nj.sgadi.us) required" },
{ status: 403 }
)
}
Get Registration Statistics
Retrieve aggregated statistics about registrations within a date range.
Endpoint
Query Parameters
start_date
string
default: "2026-07-23"
Start date (inclusive) in ISO format (YYYY-MM-DD)
end_date
string
default: "2026-08-08"
End date (inclusive) in ISO format (YYYY-MM-DD)
Response
Indicates successful operation
Statistics object returned by the get_registrations_stats RPC function
Implementation
app/api/admin/stats/route.ts
Example Request
Example Response
import { REGISTRATION_DATE_RANGE } from "@/lib/registration-date-range"
export async function GET ( request : Request ) {
const headers = new Headers ()
headers . set ( "Cache-Control" , "no-store, max-age=0" )
// ... authentication checks ...
const { searchParams } = new URL ( request . url )
const startDate = searchParams . get ( "start_date" ) ?? REGISTRATION_DATE_RANGE . start
const endDate = searchParams . get ( "end_date" ) ?? REGISTRATION_DATE_RANGE . end
const { data , error } = await supabase . rpc ( "get_registrations_stats" , {
p_start_date: startDate ,
p_end_date: endDate ,
})
if ( error ) {
return NextResponse . json (
{ error: "Stats query failed" , details: error . message },
{ status: 500 , headers }
)
}
return NextResponse . json (
{
success: true ,
stats: data ,
},
{ status: 200 , headers }
)
}
The stats endpoint calls the get_registrations_stats RPC function in Supabase, which performs read-only aggregations. No table writes occur.
Get Paginated Registrations
Retrieve paginated registration records with advanced filtering and search capabilities.
Endpoint
GET /api/admin/registrations
Number of records per page. Allowed values: 25, 50, 100
ID of the last record from the previous page (for keyset pagination)
Pagination direction: next or prev
Filter Parameters
Filter by exact ghaam name
Filter by exact mandal name
Earliest arrival date (YYYY-MM-DD, inclusive)
Latest arrival date (YYYY-MM-DD, inclusive)
Earliest departure date (YYYY-MM-DD, inclusive)
Latest departure date (YYYY-MM-DD, inclusive)
Text search across first name, last name, email, and mobile number. Minimum 2 characters per word.
Response
Indicates successful operation
Array of registration records
Number of records in current page
Cursor for the next page (null if no more pages)
Cursor for the previous page (null if on first page)
Whether more records exist after current page
Whether records exist before current page
Implementation
app/api/admin/registrations/route.ts
Example Request
Example Response
const PAGE_SIZES = [ 25 , 50 , 100 ] as const
export async function GET ( request : Request ) {
// ... authentication checks ...
const { searchParams } = new URL ( request . url )
const pageSize = parsePageSize ( searchParams . get ( "page_size" ))
const cursor = cursorRaw ? parseInt ( cursorRaw , 10 ) : null
const direction = searchParams . get ( "direction" ) === "prev" ? "prev" : "next"
// Parse filters
const ghaam = searchParams . get ( "ghaam" )?. trim () || null
const mandal = searchParams . get ( "mandal" )?. trim () || null
const country = searchParams . get ( "country" )?. trim () || null
const age = parseOptionalInt ( searchParams . get ( "age" ))
const ageMin = parseOptionalInt ( searchParams . get ( "age_min" ))
const ageMax = parseOptionalInt ( searchParams . get ( "age_max" ))
const search = searchRaw && searchRaw . length >= 2 ? searchRaw : null
if ( ageMin != null && ageMax != null && ageMin > ageMax ) {
return NextResponse . json (
{ error: "age_min must be <= age_max" },
{ status: 400 , headers }
)
}
const { data , error } = await supabase . rpc ( "get_registrations_filtered" , {
p_page_size: pageSize ,
p_cursor: cursor ,
p_direction: direction ,
p_ghaam: ghaam ,
p_mandal: mandal ,
p_country: country ,
p_age: age ,
p_age_min: ageMin ,
p_age_max: ageMax ,
p_arrival_from: arrivalFrom ,
p_arrival_to: arrivalTo ,
p_departure_from: departureFrom ,
p_departure_to: departureTo ,
p_search: search ,
})
return NextResponse . json ({
success: true ,
rows: result . rows ?? [],
pageSize: result . pageSize ?? pageSize ,
nextCursor: result . nextCursor ?? null ,
prevCursor: result . prevCursor ?? null ,
hasMore: result . hasMore ?? false ,
hasPrev: result . hasPrev ?? false ,
})
}
Keyset pagination is more efficient than offset-based pagination for large datasets. Always use the returned cursors for navigation.
Get Distinct Values
Retrieve distinct values for filter dropdowns (ghaam, mandal, country).
Endpoint
GET /api/admin/registrations/distinct
Response
Indicates successful operation
Array of unique ghaam values
Array of unique mandal values
Array of unique country values
Implementation
app/api/admin/registrations/distinct/route.ts
Example Request
Example Response
export async function GET () {
// ... authentication checks ...
const { data , error } = await supabase . rpc ( "get_registrations_distinct_values" )
if ( error ) {
return NextResponse . json (
{ error: "Query failed" , details: error . message },
{ status: 500 , headers }
)
}
return NextResponse . json (
{ success: true , ... ( data as object ) },
{ status: 200 , headers }
)
}
Test Read Endpoint
Test Supabase connectivity and RLS policies by reading a single registration record.
Endpoint
Description
Read-only test endpoint that returns the most recent registration record. Useful for verifying:
Supabase connection
Authentication flow
Row Level Security (RLS) policies
This endpoint does not write to any tables.
Response
Indicates successful operation
Description of the test result
Single registration record (most recent)
Number of rows returned (0 or 1)
Implementation
app/api/admin/test-read/route.ts
Example Request
Example Response
export async function GET () {
// ... authentication checks ...
const { data , error } = await supabase
. from ( "registrations" )
. select ( "id, first_name, last_name, email, ghaam, mandal, arrival_date, departure_date" )
. order ( "id" , { ascending: false })
. limit ( 1 )
. maybeSingle ()
if ( error ) {
return NextResponse . json (
{ error: "Query failed" , details: error . message },
{ status: 500 , headers }
)
}
return NextResponse . json (
{
success: true ,
message: "Read-only test: 1 row from registrations" ,
row: data ,
rowCount: data ? 1 : 0 ,
},
{ status: 200 , headers }
)
}
Error Responses
All admin endpoints return consistent error responses:
401 Unauthorized
403 Forbidden
400 Bad Request
500 Internal Server Error
{
"error" : "Unauthorized" ,
"message" : "Sign in required"
}
Cache Control
All admin endpoints set strict no-cache headers to ensure fresh data:
const headers = new Headers ()
headers . set ( "Cache-Control" , "no-store, max-age=0" )
Best Practices
Always use keyset pagination with cursors for large datasets
Store cursors from responses for navigation
Use appropriate page sizes based on UI needs
Filtering
Combine multiple filters for precise queries
Use text search with minimum 2 characters
Validate age ranges before submission
Cache distinct values for filter dropdowns
Use date range filters to limit result sets
Monitor query performance with larger datasets
Security
Never expose Supabase keys in client code
Always verify admin domain on server side
Log failed authentication attempts for monitoring