# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Changemaker Lite is a self-hosted political campaign platform built with Docker Compose. It consolidates advocacy email campaigns, geographic mapping, volunteer management, and administration into a single TypeScript stack. The primary domain is `cmlite.org`. **Current state:** V2 rebuild in progress on the `v2` branch. See `V2_PLAN.md` for the full roadmap. --- ## V2 Architecture (Active Development) ### Stack - **Single unified Express.js API** — TypeScript, port 4000, Prisma ORM + PostgreSQL 16 - **React Admin GUI** — Vite + Ant Design + Zustand, port 3000 - **Nginx reverse proxy** — subdomain routing (`*.cmlite.org`) - **NocoDB v2** — read-only data browser on port 8091 - **JWT auth** — access tokens (15min) + refresh tokens (7 days, stored in DB) - **BullMQ** — async email job queue, **Listmonk** for newsletters - **Redis** — caching, rate limiting, BullMQ backend ### Directory Structure ``` changemaker.lite/ ├── api/ # Unified Express.js API (TypeScript) │ ├── prisma/ # Schema, migrations, seed │ └── src/ │ ├── config/ # env.ts, database.ts, redis.ts │ ├── middleware/ # error-handler, validate, rate-limit, auth, rbac │ ├── modules/ │ │ ├── auth/ # auth.service, auth.routes, auth.schemas │ │ ├── users/ # users.service, users.routes, users.schemas │ │ ├── influence/ # campaigns, representatives, responses, postal-codes │ │ └── map/ # locations, shifts, cuts │ ├── types/ # express.d.ts (Request augmentation) │ └── utils/ # logger.ts (Winston), metrics.ts (prom-client) ├── admin/ # React Admin (Vite + Ant Design + Zustand) │ └── src/ │ ├── components/ # ProtectedRoute, AppLayout │ ├── pages/ # LoginPage, DashboardPage, UsersPage │ ├── stores/ # auth.store.ts (Zustand) │ ├── lib/ # api.ts (axios instance + interceptors) │ └── types/ # api.ts (TypeScript interfaces) ├── nginx/ # Reverse proxy config ├── public-web/ # Public landing pages ├── docker-compose.yml # V2 orchestration ├── docker-compose.v1.yml # V1 backup for reference └── V2_PLAN.md # Full 14-phase roadmap ``` ### Key Files | File | Purpose | |------|---------| | `api/prisma/schema.prisma` | Full database schema (20+ models) | | `api/src/server.ts` | API entry point, middleware stack, route wiring | | `api/src/config/env.ts` | Zod-validated environment config | | `api/src/modules/auth/` | JWT auth (login, register, refresh, logout) | | `api/src/modules/users/` | User CRUD with pagination + search | | `admin/src/App.tsx` | React admin shell with routing | | `admin/src/stores/auth.store.ts` | Zustand auth state with token persistence | | `admin/src/lib/api.ts` | Axios instance with 401 refresh interceptor | | `docker-compose.yml` | V2 service orchestration | | `.env.example` | All required environment variables | ### Auth Flow - JWT-based: access tokens (15min) + refresh tokens (7 days, stored in DB) - Login → verify bcrypt hash → generate token pair → return tokens + user - Refresh → validate refresh token → rotate (invalidate old, issue new) → return new pair - Roles: `SUPER_ADMIN`, `INFLUENCE_ADMIN`, `MAP_ADMIN`, `USER`, `TEMP` - RBAC middleware: `requireRole(...roles)`, `requireNonTemp` ### Nginx Routing | Subdomain | Target | |-----------|--------| | `app.cmlite.org` | Admin React app (port 3000) | | `api.cmlite.org` | Express API (port 4000) | | `data.cmlite.org` | NocoDB read-only (port 8091) | | `docs.cmlite.org` | MkDocs (port 4001) | | `cmlite.org` | Public landing pages | --- ## V2 Development Commands ### API Development ```bash cd api && npm run dev # Dev server with tsx watch (auto-reload) cd api && npx tsc --noEmit # Type-check without emitting cd api && npx prisma migrate dev # Run/create migrations cd api && npx prisma studio # Browse database in browser cd api && npx prisma generate # Regenerate Prisma client ``` ### Admin GUI Development ```bash cd admin && npm run dev # Vite dev server (port 3000) cd admin && npx tsc --noEmit # Type-check without emitting cd admin && npm run build # Production build (tsc + vite) ``` ### Docker (V2 Services) ```bash docker compose up -d v2-postgres redis api # Start API + dependencies docker compose up -d admin # Start admin GUI docker compose up -d # Start all v2 services docker compose logs -f api # Tail API logs docker compose exec api npx prisma migrate dev # Run migrations in container docker compose down # Stop all services ``` ### Type Checking (Both Projects) ```bash cd api && npx tsc --noEmit && cd ../admin && npx tsc --noEmit ``` --- ## Port Reference (V2) | Port | Service | |------|---------| | 3000 | Admin GUI (Vite dev / React) | | 3001 | Grafana | | 3010 | Homepage | | 3030 | Gitea | | 4000 | V2 API (Express.js) | | 4001 | MkDocs (built static) | | 5432 | Listmonk PostgreSQL | | 5433 | V2 PostgreSQL (localhost) | | 5678 | n8n | | 6379 | Redis | | 8025 | MailHog Web UI | | 8080 | cAdvisor | | 8089 | Mini QR | | 8091 | NocoDB v2 (read-only) | | 8888 | Code Server | | 9001 | Listmonk | | 9090 | Prometheus | | 9093 | Alertmanager | --- ## V1 Reference (Legacy) V1 code is preserved in `influence/` and `map/` directories and backed up in `docker-compose.v1.yml`. ### V1 Architecture Two independent Express.js apps using NocoDB REST API as data layer: - **Influence** (`influence/app/`, port 3333) — Postal code → representative lookup, email campaigns, response tracking - **Map** (`map/app/`, port 3000) — Leaflet.js map, volunteer shifts, walk sheets, QR codes Both apps use: session-based auth (Redis-backed), bcryptjs passwords, Bull job queues, NocoDB REST API (not direct DB). ### V1 Express App Structure ``` app/ ├── server.js # Entry point, middleware stack ├── config/ # Environment-based configuration ├── routes/ # Express route definitions ├── controllers/ # Business logic ├── services/ # External integrations (nocodb.js, email.js, listmonk.js) ├── middleware/ # auth.js, csrf.js, rateLimiter.js ├── utils/ # logger.js, metrics.js, validators.js ├── public/ # Static assets └── templates/ # Server-rendered HTML templates ``` ### V1 Commands ```bash cd influence && cp example.env .env ./scripts/build-nocodb.sh # Initialize NocoDB tables docker compose up -d docker compose exec influence-app npm test # Run Jest tests cd map && cp example.env .env ./build-nocodb.sh # Initialize NocoDB tables docker compose up -d ``` ### V1 Build Scripts - `config.sh` — Interactive wizard that generates `.env` with secure random passwords - `start-production.sh` — Installs cloudflared, creates tunnel, configures DNS - `map/build-nocodb.sh` and `influence/scripts/build-nocodb.sh` — Create NocoDB schema + seed data - `reset-site.sh` — Resets MkDocs to baseline ### V1 Documentation - `influence/README.MD` — Features, config, campaign management, email testing - `influence/files-explainer.md` — File-by-file code documentation - `map/README.md` — Features, config, setup instructions - `map/files-explainer.md` — File-by-file code documentation --- ## Key Configuration Files | File | Purpose | |------|---------| | `docker-compose.yml` | V2 orchestration (all services) | | `docker-compose.v1.yml` | V1 backup | | `.env` / `.env.example` | Environment variables (never committed) | | `api/prisma/schema.prisma` | Database schema | | `nginx/` | Reverse proxy configuration | | `configs/prometheus/prometheus.yml` | Monitoring scrape targets | | `configs/cloudflare/tunnel-config.yml` | Production ingress routing | ## Networking All containers share the `changemaker-lite` bridge network and reference each other by container name. Production uses Cloudflare tunnel with ingress rules mapping `*.cmlite.org` subdomains.