import { NextResponse } from "next/server"; import { ApiSuccessResponse, ApiErrorResponse, PaginatedResponse, ResponseMeta, PaginationMeta, ApiErrorCode, } from "./types"; /** * API Response Helpers for FitAI Admin API * * Provides consistent response formatting across all API endpoints. */ /** * Generate a unique request ID */ function generateRequestId(): string { return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; } /** * Create response metadata */ function createMeta(options?: { requestId?: string; pagination?: PaginationMeta; }): ResponseMeta { return { timestamp: new Date().toISOString(), requestId: options?.requestId || generateRequestId(), ...(options?.pagination && { pagination: options.pagination }), }; } /** * Create a standardized success response * * @param data - The response data * @param options - Optional metadata configuration * @returns NextResponse with standardized format * * @example * return successResponse({ user: { id: '123', email: 'user@example.com' } }); * * @example * return successResponse({ message: 'User created' }, { status: 201 }); */ export function successResponse( data: T, options?: { status?: number; requestId?: string; headers?: HeadersInit; }, ): NextResponse> { const response: ApiSuccessResponse = { success: true, data, meta: createMeta({ requestId: options?.requestId }), }; return NextResponse.json(response, { status: options?.status || 200, headers: options?.headers, }); } /** * Create a standardized paginated response * * @param data - Array of items to return * @param pagination - Pagination metadata * @param options - Optional configuration * @returns NextResponse with paginated format * * @example * const users = await db.getUsers(); * return paginatedResponse(users, { * page: 1, * pageSize: 20, * totalItems: 100, * totalPages: 5, * hasNextPage: true, * hasPreviousPage: false, * }); */ export function paginatedResponse( data: T[], pagination: PaginationMeta, options?: { requestId?: string; headers?: HeadersInit; }, ): NextResponse> { const response: PaginatedResponse = { success: true, data, meta: { ...createMeta({ requestId: options?.requestId }), pagination, }, }; return NextResponse.json(response, { status: 200, headers: options?.headers, }); } /** * Create a standardized error response * * @param message - Error message * @param options - Error configuration * @returns NextResponse with error format * * @example * return errorResponse('User not found', { * status: 404, * code: ApiErrorCode.NOT_FOUND, * }); * * @example * return errorResponse('Validation failed', { * status: 400, * code: ApiErrorCode.VALIDATION_ERROR, * details: { email: ['Invalid email format'] }, * }); */ export function errorResponse( message: string, options?: { status?: number; code?: ApiErrorCode | string; details?: Record; requestId?: string; headers?: HeadersInit; }, ): NextResponse { const response: ApiErrorResponse = { success: false, error: { message, code: options?.code, details: options?.details, }, meta: createMeta({ requestId: options?.requestId }), }; return NextResponse.json(response, { status: options?.status || 500, headers: options?.headers, }); } /** * Create a 400 Bad Request response */ export function badRequestResponse( message: string = "Bad request", details?: Record, headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 400, code: ApiErrorCode.INVALID_INPUT, details, headers, }); } /** * Create a 401 Unauthorized response */ export function unauthorizedResponse( message: string = "Unauthorized", headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 401, code: ApiErrorCode.UNAUTHORIZED, headers, }); } /** * Create a 403 Forbidden response */ export function forbiddenResponse( message: string = "Forbidden", headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 403, code: ApiErrorCode.FORBIDDEN, headers, }); } /** * Create a 404 Not Found response */ export function notFoundResponse( message: string = "Resource not found", headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 404, code: ApiErrorCode.NOT_FOUND, headers, }); } /** * Create a 409 Conflict response */ export function conflictResponse( message: string = "Resource conflict", headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 409, code: ApiErrorCode.CONFLICT, headers, }); } /** * Create a 500 Internal Server Error response */ export function internalErrorResponse( message: string = "Internal server error", headers?: HeadersInit, ): NextResponse { return errorResponse(message, { status: 500, code: ApiErrorCode.INTERNAL_ERROR, headers, }); } /** * Calculate pagination metadata from array and page parameters * * @param totalItems - Total number of items * @param page - Current page number (1-indexed) * @param pageSize - Number of items per page * @returns Pagination metadata * * @example * const pagination = calculatePagination(100, 1, 20); * // { page: 1, pageSize: 20, totalItems: 100, totalPages: 5, hasNextPage: true, hasPreviousPage: false } */ export function calculatePagination( totalItems: number, page: number, pageSize: number, ): PaginationMeta { const totalPages = Math.ceil(totalItems / pageSize); return { page, pageSize, totalItems, totalPages, hasNextPage: page < totalPages, hasPreviousPage: page > 1, }; } /** * Paginate an array of items * * @param items - Array to paginate * @param page - Current page (1-indexed) * @param pageSize - Items per page * @returns Paginated slice of items and metadata * * @example * const users = await db.getAllUsers(); * const { items, pagination } = paginateArray(users, 1, 20); * return paginatedResponse(items, pagination); */ export function paginateArray( items: T[], page: number, pageSize: number, ): { items: T[]; pagination: PaginationMeta; } { const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedItems = items.slice(startIndex, endIndex); return { items: paginatedItems, pagination: calculatePagination(items.length, page, pageSize), }; } /** * Add CORS headers to response * * @param headers - Existing headers or undefined * @returns Headers with CORS added */ export function addCorsHeaders(headers?: HeadersInit): HeadersInit { const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }; if (!headers) { return corsHeaders; } if (headers instanceof Headers) { const newHeaders = new Headers(headers); Object.entries(corsHeaders).forEach(([key, value]) => { newHeaders.set(key, value); }); return newHeaders; } if (Array.isArray(headers)) { return [...headers, ...Object.entries(corsHeaders)]; } return { ...headers, ...corsHeaders }; } /** * Create a 204 No Content response */ export function noContentResponse( headers?: HeadersInit, ): NextResponse { return new NextResponse(undefined, { status: 204, headers, }); } /** * Create a standardized created response (201) */ export function createdResponse( data: T, options?: { requestId?: string; headers?: HeadersInit; }, ): NextResponse> { return successResponse(data, { status: 201, requestId: options?.requestId, headers: options?.headers, }); }