import { NextResponse, type NextRequest } from "next/server"; import { ZodError, type ZodSchema } from "zod"; import { getSessionUser, type Role, type SessionUser } from "@/lib/session"; import { clientIp } from "@/lib/request"; export class ApiError extends Error { constructor(public status: number, public code: string, message: string) { super(message); } } export function errorResponse(err: unknown): NextResponse { if (err instanceof ApiError) { return NextResponse.json({ error: err.message, code: err.code }, { status: err.status }); } if (err instanceof ZodError) { return NextResponse.json( { error: "Validation failed", code: "validation_failed", issues: err.issues.map((i) => ({ path: i.path, message: i.message })), }, { status: 400 }, ); } if ( err && typeof err === "object" && "code" in err && typeof (err as { code: unknown }).code === "string" && ((err as { code: string }).code === "P2002" || (err as { code: string }).code === "P2003") ) { const p = err as { code: string; meta?: { target?: unknown } }; const msg = p.code === "P2002" ? `Duplicate value${p.meta?.target ? ` on ${JSON.stringify(p.meta.target)}` : ""}` : "Foreign key constraint failed"; return NextResponse.json( { error: msg, code: p.code === "P2002" ? "duplicate" : "fk_violation" }, { status: 409 }, ); } console.error("[api] unhandled error:", err); return NextResponse.json( { error: "Internal server error", code: "internal" }, { status: 500 }, ); } export async function requireRole(role: Role): Promise { const user = await getSessionUser(); if (!user) throw new ApiError(401, "unauthenticated", "Sign in required"); if (user.role !== role) throw new ApiError(403, "forbidden", "Not allowed"); return user; } export async function parseJson(req: NextRequest, schema: ZodSchema): Promise { let raw: unknown; try { raw = await req.json(); } catch { throw new ApiError(400, "invalid_json", "Expected JSON body"); } return schema.parse(raw); } export function actorContext(req: NextRequest, user: SessionUser) { return { actorId: user.id, ipAddress: clientIp(req) }; } export function ok(data: T, init?: ResponseInit): NextResponse { return NextResponse.json(data, init); }