From 7d48c0bc4e07e2d6a68ce7d8aed7692f2ee579b8 Mon Sep 17 00:00:00 2001 From: nmemmert Date: Thu, 3 Sep 2026 17:05:26 -0400 Subject: [PATCH] Add all wiki pages: Architecture, Deployment, Admin Guide, Study System, API Reference --- API-Reference.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ Admin-Guide.md | 59 +++++++++++++++++++++++++++++ Architecture.md | 66 +++++++++++++++++++++++++++++++++ Deployment.md | 73 ++++++++++++++++++++++++++++++++++++ Home.md | 23 +++++++++++- Study-System.md | 73 ++++++++++++++++++++++++++++++++++++ 6 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 API-Reference.md create mode 100644 Admin-Guide.md create mode 100644 Architecture.md create mode 100644 Deployment.md create mode 100644 Study-System.md diff --git a/API-Reference.md b/API-Reference.md new file mode 100644 index 0000000..01497ac --- /dev/null +++ b/API-Reference.md @@ -0,0 +1,96 @@ +# API Reference + +All endpoints are at port **4173**. Study endpoints require a study session cookie. Admin endpoints require an admin session cookie. + +## Authentication + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/study-auth/signup` | — | Create study account | +| POST | `/api/study-auth/login` | — | Sign in, sets session cookie | +| POST | `/api/study-auth/logout` | Study | Sign out | +| GET | `/api/study-auth/status` | — | Auth state + enrolled slugs | + +## Study Data + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/study-community?studySlug=` | Study | List community posts | +| POST | `/api/study-community/posts` | Study | Create a post | +| POST | `/api/study-community/posts/:id/replies` | Study | Reply to a post | +| POST | `/api/study-community/posts/:id/like` | Study | Toggle like | +| GET | `/api/study-notes/:noteId` | Study | Get a note | +| PUT | `/api/study-notes/:noteId` | Study | Save a note | +| GET | `/api/study-quiz/:studySlug/:sectionId` | Study | Get quiz answers | +| POST | `/api/study-quiz/:studySlug/:sectionId` | Study | Save quiz answers | +| GET | `/api/study-progress/:studySlug` | Study | Get progress | +| POST | `/api/study-progress/:studySlug/:sectionId/complete` | Study | Mark section complete | + +## Study Account + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/study-account` | Study | Get profile | +| PATCH | `/api/study-account` | Study | Update display name, avatar, preferences | +| POST | `/api/study-account/enroll` | Study | Enroll in a study | +| DELETE | `/api/study-account` | Study | Delete own account | + +## Comments (per-lesson, separate from community feed) + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/study-comments/:studySlug/:sectionId` | — | Public approved comments | +| POST | `/api/study-comments/:studySlug/:sectionId` | Study | Post a comment | +| DELETE | `/api/study-comments/:studySlug/:sectionId/:id` | Study | Delete own comment | + +## Certificates + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/study-certificate/:studySlug` | Study | Get own certificate | + +## Public / Content + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/admin-content` | — | Public site content | +| GET | `/api/episodes` | — | Episode list | +| GET | `/api/bible-passage` | — | Fetch a Bible passage (BSB) | +| GET | `/api/episode-scripts/search?q=` | — | Full-text script search | +| GET | `/sitemap.xml` | — | Dynamic sitemap | +| GET | `/feed.xml` | — | Podcast RSS feed | + +## Contact / Questions + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/contact` | — | Submit a contact form | +| GET | `/api/questions` | — | Public approved Q&A | +| POST | `/api/questions` | — | Submit a question | + +## Admin + +All admin routes require an admin session cookie. + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/api/admin-auth/login` | Admin sign in | +| POST | `/api/admin-auth/logout` | Admin sign out | +| GET/POST/PATCH | `/api/admin-content` | Read/write site content | +| GET/POST/DELETE | `/api/admin-assets` | Upload/list/delete assets | +| GET | `/api/admin/study-users` | List all study users | +| PATCH | `/api/admin/study-users/:id` | Edit a user | +| DELETE | `/api/admin/study-users/:id` | Delete a user | +| POST | `/api/admin/study-users/:id/send-reengagement` | Trigger re-engagement email | +| GET | `/api/admin-backup` | List backups | +| POST | `/api/admin-backup/restore` | Restore a backup | +| GET/POST/DELETE | `/api/admin-episode-scripts/:n` | Manage episode scripts | +| GET/POST/PATCH/DELETE | `/api/webhooks` | Manage outgoing webhooks | + +## Analytics + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/analytics/hits` | Admin | Page hit stats | +| GET | `/api/analytics/visitors` | Admin | Visitor stats | +| POST | `/api/analytics/event` | — | Track a custom event | diff --git a/Admin-Guide.md b/Admin-Guide.md new file mode 100644 index 0000000..7e52a75 --- /dev/null +++ b/Admin-Guide.md @@ -0,0 +1,59 @@ +# Admin Guide + +The admin panel lives at `/admin`. Sign in with the `ADMIN_PASSWORD` environment variable. + +> ⚠️ Change the default password (`changeme`) before going to production. + +## Sections + +### Site Content + +Edit global site settings: SEO metadata, canonical URL, Ko-fi URL, legal pages (Privacy, Terms), and the Studies dropdown in the nav. + +### Episodes + +Add, edit, and reorder podcast episodes. Each episode has: + +- **Number, title, description, URL** (podcast player link) +- **Show notes** — legacy free-text OR structured fields: + - Summary, Key Themes, Scripture References, Notable Quotes, Resources +- **Scriptures** — `Book Chapter:Verse` strings powering the Scripture Index +- **Tags** for filtering + +Episode data lives in `data/admin-content.json` under `siteContent.episodes`. + +### Studies + +Create and manage Bible study courses. Each study has: + +- Slug (URL identifier, e.g. `colossians`) +- Title, description, hero image +- **Sections (lessons)** — each with: id, title, release date, scripture text, commentary, reflection questions, Greek notes + +Sections with a future `releaseDate` are locked for students until that date. + +### Assets + +Upload images, PDFs, and documents. Stored in `data/uploads/`. Tag them for easy lookup. + +### Study Users + +View all enrolled study users with progress, note count, streak, and reminder settings. + +Admin actions per user: +- Edit display name or reset password +- Add / remove study enrollments +- Manually trigger a re-engagement email (T1 / T2 / T3) +- Delete account + +### Backups + +Snapshots of `data/` are taken on startup and on a schedule. The Backups tab lists available snapshots and lets you restore any of them. + +### Episode Scripts + +Upload `.docx` script files for episodes. Text is extracted and made full-text-searchable via `/api/episode-scripts/search`. + +### Webhooks + +Configure outgoing webhooks triggered by app events (e.g. new contact submission). diff --git a/Architecture.md b/Architecture.md new file mode 100644 index 0000000..1ad36af --- /dev/null +++ b/Architecture.md @@ -0,0 +1,66 @@ +# 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 (.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 diff --git a/Deployment.md b/Deployment.md new file mode 100644 index 0000000..7911868 --- /dev/null +++ b/Deployment.md @@ -0,0 +1,73 @@ +# Deployment + +## CI Pipeline (Gitea Actions) + +Pushing to `main` triggers `.github/workflows/publish.yml`: + +1. Checks out code +2. Installs dependencies (`npm ci`) +3. Runs `npm run build` (Vite) +4. Logs into Gitea Container Registry +5. Builds and pushes a **multi-arch image** (amd64 + arm64) tagged `latest` + short SHA + +Image: `git.necloud.us/nate/siteforge:latest` + +## Servers + +### Rocky (Beta) + +Podman on ZimaOS NAS, reachable over Tailscale. + +```bash +podman run -d --name siteforge-app \ + --restart unless-stopped \ + -p 4173:4173 \ + --env-file /mnt/raid/siteforge/.env \ + --mount type=bind,source=/mnt/raid/siteforge/data,target=/app/data,Z \ + git.necloud.us/nate/siteforge:latest +``` + +### Oracle Arm (Production) + +Docker on Oracle Cloud free-tier ARM instance. + +```bash +docker pull git.necloud.us/nate/siteforge:latest + +docker rm -f siteforge-app + +docker run -d --name siteforge-app \ + --restart unless-stopped \ + -p 4173:4173 \ + --env-file /home/opc/siteforge/.env \ + --mount type=bind,source=/home/opc/siteforge/data,target=/app/data \ + git.necloud.us/nate/siteforge:latest + +docker ps +``` + +## Environment Variables (`.env`) + +| Variable | Description | +|----------|-------------| +| `ADMIN_PASSWORD` | Password for `/admin` — **change from default `changeme`** | +| `PORT` | Server port (default `4173`) | +| `TRUST_PROXY_HOPS` | Number of reverse-proxy hops to trust (default `1`) | +| `RESEND_API_KEY` | Resend API key for transactional email | +| `FROM_EMAIL` | Sender address for study emails | + +## Data Directory + +The `data/` bind mount is the only stateful piece. Upgrading is safe — pull the new image and restart. The data directory is never modified by the container build. + +**To back up**: copy the entire `data/` directory from the host. + +## Service Worker Cache Note + +After a deploy, users may see the old version until the Workbox service worker refreshes. Force-clear it in the browser console: + +```js +const regs = await navigator.serviceWorker.getRegistrations() +for (const r of regs) await r.unregister() +location.reload() +``` diff --git a/Home.md b/Home.md index 5d08b7b..2a10cb8 100644 --- a/Home.md +++ b/Home.md @@ -1 +1,22 @@ -Welcome to the Wiki. \ No newline at end of file +# Siteforge Wiki + +Documentation for the **Verse by Verse with Nate** web app (Siteforge). + +## Pages + +| Page | What it covers | +|------|---------------| +| [Architecture](Architecture) | Tech stack, folder layout, data storage, key files | +| [Deployment](Deployment) | CI pipeline, container registry, server deploy commands | +| [Admin Guide](Admin-Guide) | Using the admin panel: content, episodes, users, backups | +| [Study System](Study-System) | Lessons, sections, community, notes, reminders, certificates | +| [API Reference](API-Reference) | All `/api/` endpoints grouped by feature | + +## Quick facts + +- **Stack**: Node.js + Express backend, React + TypeScript frontend, Vite build, PWA (Workbox) +- **Data**: JSON files in `/app/data/` (persisted via Docker bind mount) +- **Port**: `4173` in production +- **Admin**: `/admin` — password set via `ADMIN_PASSWORD` env var +- **Study platform**: `/study` — separate user accounts from admin +- **Version**: v2.0.0 diff --git a/Study-System.md b/Study-System.md new file mode 100644 index 0000000..2a27a13 --- /dev/null +++ b/Study-System.md @@ -0,0 +1,73 @@ +# Study System + +The study platform lives at `/study`. It uses its own user accounts, separate from the admin. + +## User Accounts + +- Sign up at `/study/signup` with email + password +- Sessions last 30 days (cookie-based) +- Users enroll in one or more studies +- Profile fields: display name, avatar URL, newsletter opt-in, reminder preferences + +User data: `data/study-users.json` + +## Studies & Sections + +Each study (e.g. "Colossians: Rooted in Christ", slug `colossians`) has a list of **sections** (lessons): + +| Field | Description | +|-------|-------------| +| `id` | URL slug, e.g. `1-1-2` | +| `title` | Lesson title | +| `releaseDate` | Locks the lesson until this date | +| `scriptureText` | Passage text | +| `commentary` | Instructor commentary | +| `reflectionQuestions` | Array of questions | +| `greekNotes` | Array of word study entries | + +URL pattern: `/study/:studySlug/:sectionId` + +## Progress Tracking + +Completing all reflection questions marks a section complete. Progress is stored in `data/study-progress/.json`. + +Completion unlocks the Next button to the following section. + +## Notes + +Students take notes anchored to parts of each lesson (Scripture, Commentary, Reflection). Notes are stored in `data/study-notes/.json` as a map of anchor keys → HTML strings (TipTap rich text). Auto-saved with a 1.5-second debounce. + +## Community + +Each study has a feed at `/study/:studySlug/community`. + +- Posts are scoped to a study, optionally to a specific lesson (`sectionId`) +- Enrolled users can post, reply, and like posts +- Likes stored as `likedBy` array of user IDs on each post +- **"Discuss this lesson"** shortcut in the lesson sidebar links to the community pre-filtered to the current lesson + +Data: `data/study-community.json` (up to 500 posts, 50 replies each) + +## Reminders & Re-engagement Emails + +Users opt into study reminder emails via their account page. + +| Type | Trigger | +|------|---------| +| Lesson reminder | New lesson released → email to enrolled users with reminders on | +| Re-engagement | User inactive 7 / 14 / 30 days → tiered emails (T1/T2/T3) | +| Weekly digest | Weekly lesson summary (for users who chose digest frequency) | + +User fields: `studyRemindersEnabled`, `reminderFrequency` (`immediate` or `weekly`), `remindersPausedUntil`. + +Emails sent via **Resend**. Templates live in the Resend dashboard. + +## Certificates + +Completing all sections of a study generates a certificate, emailed to the student. Publicly viewable at `/certificate/:token`. + +Data: `data/study-certificates.json` + +## Streaks + +Each section completion updates `currentStreak` and `longestStreak` on the user. Displayed on the account page.