# CLAUDE.md – HOA Financial Platform (HOALedgerIQ) ## Project Overview Multi-tenant SaaS platform for HOA (Homeowners Association) financial management. Handles chart of accounts, journal entries, budgets, invoices, payments, reserve planning, and board scenario planning. --- ## Stack & Framework | Layer | Technology | | --------- | --------------------------------------------------- | | Backend | **NestJS 10** (TypeScript), runs on port 3000 | | Frontend | **React 18** + Vite 5 + Mantine UI + Zustand | | Database | **PostgreSQL** via **TypeORM 0.3** | | Cache | **Redis** (BullMQ for queues) | | Auth | **Passport.js** – JWT access + httpOnly refresh | | Payments | **Stripe** (checkout, subscriptions, webhooks) | | Email | **Resend** | | AI | NVIDIA API (Qwen model) for investment advisor | | Monitoring| **New Relic** APM (app name: `HOALedgerIQ_App`) | | Infra | Docker Compose (dev + prod), Nginx reverse proxy | --- ## Auth Pattern - **Access token**: JWT, 1-hour TTL, payload `{ sub, email, orgId, role, isSuperadmin }` - **Refresh token**: 64-byte random, SHA256-hashed in DB, 30-day TTL, sent as httpOnly cookie `ledgeriq_rt` - **MFA**: TOTP via `otplib`, challenge token (5-min TTL), recovery codes - **Passkeys**: WebAuthn via `@simplewebauthn/server` - **SSO**: Google OAuth 2.0, Azure AD - **Password hashing**: bcryptjs, cost 12 - **Rate limiting**: 100 req/min global (Throttler), custom per endpoint ### Guards & Middleware - `TenantMiddleware` – extracts `orgId` from JWT, sets tenant schema (60s cache) - `JwtAuthGuard` – Passport JWT guard on all protected routes - `WriteAccessGuard` – blocks write ops for `viewer` role and `past_due` orgs - `@AllowViewer()` decorator – exempts read endpoints from WriteAccessGuard ### Roles `president`, `treasurer`, `secretary`, `member_at_large`, `manager`, `homeowner`, `admin`, `viewer` --- ## Multi-Tenant Architecture - **Shared schema** (`shared`): users, organizations, user_organizations, refresh_tokens, invite_tokens, login_history, cd_rates - **Tenant schemas** (dynamic, per org): accounts, journal_entries, budgets, invoices, payments, units, vendors, etc. - Schema name stored in `shared.organizations.schema_name` --- ## Route Map (180+ endpoints) ### Auth (`/api/auth`) | Method | Path | Purpose | | ------ | ----------------------- | -------------------------------- | | POST | /login | Email/password login | | POST | /refresh | Refresh access token (cookie) | | POST | /logout | Revoke refresh token | | POST | /logout-everywhere | Revoke all sessions | | GET | /profile | Current user profile | | POST | /register | Register (disabled by default) | | POST | /activate | Activate invited user | | POST | /forgot-password | Request password reset | | POST | /reset-password | Reset with token | | PATCH | /change-password | Change password (authed) | | POST | /switch-org | Switch active organization | ### Auth MFA (`/api/auth/mfa`) | POST | /setup | POST /enable | POST /verify | POST /disable | GET /status | ### Auth Passkeys (`/api/auth/passkeys`) | POST /register-options | POST /register | POST /login-options | POST /login | GET / | DELETE /:id | ### Admin (`/api/admin`) – superadmin only | GET /metrics | GET /users | GET /organizations | PUT /organizations/:id/subscription | POST /impersonate/:userId | POST /tenants | ### Organizations (`/api/organizations`) | POST / | GET / | PATCH /settings | GET /members | POST /members | PUT /members/:id/role | DELETE /members/:id | ### Accounts (`/api/accounts`) | GET / | GET /trial-balance | POST / | PUT /:id | PUT /:id/set-primary | POST /bulk-opening-balances | POST /:id/opening-balance | POST /:id/adjust-balance | ### Journal Entries (`/api/journal-entries`) | GET / | GET /:id | POST / | POST /:id/post | POST /:id/void | ### Budgets (`/api/budgets`) | GET /:year | PUT /:year | GET /:year/vs-actual | POST /:year/import | GET /:year/template | ### Invoices (`/api/invoices`) | GET / | GET /:id | POST /generate-preview | POST /generate-bulk | POST /apply-late-fees | ### Payments (`/api/payments`) | GET / | GET /:id | POST / | PUT /:id | DELETE /:id | ### Units (`/api/units`) | GET / | GET /:id | POST / | PUT /:id | DELETE /:id | GET /export | POST /import | ### Vendors (`/api/vendors`) | GET / | GET /:id | POST / | PUT /:id | GET /export | POST /import | GET /1099-data | ### Reports (`/api/reports`) | GET /dashboard | GET /balance-sheet | GET /income-statement | GET /cash-flow | GET /cash-flow-sankey | GET /aging | GET /year-end | GET /cash-flow-forecast | GET /quarterly | ### Board Planning (`/api/board-planning`) Scenarios CRUD, scenario investments, scenario assessments, projections, budget plans – 28 endpoints total. ### Other Modules - `/api/fiscal-periods` – list, close, lock - `/api/reserve-components` – CRUD - `/api/capital-projects` – CRUD - `/api/projects` – CRUD + planning + import/export - `/api/assessment-groups` – CRUD + summary + default - `/api/monthly-actuals` – GET/POST /:year/:month - `/api/health-scores` – latest + calculate - `/api/investment-planning` – snapshot, market-rates, recommendations - `/api/investment-accounts` – CRUD - `/api/attachments` – upload, list, download, delete (10MB limit) - `/api/onboarding` – progress get/patch - `/api/billing` – trial, checkout, webhook, subscription, portal --- ## Database - **Connection pool**: min 5, max 30, 30s idle, 5s connect timeout - **Migrations**: SQL files in `db/migrations/` (manual execution, no ORM runner) - **Init script**: `db/init/00-init.sql` (shared schema DDL) --- ## Key File Paths | Purpose | Path | | ---------------------- | ------------------------------------------------- | | NestJS bootstrap | `backend/src/main.ts` | | Root module | `backend/src/app.module.ts` | | Auth controller | `backend/src/modules/auth/auth.controller.ts` | | Auth service | `backend/src/modules/auth/auth.service.ts` | | Refresh token svc | `backend/src/modules/auth/refresh-token.service.ts` | | JWT strategy | `backend/src/modules/auth/strategies/jwt.strategy.ts` | | Tenant middleware | `backend/src/database/tenant.middleware.ts` | | Write-access guard | `backend/src/common/guards/write-access.guard.ts` | | DB schema init | `db/init/00-init.sql` | | Env example | `.env.example` | | Docker compose (dev) | `docker-compose.yml` | | Frontend entry | `frontend/src/main.tsx` | | Frontend pages | `frontend/src/pages/` | --- ## Environment Variables (critical) ``` DATABASE_URL – PostgreSQL connection string REDIS_URL – Redis connection JWT_SECRET – JWT signing key INVITE_TOKEN_SECRET – Invite token signing STRIPE_SECRET_KEY – Stripe API key STRIPE_WEBHOOK_SECRET – Stripe webhook verification RESEND_API_KEY – Email service NEW_RELIC_APP_NAME – "HOALedgerIQ_App" NEW_RELIC_LICENSE_KEY – New Relic license APP_URL – Base URL for email links ``` --- ## New Relic - **App name**: `HOALedgerIQ_App` (env: `NEW_RELIC_APP_NAME`) - Enabled via `NEW_RELIC_ENABLED=true` - NRQL query library: `load-tests/analysis/nrql-queries.sql` --- ## Load Testing ### Run k6 scenarios ```bash # Auth + Dashboard flow (staging) k6 run --env TARGET_ENV=staging load-tests/scenarios/auth-dashboard-flow.js # CRUD flow (staging) k6 run --env TARGET_ENV=staging load-tests/scenarios/crud-flow.js # Local dev k6 run --env TARGET_ENV=local load-tests/scenarios/auth-dashboard-flow.js ``` ### Conventions - Scenarios live in `load-tests/scenarios/` - Config in `load-tests/config/environments.json` (staging/production/local thresholds) - Test users parameterized from `load-tests/config/user-pool.csv` - Baseline results stored in `load-tests/analysis/baseline.json` - NRQL queries for New Relic in `load-tests/analysis/nrql-queries.sql` - All k6 scripts use `SharedArray` for user pool, `http.batch()` for parallel requests - Custom metrics: `*_duration` trends + `*_error_rate` rates per journey - Thresholds: p95 latency + error rate per environment ### User Pool CSV Format ``` email,password,orgId,role ``` Roles match the app: `treasurer`, `admin`, `president`, `manager`, `member_at_large`, `viewer`, `homeowner` --- ## Fix Conventions - Backend tests: `npm run test` (Jest, `*.spec.ts` co-located with source) - E2E tests: `npm run test:e2e` - Backend build: `npm run build` (NestJS CLI) - Frontend dev: `npm run dev` (Vite, port 5173) - Frontend build: `npm run build` - Always run `npm run build` in `backend/` after changes to verify compilation - TypeORM entities use decorators (`@Entity`, `@Column`, etc.) - Multi-tenant: any new module touching tenant data must use `TenantService` to get the correct schema connection - New endpoints need `@UseGuards(JwtAuthGuard)` and should respect `WriteAccessGuard` - Use `@AllowViewer()` on read-only endpoints