Skip to content
119 changes: 78 additions & 41 deletions backend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,34 @@
# Errors are thrown as GraphQL errors with a machine-readable
# `extensions.code`. The client switches on the code, never on the message.
#
# UNAUTHENTICATED The operation needs a logged-in coach.
# UNAUTHENTICATED The operation needs a logged-in coach, or the session token
# or the Google token is missing, invalid or expired.
# NOT_FOUND The id does not exist, or belongs to another coach.
# Both cases give the same answer, so that nobody can find
# out which ids exist (constraint 9 in docs/DATA_MODEL.md).
# Also removeGymnastElement for an item that is not in the
# gymnast's list.
# ALREADY_EXISTS addGymnastElement for an item that is already in the
# gymnast's list.
# NAME_TAKEN The coach already has a routine with this name for this
# gymnast and apparatus (constraint 6).
# INVALID_ITEMS The items do not fit the routine: a vault outside vault,
# an element on vault, more than one vault, or an item from
# another apparatus (constraints 7 and 8). Also a removed
# item added to a gymnast's list.
# BAD_USER_INPUT Empty name, malformed id, `first` out of range, a
# DIFFICULTY sort without an apparatus filter, or a
# difficulty filter together with vault, or a difficulty
# value that is not 0.1-1.0 in steps of 0.1.
# another apparatus (constraints 7 and 8). Also an item that
# has been removed from the Code of Points (`removed`) and
# is not already in the routine or the gymnast's list.
# BAD_USER_INPUT Empty name, name longer than 70 characters, malformed id,
# `first` out of range, a DIFFICULTY sort without an
# apparatus filter, a difficulty filter together with vault,
# or a difficulty value that is not 0.1-1.0 in steps of 0.1.
# LIMIT_REACHED The coach has reached a limit: 200 gymnasts, 50 routines
# per gymnast, or 50 items in a routine.
# per gymnast, 50 items in a routine, or 100 items in a
# gymnast's list.
#
# Apollo Server also adds codes of its own. The ones that matter here:
# GRAPHQL_VALIDATION_FAILED The query is invalid, or deeper than the limit.
# INTERNAL_SERVER_ERROR Unexpected error. The client shows a general
# message and no details.
#
# Why this way:
# - `me` is the only operation where "not logged in" is a normal answer. The
Expand All @@ -39,7 +51,9 @@ type Apparatus {
groups: [ElementGroup!]!
}

"""A group of elements or vaults on one apparatus, e.g. "1.000 Mounts"."""
"""
A group of elements or vaults on one apparatus, e.g. "1.000 Mounts".
"""
type ElementGroup {
id: ID!
number: Int!
Expand All @@ -59,8 +73,9 @@ interface CatalogItem {
elementGroup: ElementGroup!
"""
True when the item is no longer in the Code of Points. It can still be
fetched by id, and it stays in routines and gymnast lists, but it is not
found by the search and does not count in a D-value.
fetched by id, and it stays in routines and gymnast lists, so the UI should
warn about it there. It is not found by the search, it does not count in the
D-score, and it cannot be added to a routine or a list.
"""
removed: Boolean!
}
Expand Down Expand Up @@ -107,9 +122,11 @@ type Vault implements CatalogItem {
found by the search and does not count in a D-value.
"""
removed: Boolean!
"""Fixed difficulty score, e.g. 2.6."""
dValue: Float!
"""Other ways of performing the same vault. Removed variants are left out."""
"""
Fixed difficulty score, e.g. 2.6.
"""dValue: Float!"""
Other ways of performing the same vault. Removed variants are left out.
"""
variants: [Vault!]!
}

Expand Down Expand Up @@ -145,9 +162,9 @@ input CatalogFilter {
otherwise BAD_USER_INPUT. Vaults have D-values and not difficulty values,
so they are left out when this is set, and together with apparatusId "VT"
it throws BAD_USER_INPUT. Empty or missing means no difficulty filter.
"""difficultyValues: [Float!]"""
Only items named after a gymnast.
"""
difficultyValues: [Float!]
"""Only items named after a gymnast."""
namedOnly: Boolean
}

Expand Down Expand Up @@ -176,8 +193,11 @@ type CatalogConnection {
type Query {
apparatus: [Apparatus!]!
elementGroup(id: ID!): ElementGroup
"""Also finds removed elements (see `removed`), so routines can show them."""
element(id: ID!): Element
"""Also finds removed vaults (see `removed`), so routines can show them."""
vault(id: ID!): Vault
"""Also finds removed items (see `removed`), so routines can show them."""
catalogItem(id: ID!): CatalogItem

"""The logged-in coach, or null when nobody is logged in."""
Expand All @@ -188,8 +208,9 @@ type Query {
routine(id: ID!): Routine
"""
The D-score of a list of items, for the calculator. Open to everyone and
nothing is saved. Throws INVALID_ITEMS when the items do not fit together.
Removed items do not count. At most 50 ids, otherwise BAD_USER_INPUT.
nothing is saved. Throws INVALID_ITEMS when the items do not fit together, or
when an item has been removed (see `removed`). At most 50 ids, otherwise
BAD_USER_INPUT.
"""
calculateDScore(itemIds: [ID!]!): Float!

Expand Down Expand Up @@ -232,9 +253,11 @@ type Gymnast {
routines(apparatusId: ID): [Routine!]!
"""
The elements and vaults the gymnast can do or is practicing, in the order
they were added. Removed items stay in the list, with removed: true.
they were added. Optionally only the elements on one apparatus. Removed
items stay in the list, with removed: true. Limited to 100 per gymnast by
the backend.
"""
elements: [CatalogItem!]!
elements(apparatusId: ID): [CatalogItem!]!
}

type Routine {
Expand All @@ -249,10 +272,13 @@ type Routine {
"""
items: [CatalogItem!]!
"""
Sum of the eight highest values, each code counted once. Removed items do
not count. Calculated.
Sum of the eight highest values, each code counted once. Calculated. Removed
items (see `removed`) do not count.
"""
dScore: Float!
createdAt: DateTime!
"""When the routine, its name or its items were last changed."""
updatedAt: DateTime!
}

type AuthPayload {
Expand All @@ -271,32 +297,43 @@ type Mutation {
"""Verifies the Google token and creates the coach on the first login."""
login(googleToken: String!): AuthPayload!

"""Throws LIMIT_REACHED."""
createGymnast(name: String!): Gymnast!
renameGymnast(id: ID!, name: String!): Gymnast!
"""Also deletes the gymnast's routines. Returns the id of the deleted gymnast."""
deleteGymnast(id: ID!): ID!

"""Throws NAME_TAKEN or LIMIT_REACHED."""
createRoutine(input: CreateRoutineInput!): Routine!
"""Throws NAME_TAKEN."""
renameRoutine(id: ID!, name: String!): Routine!
deleteRoutine(id: ID!): ID!
"""
Replaces the whole list. The client sends the ids in the wanted order.
More than 50 ids throws LIMIT_REACHED. Throws INVALID_ITEMS.
The name is trimmed and must be 1-70 characters (BAD_USER_INPUT otherwise).
Throws LIMIT_REACHED.
"""createGymnast(name: String!): Gymnast!"""
The name is trimmed and must be 1-70 characters (BAD_USER_INPUT otherwise).
"""renameGymnast(id: ID!, name: String!): Gymnast!"""
Also deletes the gymnast's routines. Returns the id of the deleted gymnast.
"""
setRoutineItems(routineId: ID!, itemIds: [ID!]!): Routine!
deleteGymnast(id: ID!): ID!

"""
Adds an element or vault to the gymnast's list. Adding an item that is
already in the list does nothing. Throws NOT_FOUND for an unknown gymnast
or item, and INVALID_ITEMS for a removed item.
Adds an element or vault to the gymnast's list. Throws NOT_FOUND,
ALREADY_EXISTS (the item is already in the list), INVALID_ITEMS (a removed
item) or LIMIT_REACHED.
"""
addGymnastElement(gymnastId: ID!, itemId: ID!): Gymnast!
"""
Removes an element or vault from the gymnast's list. Removing an item that
is not in the list does nothing. Throws NOT_FOUND for an unknown gymnast.
Removes an item from the gymnast's list, also when it has been removed from
the Code of Points. Throws NOT_FOUND when the gymnast does not exist or the
item is not in the list.
"""
removeGymnastElement(gymnastId: ID!, itemId: ID!): Gymnast!

"""
The name is trimmed and must be 1-70 characters (BAD_USER_INPUT otherwise).
Throws NAME_TAKEN or LIMIT_REACHED.
"""createRoutine(input: CreateRoutineInput!): Routine!"""
The name is trimmed and must be 1-70 characters (BAD_USER_INPUT otherwise).
Throws NAME_TAKEN.
"""
renameRoutine(id: ID!, name: String!): Routine!
deleteRoutine(id: ID!): ID!
"""
Replaces the whole list. The client sends the ids in the wanted order.
More than 50 ids throws LIMIT_REACHED. Throws INVALID_ITEMS. A removed item
can stay in the list, but it cannot be added to it. The last save wins: the
mutation does not check if the routine was changed since the client loaded it.
"""
setRoutineItems(routineId: ID!, itemIds: [ID!]!): Routine!
}
19 changes: 19 additions & 0 deletions docs/AI_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,25 @@
does not, and checked the browser support and the cascade layers in the
Tailwind documentation. Claude checked lint, build and tests.

## Missing fields in the GraphQL schema (`backend/src/schema.graphql`, `docs/API.md`)

- **Tool:** Claude Code, and Claude for the research.
- **What the team did:** Wrote issue #37, which lists the fields that the
database has and the schema does not expose: the gymnast's list of elements
and vaults with mutations, `createdAt` and `updatedAt` on a routine, and a
flag for removed items. The team made the decisions: adding an item that is
already in the list, or removing one that is not, gives an error and is
never silent. A gymnast's list is limited to 100 items. A removed item does
not count in the D-score. The team also defined what "removed" means (an
item that is no longer in the Code of Points edition the app uses).
- **What Claude did:** Researched how others handle this (idempotency in RFC
9110, errors as data in Apollo's guide, the gRPC error names, and soft
delete and warnings in the UI) and proposed the error names `ALREADY_EXISTS`
and `NOT_FOUND`. Added the fields, mutations and error codes to the schema,
and described them in `docs/API.md`, including the limits, the warning in
the UI and the tests. Checked that the schema parses with the `graphql`
library.

## Catalog search (`backend/src/catalog/search.ts`, `eslint.config.js`, `docs/API.md`)

- **Tool:** Claude Code
Expand Down
Loading