Skip to content

feat(cli): first-class managed_club marker + ccas clubs group (#101)#104

Merged
Sootopolis merged 1 commit into
mainfrom
feat/101-managed-club
Jun 20, 2026
Merged

feat(cli): first-class managed_club marker + ccas clubs group (#101)#104
Sootopolis merged 1 commit into
mainfrom
feat/101-managed-club

Conversation

@Sootopolis

Copy link
Copy Markdown
Owner

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_club table (club_id PK, marked_at, FK → club). Idempotent markManaged (ON CONFLICT DO NOTHING); selectClubIds returns managed, tombstone-excluded club ids — the non-leaky source Seed default History/Membership schedules per managed club #102 will seed from; selectAllWithClub for 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-clubs routes (GET/POST/DELETE), wired into CcasServer; table into Tables.ensureTables.
  • CLI ccas clubs add|remove|list group, mirroring blacklist/schedule.
  • Completion: spec + golden ccas.bash updated. Emitter fix: the group base=2 guard (and the subcommand comment) were hardcoded to blacklist/schedule; now derived from CompletionSpec.groups, so a new group can't leave its <slug> completion off-by-one.

Fixes

Implements #101. The non-leaky selectClubIds unblocks #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-excluded selectClubIds, join/order, delete).
  • TestRoutes managed-club suite (mark→list, 404 unknown club, delete, wire shape).
  • TestCliParser (clubs add/remove/list); TestCcasCompletion drift guard (golden == emitter, bash/zsh/fish syntax).
  • Full sbt test green (842) via pre-push hook.

🤖 Generated with Claude Code

https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv

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
@Sootopolis Sootopolis merged commit 9f79396 into main Jun 20, 2026
1 check passed
@Sootopolis Sootopolis deleted the feat/101-managed-club branch June 20, 2026 01:18
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>
@Sootopolis Sootopolis mentioned this pull request Jun 21, 2026
12 tasks
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