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.
This commit is contained in:
Jakub Zych
2026-10-27 01:44:38 +01:00
parent 3241cc9894
commit ff2f97eb2b
9 changed files with 58 additions and 16 deletions

View File

@@ -45,7 +45,7 @@ Every route except the calendar feed itself needs `Authorization: Bearer <golem1
| DELETE | `/_bm/api/v1/readings/{id}/progress` | | Clears the mark (idempotent) |
| PUT | `/_bm/api/v1/readings/{id}/owned` | | Marks the reading owned (idempotent) |
| DELETE | `/_bm/api/v1/readings/{id}/owned` | | Clears the owned mark (idempotent) |
| GET | `/_bm/api/v1/materials` | `course`, `tag` (slug), `type`, `page`, `per_page` (1–100) | One page of published materials |
| GET | `/_bm/api/v1/materials` | `course`, `meeting`, `tag` (slug), `type`, `page`, `per_page` (1–100) | One page of published materials |
| GET | `/_bm/api/v1/materials/{id}` | | One published material with body, tags and files |
| GET | `/_bm/api/v1/calendar-feed` | | The caller's calendar feed, or `null` |
| PUT | `/_bm/api/v1/calendar-feed` | | Creates the feed when missing, applies the settings in the body |
@@ -83,17 +83,17 @@ A deadline (`kind` is one of `zaliczenie`, `egzamin`, `praca`, `organizacyjne`,
{"id": 1, "title": "…", "due_at": "2026-10-05T23:59:00+02:00", "kind": "organizacyjne", "description": null, "course_id": null, "course": null}
```
A reading (`read_at` is the caller's, or null):
A reading (`read_at` is the caller's, or null; `meeting_id` is the zjazd the reading is meant for, null when none):
```json
{"id": 1, "course_id": 1, "course": {…}, "title": "…", "author": null, "source": null, "url": null,
{"id": 1, "course_id": 1, "course": {…}, "meeting_id": 4, "title": "…", "author": null, "source": null, "url": null,
"is_required": true, "sort_order": 1, "read_at": "2026-10-02T21:15:04+02:00"}
```
A material in a list (`type` is one of `pdf`, `notatka`, `link`, `inne`; `files_count` counts public files):
A material in a list (`type` is one of `pdf`, `notatka`, `link`, `inne`; `files_count` counts public files; `meeting_id` is the zjazd the material is meant for, null when none):
```json
{"id": 1, "course_id": 1, "course": {…}, "title": "…", "type": "notatka", "source": null, "url": null,
{"id": 1, "course_id": 1, "course": {…}, "meeting_id": null, "title": "…", "type": "notatka", "source": null, "url": null,
"published_at": "2026-10-01T12:00:00+02:00", "tags": [{"id": 1, "name": "Geopolityka", "slug": "geopolityka"}], "files_count": 1}
```
@@ -152,7 +152,7 @@ The owned routes record that the caller owns a copy of the reading, apart from w
{"data": [<material>, …], "meta": {"current_page": 1, "last_page": 1, "per_page": 20, "total": 2}}
```
Newest publication first. The detail adds the Markdown body and the public files; `url` starts with `storage.uploads.public_path_prefix`:
Newest publication first. `meeting` keeps the materials assigned to that zjazd (meeting id); like every filter it applies to published materials only. The detail adds the Markdown body and the public files; `url` starts with `storage.uploads.public_path_prefix`:
```json
{"data": {…material…, "body": "# …",
@@ -217,7 +217,7 @@ The record tables (`controllers/api/manage_records.go`, mounted by `mountRecords
| `courses` | `Course` | courses | `semester`, `form`, `code` | semester, sort_order |
| `meetings` | `Meeting` | schedule | | number |
| `class-sessions` | `ClassSession` | schedule | `course_id`, `meeting_id` | starts_at |
| `readings` | `Reading` | courses | `course_id`, `is_required` | course_id, sort_order |
| `readings` | `Reading` | courses | `course_id`, `meeting_id`, `is_required` | course_id, sort_order |
| `deadlines` | `Deadline` | schedule | `course_id`, `kind` | due_at |
A row is answered as an object of its columns (`id`, `created_at` and `updated_at` included); times are RFC 3339 in Europe/Warsaw, dates `YYYY-MM-DD`. A POST or PATCH body is an object of columns: only the model's `Fillable()` ones (another key is a 422 that lists them), with times as RFC 3339 or `2006-01-02 15:04` in Europe/Warsaw, dates as `YYYY-MM-DD`, `""` or `null` clearing an optional column, and a number accepted for a text column. The model's `Rules()` (the admin's) run on the row as it would be saved, so a PATCH of one column still meets the required ones, and `unique` skips the row itself; `TemporalErrors()` checks the order of times; `course_id` and `meeting_id` must exist. A seed run (`bm:seed`, `POST /seed`) overwrites the columns it carries on the rows it matches, so changes made here are lost when the same rows are seeded again.