1
Architecture
nmemmert edited this page 2026-09-03 17:05:26 -04:00

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

  1. Browser hits port 4173
  2. Express checks /api/* routes first
  3. Hit-counting middleware records page views
  4. public.js serves dist/ static files; unknown paths fall back to dist/index.html (SPA)
  5. 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/*.js exports register(app) and is called in server.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_PASSWORD env var; session token in state.adminSessions
  • Optimistic UI: community posts and likes update the frontend immediately before the API call confirms