feat(scheduler): seed global maintenance schedules at boot (#45)#103
Merged
Conversation
CcasServer ran zero scheduled jobs on a fresh DB: job_schedule started empty and MatchRef/ClubData (server-internal all-clubs sweeps with no manual trigger) never ran. ServerTables.ensureTables now seeds a global (club_id NULL) schedule for each at boot. Cadence/enabled come from a new scheduler.defaults HOCON block (env-overridable), not hardcoded -- seed-only: once a row exists, ScheduleRoutes/`ccas schedule` is the live truth. SchedulerDefaults reads and range-checks the config (1..SMALLINT); ServerTables wraps the read in ZIO.attempt so malformed config fails boot cleanly rather than as a defect. Idempotent via a NULL-safe WHERE NOT EXISTS guard rather than ON CONFLICT (kind, club_id): Postgres treats NULL club_id as distinct, so plain ON CONFLICT would re-insert a duplicate global row on every boot. Per-club History/Membership seeding is intentionally out of scope (it would crawl all incidentally-known clubs); split to #101 (managed_club marker) and #102. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv
This was referenced Jun 19, 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
ServerTables.ensureTablesnow seeds a global (club_id IS NULL)JobSchedulerow at boot for each server-internal maintenance kind —MatchRef(24h) andClubData(6h). Cadence/enabled come from a newscheduler.defaultsHOCON block (env-overridable), not hardcoded.ScheduleRoutes/ccas schedule) is authoritative — later config edits are not re-applied.WHERE NOT EXISTSguard. PlainON CONFLICT (kind, club_id)would not work: Postgres treats NULLclub_idas distinct, so it would re-insert a duplicate global row every boot.SchedulerDefaultsreads + range-checks the config (1..Short.MaxValue, avoids silent.toShortoverflow);ServerTableswraps the read inZIO.attemptso malformed config fails boot as a typed error, not a defect.Fixes
Closes #45 (rescoped to globals-only).
Per-club
History/Membershipseeding was deliberately left out — seeding History for every incidentally-known club would blow up the DB. Split to #101 (first-classmanaged_clubmarker) → #102 (per-managed-club seeding).Testing
TestSchedulerDefaults(new): defaults, override, reject ≤0, reject > SMALLINT.TestJobScheduleSql:seedGlobalIfAbsentinserts a global row, is a no-op on re-seed, and leaves a hand-disabled row untouched.JobScheduler/HistoryAppchange — existing MatchRef/ClubData dispatch already runs all-clubs.sbt testgreen (829) via pre-push hook.🤖 Generated with Claude Code
https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv