Phase 1-14 complete: - Unified Express.js API (TypeScript, Prisma ORM, PostgreSQL 16) - React Admin GUI (Vite + Ant Design + Zustand) - JWT auth with refresh tokens - Influence: Campaigns, Representatives, Responses, Email Queue - Map: Locations, Cuts, Shifts, Canvassing System - NAR data import infrastructure (2025 format) - Listmonk newsletter integration - Landing page builder (GrapesJS) - MkDocs + Code Server integration - Volunteer portal with GPS tracking - Monitoring stack (Prometheus, Grafana, Alertmanager) - Pangolin tunnel integration Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.4 KiB
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
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
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)
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)
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
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.envwith secure random passwordsstart-production.sh— Installs cloudflared, creates tunnel, configures DNSmap/build-nocodb.shandinfluence/scripts/build-nocodb.sh— Create NocoDB schema + seed datareset-site.sh— Resets MkDocs to baseline
V1 Documentation
influence/README.MD— Features, config, campaign management, email testinginfluence/files-explainer.md— File-by-file code documentationmap/README.md— Features, config, setup instructionsmap/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.