Skip to content

Latest commit

 

History

84 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scheduler

Group event scheduling for scheduler.snoking.app. Pick a set of candidate days, share one link, and let everyone mark the days that work for them — no accounts, no email. Built on Cloudflare Workers + D1 with a no-build static frontend.

Roles

Three distinct roles, each identified by a secret bearer key/id stored in localStorage:

Role Page Can do
Admin /admin Create and delete organizers (mint/revoke organizer keys).
Organizer /organizer Create, edit, and delete their own events; see the availability matrix; pick the final date.
Attendee /e/:id Mark each candidate day as Works / Could work / Doesn't work; edit anytime until finalized.

Keys are GUIDs. Only their SHA-256 hashes are stored server-side — the raw key is shown once and never again. The same person can hold both an admin key and an organizer key (the admin console's "Open as this organizer" button bridges the two on one device).

Bootstrapping the first admin

Admin creation is gated by the ADMIN_BOOTSTRAP secret and fails closed: if the secret is not set, bootstrapping is unavailable (no "first admin free" path), so a public deployment can never hand admin to the first anonymous caller. Set the secret before the first deploy, then on /admin enter the bootstrap code to mint an admin key for your device.

# set/change the bootstrap code (required before bootstrapping any admin)
echo "your-secret-code" | npx wrangler secret put ADMIN_BOOTSTRAP

Data model (D1)

  • admins(id, name, created_at)id = sha256(admin key)
  • organizers(id, name, created_by, created_at)id = sha256(organizer key)
  • events(id, organizer_id, title, location, slots, organizer_prefs, questions, chosen_date, …)slots = JSON [{id,date,start,end}]; questions = JSON [{id,text}]; chosen_date holds the chosen slot id
  • attendees(id, name, …)id = attendee GUID from their device
  • responses(event_id, attendee_id, day, status)day holds a slot id; statusworks | maybe | no
  • answers(event_id, attendee_id, question_id, answer) — attendee free-text answers
  • push_subs(endpoint, organizer_id, p256dh, auth, created_at) — organizer Web Push subscriptions

PWA + push notifications

Installable PWA (manifest.webmanifest, push-only sw.js, calendar-emoji icons). The Worker injects a build-id auto-reload script into every HTML page (/api/version + CF_VERSION_METADATA), so the app picks up JS/CSS changes automatically — including warm-resumed home-screen apps. run_worker_first lets the Worker serve/inject HTML.

Organizers can enable Web Push (VAPID, RFC 8291 aes128gcm) to get notified when someone responds. On iOS this only works once the PWA is added to the Home Screen. Keys:

# private VAPID key (JWK); public key + subject live in wrangler.jsonc [vars]
echo '<VAPID_PRIVATE_JWK_json>' | npx wrangler secret put VAPID_PRIVATE_JWK

Develop

npm install
npm run db:local       # apply schema to the local D1
npm run dev            # wrangler dev

Deploy

npm run db:remote      # apply schema to the remote D1 (first time / on changes)
npm run deploy

The custom domain scheduler.snoking.app is wired up via the routes block in wrangler.jsonc.

CI/CD

.github/workflows/deploy.yml deploys the Worker + assets on every push to master (and via manual "Run workflow"). It uses two repo secrets:

  • CLOUDFLARE_API_TOKEN — token with Workers Scripts:Edit (+ D1 if migrating)
  • CLOUDFLARE_ACCOUNT_ID

CI runs wrangler deploy only. Schema migrations are not run by CI — apply schema.sql (or one-off ALTERs) manually with npm run db:remote. Worker secrets like ADMIN_BOOTSTRAP live in Cloudflare and are untouched by deploys.

About

Group event scheduling for scheduler.snoking.app.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages