Rewrite README to accurately describe the app; bump to v1.0.2
Replaces outdated portfolio/project-cards description with accurate documentation of the podcast/ministry site: features, all env vars, routes, API endpoints, persistent data paths, and admin capabilities. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,136 +1,259 @@
|
|||||||
# Siteforge
|
# Siteforge
|
||||||
|
|
||||||
This project is a React + TypeScript portfolio app built with Vite.
|
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.
|
||||||
|
|
||||||
It is designed to showcase two categories of work:
|
## Features
|
||||||
|
|
||||||
- Websites
|
- **Podcast episodes** — episode listing, detail pages, downloadable resources, finished series archive
|
||||||
- Apps
|
- **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
|
||||||
|
|
||||||
The app now supports:
|
---
|
||||||
|
|
||||||
- Dynamic cards generated from a project data source
|
## Tech Stack
|
||||||
- Built vs Hosted filters
|
|
||||||
- A dedicated About page route for each project
|
|
||||||
- An admin editor route to update cards and About pages
|
|
||||||
- A per-project README summary block on each project page
|
|
||||||
- Embedded Spotify podcast player on the home section
|
|
||||||
|
|
||||||
## Run locally
|
| 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
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Install dependencies
|
||||||
npm install
|
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
|
npm run dev:full
|
||||||
```
|
|
||||||
|
|
||||||
`dev:full` starts both:
|
# Production build
|
||||||
|
|
||||||
- Vite frontend (`http://localhost:5173`)
|
|
||||||
- Admin content API (`http://localhost:4173`)
|
|
||||||
|
|
||||||
## Build for production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
# Start production server (reads .env)
|
||||||
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run in container
|
Create a `.env` file at the project root with the required environment variables before running locally. The server uses `--env-file=.env`.
|
||||||
|
|
||||||
Pull image directly:
|
---
|
||||||
|
|
||||||
```bash
|
## Docker / Deployment
|
||||||
docker pull ghcr.io/nmemmert/siteforge:latest
|
|
||||||
docker run -d --name siteforge-app -p 4173:4173 -v ./data:/app/data ghcr.io/nmemmert/siteforge:latest
|
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.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /path/to/your/data:/app/data
|
||||||
```
|
```
|
||||||
|
|
||||||
Run with Docker Compose:
|
### Useful commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Pull latest image and restart
|
||||||
docker compose pull
|
docker compose pull
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
|
||||||
|
|
||||||
Admin auth:
|
# View logs
|
||||||
|
|
||||||
- Set `ADMIN_PASSWORD` on the server/container to protect `/admin` and admin stats/maintenance endpoints.
|
|
||||||
- Without `ADMIN_PASSWORD`, admin login is disabled until configured.
|
|
||||||
- Optional: set `CACHE_PURGE_WEBHOOK_URL` to enable one-click cache purge from Admin Operations.
|
|
||||||
- Optional: set `DEPLOY_WEBHOOK_URL` to enable one-click deploy trigger from Admin Operations.
|
|
||||||
- Optional: set `BUILD_COMMIT`, `BUILD_NUMBER`, and `DEPLOYED_AT` to display deployment status in Admin.
|
|
||||||
|
|
||||||
The app will be available at `http://localhost:4173`.
|
|
||||||
|
|
||||||
Persistent admin saves:
|
|
||||||
|
|
||||||
- Admin updates are written to `data/admin-content.json`.
|
|
||||||
- Admin drafts are written to `data/admin-content-draft.json`.
|
|
||||||
- Built-in page hit stats are written to `data/hit-stats.json`.
|
|
||||||
- Detailed visitor analytics are written to `data/visitor-stats.json`.
|
|
||||||
- Backup snapshots are written to `data/backups/`.
|
|
||||||
- Uploaded admin assets are stored in `data/uploads/` and served at `/uploads/*`.
|
|
||||||
- `docker-compose.yml` mounts `./data` into the container at `/app/data`.
|
|
||||||
- This keeps all admin-managed data after container restarts/rebuilds/updates.
|
|
||||||
|
|
||||||
## Where to edit content
|
|
||||||
|
|
||||||
- Project domain data and ownership: `src/data/projects.ts`
|
|
||||||
- Portfolio rendering logic: `src/App.tsx`
|
|
||||||
- Portfolio UI styles: `src/App.css`
|
|
||||||
- Global theme variables: `src/index.css`
|
|
||||||
|
|
||||||
## Routes
|
|
||||||
|
|
||||||
- Site home: `/`
|
|
||||||
- Start Here: `/start-here`
|
|
||||||
- Questions: `/questions`
|
|
||||||
- Privacy: `/privacy`
|
|
||||||
- Terms: `/terms`
|
|
||||||
- Admin editor: `/admin`
|
|
||||||
- Admin draft preview: `/preview`
|
|
||||||
|
|
||||||
## Admin editing
|
|
||||||
|
|
||||||
- Open `/admin` to edit homepage content, links, archived studies, Q&A moderation, and analytics.
|
|
||||||
- Use **Operations** to manage SEO metadata, sitemap paths, legal page text, redirect rules, and podcast highlights.
|
|
||||||
- Use **Operations** to upload/delete hosted image assets for use in content and social cards.
|
|
||||||
- Use **Save Draft**, **Preview Draft**, and **Publish Draft** for a safer publish workflow.
|
|
||||||
- Saved edits are written to `data/admin-content.json` through the API server.
|
|
||||||
- Built-in stats in `/admin` include page hits plus visitor details (IP, country/state/county/city, returning visitors, and recent visitor log).
|
|
||||||
- Site Stats in `/admin` includes a Bible Questions inbox sourced from contact form submissions marked as Bible Question.
|
|
||||||
- Analytics cookies are consent-based. Visitors can accept or decline tracking from the site banner.
|
|
||||||
- Admin now includes maintenance actions: **Export JSON**, **Backup Now**, **Prune Old Data**, and **Clear Analytics**.
|
|
||||||
- Admin also supports restoring from a backup snapshot from `/admin`.
|
|
||||||
- The server creates startup + daily backup snapshots and retains recent backups automatically.
|
|
||||||
- Operations can trigger optional **Purge Cache** and **Trigger Deploy** webhooks.
|
|
||||||
|
|
||||||
Deployment note:
|
|
||||||
|
|
||||||
- To keep Admin saves working on the internet, deploy with the Node API (`server.js`) and writable server storage for `data/`.
|
|
||||||
|
|
||||||
Titus study download gate:
|
|
||||||
|
|
||||||
- The site now gates Titus study downloads behind a name/email form.
|
|
||||||
- Default source file is `A_Study_of_Titus.pdf` in the project root.
|
|
||||||
- Override source path with `TITUS_STUDY_FILE` (relative to project root or absolute).
|
|
||||||
- Override downloaded filename with `TITUS_STUDY_DOWNLOAD_NAME`.
|
|
||||||
- When the form checkbox is left enabled (default), contacts are synced to Resend using the same contact sync flow as the contact form.
|
|
||||||
|
|
||||||
Useful container commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose logs -f
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Stop
|
||||||
docker compose down
|
docker compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
Update to latest image:
|
---
|
||||||
|
|
||||||
```bash
|
## Persistent Data
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
All server-managed data lives under `/app/data` (or `./data` locally):
|
||||||
|
|
||||||
- The current project cards are sample placeholders.
|
| Path | Contents |
|
||||||
- Most metadata was scanned from live pages.
|
|---|---|
|
||||||
- Skywatch currently uses a manual fallback description due to a `401` response during automated scan.
|
| `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.
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user