Implement people management features by adding API endpoints for retrieving school rosters and individual person cards. Enhance the UI to support a people browser with filtering and pagination capabilities. Update localization strings for improved user experience and ensure robust handling of person data. Revise documentation to reflect new API functionalities and update tests to validate the new features.
ci / server (push) Failing after 11s
ci / client (push) Successful in 17s

This commit is contained in:
Leonid Pershin
2026-08-18 19:44:42 +03:00
parent 52c5082418
commit 533bd80f5e
23 changed files with 2134 additions and 47 deletions
+89 -2
View File
@@ -2,8 +2,9 @@
The client talks to the server two ways:
- **HTTP/JSON** for the main menu — listing, creating and deleting schools, listing mods and
loading a catalog for the create editor. Those are request/response by nature, so they are
- **HTTP/JSON** for the main menu and the in-school people browser — listing, creating and
deleting schools, listing mods, loading a catalog for the create editor, and reading a school's
roster (filtered list + one-person card). Those are request/response by nature, so they are
plain REST.
- **A binary WebSocket at `/ws/game`** for the school calendar (20 Hz) and the one-shot map
snapshot sent when a school is opened.
@@ -112,6 +113,92 @@ Failures are RFC 7807 problem details with an extra `code` field — that is wha
`204` when deleted, `404` when the id is unknown. Anyone watching that school over a WebSocket
gets a `SchoolGone` frame.
### `GET /api/schools/{id}/people`
The in-school people list. Reads the **published roster snapshot** and that school's last clock;
it does not post to the worker. Unknown `{id}` is `404` `unknown-school`. Bad query parameters
are `400` `invalid-query`. `lang` is `ru` or `en`, same as the catalog — anything other than
`en` is Russian.
| Query | Meaning |
| --- | --- |
| `role` | `student`, `staff` or `parent`. A staff parent matches `parent`. |
| `year` | Class parallel (`5` for fifth year). |
| `letter` | Class letter as stored on the roster (`А`, not a room number). |
| `position` | Staff `PositionDef` name (`Teacher`). |
| `sex` | `male` or `female`. |
| `ageMin` / `ageMax` | Inclusive age in full years at the school's current game time. |
| `sort` | `surname` (default), `age`, `year`, `position`. |
| `dir` | `asc` (default) or `desc`. |
| `page` | 1-based. Default `1`. `0` is invalid. A page past the end is empty and still reports `total`. |
| `pageSize` | Default `50`, max `100`. |
| `lang` | Label language for positions and filter options. |
```json
{
"total": 512,
"page": 1,
"pageSize": 50,
"people": [
{
"id": "f0.c0",
"fullName": "Иванова Мария Петровна",
"surname": "Иванова",
"given": "Мария",
"patronymic": "Петровна",
"female": true,
"age": 12,
"roles": ["student"],
"classYear": 5,
"classLetter": "А",
"position": null,
"positionLabel": null
}
],
"filters": {
"years": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
"letters": ["А"],
"positions": [{ "defName": "Teacher", "label": "Учитель" }]
}
}
```
The list row is identity only — skills, traits and needs stay on the card. Age is computed from
`birthDate` and the published game time, not from the World.
### `GET /api/schools/{id}/people/{personId}`
One person's card. Goes through the school's mailbox because need values live on entities and
tick with the clock. Unknown school is `404` `unknown-school`; unknown person is `404`
`unknown-person`. `?lang=ru|en` labels body, skills, traits, needs and the position.
Family links are other members of the same family: parents and siblings for a child, children
and partners for a parent. The client opens another card by id; this response does not nest
cards.
```json
{
"id": "f0.c0",
"fullName": "Иванова Мария Петровна",
"female": true,
"age": 12,
"birthDate": "2000-03-14T00:00:00Z",
"roles": ["student"],
"classYear": 5,
"classLetter": "А",
"body": [{ "id": "Height", "label": "Рост", "value": "164" }],
"skills": [{ "id": "Math", "label": "Математика", "value": "62" }],
"traits": [{ "defName": "Diligent", "label": "Усидчивый" }],
"needs": [{ "id": "Sleep", "label": "Сон", "value": 1 }],
"family": {
"parents": [{ "id": "f0.p1", "fullName": "Иванова Ольга Михайловна", "female": true }],
"children": [],
"siblings": [{ "id": "f0.c1", "fullName": "Иванов Кирилл Петрович", "female": false }],
"partners": []
}
}
```
## WebSocket message ids
Client-to-server ids live in `0x000x7F`, server-to-client ids in `0x800xFF`, so a misrouted