audit: production safety fixes, cleanup, and documentation overhaul

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>
This commit is contained in:
AI Assistant
2026-03-26 06:40:34 +02:00
parent c012adaa77
commit 0c4b91707f
25 changed files with 579 additions and 3405 deletions
+197 -463
View File
@@ -1,463 +1,197 @@
# ArchiTools — Project Context for AI Assistants
> This file provides all context needed for Claude Code, Sonnet, or any AI model to work on this project from scratch.
---
## Quick Start
```bash
npm install
npm run dev # http://localhost:3000
npx next build # verify zero errors before pushing
git push origin main # auto-deploys via Portainer webhook
```
---
## Project Overview
**ArchiTools** is a modular internal web dashboard for an architecture/engineering office group of 3 companies:
- **Beletage** (architecture)
- **Urban Switch** (urbanism)
- **Studii de Teren** (geotechnics)
It runs on two on-premise servers, containerized with Docker, managed via Portainer CE.
### Stack
| Layer | Technology |
| ------------ | ---------------------------------------------------------------------------- |
| Framework | Next.js 16.x, App Router, TypeScript (strict) |
| Styling | Tailwind CSS v4, shadcn/ui |
| Database | PostgreSQL (10.10.10.166:5432) via Prisma v6 ORM |
| Storage | `DatabaseStorageAdapter` (PostgreSQL) — localStorage fallback available |
| File Storage | MinIO (10.10.10.166:9002 API / 9003 UI) — client configured, adapter pending |
| Auth | NextAuth v4 + Authentik OIDC (auth.beletage.ro) |
| Proxy | Traefik v3 on `10.10.10.199` (proxy server), SSL via Let's Encrypt |
| Deploy | Docker multi-stage, Portainer CE on `10.10.10.166` (satra) |
| Repo | Gitea at `https://git.beletage.ro/gitadmin/ArchiTools` |
| Language | Code in **English**, UI in **Romanian** |
### Architecture Principles
- **Module platform, not monolith** — each module isolated with own types/services/hooks/components
- **Feature flags** gate module loading (disabled = zero bundle cost)
- **Storage abstraction**: `StorageService` interface with adapters (database default via Prisma, localStorage fallback)
- **Cross-module tagging system** as shared service
- **Auth via Authentik SSO** — NextAuth v4 + OIDC, group→role/company mapping
- **All entities** include `visibility` / `createdBy` fields from day one
- **Company logos** — theme-aware (light/dark variants), dual-rendered for SSR safety
---
## Repository Structure
```
src/
├── app/ # Routing only (thin wrappers)
│ ├── (modules)/ # Module route pages
│ └── layout.tsx # App shell
├── core/ # Platform services
│ ├── module-registry/ # Module registration + types
│ ├── feature-flags/ # Flag evaluation + env override
│ ├── storage/ # StorageService + adapters
│ │ └── adapters/ # localStorage adapter (+ future DB/MinIO)
│ ├── tagging/ # Cross-module tag service
│ ├── i18n/ # Romanian translations
│ ├── theme/ # Light/dark theme
│ └── auth/ # Auth types + stub (future Authentik)
├── modules/ # Module business logic
│ ├── <module-name>/
│ │ ├── components/ # Module UI components
│ │ ├── hooks/ # Module-specific hooks
│ │ ├── services/ # Module business logic
│ │ ├── types.ts # Module types
│ │ ├── config.ts # Module metadata
│ │ └── index.ts # Public exports
│ └── ...
├── shared/ # Shared UI
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── layout/ # Sidebar, Header
│ │ └── common/ # Reusable app components
│ ├── hooks/ # Shared hooks
│ └── lib/ # Utils (cn, etc.)
├── config/ # Global config
│ ├── modules.ts # Module registry entries
│ ├── flags.ts # Default feature flags
│ ├── navigation.ts # Sidebar nav structure
│ └── companies.ts # Company definitions
docs/ # 16 internal technical docs
legacy/ # Original HTML tools for reference
```
---
## Implemented Modules (16 total — 14 original + 2 new)
| # | Module | Route | Version | Key Features |
| --- | ---------------------- | --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | **Dashboard** | `/` | 0.1.0 | KPI cards (6), activity feed (last 20), module grid, external tools |
| 2 | **Email Signature** | `/email-signature` | 0.1.0 | Multi-company branding, address toggle (BTG/US/SDT), live preview, zoom/copy/download |
| 3 | **Word XML Generator** | `/word-xml` | 0.1.0 | Category-based XML gen, simple/advanced mode, ZIP export |
| 4 | **Registratura** | `/registratura` | 0.5.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, **legal deadline tracking** (6 categories, 18 types), recipient registration, document expiry, **NAS network path attachments** (A/O/P/T drives, copy-to-clipboard), **detail sheet side panel**, **configurable column visibility**, **QuickLook attachment preview** (images: zoom/pan, PDFs: native viewer, multi-file navigation), **email notifications** (Brevo SMTP daily digest, per-user preferences), **compact registry numbers** (single-letter company badge + direction arrow + plain number) |
| 5 | **Tag Manager** | `/tag-manager` | 0.2.0 | CRUD tags, category/scope/color, US/SDT seeds, mandatory categories, **ManicTime bidirectional sync** |
| 6 | **IT Inventory** | `/it-inventory` | 0.2.0 | Dynamic equipment types, rented status (purple pulse), **42U rack visualization**, type/status/company filters |
| 7 | **Address Book** | `/address-book` | 0.2.0 | CRUD contacts (person OR institution), card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)**, **name OR company required** (flexible validation), **ContactPerson with department field**, **quick contact from Registratura** (persons + institutions) |
| 8 | **Password Vault** | `/password-vault` | 0.4.0 | CRUD credentials, 9 categorii cu iconițe, **WiFi QR code real**, context-aware form, strength meter, company scope, **AES-256-GCM encryption**, **utilizatori multipli per intrare** (VaultUser[]: username/password/email/notes, colapsibil în form, badge în list) |
| 9 | **Mini Utilities** | `/mini-utilities` | 0.4.0 | Text case, char counter, percentage, **TVA calculator (cotă configurabilă: 5/9/19/21% + custom)**, area converter, U→R, num→text, artifact cleaner, MDLPA, **PDF compression** (qpdf local lossless + iLovePDF API cloud lossy, streaming upload for large files), PDF unlock, **DWG→DXF**, OCR, color palette, **paste on all drop zones**, **thermal drag-drop reorder**, **Calculator scară desen** (real cm↔desen mm, 7 preseturi 1:20..1:5000 + custom) |
| 10 | **Prompt Generator** | `/prompt-generator` | 0.2.0 | Template-driven prompt builder, **18 templates** (14 text + 4 image), search bar, target type filter |
| 11 | **Digital Signatures** | `/digital-signatures` | 0.1.0 | CRUD assets, drag-and-drop file upload, tag chips |
| 12 | **Word Templates** | `/word-templates` | 0.1.0 | Template library, 8 categories, version tracking, .docx placeholder auto-detection |
| 13 | **AI Chat** | `/ai-chat` | 0.2.0 | Multi-provider (OpenAI/Claude/Ollama/demo), **project linking via Tag Manager**, provider status badge |
| 14 | **Hot Desk** | `/hot-desk` | 0.1.1 | 4 desks, week-ahead calendar, room layout (window+door proportioned), reserve/cancel |
| 15 | **ParcelSync** | `/parcel-sync` | 0.6.0 | eTerra ANCPI integration, **PostGIS database**, background sync, 23-layer catalog, enrichment pipeline, owner search, **per-UAT analytics dashboard**, **health check + maintenance detection**, **ANCPI ePay CF extract ordering** (batch orders, MinIO PDF storage, dedup protection, credit tracking), **static WORKSPACE_TO_COUNTY mapping**, **GisUat geometry select optimization**, **feature count cache (5-min TTL)** |
| 16 | **Visual Copilot** | `/visual-copilot` | 0.1.0 | AI-powered image analysis — **developed in separate repo** (`https://git.beletage.ro/gitadmin/vim`), placeholder in ArchiTools, will be merged as module later |
### Registratura — Legal Deadline Tracking (Termene Legale)
The Registratura module includes a full legal deadline tracking engine for Romanian construction permitting:
- **18 deadline types** across 6 categories (Certificat, Avize, Completari, Urbanism, Autorizare, Litigii)
- **Working days vs calendar days** with Romanian public holiday support (including Orthodox Easter via Meeus algorithm)
- **Chain deadlines** (resolving one prompts adding the next — e.g., CU analiza → emitere, PUZ/PUD analiza → post-CTATU → emitere)
- **Tacit approval** (auto-detected when overdue + applicable type)
- **Tabbed UI**: "Registru" tab (existing registry) + "Termene legale" tab (deadline dashboard)
- **Email notifications**: daily digest via Brevo SMTP, per-user opt-in/opt-out preferences, N8N cron trigger
Key files:
- `services/working-days.ts` — Romanian holidays, `addWorkingDays()`, `isWorkingDay()`
- `services/deadline-catalog.ts` — 18 `DeadlineTypeDef` entries across 6 categories
- `services/deadline-service.ts``createTrackedDeadline()`, `resolveDeadline()`, `aggregateDeadlines()`
- `components/attachment-preview.tsx` — QuickLook-style fullscreen preview (images: zoom/pan, PDFs: blob URL iframe, multi-file nav)
- `components/deadline-dashboard.tsx` — Stats + filters + table
- `components/deadline-add-dialog.tsx` — 3-step wizard (category → type → date preview)
- `components/notification-preferences.tsx` — Bell button + dialog with per-type toggles
- `components/registry-table.tsx``CompactNumber` component: single-letter company badge (B/U/S/G), direction arrow (↓ intrat / ↑ iesit), plain number
### Address Book — Flexible Contact Model
The Address Book supports both persons and institutions:
- **Flexible validation**: either `name` OR `company` required (not both mandatory)
- **Auto-type detection**: when only company is set via quick-create, type defaults to "institution"
- **ContactPerson sub-entities**: each has `name`, `department`, `role`, `email`, `phone`
- **Quick contact creation from Registratura**: inline dialog with name + company + phone + email
- **Display logic**: if no name, company shows as primary; if both, shows "Name (Company)"
- **Creatable types**: dropdown with defaults (client/supplier/institution/collaborator/internal) + user-created custom types
Key files:
- `modules/address-book/types.ts``AddressContact`, `ContactPerson` interfaces
- `modules/address-book/components/address-book-module.tsx` — Full UI (cards, detail dialog, form)
- `modules/address-book/hooks/use-contacts.ts` — Storage hook with search/filter
- `modules/address-book/services/vcard-export.ts` — vCard 3.0 export
- `modules/registratura/components/quick-contact-dialog.tsx` — Quick create from registry
### PDF Compression — Dual Mode (Local + Cloud)
Two compression routes, both with streaming upload support for large files (tested up to 287MB):
- **Local (qpdf)**: lossless structural optimization — stream compression, object dedup, linearization. Safe, no font corruption. Typical reduction: 3-15%.
- **Cloud (iLovePDF API)**: lossy image re-compression via iLovePDF REST API. Levels: extreme/recommended/low. Typical reduction: 50-91%. Requires `ILOVEPDF_PUBLIC_KEY` env var.
**Architecture** (zero-memory for any file size):
1. `parseMultipartUpload()` streams request body to disk (constant 64KB memory)
2. Scans raw file for multipart boundaries using `findInFile()` with 64KB sliding window
3. Stream-copies PDF bytes to separate file
4. Route handler processes (qpdf exec or iLovePDF API) and streams response back
**Critical gotchas**:
- Middleware body buffering: `api/compress-pdf` routes are **excluded from middleware matcher** (middleware buffers entire body at 10MB default)
- Auth: route-level `requireAuth()` instead of middleware (in `auth-check.ts`)
- Unicode filenames: `Content-Disposition` header uses `encodeURIComponent()` to avoid ByteString errors with Romanian chars (Ș, Ț, etc.)
- Ghostscript `-sDEVICE=pdfwrite` destroys font encodings — **never use GS for compression**, only qpdf
Key files:
- `app/api/compress-pdf/parse-upload.ts` — Streaming multipart parser (zero memory)
- `app/api/compress-pdf/extreme/route.ts` — qpdf local compression
- `app/api/compress-pdf/cloud/route.ts` — iLovePDF API integration
- `app/api/compress-pdf/auth-check.ts` — Shared auth for routes excluded from middleware
### Email Notifications (Brevo SMTP)
Platform-level notification service for daily email digests:
- **Brevo SMTP relay** via nodemailer (port 587, STARTTLS)
- **N8N cron**: weekdays 8:00 → POST `/api/notifications/digest` with Bearer token
- **Per-user preferences**: stored in KeyValueStore (`notifications` namespace), toggle global opt-out + 3 notification types
- **Digest content**: urgent deadlines (<=5 days), overdue deadlines, expiring documents (CU/AC)
- **HTML email**: inline-styled table layout, color-coded rows (red/yellow/blue), per-company grouping
- **Sender**: "Alerte Termene" &lt;noreply@beletage.ro&gt;, test mode via `?test=true` query param
Key files:
- `src/core/notifications/types.ts``NotificationType`, `NotificationPreference`, `DigestSection`, `DigestItem`
- `src/core/notifications/email-service.ts` — Nodemailer transport singleton (Brevo SMTP)
- `src/core/notifications/notification-service.ts``runDigest()`, `buildCompanyDigest()`, `renderDigestHtml()`, preference CRUD
- `src/app/api/notifications/digest/route.ts` — POST endpoint (N8N cron, Bearer auth)
- `src/app/api/notifications/preferences/route.ts` — GET/PUT (user session auth)
### ParcelSync — eTerra ANCPI GIS Integration
The ParcelSync module connects to Romania's national eTerra/ANCPI cadastral system:
- **eTerra API client** (`eterra-client.ts`): form-post auth, JSESSIONID cookie jar, session caching (9min TTL), auto-relogin, paginated fetching with `maxRecordCount=1000` + fallback page sizes (500, 200)
- **23-layer catalog** (`eterra-layers.ts`): TERENURI_ACTIVE, CLADIRI_ACTIVE, LIMITE_UAT, etc. organized in 6 categories
- **PostGIS storage**: `GisFeature` model with geometry column, SIRUTA-based partitioning, `enrichment` JSONB field
- **Background sync**: long-running jobs via server singleton, progress polling (2s), phase tracking (fetch → save → enrich)
- **Enrichment pipeline** (`enrich-service.ts`): hits eTerra `/api/immovable/list` per parcel to extract NR_CAD, NR_CF, PROPRIETARI, SUPRAFATA, INTRAVILAN, CATEGORIE_FOLOSINTA, HAS_BUILDING, etc.
- **Owner search**: DB-first (ILIKE on enrichment JSON) with eTerra API fallback
- **Per-UAT dashboard**: SQL aggregates (area stats, intravilan/extravilan, land use, top owners), CSS-only visualizations (donut ring, bar charts)
- **Health check** (`eterra-health.ts`): pings `eterra.ancpi.ro` every 3min, detects maintenance by keywords in HTML response, blocks login when down, UI shows amber "Mentenanță" state
- **ANCPI ePay CF extract ordering**: batch orders via `epay-client.ts`, PDF storage to MinIO, dedup protection (queue + API level), credit tracking
- **Static county mapping**: `WORKSPACE_TO_COUNTY` in `county-refresh.ts` — 42 verified entries, preferred over unreliable nomenclature API
- **Performance**: GisUat queries use `select` to exclude geometry column; feature counts cached 5-min TTL
- **Test UAT**: Feleacu (SIRUTA 57582, ~30k immovables, ~8k GIS features)
Key files:
- `services/eterra-client.ts` — API client (~1000 lines), session cache, pagination, retry
- `services/eterra-layers.ts` — 23-layer catalog with categories
- `services/sync-service.ts` — Layer sync engine with progress tracking
- `services/enrich-service.ts` — Enrichment pipeline (FeatureEnrichment type)
- `services/eterra-health.ts` — Health check singleton, maintenance detection
- `services/session-store.ts` — Server-side session management
- `services/epay-client.ts` — ePay HTTP client (login, cart, metadata, submit, poll, download)
- `services/epay-queue.ts` — Batch queue with dedup protection
- `services/epay-storage.ts` — MinIO storage helpers for CF extract PDFs
- `services/epay-counties.ts` — County index mapping (eTerra county name → ePay alphabetical index 0-41)
- `app/api/eterra/session/county-refresh.ts` — Static `WORKSPACE_TO_COUNTY` mapping, LIMITE_UAT geometry refresh
- `components/parcel-sync-module.tsx` — Main UI (~4100 lines), 5 tabs (Export/Layers/Search/DB/Extrase CF)
- `components/uat-dashboard.tsx` — Per-UAT analytics dashboard (CSS-only charts)
- `components/epay-tab.tsx` — CF extract ordering tab
- `components/epay-connect.tsx` — ePay connection widget
---
## Infrastructure
### Server: `satra` — 10.10.10.166 (Ubuntu, app server)
| Service | Port | Purpose |
| ----------------------- | ---------------------- | ----------------------------------- |
| **ArchiTools** | 3000 | This app (tools.beletage.ro) |
| **Gitea** | 3002 | Git hosting (git.beletage.ro) |
| **PostgreSQL** | 5432 | App database (Prisma ORM) |
| **Portainer CE** | 9000 | Docker management + deploy |
| **Uptime Kuma** | 3001 | Service monitoring |
| **MinIO** | 9002 (API) / 9003 (UI) | Object storage |
| **Authentik** | 9100 | SSO (auth.beletage.ro) — **active** |
| **N8N** | 5678 | Workflow automation (daily digest) |
| **Stirling PDF** | 8087 | PDF tools |
| **IT-Tools** | 8085 | Developer utilities |
| **FileBrowser** | 8086 | File management |
| **Netdata** | 19999 | System monitoring |
| **Dozzle** | 9999 | Docker log viewer |
| **CrowdSec** | 8088 | Security |
### Server: `proxy` — 10.10.10.199 (Traefik reverse proxy)
| Config | Path / Value |
| ----------------------- | ---------------------------------------- |
| **Static config** | `/opt/traefik/traefik.yml` |
| **Dynamic configs** | `/opt/traefik/dynamic/` (file provider, `watch: true`) |
| **ArchiTools route** | `/opt/traefik/dynamic/tools.yml` |
| **SSL** | Let's Encrypt ACME, HTTP challenge |
| **Timeouts** | `readTimeout: 600s`, `writeTimeout: 600s`, `idleTimeout: 600s` on `websecure` entrypoint |
| **Response forwarding** | `flushInterval: 100ms` (streaming support) |
**IMPORTANT**: Default Traefik v2.11+ has 60s `readTimeout` — breaks large file uploads. Must set explicitly in static config.
### Deployment Pipeline
```
git push origin main
→ Gitea webhook fires
→ Portainer CE: Stacks → architools → "Pull and redeploy"
→ Toggle "Re-pull image and redeploy" ON → click "Update"
→ Portainer re-clones git repo + Docker multi-stage build (~2 min)
→ Container starts on :3000
→ Traefik routes tools.beletage.ro → http://10.10.10.166:3000
```
**Portainer CE deploy**: NOT automatic. Must manually click "Pull and redeploy" in Portainer UI after each push. The stack is configured from git repo `http://10.10.10.166:3002/gitadmin/ArchiTools`.
### Docker
- `Dockerfile`: 3-stage build (deps → builder → runner), `node:22-alpine`, non-root user
- Runner stage installs: `gdal gdal-tools ghostscript qpdf` (for PDF compression, GIS)
- `Dockerfile` includes `npx prisma generate` before build step
- `docker-compose.yml`: single service, port 3000, **all env vars hardcoded** (Portainer CE can't inject env vars)
- `output: 'standalone'` in `next.config.ts` is **required**
- `@prisma/client` must be in `dependencies` (not devDependencies) for runtime
---
## Development Rules
### TypeScript Strict Mode Gotchas
- `array.split()[0]` returns `string | undefined` — use `.slice(0, 10)` instead
- `Record<string, T>[key]` returns `T | undefined` — always guard with null check
- Spread of possibly-undefined objects: `{ ...obj[key], field }` — check existence first
- lucide-react Icons: cast through `unknown``React.ComponentType<{ className?: string }>`
- `arr[0]` is `T | undefined` even after `arr.length > 0` check — assign to const first: `const first = arr[0]; if (first) { ... }`
- Prisma `$queryRaw` returns `unknown[]` — always cast with `as Array<{ field: type }>` and guard access
- `?? ""` on an object field typed `{}` produces `{}` not `string` — use explicit `typeof x === 'string'` or `'number'` check
### Conventions
- **Code**: English
- **UI text**: Romanian
- **Components**: functional, `'use client'` directive where needed
- **State**: localStorage via `useStorage('module-name')` hook
- **IDs**: `uuid v4`
- **Dates**: ISO strings (`YYYY-MM-DD` for display, full ISO for timestamps)
- **No emojis** in code or UI unless explicitly requested
### Storage Performance Rules
- **NEVER** use `storage.list()` followed by `storage.get()` in a loop — this is an N+1 query bug
- `list()` fetches ALL items (keys+values) from DB but discards values, then each `get()` re-fetches individually
- **ALWAYS** use `storage.exportAll()` (namespaced) or `storage.export(namespace)` (service-level) to batch-load
- Filter items client-side after a single fetch: `for (const [key, value] of Object.entries(all)) { ... }`
- After mutations (add/update), either do optimistic local state update or a single `refresh()` — never both
- **NEVER store large binary data (base64 files) inside entity JSON** — this makes list loading transfer tens of MB
- For modules with attachments: use `exportAll({ lightweight: true })` for listing, `storage.get()` for single-entry full load
- The API `?lightweight=true` parameter strips `data`/`fileData` strings >1KB from JSON values server-side
- Future: move file data to MinIO; only store metadata (name, size, type, url) in the entity JSON
### Module Development Pattern
Every module follows:
```
src/modules/<name>/
├── components/ # React components
├── hooks/ # Custom hooks (use-<name>.ts)
├── services/ # Business logic (pure functions)
├── types.ts # TypeScript interfaces
├── config.ts # ModuleConfig metadata
└── index.ts # Public exports
```
### Middleware & Large Upload Routes
- Next.js middleware buffers the **entire request body** even if it only reads cookies/headers
- Default middleware body limit is 10MB — any upload route handling large files MUST be excluded
- Excluded routes pattern in `src/middleware.ts` matcher: `api/auth|api/notifications/digest|api/compress-pdf`
- Excluded routes handle auth via `requireAuth()` helper (`src/app/api/compress-pdf/auth-check.ts`)
- To add a new large-upload route: (1) add to middleware matcher exclusion, (2) add `requireAuth()` call in route handler
- `next.config.ts` has `experimental: { middlewareClientMaxBodySize: '500mb' }` but this is unreliable with `output: 'standalone'`
### eTerra / External API Rules
- **ArcGIS REST API** has `maxRecordCount=1000` — always paginate with `resultOffset`/`resultRecordCount`
- **eTerra sessions expire after ~10min** — session cache TTL is 9min, auto-relogin on 401/redirect
- **eTerra goes into maintenance regularly** — health check must detect and block login attempts
- **Never hardcode timeouts too low** — eTerra 1000-feature geometry pages can take 60-90s; default is 120s
- **CookieJar + axios-cookiejar-support** required for eTerra auth (JSESSIONID tracking)
- **Page size fallbacks**: if 1000 fails, retry with 500, then 200
- **WORKSPACE_TO_COUNTY is the authoritative county mapping** — static 42-entry map in `county-refresh.ts`, preferred over `fetchCounties()` which 404s intermittently
- **GisUat.geometry is huge** — always use Prisma `select` to exclude it in list queries; forgetting this turns 50ms into 5+ seconds
- **Feature counts are expensive** — cached in global with 5-min TTL in UATs route; returns stale data while refreshing
### ANCPI ePay Rules
- **ePay county IDs = eTerra WORKSPACE_IDs** (CLUJ=127, ALBA=10) — zero discovery calls needed
- **ePay UAT IDs = SIRUTA codes** — use `GisUat.workspacePk` + `siruta` directly
- **EpayJsonInterceptor uses form-urlencoded** (NOT JSON body) — `reqType=nomenclatorUAT&countyId=127`
- **saveProductMetadataForBasketItem uses multipart/form-data** (form-data npm package)
- **Document IDs are HTML-encoded** in ShowOrderDetails — `&quot;idDocument&quot;:47301767` must be decoded before JSON parse
- **ePay auth is OpenAM** — gets `AMAuthCookie`, then navigate to `http://` (not https) for JSESSIONID
- **MinIO metadata must be ASCII** — strip diacritics from values before storing
- Env vars: `ANCPI_USERNAME`, `ANCPI_PASSWORD`, `ANCPI_BASE_URL`, `ANCPI_LOGIN_URL`, `ANCPI_DEFAULT_SOLICITANT_ID`, `MINIO_BUCKET_ANCPI`
### Before Pushing
1. `npx next build` — must pass with zero errors
2. Test the feature manually on `localhost:3000`
3. Commit with descriptive message
4. `git push origin main` — Portainer auto-deploys
---
## Company IDs
| ID | Name | Prefix |
| ----------------- | --------------- | ------ |
| `beletage` | Beletage | B |
| `urban-switch` | Urban Switch | US |
| `studii-de-teren` | Studii de Teren | SDT |
| `group` | Grup | G |
---
## Current Integrations
| Feature | Status | Notes |
| -------------------- | ---------------------- | ------------------------------------------------------------------------------------------ |
| **Authentik SSO** | ✅ Active | NextAuth v4 + OIDC, group→role/company mapping |
| **PostgreSQL** | ✅ Active | Prisma ORM, `KeyValueStore` model, `/api/storage` route |
| **MinIO** | Client configured | 10.10.10.166:9002, bucket `tools`, adapter pending |
| **AI Chat API** | ✅ Multi-provider | `/api/ai-chat` — OpenAI/Claude/Ollama/demo; needs API key env |
| **Vault Encryption** | ✅ Active | AES-256-GCM server-side, `/api/vault`, ENCRYPTION_SECRET env |
| **ManicTime Sync** | ✅ Implemented | `/api/manictime` — bidirectional Tags.txt sync, needs SMB mount |
| **NAS Paths** | ✅ Active | `\\newamun` (10.10.10.10), drives A/O/P/T, hostname+IP fallback, `src/config/nas-paths.ts` |
| **eTerra ANCPI** | ✅ Active | ParcelSync module, `eterra-client.ts`, health check + maintenance detection |
| **ANCPI ePay** | ✅ Active | CF extract ordering, `epay-client.ts`, MinIO PDF storage, batch queue + dedup, `/api/ancpi/*` routes |
| **PostGIS** | ✅ Active | `GisFeature` model, geometry storage, spatial queries, used by ParcelSync |
| **Email Notifications** | ✅ Implemented | Brevo SMTP daily digest, `/api/notifications/digest` + `/preferences`, N8N cron trigger |
| **N8N automations** | ✅ Active (digest cron) | Daily digest cron `0 8 * * 1-5`, Bearer token auth, future: backups, workflows |
| **iLovePDF API** | ✅ Active | Cloud PDF compression, `ILOVEPDF_PUBLIC_KEY` env, free tier 250 files/month |
| **qpdf** | ✅ Active | Local lossless PDF optimization, installed in Docker image (`apk add qpdf`) |
---
## Model Recommendations
| Task Type | Claude | OpenAI | Google | Notes |
| ----------------------------- | -------------- | ------------- | ---------------- | ----------------------------------------------- |
| **Bug fixes, config** | Haiku 4.5 | GPT-4o-mini | Gemini 2.5 Flash | Fast, cheap |
| **Features, tests, UI** | **Sonnet 4.6** | GPT-5.2 | Gemini 3 Flash | Best value — Opus-class quality at Sonnet price |
| **New modules, architecture** | Opus 4.6 | GPT-5.3-Codex | Gemini 3 Pro | Complex multi-file, business logic |
**Default: Sonnet 4.6** for most work. See `ROADMAP.md` for per-task recommendations.
### Session Handoff Tips
- Read this `CLAUDE.md` first — it has all context
- Read `ROADMAP.md` for the complete task list with dependencies
- Check `docs/` for deep dives on specific systems
- Check `src/modules/<name>/types.ts` before modifying any module
- Always run `npx next build` before committing
- Push to `main` → Portainer auto-deploys via Gitea webhook
- The 16 docs in `docs/` total ~10,600 lines — search them for architecture questions
---
## Documentation Index
| Doc | Path | Content |
| ------------------- | ------------------------------------------ | -------------------------------------------- |
| System Architecture | `docs/architecture/SYSTEM-ARCHITECTURE.md` | Overall architecture, module platform design |
| Module System | `docs/architecture/MODULE-SYSTEM.md` | Module registry, lifecycle, config format |
| Feature Flags | `docs/architecture/FEATURE-FLAGS.md` | Flag system, env overrides |
| Storage Layer | `docs/architecture/STORAGE-LAYER.md` | StorageService interface, adapters |
| Tagging System | `docs/architecture/TAGGING-SYSTEM.md` | Cross-module tags |
| Security & Roles | `docs/architecture/SECURITY-AND-ROLES.md` | Visibility, auth, roles |
| Module Dev Guide | `docs/guides/MODULE-DEVELOPMENT.md` | How to create a new module |
| HTML Integration | `docs/guides/HTML-TOOL-INTEGRATION.md` | Legacy tool migration |
| UI Design System | `docs/guides/UI-DESIGN-SYSTEM.md` | Design tokens, component patterns |
| Docker Deployment | `docs/guides/DOCKER-DEPLOYMENT.md` | Full Docker/Portainer/Nginx guide |
| Coding Standards | `docs/guides/CODING-STANDARDS.md` | TS strict, naming, patterns |
| Testing Strategy | `docs/guides/TESTING-STRATEGY.md` | Testing approach |
| Configuration | `docs/guides/CONFIGURATION.md` | Env vars, flags, companies |
| Data Model | `docs/DATA-MODEL.md` | All entity schemas |
| Repo Structure | `docs/REPO-STRUCTURE.md` | Directory layout |
| Prompt Generator | `docs/modules/PROMPT-GENERATOR.md` | Prompt module deep dive |
# ArchiTools — Project Context for AI Assistants
## Quick Start
```bash
npm install
npm run dev # http://localhost:3000
npx next build # verify zero errors before pushing
git push origin main # manual redeploy via Portainer UI
```
---
## Project Overview
**ArchiTools** is a modular internal web dashboard for 3 architecture/engineering companies:
**Beletage** (architecture), **Urban Switch** (urbanism), **Studii de Teren** (geotechnics).
Production: `tools.beletage.ro` — Docker on-premise, Portainer CE, Traefik v3 proxy.
### Stack
| Layer | Technology |
| ---------- | ------------------------------------------------------- |
| Framework | Next.js 16.x, App Router, TypeScript (strict) |
| Styling | Tailwind CSS v4, shadcn/ui |
| Database | PostgreSQL + PostGIS via Prisma v6 ORM |
| Storage | `DatabaseStorageAdapter` (Prisma), localStorage fallback |
| Files | MinIO (S3-compatible object storage) |
| Auth | NextAuth v4 + Authentik OIDC (auth.beletage.ro) |
| Deploy | Docker multi-stage → Portainer CE → Traefik v3 + SSL |
| Repo | Gitea at `git.beletage.ro/gitadmin/ArchiTools` |
| Language | Code: **English**, UI: **Romanian** |
### Architecture Principles
- **Module platform** — each module isolated: own types/services/hooks/components
- **Feature flags** gate loading (disabled = zero bundle cost)
- **Storage abstraction** via `StorageService` interface + adapters
- **Auth via Authentik SSO** — group → role/company mapping
- **All entities** include `visibility` / `createdBy` from day one
---
## Repository Structure
```
src/
├── app/(modules)/ # Route pages (thin wrappers)
├── core/ # Platform: auth, storage, flags, tagging, i18n, theme
├── modules/<name>/ # Module business logic (see MODULE-MAP.md)
│ ├── components/ # UI components
│ ├── hooks/ # Module hooks
│ ├── services/ # Business logic
│ ├── types.ts # Interfaces
│ ├── config.ts # Module metadata
│ └── index.ts # Public exports
├── shared/components/ # ui/ (shadcn), layout/ (sidebar/header), common/
├── config/ # modules.ts, flags.ts, navigation.ts, companies.ts
docs/ # Architecture, guides, module deep-dives
```
---
## Modules (17 total)
| Module | Route | Key Features |
| ------------------ | ------------------- | --------------------------------------------------- |
| Dashboard | `/` | KPI cards, activity feed, module grid |
| Email Signature | `/email-signature` | Multi-company, live preview, copy/download |
| Word XML | `/word-xml` | Category-based XML, simple/advanced, ZIP export |
| Registratura | `/registratura` | Registry CRUD, legal deadlines, notifications, NAS |
| Tag Manager | `/tag-manager` | Tags CRUD, ManicTime sync |
| IT Inventory | `/it-inventory` | Equipment, rack visualization, filters |
| Address Book | `/address-book` | Contacts, vCard, Registratura integration |
| Password Vault | `/password-vault` | AES-256-GCM encrypted, WiFi QR, multi-user |
| Mini Utilities | `/mini-utilities` | 12+ tools: PDF compress, OCR, converters, calc |
| Prompt Generator | `/prompt-generator` | 18 templates, text + image targets |
| Digital Signatures | `/digital-signatures` | Assets CRUD, file upload, tags |
| Word Templates | `/word-templates` | Template library, .docx placeholder detection |
| AI Chat | `/ai-chat` | Multi-provider (OpenAI/Claude/Ollama) |
| Hot Desk | `/hot-desk` | 4 desks, week calendar, room layout |
| ParcelSync | `/parcel-sync` | eTerra ANCPI, PostGIS, enrichment, ePay ordering |
| Geoportal | `/geoportal` | MapLibre viewer, parcel search, UAT layers |
| Visual CoPilot | `/visual-copilot` | Placeholder — separate repo |
See `docs/MODULE-MAP.md` for entry points, API routes, and cross-module deps.
---
## Development Rules
### TypeScript Strict Mode Gotchas
- `arr[0]` is `T | undefined` even after length check — assign to const first
- `Record<string, T>[key]` returns `T | undefined` — always null-check
- Spread of possibly-undefined: `{ ...obj[key] }` — check existence first
- lucide-react Icons: cast through `unknown``React.ComponentType<{ className?: string }>`
- Prisma `$queryRaw` returns `unknown[]` — cast with `as Array<{ field: type }>`
- `?? ""` on `{}` field produces `{}` not `string` — use `typeof` check
### Conventions
- **Code**: English | **UI text**: Romanian | **IDs**: uuid v4
- **Dates**: ISO strings (`YYYY-MM-DD` display, full ISO timestamps)
- **Components**: functional, `'use client'` where needed
- **No emojis** in code or UI
### Storage Performance (CRITICAL)
- **NEVER** `storage.list()` + `storage.get()` in loop — N+1 bug
- **ALWAYS** use `storage.exportAll()` or `storage.export(namespace)` for batch-load
- **NEVER** store base64 files in entity JSON — use `lightweight: true` for listing
- After mutations: optimistic update OR single `refresh()` — never both
### Middleware & Large Uploads
- Middleware buffers entire body — exclude large-upload routes from matcher
- Excluded routes: `api/auth|api/notifications/digest|api/compress-pdf|api/address-book|api/projects`
- Excluded routes use `requireAuth()` from `auth-check.ts` instead
- To add new upload route: (1) exclude from middleware, (2) add `requireAuth()`
### eTerra / ANCPI Rules
- ArcGIS: paginate with `resultOffset`/`resultRecordCount` (max 1000)
- Sessions expire ~10min — cache TTL 9min, auto-relogin on 401
- Health check detects maintenance — block login when down
- `WORKSPACE_TO_COUNTY` (42 entries in `county-refresh.ts`) is authoritative
- `GisUat.geometry` is huge — always `select` to exclude in list queries
- Feature counts cached 5-min TTL
- ePay: form-urlencoded body, OpenAM auth, MinIO metadata must be ASCII
### Before Pushing
1. `npx next build` — zero errors
2. Test on `localhost:3000`
3. Commit with descriptive message
4. `git push origin main` → manual Portainer redeploy
---
## Common Pitfalls (Top 10)
1. **Middleware body buffering** — upload routes >10MB must be excluded from matcher
2. **N+1 storage queries** — use `exportAll()`, never `list()` + `get()` loop
3. **GisUat geometry in queries** — exclude with `select`, or 50ms → 5+ seconds
4. **Enrichment data loss on re-sync** — upsert must preserve enrichment field
5. **Ghostscript corrupts fonts** — use qpdf for PDF compression, never GS
6. **eTerra timeout too low** — geometry pages need 60-90s; default 120s
7. **Traefik 60s readTimeout** — must set 600s in static config for uploads
8. **Portainer CE can't inject env vars** — all env in docker-compose.yml
9. **`@prisma/client` in dependencies** (not devDeps) — runtime requirement
10. **`output: 'standalone'`** in next.config.ts — required for Docker
---
## Infrastructure Quick Reference
| Service | Address | Purpose |
| ----------- | ------------------------ | -------------------------- |
| App | 10.10.10.166:3000 | ArchiTools (tools.beletage.ro) |
| PostgreSQL | 10.10.10.166:5432 | Database (Prisma) |
| MinIO | 10.10.10.166:9002/9003 | Object storage |
| Authentik | 10.10.10.166:9100 | SSO (auth.beletage.ro) |
| Portainer | 10.10.10.166:9000 | Docker management |
| Gitea | 10.10.10.166:3002 | Git (git.beletage.ro) |
| Traefik | 10.10.10.199 | Reverse proxy + SSL |
| N8N | 10.10.10.166:5678 | Workflow automation |
| Stirling PDF | 10.10.10.166:8087 | PDF tools (needs env vars!) |
## Company IDs
| ID | Name | Prefix |
| ----------------- | --------------- | ------ |
| `beletage` | Beletage | B |
| `urban-switch` | Urban Switch | US |
| `studii-de-teren` | Studii de Teren | SDT |
| `group` | Grup | G |
---
## Documentation
| Doc | Path |
| ------------------- | ------------------------------------------ |
| Module Map | `docs/MODULE-MAP.md` |
| Architecture Quick | `docs/ARCHITECTURE-QUICK.md` |
| System Architecture | `docs/architecture/SYSTEM-ARCHITECTURE.md` |
| Module System | `docs/architecture/MODULE-SYSTEM.md` |
| Feature Flags | `docs/architecture/FEATURE-FLAGS.md` |
| Storage Layer | `docs/architecture/STORAGE-LAYER.md` |
| Security & Roles | `docs/architecture/SECURITY-AND-ROLES.md` |
| Module Dev Guide | `docs/guides/MODULE-DEVELOPMENT.md` |
| Docker Deployment | `docs/guides/DOCKER-DEPLOYMENT.md` |
| Coding Standards | `docs/guides/CODING-STANDARDS.md` |
| Data Model | `docs/DATA-MODEL.md` |
For module-specific deep dives, see `docs/modules/`.