deafmissoula-website/cline_docs/systemPatterns.md

88 lines
4 KiB
Markdown

# System Patterns
## Architecture
Single-container Express app serves both the React SPA and the REST API. MongoDB runs in a separate container on an internal Docker network.
```
Browser
└─► Caddy (caddy_network)
└─► deafmissoulaorg-app-1:801
├─ GET /api/* → Express routes
├─ GET /uploads/* → multer upload volume
├─ GET /photos/*, /videos/*, etc. → client/build/ static assets
└─ GET * → client/build/index.html (SPA catch-all)
└─► deafmissoulaorg-mongodb-1:27017 (internal network only)
```
## Data Flow
All frontend data is fetched from the API at runtime — nothing is hardcoded in components. Each component does a `useEffect``fetch('/api/...')``useState` pattern.
## File Organization
```
client/src/components/
├── Manage.tsx # /manage dashboard (auth + all admin tabs)
├── App.tsx # Routing: /manage/* outside Header/Footer
├── Home.tsx # Landing page (fetches events)
├── Calendar.tsx # Events calendar (fetches events)
├── Meet-Board.tsx # Board member profiles (fetches /api/board)
├── Gallery.tsx # Photo gallery (fetches /api/gallery)
├── VideoGallery.tsx # Video gallery (hardcoded paths in client/public/videos/)
├── Minutes.tsx # Meeting minutes table (fetches /api/minutes)
├── Sponsors.tsx # Sponsors (fetches /api/sponsors)
├── Bylaws.tsx # Static PDF links (hardcoded paths in client/public/)
└── Resources.tsx # Montana/National Deaf resources directory (static)
server/src/
├── models/ # Mongoose schemas
│ ├── BoardMember.ts # Includes auth fields (password, mustChangePassword, resetToken)
│ ├── Event.ts
│ ├── GalleryItem.ts
│ ├── Minute.ts # Has 'order' field for drag-and-drop sorting
│ ├── Sponsor.ts
│ ├── Member.ts
│ └── User.ts # Admin-only account (not BoardMember)
├── routes/
│ ├── auth.ts # Login, forgot/reset password, change password
│ ├── board.ts # CRUD + reorder + welcome email on create
│ ├── minutes.ts # CRUD + reorder (new entries auto-insert at top)
│ ├── events.ts
│ ├── gallery.ts
│ ├── sponsors.ts
│ ├── members.ts
│ └── email.ts # Contact form
├── middleware/
│ ├── auth.ts # JWT verification, exports AuthRequest
│ ├── upload.ts # Multer config for headshots/photos/sponsors/minutes
│ └── errorHandler.ts # ZodError → 400, others → 500
├── db.ts # mongoose.connect()
├── index.ts # Express app setup, static serving, route mounting
└── seed.ts # One-time data population (idempotent — skips if data exists)
```
## Dashboard (`/manage`) Patterns
- **Route security:** `/manage` is not linked from public nav (obscurity) + JWT required for all write operations
- **Login:** Checks BoardMember by email first, then User (admin fallback)
- **Drag-and-drop:** `useDragReorder<T>` custom hook using HTML5 drag API + `useRef` for drag index tracking; calls `PATCH /api/[resource]/reorder` with `{ ids: string[] }` on drop
- **Image preview:** `URL.createObjectURL(file)` in `ImagePicker` component before upload
- **Forced password change:** `mustChangePassword: true` → server issues `tempToken` (1-hour) → client shows change-password form before dashboard loads
## Deployment Pattern
1. Code changes pushed to Gitea (`git push origin main`)
2. SSH to production server (`ssh chaulmark@10.4.0.205`)
3. `git pull origin main && docker compose down && docker compose up -d --build`
No Portainer. No webhooks. Manual pull + rebuild.
## Rollback Pattern
`v1` tag in Git points to the last hardcoded version (no database):
```bash
git checkout v1
docker compose down
docker compose up -d --build
```