After the login (#70) the server knows who is asking, but the operations on gymnasts and routines still answer `NOT_IMPLEMENTED`. This issue builds them, so that a coach can keep gymnasts, their element lists and their routines. The schema is `backend/src/schema.graphql` (PR #49 added the list, the timestamps and the limits), and the rules are in `docs/API.md` and `docs/DATA_MODEL.md`. ## Scope - **Ownership.** Every operation only reaches the logged-in coach's own data. Another coach's id gives `NOT_FOUND`, the same answer as an id that does not exist, so ids cannot be probed. No login gives `UNAUTHENTICATED`. - **Queries.** `gymnast(id)`, `routine(id)`, `Coach.gymnasts`, `Gymnast.routines(apparatusId)`, `Gymnast.elements(apparatusId)`, `Routine.items` (in order), `Routine.dScore`. The lists are plain lists, capped by the limits (`docs/USER_LIST_PAGINATION.md`). DataLoaders for the lists, so a coach with many gymnasts does not give one query per gymnast. - **Gymnast mutations.** `createGymnast`, `renameGymnast`, `deleteGymnast` (also deletes the routines and the list). Names are trimmed and 1–70 characters (`BAD_USER_INPUT`). At most 200 gymnasts per coach (`LIMIT_REACHED`). - **The gymnast's list.** `addGymnastElement` and `removeGymnastElement` return the `Gymnast`. Adding an item that is already in the list, or removing one that is not, changes nothing and gives no error. A removed item cannot be added (`INVALID_ITEMS`) but can be removed. At most 100 items (`LIMIT_REACHED`). - **Routine mutations.** `createRoutine`, `renameRoutine`, `deleteRoutine`, `setRoutineItems` (replaces the whole list in a transaction and updates `updatedAt`). Names are trimmed and 1–70 characters, and unique for the gymnast and apparatus (`NAME_TAKEN`). At most 50 routines per gymnast and 50 items per routine (`LIMIT_REACHED`). Constraints 7 and 8: one vault on vault and no vault elsewhere, items on the routine's apparatus (`INVALID_ITEMS`). A removed item may stay in a routine but cannot be added. The same element twice is accepted. - **`Routine.dScore`** uses the function from the calculator issue: `null` while the routine has a removed item. - **New error classes** `NotFoundError`, `NameTakenError`, `LimitReachedError` in `backend/src/errors.ts`, with the codes `NOT_FOUND`, `NAME_TAKEN`, `LIMIT_REACHED`. - **Tests** against the local database, with two test coaches: every operation, ownership (the other coach gets `NOT_FOUND` and nothing changes), each limit, each error code, the no-op rules, `setRoutineItems` order and that a failed call changes nothing, `dScore` with a removed item, and the query count for a coach with many gymnasts. They delete their data afterwards. - **Docs.** `docs/API.md` (remove the "not built yet" table) and the README. ## Done when - [ ] A logged-in coach can create, rename and delete gymnasts and routines, edit a gymnast's list and set a routine's items - [ ] A coach can never read or change another coach's data - [ ] All limits and error codes in `docs/API.md` are returned in the right cases - [ ] `Routine.dScore` is right, and `null` with a removed item - [ ] The number of queries does not grow with the number of gymnasts or routines - [ ] The tests pass, and lint, typecheck and build pass - [ ] The "not built yet" text is removed from `docs/API.md` and the README Depends on the login (#70) and on PR #49 (the schema fields). Uses the D-score function from the calculator issue.