| import { NextRequest, NextResponse } from "next/server"; |
| import { Logger } from "@/lib/logger"; |
|
|
| |
| |
| |
| export interface ApiContext { |
| userId?: string; |
| ip?: string; |
| method: string; |
| path: string; |
| requestId?: string; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function withErrorHandling( |
| handler: (req: NextRequest, context: ApiContext) => Promise<Response>, |
| req: NextRequest, |
| context?: Partial<ApiContext> |
| ): Promise<Response> { |
| const startTime = performance.now(); |
| const requestId = crypto.randomUUID().substring(0, 8); |
|
|
| const apiContext: ApiContext = { |
| method: req.method, |
| path: new URL(req.url).pathname, |
| ip: req.headers.get("x-forwarded-for") || req.headers.get("cf-connecting-ip") || "unknown", |
| requestId, |
| ...context, |
| }; |
|
|
| try { |
| const response = await handler(req, apiContext); |
| const duration = Math.round(performance.now() - startTime); |
|
|
| |
| if (response.status < 400) { |
| Logger.info("API Request Success", { |
| ...apiContext, |
| duration, |
| status: response.status, |
| }); |
| } |
|
|
| |
| const newResponse = new NextResponse(response.body, response); |
| newResponse.headers.set("X-Request-Id", requestId); |
| return newResponse; |
| } catch (error) { |
| const duration = Math.round(performance.now() - startTime); |
|
|
| |
| Logger.error("API Request Error", { |
| ...apiContext, |
| duration, |
| error: error instanceof Error ? error.message : String(error), |
| stack: error instanceof Error ? error.stack : undefined, |
| }); |
|
|
| |
| return NextResponse.json( |
| { |
| success: false, |
| error: "Internal server error", |
| code: "INTERNAL_SERVER_ERROR", |
| requestId, |
| }, |
| { |
| status: 500, |
| headers: { |
| "X-Request-Id": requestId, |
| }, |
| } |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function withServerActionErrorHandling<T>( |
| action: () => Promise<T>, |
| context?: Partial<ApiContext> |
| ): Promise<{ success: true; data: T } | { success: false; error: string }> { |
| const startTime = performance.now(); |
|
|
| try { |
| const result = await action(); |
| const duration = Math.round(performance.now() - startTime); |
|
|
| Logger.info("Server Action Success", { |
| ...context, |
| duration, |
| }); |
|
|
| return { success: true, data: result }; |
| } catch (error) { |
| const duration = Math.round(performance.now() - startTime); |
|
|
| Logger.error("Server Action Error", { |
| ...context, |
| duration, |
| error: error instanceof Error ? error.message : String(error), |
| stack: error instanceof Error ? error.stack : undefined, |
| }); |
|
|
| return { |
| success: false, |
| error: error instanceof Error ? error.message : "An unknown error occurred", |
| }; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export function checkRequestValidity(req: NextRequest): NextResponse | null { |
| |
| const contentType = req.headers.get("content-type"); |
|
|
| if (req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS") { |
| if (!contentType) { |
| return NextResponse.json( |
| { |
| success: false, |
| error: "Content-Type header is required", |
| code: "MISSING_CONTENT_TYPE", |
| }, |
| { status: 400 } |
| ); |
| } |
| } |
|
|
| return null; |
| } |
|
|