docs: document transaction log API
This commit is contained in:
parent
54975d969b
commit
431f0a6640
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# 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|
|
||||
t.json "config", default: {}, null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
Loading…
Reference in New Issue