From 73a1d58838134d38b04c405f281cdaedf80199d5 Mon Sep 17 00:00:00 2001 From: ort Date: Sat, 15 Aug 2026 13:48:01 -0400 Subject: [PATCH] Add takeover.md Project overview, current status, architecture summary, and the commit/push workflow for the Gitea remote (deploy key location, GIT_SSH_COMMAND usage). --- takeover.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 takeover.md diff --git a/takeover.md b/takeover.md new file mode 100644 index 0000000..a23b481 --- /dev/null +++ b/takeover.md @@ -0,0 +1,172 @@ +# Kids Calendar — Takeover Notes + +## What this is + +A self-hosted family weekly calendar, evolved from a single static HTML +file (`weekly_schedule.html`, still sitting untouched one directory up, kept +as a design reference — not part of this app). + +Three roles: + +- **Parents** — real accounts (email + password), grouped into a + "household." Both parents in a household see and edit the same children + and calendars. A parent invites their spouse via a one-time link rather + than open signup into an existing household. +- **Children** — no accounts. Each child gets an unguessable link + (`/k/`) meant to be opened on a tablet. That view is + read-only except for checkboxes — there is no server route that lets a + kiosk link edit text, add/delete tasks, or see another child's data. The + access boundary is enforced by which API routes exist, not by hiding UI. +- Checkbox taps on the kiosk show up in the parent's browser within ~4 + seconds via polling, without a manual reload. + +Each child can have multiple **calendars** (e.g. "Week of Aug 17"), each +independently saved/reopened/edited/duplicated/printed — not one calendar +that just gets overwritten every week. + +## Status + +Fully built and manually verified end-to-end (browser-driven testing, not +an automated test suite — see "Not done" below). All planned phases are +complete: + +1. Scaffold (Express + SQLite) +2. Auth + household (signup/login/logout, sessions) +3. Spouse invite flow +4. Children + calendar CRUD, dashboard, calendar editor +5. Kiosk view +6. Polling for near-real-time sync +7. Packaging (Docker) + hardening (rate limits, input length caps, CSRF) + +Verified specifically: +- Full parent flow: signup → invite spouse → add child → create calendar → + edit tasks/labels → duplicate week (checks reset, tasks carry over) → + print → reload persists everything. +- Kiosk boundary: checkbox toggle works; direct API calls attempting to + edit text/add/delete tasks through the kiosk token return 404 (no such + route); bogus tokens 404; unauthenticated parent routes 401; cross-origin + POSTs blocked (CSRF check) 403. +- Polling: kiosk checkbox change reflected on parent view without reload; + parent's in-progress edit is not clobbered by an incoming poll (polling + pauses while any `contenteditable` field has focus, resumes on blur). +- Docker: `docker build`, plain `docker run`, and `docker compose up -d + --build` all tested directly — image builds with no native compile step, + container survives a restart with data intact, compose's named volume + and `.env` wiring both work as documented in `README.md`. + +### Not done / worth knowing + +- No automated test suite (unit/integration tests) — all verification so + far was manual (curl scripts + the in-app browser tool). If this grows, + that's the first gap to close. +- No production deployment behind a real reverse proxy/HTTPS has been + tested — `COOKIE_SECURE` and the reverse-proxy notes in `README.md` are + written but unverified against a live TLS setup. +- Git author identity on existing commits was auto-filled by git from the + machine's username/hostname (`Jorge Ortega II + `). Reset with `git config --global + user.name` / `user.email` if that's wrong, then optionally `git commit + --amend --reset-author` on affected commits. + +See `README.md` for full run instructions (local + Docker) and the +environment variable reference — this file is about *repo/process* +context, `README.md` is about *running* the app. + +## Architecture at a glance + +- **Backend**: Node.js + Express, `src/app.js` wires everything up. + `server.js` is the entry point. +- **DB**: SQLite via the built-in `node:sqlite` module (not + `better-sqlite3` — that failed to compile natively in this environment, + and `node:sqlite` has the added benefit of needing zero native build + tooling anywhere, including on a NAS/ARM host). Requires Node ≥22.5. + Schema in `src/db/schema.sql`, applied idempotently on boot by + `src/db/migrate.js`. +- **Sessions**: custom SQLite-backed session store + (`src/lib/sqliteSessionStore.js`) instead of `connect-sqlite3`, because + that package pulls in the native `sqlite3` module — same reason as + above, avoid native deps entirely. +- **Auth**: `bcryptjs` (pure JS) for password hashing, `express-session` + for cookies. CSRF is handled by a same-origin Origin/Referer check + (`src/middleware/csrf.js`) rather than a token library — reasonable for + a small LAN-scale app, called out in `README.md` as worth upgrading if + ever exposed to the wider internet. +- **Routes**: `src/routes/*.js`, one file per resource area (auth, + household, invites, children, calendars, kiosk). The kiosk router is + mounted separately with no session middleware at all + (`app.use('/api/kiosk/:token', kioskRoutes)`), authenticated purely by + the token in the URL. +- **Frontend**: plain HTML/CSS/vanilla JS in `public/`, no build step, + served via `express.static`. `public/js/calendarRender.js` is the shared + board-rendering logic used by both the parent editor + (`calendar.html`/`calendar.js`, `editable: true`) and the kiosk view + (`kiosk.html`/`kiosk.js`, `editable: false`) — one renderer, one + `editable` flag gates every mutation affordance. +- **Data model**: household → parents / children → calendars → + calendar_blocks (per-calendar block labels) + calendar_tasks (per + day+block). See `src/db/schema.sql` for the authoritative shape. + +## How to commit / push to Gitea + +Remote: `git@git.oservr.com:ort/KCal.git`, **port 23** (non-standard — +that's why the remote is configured as a full `ssh://` URL rather than the +usual `git@host:path` shorthand, since the shorthand can't carry a custom +port). + +```bash +git remote -v +# origin ssh://git@git.oservr.com:23/ort/KCal.git (fetch) +# origin ssh://git@git.oservr.com:23/ort/KCal.git (push) +``` + +### The deploy key + +Lives in `GitOServr/` in this project (note the spelling — no "e" before +"r"): + +- `GitOServr/GitOservr` — the private key (ED25519). **Never commit + this.** `GitOServr/` is listed in `.gitignore` specifically to prevent + that, but that's a safety net, not a substitute for being careful — e.g. + don't run `git add -f` on it. +- `GitOServr/GitOservr.pub` — the matching public key. +- `GitOServr/known_hosts` — the Gitea host's SSH host keys, pinned via + `ssh-keyscan -p 23 git.oservr.com` the first time this was set up (TOFU — + trust on first use). If the server's host key ever legitimately changes + (e.g. server migration), you'll need to re-run that scan and confirm the + new key out-of-band before trusting it again — don't just delete + `known_hosts` to make a warning go away. +- `GitOServr/GitOServr_0x9ED086FFE7AE26B8_public.asc` — a PGP public key, + not used by the git push flow above (likely for commit signing, unused + so far). + +The private key is outside `~/.ssh/`, so plain `git push` won't find it — +every push needs `GIT_SSH_COMMAND` pointed at it explicitly: + +```bash +GIT_SSH_COMMAND='ssh -i "GitOServr/GitOservr" -o UserKnownHostsFile="GitOServr/known_hosts"' git push +``` + +(Run from the `kids-calendar/` directory, since the paths above are +relative to it.) + +If this gets tedious, the standing alternative — deliberately not set up, +per a "no thanks" during initial setup — is a repo-local git config entry: + +```bash +git config core.sshCommand 'ssh -i "GitOServr/GitOservr" -o UserKnownHostsFile="GitOServr/known_hosts"' +``` + +That makes plain `git push`/`git pull` work without the env var prefix. +It's local to this repo's `.git/config` (never pushed, never affects other +repos on this machine). + +### Normal workflow + +```bash +git add +git commit -m "..." +GIT_SSH_COMMAND='ssh -i "GitOServr/GitOservr" -o UserKnownHostsFile="GitOServr/known_hosts"' git push +``` + +Current state: 2 commits on `main`, both pushed — initial app commit, then +a follow-up that added `GitOServr/` and `.DS_Store` to `.gitignore`.