feat: material managers get a realtime token

- jz.bm.realtime-access admits holders of jz.bm.access_materials
  (Assumption A5), so every editor can follow materials:{id}
- README documents the materials channel, event and authorizer
This commit is contained in:
Jakub Zych
2026-11-09 01:14:56 +01:00
parent 2a47186a7a
commit 8bef2d7acf
5 changed files with 51 additions and 9 deletions

View File

@@ -441,6 +441,12 @@ Attached PDFs are searchable too. After any attach (the admin form, `POST /api/v
Configuration, in the app's `config/search.yaml`: `search.driver`, `search.prefix`, `search.typesense.host`, `port`, `protocol` and `connection_timeout_seconds`. The API key goes only in the environment, `SUMMER_SEARCH__TYPESENSE__API_KEY`; with no key the engine is not configured, nothing is sent and every search answers 503.
## Realtime
An open editor learns of a newer revision before its own save (D-20); the save-time 409 stays the guarantee. After every write that bumps a material's revision (the author's `PUT /_bm/api/v1/materials/{id}`, the management `PUT` and `POST` upsert, so also `bm-notatka push` and the MCP tools, and a `/bmadmin` save) the server enqueues, on the write's own transaction, one broadcast on channel `materials:{id}` with event `updated.jz.bm.material` and payload `{"material_id": 5, "revision": 4}`. It is published only if the write commits, and only ids and the revision number travel, never content. The enqueue runs in a savepoint: a failure is logged and never fails or loses the save. A write that does not bump (visibility, shares, unpublish, files, identical content) publishes nothing (`classes.MaterialRevisionNotifier`).
jz.bm owns the realtime namespace `materials`. Its authorizer accepts only channels of the form `materials:{id}` (`^materials:[1-9][0-9]{0,18}$`; a `presence:` prefix, a leading zero or trailing characters are denied) and allows a subscribe only when the material is visible to the user and they may manage it: `jz.bm.access_materials` and its author, or any such user while it has no author (`api.CanWatchMaterial`), checked on current state at every subscribe. The connection-token guard `jz.bm.realtime-access` admits material managers besides chat-group members and presentation owners, so every possible editor can get a token; the token and subscribe-proxy routes are mounted only while `golem15.chat` or `golem15.presentations` is active. The Centrifugo instance needs the `materials` namespace (see the app's `DEPLOY.md`).
## CLI commands
### bm:materials-reindex