phase 2 and 3
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user