feat: expose tenant-safe dataset registry HTTP surface

This commit is contained in:
2026-09-07 18:06:36 +02:00
parent 70fd81dc4d
commit 102e1e1616
+91
View File
@@ -0,0 +1,91 @@
import { NextRequest, NextResponse } from "next/server";
import { assertTenantMatchesVerified } from "@/src/http/tenant-guard";
import { siaxErrorResponse } from "@/src/http/siax-error-response";
import { SiaxError } from "@/src/platform/errors";
import { datasetService } from "@/src/services/datasets";
import type { DatasetStatus, DatasetType } from "@/src/types/dataset";
const DATASET_TYPES = new Set<DatasetType>(["raw", "structured", "enriched", "curated", "feature", "derived"]);
const DATASET_STATUSES = new Set<DatasetStatus>(["active", "archived", "deprecated"]);
function actorId(request: NextRequest): string {
return request.headers.get("x-0pt-verified-sub") ?? "local-dev";
}
function stringField(body: Record<string, unknown>, field: string): string {
const value = body[field];
if (typeof value !== "string" || !value.trim()) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", `${field} is required`);
}
return value.trim();
}
export async function POST(request: NextRequest) {
try {
const raw = await request.json();
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "JSON object body is required");
}
const body = raw as Record<string, unknown>;
const tenantId = stringField(body, "tenant_id");
const guard = assertTenantMatchesVerified(request, tenantId);
if (guard) return guard;
const datasetType = stringField(body, "dataset_type") as DatasetType;
if (!DATASET_TYPES.has(datasetType)) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "unsupported dataset_type");
}
const status = body.status === undefined ? undefined : String(body.status) as DatasetStatus;
if (status !== undefined && !DATASET_STATUSES.has(status)) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "unsupported status");
}
const dataset = await datasetService.registerDataset({
dataset_id: stringField(body, "dataset_id"),
tenant_id: tenantId,
project_id: stringField(body, "project_id"),
name: stringField(body, "name"),
dataset_type: datasetType,
...(status ? { status } : {}),
...(typeof body.description === "string" && body.description.trim()
? { description: body.description.trim() }
: {}),
...(typeof body.correlation_id === "string" && body.correlation_id.trim()
? { correlation_id: body.correlation_id.trim() }
: {}),
created_by: actorId(request),
});
return NextResponse.json(dataset, { status: 201 });
} catch (error: unknown) {
return siaxErrorResponse(error);
}
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const tenantId = searchParams.get("tenant_id")?.trim();
if (!tenantId) throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "tenant_id is required");
const guard = assertTenantMatchesVerified(request, tenantId);
if (guard) return guard;
const typeRaw = searchParams.get("dataset_type") ?? undefined;
const statusRaw = searchParams.get("status") ?? undefined;
if (typeRaw && !DATASET_TYPES.has(typeRaw as DatasetType)) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "unsupported dataset_type");
}
if (statusRaw && !DATASET_STATUSES.has(statusRaw as DatasetStatus)) {
throw new SiaxError("INVALID_REQUEST", "0pt.dataset", "unsupported status");
}
return NextResponse.json(await datasetService.listDatasets({
tenant_id: tenantId,
project_id: searchParams.get("project_id") ?? undefined,
dataset_type: typeRaw as DatasetType | undefined,
status: statusRaw as DatasetStatus | undefined,
}));
} catch (error: unknown) {
return siaxErrorResponse(error);
}
}