docs: update CLAUDE.md with infrastructure, PDF compression, address book changes

- Two-server architecture: satra (app) + proxy (Traefik)
- Traefik config details (timeouts, dynamic config paths)
- Portainer CE deploy workflow (manual Pull and redeploy)
- PDF compression dual-mode docs (qpdf local + iLovePDF cloud)
- Streaming upload architecture (zero-memory parsing)
- Middleware exclusion pattern for large upload routes
- Address Book flexible contact model (name OR company)
- ContactPerson department field
- Updated module versions and integration table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-13 21:23:09 +02:00
parent 8e2534ebe3
commit de52b5dced
+85 -13
View File
@@ -23,7 +23,7 @@ git push origin main # auto-deploys via Portainer webhook
- **Urban Switch** (urbanism)
- **Studii de Teren** (geotechnics)
It runs on an on-premise Ubuntu server at `10.10.10.166`, containerized with Docker, managed via Portainer, served by Nginx Proxy Manager.
It runs on two on-premise servers, containerized with Docker, managed via Portainer CE.
### Stack
@@ -35,7 +35,8 @@ It runs on an on-premise Ubuntu server at `10.10.10.166`, containerized with Doc
| 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) |
| Deploy | Docker multi-stage, Portainer CE, Nginx Proxy Manager |
| 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** |
@@ -104,9 +105,9 @@ legacy/ # Original HTML tools for reference
| 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.1.1 | CRUD contacts, card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)**, **alphabetically sorted type dropdown** |
| 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.3.0 | Text case, char counter, percentage, **TVA calculator (cotă configurabilă: 5/9/19/21% + custom)**, area converter, U→R, num→text, artifact cleaner, MDLPA, **extreme PDF compression (GS+qpdf)**, 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) |
| 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 |
@@ -137,6 +138,51 @@ Key files:
- `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:
@@ -185,19 +231,18 @@ Key files:
## Infrastructure
### Server: `10.10.10.166` (Ubuntu)
### 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** | 9000 | Docker management |
| **Nginx Proxy Manager** | 81 (admin) | Reverse proxy + SSL termination |
| **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 cron) |
| **N8N** | 5678 | Workflow automation (daily digest) |
| **Stirling PDF** | 8087 | PDF tools |
| **IT-Tools** | 8085 | Developer utilities |
| **FileBrowser** | 8086 | File management |
@@ -205,21 +250,37 @@ Key files:
| **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 detects new commit
Manual "Pull and redeploy" in Portainer (CE doesn't auto-rebuild)
→ Docker multi-stage build (~1-2 min)
→ 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
Nginx Proxy Manager routes to tools.beletage.ro
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:20-alpine`, non-root user
- `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**
@@ -275,6 +336,15 @@ src/modules/<name>/
└── 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`
@@ -319,6 +389,8 @@ src/modules/<name>/
| **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`) |
---