Spaces:
Configuration error
Configuration error
| import { NextRequest, NextResponse } from 'next/server'; | |
| import { z, ZodSchema } from 'zod'; | |
| import { db } from '@/lib/db'; | |
| // ============================================================ | |
| // Standard API Response | |
| // ============================================================ | |
| export interface ApiResponse<T = unknown> { | |
| success: boolean; | |
| data?: T; | |
| error?: string; | |
| meta?: { | |
| page: number; | |
| limit: number; | |
| total: number; | |
| totalPages: number; | |
| }; | |
| } | |
| export function successResponse<T>(data: T, status = 200): NextResponse<ApiResponse<T>> { | |
| return NextResponse.json({ success: true, data }, { status }); | |
| } | |
| export function errorResponse(error: string, status = 400): NextResponse<ApiResponse> { | |
| return NextResponse.json({ success: false, error }, { status }); | |
| } | |
| export function paginatedResponse<T>( | |
| data: T[], | |
| page: number, | |
| limit: number, | |
| total: number | |
| ): NextResponse<ApiResponse<T[]>> { | |
| return NextResponse.json({ | |
| success: true, | |
| data, | |
| meta: { | |
| page, | |
| limit, | |
| total, | |
| totalPages: Math.ceil(total / limit), | |
| }, | |
| }); | |
| } | |
| // ============================================================ | |
| // Pagination Helper | |
| // ============================================================ | |
| export interface PaginationParams { | |
| page: number; | |
| limit: number; | |
| skip: number; | |
| } | |
| export function getPagination(request: NextRequest): PaginationParams { | |
| const url = new URL(request.url); | |
| const page = Math.max(1, parseInt(url.searchParams.get('page') || '1')); | |
| const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get('limit') || '20'))); | |
| const skip = (page - 1) * limit; | |
| return { page, limit, skip }; | |
| } | |
| // ============================================================ | |
| // Sorting Helper | |
| // ============================================================ | |
| export function getSorting(request: NextRequest, defaultField = 'createdAt', defaultOrder: 'asc' | 'desc' = 'desc') { | |
| const url = new URL(request.url); | |
| const sortField = url.searchParams.get('sort') || defaultField; | |
| const sortOrder = url.searchParams.get('order') || defaultOrder; | |
| return { [sortField]: sortOrder } as Record<string, 'asc' | 'desc'>; | |
| } | |
| // ============================================================ | |
| // Filtering Helper | |
| // ============================================================ | |
| export function getFilters(request: NextRequest, allowedFilters: string[]): Record<string, string> { | |
| const url = new URL(request.url); | |
| const filters: Record<string, string> = {}; | |
| for (const key of allowedFilters) { | |
| const value = url.searchParams.get(key); | |
| if (value) filters[key] = value; | |
| } | |
| return filters; | |
| } | |
| // ============================================================ | |
| // Validation Helper | |
| // ============================================================ | |
| export function validateBody<T>(schema: ZodSchema<T>, body: unknown): { data?: T; error?: string } { | |
| try { | |
| const data = schema.parse(body); | |
| return { data }; | |
| } catch (err) { | |
| if (err instanceof z.ZodError) { | |
| const messages = err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); | |
| return { error: messages }; | |
| } | |
| return { error: 'Invalid input' }; | |
| } | |
| } | |
| // ============================================================ | |
| // Request Body Helper | |
| // ============================================================ | |
| export async function getBody(request: NextRequest): Promise<unknown> { | |
| try { | |
| return await request.json(); | |
| } catch { | |
| return {}; | |
| } | |
| } | |
| // ============================================================ | |
| // Simple Rate Limiter (in-memory) | |
| // ============================================================ | |
| const rateLimitMap = new Map<string, { count: number; resetTime: number }>(); | |
| export function rateLimit(key: string, maxRequests = 60, windowMs = 60000): boolean { | |
| const now = Date.now(); | |
| const entry = rateLimitMap.get(key); | |
| if (!entry || now > entry.resetTime) { | |
| rateLimitMap.set(key, { count: 1, resetTime: now + windowMs }); | |
| return true; | |
| } | |
| if (entry.count >= maxRequests) { | |
| return false; | |
| } | |
| entry.count++; | |
| return true; | |
| } | |
| // ============================================================ | |
| // API Handler Wrapper | |
| // ============================================================ | |
| export function apiHandler( | |
| handler: (request: NextRequest, context?: { params: Promise<Record<string, string>> }) => Promise<NextResponse> | |
| ) { | |
| return async (request: NextRequest, context?: { params: Promise<Record<string, string>> }) => { | |
| try { | |
| // Rate limiting | |
| const ip = request.headers.get('x-forwarded-for') || 'unknown'; | |
| if (!rateLimit(ip)) { | |
| return errorResponse('Rate limit exceeded', 429); | |
| } | |
| return await handler(request, context); | |
| } catch (error) { | |
| console.error('API Error:', error); | |
| const message = error instanceof Error ? error.message : 'Internal server error'; | |
| return errorResponse(message, 500); | |
| } | |
| }; | |
| } | |