HASH f9bab020c966
DATE 2025-12-11
SUBJECT ookstats: throw away period based season, we use timestamps now
FILES 10 CHANGED
HASH f9bab020c966
DATE 2025-12-11
SUBJECT ookstats: throw away period based
season, we use timestamps now
FILES 10 CHANGED
┌─ nix/pkgs/ookstats/src/cmd/backfill.go ────────────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/backfill.go b/nix/pkgs/ookstats/src/cmd/bac │
│ kfill.go │
│ new file mode 100644 │
│ index 0000000..a56bd5a │
│ --- /dev/null │
│ +++ b/nix/pkgs/ookstats/src/cmd/backfill.go │
│ @@ -0,0 +1,78 @@ │
│ +package cmd │
│ + │
│ +import ( │
│ + "fmt" │
│ + "ookstats/internal/database" │
│ + │
│ + "github.com/spf13/cobra" │
│ +) │
│ + │
│ +var backfillCmd = &cobra.Command{ │
│ + Use: "backfill", │
│ + Short: "Backfill missing data", │
│ + Long: `Backfill and update historical data with new fields.`, │
│ +} │
│ + │
│ +var backfillSeasonsCmd = &cobra.Command{ │
│ + Use: "seasons", │
│ + Short: "Assign season_id to all challenge runs", │
│ + Long: `Assigns season_id to all existing challenge runs based on their completed │
│ _timestamp │
│ +and the season start/end timestamps for their region.`, │
│ + RunE: func(cmd *cobra.Command, args []string) error { │
│ + fmt.Println("=== Backfill Season Assignments ===") │
│ + │
│ + db, err := database.Connect() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to connect to database: %w", err) │
│ + } │
│ + defer db.Close() │
│ + │
│ + fmt.Printf("Connected to database: %s\n", database.DBFilePath()) │
│ + │
│ + dbService := database.NewDatabaseService(db) │
│ + │
│ + fmt.Println("\nAssigning seasons to challenge runs based on timestamps...") │
│ + err = dbService.AssignRunsToSeasons() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to assign seasons: %w", err) │
│ + } │
│ + │
│ + // show stats │
│ + fmt.Println("\n Season Assignment Statistics") │
│ + rows, err := db.Query(` │
│ + SELECT │
│ + s.region, │
│ + COALESCE(s.season_name, 'Season ' || s.season_number) as season_display, │
│ + COUNT(cr.id) as run_count │
│ + FROM seasons s │
│ + LEFT JOIN challenge_runs cr ON cr.season_id = s.season_number AND cr.realm_id │
│ IN ( │
│ + SELECT id FROM realms WHERE region = s.region │
│ + ) │
│ + GROUP BY s.id, s.region, s.season_name, s.season_number │
│ + ORDER BY s.region, s.season_number │
│ + `) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to query stats: %w", err) │
│ + } │
│ + defer rows.Close() │
│ + │
│ + fmt.Printf("\n%-8s %-20s %10s\n", "Region", "Season", "Runs") │
│ + fmt.Println("----------------------------------------") │
│ + for rows.Next() { │
│ + var region, seasonName string │
│ + var runCount int │
│ + if err := rows.Scan(®ion, &seasonName, &runCount); err != nil { │
│ + return fmt.Errorf("failed to scan row: %w", err) │
│ + } │
│ + fmt.Printf("%-8s %-20s %10d\n", region, seasonName, runCount) │
│ + } │
│ + │
│ + fmt.Printf("\n[OK] Season backfill complete!\n") │
│ + return nil │
│ + }, │
│ +} │
│ + │
│ +func init() { │
│ + rootCmd.AddCommand(backfillCmd) │
│ + backfillCmd.AddCommand(backfillSeasonsCmd) │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../ookstats/src/cmd/backfill.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/backf │
│ ill.go b/nix/pkgs/ookstats/src/cmd/backfill. │
│ go │
│ new file mode 100644 │
│ index 0000000..a56bd5a │
│ --- /dev/null │
│ +++ b/nix/pkgs/ookstats/src/cmd/backfill.go │
│ @@ -0,0 +1,78 @@ │
│ +package cmd │
│ + │
│ +import ( │
│ + "fmt" │
│ + "ookstats/internal/database" │
│ + │
│ + "github.com/spf13/cobra" │
│ +) │
│ + │
│ +var backfillCmd = &cobra.Command{ │
│ + Use: "backfill", │
│ + Short: "Backfill missing data", │
│ + Long: `Backfill and update historical dat │
│ a with new fields.`, │
│ +} │
│ + │
│ +var backfillSeasonsCmd = &cobra.Command{ │
│ + Use: "seasons", │
│ + Short: "Assign season_id to all challenge │
│ runs", │
│ + Long: `Assigns season_id to all existing c │
│ hallenge runs based on their completed_times │
│ tamp │
│ +and the season start/end timestamps for the │
│ ir region.`, │
│ + RunE: func(cmd *cobra.Command, args []stri │
│ ng) error { │
│ + fmt.Println("=== Backfill Season Assignme │
│ nts ===") │
│ + │
│ + db, err := database.Connect() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to connect to │
│ database: %w", err) │
│ + } │
│ + defer db.Close() │
│ + │
│ + fmt.Printf("Connected to database: %s\n", │
│ database.DBFilePath()) │
│ + │
│ + dbService := database.NewDatabaseService( │
│ db) │
│ + │
│ + fmt.Println("\nAssigning seasons to chall │
│ enge runs based on timestamps...") │
│ + err = dbService.AssignRunsToSeasons() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to assign seas │
│ ons: %w", err) │
│ + } │
│ + │
│ + // show stats │
│ + fmt.Println("\n Season Assignment Statist │
│ ics") │
│ + rows, err := db.Query(` │
│ + SELECT │
│ + s.region, │
│ + COALESCE(s.season_name, 'Season ' || s. │
│ season_number) as season_display, │
│ + COUNT(cr.id) as run_count │
│ + FROM seasons s │
│ + LEFT JOIN challenge_runs cr ON cr.season │
│ _id = s.season_number AND cr.realm_id IN ( │
│ + SELECT id FROM realms WHERE region = s. │
│ region │
│ + ) │
│ + GROUP BY s.id, s.region, s.season_name, │
│ s.season_number │
│ + ORDER BY s.region, s.season_number │
│ + `) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to query stats │
│ : %w", err) │
│ + } │
│ + defer rows.Close() │
│ + │
│ + fmt.Printf("\n%-8s %-20s %10s\n", "Region │
│ ", "Season", "Runs") │
│ + fmt.Println("---------------------------- │
│ ------------") │
│ + for rows.Next() { │
│ + var region, seasonName string │
│ + var runCount int │
│ + if err := rows.Scan(®ion, &seasonName │
│ , &runCount); err != nil { │
│ + return fmt.Errorf("failed to scan row: │
│ %w", err) │
│ + } │
│ + fmt.Printf("%-8s %-20s %10d\n", region, │
│ seasonName, runCount) │
│ + } │
│ + │
│ + fmt.Printf("\n[OK] Season backfill comple │
│ te!\n") │
│ + return nil │
│ + }, │
│ +} │
│ + │
│ +func init() { │
│ + rootCmd.AddCommand(backfillCmd) │
│ + backfillCmd.AddCommand(backfillSeasonsCmd) │
│ +} │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/cmd/build.go ───────────────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/build.go b/nix/pkgs/ookstats/src/cmd/build. │
│ go │
│ index dd53a82..c532b2c 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/build.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/build.go │
│ @@ -151,19 +151,25 @@ var buildCmd = &cobra.Command{ │
│ "players", result.TotalPlayers, │
│ "duration", result.Duration) │
│ │
│ - // 4) Fingerprint players (merge duplicates) │
│ + // 4) Assign seasons to runs based on timestamps │
│ + log.Info("assigning seasons to runs", "method", "timestamp-based") │
│ + if err := dbService.AssignRunsToSeasons(); err != nil { │
│ + return fmt.Errorf("assign seasons: %w", err) │
│ + } │
│ + │
│ + // 5) Fingerprint players (merge duplicates) │
│ log.Info("fingerprinting players", "stage", "identity detection + merge") │
│ if err := fingerprintPlayersOnce(db, client); err != nil { │
│ return err │
│ } │
│ │
│ - // 5) Process players (aggregations + rankings) │
│ + // 6) Process players (aggregations + rankings) │
│ log.Info("processing players", "stage", "aggregations + rankings") │
│ if err := processPlayersOnce(db); err != nil { │
│ return err │
│ } │
│ │
│ - // 6) Fetch detailed player profiles (optional) │
│ + // 7) Fetch detailed player profiles (optional) │
│ if !skipProfiles { │
│ log.Info("fetching detailed player profiles", "coverage", "9/9") │
│ if err := fetchProfilesOnce(db, client); err != nil { │
│ @@ -173,19 +179,19 @@ var buildCmd = &cobra.Command{ │
│ log.Info("skipping player profile fetch", "reason", "flag") │
│ } │
│ │
│ - // 7) Process run rankings (global/regional) │
│ + // 8) Process run rankings (global/regional) │
│ log.Info("processing run rankings", "scopes", "global + regional") │
│ if err := processRunRankingsOnce(db); err != nil { │
│ return err │
│ } │
│ │
│ - // 8) Generate static API │
│ + // 9) Generate static API │
│ log.Info("generating static API") │
│ if err := generateAllAPI(db, normalizedOut, pageSize, shardSize, regionsCSV); e │
│ rr != nil { │
│ return err │
│ } │
│ │
│ - // 9) Generate status API via analyze │
│ + // 10) Generate status API via analyze │
│ log.Info("generating status API", "method", "analyze") │
│ statusDir := filepath.Join(normalizedOut, "api", "status") │
│ outPath := filepath.Join(statusDir, "latest-runs.json") │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...kgs/ookstats/src/cmd/build.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/build │
│ .go b/nix/pkgs/ookstats/src/cmd/build.go │
│ index dd53a82..c532b2c 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/build.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/build.go │
│ @@ -151,19 +151,25 @@ var buildCmd = &cobra. │
│ Command{ │
│ "players", result.TotalPlayers, │
│ "duration", result.Duration) │
│ │
│ - // 4) Fingerprint players (merge duplicat │
│ es) │
│ + // 4) Assign seasons to runs based on tim │
│ estamps │
│ + log.Info("assigning seasons to runs", "me │
│ thod", "timestamp-based") │
│ + if err := dbService.AssignRunsToSeasons() │
│ ; err != nil { │
│ + return fmt.Errorf("assign seasons: %w", │
│ err) │
│ + } │
│ + │
│ + // 5) Fingerprint players (merge duplicat │
│ es) │
│ log.Info("fingerprinting players", "stage │
│ ", "identity detection + merge") │
│ if err := fingerprintPlayersOnce(db, clie │
│ nt); err != nil { │
│ return err │
│ } │
│ │
│ - // 5) Process players (aggregations + ran │
│ kings) │
│ + // 6) Process players (aggregations + ran │
│ kings) │
│ log.Info("processing players", "stage", " │
│ aggregations + rankings") │
│ if err := processPlayersOnce(db); err != │
│ nil { │
│ return err │
│ } │
│ │
│ - // 6) Fetch detailed player profiles (opt │
│ ional) │
│ + // 7) Fetch detailed player profiles (opt │
│ ional) │
│ if !skipProfiles { │
│ log.Info("fetching detailed player profi │
│ les", "coverage", "9/9") │
│ if err := fetchProfilesOnce(db, client); │
│ err != nil { │
│ @@ -173,19 +179,19 @@ var buildCmd = &cobra. │
│ Command{ │
│ log.Info("skipping player profile fetch" │
│ , "reason", "flag") │
│ } │
│ │
│ - // 7) Process run rankings (global/region │
│ al) │
│ + // 8) Process run rankings (global/region │
│ al) │
│ log.Info("processing run rankings", "scop │
│ es", "global + regional") │
│ if err := processRunRankingsOnce(db); err │
│ != nil { │
│ return err │
│ } │
│ │
│ - // 8) Generate static API │
│ + // 9) Generate static API │
│ log.Info("generating static API") │
│ if err := generateAllAPI(db, normalizedOu │
│ t, pageSize, shardSize, regionsCSV); err != │
│ nil { │
│ return err │
│ } │
│ │
│ - // 9) Generate status API via analyze │
│ + // 10) Generate status API via analyze │
│ log.Info("generating status API", "method │
│ ", "analyze") │
│ statusDir := filepath.Join(normalizedOut, │
│ "api", "status") │
│ outPath := filepath.Join(statusDir, "late │
│ st-runs.json") │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/cmd/schema.go ──────────────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/schema.go b/nix/pkgs/ookstats/src/cmd/schem │
│ a.go │
│ index d5fb0cd..57c1531 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/schema.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/schema.go │
│ @@ -41,7 +41,56 @@ var schemaInitCmd = &cobra.Command{ │
│ }, │
│ } │
│ │
│ +var schemaMigrateCmd = &cobra.Command{ │
│ + Use: "migrate", │
│ + Short: "Migrate database schema to add season_id column", │
│ + Long: `Adds the season_id column to challenge_runs table if it doesn't exist.`, │
│ + RunE: func(cmd *cobra.Command, args []string) error { │
│ + fmt.Println("=== Database Schema Migration ===") │
│ + │
│ + db, err := database.Connect() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to connect to database: %w", err) │
│ + } │
│ + defer db.Close() │
│ + │
│ + fmt.Printf("Connected to database: %s\n", database.DBFilePath()) │
│ + │
│ + // check if season_id column already exists │
│ + var columnExists int │
│ + err = db.QueryRow(` │
│ + SELECT COUNT(*) │
│ + FROM pragma_table_info('challenge_runs') │
│ + WHERE name = 'season_id' │
│ + `).Scan(&columnExists) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to check column existence: %w", err) │
│ + } │
│ + │
│ + if columnExists > 0 { │
│ + fmt.Println("[SKIP] season_id column already exists") │
│ + return nil │
│ + } │
│ + │
│ + fmt.Println("Adding season_id column to challenge_runs table...") │
│ + _, err = db.Exec("ALTER TABLE challenge_runs ADD COLUMN season_id INTEGER") │
│ + if err != nil { │
│ + return fmt.Errorf("failed to add season_id column: %w", err) │
│ + } │
│ + │
│ + fmt.Println("Creating index on season_id...") │
│ + _, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_challenge_runs_season ON chall │
│ enge_runs(season_id, dungeon_id, completed_timestamp)") │
│ + if err != nil { │
│ + return fmt.Errorf("failed to create index: %w", err) │
│ + } │
│ + │
│ + fmt.Printf("\n[OK] Schema migration complete!\n") │
│ + return nil │
│ + }, │
│ +} │
│ + │
│ func init() { │
│ rootCmd.AddCommand(schemaCmd) │
│ schemaCmd.AddCommand(schemaInitCmd) │
│ + schemaCmd.AddCommand(schemaMigrateCmd) │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...gs/ookstats/src/cmd/schema.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/schem │
│ a.go b/nix/pkgs/ookstats/src/cmd/schema.go │
│ index d5fb0cd..57c1531 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/schema.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/schema.go │
│ @@ -41,7 +41,56 @@ var schemaInitCmd = &cobr │
│ a.Command{ │
│ }, │
│ } │
│ │
│ +var schemaMigrateCmd = &cobra.Command{ │
│ + Use: "migrate", │
│ + Short: "Migrate database schema to add sea │
│ son_id column", │
│ + Long: `Adds the season_id column to chall │
│ enge_runs table if it doesn't exist.`, │
│ + RunE: func(cmd *cobra.Command, args []stri │
│ ng) error { │
│ + fmt.Println("=== Database Schema Migratio │
│ n ===") │
│ + │
│ + db, err := database.Connect() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to connect to │
│ database: %w", err) │
│ + } │
│ + defer db.Close() │
│ + │
│ + fmt.Printf("Connected to database: %s\n", │
│ database.DBFilePath()) │
│ + │
│ + // check if season_id column already exis │
│ ts │
│ + var columnExists int │
│ + err = db.QueryRow(` │
│ + SELECT COUNT(*) │
│ + FROM pragma_table_info('challenge_runs') │
│ + WHERE name = 'season_id' │
│ + `).Scan(&columnExists) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to check colum │
│ n existence: %w", err) │
│ + } │
│ + │
│ + if columnExists > 0 { │
│ + fmt.Println("[SKIP] season_id column alr │
│ eady exists") │
│ + return nil │
│ + } │
│ + │
│ + fmt.Println("Adding season_id column to c │
│ hallenge_runs table...") │
│ + _, err = db.Exec("ALTER TABLE challenge_r │
│ uns ADD COLUMN season_id INTEGER") │
│ + if err != nil { │
│ + return fmt.Errorf("failed to add season_ │
│ id column: %w", err) │
│ + } │
│ + │
│ + fmt.Println("Creating index on season_id. │
│ ..") │
│ + _, err = db.Exec("CREATE INDEX IF NOT EXI │
│ STS idx_challenge_runs_season ON challenge_r │
│ uns(season_id, dungeon_id, completed_timesta │
│ mp)") │
│ + if err != nil { │
│ + return fmt.Errorf("failed to create inde │
│ x: %w", err) │
│ + } │
│ + │
│ + fmt.Printf("\n[OK] Schema migration compl │
│ ete!\n") │
│ + return nil │
│ + }, │
│ +} │
│ + │
│ func init() { │
│ rootCmd.AddCommand(schemaCmd) │
│ schemaCmd.AddCommand(schemaInitCmd) │
│ + schemaCmd.AddCommand(schemaMigrateCmd) │
│ } │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/database/client.go ────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/database/client.go b/nix/pkgs/ookstats │
│ /src/internal/database/client.go │
│ index 64ecdc6..34821d0 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/client.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/client.go │
│ @@ -157,7 +157,8 @@ func EnsureCompleteSchema(db *sql.DB) error { │
│ period_id INTEGER, │
│ period_start_timestamp INTEGER, │
│ period_end_timestamp INTEGER, │
│ - team_signature TEXT │
│ + team_signature TEXT, │
│ + season_id INTEGER │
│ )`, │
│ │
│ `CREATE TABLE IF NOT EXISTS players ( │
│ @@ -429,6 +430,7 @@ func ensureRecommendedIndexes(db *sql.DB) error { │
│ "CREATE INDEX IF NOT EXISTS idx_run_members_player_id ON run_members(player_id) │
│ ", │
│ "CREATE INDEX IF NOT EXISTS idx_challenge_runs_dungeon_duration ON challenge_ru │
│ ns(dungeon_id, duration)", │
│ // Season-related indexes │
│ + "CREATE INDEX IF NOT EXISTS idx_challenge_runs_season ON challenge_runs(season_ │
│ id, dungeon_id, completed_timestamp)", │
│ "CREATE INDEX IF NOT EXISTS idx_run_rankings_season ON run_rankings(season_id, │
│ ranking_type, ranking_scope, dungeon_id)", │
│ "CREATE INDEX IF NOT EXISTS idx_player_best_runs_season ON player_best_runs(sea │
│ son_id, player_id)", │
│ "CREATE INDEX IF NOT EXISTS idx_player_profiles_season ON player_profiles(seaso │
│ n_id, global_ranking)", │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...c/internal/database/client.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ database/client.go b/nix/pkgs/ookstats/src/i │
│ nternal/database/client.go │
│ index 64ecdc6..34821d0 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/client.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/client.go │
│ @@ -157,7 +157,8 @@ func EnsureCompleteSchem │
│ a(db *sql.DB) error { │
│ period_id INTEGER, │
│ period_start_timestamp INTEGER, │
│ period_end_timestamp INTEGER, │
│ - team_signature TEXT │
│ + team_signature TEXT, │
│ + season_id INTEGER │
│ )`, │
│ │
│ `CREATE TABLE IF NOT EXISTS players ( │
│ @@ -429,6 +430,7 @@ func ensureRecommendedIn │
│ dexes(db *sql.DB) error { │
│ "CREATE INDEX IF NOT EXISTS idx_run_membe │
│ rs_player_id ON run_members(player_id)", │
│ "CREATE INDEX IF NOT EXISTS idx_challenge │
│ _runs_dungeon_duration ON challenge_runs(dun │
│ geon_id, duration)", │
│ // Season-related indexes │
│ + "CREATE INDEX IF NOT EXISTS idx_challenge │
│ _runs_season ON challenge_runs(season_id, du │
│ ngeon_id, completed_timestamp)", │
│ "CREATE INDEX IF NOT EXISTS idx_run_ranki │
│ ngs_season ON run_rankings(season_id, rankin │
│ g_type, ranking_scope, dungeon_id)", │
│ "CREATE INDEX IF NOT EXISTS idx_player_be │
│ st_runs_season ON player_best_runs(season_id │
│ , player_id)", │
│ "CREATE INDEX IF NOT EXISTS idx_player_pr │
│ ofiles_season ON player_profiles(season_id, │
│ global_ranking)", │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/database/operations.go ────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/database/operations.go b/nix/pkgs/ooks │
│ tats/src/internal/database/operations.go │
│ index 08fe0a3..51f547e 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/operations.go │
│ @@ -148,6 +148,18 @@ func (ds *DatabaseService) InsertLeaderboardData(leaderboard │
│ *blizzard.Leaderboa │
│ playersInserted := 0 │
│ allRealms := blizzard.GetAllRealms() │
│ │
│ + // determine season for runs based on completed timestamp │
│ + var seasonID *int │
│ + if leaderboard.LeadingGroups[0].CompletedTimestamp > 0 { │
│ + sid, err := ds.DetermineSeasonForRun(realmInfo.Region, leaderboard.LeadingGroup │
│ s[0].CompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to determine season: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + seasonID = &sid │
│ + } │
│ + } │
│ + │
│ // begin transaction │
│ tx, err := ds.db.Begin() │
│ if err != nil { │
│ @@ -170,11 +182,25 @@ func (ds *DatabaseService) InsertLeaderboardData(leaderboard │
│ *blizzard.Leaderboa │
│ │
│ teamSignature := utils.ComputeTeamSignature(playerIDs) │
│ │
│ + // determine season for this specific run if different from first │
│ + runSeasonID := seasonID │
│ + if run.CompletedTimestamp != leaderboard.LeadingGroups[0].CompletedTimestamp { │
│ + sid, err := ds.DetermineSeasonForRun(realmInfo.Region, run.CompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to determine season for run: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + runSeasonID = &sid │
│ + } else { │
│ + runSeasonID = nil │
│ + } │
│ + } │
│ + │
│ // insert run with team signature to prevent duplicates │
│ runQuery := ` │
│ INSERT OR IGNORE INTO challenge_runs │
│ - (duration, completed_timestamp, keystone_level, dungeon_id, realm_id, period_i │
│ d, period_start_timestamp, period_end_timestamp, team_signature) │
│ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ + (duration, completed_timestamp, keystone_level, dungeon_id, realm_id, period_i │
│ d, period_start_timestamp, period_end_timestamp, team_signature, season_id) │
│ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ ` │
│ │
│ result, err := tx.Exec(runQuery, │
│ @@ -187,6 +213,7 @@ func (ds *DatabaseService) InsertLeaderboardData(leaderboard * │
│ blizzard.Leaderboa │
│ leaderboard.PeriodStartTimestamp, │
│ leaderboard.PeriodEndTimestamp, │
│ teamSignature, │
│ + runSeasonID, │
│ ) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to insert run: %w", err) │
│ @@ -718,11 +745,23 @@ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.T │
│ x, leaderboard *bliz │
│ │
│ teamSignature := utils.ComputeTeamSignature(playerIDs) │
│ │
│ + // determine season for this run │
│ + var runSeasonID *int │
│ + if run.CompletedTimestamp > 0 { │
│ + sid, err := ds.DetermineSeasonForRun(realmInfo.Region, run.CompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to determine season for run: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + runSeasonID = &sid │
│ + } │
│ + } │
│ + │
│ // insert run │
│ runQuery := ` │
│ INSERT OR IGNORE INTO challenge_runs │
│ - (duration, completed_timestamp, keystone_level, dungeon_id, realm_id, period_i │
│ d, period_start_timestamp, period_end_timestamp, team_signature) │
│ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ + (duration, completed_timestamp, keystone_level, dungeon_id, realm_id, period_i │
│ d, period_start_timestamp, period_end_timestamp, team_signature, season_id) │
│ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ ` │
│ │
│ result, err := tx.Exec(runQuery, │
│ @@ -735,6 +774,7 @@ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.Tx, │
│ leaderboard *bliz │
│ leaderboard.PeriodStartTimestamp, │
│ leaderboard.PeriodEndTimestamp, │
│ teamSignature, │
│ + runSeasonID, │
│ ) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to insert run: %w", err) │
│ @@ -1517,6 +1557,80 @@ func (ds *DatabaseService) GetPeriodsForRegion(region strin │
│ g) ([]int, error) { │
│ return periods, rows.Err() │
│ } │
│ │
│ +// DetermineSeasonForRun determines which season a run belongs to based on │
│ +// completed_timestamp and region. Returns season_number (not id) for the matchin │
│ g season. │
│ +func (ds *DatabaseService) DetermineSeasonForRun(region string, completedTimestam │
│ p int64) (int, error) { │
│ + query := ` │
│ + SELECT season_number │
│ + FROM seasons │
│ + WHERE region = ? │
│ + AND start_timestamp <= ? │
│ + AND (end_timestamp IS NULL OR end_timestamp > ?) │
│ + ORDER BY start_timestamp DESC │
│ + LIMIT 1 │
│ + ` │
│ + var seasonNumber int │
│ + err := ds.db.QueryRow(query, region, completedTimestamp, completedTimestamp).Sca │
│ n(&seasonNumber) │
│ + if err == sql.ErrNoRows { │
│ + return 0, nil │
│ + } │
│ + return seasonNumber, err │
│ +} │
│ + │
│ +// AssignRunsToSeasons assigns season_id to all challenge_runs based on completed │
│ _timestamp │
│ +// and realm region. This is used to backfill existing data. │
│ +func (ds *DatabaseService) AssignRunsToSeasons() error { │
│ + // get all regions │
│ + regions := []string{"us", "eu", "kr", "tw"} │
│ + │
│ + for _, region := range regions { │
│ + fmt.Printf("Assigning runs to seasons for region: %s\n", region) │
│ + │
│ + // update runs for this region │
│ + query := ` │
│ + UPDATE challenge_runs │
│ + SET season_id = ( │
│ + SELECT s.season_number │
│ + FROM seasons s │
│ + JOIN realms r ON r.region = s.region │
│ + WHERE r.id = challenge_runs.realm_id │
│ + AND s.start_timestamp <= challenge_runs.completed_timestamp │
│ + AND (s.end_timestamp IS NULL OR s.end_timestamp > challenge_runs.completed_ │
│ timestamp) │
│ + ORDER BY s.start_timestamp DESC │
│ + LIMIT 1 │
│ + ) │
│ + WHERE realm_id IN ( │
│ + SELECT id FROM realms WHERE region = ? │
│ + ) │
│ + ` │
│ + │
│ + result, err := ds.db.Exec(query, region) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to assign seasons for region %s: %w", region, err) │
│ + } │
│ + │
│ + rowsAffected, err := result.RowsAffected() │
│ + if err != nil { │
│ + return fmt.Errorf("failed to get rows affected for region %s: %w", region, err │
│ ) │
│ + } │
│ + │
│ + fmt.Printf(" Updated %d runs for region %s\n", rowsAffected, region) │
│ + } │
│ + │
│ + // count runs without season assignment │
│ + var orphanedRuns int │
│ + err := ds.db.QueryRow("SELECT COUNT(*) FROM challenge_runs WHERE season_id IS NU │
│ LL").Scan(&orphanedRuns) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to count orphaned runs: %w", err) │
│ + } │
│ + │
│ + if orphanedRuns > 0 { │
│ + fmt.Printf("Warning: %d runs could not be assigned to any season\n", orphanedRu │
│ ns) │
│ + } │
│ + │
│ + return nil │
│ +} │
│ + │
│ // GetRealmPoolIDs returns all realm IDs in a realm pool (parent + all children) │
│ // For a child realm, it returns the parent and all siblings │
│ // For a parent realm, it returns itself and all children │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ternal/database/operations.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ database/operations.go b/nix/pkgs/ookstats/s │
│ rc/internal/database/operations.go │
│ index 08fe0a3..51f547e 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ @@ -148,6 +148,18 @@ func (ds *DatabaseServi │
│ ce) InsertLeaderboardData(leaderboard *blizz │
│ ard.Leaderboa │
│ playersInserted := 0 │
│ allRealms := blizzard.GetAllRealms() │
│ │
│ + // determine season for runs based on comp │
│ leted timestamp │
│ + var seasonID *int │
│ + if leaderboard.LeadingGroups[0].CompletedT │
│ imestamp > 0 { │
│ + sid, err := ds.DetermineSeasonForRun(real │
│ mInfo.Region, leaderboard.LeadingGroups[0].C │
│ ompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to deter │
│ mine season: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + seasonID = &sid │
│ + } │
│ + } │
│ + │
│ // begin transaction │
│ tx, err := ds.db.Begin() │
│ if err != nil { │
│ @@ -170,11 +182,25 @@ func (ds *DatabaseServ │
│ ice) InsertLeaderboardData(leaderboard *bliz │
│ zard.Leaderboa │
│ │
│ teamSignature := utils.ComputeTeamSignatu │
│ re(playerIDs) │
│ │
│ + // determine season for this specific run │
│ if different from first │
│ + runSeasonID := seasonID │
│ + if run.CompletedTimestamp != leaderboard. │
│ LeadingGroups[0].CompletedTimestamp { │
│ + sid, err := ds.DetermineSeasonForRun(rea │
│ lmInfo.Region, run.CompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to dete │
│ rmine season for run: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + runSeasonID = &sid │
│ + } else { │
│ + runSeasonID = nil │
│ + } │
│ + } │
│ + │
│ // insert run with team signature to prev │
│ ent duplicates │
│ runQuery := ` │
│ INSERT OR IGNORE INTO challenge_runs │
│ - (duration, completed_timestamp, keystone │
│ _level, dungeon_id, realm_id, period_id, per │
│ iod_start_timestamp, period_end_timestamp, t │
│ eam_signature) │
│ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ + (duration, completed_timestamp, keystone │
│ _level, dungeon_id, realm_id, period_id, per │
│ iod_start_timestamp, period_end_timestamp, t │
│ eam_signature, season_id) │
│ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ ` │
│ │
│ result, err := tx.Exec(runQuery, │
│ @@ -187,6 +213,7 @@ func (ds *DatabaseServic │
│ e) InsertLeaderboardData(leaderboard *blizza │
│ rd.Leaderboa │
│ leaderboard.PeriodStartTimestamp, │
│ leaderboard.PeriodEndTimestamp, │
│ teamSignature, │
│ + runSeasonID, │
│ ) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to inser │
│ t run: %w", err) │
│ @@ -718,11 +745,23 @@ func (ds *DatabaseServ │
│ ice) insertLeaderboardDataTx(tx *sql.Tx, lea │
│ derboard *bliz │
│ │
│ teamSignature := utils.ComputeTeamSignatu │
│ re(playerIDs) │
│ │
│ + // determine season for this run │
│ + var runSeasonID *int │
│ + if run.CompletedTimestamp > 0 { │
│ + sid, err := ds.DetermineSeasonForRun(rea │
│ lmInfo.Region, run.CompletedTimestamp) │
│ + if err != nil { │
│ + return 0, 0, fmt.Errorf("failed to dete │
│ rmine season for run: %w", err) │
│ + } │
│ + if sid > 0 { │
│ + runSeasonID = &sid │
│ + } │
│ + } │
│ + │
│ // insert run │
│ runQuery := ` │
│ INSERT OR IGNORE INTO challenge_runs │
│ - (duration, completed_timestamp, keystone │
│ _level, dungeon_id, realm_id, period_id, per │
│ iod_start_timestamp, period_end_timestamp, t │
│ eam_signature) │
│ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ + (duration, completed_timestamp, keystone │
│ _level, dungeon_id, realm_id, period_id, per │
│ iod_start_timestamp, period_end_timestamp, t │
│ eam_signature, season_id) │
│ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) │
│ ` │
│ │
│ result, err := tx.Exec(runQuery, │
│ @@ -735,6 +774,7 @@ func (ds *DatabaseServic │
│ e) insertLeaderboardDataTx(tx *sql.Tx, leade │
│ rboard *bliz │
│ leaderboard.PeriodStartTimestamp, │
│ leaderboard.PeriodEndTimestamp, │
│ teamSignature, │
│ + runSeasonID, │
│ ) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to inser │
│ t run: %w", err) │
│ @@ -1517,6 +1557,80 @@ func (ds *DatabaseSer │
│ vice) GetPeriodsForRegion(region string) ([] │
│ int, error) { │
│ return periods, rows.Err() │
│ } │
│ │
│ +// DetermineSeasonForRun determines which s │
│ eason a run belongs to based on │
│ +// completed_timestamp and region. Returns │
│ season_number (not id) for the matching seas │
│ on. │
│ +func (ds *DatabaseService) DetermineSeasonF │
│ orRun(region string, completedTimestamp int6 │
│ 4) (int, error) { │
│ + query := ` │
│ + SELECT season_number │
│ + FROM seasons │
│ + WHERE region = ? │
│ + AND start_timestamp <= ? │
│ + AND (end_timestamp IS NULL OR end_times │
│ tamp > ?) │
│ + ORDER BY start_timestamp DESC │
│ + LIMIT 1 │
│ + ` │
│ + var seasonNumber int │
│ + err := ds.db.QueryRow(query, region, compl │
│ etedTimestamp, completedTimestamp).Scan(&sea │
│ sonNumber) │
│ + if err == sql.ErrNoRows { │
│ + return 0, nil │
│ + } │
│ + return seasonNumber, err │
│ +} │
│ + │
│ +// AssignRunsToSeasons assigns season_id to │
│ all challenge_runs based on completed_times │
│ tamp │
│ +// and realm region. This is used to backfi │
│ ll existing data. │
│ +func (ds *DatabaseService) AssignRunsToSeas │
│ ons() error { │
│ + // get all regions │
│ + regions := []string{"us", "eu", "kr", "tw" │
│ } │
│ + │
│ + for _, region := range regions { │
│ + fmt.Printf("Assigning runs to seasons for │
│ region: %s\n", region) │
│ + │
│ + // update runs for this region │
│ + query := ` │
│ + UPDATE challenge_runs │
│ + SET season_id = ( │
│ + SELECT s.season_number │
│ + FROM seasons s │
│ + JOIN realms r ON r.region = s.region │
│ + WHERE r.id = challenge_runs.realm_id │
│ + AND s.start_timestamp <= challenge_ru │
│ ns.completed_timestamp │
│ + AND (s.end_timestamp IS NULL OR s.end │
│ _timestamp > challenge_runs.completed_timest │
│ amp) │
│ + ORDER BY s.start_timestamp DESC │
│ + LIMIT 1 │
│ + ) │
│ + WHERE realm_id IN ( │
│ + SELECT id FROM realms WHERE region = ? │
│ + ) │
│ + ` │
│ + │
│ + result, err := ds.db.Exec(query, region) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to assign seas │
│ ons for region %s: %w", region, err) │
│ + } │
│ + │
│ + rowsAffected, err := result.RowsAffected( │
│ ) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to get rows af │
│ fected for region %s: %w", region, err) │
│ + } │
│ + │
│ + fmt.Printf(" Updated %d runs for region │
│ %s\n", rowsAffected, region) │
│ + } │
│ + │
│ + // count runs without season assignment │
│ + var orphanedRuns int │
│ + err := ds.db.QueryRow("SELECT COUNT(*) FRO │
│ M challenge_runs WHERE season_id IS NULL").S │
│ can(&orphanedRuns) │
│ + if err != nil { │
│ + return fmt.Errorf("failed to count orphan │
│ ed runs: %w", err) │
│ + } │
│ + │
│ + if orphanedRuns > 0 { │
│ + fmt.Printf("Warning: %d runs could not be │
│ assigned to any season\n", orphanedRuns) │
│ + } │
│ + │
│ + return nil │
│ +} │
│ + │
│ // GetRealmPoolIDs returns all realm IDs in │
│ a realm pool (parent + all children) │
│ // For a child realm, it returns the parent │
│ and all siblings │
│ // For a parent realm, it returns itself an │
│ d all children │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/generator/leaderboards.go ─────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/generator/leaderboards.go b/nix/pkgs/o │
│ okstats/src/internal/generator/leaderboards.go │
│ index 274bd09..70dd0a6 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/generator/leaderboards.go │
│ +++ b/nix/pkgs/ookstats/src/internal/generator/leaderboards.go │
│ @@ -210,8 +210,8 @@ func generateGlobalLeaderboard(db *sql.DB, out string, d dunge │
│ onInfo, seasonID, │
│ err := db.QueryRow(` │
│ SELECT COUNT(DISTINCT team_signature) │
│ FROM challenge_runs cr │
│ - LEFT JOIN period_seasons ps ON cr.period_id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND COALESCE(ps.season_id, 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND cr.season_id = ? │
│ `, d.ID, seasonID).Scan(&total) │
│ if err != nil { │
│ return fmt.Errorf("global count: %w", err) │
│ @@ -245,8 +245,8 @@ func generateRegionalLeaderboard(db *sql.DB, out, region strin │
│ g, d dungeonInfo, │
│ SELECT team_signature │
│ FROM challenge_runs cr │
│ JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN period_seasons ps ON cr.period_id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND r.region = ? AND COALESCE(ps.season_id, 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND r.region = ? AND cr.season_id = ? │
│ GROUP BY team_signature │
│ ) x │
│ `, d.ID, region, seasonID).Scan(&total) │
│ @@ -288,8 +288,8 @@ func generateRealmLeaderboard(db *sql.DB, out, region, realmSl │
│ ug string, d dunge │
│ SELECT team_signature │
│ FROM challenge_runs cr │
│ JOIN realms rr ON cr.realm_id = rr.id │
│ - LEFT JOIN period_seasons ps ON cr.period_id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND rr.region = ? AND rr.slug = ? AND COALESCE(ps.seas │
│ on_id, 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND rr.region = ? AND rr.slug = ? AND cr.season_id = ? │
│ GROUP BY team_signature │
│ ) x │
│ `, d.ID, region, realmSlug, seasonID).Scan(&total); err != nil { │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...nal/generator/leaderboards.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ generator/leaderboards.go b/nix/pkgs/ookstat │
│ s/src/internal/generator/leaderboards.go │
│ index 274bd09..70dd0a6 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/generat │
│ or/leaderboards.go │
│ +++ b/nix/pkgs/ookstats/src/internal/generat │
│ or/leaderboards.go │
│ @@ -210,8 +210,8 @@ func generateGlobalLeade │
│ rboard(db *sql.DB, out string, d dungeonInfo │
│ , seasonID, │
│ err := db.QueryRow(` │
│ SELECT COUNT(DISTINCT team_signature) │
│ FROM challenge_runs cr │
│ - LEFT JOIN period_seasons ps ON cr.period_ │
│ id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND COALESCE(ps.s │
│ eason_id, 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND cr.season_id │
│ = ? │
│ `, d.ID, seasonID).Scan(&total) │
│ if err != nil { │
│ return fmt.Errorf("global count: %w", err │
│ ) │
│ @@ -245,8 +245,8 @@ func generateRegionalLea │
│ derboard(db *sql.DB, out, region string, d d │
│ ungeonInfo, │
│ SELECT team_signature │
│ FROM challenge_runs cr │
│ JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN period_seasons ps ON cr.period │
│ _id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND r.region = ? │
│ AND COALESCE(ps.season_id, 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND r.region = ? │
│ AND cr.season_id = ? │
│ GROUP BY team_signature │
│ ) x │
│ `, d.ID, region, seasonID).Scan(&total) │
│ @@ -288,8 +288,8 @@ func generateRealmLeader │
│ board(db *sql.DB, out, region, realmSlug str │
│ ing, d dunge │
│ SELECT team_signature │
│ FROM challenge_runs cr │
│ JOIN realms rr ON cr.realm_id = rr.id │
│ - LEFT JOIN period_seasons ps ON cr.period │
│ _id = ps.period_id │
│ - WHERE cr.dungeon_id = ? AND rr.region = │
│ ? AND rr.slug = ? AND COALESCE(ps.season_id, │
│ 1) = ? │
│ + │
│ + WHERE cr.dungeon_id = ? AND rr.region = │
│ ? AND rr.slug = ? AND cr.season_id = ? │
│ GROUP BY team_signature │
│ ) x │
│ `, d.ID, region, realmSlug, seasonID).Scan │
│ (&total); err != nil { │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/loader/runs.go ────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/loader/runs.go b/nix/pkgs/ookstats/src │
│ /internal/loader/runs.go │
│ index a745fc8..c13376d 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/loader/runs.go │
│ +++ b/nix/pkgs/ookstats/src/internal/loader/runs.go │
│ @@ -211,7 +211,7 @@ func LoadCanonicalRuns(db *sql.DB, dungeonID int, region strin │
│ g, realmSlug strin │
│ args = append(args, realmSlug) │
│ } │
│ // Filter by season │
│ - where += " AND COALESCE(ps.season_id, 1) = ?" │
│ + where += " AND cr.season_id = ?" │
│ args = append(args, seasonID) │
│ │
│ q := fmt.Sprintf(` │
│ @@ -220,7 +220,6 @@ func LoadCanonicalRuns(db *sql.DB, dungeonID int, region strin │
│ g, realmSlug strin │
│ ROW_NUMBER() OVER (PARTITION BY cr.team_signature ORDER BY cr.dura │
│ tion ASC, cr.completed_timestamp ASC, cr.id ASC) AS rn │
│ FROM challenge_runs cr │
│ JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN period_seasons ps ON cr.period_id = ps.period_id │
│ %s │
│ ) │
│ SELECT id FROM ranked WHERE rn = 1 │
│ @@ -282,8 +281,7 @@ func LoadCanonicalRuns(db *sql.DB, dungeonID int, region strin │
│ g, realmSlug strin │
│ ROW_NUMBER() OVER (PARTITION BY cr.dungeon_id, cr.realm_id ORDER B │
│ Y cr.duration ASC, cr.completed_timestamp ASC, cr.id ASC) as realm_ranking, │
│ COUNT(*) OVER (PARTITION BY cr.dungeon_id, cr.realm_id) as total_i │
│ n_realm_dungeon │
│ FROM challenge_runs cr │
│ - LEFT JOIN period_seasons ps ON cr.period_id = ps.period_id │
│ - WHERE COALESCE(ps.season_id, 1) = ? │
│ + WHERE cr.season_id = ? │
│ ) │
│ SELECT cr.id, cr.duration, cr.completed_timestamp, cr.keystone_level, │
│ d.name, rr.name, rr.region, │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...s/src/internal/loader/runs.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ loader/runs.go b/nix/pkgs/ookstats/src/inter │
│ nal/loader/runs.go │
│ index a745fc8..c13376d 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/loader/ │
│ runs.go │
│ +++ b/nix/pkgs/ookstats/src/internal/loader/ │
│ runs.go │
│ @@ -211,7 +211,7 @@ func LoadCanonicalRuns(d │
│ b *sql.DB, dungeonID int, region string, rea │
│ lmSlug strin │
│ args = append(args, realmSlug) │
│ } │
│ // Filter by season │
│ - where += " AND COALESCE(ps.season_id, 1) = │
│ ?" │
│ + where += " AND cr.season_id = ?" │
│ args = append(args, seasonID) │
│ │
│ q := fmt.Sprintf(` │
│ @@ -220,7 +220,6 @@ func LoadCanonicalRuns(d │
│ b *sql.DB, dungeonID int, region string, rea │
│ lmSlug strin │
│ ROW_NUMBER() OVER (PARTITION │
│ BY cr.team_signature ORDER BY cr.duration A │
│ SC, cr.completed_timestamp ASC, cr.id ASC) A │
│ S rn │
│ FROM challenge_runs cr │
│ JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN period_seasons ps ON cr.p │
│ eriod_id = ps.period_id │
│ %s │
│ ) │
│ SELECT id FROM ranked WHERE rn = 1 │
│ @@ -282,8 +281,7 @@ func LoadCanonicalRuns(d │
│ b *sql.DB, dungeonID int, region string, rea │
│ lmSlug strin │
│ ROW_NUMBER() OVER (PARTITION │
│ BY cr.dungeon_id, cr.realm_id ORDER BY cr.d │
│ uration ASC, cr.completed_timestamp ASC, cr. │
│ id ASC) as realm_ranking, │
│ COUNT(*) OVER (PARTITION BY │
│ cr.dungeon_id, cr.realm_id) as total_in_real │
│ m_dungeon │
│ FROM challenge_runs cr │
│ - LEFT JOIN period_seasons ps ON cr.p │
│ eriod_id = ps.period_id │
│ - WHERE COALESCE(ps.season_id, 1) = ? │
│ + WHERE cr.season_id = ? │
│ ) │
│ SELECT cr.id, cr.duration, cr.complet │
│ ed_timestamp, cr.keystone_level, │
│ d.name, rr.name, rr.region, │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/pipeline/process.go ───────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/pipeline/process.go b/nix/pkgs/ookstat │
│ s/src/internal/pipeline/process.go │
│ index f9cd01f..89a7455 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/pipeline/process.go │
│ +++ b/nix/pkgs/ookstats/src/internal/pipeline/process.go │
│ @@ -1,5 +1,8 @@ │
│ package pipeline │
│ │
│ +// season assignment has been migrated to use cr.season_id directly instead of pe │
│ riod_seasons lookups. │
│ +// all queries now use timestamp-based season assignment from the challenge_runs │
│ table. │
│ + │
│ import ( │
│ "database/sql" │
│ "fmt" │
│ @@ -171,7 +174,7 @@ func createPlayerAggregations(tx *sql.Tx) (int, error) { │
│ cr.dungeon_id, │
│ cr.id as run_id, │
│ cr.duration, │
│ - COALESCE(s_agg.season_number, 1) as season_id, │
│ + cr.season_id, │
│ cr.completed_timestamp, │
│ rr_gf.ranking as global_ranking_filtered, │
│ rr_rf.ranking as regional_ranking_filtered, │
│ @@ -181,41 +184,29 @@ func createPlayerAggregations(tx *sql.Tx) (int, error) { │
│ rr_lf.percentile_bracket as realm_percentile_bracket │
│ FROM run_members rm │
│ INNER JOIN challenge_runs cr ON rm.run_id = cr.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ INNER JOIN ( │
│ SELECT │
│ rm2.player_id, │
│ cr2.dungeon_id, │
│ - COALESCE(s2_agg.season_number, 1) as season_id, │
│ + cr2.season_id, │
│ MIN(cr2.duration) as best_duration │
│ FROM run_members rm2 │
│ INNER JOIN challenge_runs cr2 ON rm2.run_id = cr2.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s2_agg ON cr2.period_id = s2_agg.period_id │
│ - GROUP BY rm2.player_id, cr2.dungeon_id, COALESCE(s2_agg.season_number, 1) │
│ + GROUP BY rm2.player_id, cr2.dungeon_id, cr2.season_id │
│ ) best_times ON rm.player_id = best_times.player_id │
│ AND cr.dungeon_id = best_times.dungeon_id │
│ - AND COALESCE(s_agg.season_number, 1) = best_times.season_id │
│ + AND cr.season_id = best_times.season_id │
│ AND cr.duration = best_times.best_duration │
│ LEFT JOIN run_rankings rr_gf ON cr.id = rr_gf.run_id │
│ AND rr_gf.ranking_type = 'global' AND rr_gf.ranking_scope = 'filtered' │
│ - AND rr_gf.season_id = COALESCE(s_agg.season_number, 1) │
│ + AND rr_gf.season_id = cr.season_id │
│ LEFT JOIN run_rankings rr_rf ON cr.id = rr_rf.run_id │
│ AND rr_rf.ranking_type = 'regional' AND rr_rf.ranking_scope = 'filtered' │
│ - AND rr_rf.season_id = COALESCE(s_agg.season_number, 1) │
│ + AND rr_rf.season_id = cr.season_id │
│ LEFT JOIN run_rankings rr_lf ON cr.id = rr_lf.run_id │
│ AND rr_lf.ranking_type = 'realm' AND rr_lf.ranking_scope = 'filtered' │
│ - AND rr_lf.season_id = COALESCE(s_agg.season_number, 1) │
│ - GROUP BY rm.player_id, cr.dungeon_id, COALESCE(s_agg.season_number, 1) │
│ + AND rr_lf.season_id = cr.season_id │
│ + GROUP BY rm.player_id, cr.dungeon_id, cr.season_id │
│ HAVING cr.id = MIN(cr.id) │
│ `) │
│ if err != nil { │
│ @@ -250,16 +241,10 @@ func createPlayerAggregations(tx *sql.Tx) (int, error) { │
│ FROM players p │
│ INNER JOIN player_best_runs pbr ON p.id = pbr.player_id │
│ INNER JOIN ( │
│ - SELECT rm.player_id, COALESCE(s_agg.season_number, 1) as season_id, COUNT(*) a │
│ s run_count │
│ + SELECT rm.player_id, cr.season_id, COUNT(*) as run_count │
│ FROM run_members rm │
│ INNER JOIN challenge_runs cr ON rm.run_id = cr.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ - GROUP BY rm.player_id, COALESCE(s_agg.season_number, 1) │
│ + GROUP BY rm.player_id, cr.season_id │
│ ) season_runs ON p.id = season_runs.player_id AND pbr.season_id = season_runs.s │
│ eason_id │
│ GROUP BY p.id, pbr.season_id, p.name, p.realm_id, season_runs.run_count │
│ `, currentTime) │
│ @@ -855,16 +840,10 @@ func computeGlobalRankings(tx *sql.Tx) error { │
│ cr.dungeon_id, │
│ 'global' as ranking_type, │
│ 'all' as ranking_scope, │
│ - ROW_NUMBER() OVER (PARTITION BY cr.dungeon_id, COALESCE(s_agg.season_number, 1 │
│ ) ORDER BY cr.duration ASC, cr.completed_timestamp ASC) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as season_id, │
│ + ROW_NUMBER() OVER (PARTITION BY cr.dungeon_id, cr.season_id ORDER BY cr.durati │
│ on ASC, cr.completed_timestamp ASC) as ranking, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ `, currentTime) │
│ if err != nil { │
│ return err │
│ @@ -930,14 +909,8 @@ func computeGlobalRankings(tx *sql.Tx) error { │
│ │
│ // Get all seasons (including fallback season 1 for unmapped periods) │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_number, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_id │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ `) │
│ if err != nil { │
│ return err │
│ @@ -962,13 +935,7 @@ func computeGlobalRankings(tx *sql.Tx) error { │
│ cr.team_signature, │
│ MIN(cr.duration) as best_duration │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ - WHERE cr.dungeon_id = ? AND COALESCE(s_agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -978,15 +945,9 @@ func computeGlobalRankings(tx *sql.Tx) error { │
│ cr.completed_timestamp, │
│ ROW_NUMBER() OVER (ORDER BY cr.duration ASC, cr.completed_timestamp ASC) as │
│ filtered_rank │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ INNER JOIN best_team_runs btr ON cr.team_signature = btr.team_signature │
│ AND cr.duration = btr.best_duration │
│ - WHERE cr.dungeon_id = ? AND COALESCE(s_agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
│ @@ -1089,17 +1050,11 @@ func computeRegionalRankings(tx *sql.Tx) error { │
│ cr.dungeon_id, │
│ 'regional' as ranking_type, │
│ ? as ranking_scope, │
│ - ROW_NUMBER() OVER (PARTITION BY cr.dungeon_id, COALESCE(s_agg.season_number, │
│ 1) ORDER BY cr.duration ASC, cr.completed_timestamp ASC) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as season_id, │
│ + ROW_NUMBER() OVER (PARTITION BY cr.dungeon_id, cr.season_id ORDER BY cr.durat │
│ ion ASC, cr.completed_timestamp ASC) as ranking, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ WHERE r.region = ? │
│ `, region, currentTime, region) │
│ │
│ @@ -1167,15 +1122,9 @@ func computeRegionalRankings(tx *sql.Tx) error { │
│ │
│ // Get all seasons for this region │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_number, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_id │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ WHERE r.region = ? │
│ `, region) │
│ if err != nil { │
│ @@ -1203,13 +1152,7 @@ func computeRegionalRankings(tx *sql.Tx) error { │
│ MIN(cr.duration) as best_duration │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ - WHERE cr.dungeon_id = ? AND r.region = ? AND COALESCE(s_agg.season_number, │
│ 1) = ? │
│ + WHERE cr.dungeon_id = ? AND r.region = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -1220,15 +1163,9 @@ func computeRegionalRankings(tx *sql.Tx) error { │
│ ROW_NUMBER() OVER (ORDER BY cr.duration ASC, cr.completed_timestamp ASC) a │
│ s filtered_rank │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ INNER JOIN best_team_runs btr ON cr.team_signature = btr.team_signature │
│ AND cr.duration = btr.best_duration │
│ - WHERE cr.dungeon_id = ? AND r.region = ? AND COALESCE(s_agg.season_number, │
│ 1) = ? │
│ + WHERE cr.dungeon_id = ? AND r.region = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
│ @@ -1348,20 +1285,14 @@ func computeRealmRankings(tx *sql.Tx) error { │
│ 'realm' as ranking_type, │
│ ? as ranking_scope, │
│ ROW_NUMBER() OVER ( │
│ - PARTITION BY cr.dungeon_id, COALESCE(s_agg.season_number, 1) │
│ + PARTITION BY cr.dungeon_id, cr.season_id │
│ ORDER BY cr.duration ASC, cr.completed_timestamp ASC │
│ ) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as season_id, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ LEFT JOIN realms parent_r ON r.parent_realm_slug = parent_r.slug AND r.region │
│ = parent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ WHERE r.region = ? AND COALESCE(parent_r.slug, r.slug) = ? │
│ `, pool.PoolSlug, currentTime, pool.Region, pool.PoolSlug) │
│ │
│ @@ -1430,16 +1361,10 @@ func computeRealmRankings(tx *sql.Tx) error { │
│ for _, pool := range pools { │
│ // Get seasons for this pool │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_number, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_id │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ LEFT JOIN realms parent_r ON r.parent_realm_slug = parent_r.slug AND r.region │
│ = parent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ WHERE r.region = ? AND COALESCE(parent_r.slug, r.slug) = ? │
│ `, pool.Region, pool.PoolSlug) │
│ if err != nil { │
│ @@ -1468,16 +1393,10 @@ func computeRealmRankings(tx *sql.Tx) error { │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ LEFT JOIN realms parent_r ON r.parent_realm_slug = parent_r.slug AND r.regi │
│ on = parent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ WHERE cr.dungeon_id = ? │
│ AND r.region = ? │
│ AND COALESCE(parent_r.slug, r.slug) = ? │
│ - AND COALESCE(s_agg.season_number, 1) = ? │
│ + AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -1489,18 +1408,12 @@ func computeRealmRankings(tx *sql.Tx) error { │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.id │
│ LEFT JOIN realms parent_r ON r.parent_realm_slug = parent_r.slug AND r.regi │
│ on = parent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ INNER JOIN best_team_runs btr ON cr.team_signature = btr.team_signature │
│ AND cr.duration = btr.best_duration │
│ WHERE cr.dungeon_id = ? │
│ AND r.region = ? │
│ AND COALESCE(parent_r.slug, r.slug) = ? │
│ - AND COALESCE(s_agg.season_number, 1) = ? │
│ + AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../internal/pipeline/process.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ pipeline/process.go b/nix/pkgs/ookstats/src/ │
│ internal/pipeline/process.go │
│ index f9cd01f..89a7455 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/pipelin │
│ e/process.go │
│ +++ b/nix/pkgs/ookstats/src/internal/pipelin │
│ e/process.go │
│ @@ -1,5 +1,8 @@ │
│ package pipeline │
│ │
│ +// season assignment has been migrated to u │
│ se cr.season_id directly instead of period_s │
│ easons lookups. │
│ +// all queries now use timestamp-based seas │
│ on assignment from the challenge_runs table. │
│ + │
│ import ( │
│ "database/sql" │
│ "fmt" │
│ @@ -171,7 +174,7 @@ func createPlayerAggrega │
│ tions(tx *sql.Tx) (int, error) { │
│ cr.dungeon_id, │
│ cr.id as run_id, │
│ cr.duration, │
│ - COALESCE(s_agg.season_number, 1) as seas │
│ on_id, │
│ + cr.season_id, │
│ cr.completed_timestamp, │
│ rr_gf.ranking as global_ranking_filtered │
│ , │
│ rr_rf.ranking as regional_ranking_filter │
│ ed, │
│ @@ -181,41 +184,29 @@ func createPlayerAggre │
│ gations(tx *sql.Tx) (int, error) { │
│ rr_lf.percentile_bracket as realm_percen │
│ tile_bracket │
│ FROM run_members rm │
│ INNER JOIN challenge_runs cr ON rm.run_id │
│ = cr.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number │
│ ) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ INNER JOIN ( │
│ SELECT │
│ rm2.player_id, │
│ cr2.dungeon_id, │
│ - COALESCE(s2_agg.season_number, 1) as se │
│ ason_id, │
│ + cr2.season_id, │
│ MIN(cr2.duration) as best_duration │
│ FROM run_members rm2 │
│ INNER JOIN challenge_runs cr2 ON rm2.run │
│ _id = cr2.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s2_agg ON cr2.period_id = s2_agg.perio │
│ d_id │
│ - GROUP BY rm2.player_id, cr2.dungeon_id, │
│ COALESCE(s2_agg.season_number, 1) │
│ + GROUP BY rm2.player_id, cr2.dungeon_id, │
│ cr2.season_id │
│ ) best_times ON rm.player_id = best_times │
│ .player_id │
│ AND cr.dungeon_id = best_times.dungeo │
│ n_id │
│ - AND COALESCE(s_agg.season_number, 1) │
│ = best_times.season_id │
│ + AND cr.season_id = best_times.season_ │
│ id │
│ AND cr.duration = best_times.best_dur │
│ ation │
│ LEFT JOIN run_rankings rr_gf ON cr.id = r │
│ r_gf.run_id │
│ AND rr_gf.ranking_type = 'global' AND rr │
│ _gf.ranking_scope = 'filtered' │
│ - AND rr_gf.season_id = COALESCE(s_agg.sea │
│ son_number, 1) │
│ + AND rr_gf.season_id = cr.season_id │
│ LEFT JOIN run_rankings rr_rf ON cr.id = r │
│ r_rf.run_id │
│ AND rr_rf.ranking_type = 'regional' AND │
│ rr_rf.ranking_scope = 'filtered' │
│ - AND rr_rf.season_id = COALESCE(s_agg.sea │
│ son_number, 1) │
│ + AND rr_rf.season_id = cr.season_id │
│ LEFT JOIN run_rankings rr_lf ON cr.id = r │
│ r_lf.run_id │
│ AND rr_lf.ranking_type = 'realm' AND rr_ │
│ lf.ranking_scope = 'filtered' │
│ - AND rr_lf.season_id = COALESCE(s_agg.sea │
│ son_number, 1) │
│ - GROUP BY rm.player_id, cr.dungeon_id, COA │
│ LESCE(s_agg.season_number, 1) │
│ + AND rr_lf.season_id = cr.season_id │
│ + GROUP BY rm.player_id, cr.dungeon_id, cr. │
│ season_id │
│ HAVING cr.id = MIN(cr.id) │
│ `) │
│ if err != nil { │
│ @@ -250,16 +241,10 @@ func createPlayerAggre │
│ gations(tx *sql.Tx) (int, error) { │
│ FROM players p │
│ INNER JOIN player_best_runs pbr ON p.id = │
│ pbr.player_id │
│ INNER JOIN ( │
│ - SELECT rm.player_id, COALESCE(s_agg.seas │
│ on_number, 1) as season_id, COUNT(*) as run_ │
│ count │
│ + SELECT rm.player_id, cr.season_id, COUNT │
│ (*) as run_count │
│ FROM run_members rm │
│ INNER JOIN challenge_runs cr ON rm.run_i │
│ d = cr.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_i │
│ d │
│ - GROUP BY rm.player_id, COALESCE(s_agg.se │
│ ason_number, 1) │
│ + GROUP BY rm.player_id, cr.season_id │
│ ) season_runs ON p.id = season_runs.playe │
│ r_id AND pbr.season_id = season_runs.season_ │
│ id │
│ GROUP BY p.id, pbr.season_id, p.name, p.r │
│ ealm_id, season_runs.run_count │
│ `, currentTime) │
│ @@ -855,16 +840,10 @@ func computeGlobalRank │
│ ings(tx *sql.Tx) error { │
│ cr.dungeon_id, │
│ 'global' as ranking_type, │
│ 'all' as ranking_scope, │
│ - ROW_NUMBER() OVER (PARTITION BY cr.dunge │
│ on_id, COALESCE(s_agg.season_number, 1) ORDE │
│ R BY cr.duration ASC, cr.completed_timestamp │
│ ASC) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as seas │
│ on_id, │
│ + ROW_NUMBER() OVER (PARTITION BY cr.dunge │
│ on_id, cr.season_id ORDER BY cr.duration ASC │
│ , cr.completed_timestamp ASC) as ranking, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number │
│ ) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ `, currentTime) │
│ if err != nil { │
│ return err │
│ @@ -930,14 +909,8 @@ func computeGlobalRanki │
│ ngs(tx *sql.Tx) error { │
│ │
│ // Get all seasons (including fallback sea │
│ son 1 for unmapped periods) │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_num │
│ ber, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_id │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_number │
│ ) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_id │
│ `) │
│ if err != nil { │
│ return err │
│ @@ -962,13 +935,7 @@ func computeGlobalRanki │
│ ngs(tx *sql.Tx) error { │
│ cr.team_signature, │
│ MIN(cr.duration) as best_duration │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_num │
│ ber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period │
│ _id │
│ - WHERE cr.dungeon_id = ? AND COALESCE(s │
│ _agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND cr.season_ │
│ id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -978,15 +945,9 @@ func computeGlobalRanki │
│ ngs(tx *sql.Tx) error { │
│ cr.completed_timestamp, │
│ ROW_NUMBER() OVER (ORDER BY cr.durati │
│ on ASC, cr.completed_timestamp ASC) as filte │
│ red_rank │
│ FROM challenge_runs cr │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_num │
│ ber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period │
│ _id │
│ INNER JOIN best_team_runs btr ON cr.te │
│ am_signature = btr.team_signature │
│ AND cr.duration = btr.best_dur │
│ ation │
│ - WHERE cr.dungeon_id = ? AND COALESCE(s │
│ _agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND cr.season_ │
│ id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
│ @@ -1089,17 +1050,11 @@ func computeRegional │
│ Rankings(tx *sql.Tx) error { │
│ cr.dungeon_id, │
│ 'regional' as ranking_type, │
│ ? as ranking_scope, │
│ - ROW_NUMBER() OVER (PARTITION BY cr.dung │
│ eon_id, COALESCE(s_agg.season_number, 1) ORD │
│ ER BY cr.duration ASC, cr.completed_timestam │
│ p ASC) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as sea │
│ son_id, │
│ + ROW_NUMBER() OVER (PARTITION BY cr.dung │
│ eon_id, cr.season_id ORDER BY cr.duration AS │
│ C, cr.completed_timestamp ASC) as ranking, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.i │
│ d │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_i │
│ d │
│ WHERE r.region = ? │
│ `, region, currentTime, region) │
│ │
│ @@ -1167,15 +1122,9 @@ func computeRegionalR │
│ ankings(tx *sql.Tx) error { │
│ │
│ // Get all seasons for this region │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_nu │
│ mber, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_i │
│ d │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.i │
│ d │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_i │
│ d │
│ WHERE r.region = ? │
│ `, region) │
│ if err != nil { │
│ @@ -1203,13 +1152,7 @@ func computeRegionalR │
│ ankings(tx *sql.Tx) error { │
│ MIN(cr.duration) as best_duration │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = │
│ r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_nu │
│ mber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.i │
│ d │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.perio │
│ d_id │
│ - WHERE cr.dungeon_id = ? AND r.region │
│ = ? AND COALESCE(s_agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND r.region │
│ = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -1220,15 +1163,9 @@ func computeRegionalR │
│ ankings(tx *sql.Tx) error { │
│ ROW_NUMBER() OVER (ORDER BY cr.durat │
│ ion ASC, cr.completed_timestamp ASC) as filt │
│ ered_rank │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = │
│ r.id │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_nu │
│ mber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.i │
│ d │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.perio │
│ d_id │
│ INNER JOIN best_team_runs btr ON cr.t │
│ eam_signature = btr.team_signature │
│ AND cr.duration = btr.best_du │
│ ration │
│ - WHERE cr.dungeon_id = ? AND r.region │
│ = ? AND COALESCE(s_agg.season_number, 1) = ? │
│ + WHERE cr.dungeon_id = ? AND r.region │
│ = ? AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
│ @@ -1348,20 +1285,14 @@ func computeRealmRan │
│ kings(tx *sql.Tx) error { │
│ 'realm' as ranking_type, │
│ ? as ranking_scope, │
│ ROW_NUMBER() OVER ( │
│ - PARTITION BY cr.dungeon_id, COALESCE(s │
│ _agg.season_number, 1) │
│ + PARTITION BY cr.dungeon_id, cr.season_ │
│ id │
│ ORDER BY cr.duration ASC, cr.completed │
│ _timestamp ASC │
│ ) as ranking, │
│ - COALESCE(s_agg.season_number, 1) as sea │
│ son_id, │
│ + cr.season_id as season_id, │
│ ? as computed_at │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.i │
│ d │
│ LEFT JOIN realms parent_r ON r.parent_re │
│ alm_slug = parent_r.slug AND r.region = pare │
│ nt_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_i │
│ d │
│ WHERE r.region = ? AND COALESCE(parent_r │
│ .slug, r.slug) = ? │
│ `, pool.PoolSlug, currentTime, pool.Regio │
│ n, pool.PoolSlug) │
│ │
│ @@ -1430,16 +1361,10 @@ func computeRealmRan │
│ kings(tx *sql.Tx) error { │
│ for _, pool := range pools { │
│ // Get seasons for this pool │
│ seasonRows, err := tx.Query(` │
│ - SELECT DISTINCT COALESCE(s_agg.season_nu │
│ mber, 1) as season_id │
│ + SELECT DISTINCT cr.season_id as season_i │
│ d │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = r.i │
│ d │
│ LEFT JOIN realms parent_r ON r.parent_re │
│ alm_slug = parent_r.slug AND r.region = pare │
│ nt_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_numbe │
│ r) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.id │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.period_i │
│ d │
│ WHERE r.region = ? AND COALESCE(parent_r │
│ .slug, r.slug) = ? │
│ `, pool.Region, pool.PoolSlug) │
│ if err != nil { │
│ @@ -1468,16 +1393,10 @@ func computeRealmRan │
│ kings(tx *sql.Tx) error { │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = │
│ r.id │
│ LEFT JOIN realms parent_r ON r.parent │
│ _realm_slug = parent_r.slug AND r.region = p │
│ arent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_nu │
│ mber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.i │
│ d │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.perio │
│ d_id │
│ WHERE cr.dungeon_id = ? │
│ AND r.region = ? │
│ AND COALESCE(parent_r.slug, r.slug) │
│ = ? │
│ - AND COALESCE(s_agg.season_number, 1) │
│ = ? │
│ + AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ ), │
│ filtered_runs AS ( │
│ @@ -1489,18 +1408,12 @@ func computeRealmRan │
│ kings(tx *sql.Tx) error { │
│ FROM challenge_runs cr │
│ INNER JOIN realms r ON cr.realm_id = │
│ r.id │
│ LEFT JOIN realms parent_r ON r.parent │
│ _realm_slug = parent_r.slug AND r.region = p │
│ arent_r.region │
│ - LEFT JOIN ( │
│ - SELECT ps.period_id, MIN(s.season_nu │
│ mber) as season_number │
│ - FROM period_seasons ps │
│ - JOIN seasons s ON ps.season_id = s.i │
│ d │
│ - GROUP BY ps.period_id │
│ - ) s_agg ON cr.period_id = s_agg.perio │
│ d_id │
│ INNER JOIN best_team_runs btr ON cr.t │
│ eam_signature = btr.team_signature │
│ AND cr.duration = btr.best_du │
│ ration │
│ WHERE cr.dungeon_id = ? │
│ AND r.region = ? │
│ AND COALESCE(parent_r.slug, r.slug) │
│ = ? │
│ - AND COALESCE(s_agg.season_number, 1) │
│ = ? │
│ + AND cr.season_id = ? │
│ GROUP BY cr.team_signature │
│ HAVING cr.id = MIN(cr.id) │
│ ) │
└──────────────────────────────────────────────┘
┌─ ...nts/Leaderboard/LeaderboardScopeFilter/LeaderboardScopeFilter.astro ───┐
│ diff --git a/web/src/components/Leaderboard/LeaderboardScopeFilter/LeaderboardScop │
│ eFilter.astro b/web/src/components/Leaderboard/LeaderboardScopeFilter/LeaderboardS │
│ copeFilter.astro │
│ index f1877ea..a8eecff 100644 │
│ --- a/web/src/components/Leaderboard/LeaderboardScopeFilter/LeaderboardScopeFilter │
│ .astro │
│ +++ b/web/src/components/Leaderboard/LeaderboardScopeFilter/LeaderboardScopeFilter │
│ .astro │
│ @@ -21,6 +21,25 @@ const { │
│ currentClass = "all", │
│ } = Astro.props; │
│ │
│ +// Fetch available seasons from API │
│ +let seasonOptions: { id: number; name: string }[] = []; │
│ +try { │
│ + const response = await fetch( │
│ + `${Astro.url.origin}/api/leaderboard/season/index.json`, │
│ + ); │
│ + if (response.ok) { │
│ + const data = await response.json(); │
│ + seasonOptions = data.data.map((season: any) => ({ │
│ + id: season.id, │
│ + name: season.name || `Season ${season.id}`, │
│ + })); │
│ + } │
│ +} catch (error) { │
│ + console.error("Failed to fetch seasons:", error); │
│ + // Fallback to current season if API fails │
│ + seasonOptions = [{ id: currentSeason, name: `Season ${currentSeason}` }]; │
│ +} │
│ + │
│ // Scope options: Global + individual regions │
│ const scopeOptions = [ │
│ { value: "global", label: "Global" }, │
│ @@ -82,7 +101,20 @@ function buildUrl(region: string, realm: string = ""): string │
│ { │
│ <div class="filter-group"> │
│ <label for="season-filter">Season</label> │
│ <select id="season-filter" data-param="season"> │
│ - <option value="1" selected={currentSeason === 1}>Season 1</option> │
│ + { │
│ + seasonOptions.map((season) => ( │
│ + <option │
│ + value={season.id} │
│ + selected={season.id === currentSeason} │
│ + data-url={buildUrl(currentRegion, currentRealm).replace( │
│ + `season${currentSeason}`, │
│ + `season${season.id}`, │
│ + )} │
│ + > │
│ + {season.name} │
│ + </option> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
│ @@ -127,6 +159,9 @@ function buildUrl(region: string, realm: string = ""): string │
│ { │
│ </div> │
│ │
│ <script> │
│ + const seasonSelect = document.getElementById( │
│ + "season-filter", │
│ + ) as HTMLSelectElement; │
│ const scopeSelect = document.getElementById( │
│ "scope-filter", │
│ ) as HTMLSelectElement; │
│ @@ -134,6 +169,18 @@ function buildUrl(region: string, realm: string = ""): string │
│ { │
│ "realm-filter", │
│ ) as HTMLSelectElement | null; │
│ │
│ + // Handle season change - just read the URL from the data attribute │
│ + if (seasonSelect) { │
│ + seasonSelect.addEventListener("change", (e) => { │
│ + const target = e.target as HTMLSelectElement; │
│ + const selectedOption = target.options[target.selectedIndex]; │
│ + const url = selectedOption.dataset.url; │
│ + if (url) { │
│ + window.location.href = url; │
│ + } │
│ + }); │
│ + } │
│ + │
│ // Handle scope change - just read the URL from the data attribute │
│ if (scopeSelect) { │
│ scopeSelect.addEventListener("change", (e) => { │
│ @@ -157,6 +204,4 @@ function buildUrl(region: string, realm: string = ""): string │
│ { │
│ } │
│ }); │
│ } │
│ - │
│ - // TODO: Handle season change (for now, only season 1 exists) │
│ </script> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../LeaderboardScopeFilter.astro ───┐
│ diff --git a/web/src/components/Leaderboard/ │
│ LeaderboardScopeFilter/LeaderboardScopeFilte │
│ r.astro b/web/src/components/Leaderboard/Lea │
│ derboardScopeFilter/LeaderboardScopeFilter.a │
│ stro │
│ index f1877ea..a8eecff 100644 │
│ --- a/web/src/components/Leaderboard/Leaderb │
│ oardScopeFilter/LeaderboardScopeFilter.astro │
│ +++ b/web/src/components/Leaderboard/Leaderb │
│ oardScopeFilter/LeaderboardScopeFilter.astro │
│ @@ -21,6 +21,25 @@ const { │
│ currentClass = "all", │
│ } = Astro.props; │
│ │
│ +// Fetch available seasons from API │
│ +let seasonOptions: { id: number; name: stri │
│ ng }[] = []; │
│ +try { │
│ + const response = await fetch( │
│ + `${Astro.url.origin}/api/leaderboard/se │
│ ason/index.json`, │
│ + ); │
│ + if (response.ok) { │
│ + const data = await response.json(); │
│ + seasonOptions = data.data.map((season: │
│ any) => ({ │
│ + id: season.id, │
│ + name: season.name || `Season ${season │
│ .id}`, │
│ + })); │
│ + } │
│ +} catch (error) { │
│ + console.error("Failed to fetch seasons:", │
│ error); │
│ + // Fallback to current season if API fail │
│ s │
│ + seasonOptions = [{ id: currentSeason, nam │
│ e: `Season ${currentSeason}` }]; │
│ +} │
│ + │
│ // Scope options: Global + individual regio │
│ ns │
│ const scopeOptions = [ │
│ { value: "global", label: "Global" }, │
│ @@ -82,7 +101,20 @@ function buildUrl(region │
│ : string, realm: string = ""): string { │
│ <div class="filter-group"> │
│ <label for="season-filter">Season</labe │
│ l> │
│ <select id="season-filter" data-param=" │
│ season"> │
│ - <option value="1" selected={currentSe │
│ ason === 1}>Season 1</option> │
│ + { │
│ + seasonOptions.map((season) => ( │
│ + <option │
│ + value={season.id} │
│ + selected={season.id === current │
│ Season} │
│ + data-url={buildUrl(currentRegio │
│ n, currentRealm).replace( │
│ + `season${currentSeason}`, │
│ + `season${season.id}`, │
│ + )} │
│ + > │
│ + {season.name} │
│ + </option> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
│ @@ -127,6 +159,9 @@ function buildUrl(region │
│ : string, realm: string = ""): string { │
│ </div> │
│ │
│ <script> │
│ + const seasonSelect = document.getElementB │
│ yId( │
│ + "season-filter", │
│ + ) as HTMLSelectElement; │
│ const scopeSelect = document.getElementBy │
│ Id( │
│ "scope-filter", │
│ ) as HTMLSelectElement; │
│ @@ -134,6 +169,18 @@ function buildUrl(regio │
│ n: string, realm: string = ""): string { │
│ "realm-filter", │
│ ) as HTMLSelectElement | null; │
│ │
│ + // Handle season change - just read the U │
│ RL from the data attribute │
│ + if (seasonSelect) { │
│ + seasonSelect.addEventListener("change", │
│ (e) => { │
│ + const target = e.target as HTMLSelect │
│ Element; │
│ + const selectedOption = target.options │
│ [target.selectedIndex]; │
│ + const url = selectedOption.dataset.ur │
│ l; │
│ + if (url) { │
│ + window.location.href = url; │
│ + } │
│ + }); │
│ + } │
│ + │
│ // Handle scope change - just read the UR │
│ L from the data attribute │
│ if (scopeSelect) { │
│ scopeSelect.addEventListener("change", │
│ (e) => { │
│ @@ -157,6 +204,4 @@ function buildUrl(region │
│ : string, realm: string = ""): string { │
│ } │
│ }); │
│ } │
│ - │
│ - // TODO: Handle season change (for now, o │
│ nly season 1 exists) │
│ </script> │
└──────────────────────────────────────────────┘
┌─ web/src/pages/challenge-mode/index.astro ─────────────────────────────────┐
│ diff --git a/web/src/pages/challenge-mode/index.astro b/web/src/pages/challenge-mo │
│ de/index.astro │
│ index ee71b51..8b7409e 100644 │
│ --- a/web/src/pages/challenge-mode/index.astro │
│ +++ b/web/src/pages/challenge-mode/index.astro │
│ @@ -1,6 +1,27 @@ │
│ --- │
│ -// redirect to default dungeon leaderboard │
│ +// fetch current season and redirect to default dungeon leaderboard │
│ +let currentSeasonId = 1; // fallback │
│ +try { │
│ + const response = await fetch( │
│ + `${Astro.url.origin}/api/leaderboard/season/index.json`, │
│ + ); │
│ + if (response.ok) { │
│ + const data = await response.json(); │
│ + // find the most recent current season (highest ID with is_current: true) │
│ + const currentSeasons = data.data.filter((s: any) => s.is_current); │
│ + const currentSeason = │
│ + currentSeasons.length > 0 │
│ + ? currentSeasons[currentSeasons.length - 1] // last current season (highe │
│ st ID) │
│ + : data.data[data.data.length - 1]; // or just the last season │
│ + if (currentSeason) { │
│ + currentSeasonId = currentSeason.id; │
│ + } │
│ + } │
│ +} catch (error) { │
│ + console.error("Failed to fetch current season:", error); │
│ +} │
│ + │
│ return Astro.redirect( │
│ - "/challenge-mode/season1/global/temple-of-the-jade-serpent", │
│ + `/challenge-mode/season${currentSeasonId}/global/temple-of-the-jade-serpent`, │
│ ); │
│ --- │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...es/challenge-mode/index.astro ───┐
│ diff --git a/web/src/pages/challenge-mode/in │
│ dex.astro b/web/src/pages/challenge-mode/ind │
│ ex.astro │
│ index ee71b51..8b7409e 100644 │
│ --- a/web/src/pages/challenge-mode/index.ast │
│ ro │
│ +++ b/web/src/pages/challenge-mode/index.ast │
│ ro │
│ @@ -1,6 +1,27 @@ │
│ --- │
│ -// redirect to default dungeon leaderboard │
│ +// fetch current season and redirect to def │
│ ault dungeon leaderboard │
│ +let currentSeasonId = 1; // fallback │
│ +try { │
│ + const response = await fetch( │
│ + `${Astro.url.origin}/api/leaderboard/se │
│ ason/index.json`, │
│ + ); │
│ + if (response.ok) { │
│ + const data = await response.json(); │
│ + // find the most recent current season │
│ (highest ID with is_current: true) │
│ + const currentSeasons = data.data.filter │
│ ((s: any) => s.is_current); │
│ + const currentSeason = │
│ + currentSeasons.length > 0 │
│ + ? currentSeasons[currentSeasons.len │
│ gth - 1] // last current season (highest ID) │
│ + : data.data[data.data.length - 1]; │
│ // or just the last season │
│ + if (currentSeason) { │
│ + currentSeasonId = currentSeason.id; │
│ + } │
│ + } │
│ +} catch (error) { │
│ + console.error("Failed to fetch current se │
│ ason:", error); │
│ +} │
│ + │
│ return Astro.redirect( │
│ - "/challenge-mode/season1/global/temple-of │
│ -the-jade-serpent", │
│ + `/challenge-mode/season${currentSeasonId} │
│ /global/temple-of-the-jade-serpent`, │
│ ); │
│ --- │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET