feat(cli): first-class managed_club marker + ccas clubs group (#101)#104
Merged
Conversation
CCAS had no first-class notion of "a club the user manages": the club table holds many incidentally-ingested scouted/opponent clubs, and the only signals for "managed" were leaky side-effect tables (has-criteria, has-membership-run). That made per-club automation (e.g. #102's History/Membership seeding) unable to target the right clubs without risking a DB-blowup crawl of every known club. Add an explicit, never-derived marker: - managed_club(club_id PK, marked_at), FK to club. Idempotent markManaged via ON CONFLICT DO NOTHING; selectClubIds (managed, tombstone-excluded) is the non-leaky source #102 will seed from. - ManagedClubApp (mark/unmark/list) — no Chess.com fetch; an unknown slug 404s rather than triggering rename recovery (managing requires the club to exist locally, per #101 scope). - /api/managed-clubs routes (GET/POST/DELETE), synchronous like BlacklistRoutes. - CLI `ccas clubs add|remove|list`, mirroring the blacklist/schedule groups. Also derive the completion emitter's group `base=2` guard (and the subcommand comment) from CompletionSpec.groups: it was hardcoded to blacklist/schedule, so a new group would have left its `<slug>` completion off-by-one. Server-scoped for v0 (single-user, loopback, #40). Per-user ownership migrates to (user_id, club_id) at #66; per-device working-set is a client-config concern (#86) — both recorded on #101, deliberately kept off the shared row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv
This was referenced Jun 20, 2026
Sootopolis
added a commit
that referenced
this pull request
Jun 21, 2026
…d club (#102) (#105) Boot-seeding (#45/#103) covered only the global MatchRef/ClubData maintenance jobs; per-club History/Membership were deferred until a non-leaky "clubs I manage" signal existed. #101/#104 delivered that (managed_club + ManagedClub.selectClubIds, tombstone-excluded), so this extends ServerTables.ensureTables to seed a default History and Membership job_schedule row for each managed club — never all known clubs, which would crawl scouted/opponent clubs and blow up the DB. Per-club rows use INSERT ... ON CONFLICT (kind, club_id) DO NOTHING (non-NULL club_id is deduped by UNIQUE(kind, club_id)), unlike the global seeder's NULL-safe WHERE NOT EXISTS. Defaults (History 24h, Membership 24h, both enabled) come from new scheduler.defaults.history / .membership HOCON blocks with the usual ${?ENV} overrides; seed-only, so the live row wins after first boot. Scheduler dispatch already routes per-club History/Membership, so no dispatch change. The (JobKind, String, Short) seed tuples become a SeedDefault case class shared by the global and per-club lists. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Introduces a first-class, explicitly-set managed-club marker — the set of clubs the user actively manages with CCAS, distinct from the many scouted/opponent clubs the DB ingests incidentally.
managed_clubtable (club_idPK,marked_at, FK →club). IdempotentmarkManaged(ON CONFLICT DO NOTHING);selectClubIdsreturns managed, tombstone-excluded club ids — the non-leaky source Seed default History/Membership schedules per managed club #102 will seed from;selectAllWithClubfor display.ManagedClubApp(mark/unmark/list) — synchronous, no Chess.com fetch. An unknown/renamed slug 404s rather than triggering rename recovery (managing requires the club to exist locally)./api/managed-clubsroutes (GET/POST/DELETE), wired intoCcasServer; table intoTables.ensureTables.ccas clubs add|remove|listgroup, mirroringblacklist/schedule.ccas.bashupdated. Emitter fix: the groupbase=2guard (and the subcommand comment) were hardcoded toblacklist/schedule; now derived fromCompletionSpec.groups, so a new group can't leave its<slug>completion off-by-one.Fixes
Implements #101. The non-leaky
selectClubIdsunblocks #102 (per-managed-club History/Membership seeding).Scope / future
Server-scoped for v0 (single-user, loopback — #40). Per-user ownership migrates to
(user_id, club_id)at #66; per-device working-set is a client-config concern (#86). Both recorded on #101 and deliberately kept off the shared row.Testing
TestManagedClubSql(mark idempotency, tombstone-excludedselectClubIds, join/order, delete).TestRoutesmanaged-club suite (mark→list, 404 unknown club, delete, wire shape).TestCliParser(clubs add/remove/list);TestCcasCompletiondrift guard (golden == emitter, bash/zsh/fish syntax).sbt testgreen (842) via pre-push hook.🤖 Generated with Claude Code
https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv