Management API: record routes for courses, meetings, class sessions, readings and deadlines (Fillable + Rules + TemporalErrors, PATCH, filters)

This commit is contained in:
Jakub Zych
2026-10-17 18:05:39 +02:00
parent 996b4d1542
commit 2536bcb959
6 changed files with 618 additions and 22 deletions

View File

@@ -192,7 +192,6 @@ The calendar is RFC 5545 (CRLF lines folded at 75 octets, escaped TEXT) with tim
| Method | Route | Scope, permission | Answer |
|--------|-------|-------------------|--------|
| GET | `/api/v1/bm/me` | read | The token owner's `user_id` and the token (`id`, `name`, `scopes`, `expires_at`). |
| GET | `/api/v1/bm/courses` | read, courses | Every course (`courseJSON`), for the `course_id` of a material. |
| GET | `/api/v1/bm/materials` | read, materials | Every material, drafts included, newest first. |
| GET | `/api/v1/bm/materials/{id}` | read, materials | One material, draft or not, with its public `files`. 404 when unknown. |
| POST | `/api/v1/bm/materials` | write, materials | Upsert on `title` + `course_id`: 201 `{"data", "created": true}` for a new material, 200 `{"data", "created": false}` for an updated one. |
@@ -200,8 +199,25 @@ The calendar is RFC 5545 (CRLF lines folded at 75 octets, escaped TEXT) with tim
| DELETE | `/api/v1/bm/materials/{id}` | write, materials | 204, with the material's files (rows, then blobs); 404 when unknown. |
| POST | `/api/v1/bm/materials/{id}/files` | write, materials | Multipart body, parts named `file`: each is stored as a public file of the material, after the ones it has. 201 with the material as GET shows it; 404 when unknown; 422 on `file` when there is no file part, a type is not allowed, a file is over 50 MB or the material would hold more than 20 files (nothing is stored then); 413 over the body cap (`body.limit`, 51 MB, so one large file per request). |
| DELETE | `/api/v1/bm/materials/{id}/files/{file}` | write, materials | 204: the file row, then its blob and thumbnails; 404 when the material or a file of it is unknown. |
| GET | `/api/v1/bm/{records}` | read, see below | Every row of the table, filtered by `?column=value` on the columns listed below (`null` matches NULL). |
| GET | `/api/v1/bm/{records}/{id}` | read | One row; 404 when unknown. |
| POST | `/api/v1/bm/{records}` | write | 201 with the new row. |
| PATCH | `/api/v1/bm/{records}/{id}` | write | Only the sent columns change; 200 with the row. |
| DELETE | `/api/v1/bm/{records}/{id}` | write | 204 (class sessions, readings, deadlines only; courses and meetings are deleted in the admin, because rows hang on them). |
| POST | `/api/v1/bm/seed?dry_run=&prune=` | write, courses + schedule + materials | `bm:seed` over HTTP: the body is the seed document, the answer the summary table. |
The record tables (`controllers/api/manage_records.go`, mounted by `mountRecords` in `routes.go`):
| `{records}` | Model | Permission | List filters | Order |
|-------------|-------|------------|--------------|-------|
| `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 |
| `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.
The material body (POST and PUT), unknown keys are a 422:
```json