import "server-only"; import { NextResponse, type NextRequest } from "next/server"; import { z } from "zod"; import { getSession, assertSameOrigin, AuthError } from "./auth"; type Handler

= (req: NextRequest, ctx: { params: P; session: { id: string } }) => Promise; /** Wraps a route handler: requires a valid session, checks Origin on mutations, maps errors to JSON. */ export function guarded

>(fn: Handler

) { return async (req: NextRequest, ctx: { params: Promise

}): Promise => { try { const session = await getSession(); if (!session) throw new AuthError(); if (req.method !== "GET" && req.method !== "HEAD") await assertSameOrigin(); return await fn(req, { params: await ctx.params, session }); } catch (e) { return errorResponse(e); } }; } export function errorResponse(e: unknown): Response { const err = e as { status?: number; message?: string; issues?: unknown }; if (e instanceof z.ZodError) return NextResponse.json({ error: "RequĂȘte invalide", issues: e.issues }, { status: 400 }); const status = typeof err.status === "number" && err.status >= 400 && err.status < 600 ? err.status : 500; if (status >= 500) console.error(e); return NextResponse.json({ error: err.message ?? "Erreur interne" }, { status }); } export const json = (data: unknown, init?: ResponseInit) => NextResponse.json(data, init); export async function body(req: NextRequest, schema: T): Promise> { return schema.parse(await req.json()); } /** Server-Sent Events response. `run` receives an emit function and must resolve when finished. */ export function sse(run: (emit: (event: string, data: unknown) => void, signal: AbortSignal) => Promise, signal: AbortSignal): Response { const enc = new TextEncoder(); let closed = false; const stream = new ReadableStream({ start(controller) { const emit = (event: string, data: unknown) => { if (closed) return; try { controller.enqueue(enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)); } catch { closed = true; } }; const ping = setInterval(() => { if (!closed) try { controller.enqueue(enc.encode(`: ping\n\n`)); } catch { closed = true; } }, 15_000); run(emit, signal).catch(e => emit("error", { message: (e as Error).message ?? String(e) })).finally(() => { clearInterval(ping); closed = true; try { controller.close(); } catch { /* already closed */ } }); }, cancel() { closed = true; }, }); return new Response(stream, { headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" } }); }