Skip to content

feat(scheduler): seed global maintenance schedules at boot (#45)#103

Merged
Sootopolis merged 1 commit into
mainfrom
feat/45-seed-global-schedules
Jun 19, 2026
Merged

feat(scheduler): seed global maintenance schedules at boot (#45)#103
Sootopolis merged 1 commit into
mainfrom
feat/45-seed-global-schedules

Conversation

@Sootopolis

Copy link
Copy Markdown
Owner

What

ServerTables.ensureTables now seeds a global (club_id IS NULL) JobSchedule row at boot for each server-internal maintenance kind — MatchRef (24h) and ClubData (6h). Cadence/enabled come from a new scheduler.defaults HOCON block (env-overridable), not hardcoded.

  • Seed-only: applied once on a fresh DB; afterwards the live row (ScheduleRoutes / ccas schedule) is authoritative — later config edits are not re-applied.
  • Idempotent via a NULL-safe WHERE NOT EXISTS guard. Plain ON CONFLICT (kind, club_id) would not work: Postgres treats NULL club_id as distinct, so it would re-insert a duplicate global row every boot.
  • SchedulerDefaults reads + range-checks the config (1..Short.MaxValue, avoids silent .toShort overflow); ServerTables wraps the read in ZIO.attempt so malformed config fails boot as a typed error, not a defect.

Fixes

Closes #45 (rescoped to globals-only).

Per-club History/Membership seeding was deliberately left out — seeding History for every incidentally-known club would blow up the DB. Split to #101 (first-class managed_club marker) → #102 (per-managed-club seeding).

Testing

  • TestSchedulerDefaults (new): defaults, override, reject ≤0, reject > SMALLINT.
  • TestJobScheduleSql: seedGlobalIfAbsent inserts a global row, is a no-op on re-seed, and leaves a hand-disabled row untouched.
  • No JobScheduler/HistoryApp change — existing MatchRef/ClubData dispatch already runs all-clubs.
  • Full sbt test green (829) via pre-push hook.

🤖 Generated with Claude Code

https://claude.ai/code/session_019GbAQC25N8MiDThZKZEyMv

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
@Sootopolis Sootopolis merged commit 31d8874 into main Jun 19, 2026
1 check passed
@Sootopolis Sootopolis deleted the feat/45-seed-global-schedules branch June 19, 2026 19:35
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>
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.

Bootstrap default JobSchedule rows for server-internal kinds at first boot

1 participant