# Technical Context ## Stack (v2 — current) ### Frontend - **React 19** with TypeScript 5.8 - **Tailwind CSS** (via PostCSS, built into `src/tailwind.css`) - **Framer Motion 12.x** for animations - **React Router 7.x** for client-side routing - Build tool: Create React App (`react-scripts 5.0.1`) - Install note: `npm install --legacy-peer-deps` required (react-scripts peer dep conflict with TS 5.x) ### Backend - **Node.js 22 LTS**, **Express 5.1**, TypeScript 5.8 - **Mongoose 8.x** ODM → **MongoDB 7.0** - **`jose` 5.x** for JWT signing/verification - **`argon2`** for password hashing (native compilation — requires `python3 make g++` in Docker) - **`multer` 1.4.5-lts.1** for file uploads - **`nodemailer` 6.x** for email via Google Workspace SMTP - **`express-rate-limit` 8.5.x** on auth routes (5 attempts / 5 min) - **`zod` 3.x** for request validation - Dev runner: `tsx watch` ### Express 5 Breaking Change `app.get('*', handler)` is **invalid** in Express 5. SPA catch-all must use: ```typescript app.use((_req, res) => { res.sendFile(path.join(clientBuild, 'index.html')); }); ``` ## Infrastructure ### Docker (Production) Two containers managed by `docker-compose.yml`: - `deafmissoulaorg-app-1` — Node.js app (Express serves React build + API) on port 801 - `deafmissoulaorg-mongodb-1` — MongoDB 7.0, internal network only (not exposed) Named volumes: - `mcdi-mongo-data` — MongoDB data - `mcdi-uploads-data` — Dashboard file uploads Networks: - `caddy_network` (external) — shared with Caddy reverse proxy; app joins this so Caddy can route by container name - `internal` (bridge) — app ↔ MongoDB only ### Caddy Reverse Proxy - **Production server:** `10.4.0.205` (WireGuard VPN) - **Config location:** `~/docker/caddy/config/Caddyfile` Because the app joins `caddy_network`, Caddy can use the container name: ``` deafmissoula.org { reverse_proxy deafmissoulaorg-app-1:801 } ``` If you see a 502 after a rebuild, check the container name hasn't changed: ```bash docker ps --filter name=deafmissoula docker exec caddy caddy reload --config /etc/caddy/Caddyfile ``` ### Production Server CPU Note The Proxmox VM running 10.4.0.205 has CPU type set to `host` (passthrough). This is required for MongoDB 7.0+ which needs AVX instructions. Do not change the VM CPU type back to `kvm64` or `Common KVM processor` — MongoDB will crash. ## Authentication - Board members log in at `/manage` with their `@deafmissoula.org` email - First login: `mustChangePassword: true` → server returns `tempToken` (1-hour JWT) → client redirects to force-change screen - Forgot password: generates `resetToken` + `resetTokenExpiry` (1 hour) on BoardMember, emails `${APP_URL}/manage/reset-password?token=xxx` - New board member added via dashboard: auto-sends welcome email with 7-day reset link - Admin fallback: `admin@deafmissoula.org` is a `User` model (not BoardMember), password set via `ADMIN_PASSWORD` in `stack.env` ## File Serving Two separate file trees: 1. **Static assets** (baked into Docker image from `client/public/`) served at root paths: `/photos/`, `/videos/`, `/minutes/`, `/headshots/`, `/sponsors/`, `/logos/` 2. **Dashboard uploads** (Docker volume `mcdi-uploads-data`) served at `/uploads/headshots/`, `/uploads/photos/`, `/uploads/sponsors/`, `/uploads/minutes/` ## Deployment ```bash ssh chaulmark@10.4.0.205 cd ~/websites/deafmissoula.org git pull origin main docker compose down && docker compose up -d --build # First deploy only: docker exec deafmissoulaorg-app-1 node dist/seed.js ``` CACHEBUST in `stack.env` forces a full Docker layer cache bust when incremented. Update it if a rebuild is pulling stale cached layers.