docs: document transaction log API

This commit is contained in:
Daniel Schädler 2026-04-30 14:09:45 +02:00
parent 54975d969b
commit 431f0a6640
3 changed files with 282 additions and 1 deletions

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_04_30_120000) do ActiveRecord::Schema[8.1].define(version: 2026_04_30_121000) do
create_table "beamers", force: :cascade do |t| create_table "beamers", force: :cascade do |t|
t.json "config", default: {}, null: false t.json "config", default: {}, null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false

View File

@ -0,0 +1,281 @@
# Tournament Transaction Log API
## Purpose
Tournament transaction log is owner-readable tournament history.
It records ordered tournament events such as:
- tournament creation
- timer changes
- match state changes
- match score changes
- decider match creation
- playoff start attempts and failures
- playoff start success
- group match auto-starts
Frontend should show this as an append-only history feed for tournament owners.
## Read Flow
Transaction log is not embedded in normal tournament payload.
Read it from dedicated owner-only endpoint:
- `GET /tournaments/:id/transaction_log`
Auth:
- same Devise token auth headers as other owner APIs
- endpoint requires authenticated tournament owner
Response:
- `200 OK`
- JSON array
- ordered by `sequence` ascending
- no pagination yet
Example:
```json
[
{
"id": 17,
"sequence": 1,
"action": "tournament.created",
"message": "owner created tournament Spring Cup(123)",
"metadata": {
"tournament_id": 123,
"tournament_name": "Spring Cup"
},
"created_at": "2026-04-30T10:12:42Z",
"user": {
"id": 4,
"username": "owner"
}
},
{
"id": 18,
"sequence": 2,
"action": "match.state_changed",
"message": "owner changed match 77 (Team A(10) vs Team B(11)) to running",
"metadata": {
"match_id": 77,
"stage_type": "playoffs",
"old_state": "not_started",
"new_state": "in_progress",
"team_ids": [10, 11],
"teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" }
],
"winner_team_id": null,
"advanced_match_ids": [],
"group_scores": []
},
"created_at": "2026-04-30T10:13:01Z",
"user": {
"id": 4,
"username": "owner"
}
}
]
```
## Field Contract
Each entry has:
- `id`: DB id for entry
- `sequence`: per-tournament monotonic order number, starts at `1`
- `action`: machine-readable action key
- `message`: human-readable owner history text
- `metadata`: structured action-specific data
- `created_at`: ISO-8601 timestamp
- `user`: actor object, or `null` for system actions
`user` shape:
```json
{
"id": 4,
"username": "owner"
}
```
Frontend display guidance:
- sort by `sequence`, not by timestamp
- use `message` for first UI pass
- use `metadata` for links/details/chips
- tolerate unknown future `action` values
- tolerate extra `metadata` fields
## Current Action Keys
Current `action` values:
- `tournament.created`
- `tournament.timer_changed`
- `match.state_changed`
- `match_score.updated`
- `decider_match.created`
- `playoffs.start_failed`
- `playoffs.started`
- `group_matches.started`
Future backend work may add more action keys.
Unknown actions should still render via `message`.
## Metadata Notes
`metadata` is stable JSON, but shape depends on `action`.
Common match metadata includes:
- `match_id`
- `stage_id`
- `group_id`
- `stage_type`: `groupstage` or `playoffs`
- `team_ids`
- `teams`
- `winner_team_id`
- `advanced_match_ids`
- `group_scores`
Team object shape:
```json
{
"id": 10,
"name": "Team A"
}
```
Group score object shape:
```json
{
"team_id": 10,
"team_name": "Team A",
"points": 9,
"matches_played": 3,
"wins": 3,
"draws": 0,
"losses": 0,
"score": 12,
"score_against": 4,
"score_difference": 8,
"position": 1
}
```
## Example Actions
### Match state change
```json
{
"action": "match.state_changed",
"message": "owner changed match 77 to finished (Team A(10) wins) - playoffs - Team A(10) advances to match 80",
"metadata": {
"match_id": 77,
"stage_type": "playoffs",
"old_state": "in_progress",
"new_state": "finished",
"winner_team_id": 10,
"advanced_match_ids": [80]
}
}
```
### Match score update
```json
{
"action": "match_score.updated",
"message": "owner changed matchscore 91 from match 77 to 3 (3 points for Team A(10) in match vs Team B(11))",
"metadata": {
"match_id": 77,
"match_score_id": 91,
"team_id": 10,
"changes": {
"points": {
"from": 2,
"to": 3
}
}
}
}
```
### Playoff start failure
```json
{
"action": "playoffs.start_failed",
"message": "owner attempted playoff start, failed because decider missing",
"metadata": {
"stage_id": 12,
"reason": "decider_missing",
"blocking_ties": [
{
"group_id": 5,
"team_ids": [10, 11],
"teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" }
]
}
]
}
}
```
### Playoffs started
```json
{
"action": "playoffs.started",
"message": "owner started playoffs, advancing teams: Team A(10), Team B(11), Team C(12), Team D(13)",
"metadata": {
"stage_id": 12,
"advancing_team_ids": [10, 11, 12, 13],
"advancing_teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" },
{ "id": 12, "name": "Team C" },
{ "id": 13, "name": "Team D" }
]
}
}
```
## Error Behavior
Expected errors:
- `401 Unauthorized`: missing or invalid auth
- `403 Forbidden`: authenticated user is not tournament owner
- `404 Not Found`: tournament id does not exist
Read-only follower tournaments:
- log read is allowed for owner
- log write is internal only; frontend has no write endpoint
## Frontend Implementation Guide
Recommended owner-history flow:
1. Load tournament as usual.
2. If current user is owner, request `GET /tournaments/:id/transaction_log`.
3. Render entries sorted by `sequence`.
4. Use `message` as main text.
5. Show `created_at` as event time.
6. Link matches/teams from `metadata` when ids are present.
7. Refresh after owner mutations, or poll while history panel is open.
There is no websocket stream for transaction log yet.
Existing tournament/match websocket updates still fire for live tournament state.