Files
ArchiTools/src/app/api/gis/parcel/[id]/pad/route.ts
T
Claude VM 71cfc29f9a feat(geoportal-v2): export toolbar + Semnez ca picker + CF intern/Extras split
V2 panel toolbar replaces the single "Comandă CF" button with two rows:

  [Încadrare] [Pl. situație] [Coord.] [DXF]       ← 4 exports
  [CF intern] [Extras CF]                          ← 2 CF flows

Each export button pops an inline modal:
  - PIZ / PAD: SignAsPicker (PFA / PJA radio list, manual-add inline,
    co-signer slot on PIZ) + basemap toggle (google / orto for PIZ).
  - Coord / DXF: no picker — single-click download via JWT proxy.

"CF intern" is the free copycf flow from eTerra (proxied via gis-api);
"Extras CF" keeps the existing CfOrderModal (1 credit ePay). The two
modes are now visually balanced as a 2-button row.

Sign-as picker rows merge user-owned Signatory table entries with the
SIGN_AS_DEFAULT_OPTIONS env-driven fallback (org-wide hardcoded options;
defaults seed two Studii de teren entries — Tiurbe PFA + SRL PJA). New
rows added via the picker's "Adaugă autorizație" inline form write to
the Signatory table; ENV rows are read-only.

Architots side ships fully:
  - prisma Signatory model + ALTER TABLE applied (per the schema-drift
    feedback memory).
  - /api/sign-as-options (GET, POST) + /api/sign-as-options/[id]
    (PATCH, DELETE).
  - /api/cf-intern/order and /api/gis/parcel/[id]/{piz,pad,coords,dxf}
    proxy routes — auth check + JWT forward, stream binary back.
  - gis-api thin client extended with the matching exports.* namespace.

Until the gis-api endpoints ship (next session — full spec in
docs/plans/005-gis-api-export-endpoints.md), each export proxy returns
501 "…urmează" with a Romanian message so the modal shows what's
coming instead of a hard error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:57:55 +03:00

93 lines
2.8 KiB
TypeScript

// POST /api/gis/parcel/[id]/pad
//
// Forwards a PAD (Plan de amplasament și delimitare) export request to gis-api.
// Until gis-api ships POST /api/v1/parcel/:id/pad (see docs/plans/005-…),
// this route returns 501 with a friendly Romanian message.
import { NextResponse } from "next/server";
import { getAuthSession } from "@/core/auth/require-auth";
import { gisApi, GisApiError } from "@/lib/gis-api-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
type SignerInput = {
kind?: unknown;
displayName?: unknown;
authClass?: unknown;
authNumber?: unknown;
};
type Body = {
signer?: SignerInput;
layerId?: "TERENURI_ACTIVE" | "CLADIRI_ACTIVE";
scale?: number;
paper?: string;
};
function parseSigner(raw: SignerInput | null | undefined) {
if (!raw || (raw.kind !== "user" && raw.kind !== "org")) return null;
if (typeof raw.displayName !== "string" || !raw.displayName.trim()) return null;
if (typeof raw.authNumber !== "string" || !raw.authNumber.trim()) return null;
return {
kind: raw.kind as "user" | "org",
displayName: raw.displayName.trim(),
authClass:
typeof raw.authClass === "string" && raw.authClass.trim()
? raw.authClass.trim()
: null,
authNumber: raw.authNumber.trim(),
};
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const session = await getAuthSession();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const body = (await request.json()) as Body;
const signer = parseSigner(body.signer);
if (!signer) {
return NextResponse.json({ error: "signer_required" }, { status: 400 });
}
try {
const upstream = await gisApi.exports.pad(id, {
signer,
layerId: body.layerId,
scale: body.scale,
paper: body.paper,
});
const headers = new Headers();
headers.set(
"Content-Type",
upstream.headers.get("Content-Type") ?? "application/pdf",
);
const cd = upstream.headers.get("Content-Disposition");
if (cd) headers.set("Content-Disposition", cd);
return new NextResponse(upstream.body, { status: 200, headers });
} catch (err) {
if (err instanceof GisApiError) {
if (err.status === 404) {
return NextResponse.json(
{
error: "endpoint_not_deployed",
message:
"Plan de situație urmează — endpoint-ul gis-api se livrează în sesiunea următoare.",
},
{ status: 501 },
);
}
return NextResponse.json(
{ error: err.code, body: err.body },
{ status: err.status },
);
}
const msg = err instanceof Error ? err.message : String(err);
return NextResponse.json({ error: "internal_error", hint: msg.slice(0, 200) }, { status: 500 });
}
}