deafmissoula-website/cline_docs/systemPatterns.md

4 KiB

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 useEffectfetch('/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):

git checkout v1
docker compose down
docker compose up -d --build