Skip to content

Series-aware event badges on solving times, one public-visibility rule, API detail gate for editions - #206

Merged
JanMikes merged 1 commit into
mainfrom
fix/series-aware-event-badges
Aug 19, 2026
Merged

Series-aware event badges on solving times, one public-visibility rule, API detail gate for editions#206
JanMikes merged 1 commit into
mainfrom
fix/series-aware-event-badges

Conversation

@JanMikes

Copy link
Copy Markdown
Member

Part 2 of 4 for #204 (after #205). Visual no-op for every existing solving time — all of them link to standalone competitions. This PR prepares the rendering (and the API) for times linked to editions of recurring series, which the next PR makes selectable in the add-time picker.

What

  • IsCompetitionPubliclyVisible — the single visibility rule for a competition row: standalone → approved & not rejected; edition → its series approved & not rejected (editions are never individually approved, ConvertCompetitionToSeries even clears approved_at). The predicate is exposed as SQL_CONDITION so the picker's list query (next PR) embeds the exact same rule.
  • Series-aware read models — the 13 SQL blocks that select competition.name/shortcut/slug next to a solving time now also select the series name/shortcut/slug (SolvedPuzzle, PuzzleSolver, PuzzleSolversGroup, RecentActivityItem got 3 nullable fields, appended with defaults). GetLastSolvedPuzzle deleted — no consumers.
  • One badge partial templates/_competition_badge.html.twig replaces the 5 copy-pasted trophy-badge snippets (_player_solvings, LadderTable, PuzzleTimes ×2, RecentActivity). Standalone output is unchanged (shortcut ?? nameevent_detail); an edition renders series.shortcut ?? series.name · edition and links directly to edition_detail — never event_detail by bare slug, which is only unique per series. Also fixes the PuzzleTimes.html.twig row that mixed solving_time.competitionShortcut ?? aggregated_solver[0].competitionName. _ladder.html.twig deleted (never included).
  • APIGET /api/v1/competitions/{id} gates on the visibility rule (editions of approved series are readable; editions of unapproved/rejected series, and unapproved/rejected standalone, still 404) and carries an additive series: {id, name, slug} | null. The list endpoint stays standalone-only (documented).
  • Fixtures: SERIES_UNAPPROVED + EDITION_UNAPPROVED_1 (documented in .claude/fixtures.md).

Perf

EXPLAIN (ANALYZE, BUFFERS) for GetFastestPlayers (500 pcs, LIMIT 100) on the dev prod-like dump (339k solving times): before 177–188 ms warm, after 170–176 ms warm — the extra node is a hash join on a ≤200-row table applied after the CTE. Negligible everywhere else (PK lookup per row with a competition_id).

Tests

+25 tests: IsCompetitionPubliclyVisibleTest (8), API detail endpoint (edition 200 with series, rejected/unapproved series 404, standalone series: null), query tests for the new columns in GetPlayerSolvedPuzzles/GetPuzzleSolvers/GetFastestPlayers/GetRecentActivity (in-test UPDATE seeding under DAMA rollback — no permanent solving-time fixture rows, the picker/statistics tests pin exact counts), CompetitionBadgeTemplateTest (8 rendering cases incl. hrefs), PuzzleTimesCompetitionBadgeTest (live component). Full gates green locally (cs, phpstan max, 2209 tests, schema:validate, prod warmup).

Refs #204

🤖 Generated with Claude Code

https://claude.ai/code/session_01QUegPi2i9SjwkcrNWyENnL

…e, API detail gate for editions

Solving times will soon be linkable to editions of recurring series (a
competition row with series_id set, no logo/shortcut, approved via its
series). Prepare every place a time's event is shown:

- IsCompetitionPubliclyVisible: the single rule (standalone: approved &
  not rejected; edition: parent series approved & not rejected), with the
  predicate exposed as SQL_CONDITION for list queries.
- 13 read-model SQL blocks select the series name/shortcut/slug next to
  the competition columns (SolvedPuzzle, PuzzleSolver, PuzzleSolversGroup,
  RecentActivityItem DTOs); GetLastSolvedPuzzle deleted (no consumers).
- One shared partial _competition_badge.html.twig replaces 5 copy-pasted
  badge snippets (fixes the PuzzleTimes mixed-row label bug); editions
  render "series · edition" and link straight to edition_detail — never
  event_detail by bare slug, which is ambiguous across series.
  _ladder.html.twig deleted (never included).
- GET /api/v1/competitions/{id} gates on the visibility rule (editions of
  approved series become readable) and carries an additive series object.

Fixtures: SERIES_UNAPPROVED + EDITION_UNAPPROVED_1.

Refs #204

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUegPi2i9SjwkcrNWyENnL
@JanMikes
JanMikes merged commit ddcdf01 into main Aug 19, 2026
5 checks passed
@JanMikes
JanMikes deleted the fix/series-aware-event-badges branch August 19, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant