0c4b91707f
CRITICAL fixes: - Fix SQL injection in geoportal search (template literal in $queryRaw) - Preserve enrichment data during GIS re-sync (upsert update explicit fields only) - Fix ePay version race condition (advisory lock in transaction) - Add requireAuth() to compress-pdf and unlock routes (were unauthenticated) - Remove hardcoded Stirling PDF API key (env vars now required) IMPORTANT fixes: - Add admin role check on registratura debug-sequences endpoint - Fix reserved slot race condition with advisory lock in transaction - Use SSO identity in close-guard-dialog instead of hardcoded "Utilizator" - Storage DELETE catches only P2025 (not found), re-throws real errors - Add onDelete: SetNull for GisFeature → GisSyncRun relation - Move portal-only users to PORTAL_ONLY_USERS env var - Add security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy) - Add periodic cleanup for eTerra/ePay session caches and progress store - Log warning when ePay dataDocument is missing (expiry fallback) Cleanup: - Delete orphaned rgi-test page (1086 lines, unregistered, inaccessible) - Delete legacy/ folder (5 files, unreferenced from src/) - Remove unused ensureBucketExists() from minio-client.ts Documentation: - Optimize CLAUDE.md: 464 → 197 lines (moved per-module details to docs/) - Create docs/ARCHITECTURE-QUICK.md (80 lines: data flow, deps, env vars) - Create docs/MODULE-MAP.md (140 lines: entry points, API routes, cross-deps) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
165 lines
6.3 KiB
Plaintext
165 lines
6.3 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model KeyValueStore {
|
|
id String @id @default(uuid())
|
|
namespace String
|
|
key String
|
|
value Json
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([namespace, key])
|
|
@@index([namespace])
|
|
}
|
|
|
|
// ─── GIS: eTerra ParcelSync ────────────────────────────────────────
|
|
|
|
model GisFeature {
|
|
id String @id @default(uuid())
|
|
layerId String // e.g. TERENURI_ACTIVE, CLADIRI_ACTIVE
|
|
siruta String
|
|
objectId Int // eTerra OBJECTID (unique per layer); negative for no-geometry parcels (= -immovablePk)
|
|
inspireId String?
|
|
cadastralRef String? // NATIONAL_CADASTRAL_REFERENCE
|
|
areaValue Float?
|
|
isActive Boolean @default(true)
|
|
attributes Json // all raw eTerra attributes
|
|
geometry Json? // GeoJSON geometry (Polygon/MultiPolygon)
|
|
geometrySource String? // null = normal GIS sync, "NO_GEOMETRY" = eTerra immovable without GIS geometry
|
|
// NOTE: native PostGIS column 'geom' is managed via SQL trigger (see prisma/postgis-setup.sql)
|
|
// Prisma doesn't need to know about it — trigger auto-populates from geometry JSON
|
|
enrichment Json? // magic data: CF, owners, address, categories, etc.
|
|
enrichedAt DateTime? // when enrichment was last fetched
|
|
syncRunId String?
|
|
projectId String? // link to project tag
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
syncRun GisSyncRun? @relation(fields: [syncRunId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([layerId, objectId])
|
|
@@index([siruta])
|
|
@@index([cadastralRef])
|
|
@@index([layerId, siruta])
|
|
@@index([projectId])
|
|
@@index([geometrySource])
|
|
}
|
|
|
|
model GisSyncRun {
|
|
id String @id @default(uuid())
|
|
siruta String
|
|
uatName String?
|
|
layerId String
|
|
status String @default("pending") // pending | running | done | error
|
|
totalRemote Int @default(0)
|
|
totalLocal Int @default(0)
|
|
newFeatures Int @default(0)
|
|
removedFeatures Int @default(0)
|
|
startedAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
errorMessage String?
|
|
features GisFeature[]
|
|
|
|
@@index([siruta])
|
|
@@index([layerId])
|
|
@@index([siruta, layerId])
|
|
}
|
|
|
|
model GisUat {
|
|
siruta String @id
|
|
name String
|
|
county String?
|
|
workspacePk Int?
|
|
geometry Json? /// EsriGeometry { rings: number[][][] } in EPSG:3844
|
|
areaValue Float? /// Area in sqm from LIMITE_UAT AREA_VALUE field
|
|
lastUpdatedDtm String? /// LAST_UPDATED_DTM from eTerra — for incremental sync
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([name])
|
|
@@index([county])
|
|
}
|
|
|
|
// ─── Registratura: Atomic Sequences + Audit ────────────────────────
|
|
|
|
model RegistrySequence {
|
|
id String @id @default(uuid())
|
|
company String // B, U, S, G (single-letter prefix)
|
|
year Int
|
|
type String // SEQ (shared across directions)
|
|
lastSeq Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([company, year, type])
|
|
@@index([company, year])
|
|
}
|
|
|
|
model RegistryAudit {
|
|
id String @id @default(uuid())
|
|
entryId String
|
|
entryNumber String
|
|
action String // created, updated, reserved_created, reserved_claimed, late_registration, closed, deleted
|
|
actor String
|
|
actorName String?
|
|
company String
|
|
detail Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([entryId])
|
|
@@index([company, createdAt])
|
|
}
|
|
|
|
// ─── ANCPI ePay: CF Extract Orders ──────────────────────────────────
|
|
|
|
model CfExtract {
|
|
id String @id @default(uuid())
|
|
orderId String? // ePay orderId (shared across batch items)
|
|
basketRowId Int? // ePay cart item ID
|
|
nrCadastral String // cadastral number
|
|
nrCF String? // CF number if different
|
|
siruta String? // UAT SIRUTA code
|
|
judetIndex Int // ePay county index (0-41)
|
|
judetName String // county display name
|
|
uatId Int // ePay UAT numeric ID
|
|
uatName String // UAT display name
|
|
prodId Int @default(14200)
|
|
solicitantId String @default("14452")
|
|
status String @default("pending") // pending|queued|cart|searching|ordering|polling|downloading|completed|failed|cancelled
|
|
epayStatus String? // raw ePay status
|
|
idDocument Int? // ePay document ID
|
|
documentName String? // ePay filename
|
|
documentDate DateTime? // when ANCPI generated
|
|
minioPath String? // MinIO object key
|
|
minioIndex Int? // file version index
|
|
creditsUsed Int @default(1)
|
|
immovableId String? // eTerra immovable ID
|
|
immovableType String? // T/C/A
|
|
measuredArea String?
|
|
legalArea String?
|
|
address String?
|
|
gisFeatureId String? // link to GisFeature
|
|
version Int @default(1) // increments on re-order
|
|
expiresAt DateTime? // 30 days after documentDate
|
|
supersededById String? // newer version id
|
|
requestedBy String?
|
|
errorMessage String?
|
|
pollAttempts Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
completedAt DateTime?
|
|
|
|
@@index([nrCadastral])
|
|
@@index([status])
|
|
@@index([orderId])
|
|
@@index([gisFeatureId])
|
|
@@index([createdAt])
|
|
@@index([nrCadastral, version])
|
|
}
|