turniere-backend/doc/team_action_lists_api.md

242 lines
5.4 KiB
Markdown

# Team Action Lists API
## Purpose
Team action lists let the backend expose checklist-style workflows for teams inside a tournament.
Current example:
- `group_stage_survivor_playoff_tokens`
That list contains all teams that advanced from the group stage and tracks whether each team already collected a playoff token.
## Read Flow
Frontend reads team action lists from the normal tournament payload:
- `GET /tournaments/:id`
Example response shape:
```json
{
"id": 123,
"name": "My Tournament",
"team_action_lists": [
{
"id": 9,
"name": "Playoff token collection",
"key": "group_stage_survivor_playoff_tokens",
"action_name": "Collect playoff token",
"source": "group_stage_survivors",
"team_action_items": [
{
"id": 41,
"completed": false,
"completed_at": null,
"team": {
"id": 55,
"name": "Team A"
}
}
]
}
]
}
```
## Write Flow
Only tournament owner may change item status.
Everyone may read status.
Follower tournaments in `read_only_mode` reject updates with `423`.
There are two supported write patterns.
### Option 1: update by item id
Use this when frontend already has the item id from tournament payload.
- `PATCH /team_action_items/:id`
Request body:
```json
{
"completed": true
}
```
### Option 2: update by list key + team id
Use this when frontend wants a business-key style request like:
- "tick off team X from list Y in tournament Z"
Endpoint:
- `PATCH /tournaments/:tournament_id/team_action_lists/:key/teams/:team_id`
Example:
- `PATCH /tournaments/123/team_action_lists/group_stage_survivor_playoff_tokens/teams/55`
Request body:
```json
{
"completed": true
}
```
Response body for both write endpoints is the updated `TeamActionItem`.
## Idempotency Rules
These endpoints are designed to be idempotent if frontend sends explicit target state.
Good:
- send `{"completed": true}` to mark complete
- send `{"completed": false}` to mark incomplete
Avoid:
- toggle-style client behavior such as "invert current state"
Why this is idempotent:
- sending `completed=true` multiple times leaves item in same final state
- sending `completed=false` multiple times leaves item in same final state
- backend does not create duplicate items
- backend stores one unique item per `(team_action_list_id, team_id)`
`completed_at` behavior:
- first successful transition to `completed=true` sets timestamp if none exists
- sending `completed=true` again keeps same completed state
- sending `completed=false` clears `completed_at`
## Frontend Implementation Guidance
Recommended frontend flow:
1. Load tournament via `GET /tournaments/:id`
2. Find desired list by stable `key`
3. Render `team_action_items`
4. On user action, send explicit target state
5. Update local UI from response or refetch tournament
Recommended frontend identifiers:
- use list `key` as stable frontend reference
- use `team.id` as stable team reference
- use `team_action_item.id` when available for direct item updates
Recommended optimistic UI:
- set checkbox immediately to requested target state
- revert if API returns error
- do not compute next state from stale cached data if multiple clients may edit
## Error Handling
Expected statuses:
- `200` update accepted
- `403` authenticated but not tournament owner
- `404` tournament, list, team, or item not found
- `423` follower tournament is read-only
- `401` unauthenticated
## Sync / Follower Behavior
Team action lists are included in tournament sync snapshots.
That means:
- source tournament owner updates item state
- backend enqueues tournament sync
- follower tournament receives updated list state
- follower users can view but not mutate item state while read-only
## Websocket / Live Update Behavior
Team action lists also support live updates over ActionCable.
Endpoint:
- `GET /cable` for websocket upgrade
Channel:
- `TournamentTeamActionListsChannel`
Subscription identifier example:
```json
{
"channel": "TournamentTeamActionListsChannel",
"tournament_id": 123
}
```
Behavior:
- subscription immediately receives current full team-action-list snapshot for that tournament
- later list/item changes broadcast updated full snapshot again
- same broadcast path is used for:
- direct source updates
- follower sync imports
Payload shape:
```json
{
"type": "team_action_lists.updated",
"tournament_id": 123,
"team_action_lists": [
{
"id": 9,
"name": "Playoff token collection",
"key": "group_stage_survivor_playoff_tokens",
"action_name": "Collect playoff token",
"source": "group_stage_survivors",
"team_action_items": [
{
"id": 41,
"completed": true,
"completed_at": "2026-04-23T09:15:00Z",
"team": {
"id": 55,
"name": "Team A"
}
}
]
}
]
}
```
Frontend guidance:
- use websocket payload as replacement snapshot for team-action-list state in that tournament
- do not merge by toggling local state blindly
- keep writes item-scoped through HTTP
- use websocket only for read/live propagation
## Future Extension Guidance
When new lists are added, frontend should not hardcode backend internals beyond:
- stable list `key`
- user-facing `name`
- user-facing `action_name`
New list types should automatically fit the same frontend rendering model:
- list metadata
- list items
- explicit `completed` state
- idempotent set-state requests