Mike Wichers be2d81b3ab Lead pipeline: local SQLite store as source of truth
Form submissions now land in /data/scout73.db before anything leaves the
box. The Google Sheet and the ntfy push become mirrors whose per-record
outcome is recorded, so a failed sheet write is replayable instead of
surviving only as a push telling you to retype it.

The table is a generic record store keyed by 'kind', with workflow columns
and an audit trail, so RSVPs and other forms can land in the same place
later without a migration. Admin panel talks to /api/admin over HTTP and
never opens the DB file.

- app/store.py     schema, writes, reads, one-time leads.jsonl backfill
- app/admin_api.py token-gated API; fails closed when ADMIN_TOKEN is unset
- app/app.py       three hunks: imports, boot init, join_post
- compose          ADMIN_TOKEN passed through from the Portainer stack env
2026-08-26 19:38:33 -04:00

scout-website

Public recruiting site for Cub Scout Pack 73 and Scouts BSA Troop 73 (Zieglerville / Green Lane, PA). Live at https://greenlanescouts73.org (NPM proxy → scout-website container, port 8132 on arrstack). Replaces the scoutpoc proof of concept (claude-workspace/stacks/scoutpoc).

Updating the calendar (the part you'll actually do)

All dates on the site (home "Next up," both program pages, the Calendar page) render from one JSON file, read fresh on every request:

/srv/scout-website/data/events.json      ← the LIVE file. Edit this.
app/events.json                          ← seed only; copied to /data on first boot.

Edit the live file on the host (or ask Claude), save, refresh the site. No rebuild, no redeploy.

Event format (one object per event):

{"date": "2026-10-17", "end": "2026-10-18", "time": "from 4:00 PM",
 "title": "Fall Campout", "location": "Green Lane Park",
 "unit": "both", "badge": "OVERNIGHT"}
  • date required (YYYY-MM-DD). end only for multi-day events.
  • unit: "pack", "troop", or "both" (default pack). Drives the Pack/Troop calendar filter and each page's "Next up."
  • badge: optional shouty label (FALL OPEN HOUSE, OVERNIGHT, THE BIG ONE).
  • Past events are hidden from "Next up" automatically; keep or prune them in the file, your call.
  • TeamSnap (team 8198615) stays the source of truth; this file is the public snapshot.

To change the seed for future rebuilds too, edit app/events.json here in Gitea and redeploy the stack.

Layout

  • app/app.py — FastAPI app, all five pages server-rendered (Home, /cubs, /troop, /calendar?unit=, /join). /contact 301s to /join.
  • app/events.json — calendar seed (see above).
  • docker-compose.yml — Portainer Repository stack definition (port 8131).
  • Images: not in the repo. Bind-mounted read-only from /srv/scout-website-assets/img. Source photos live on the NAS (Scouting-Recruitment-Images, scouting-comms/pack-73/assets/social); web-sized copies in Scouting-Recruitment-Images/_web.

Leads

/srv/scout-website/data/leads.jsonl — one JSON line per interest-form submission, plus an ntfy push per lead.

Lead pipeline (self-contained)

/join submissions write directly to the shared Google Sheet (SHEET_ID stack env, creds bind-mounted from /srv/scout-website/secrets/service_account.json) and push to the shared ntfy scouting topic (NTFY_BASE/NTFY_TOPIC/NTFY_TOKEN). The extra comments box lands in a new 8th sheet column, Comments. Every submission also appends to /data/leads.jsonl; if both sheet and push fail, a warning push goes to the fallback NTFY_URL topic. No dependency on the scoutpoc or scouting73-form containers: both can be decommissioned without breaking this site. (The flyer QR domain scouting73.thewichersfamily.com should be repointed here before its container is retired.)

S
Description
greenlanescouts73.org - Pack & Troop 73 recruiting site
Readme
1.5 MiB
Languages
Python 99.9%
Dockerfile 0.1%