feat(geoportal): one-time setup banner for PostGIS views

- GET /api/geoportal/setup-views checks if zoom views exist
- POST creates them (idempotent)
- SetupBanner component: auto-checks on mount, shows amber banner if
  views missing, button to create them, success message with docker
  restart reminder, auto-hides when everything is ready

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-24 08:05:22 +02:00
parent 7d2fe4ade0
commit 836d60b72f
3 changed files with 109 additions and 13 deletions
+24 -13
View File
@@ -1,9 +1,6 @@
/**
* POST /api/geoportal/setup-views
*
* Creates the zoom-dependent UAT views for Martin vector tiles.
* Safe to re-run (CREATE OR REPLACE VIEW).
* Original geometry in GisUat.geom is NEVER modified.
* GET /api/geoportal/setup-views — check if views exist
* POST /api/geoportal/setup-views — create views (idempotent)
*/
import { NextResponse } from "next/server";
import { prisma } from "@/core/storage/prisma";
@@ -26,25 +23,39 @@ const VIEWS = [
},
{
name: "gis_uats_z12",
sql: `CREATE OR REPLACE VIEW gis_uats_z12 AS SELECT siruta, name, county, ST_SimplifyPreserveTopology(geom, 10) AS geom FROM "GisUat" WHERE geom IS NOT NULL`,
sql: `CREATE OR REPLACE VIEW gis_uats_z12 AS SELECT siruta, name, county, geom FROM "GisUat" WHERE geom IS NOT NULL`,
},
];
/** GET — returns { ready: boolean, missing: string[] } */
export async function GET() {
try {
const existing = await prisma.$queryRaw`
SELECT viewname FROM pg_views
WHERE schemaname = 'public' AND viewname LIKE 'gis_uats_z%'
` as Array<{ viewname: string }>;
const existingNames = new Set(existing.map((r) => r.viewname));
const missing = VIEWS.filter((v) => !existingNames.has(v.name)).map((v) => v.name);
return NextResponse.json({ ready: missing.length === 0, missing });
} catch (error) {
const msg = error instanceof Error ? error.message : "Eroare";
return NextResponse.json({ ready: false, missing: VIEWS.map((v) => v.name), error: msg });
}
}
/** POST — creates all views (idempotent) */
export async function POST() {
const results: string[] = [];
try {
for (const v of VIEWS) {
await prisma.$executeRawUnsafe(v.sql);
results.push(`${v.name} OK`);
}
return NextResponse.json({ status: "ok", results });
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return NextResponse.json(
{ status: "error", results, error: msg },
{ status: 500 }
);
const msg = error instanceof Error ? error.message : "Eroare";
return NextResponse.json({ status: "error", results, error: msg }, { status: 500 });
}
}