feat(cf-order): wire session userId + surface DB-only cols in Prisma

Follow-up to the 2026-05-20 schema-drift ALTER. Now that the DB
accepts the create() call, also do the cleanup:

1. PRISMA SCHEMA — added the four DB-only columns that were
   previously raw-SQL only. CfExtract now declares:
     userId         String?                       // Authentik sub of orderer
     type           String?  @default("epay")     // 'epay' | 'admin'
     pdfData        Bytes?                        // legacy inline PDF
     adminOrderedBy String?                       // ops who placed for someone

   Plus two new indices: @@index([userId]) and the composite
   @@index([userId, nrCadastral]) so per-user "my orders" lookups
   don't scan. Prisma client regenerated; type-check clean.

2. SESSION → USER ID PROPAGATION — /api/ancpi/order now reads the
   NextAuth session at request time and stamps the userId onto each
   parcel before enqueue:
     const session = await getAuthSession();
     const userId = session?.user.id ?? session?.user.email;
     const stampedParcels = parcels.map(p => ({ ...p, userId: p.userId ?? userId }));
   Body-supplied userId still wins (admin/cron paths can override).

3. ENQUEUEORDER PATH — CfExtractCreateInput gained an optional
   userId field. epay-queue.ts's tx.cfExtract.create({}) now sets:
     userId: input.userId,    // (undefined → NULL, allowed post-patch)
     type: "epay",            // explicit; DB also has default but
                              // setting it makes the column visible
                              // in Prisma RETURNING reads.

After this commit, new orders carry the orderer's identity. Existing
NULL-userId rows from before this fix stay as-is (DB allows NULL).
Future RLS work on architots_postgres (if it survives Faza H) can
key off this column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-20 08:53:14 +03:00
parent 52e16e7807
commit 5fd8881571
4 changed files with 108 additions and 74 deletions
+19 -5
View File
@@ -5,6 +5,7 @@ import {
enqueueBatch,
} from "@/modules/parcel-sync/services/epay-queue";
import type { CfExtractCreateInput } from "@/modules/parcel-sync/services/epay-types";
import { getAuthSession } from "@/core/auth/require-auth";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -95,27 +96,40 @@ export async function POST(req: Request) {
}
}
// Stamp the orderer's session id on each enqueued row so CfExtract
// carries ownership info (was NULL before — see
// feedback_cfextract_schema_drift.md). Falls back to undefined when
// the route is hit without a session (dev tools / cron).
const session = await getAuthSession();
const userId =
((session?.user as { id?: string } | undefined)?.id ||
session?.user?.email) ?? undefined;
const stampedParcels: CfExtractCreateInput[] = parcels.map((p) => ({
...p,
userId: p.userId ?? userId,
}));
let responseBody: {
orders: Array<{ id: string; nrCadastral: string; status: string }>;
};
if (parcels.length === 1) {
const id = await enqueueOrder(parcels[0]!);
if (stampedParcels.length === 1) {
const id = await enqueueOrder(stampedParcels[0]!);
responseBody = {
orders: [
{
id,
nrCadastral: parcels[0]!.nrCadastral,
nrCadastral: stampedParcels[0]!.nrCadastral,
status: "queued",
},
],
};
} else {
const ids = await enqueueBatch(parcels);
const ids = await enqueueBatch(stampedParcels);
responseBody = {
orders: ids.map((id, i) => ({
id,
nrCadastral: parcels[i]?.nrCadastral ?? "",
nrCadastral: stampedParcels[i]?.nrCadastral ?? "",
status: "queued",
})),
};
@@ -138,6 +138,12 @@ export async function enqueueBatch(
prodId: input.prodId ?? 14200,
status: "queued",
version: (agg._max.version ?? 0) + 1,
// userId: Authentik sub of the orderer (propagated from
// /api/ancpi/order's session). Falls back to undefined for
// legacy callers that don't pass it — DB allows NULL after
// the 2026-05-20 schema patch.
userId: input.userId,
type: "epay",
},
});
});
@@ -95,6 +95,10 @@ export type CfExtractCreateInput = {
uatName: string;
gisFeatureId?: string;
prodId?: number;
/** Authentik sub (or any stable session id) of the user placing the
* order. Persisted on `CfExtract.userId` so we can audit who ordered
* what + scope RLS later. Optional for legacy callers. */
userId?: string;
};
export type OrderMetadata = {