enhancment complete with phase 10
This commit is contained in:
1 parent
2c4927478a
commit
6adb541967
44 files changed
+7820
-845
No files matched your search
@@ -0,0 +1,364 @@
|
||||
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<T>(
|
||||
data: T,
|
||||
options?: {
|
||||
status?: number;
|
||||
requestId?: string;
|
||||
headers?: HeadersInit;
|
||||
},
|
||||
): NextResponse<ApiSuccessResponse<T>> {
|
||||
const response: ApiSuccessResponse<T> = {
|
||||
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<T>(
|
||||
data: T[],
|
||||
pagination: PaginationMeta,
|
||||
options?: {
|
||||
requestId?: string;
|
||||
headers?: HeadersInit;
|
||||
},
|
||||
): NextResponse<PaginatedResponse<T>> {
|
||||
const response: PaginatedResponse<T> = {
|
||||
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<string, string[]>;
|
||||
requestId?: string;
|
||||
headers?: HeadersInit;
|
||||
},
|
||||
): NextResponse<ApiErrorResponse> {
|
||||
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<string, string[]>,
|
||||
headers?: HeadersInit,
|
||||
): NextResponse<ApiErrorResponse> {
|
||||
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<ApiErrorResponse> {
|
||||
return errorResponse(message, {
|
||||
status: 401,
|
||||
code: ApiErrorCode.UNAUTHORIZED,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 403 Forbidden response
|
||||
*/
|
||||
export function forbiddenResponse(
|
||||
message: string = "Forbidden",
|
||||
headers?: HeadersInit,
|
||||
): NextResponse<ApiErrorResponse> {
|
||||
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<ApiErrorResponse> {
|
||||
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<ApiErrorResponse> {
|
||||
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<ApiErrorResponse> {
|
||||
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<T>(
|
||||
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<undefined> {
|
||||
return new NextResponse(undefined, {
|
||||
status: 204,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standardized created response (201)
|
||||
*/
|
||||
export function createdResponse<T>(
|
||||
data: T,
|
||||
options?: {
|
||||
requestId?: string;
|
||||
headers?: HeadersInit;
|
||||
},
|
||||
): NextResponse<ApiSuccessResponse<T>> {
|
||||
return successResponse(data, {
|
||||
status: 201,
|
||||
requestId: options?.requestId,
|
||||
headers: options?.headers,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user