Files
ArchiTools/docs/plans/001-central-gis-db-multi-app.md
Claude VM 28c870fb12 harden(epay): cart-hygiene invariant uses confirmed cart count + add service architecture plan
- cartCount tracks actual cart rows (decrement only on confirmed delete) so a
  failed cleanup delete can't trigger a false dirty-cart abort.
- docs/plans/006: the multi-tenant CF-service architecture (DB-backed
  fulfiller, account pool, catalog dedup, per-tenant credential model,
  reversible flag flip) — the executable next phase. The Phase-F flag flip is
  gated on the orchestrator fulfiller existing (Plan 003 Faza F was wrong).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:06:06 +03:00

12 KiB
Raw Permalink Blame History

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

-- 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
<primarie>.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_dbpostgres-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 SaaSeterra.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

  1. Billing / metering — eTerra.live SaaS plătit: how measured? Per sync on-demand? Per enrichment request? Per user/lună?

  2. 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?)

  3. PUG private pentru primării — ce date specific sunt "private" vs "public"? (ex: observații interne, documentație anexă)

  4. 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