socialconnect / src /lib /api-utils.ts
getalvi's picture
Upload 51 files
a92567e verified
Raw
History Blame Contribute Delete
4.98 kB
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);
}
};
}