3.6 KiB
3.6 KiB
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-depsrequired (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
jose5.x for JWT signing/verificationargon2for password hashing (native compilation — requirespython3 make g++in Docker)multer1.4.5-lts.1 for file uploadsnodemailer6.x for email via Google Workspace SMTPexpress-rate-limit8.5.x on auth routes (5 attempts / 5 min)zod3.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:
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 801deafmissoulaorg-mongodb-1— MongoDB 7.0, internal network only (not exposed)
Named volumes:
mcdi-mongo-data— MongoDB datamcdi-uploads-data— Dashboard file uploads
Networks:
caddy_network(external) — shared with Caddy reverse proxy; app joins this so Caddy can route by container nameinternal(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:
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
/managewith their@deafmissoula.orgemail - First login:
mustChangePassword: true→ server returnstempToken(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.orgis aUsermodel (not BoardMember), password set viaADMIN_PASSWORDinstack.env
File Serving
Two separate file trees:
- Static assets (baked into Docker image from
client/public/) served at root paths:/photos/,/videos/,/minutes/,/headshots/,/sponsors/,/logos/ - Dashboard uploads (Docker volume
mcdi-uploads-data) served at/uploads/headshots/,/uploads/photos/,/uploads/sponsors/,/uploads/minutes/
Deployment
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.