Files
HOA_Financial_Platform/CLAUDE.md
olsch01 06bc0181f8 feat: add k6 load testing suite, NRQL query library, and CLAUDE.md
Add comprehensive load testing infrastructure:
- k6 auth-dashboard flow (login → profile → dashboard KPIs → widgets → refresh → logout)
- k6 CRUD flow (units, vendors, journal entries, payments, reports)
- Environment configs with staging/production/local thresholds
- Parameterized user pool CSV matching app roles
- New Relic NRQL query library (25+ queries for perf analysis)
- Empty baseline.json structure for all tested endpoints
- CLAUDE.md documenting full stack, auth, route map, and conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:49:22 -04:00

230 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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