158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
// src/utils/api.ts
|
|
|
|
// Standard JSON headers for all API responses
|
|
const JSON_HEADERS = {
|
|
'Content-Type': 'application/json'
|
|
} as const;
|
|
|
|
/**
|
|
* Base function to create consistent API responses
|
|
* All other response helpers use this internally
|
|
*/
|
|
export function createAPIResponse(data: any, status: number = 200, additionalHeaders?: Record<string, string>): Response {
|
|
const headers = additionalHeaders
|
|
? { ...JSON_HEADERS, ...additionalHeaders }
|
|
: JSON_HEADERS;
|
|
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Success responses (2xx status codes)
|
|
*/
|
|
export const apiResponse = {
|
|
// 200 - Success with data
|
|
success: (data: any = { success: true }): Response =>
|
|
createAPIResponse(data, 200),
|
|
|
|
// 201 - Created (for contribution submissions, uploads, etc.)
|
|
created: (data: any = { success: true }): Response =>
|
|
createAPIResponse(data, 201),
|
|
|
|
// 202 - Accepted (for async operations)
|
|
accepted: (data: any = { success: true, message: 'Request accepted for processing' }): Response =>
|
|
createAPIResponse(data, 202)
|
|
};
|
|
|
|
/**
|
|
* Client error responses (4xx status codes)
|
|
*/
|
|
export const apiError = {
|
|
// 400 - Bad Request
|
|
badRequest: (message: string = 'Bad request', details?: string[]): Response =>
|
|
createAPIResponse({
|
|
success: false,
|
|
error: message,
|
|
...(details && { details })
|
|
}, 400),
|
|
|
|
// 401 - Unauthorized
|
|
unauthorized: (message: string = 'Authentication required'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 401),
|
|
|
|
// 403 - Forbidden
|
|
forbidden: (message: string = 'Access denied'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 403),
|
|
|
|
// 404 - Not Found
|
|
notFound: (message: string = 'Resource not found'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 404),
|
|
|
|
// 422 - Unprocessable Entity (validation errors)
|
|
validation: (message: string = 'Validation failed', details?: string[]): Response =>
|
|
createAPIResponse({
|
|
success: false,
|
|
error: message,
|
|
...(details && { details })
|
|
}, 422),
|
|
|
|
// 429 - Rate Limited
|
|
rateLimit: (message: string = 'Rate limit exceeded. Please wait before trying again.'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 429)
|
|
};
|
|
|
|
/**
|
|
* Server error responses (5xx status codes)
|
|
*/
|
|
export const apiServerError = {
|
|
// 500 - Internal Server Error
|
|
internal: (message: string = 'Internal server error'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 500),
|
|
|
|
// 502 - Bad Gateway (external service issues)
|
|
badGateway: (message: string = 'External service error'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 502),
|
|
|
|
// 503 - Service Unavailable
|
|
unavailable: (message: string = 'Service temporarily unavailable'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 503),
|
|
|
|
// 504 - Gateway Timeout
|
|
timeout: (message: string = 'Request timeout'): Response =>
|
|
createAPIResponse({ success: false, error: message }, 504)
|
|
};
|
|
|
|
/**
|
|
* Specialized response helpers for common patterns
|
|
*/
|
|
export const apiSpecial = {
|
|
// JSON parsing error
|
|
invalidJSON: (): Response =>
|
|
apiError.badRequest('Invalid JSON in request body'),
|
|
|
|
// Missing required fields
|
|
missingRequired: (fields: string[]): Response =>
|
|
apiError.badRequest(`Missing required fields: ${fields.join(', ')}`),
|
|
|
|
// Empty request body
|
|
emptyBody: (): Response =>
|
|
apiError.badRequest('Request body cannot be empty'),
|
|
|
|
// File upload responses
|
|
uploadSuccess: (data: { url: string; filename: string; size: number; storage: string }): Response =>
|
|
apiResponse.created(data),
|
|
|
|
uploadFailed: (error: string): Response =>
|
|
apiServerError.internal(`Upload failed: ${error}`),
|
|
|
|
// Contribution responses
|
|
contributionSuccess: (data: { prUrl?: string; branchName?: string; message: string }): Response =>
|
|
apiResponse.created({ success: true, ...data }),
|
|
|
|
contributionFailed: (error: string): Response =>
|
|
apiServerError.internal(`Contribution failed: ${error}`)
|
|
};
|
|
|
|
export const apiWithHeaders = {
|
|
// Success with custom headers (e.g., Set-Cookie)
|
|
successWithHeaders: (data: any, headers: Record<string, string>): Response =>
|
|
createAPIResponse(data, 200, headers),
|
|
|
|
// Redirect response
|
|
redirect: (location: string, temporary: boolean = true): Response =>
|
|
new Response(null, {
|
|
status: temporary ? 302 : 301,
|
|
headers: { 'Location': location }
|
|
})
|
|
};
|
|
|
|
export async function handleAPIRequest<T>(
|
|
operation: () => Promise<T>,
|
|
errorMessage: string = 'Request processing failed'
|
|
): Promise<T | Response> {
|
|
try {
|
|
return await operation();
|
|
} catch (error) {
|
|
console.error(`API Error: ${errorMessage}:`, error);
|
|
return apiServerError.internal(errorMessage);
|
|
}
|
|
}
|
|
|
|
export const createAuthErrorResponse = apiError.unauthorized;
|
|
export const createBadRequestResponse = apiError.badRequest;
|
|
export const createSuccessResponse = apiResponse.success;
|
|
|