Commit Graph

81 Commits

Author SHA1 Message Date
ce6ac0a78e test: expect signed one-time material uploads
- TestUploadTicket: HMAC under the jz.bm.upload.v1: label, expiry,
  malformed and foreign tickets, single use through the cache
- TestSignedMaterialUploadRefusesBadTickets, TestCreateMaterialUploadNeedsCaller
- TestRoutes, TestBMMCP and TestManageBucketKey expect the upload-url
  route, the uploads route with its two buckets and the upload tool
- compile-only stubs
2026-10-28 15:07:30 +01:00
93066c63ea feat: attach material files over MCP as base64
- storeMaterialFile: the one attach.Store call of the management API,
  shared by the multipart route and the new inline route
- POST /api/v1/bm/materials/{id}/files/inline: JSON {name, mime?,
  content_base64}, at most 3000000 bytes decoded, body.limit 4300000
- bm_manage_attach_material_file and bm_manage_detach_material_file,
  both warning that material files are public to anyone with the link
2026-10-28 15:03:43 +01:00
a0aaeba0b7 feat: add the BM management MCP tools
- 28 typed tools over /api/v1/bm: list, show, create and update for
  courses and meetings, plus delete for class sessions, readings,
  deadlines and materials (save upserts on title and course)
- inputs mirror Fillable and Rules; enums, lengths and date formats in
  the field descriptions; no seed, me or dry_run tool
- materials accept meeting_id (absent keeps, null clears, must exist)
  and the managed material JSON shows it
- README: MCP tools section
2026-10-28 14:41:30 +01:00
b08d40cba2 test: expect the BM management MCP tools
- 28 typed management tools pinned in TestBMMCP
- body keys are Fillable columns, create inputs require the required
  columns, material save/update require body, source and url
- no seed or me tool, deletes only where the route deletes
- every description names its scope and permission
2026-10-28 14:39:07 +01:00
df42e30034 feat: MCP view of BM answers (absolute links, no material body by default)
- 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.
2026-10-28 14:37:10 +01:00
464c74b80f test: expect the MCP view of BM answers
- viaMCP, includeBody and the material views (no body unless asked)
- jz.bm.mcp-links: absolute url / *_url under MCP only
- jz.bm.manage keyed per user for dispatched calls
- bm_material and bm_materials in the pinned tool list
Compile-only stubs; the tests fail on their assertions.
2026-10-28 14:34:58 +01:00
e106b36a9a feat: add the BM student MCP tools
- token twins of the student API under /_bm/api/v1/token (user.api_token,
  per-user jz.bm.token bucket, a scope on each), from one mountStudent
- pact.HasMCP with the eleven student tools over the twins
- TestBMMCP and TestTokenMirrorRoutes
2026-10-28 14:32:03 +01:00
65a9a683af build: rebuild the student app (connect page) 2026-10-28 14:09:42 +01:00
72043ac85e feat: reserve the MCP paths
/mcp (the MCP endpoint) and /.well-known (the OAuth metadata documents)
join reservedPrefixes, so they answer a plain 404 here instead of the
student app; /connect stays a client route.
2026-10-28 14:09:22 +01:00
0ac83de8b2 test: expect the MCP paths reserved 2026-10-28 14:08:16 +01:00
e22b36e03f chore: require golem15.chat v0.1.3 2026-10-27 11:40:14 +01:00
Jakub Zych
67bc3eb944 build: rebuild the student app (pulpit reading links) 2026-10-27 01:46:33 +01:00
Jakub Zych
ff2f97eb2b feat: materials by zjazd in the student API and admin
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.
2026-10-27 01:44:38 +01:00
Jakub Zych
3241cc9894 feat: zjazd (meeting) on readings and materials
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.
2026-10-27 01:41:48 +01:00
8843e73f98 chore: require golem15.chat v0.1.2 2026-10-26 20:31:54 +01:00
3a7bc46f08 build: rebuild the student app (full-width pages) 2026-10-26 20:08:59 +01:00
35ef575b08 build: rebuild the student app (chat review fixes)
Per-message attachment total, deleted-while-offline messages, nested
code spans in links and /\host site paths.
2026-10-26 19:40:22 +01:00
be3769d75f chore: require golem15.chat v0.1.1 2026-10-26 19:39:54 +01:00
0ff943b6af fix: keep DM participant rows when a user leaves the chat group
The participant sync and the non-member prune now remove only ogólny's
rows. A user taken out of studenci, even briefly, keeps their DMs (the
live membership check denies access while they are out) and gets them
back when re-added, instead of losing them for good.
2026-10-26 19:34:53 +01:00
8b81295458 fix: revoke chat access as soon as a user leaves the chat group
The BM chat context implements golem15.chat's LiveMembership, so every
participant check also requires chat-group membership: a removed user
loses # ogólny, their DMs, uploads, subscriptions and chat:message
delivery at once instead of after the next participant sync.
2026-10-26 19:33:52 +01:00
a840d0ad02 fix: enforce the chat's per-message upload total
jz.bm.chat.max_upload_bytes is now handed to golem15.chat as the total
size of one message's attachments, so the server enforces the limit the
bootstrap advertises.
2026-10-26 19:32:07 +01:00
cf32fa18b9 build: rebuild the student app (chat) 2026-10-26 17:17:54 +01:00
2681d304e2 feat: delete-only chat moderation and embed search and resolve
- 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
2026-10-26 16:07:26 +01:00
ac4c2e6f73 test: cover chat moderation and the embed endpoints
- CanModerateMessage: group conversations only, chat members only
- parseEmbedRefs, embedKey, escapeLike
- /chat/embeds and /chat/embeds/resolve: 401, 422 before the
  database, 503 without one
2026-10-26 16:05:33 +01:00
eea171cee6 feat: open realtime tokens to chat members
The connection-token route is guarded by jz.bm.realtime-access instead
of presentations.realtime-owner: chat-group members get a token, and
when golem15.presentations is active so does everyone its owner guard
lets through (looked up by name among the activated plugins, no import).
Everyone else gets 401, 403 or 503 in the guard's error shape. The
realtime routes mount when golem15.chat or golem15.presentations is
active. /_chat, /_chatchannels and /_chatbots are reserved prefixes of
the student app.
2026-10-26 15:49:29 +01:00
cb7ba7ec8a test: cover the realtime access guard and chat prefixes
jz.bm.realtime-access admits chat-group members and, through
golem15.presentations' owner guard, presentation owners; everyone else
gets 401, 403 or 503 with Cache-Control no-cache, private. /_chat,
/_chatchannels and /_chatbots are reserved for the APIs.
2026-10-26 15:48:07 +01:00
0642fb1d08 feat: chat bootstrap, members and group sync
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.
2026-10-26 15:46:13 +01:00
07d67be1e7 test: cover the chat access policy and bootstrap guards
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.
2026-10-26 15:42:53 +01:00
64601f6817 feat: host the BM chat context
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.
2026-10-26 15:40:44 +01:00
67806f49a7 build: frontend with the owned-only readings filter 2026-10-26 11:19:22 +01:00
b4abf2630a build: frontend with the owned checkbox on readings 2026-10-26 11:12:08 +01:00
cf84f1d0e8 feat: per-student owned flag on readings
- 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
2026-10-26 11:11:22 +01:00
Jakub Zych
85cae45cb9 build: frontend with Microsoft 365 sign-in
The student app's login page offers Microsoft 365 when the provider is
configured, /auth/oauth/callback redeems the one-time code, a consent
screen finishes a first sign-in, the profile attaches Microsoft through
a link ticket, and the service worker leaves /oauth alone. The build
also carries the summer presentation theme already committed in the
frontend sources.
2026-10-21 00:23:38 +02:00
Jakub Zych
438c0b9761 fix: reserve /oauth for social sign-in
The SPA fallback no longer answers /oauth and the paths below it with
index.html, so golem15.sociallogin's start and provider callback routes
reach the backend; /oauthy is still a client route.
2026-10-21 00:15:31 +02:00
Jakub Zych
2536bcb959 Management API: record routes for courses, meetings, class sessions, readings and deadlines (Fillable + Rules + TemporalErrors, PATCH, filters) 2026-10-17 18:05:39 +02:00
Jakub Zych
996b4d1542 Management API: POST/DELETE /materials/{id}/files (attach.Store, 50 MB, 20 files), DELETE /materials/{id} removes files; download keeps the file name 2026-10-17 17:53:22 +02:00
Jakub Zych
584d15257c Sylabusy: PDF-y w public/sylabusy, typ application/pdf w serwerze statycznym 2026-10-17 12:06:24 +02:00
Jakub Zych
76f559b6d9 build: rebuild the student app (multi-line course descriptions) 2026-10-17 12:01:15 +02:00
Jakub Zych
ebd9117f8c feat: lecturer on class sessions and in the session course ref
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.
2026-10-17 11:20:11 +02:00
Jakub Zych
d2096e2128 feat(manage): GET and PUT /api/v1/bm/materials/{id}
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.
2026-10-16 20:40:47 +02:00
Jakub Zych
2a8cdac282 public: slide counter in the public deck toolbar 2026-10-16 00:44:06 +02:00
Jakub Zych
b32668029c build: rebuild the student app (deadline card padding) 2026-10-15 23:23:16 +02:00
Jakub Zych
67e04b3101 build: rebuild the student app (deck themes, scheme switch) 2026-10-13 10:18:10 +02:00
Jakub Zych
b86cbbcd2d build: rebuild the student app (pwa) 2026-10-09 02:47:29 +02:00
Jakub Zych
a406a514d3 build: rebuild the student app (title-slide notes, pilot key order) 2026-10-09 02:31:53 +02:00
Jakub Zych
9aa620f975 build: rebuild the student app (phone remote review fixes) 2026-10-08 18:53:25 +02:00
Jakub Zych
5563e080cf fix: stop throttling the realtime subscribe proxy
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.
2026-10-08 18:47:20 +02:00
Jakub Zych
2365bf6df9 build: rebuild the student app (phone remote layout) 2026-10-08 18:26:12 +02:00
Jakub Zych
e0a60e0f35 build: rebuild the student app (phone remote) 2026-10-08 18:14:15 +02:00
Jakub Zych
a740373b0e feat: wire the lighthouse realtime service
- 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
2026-10-08 17:53:54 +02:00