Page:
Architecture
Clone
1
Architecture
nmemmert edited this page 2026-09-03 17:05:26 -04:00
Table of Contents
Architecture
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| Backend | Node.js 22, Express |
| PWA | Workbox (service worker, precache) |
| Styling | Plain CSS (src/App.css) with CSS custom properties |
| Container | Docker / Podman, multi-arch (amd64 + arm64) |
| Registry | Gitea Container Registry (git.necloud.us) |
Folder Layout
Siteforge/
├── src/ # React frontend
│ ├── App.tsx # Root component + all page routes
│ ├── App.css # Global styles
│ ├── colossiansStudy.tsx # Entire study platform UI (~3700 lines)
│ ├── AdminPage.tsx # Admin panel UI
│ └── content.ts # SiteContent type definitions
├── server/ # Express backend
│ ├── routes/ # One file per feature area (see API Reference)
│ ├── data.js # Data load/save helpers + sanitizers
│ ├── state.js # In-memory state (loaded from JSON on start)
│ ├── study-helpers.js # Study auth, slug normalise, email scheduling
│ ├── email.js # Resend email senders
│ ├── auth.js # Admin session validation
│ ├── config.js # Constants (limits, file paths)
│ └── helpers.js # Shared utilities
├── server.js # Express app entry point
├── data/ # Persisted JSON (bind-mounted in Docker)
│ ├── admin-content.json # All site content (episodes, studies, SEO…)
│ ├── study-users.json # Study platform accounts
│ ├── study-community.json
│ ├── study-notes/ # Per-user note files (<userId>.json)
│ ├── study-progress/ # Per-user progress files
│ └── ...
├── dist/ # Built frontend (served by Express)
├── public/ # Static assets copied to dist
└── .github/workflows/ # Gitea Actions CI
Request Flow
- Browser hits port 4173
- Express checks
/api/*routes first - Hit-counting middleware records page views
public.jsservesdist/static files; unknown paths fall back todist/index.html(SPA)- React Router handles client-side routing
Data Storage
All state is held in memory (server/state.js) and written to JSON files in data/ via debounced write queues. On startup every file is loaded into memory.
There is no database — JSON files are the source of truth. The data/ directory is bind-mounted from the host so data survives container restarts and image upgrades.
Key Patterns
- Route registration: each
server/routes/*.jsexportsregister(app)and is called inserver.js - Sanitizers: every field written to disk passes through a sanitizer in
data.js(e.g.sanitizeStudyCommunityPosts). New persistent fields must be added to the sanitizer or they are silently stripped on the next write. - Study auth: cookie-based sessions in
state.studySessions(Map); expire after 30 days - Admin auth: single password from
ADMIN_PASSWORDenv var; session token instate.adminSessions - Optimistic UI: community posts and likes update the frontend immediately before the API call confirms