diff --git a/app/api/0pt/internal/evidence-outbox/dispatch/route.ts b/app/api/0pt/internal/evidence-outbox/dispatch/route.ts new file mode 100644 index 0000000..496236c --- /dev/null +++ b/app/api/0pt/internal/evidence-outbox/dispatch/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { verifyInternalServiceToken } from "@/src/auth/internal-token"; +import { createAud0Client } from "@/src/clients/aud0-client"; +import { + Aud0OutboxDispatcher, + createAud0OutboxRepositoryFromEnv, +} from "@/src/evidence/aud0-outbox"; +import { siaxErrorResponse } from "@/src/http/siax-error-response"; +import { SiaxError } from "@/src/platform/errors"; + +function assertInternal(request: NextRequest): NextResponse | undefined { + if (verifyInternalServiceToken(request.headers)) return undefined; + return NextResponse.json( + { error: "UNAUTHENTICATED", message: "valid X-Internal-Token is required" }, + { status: 401 }, + ); +} + +function repository() { + const repo = createAud0OutboxRepositoryFromEnv(); + if (!repo) { + throw new SiaxError( + "DEPENDENCY_UNAVAILABLE", + "0pt.aud0_outbox", + "DATABASE_URL is required for durable evidence dispatch", + ); + } + return repo; +} + +export async function POST(request: NextRequest) { + const denied = assertInternal(request); + if (denied) return denied; + + try { + const raw = await request.json().catch(() => ({})) as { limit?: unknown }; + const limit = raw.limit === undefined ? 100 : Number(raw.limit); + if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500) { + return NextResponse.json( + { error: "INVALID_REQUEST", message: "limit must be an integer between 1 and 500" }, + { status: 400 }, + ); + } + + const repo = repository(); + const dispatcher = new Aud0OutboxDispatcher(repo, createAud0Client()); + const result = await dispatcher.sweep(limit); + const stats = await repo.stats(); + return NextResponse.json({ result, stats }); + } catch (error: unknown) { + return siaxErrorResponse(error); + } +} + +export async function GET(request: NextRequest) { + const denied = assertInternal(request); + if (denied) return denied; + + try { + return NextResponse.json(await repository().stats()); + } catch (error: unknown) { + return siaxErrorResponse(error); + } +}