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).
This commit is contained in:
+172
@@ -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/<kiosk_token>`) 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
|
||||||
|
<ort84@Jorges-MacBook-Pro-2.local>`). 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 <files>
|
||||||
|
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`.
|
||||||
Reference in New Issue
Block a user