Files
menu/README.md
T
tanshu af083415a7 Make the admin's buttons work in a browser
Every mutating form now carries the CSRF token as a hidden field, and the
upload route accepts it as a multipart field as well as a header. Driving the
real UI in Firefox found what the header-only tests could not: Create Draft,
Save, Publish, Delete and the up/down buttons all posted without a token and
were rejected. Adds npm run check-site, which verifies a running Site from the
outside and exits non-zero so it can gate a cutover.
2026-09-22 21:32:52 +05:30

95 lines
3.7 KiB
Markdown

# menu-admin
Uploads, orders and publishes the customer-facing menus of a restaurant Site as
photographed Pages, replacing the `scp`-a-JPG workflow. One instance per Site;
`docs/plan.md` is the agreed design and `CONTEXT.md` is the glossary.
## Status
Built: Site config, the Menu Set store, the image pipeline, the customer-facing
renderer (landing page, Section pages, archived sets, media route) with the
approved design, the admin (sign in, create a set, upload and reorder Pages,
preview, publish, restore, delete), the `verify`/`repair` CLI, the container and
the Ansible playbook. Not yet done: the parallel-run deployment on `monoco`,
which needs a human present.
## Deploying
```sh
./deploy.sh 2026-09-22 # tests, build, push to registry.tanshu.com
```
then the playbook in `ansible-playbooks/menu-admin/` — its readme covers the
parallel run, the `site.env` step that gives the admin a password, and cutover.
After deploying, check the running site from the outside (read-only, exits
non-zero on failure, so it can gate a cutover):
```sh
npm run check-site -- https://menu-new.hopsngrains.com
```
## Layout
```
src/config.ts Site config: brand, theme, Sections, limits
src/storage/manifest.ts Manifest type, validation, atomic JSON writes
src/storage/sets.ts The Menu Set store: create, add Pages, publish, restore, delete
src/storage/validate.ts Boot validation: fatal problems and warnings
src/images/pipeline.ts sharp variants: Original, Viewing image, Thumbnail
src/auth.ts scrypt password hash, session cookie, login throttle, CSRF
src/render/ HTML builder, document shell, the customer pages, the admin
src/routes/public.ts Landing, Section, archive and media routes
src/routes/admin.ts Sign in, dashboard, editor, upload, publish, restore, delete
src/server.ts buildApp (what tests drive) and main (what the container runs)
public/ Hand-written CSS, ~2KB of progressive-enhancement JS, Archivo
config/ Per-Site config, one file per Site
test/ node:test suites
docs/plan.md The agreed plan
docs/adr/ Decisions worth remembering
deploy.sh Tests, build, push
```
The Ansible playbook lives in `ansible-playbooks/menu-admin/`, beside the other
playbooks, because that is where it is run from.
## Working on it
Node 24 or newer runs the TypeScript sources directly.
```sh
npm install
npm test # node:test
npm run typecheck # tsc --noEmit
npm run seed # placeholder Pages in .dev/data, for design review
npm run dev # http://localhost:3000 against .dev
```
To sign in locally, put a password hash in `.dev/site.env`:
```sh
printf 'ADMIN_PASSWORD_HASH=%s\nSESSION_SECRET=%s\n' \
"$(node -e "import('./src/auth.ts').then(async m => process.stdout.write(await m.hashPassword('your password')))")" \
"$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))")" \
> .dev/site.env
```
## Who can do what
`/` and `/<section>/` are public. `/admin` needs the password; a Draft's Pages
are served only to a signed-in session, and Originals have no route at all.
## Storage model
```
data/
├── originals/<section>/<set-id>/<page-id>.<hash>.<ext> # kept, never served
├── drafts/<section>/<set-id>/ # manifest.json + variants
└── published/<section>/<slug>/ # manifest.json + variants
```
The directory decides a Menu Set's state; the manifest carries metadata only.
`<slug>` is the valid-until date, else the publication date. The Current Menu
Set for a Section is the published one with the newest `publishedAt`. Published
sets are immutable: changing a menu means publishing a new set.