# Plan 001 — DB GIS Central Multi-App **Status:** DRAFT v1 — neaprobat, de discutat **Autor:** Claude + Marius Tarau **Data:** 2026-04-18 **Scope:** Arhitectură unificată GIS pentru ArchiTools, eTerra.live, SmartCity360, Planhub ERP + servicii viitoare --- ## 1. Context & motivație Date GIS (cadastru ANCPI, enrichment, PUG/PUZ, UTR) sunt folosite de multiple platforme: - **ArchiTools** — portal intern Beletage (există, producție) - **eTerra.live** — SaaS topografi (proxy eTerra, repo nou gitadmin/eterra-live) - **SmartCity360** — portal GIS primării (în arhitectură) - **Planhub ERP** — ERP arhitecți cu geoportal (în arhitectură) - Viitoare servicii Nevoie: **single source of truth GIS** cu acces controlat, sync centralizat, cache/CDN partajat, izolare multi-tenant. --- ## 2. Topologie **All on-prem.** Nimic în cloud public. - Servere: satra (10.10.10.166), shop (10.10.10.84), proxy (10.10.10.199), orchi (10.10.40.60 — CJ2 via VPN) - Gateway public: Traefik pe proxy, Sophos DNAT 80/443 - Cloudflare doar pentru domenii SaaS public-facing (eTerra.live, SmartCity360) --- ## 3. DB Central — Shop server (10.10.10.84) **Resurse disponibile (verificat 2026-04-18):** - Xeon Gold 6430, 128 vCPU, 251GB RAM, 3.5TB NVMe (1% ocupat) - Rulează deja: Supabase stack, WordPress, Tegola - Load avg ~0.5, headroom masiv **Cluster Postgres nou, dedicat:** - Container `postgres-gis` (separat de Supabase existent — izolare critică) - Postgres 16 + PostGIS 3.4 + pg_cron - SRID nativ 3844 (Stereo70) - Port intern, expus doar pe 10.10.10.0/24 + VPN CJ2 (10.10.40.0/24) - NICIODATĂ expus direct pe internet ### Schema split | Schema | Conținut | Access | |---|---|---| | `gis_core` | Cadastru național ANCPI: UAT, terenuri, clădiri, administrativ | Public read, write doar sync daemon | | `gis_urban` | PUG, PUZ, PUD, UTR, zone protecție, zone reglementate | Read public (layer public), write per tenant primărie (RLS) | | `gis_enrichment` | CF, proprietari, adrese, categorii folosință — **GDPR sensibil** | Auth obligatoriu, RLS pe rol | | `gis_meta` | Sync runs, rules, status, audit | Internal only | | `tenants` | Primării, firme arh, clienți SaaS | Control plane | ### Roluri Postgres - `gis_sync_rw` — write pe `gis_core` + `gis_meta` (sync daemons) - `gis_public_ro` — read pe `gis_core` + layer public din `gis_urban` (Martin public tiles) - `gis_app_ro` — read authenticated cu RLS pe `gis_urban` privat + `gis_enrichment` - `gis_urban_rw` — write pe `gis_urban` doar pentru tenant-ul propriu ### RLS pe multi-tenant ```sql -- exemplu pentru gis_urban tabele private CREATE POLICY tenant_isolation ON gis_urban.puz_private USING (siruta = ANY(current_setting('app.allowed_sirutas', true)::text[])); ``` --- ## 4. Layer acces — public/auth endpoints | Subdomain | Serviciu | Auth | Scop | |---|---|---|---| | `tiles.beletage.ro` | Martin (instanță dedicată) | Niciun auth pe layer public | Tile server public cadastru + PUG public | | `pmtiles.beletage.ro` | nginx static | Niciun auth | Overview Romania, PMTiles pre-generated | | `gis-api.beletage.ro` | Next.js API sau PostgREST | JWT Authentik | Enrichment, query spațial, mutations | | `sync.beletage.ro` | Orchestrator UI | Authentik admin | Control panel sync central | | `eterra.live` | SaaS topografi | Authentik | Portal topografi | | `planhub.beletage.ro` | ERP arhitecți | Authentik | Geoportal + PM | | `.smartcity360.ro` | Portal primărie | Cetățeni anonim + admin Authentik | Subdomain per tenant | **Traefik** — reverse proxy unic, SSL auto. **Cloudflare** în față pe domenii SaaS public (eTerra.live, SmartCity360) cu rate limit + CDN cache tile-uri (TTL 24h pentru UAT). --- ## 5. ACL Enrichment (per rol) | Rol | Vede enrichment | |---|---| | `topograf` (eTerra.live) | Full — CF, proprietari, adrese | | `architect` (Planhub ERP) | Full + PUG complet | | `admin_primaria` (SmartCity360) | Full pentru UAT-ul primăriei + date PUG private | | `cetatean` (SmartCity360 public) | Doar nr. cadastral + suprafață + UTR | | `admin` (ArchiTools) | Tot | Implementat prin RLS + `current_setting('app.role')`. --- ## 6. Shared enrichment pool — insight cheie **De-duplicare globală.** Dacă topograf A cere enrichment pe parcela X: 1. Salvat în `gis_enrichment` central 2. Topograf B cere aceeași parcelă → **hit instant**, zero request la eTerra 3. TTL default 90 zile → după expire auto-refresh Beneficii: - Economii masive de sesiuni eTerra - Viteză maximă pentru request-uri repetate - Baza de date crește cu datele "cele mai cerute" de topografi (natural prioritization) **TBD:** TTL fix 90 zile vs decizie manuală a topografului pentru "force refresh"? --- ## 7. Sync workers — centralizat **Extrage din ArchiTools într-un serviciu standalone** `gis-sync-orchestrator`: - Un singur scheduler (nu duplicat per app) - Workers: - `eterra-sync` — delta sync cadastru (nocturn automat + on-demand per UAT) - `enrichment-worker` — pulls CF/owners pe cerere - `pug-ingest` (viitor) — import PUG din shapefiles/PDFs - **Queue:** `pg-boss` (Postgres-backed, zero infra extra) sau BullMQ+Redis - **Delta sync** folosește `gis_uats.last_updated_dtm` deja existent - **Notificare apps** la sync complete: `Postgres LISTEN/NOTIFY` + webhook → fiecare app se abonează - **PMTiles rebuild** trigger-uit de orchestrator după sync major (webhook existent satra:9876) ### Session pool eTerra per-user - Tabel `eterra_sessions` keyed by `user_id` (fiecare topograf = sesiune proprie) - Sync on-demand eTerra.live folosește credențialele topografului logat - Sync nocturn automat folosește cont service (actualul hardcoded) - Avantaj: toți topografii + sistemul automat contribuie la menținerea DB-ului actual --- ## 8. Consumers | App | Schemas | Write | DB user | Deploy | |---|---|---|---|---| | ArchiTools | core + urban + enrichment | enrichment | `archi_rw` | satra | | eTerra.live | core + enrichment (ACL topograf) | enrichment via sync | `eterra_ro` + `eterra_sync` | satra/shop TBD | | SmartCity360 | core + urban (filter siruta) | urban (PUG/PUZ) | `sc_rw` | TBD | | Planhub ERP | core + urban | proiecte proprii (schemă `erp`) | `erp_rw` | TBD | Fiecare app: - Own Authentik OIDC app - Own DB user + permisiuni minimale - Own MinIO bucket - Own Infisical project pentru secrete --- ## 9. Securitate - **DB niciodată expus direct pe internet** → tot prin Martin / API cu auth - **Enrichment** (CF, proprietari) → NU în tile-uri publice, doar via API auth'd - **Rate limit Cloudflare** pe `tiles.*` (ex: 100 req/s per IP) - **Audit log** pe `gis_urban` în `gis_meta.audit` — cine modifică PUG primăriei - **Secrete** în Infisical, proiecte separate per app (zero reuse) - **Backup:** `pg_basebackup` + WAL archiving zilnic → MinIO satra + replică async CJ2 (PITR 14 zile) --- ## 10. Performanță - Tabele simplificate pe zoom (deja există pentru UAT: z0/5/8/12) — extinde pe `gis_terenuri` dacă nevoie - **Materialized views** pentru query-uri grele cross-tenant (refresh nocturn) - **Indexe GIST** pe toate `geom`, **BRIN** pe `created_at` pentru audit - Martin pool: 16 conexiuni per instanță, 2 instanțe behind Traefik LB - **Read replica Postgres** când QPS > 500/s (streaming replication) - **PMTiles** pentru tot ce nu se schimbă zilnic (UAT, administrativ) — mult mai rapid decât Martin live --- ## 11. Scale target an 1 - ~50 arhitecți (Planhub ERP) - ~100 topografi (eTerra.live) - ~2 primării × 5 admin = 10 admin_primaria (SmartCity360) - Citizen public access (SmartCity360) — nedeterminat **Concluzie:** Shop server (128 vCPU, 251GB RAM) = zero probleme la scale-ul ăsta. Rezerve pentru 10x creștere. --- ## 12. Migrare graduală — fără downtime ArchiTools 1. Setup `postgres-gis` container pe shop, config de bază 2. Replicare initial snapshot `architools_db` → `postgres-gis` (pg_dump + restore) 3. Creează schemas noi (`gis_urban`, `tenants`), mută tabele existente (`ALTER TABLE SET SCHEMA`) 4. Creează roluri + RLS policies, testează cu user nou 5. Extrage sync în serviciu standalone — paritate funcțională cu ArchiTools 6. Lansează Martin dedicat pe `tiles.beletage.ro` 7. ArchiTools trece pe endpoint nou (feature flag) 8. Verify paritate + performanță → cutover final 9. eTerra.live + SmartCity360 + Planhub ERP conectate la DB central --- ## 13. Date GIS existente (autoritative) | Tabel | Conținut | Features aprox | |---|---|---| | `gis_uats` | Limite UAT Romania (+ views z0/5/8/12 simplified) | 3181 UAT-uri | | `gis_administrativ` | Intravilan + arii speciale ANCPI | — | | `gis_terenuri` | Parcele cadastrale eTerra | Cluj 774K (extrapolare ~25M total Romania) | | `gis_cladiri` | Clădiri cadastrale eTerra | Cluj — (similar magnitude) | | `gis_terenuri_status` / `gis_cladiri_status` | Parcele/clădiri + flag enrichment/legal | — | | `gis_features` | Raw eTerra (attributes JSON + enrichment JSON) | — | | `gis_sync_rules`, `gis_sync_runs` | Orchestrare sync | — | Status actual: Cluj complet sincronizat (81 UAT, ~8.3h), restul României de populat (estimare 15-25h inițial, 1-2h delta). --- ## 14. Date GIS de adăugat pentru SmartCity360 + ERP **Urbanism PUG:** - `gis_urban.utr` — Unități Teritoriale Referință (code, zone_type, regulation JSON cu POT/CUT/H_max, geom) - `gis_urban.zone_reglementate` — zone funcționale PUG - `gis_urban.zone_protectie` — monumente, arheologie, peisaj, sanitară, hidrologică **PUZ/PUD:** - `gis_urban.puz` / `gis_urban.pud` — doc_ref, HCL_number, approval_date, status, geom perimetru **Administrativ suplimentar:** - `gis_urban.strazi` - `gis_urban.numerotare` - `gis_urban.dotari_publice` (școli, spitale, parcuri) **Fază 2 (opțional):** - Rețele utilități (apă, canal, gaz, electric, telecom) - Cadastru verde (arbori, scuaruri) --- ## 15. Întrebări deschise — TBD ### Tehnice 1. **Supabase vs Postgres raw pe shop** — schemă nouă în Supabase existent (câștig: RLS auto, PostgREST gratis, auth) sau container Postgres separat (izolare totală, simplu)? **Recomandare curentă:** separat. 2. **Domain SaaS** — `eterra.live` deja deținut? `smartcity360.ro` de cumpărat? Subdomain per primărie (`cluj-napoca.smartcity360.ro`) vs path-based (`smartcity360.ro/cluj-napoca`)? 3. **Portal cetățeni SmartCity** — acces complet anonim (zero login, doar rate limit) vs email/captcha pentru abuse prevention? 4. **TTL enrichment** — 90 zile auto-refresh implicit? Topograf poate forța refresh manual? 5. **Backup offsite** — replică Postgres async la CJ2 (10.10.40.x) via VPN? Sau doar MinIO snapshot zilnic? 6. **Migrare ArchiTools** — DB-ul actual rămâne paralel cu noul central, apoi cutover? Sau migrare directă cu downtime controlat? 7. **Martin shared vs per-app** — o singură instanță Martin publică pentru toate app-urile, sau instanțe separate per app (branding URL + izolare)? ### Business / organizaționale 8. **Billing / metering** — eTerra.live SaaS plătit: how measured? Per sync on-demand? Per enrichment request? Per user/lună? 9. **Data ownership** — dacă topograf X face enrichment pe parcela Y, datele sunt ale lui privat sau contribuie automat la pool-ul central public? (răspuns parțial: shared pool, dar GDPR?) 10. **PUG private pentru primării** — ce date specific sunt "private" vs "public"? (ex: observații interne, documentație anexă) 11. **SLA public tiles** — uptime target? Recovery time după outage? --- ## 16. Concluzii curente - Arhitectura e fezabilă pe infrastructura existentă fără hardware nou - Shop server = candidat ideal DB central - Shared enrichment pool = insight major cu ROI imediat - Migrarea se poate face gradual fără downtime - Securitate + multi-tenancy rezolvate cu RLS + Authentik per-app **Next steps când e aprobat:** 1. Răspuns la întrebările deschise (secțiunea 15) 2. Design detaliat schema `gis_urban` (DDL concret) 3. Design API `gis-api.beletage.ro` (endpoints + auth flow) 4. Plan migrare ArchiTools pas-cu-pas 5. POC postgres-gis pe shop + test Martin + 1 app consumer