32 lines
1.7 KiB
Markdown
32 lines
1.7 KiB
Markdown
# Scheduling — the reference plugin
|
|
|
|
A worked example of the [plugin contract](../../docs/plugin-contract.md). Copy this folder, rename
|
|
it (the folder name becomes the plugin id and mount path), and point it at your own backend.
|
|
|
|
What it demonstrates:
|
|
|
|
- **A list page that fetches upstream data** — `GET /scheduling/shifts` calls the upstream REST
|
|
service and renders the rows with the core building blocks (`shifts.ejs` → app shell, filter-bar,
|
|
data-table). Search round-trips the URL; zero-JS.
|
|
- **A form that forwards a write upstream** — `GET /scheduling/shifts/new` renders the form,
|
|
`POST /scheduling/shifts` CSRF-verifies it (`ctx.verifyCsrf`) and forwards the create upstream,
|
|
then POST-redirect-GET. The form body lives in the plugin's own `views/partials/shift-form.ejs`,
|
|
reusing the core `field` partial.
|
|
- **Permission-gated nav** — the "Shifts" nav leaf and routes are gated on `scheduling:read` /
|
|
`scheduling:write`; the whole "Scheduling" section is invisible to anyone without the grant.
|
|
|
|
The plugin holds **no state** — data lives upstream (README → *Stateless*). Handlers are thin and
|
|
`fetch` is injectable, so they unit-test as pure functions (`shifts.test.ts`).
|
|
|
|
## Upstream
|
|
|
|
Set `SCHEDULING_UPSTREAM` to your backend's base URL (it must expose `GET /shifts` and
|
|
`POST /shifts`). The dev compose points it at a tiny in-memory mock (`examples/shifts-upstream/`)
|
|
so `docker compose up` shows the plugin working out of the box.
|
|
|
|
## Granting access
|
|
|
|
A user sees Scheduling once they hold the `scheduling:read` role in Keto (and `scheduling:write`
|
|
to create). The one-command bootstrap grants both to the demo admin, so the seeded
|
|
`admin@plainpages.local` can use it immediately.
|