The management API could only upsert a material on its title and course,
so a renamed note became a second material. PUT /materials/{id} applies the
same body to one material (422 when the new title and course belong to
another one), GET /materials/{id} shows a draft or published material with
its public files, and the upsert, update and validation share one path.
The README documents the management API.
Centrifugo calls the proxy from 127.0.0.1 for every client, so the
per-IP fallback put every subscribe into one bucket that any token
holder could exhaust. Throttle only the token route, per user.
- Boot builds the realtime service and installs the user lookup
- the Centrifugo driver ships with the app; config keeps the null driver
- token and subscribe-proxy routes mount only with golem15.presentations,
the token route behind presentations.realtime-owner
- jz.bm.realtime per-user throttle bucket and websockets:health
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.