Docker Compose bot + Google Api
Build App / Build (push) Failing after 3s

This commit is contained in:
2026-08-06 16:23:50 +02:00
parent c9a3c252e1
commit 937b8554dd
44 changed files with 3360 additions and 74 deletions
+75 -7
View File
@@ -43,7 +43,7 @@ DTO field surfaces as a TypeScript error rather than a runtime 404.
| Module | Purpose |
| --- | --- |
| `src/lib/api/schema.d.ts` | Generated types — all 23 API paths and every DTO |
| `src/lib/api/schema.d.ts` | Generated types — all 31 API paths and every DTO |
| `src/lib/api/schema-helpers.ts` | Friendly aliases (`ApplicationUserDTO`, `LoginRequest`, …) |
| `src/lib/api/client.ts` | `apiRequest` — bearer auth, JSON, `ApiError`; paths constrained to real routes |
| `src/lib/api/users.ts` | `login` / `register` against `/Users/auth` and `/Users/register` |
@@ -51,10 +51,13 @@ DTO field surfaces as a TypeScript error rather than a runtime 404.
| `src/lib/api/tournaments.ts` | `listEvents` / `importSmashTournament` / `getResults`, authenticated from the session |
| `src/lib/api/games.ts` | `listGames` / `saveGame` / `deleteGame` / `searchSmashGames` |
| `src/lib/api/admin-users.ts` | `listUsers` / `listRoles` / `addUser` / `deleteUser` — all Admin-only |
| `src/lib/api/statistics.ts` | `getMatchStats` against `POST /api/Statistics/Matches` — set-level win/loss and head to head |
| `src/lib/api/statistics.ts` | `getMatchStats` (set-level win/loss and head to head), `listVersusPlayers` / `getVersus` (one pairing, per game) |
| `src/lib/api/sheets.ts` | `getSheetsConfig` / `exportToSheets` — writes a ranking table into the club's Google Spreadsheet |
| `src/lib/tournaments/results.ts` | Pure reshaping of `TournamentsResultDTO`: ranking grid, per-game placements, WordPress HTML, CSV |
| `src/lib/tournaments/sheet.ts` | Pure: ranking grid → one spreadsheet tab, plus the tab title a selection suggests |
| `src/lib/statistics/aggregate.ts` | Pure aggregation for `/statistiques`: standings, per-game and per-event summaries, CSV |
| `src/lib/statistics/load.ts` | `loadEventResults` — per-event `GetResults` fan-out with progress, partial failure and abort |
| `src/lib/ui/PlayerPicker.svelte` | Filterable player list used twice on `/statistiques/players` |
| `src/lib/games/draft.ts` | `GameDTO` ⇄ editor form, including the blank-to-NULL rules |
| `src/lib/ui/classes.ts` | The Tailwind class strings shared by the pages |
| `src/lib/stores/session.svelte.ts` | Signed-in user, persisted to `localStorage`, drops expired JWTs |
@@ -73,8 +76,13 @@ const one = await apiRequest<GameDTO>(buildPath('/api/Game/{id}', { id: 3 }));
- `/login` — username + password, posts to `POST /Users/auth`, stores the returned JWT
- `/` — guarded; greets the signed-in user with **Hello, \<name\>.** and offers sign-out
- `/tournaments` — guarded; the start.gg half of the old Avalonia `TournamentResultView`
- `/statistiques` — guarded; standings, attendance and match statistics over a chosen scope
- `/tournaments` — guarded; the start.gg half of the old Avalonia `TournamentResultView`,
and the one-click push of the generated ranking table into the club's Google Spreadsheet
- `/statistiques` — redirects to `/statistiques/rankings` (the path the navbar used before
the section became two pages)
- `/statistiques/rankings` — guarded; standings, attendance and match statistics over a
chosen scope
- `/statistiques/players` — guarded; two players, every game they met in, all events
- `/games` — guarded; the game catalogue editor, from the Avalonia `GamesView`
- `/users`**Admin only**; add and remove accounts
@@ -92,16 +100,48 @@ Ports the Smash.gg (start.gg) column of `LaDOSE.DesktopApp.Avalonia`:
selection with every matching event name (e.g. `Ranking #13\d{2}`).
3. **Generate results**`POST /api/Tournament/GetResults` with the selected ids;
the API applies the point rules in `ExternalProviderService`. Three views:
- *Ranking* — players × games with totals, highest first, plus CSV export
- *Ranking* — players × games with totals, highest first, plus CSV export and the
Google Sheets push below
- *By game* — placements and points for one game
- *HTML* — the podium table for the WordPress recap, with copy-to-clipboard
The Challonge half of the Avalonia view (date range, Challonge tournament list,
`ParseChallonge`) is deliberately not ported.
### `/statistiques`
#### Google Sheets export
Pick a scope — everything, the last 6/12 events, or a regex over event names — then
Next to **Export CSV** on the *Ranking* view: **Push to Google Sheets** writes that same
table straight into the club's spreadsheet, as one tab. It replaces the
download-then-import-by-hand step — the tab is the CSV, because `rankingToSheetTable`
reads the very same `RankingTable` that `buildCsv` does, so the two cannot drift.
Because `GetResults` merges everything it is given, selecting `Ranking #1301`, `#1302` and
`#1303` produces the cumulative table for that ranking day — which is why the tab title
defaults to the **latest** event in the selection (`Ranking #1303`). Override it in the
**Tab** box; leaving it blank uses the suggestion shown as the placeholder.
- The tab is **cleared and rewritten in place**. Formatting, notes and conditional
formatting survive (the write only sets `userEnteredValue`), but anything typed into it
by hand is lost — keep hand analysis in its own tab.
- Tabs the export does not name are **never touched or deleted**.
- Re-running changes nothing but the provenance footer's timestamp.
- Points and totals are written as **numbers**, so formulas over them keep working.
- Titles are sanitised server-side (Google forbids `: \ / ? * [ ]`, caps at 100 chars);
any rename is reported back in the success banner.
- Tab identity is the **title**, so renaming an event makes the next push write a new tab
beside the old one.
- The provenance footer, below the grid, names every event scored into the tab — so a
stale tab says so itself.
Server configuration lives in the `GoogleSheets` section — writer, target spreadsheet and
limits. The target is config-only because the sheet is replaced each year; see
`.env.example` for the one-time Google setup and the yearly swap. Set
`GoogleSheets:Writer` to `Logging` to see the exact payload in the API log without
touching a spreadsheet.
### `/statistiques/rankings`
**Rankings Statistiques.** Pick a scope — everything, the last 6/12 events, or a regex over event names — then
**Compute statistics**. Two independent sources feed the page, and they are kept apart
on purpose:
@@ -133,6 +173,31 @@ Other things worth knowing:
- `aggregate.ts` is pure, so it can be exercised under plain Node with fixtures, the
same way `src/lib/tournaments/results.ts` is.
### `/statistiques/players`
**Players Statistiques.** Pick two players and the page answers one question: how often
did they meet, and in which games. There is no event scope — the point of a pairing is
its whole history, and slicing it by season would only be the Rankings page again.
- The pickers come from `GET /api/Statistics/Players`, which lists **tournament
players** (the rows `set` points at), not the application accounts of `/users`. Only
players with at least one set in a bracket whose game is known are offered, so the
list can never suggest a player the breakdown must then report as empty.
- The breakdown comes from `GET /api/Statistics/Versus/{a}/{b}`, one request per
complete pairing, aborted and re-issued when either side changes. `winsA` is always
the first id's side.
- **A set carries no game.** The game belongs to the `Tournament` the set was played
in, and `Tournament.GameId` is nullable, so meetings in a bracket with no game cannot
be filed under one. The API excludes them from `games` and from the totals and counts
them in `unknownGameSets`; the page reports that number instead of hiding it — it is
the difference between "they never met" and "we cannot tell what they played".
- **Meetings** counts every recorded set, **Decided** only those with unequal scores.
A set with equal scores (including 0-0, and the `-1 / -1` a double DQ leaves behind)
happened, but nobody won it, so it is in neither record nor game counts. A single DQ
is stored as `-1` and clamps to zero games.
- `StatisticsService.AggregateVersus` is a pure static method over already-loaded rows,
like `Aggregate` beside it, so the win inference is testable without a database.
### `/games`
Ports `GamesView`: the list on the left (ordered by `Order`), an editor on the right.
@@ -190,6 +255,9 @@ How roles work:
call site supplies its own fallback message.
- `GetResults` only fills `slug` when **one** event id is requested, so the
"Voir le Bracket" links appear only for a single-event export.
- The Sheets export posts a 7-deep body, so `Startup.cs` sets Newtonsoft's `MaxDepth` to
32. `MaxDepth` governs *reading*; at the previous value of 4 the request was rejected
before it reached the controller.
- Player names are merged case-insensitively across brackets, matching the desktop app.
- `src/routes/+layout.ts` sets `ssr = false`: the JWT lives in the browser, so there
is nothing meaningful to render on the server.