┌─ GO ───────────────────────────────────────────────────────────────────────┐
│ package indexes │
│ │
│ import ( │
│ "database/sql" │
│ "fmt" │
│ "path/filepath" │
│ "sort" │
│ "strings" │
│ │
│ "github.com/charmbracelet/log" │
│ │
│ "ookstats/internal/wow" │
│ "ookstats/internal/writer" │
│ ) │
│ │
│ var classKeys = map[string]int{ │
│ "warrior": 1, │
│ "paladin": 2, │
│ "hunter": 3, │
│ "rogue": 4, │
│ "priest": 5, │
│ "death_knight": 6, │
│ "shaman": 7, │
│ "mage": 8, │
│ "warlock": 9, │
│ "monk": 10, │
│ "druid": 11, │
│ } │
│ │
│ // GeneratePlayersScopeIndex generates top-level players scope index │
│ func GeneratePlayersScopeIndex(outDir string, seasonID int) error { │
│ log.Info("Generating players scope index", "season", seasonID) │
│ │
│ scopes := []PlayerScopeData{ │
│ { │
│ Scope: "global", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/api/leaderboard/season/%d/players/global │
│ /{page}.json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "regional", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/api/leaderboard/season/%d/players/region │
│ al/index.json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "realm", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/ │
│ index.json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "class", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/api/leaderboard/season/%d/players/class/ │
│ index.json", seasonID)}, │
│ }, │
│ }, │
│ } │
│ │
│ index := PlayersScopeIndex{ │
│ Data: scopes, │
│ Metadata: NewIndexMetadata(len(scopes)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "leaderboard", "season", fmt.Sprintf("%d" │
│ , seasonID), "players", "index.json") │
│ if err := writer.WriteJSONFile(outPath, index); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players scope index", "season", seasonID, "path", outPath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRegionalIndex generates regional scope index (lists regions) │
│ func GeneratePlayersRegionalIndex(outDir string, seasonID int) error { │
│ log.Info("Generating players regional index", "season", seasonID) │
│ │
│ regions := []RegionData{ │
│ {Region: "us", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/regional/us │
│ /{page}.json", seasonID)}, │
│ {Region: "eu", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/regional/eu │
│ /{page}.json", seasonID)}, │
│ {Region: "kr", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/regional/kr │
│ /{page}.json", seasonID)}, │
│ {Region: "tw", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/regional/tw │
│ /{page}.json", seasonID)}, │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: regions, │
│ Metadata: NewIndexMetadata(len(regions)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "leaderboard", "season", fmt.Sprintf("%d" │
│ , seasonID), "players", "regional", "index.json") │
│ if err := writer.WriteJSONFile(outPath, index); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players regional index", "season", seasonID, "path", outPath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRealmRegionIndex generates realm scope region list │
│ func GeneratePlayersRealmRegionIndex(outDir string, seasonID int) error { │
│ log.Info("Generating players realm region index", "season", seasonID) │
│ │
│ regions := []RegionData{ │
│ {Region: "us", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/us/in │
│ dex.json", seasonID)}, │
│ {Region: "eu", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/eu/in │
│ dex.json", seasonID)}, │
│ {Region: "kr", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/kr/in │
│ dex.json", seasonID)}, │
│ {Region: "tw", Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/tw/in │
│ dex.json", seasonID)}, │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: regions, │
│ Metadata: NewIndexMetadata(len(regions)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "leaderboard", "season", fmt.Sprintf("%d" │
│ , seasonID), "players", "realm", "index.json") │
│ if err := writer.WriteJSONFile(outPath, index); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players realm region index", "season", seasonID, "path", outP │
│ ath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRealmListIndex generates list of realms for a region (player lea │
│ derboards) │
│ func GeneratePlayersRealmListIndex(db *sql.DB, outDir string, seasonID int, region │
│ string) error { │
│ log.Info("Generating players realm list index", "season", seasonID, "region", reg │
│ ion) │
│ │
│ rows, err := db.Query(`SELECT slug FROM realms WHERE region = ? ORDER BY slug`, r │
│ egion) │
│ if err != nil { │
│ return fmt.Errorf("query realms: %w", err) │
│ } │
│ defer rows.Close() │
│ │
│ var realms []RegionData │
│ for rows.Next() { │
│ var slug string │
│ if err := rows.Scan(&slug); err != nil { │
│ return err │
│ } │
│ realms = append(realms, RegionData{ │
│ Region: slug, │
│ Href: fmt.Sprintf("/api/leaderboard/season/%d/players/realm/%s/%s/{page}.json │
│ ", seasonID, region, slug), │
│ }) │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: realms, │
│ Metadata: NewIndexMetadata(len(realms)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "leaderboard", "season", fmt.Sprintf("%d" │
│ , seasonID), "players", "realm", region, "index.json") │
│ if err := writer.WriteJSONFile(outPath, index); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players realm list index", "season", seasonID, "region", regi │
│ on, "count", len(realms)) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersClassIndex generates class index │
│ func GeneratePlayersClassIndex(outDir string, seasonID int) error { │
│ log.Info("Generating players class index", "season", seasonID) │
│ │
│ classMap := make(map[string]map[string]bool) │
│ for _, info := range wow.SpecByID { │
│ if classMap[info.ClassName] == nil { │
│ classMap[info.ClassName] = make(map[string]bool) │
│ } │
│ classMap[info.ClassName][info.SpecName] = true │
│ } │
│ │
│ var classes []ClassData │
│ for className, specsMap := range classMap { │
│ classKey := strings.ToLower(strings.ReplaceAll(className, " ", "_")) │
│ classID, ok := classKeys[classKey] │
│ if !ok { │
│ log.Warn("Unknown class key", "class", className, "key", classKey) │
│ continue │
│ } │
│ │
│ specs := make([]string, 0, len(specsMap)) │
│ for spec := range specsMap { │
│ specs = append(specs, spec) │
│ } │
│ sort.Strings(specs) │
│ │
│ classes = append(classes, ClassData{ │
│ ID: classID, │
│ Key: classKey, │
│ Name: className, │
│ Specs: specs, │
│ Links: ClassLinks{ │
│ Scopes: Link{Href: fmt.Sprintf("/api/leaderboard/season/%d/players/class/%s/in │
│ dex.json", seasonID, classKey)}, │
│ }, │
│ }) │
│ } │
│ │
│ sort.Slice(classes, func(i, j int) bool { │
│ return classes[i].ID < classes[j].ID │
│ }) │
│ │
│ index := PlayersClassIndex{ │
│ Data: classes, │
│ Metadata: NewIndexMetadata(len(classes)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "leaderboard", "season", fmt.Sprintf("%d" │
│ , seasonID), "players", "class", "index.json") │
│ if err := writer.WriteJSONFile(outPath, index); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players class index", "season", seasonID, "count", len(classe │
│ s), "path", outPath) │
│ return nil │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ GO ─────────────────────────────────┐
│ package indexes │
│ │
│ import ( │
│ "database/sql" │
│ "fmt" │
│ "path/filepath" │
│ "sort" │
│ "strings" │
│ │
│ "github.com/charmbracelet/log" │
│ │
│ "ookstats/internal/wow" │
│ "ookstats/internal/writer" │
│ ) │
│ │
│ var classKeys = map[string]int{ │
│ "warrior": 1, │
│ "paladin": 2, │
│ "hunter": 3, │
│ "rogue": 4, │
│ "priest": 5, │
│ "death_knight": 6, │
│ "shaman": 7, │
│ "mage": 8, │
│ "warlock": 9, │
│ "monk": 10, │
│ "druid": 11, │
│ } │
│ │
│ // GeneratePlayersScopeIndex generates top-l │
│ evel players scope index │
│ func GeneratePlayersScopeIndex(outDir string │
│ , seasonID int) error { │
│ log.Info("Generating players scope index", │
│ "season", seasonID) │
│ │
│ scopes := []PlayerScopeData{ │
│ { │
│ Scope: "global", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/ap │
│ i/leaderboard/season/%d/players/global/{page │
│ }.json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "regional", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/ap │
│ i/leaderboard/season/%d/players/regional/ind │
│ ex.json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "realm", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/ap │
│ i/leaderboard/season/%d/players/realm/index. │
│ json", seasonID)}, │
│ }, │
│ }, │
│ { │
│ Scope: "class", │
│ Links: PlayerScopeLinks{ │
│ Leaderboard: Link{Href: fmt.Sprintf("/ap │
│ i/leaderboard/season/%d/players/class/index. │
│ json", seasonID)}, │
│ }, │
│ }, │
│ } │
│ │
│ index := PlayersScopeIndex{ │
│ Data: scopes, │
│ Metadata: NewIndexMetadata(len(scopes)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "le │
│ aderboard", "season", fmt.Sprintf("%d", seas │
│ onID), "players", "index.json") │
│ if err := writer.WriteJSONFile(outPath, ind │
│ ex); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players scope index", " │
│ season", seasonID, "path", outPath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRegionalIndex generates re │
│ gional scope index (lists regions) │
│ func GeneratePlayersRegionalIndex(outDir str │
│ ing, seasonID int) error { │
│ log.Info("Generating players regional index │
│ ", "season", seasonID) │
│ │
│ regions := []RegionData{ │
│ {Region: "us", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/regional/us/{page │
│ }.json", seasonID)}, │
│ {Region: "eu", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/regional/eu/{page │
│ }.json", seasonID)}, │
│ {Region: "kr", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/regional/kr/{page │
│ }.json", seasonID)}, │
│ {Region: "tw", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/regional/tw/{page │
│ }.json", seasonID)}, │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: regions, │
│ Metadata: NewIndexMetadata(len(regions)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "le │
│ aderboard", "season", fmt.Sprintf("%d", seas │
│ onID), "players", "regional", "index.json") │
│ if err := writer.WriteJSONFile(outPath, ind │
│ ex); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players regional index" │
│ , "season", seasonID, "path", outPath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRealmRegionIndex generates │
│ realm scope region list │
│ func GeneratePlayersRealmRegionIndex(outDir │
│ string, seasonID int) error { │
│ log.Info("Generating players realm region i │
│ ndex", "season", seasonID) │
│ │
│ regions := []RegionData{ │
│ {Region: "us", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/realm/us/index.js │
│ on", seasonID)}, │
│ {Region: "eu", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/realm/eu/index.js │
│ on", seasonID)}, │
│ {Region: "kr", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/realm/kr/index.js │
│ on", seasonID)}, │
│ {Region: "tw", Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/realm/tw/index.js │
│ on", seasonID)}, │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: regions, │
│ Metadata: NewIndexMetadata(len(regions)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "le │
│ aderboard", "season", fmt.Sprintf("%d", seas │
│ onID), "players", "realm", "index.json") │
│ if err := writer.WriteJSONFile(outPath, ind │
│ ex); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players realm region in │
│ dex", "season", seasonID, "path", outPath) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersRealmListIndex generates l │
│ ist of realms for a region (player leaderboa │
│ rds) │
│ func GeneratePlayersRealmListIndex(db *sql.D │
│ B, outDir string, seasonID int, region strin │
│ g) error { │
│ log.Info("Generating players realm list ind │
│ ex", "season", seasonID, "region", region) │
│ │
│ rows, err := db.Query(`SELECT slug FROM rea │
│ lms WHERE region = ? ORDER BY slug`, region) │
│ if err != nil { │
│ return fmt.Errorf("query realms: %w", err) │
│ } │
│ defer rows.Close() │
│ │
│ var realms []RegionData │
│ for rows.Next() { │
│ var slug string │
│ if err := rows.Scan(&slug); err != nil { │
│ return err │
│ } │
│ realms = append(realms, RegionData{ │
│ Region: slug, │
│ Href: fmt.Sprintf("/api/leaderboard/sea │
│ son/%d/players/realm/%s/%s/{page}.json", sea │
│ sonID, region, slug), │
│ }) │
│ } │
│ │
│ index := RegionsIndex{ │
│ Data: realms, │
│ Metadata: NewIndexMetadata(len(realms)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "le │
│ aderboard", "season", fmt.Sprintf("%d", seas │
│ onID), "players", "realm", region, "index.js │
│ on") │
│ if err := writer.WriteJSONFile(outPath, ind │
│ ex); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players realm list inde │
│ x", "season", seasonID, "region", region, "c │
│ ount", len(realms)) │
│ return nil │
│ } │
│ │
│ // GeneratePlayersClassIndex generates class │
│ index │
│ func GeneratePlayersClassIndex(outDir string │
│ , seasonID int) error { │
│ log.Info("Generating players class index", │
│ "season", seasonID) │
│ │
│ classMap := make(map[string]map[string]bool │
│ ) │
│ for _, info := range wow.SpecByID { │
│ if classMap[info.ClassName] == nil { │
│ classMap[info.ClassName] = make(map[strin │
│ g]bool) │
│ } │
│ classMap[info.ClassName][info.SpecName] = │
│ true │
│ } │
│ │
│ var classes []ClassData │
│ for className, specsMap := range classMap { │
│ classKey := strings.ToLower(strings.Replac │
│ eAll(className, " ", "_")) │
│ classID, ok := classKeys[classKey] │
│ if !ok { │
│ log.Warn("Unknown class key", "class", cl │
│ assName, "key", classKey) │
│ continue │
│ } │
│ │
│ specs := make([]string, 0, len(specsMap)) │
│ for spec := range specsMap { │
│ specs = append(specs, spec) │
│ } │
│ sort.Strings(specs) │
│ │
│ classes = append(classes, ClassData{ │
│ ID: classID, │
│ Key: classKey, │
│ Name: className, │
│ Specs: specs, │
│ Links: ClassLinks{ │
│ Scopes: Link{Href: fmt.Sprintf("/api/lea │
│ derboard/season/%d/players/class/%s/index.js │
│ on", seasonID, classKey)}, │
│ }, │
│ }) │
│ } │
│ │
│ sort.Slice(classes, func(i, j int) bool { │
│ return classes[i].ID < classes[j].ID │
│ }) │
│ │
│ index := PlayersClassIndex{ │
│ Data: classes, │
│ Metadata: NewIndexMetadata(len(classes)), │
│ } │
│ │
│ outPath := filepath.Join(outDir, "api", "le │
│ aderboard", "season", fmt.Sprintf("%d", seas │
│ onID), "players", "class", "index.json") │
│ if err := writer.WriteJSONFile(outPath, ind │
│ ex); err != nil { │
│ return err │
│ } │
│ │
│ log.Info("Generated players class index", " │
│ season", seasonID, "count", len(classes), "p │
│ ath", outPath) │
│ return nil │
│ } │
└──────────────────────────────────────────────┘