90 lines
3.6 KiB
Markdown
90 lines
3.6 KiB
Markdown
# 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.
|