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:
@@ -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`) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user