The Pulpit hero lists what to read for the current or next zjazd, so
readings and materials get a nullable meeting_id (FK to jz_bm_meetings,
ON DELETE SET NULL, indexed). The reading admin form gets a "Na zjazd"
picker, the student reading JSON carries meeting_id, and the management
/readings records filter and validate it.
- ChatContext implements golem15.chat's MessageModerator: moderator
group members who are chat members delete in group conversations
only, never in DMs
- GET /chat/embeds?type=deadline|class|reading&q= (members only,
at most 20 rows, escaped ILIKE, q at most 100 characters)
- POST /chat/embeds/resolve {refs} (members only, at most 50 distinct
refs, 8 KiB body): current data or null per ref
- CanModerateMessage: group conversations only, chat members only
- parseEmbedRefs, embedKey, escapeLike
- /chat/embeds and /chat/embeds/resolve: 401, 422 before the
database, 503 without one
GET /_bm/api/v1/chat gives a chat-group member ogólny (the bm/1 group
conversation, adopted once as the system default channel under an
advisory lock), its unread and mention counts, the member's DMs newest
message first, the moderator flag and the composer limits. Every member
bootstrap syncs participants with the group: missing members join
ogólny, users no longer in the group lose their rows in every bm/1
conversation. A non-member gets only member:false and is pruned.
GET /_bm/api/v1/chat/members lists members (id, name, surname, handle,
is_bot from golem15.chatbots when active) and assigns missing handles;
non-members get 404.
The chatchannels access policy (ChatAccess) is installed from a
database hook: bm/1 for members only, nobody manages channels.
bm:chat-sync creates the groups when missing, syncs and assigns
handles. Migration 20261026130000_seed_jz_bm_chat_groups creates
studenci and moderatorzy without members; its rollback keeps them.
ChatAccess admits only chat-group members to context bm/1 and manages
nothing; the chat context oversees nothing and its loader knows only
id 1; /chat and /chat/members need a caller and a database. The
migration count rises to 15 for the chat group seed.
When golem15.chat is active, Boot registers the context alias bm (id 1)
on its registry. Membership is the user group jz.bm.chat.group (default
studenci); CanOverseeChats is always false, so nobody reads other
people's DMs or edits their messages; the broadcast channel is
chat:context:1, never empty.
GET /_bm/api/v1/chat answers the caller's membership behind jwt.auth and
the per-user bucket jz.bm.chat (240 a minute). The chat config block
(group, moderator_group, max_upload_bytes, max_embeds) is added. jz.bm
still Requires only golem15.user; all chat wiring is gated by HasPlugin.
- new table jz_bm_reading_ownership and its migration
20261026120000_create_jz_bm_reading_ownership
- PUT and DELETE /_bm/api/v1/readings/{id}/owned, idempotent
- owned_at on every reading in /readings and /courses/{id}
- read semantics, progress routes and counts unchanged
The schedule showed a lecturer only where the session notes named one
(Globalizacja), so the student plan had nobody under the other courses.
The session JSON now carries the course lecturer in its course ref and an
optional per-session lecturer (new nullable column, admin field, seed key)
for courses shared between two people. The student app shows the
session's own lecturer or, failing that, the course's.
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.
Datepickers on native time.Time / lagoon.Date fields, file uploads on
materials, readings and class sessions managed from the course form
(course_id made nullable).
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.
Seven admin controllers in a "Studia BM" navigation group, guarded by
jz.bm.access_courses, jz.bm.access_schedule and jz.bm.access_materials,
with Polish and English labels. Date and time fields are checked by the
models before a save and answered as 422 on the field.