2026-07-22 13:03:05 -04:00
2026-06-18 09:35:44 -04:00
2026-04-27 11:26:01 -04:00
2026-05-18 08:27:24 -04:00
2026-06-08 10:24:37 -04:00
2026-06-04 10:12:13 -04:00
2026-07-22 13:03:05 -04:00

Siteforge

A podcast and ministry website for Verse by Verse with Nate — a verse-by-verse Bible teaching podcast. Built with React + TypeScript (Vite) on the frontend and Express.js on the backend, deployed as a Docker container via GitHub Actions.

Features

  • Podcast episodes — episode listing, detail pages, downloadable resources, finished series archive
  • Study system — per-user enrollment in structured Bible studies with sections, notes, progress tracking, section quizzes, community posts/comments, and completion certificates
  • Study accounts — signup/login, email verification, avatar upload, email change with re-verification
  • Two-factor authentication — TOTP authenticator app + email OTP backup, recovery codes
  • Contact form — submissions stored server-side, admin reply with templates, inbound email webhook (Resend)
  • Q&A — public question submission, admin moderation, pinned answers, shareable question links
  • Email integration — welcome emails, admin notifications, admin replies, study reminder emails (via Resend)
  • Admin panel — content management with draft/publish workflow, episode scripts, podcast checklist, QR code management, subscriber management, asset uploads
  • Analytics — page views, audio plays, download tracking, geo-based visitor stats (country/state/city), consent-based cookie opt-in
  • Backup system — automatic JSON snapshots with restore, full tar.gz data export/import for server migration
  • Version display — app version and git commit shown in admin sidebar; /api/version endpoint

Tech Stack

Layer Technology
Frontend React 19, TypeScript, Vite, React Router v7
Backend Express 5, Node.js 22
Email Resend
TOTP otplib
QR Codes qrcode
Rich text TipTap
Charts Chart.js + react-chartjs-2, react-simple-maps
Documents docx, mammoth
Container Docker (GHCR), GitHub Actions CI

Project Structure

siteforge/
├── src/                    # React frontend
│   ├── App.tsx             # Root + all client routes
│   ├── App.css
│   └── ...                 # Page and component files
├── server/                 # Express backend
│   ├── config.js           # Constants, env vars, APP_VERSION, GIT_COMMIT
│   ├── data.js             # In-memory state + JSON persistence
│   ├── helpers.js          # Shared utilities (IP, rate limiting, etc.)
│   ├── study-helpers.js    # Study email scheduling, cert generation
│   └── routes/             # Route handlers (one file per domain)
├── data/                   # Seed data (JSON); live data at /app/data in container
├── server.js               # Entry point — mounts all routers
├── Dockerfile
├── entrypoint.sh           # Seeds /app/data on first run, never overwrites live data
├── docker-compose.yml
└── .github/workflows/docker-publish.yml

Environment Variables

Variable Required Description
SESSION_SECRET Yes Secret for Express session signing
ADMIN_PASSWORD_HASH Yes bcrypt hash of the admin password
RESEND_API_KEY Yes Resend API key for sending email
RESEND_FROM Yes Sender address (e.g. Nate <nate@example.com>)
RESEND_TO Yes Address that receives contact form notifications
RESEND_REPLY_TO No Reply-to address on outbound emails
RESEND_WEBHOOK_TOKEN No Validates inbound Resend webhook requests
INBOUND_EMAIL_SECRET No Validates inbound email route requests
RESEND_CONTACTS_API_KEY No Resend Contacts API key for subscriber sync
RESEND_SEGMENT_ID No Resend segment ID for subscriber list
COMMIT_SHA No Git SHA baked in at build time by CI (shown in admin UI)
TRUST_PROXY_HOPS No Number of proxy hops to trust (default 1) — set correctly for accurate client IPs behind a reverse proxy
ALLOW_INSECURE_COOKIES No Set to true to allow session cookies over HTTP (for local/non-HTTPS access)
TITUS_STUDY_FILE No Path to the Titus study PDF (defaults to bundled file)
TITUS_STUDY_DOWNLOAD_NAME No Filename shown to users downloading the Titus PDF
PORT No HTTP port (default 4173)
CACHE_PURGE_WEBHOOK_URL No Webhook URL for one-click cache purge from Admin Operations
DEPLOY_WEBHOOK_URL No Webhook URL for one-click deploy trigger from Admin Operations

Frontend Routes

Path Page
/ Landing page
/start-here Start here / intro page
/episodes Episode listing
/episodes/:id Episode detail
/finished Finished series listing
/finished/:id Finished series detail
/resources Resources listing
/downloads/:id Download detail
/about About page
/contact Contact form
/questions Q&A listing
/subscribe Email subscribe
/subscribe/thanks Subscribe confirmation
/study Study landing page
/study/signup Study account signup
/study/account Study account dashboard
/study/:studySlug Study index
/study/:studySlug/community Study community posts
/study/:studySlug/notes Study notes
/study/:studySlug/:sectionId Study section
/study/:studySlug/:sectionId/quiz Section quiz
/certificate/:token Public completion certificate
/privacy Privacy policy
/thanks Contact thank-you
/admin Admin panel (requires password + TOTP)
/preview Content draft preview

Key API Endpoints

Endpoint Description
GET /api/version Returns { version, commit }
GET /api/content Public site content (CMS data)
POST /api/contact Submit contact form
POST /api/analytics/pageview Record page view
POST /api/analytics/play Record audio play
GET /api/episode-audio Episode audio proxy
GET /api/admin-auth/status Admin session status + version info
POST /api/admin-auth/login Admin login
POST /api/admin-auth/totp-verify Complete TOTP 2FA
POST /api/admin-auth/totp-setup-init Begin TOTP enrollment
POST /api/admin-auth/totp-setup-confirm Confirm TOTP enrollment
GET /api/admin-stats Hit/visitor analytics
GET /api/admin-stats/backup Trigger stats snapshot
GET /api/admin-backup/export Download full tar.gz data backup
POST /api/admin-backup/import Restore from tar.gz backup
GET /api/admin/study-users List study accounts
GET /api/admin-study-certificates Study completion certificates
GET /api/admin-contact-submissions Contact form submissions
POST /api/admin-contact-submissions/:id/reply Reply to a submission
GET /api/admin/qr-codes QR code management
GET /api/admin-episode-scripts/:episodeNumber Episode script
GET /api/admin-podcast-checklist Podcast checklist state
GET /api/admin-subscribers Subscriber list

Development

# Install dependencies
npm install

# Start frontend dev server only (Vite, http://localhost:5173)
npm run dev

# Start backend API server only (reads .env, http://localhost:4173)
npm run api

# Start both concurrently
npm run dev:full

# Production build
npm run build

# Start production server (reads .env)
npm start

Create a .env file at the project root with the required environment variables before running locally. The server uses --env-file=.env.


Docker / Deployment

Images are built and pushed to ghcr.io/nmemmert/siteforge automatically on every push to main or when a v* tag is pushed. The git commit SHA is baked into the image as COMMIT_SHA at build time and displayed in the admin sidebar.

First run

On first boot, entrypoint.sh copies seed data from the bundled data-seed/ into /app/data/ if that directory is empty. Subsequent restarts skip this step, so live data is never overwritten by an upgrade.

docker-compose.yml

A docker-compose.yml is included at the project root. Mount /app/data to a persistent volume or host path and supply all required environment variables.

volumes:
  - /path/to/your/data:/app/data

Useful commands

# Pull latest image and restart
docker compose pull
docker compose up -d

# View logs
docker compose logs -f

# Stop
docker compose down

Persistent Data

All server-managed data lives under /app/data (or ./data locally):

Path Contents
admin-content.json Published CMS content
admin-content-draft.json Draft content (unpublished)
hit-stats.json Page hit analytics
visitor-stats.json Detailed visitor analytics
contact-submissions.json Contact form submissions
study-users.json Study account records
study-reminders.json Scheduled study reminder state
qr-codes.json QR code registry
episode-scripts/ Per-episode script files
backups/ Automatic JSON snapshot backups
uploads/ Admin-uploaded assets (served at /uploads/*)

Admin Access

  1. Navigate to /admin
  2. Enter the admin password (hashed value in ADMIN_PASSWORD_HASH)
  3. Complete TOTP 2FA, or use an email OTP if no TOTP app is enrolled
  4. On first use, enroll a TOTP authenticator app from Admin → Settings

Recovery codes are generated during TOTP setup and can be regenerated from Admin → Settings.

Admin capabilities

  • Content — edit and publish site content with draft/preview workflow
  • Episode scripts — write and manage per-episode scripts; export to .docx
  • Podcast checklist — track per-episode production steps
  • QR codes — create, manage, and track scan counts for QR codes
  • Questions — moderate submitted Q&A, pin answers, reply
  • Contact — view submissions, reply with templates, check email health
  • Study users — view enrollments, progress, certificates, manually enroll/unenroll
  • Study comments — moderate community post comments
  • Subscribers — view and export subscriber list
  • Assets — upload and delete hosted images
  • Stats — page hits, visitor geo analytics, download stats; export CSV
  • Backup — snapshot now, restore from snapshot, export/import full tar.gz
  • Operations — purge cache, trigger deploy, manage SEO/sitemap/redirects

Versioning

The app version is read from package.json at startup (APP_VERSION). The git commit is baked in via COMMIT_SHA at Docker build time (set automatically by CI). Both are visible in the admin sidebar and at GET /api/version.

Bump package.json before every commit pushed to main: patch for fixes, minor for features, major for breaking changes.

S
Description
No description provided
Readme 31 MiB
Languages
TypeScript 47%
JavaScript 26.6%
CSS 15%
HTML 10.9%
Shell 0.4%