74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
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<SessionUser> {
|
|
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<T>(req: NextRequest, schema: ZodSchema<T>): Promise<T> {
|
|
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<T>(data: T, init?: ResponseInit): NextResponse {
|
|
return NextResponse.json(data, init);
|
|
}
|