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:
@@ -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" <noreply@beletage.ro>, 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 — `"idDocument":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/`.
|
||||
|
||||
Reference in New Issue
Block a user