A per-user jz_bm_material_pins table and PUT/DELETE /materials/{id}/pin
on the student API and its token twins: 204 for a material the caller
may see, idempotent, 404 otherwise. List rows, course rows and the
detail carry pinned. The plain list lists the caller's pins first
(newest pin first); with q the relevance order stays. A pin writes only
the pins table and never changes the material's revision.
- PUT /_bm/api/v1/materials/{id} (jwt.auth only, no token twin): every
key of the edit body required, revision checked on the row locked FOR
UPDATE, 409 with the student detail of the server version
- editableMaterialForCaller: 404 for what the caller cannot see, 403 for
what they see but may not manage
- tags from the dictionary only (at most 4), http(s) urls, the body
normalised as bm-notatka push does; course, author and publication kept
- loadMaterialDetail shared by GET /materials/{id} and the edit answers
- add jz_bm_materials.revision (default 1), read-only for GORM
- bump it only on content changes (models.ReviseMaterial)
- PUT /materials/{id} locks the row and answers 409 to a stale revision
- management answers carry revision and updated_at, the student detail
carries revision
- jz_bm_material_file_texts caches the pdftotext output per file
- the River job jz.bm.extract_material_text extracts and resyncs
- the management multipart attach queues it inside its transaction
- file_texts in the document and a file snippet with its name
- Material is a beachcomber Searchable (index jz_bm_materials) in the
hand-written models/material_search.go; jz.bm requires summercms v0.6.4
- classes.MaterialIndexer asks Typesense for up to 250 candidates with
body highlights; the app-wide Gate is on only for jz.bm-marked contexts
- GET /_bm/api/v1/materials takes q: candidates are re-gated, counted and
paged in SQL in rank order, each row with at most one plain snippet
- search failure answers 503 Search is unavailable.; without q the list
is unchanged
A material may be private: only its author and the users it is shared
with see it. Every student read (list, detail, course detail and
counters, the checklist linked material, the token twins) goes through
one visibility fragment. Existing materials stay public with no author.
GET/PUT /_bm/api/v1/materials/{id}/access let the author, a material
manager, toggle the flag and pick the users; the first change of an
author-less material makes the caller its author.
- jz.bm.mcp-links on the token twins and the management API: under MCP,
url and *_url strings starting with a single / get the app base
- material answers under MCP leave the body out unless include_body,
marking it with body_omitted; write answers never echo it
- jz.bm.manage keys a dispatched call by user
- bm_materials and bm_material student tools
Plain HTTP answers are unchanged.
The material admin form gets the "Na zjazd" picker, every student
material JSON carries meeting_id, and GET /materials accepts meeting=
inside the published-only scope, so the Pulpit hero can list the
zjazd's materials next to its readings.
Dashboard, courses with counters, schedule, meetings, deadlines,
readings with per-user progress (mark and unmark) and paginated
published materials with tags and files, behind golem15.user's
jwt.auth. Responses are built field by field with Warsaw RFC 3339
timestamps; bad query parameters answer 422 in Laravel's shape.