Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled

Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:52:51 +01:00
commit 67d7e6e3d5
572 changed files with 86496 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
# Port Nimara CRM — Architecture Decision Record
**Compiled:** 2026-03-11
**Status:** Draft — requires review and sign-off before implementation begins
Each decision is numbered for reference. Decisions marked **[NEEDS INPUT]** require Matt's explicit choice before proceeding.
---
## ADR-001: Replace NocoDB with PostgreSQL + Drizzle ORM
**Status:** Recommended
**Context:** NocoDB serves as both database and admin UI, accessed entirely via REST API. This causes: no transactions (manual rollback code everywhere), no JOINs (N+1 queries), no migrations (schema changes invisible), no type safety (field names as scattered strings), no foreign key constraints (data integrity enforced by application code only). The invoice deletion filter bug and expense_ids comma-separated string are direct consequences.
**Decision:** PostgreSQL as primary database. Drizzle ORM for type-safe schema definition, migrations, and query building.
**Consequences:**
- Every API endpoint must be rewritten against the ORM instead of raw REST calls
- Data migration from NocoDB required (export JSON → transform → seed PostgreSQL)
- NocoDB can optionally remain connected to PostgreSQL as a read-only admin viewer
- Gains: real transactions, JOINs, migrations tracked in version control, foreign key constraints, pg_trgm for fuzzy duplicate detection, proper pagination with cursors
**[NEEDS INPUT]:** Do you want to keep NocoDB connected as an admin read view on top of PostgreSQL, or remove it entirely?
---
## ADR-002: Keep Keycloak for Authentication
**Status:** Accepted
**Context:** Keycloak OIDC works correctly. The circuit breaker, session caching, and token refresh patterns are good. Problems are implementation-level (dev bypass exposed publicly, 3 redundant auth composables) not architectural.
**Decision:** Keep Keycloak. Clean up implementation:
- Single `useAuth()` composable replacing the current three
- Dev bypass moved to server-only config with build-time production guard
- Remove all `useDirectusUser()` references
- Internal service auth replaced with proper HMAC using env-var secret
**Consequences:**
- Same Keycloak realm and client configuration can be reused
- No user migration needed — Keycloak is the identity source of truth
- Auth flow tested in Phase 1 of rebuild
---
## ADR-003: Keep Documenso for E-Signatures
**Status:** Accepted
**Context:** The 3-party EOI signing workflow is a critical legal process that works end-to-end. Webhook handling includes proper deduplication (signature hashing), locking, and notification chaining. Two duplicate ~400-line implementations exist but the underlying logic is sound.
**Decision:** Keep Documenso (self-hosted). Consolidate into a single `EOIService` module with:
- Single document generation path (eliminating duplicate code)
- Recipient configuration from database/environment (not hardcoded)
- Signature events stored as first-class database records (replacing 15+ nullable fields)
- Template-based PDF generation with external template files
**Consequences:**
- Pin Documenso version and add integration tests for webhook handler
- Normalize `eoi_documents`, `eoi_recipients`, and `signature_events` as proper database tables
- Webhook idempotency at database layer (replacing in-memory store)
---
## ADR-004: Keep MinIO for File Storage
**Status:** Accepted
**Context:** S3-compatible object storage via MinIO is solid infrastructure. Presigned URLs, organized prefix-based storage, and the file browser pattern all work well.
**Decision:** Keep MinIO. Improvements:
- ALL credentials via environment variables (rotate current hardcoded keys immediately)
- Database-backed file metadata (instead of relying solely on MinIO listing)
- Formalized storage namespaces: separate prefixes or buckets for documents, EOIs, email attachments, exports
- Proper audit trail (current file audit functions only print to console)
**Consequences:**
- File metadata table in PostgreSQL enables search, filtering, and relationship tracking
- MinIO credentials must be rotated before rebuild goes live
---
## ADR-005: UI Framework — Tailwind CSS + Component Library
**Status:** Recommended
**Context:** Three UI systems currently coexist: Vuetify (legacy), Maritime Design System (custom CSS tokens), and Nuxt UI v3. The dual-system support adds ~30-40% code volume to templates. Maritime design tokens (colors, typography, spacing, glassmorphism) provide the visual identity.
**Decision:** Tailwind CSS as the styling foundation. Maritime design tokens become Tailwind theme extensions. Drop Vuetify entirely.
**[NEEDS INPUT]:** Component library choice:
- **Option A: Nuxt UI v3** — Already partially in use. Built on Radix Vue. Tight Nuxt integration. Opinionated but productive.
- **Option B: Headless UI (or Radix Vue directly)** — More control over styling. Less opinionated. More work for common patterns.
- **Option C: shadcn-vue** — Copy-paste components. Full ownership. Good Tailwind integration.
**Consequences:**
- Maritime glassmorphism aesthetic preserved as Tailwind utilities/theme
- All Vuetify imports, theme config, and feature flag toggle code eliminated
- Single component library used consistently throughout
---
## ADR-006: State Management — Pinia + TanStack Query
**Status:** Recommended
**Context:** Currently one Pinia store (expenses) with good patterns (5-min cache, optimistic updates, rollback). All other state is scattered across page-level refs, composable-level refs, and payload data. Auth state read from 3 different composables.
**Decision:** Pinia stores for domain state (auth, UI preferences). TanStack Query (VueQuery) for all server state (interests, berths, expenses, invoices, files).
**Consequences:**
- Automatic cache invalidation and background refetching
- Built-in loading/error states per query
- Optimistic updates with rollback
- Eliminates manual cache TTL patterns
- Consistent data fetching pattern across all domains
---
## ADR-007: API Design — REST with Zod Validation
**Status:** Recommended
**Context:** Current API mixes RPC-style, REST-style, and ad-hoc patterns (~100 endpoints). No input validation. Inconsistent error responses. No pagination.
**Decision:** Consistent REST conventions. Reduce from ~100 to ~40-50 well-designed endpoints. Standardize on:
- Resource-oriented URLs: `GET /api/interests`, `POST /api/interests`, `PATCH /api/interests/:id`
- Zod schemas for ALL request validation (body, query, params)
- Standard error response: `{ error: { code, message, details? } }` with proper HTTP status codes
- Cursor-based pagination for all list endpoints
- Status transitions as explicit actions: `POST /api/interests/:id/transitions` instead of raw field mutation
**[NEEDS INPUT]:** Do you want to add OpenAPI/Swagger auto-generated documentation? Useful for future integrations but adds setup overhead.
**Consequences:**
- Cleaner, documentable API surface
- Type-safe request/response contracts
- Proper pagination from day one (no more unbounded fetches)
---
## ADR-008: Email Architecture — Hybrid Approach
**Status:** Recommended
**Context:** Current email system has two parallel IMAP implementations, hardcoded credentials, inline HTML templates in 4+ files, TLS verification disabled, and in-memory credential cache lost on restart.
**Decision:**
- **Outbound (transactional):** Dedicated email service (Resend or SendGrid) for CRM notifications, reminders, EOI emails. Template management via service. Better deliverability and analytics.
- **Outbound (user mailbox):** Keep SMTP for sending-as-user capability. Store credentials encrypted in database (not in-memory).
- **Inbound:** Keep IMAP sync for sales inbox and user thread viewing. Move to background worker (not blocking HTTP requests). Store thread metadata in PostgreSQL.
- **Templates:** Extract all inline HTML to MJML template files compiled at build time.
**[NEEDS INPUT]:** Which transactional email service? Resend (simpler, developer-focused) vs. SendGrid (more established, more features)?
**Consequences:**
- Single IMAP implementation (eliminate V1/V2 duplication)
- All credentials in encrypted database storage or env vars
- TLS verification enabled everywhere
- Email thread metadata queryable via PostgreSQL
---
## ADR-009: Job Queue — BullMQ + Redis
**Status:** Recommended
**Context:** Background tasks currently use Nitro experimental tasks and `setInterval` with node-cron. Task results are not tracked. Failures require log analysis. In-memory state (webhook event store, credential cache) lost on restart.
**Decision:** BullMQ with Redis for all background processing:
- Notification processing (currently 30s poll → event-driven via queue)
- Signature polling fallback (keep as scheduled job)
- EOI reminders (if re-enabled)
- Email sync (background worker)
- Currency rate refresh
Redis also serves as:
- Session store (enables horizontal scaling)
- Cache layer (replacing file-based and in-memory caches)
- Webhook event deduplication store
**Consequences:**
- Proper job tracking: start time, end time, success/failure, error details
- Built-in retry with exponential backoff and dead letter queue
- Admin dashboard for task monitoring
- Multi-instance deployment possible (no more single-container state dependency)
---
## ADR-010: Deployment — Single Container + Redis Sidecar
**Status:** Recommended
**Context:** Current single Nuxt container works but holds all state in memory. Docker + Gitea CI/CD pipeline has no test stage.
**Decision:** Keep single Nuxt container for app (Nitro serves both API and SPA). Add Redis container as sidecar. Add PostgreSQL (or connect to existing managed instance).
**Improvements to CI/CD:**
- Add test stage (Vitest unit + Playwright E2E) before image publish
- Environment-specific builds (dev/staging/production)
- Health check endpoint for container orchestration
**[NEEDS INPUT]:** Where does PostgreSQL run? Options:
- **Managed service** (e.g., on the same host, or cloud-managed) — less ops burden
- **Another Docker container** — simpler initial setup, more ops responsibility
- **Existing infrastructure** — do you already have a PostgreSQL instance anywhere?
**Consequences:**
- Horizontal scaling possible with Redis handling shared state
- Tests run before every deployment
- Container health checks enable automatic restart on failure
---
## ADR-011: Testing Strategy
**Status:** Recommended
**Context:** Currently one test file exists (`session-manager.test.ts`). No integration or E2E tests. CI/CD publishes without running any tests.
**Decision:**
- **Vitest** for unit tests: Services (EOIService, email, invoice generation), business rule validation, utility functions
- **Playwright** for E2E tests: Critical workflows (interest creation, berth linking, EOI generation, expense management)
- **Integration tests**: Documenso webhook handler, database migrations, auth flow
- Test coverage targets: 80% for services, 100% for business rules (the 13 critical rules listed in the spec)
**Consequences:**
- CI/CD blocks deployment on test failure
- Regression protection for business rules
- Confidence in refactoring
---
## ADR-012: Logging and Monitoring
**Status:** Recommended
**Context:** Nearly every endpoint uses `console.log()` with no level distinction. Debug blocks shipped in production. Audit logging inconsistent (some to database, some to console only).
**Decision:**
- **Structured logger**: `consola` with level control (debug/info/warn/error)
- **Audit logging**: All domain operations write to `audit_logs` table consistently
- **Keep custom alert system**: Per-type failure counters, cooldowns, admin notifications — enhance with dashboard
- **Remove all debug blocks**: No `console.log("DEBUGGING")` in production
**Consequences:**
- Log levels configurable per environment
- Audit trail complete and queryable
- Alert dashboard gives ops team visibility into system health
---
## Decision Summary
| # | Decision | Status |
| ------- | ----------------------------------------- | --------------------------------------------------- |
| ADR-001 | PostgreSQL + Drizzle ORM (replace NocoDB) | Recommended — **needs input on NocoDB admin view** |
| ADR-002 | Keep Keycloak | Accepted |
| ADR-003 | Keep Documenso | Accepted |
| ADR-004 | Keep MinIO | Accepted |
| ADR-005 | Tailwind CSS + component library | Recommended — **needs input on component library** |
| ADR-006 | Pinia + TanStack Query | Recommended |
| ADR-007 | REST + Zod validation | Recommended — **needs input on OpenAPI docs** |
| ADR-008 | Hybrid email (service + IMAP) | Recommended — **needs input on email service** |
| ADR-009 | BullMQ + Redis | Recommended |
| ADR-010 | Single container + Redis sidecar | Recommended — **needs input on PostgreSQL hosting** |
| ADR-011 | Vitest + Playwright testing | Recommended |
| ADR-012 | Structured logging + consistent audit | Recommended |
---
## Proposed Technology Stack
| Layer | Current | Rebuild |
| ---------------- | -------------------------------------- | --------------------------------------------- |
| Framework | Nuxt 3 | Nuxt 3 (latest) |
| UI | Vuetify + Maritime CSS + Nuxt UI | Tailwind CSS + shadcn/ui + Maritime tokens |
| State | Mixed (1 Pinia store + refs + payload) | Pinia + TanStack Query |
| Database | NocoDB (REST API) | PostgreSQL + Drizzle ORM |
| File Storage | MinIO | MinIO (credentials in env) |
| Auth | Keycloak OIDC | Keycloak OIDC (cleaned up) |
| Signing | Documenso | Documenso (consolidated service) |
| Email (outbound) | Nodemailer direct SMTP | Transactional service + SMTP for user mailbox |
| Email (inbound) | IMAP (dual implementation) | IMAP (single, background worker) |
| PDF | PDFKit + @pdfme | @pdfme only |
| Jobs | Nitro tasks + node-cron + setInterval | BullMQ + Redis |
| Cache | File-based + in-memory | Redis |
| Icons | Lucide + mdi | Lucide only |
| Logging | console.log | consola (structured) |
| Testing | None | Vitest + Playwright |
---
## Proposed Build Phases
**Phase 1 — Foundation (weeks 1-2):** Nuxt 3 + TypeScript strict, PostgreSQL + Drizzle schema, Keycloak auth (single composable), Tailwind + Maritime tokens, layout shell
**Phase 2 — Core CRUD (weeks 3-4):** Interest management, berth management, berth-interest linking, RBAC
**Phase 3 — Business Workflows (weeks 5-7):** EOI/Documenso integration, email composer + thread viewer, expense management, invoice creation, file management
**Phase 4 — Admin & Operations (week 8):** Admin dashboard, audit logging, alert/reminder settings, scheduled background tasks
**Phase 5 — Polish & Migration (weeks 9-10):** Data migration from NocoDB → PostgreSQL, performance optimization, PWA (if keeping), testing suite, security hardening