HASH 62d7523d49e4
DATE 2025-09-22
SUBJECT web/nix: support tw realms
FILES 13 CHANGED
HASH 62d7523d49e4
DATE 2025-09-22
SUBJECT web/nix: support tw realms
FILES 13 CHANGED
┌─ nix/apps/default.nix ─────────────────────────────────────────────────────┐
│ diff --git a/nix/apps/default.nix b/nix/apps/default.nix │
│ index bc0662a..a8affa5 100644 │
│ --- a/nix/apps/default.nix │
│ +++ b/nix/apps/default.nix │
│ @@ -39,6 +39,7 @@ │
│ rankingProcessor = import ./challenge-mode/ranking-processor.nix {inherit wri │
│ ters python3Packages;}; │
│ # analyze periods across all dungeons to find optimal API strategy │
│ periodAnalyzer = import ./utils/period-analyzer.nix {inherit writers python3P │
│ ackages;}; │
│ + multiPeriodAnalyzer = import ./utils/multi-period-analyzer.nix {inherit api w │
│ riters python3Packages;}; │
│ # convert simulation data to apps │
│ │
│ # DEPRECATED SCRIPTS │
│ @@ -113,6 +114,10 @@ │
│ type = "app"; │
│ program = "${periodAnalyzer}/bin/period-analyzer"; │
│ }; │
│ + multiPeriodAnalyzer = { │
│ + type = "app"; │
│ + program = "${multiPeriodAnalyzer}/bin/multi-period-analyzer"; │
│ + }; │
│ wowstats-db = { │
│ type = "app"; │
│ program = "${wowsimstats-cli}/bin/wowstats-db"; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/apps/default.nix ───────────────┐
│ diff --git a/nix/apps/default.nix b/nix/apps │
│ /default.nix │
│ index bc0662a..a8affa5 100644 │
│ --- a/nix/apps/default.nix │
│ +++ b/nix/apps/default.nix │
│ @@ -39,6 +39,7 @@ │
│ rankingProcessor = import ./challenge-m │
│ ode/ranking-processor.nix {inherit writers p │
│ ython3Packages;}; │
│ # analyze periods across all dungeons t │
│ o find optimal API strategy │
│ periodAnalyzer = import ./utils/period- │
│ analyzer.nix {inherit writers python3Package │
│ s;}; │
│ + multiPeriodAnalyzer = import ./utils/mu │
│ lti-period-analyzer.nix {inherit api writers │
│ python3Packages;}; │
│ # convert simulation data to apps │
│ │
│ # DEPRECATED SCRIPTS │
│ @@ -113,6 +114,10 @@ │
│ type = "app"; │
│ program = "${periodAnalyzer}/bin/ │
│ period-analyzer"; │
│ }; │
│ + multiPeriodAnalyzer = { │
│ + type = "app"; │
│ + program = "${multiPeriodAnalyzer} │
│ /bin/multi-period-analyzer"; │
│ + }; │
│ wowstats-db = { │
│ type = "app"; │
│ program = "${wowsimstats-cli}/bin │
│ /wowstats-db"; │
└──────────────────────────────────────────────┘
┌─ nix/apps/utils/multi-period-analyzer.nix ─────────────────────────────────┐
│ diff --git a/nix/apps/utils/multi-period-analyzer.nix b/nix/apps/utils/multi-perio │
│ d-analyzer.nix │
│ new file mode 100644 │
│ index 0000000..eb5986a │
│ --- /dev/null │
│ +++ b/nix/apps/utils/multi-period-analyzer.nix │
│ @@ -0,0 +1,365 @@ │
│ +{ │
│ + writers, │
│ + python3Packages, │
│ + api, │
│ + ... │
│ +}: let │
│ + inherit (api) realm; │
│ + │
│ + multiPeriodAnalyzerScript = │
│ + writers.writePython3Bin "multi-period-analyzer" { │
│ + libraries = [python3Packages.aiohttp]; │
│ + doCheck = false; │
│ + } │
│ + '' │
│ + import os │
│ + import aiohttp │
│ + import asyncio │
│ + import json │
│ + import sys │
│ + import time │
│ + from collections import defaultdict │
│ + import csv │
│ + │
│ + # All Challenge Mode dungeons (from constants.go) │
│ + DUNGEONS = [ │
│ + {"id": 2, "name": "Temple of the Jade Serpent", "slug": "temple-of-the- │
│ jade-serpent"}, │
│ + {"id": 56, "name": "Stormstout Brewery", "slug": "stormstout-brewery"}, │
│ + {"id": 57, "name": "Gate of the Setting Sun", "slug": "gate-of-the-sett │
│ ing-sun"}, │
│ + {"id": 58, "name": "Shado-Pan Monastery", "slug": "shado-pan-monastery" │
│ }, │
│ + {"id": 59, "name": "Siege of Niuzao Temple", "slug": "siege-of-niuzao-t │
│ emple"}, │
│ + {"id": 60, "name": "Mogu'shan Palace", "slug": "mogu-shan-palace"}, │
│ + {"id": 76, "name": "Scholomance", "slug": "scholomance"}, │
│ + {"id": 77, "name": "Scarlet Halls", "slug": "scarlet-halls"}, │
│ + {"id": 78, "name": "Scarlet Monastery", "slug": "scarlet-monastery"}, │
│ + ] │
│ + │
│ + # All realms (from constants.go and nix/api/realm.nix) │
│ + REALMS = [ │
│ + # US Realms │
│ + {"id": 4372, "region": "us", "name": "Atiesh", "slug": "atiesh"}, │
│ + {"id": 4373, "region": "us", "name": "Myzrael", "slug": "myzrael"}, │
│ + {"id": 4374, "region": "us", "name": "Old Blanchy", "slug": "old-blanch │
│ y"}, │
│ + {"id": 4376, "region": "us", "name": "Azuresong", "slug": "azuresong"}, │
│ + {"id": 4384, "region": "us", "name": "Mankrik", "slug": "mankrik"}, │
│ + {"id": 4385, "region": "us", "name": "Pagle", "slug": "pagle"}, │
│ + {"id": 4387, "region": "us", "name": "Ashkandi", "slug": "ashkandi"}, │
│ + {"id": 4388, "region": "us", "name": "Westfall", "slug": "westfall"}, │
│ + {"id": 4395, "region": "us", "name": "Whitemane", "slug": "whitemane"}, │
│ + {"id": 4408, "region": "us", "name": "Faerlina", "slug": "faerlina"}, │
│ + {"id": 4647, "region": "us", "name": "Grobbulus", "slug": "grobbulus"}, │
│ + {"id": 4648, "region": "us", "name": "Bloodsail Buccaneers", "slug": "b │
│ loodsail-buccaneers"}, │
│ + {"id": 4667, "region": "us", "name": "Remulos", "slug": "remulos"}, │
│ + {"id": 4669, "region": "us", "name": "Arugal", "slug": "arugal"}, │
│ + {"id": 4670, "region": "us", "name": "Yojamba", "slug": "yojamba"}, │
│ + {"id": 4725, "region": "us", "name": "Skyfury", "slug": "skyfury"}, │
│ + {"id": 4726, "region": "us", "name": "Sulfuras", "slug": "sulfuras"}, │
│ + {"id": 4727, "region": "us", "name": "Windseeker", "slug": "windseeker" │
│ }, │
│ + {"id": 4728, "region": "us", "name": "Benediction", "slug": "benedictio │
│ n"}, │
│ + {"id": 4731, "region": "us", "name": "Earthfury", "slug": "earthfury"}, │
│ + {"id": 4738, "region": "us", "name": "Maladath", "slug": "maladath"}, │
│ + {"id": 4795, "region": "us", "name": "Angerforge", "slug": "angerforge" │
│ }, │
│ + {"id": 4800, "region": "us", "name": "Eranikus", "slug": "eranikus"}, │
│ + │
│ + # EU Realms │
│ + {"id": 4440, "region": "eu", "name": "Everlook", "slug": "everlook"}, │
│ + {"id": 4441, "region": "eu", "name": "Auberdine", "slug": "auberdine"}, │
│ + {"id": 4442, "region": "eu", "name": "Lakeshire", "slug": "lakeshire"}, │
│ + {"id": 4452, "region": "eu", "name": "Chromie", "slug": "chromie"}, │
│ + {"id": 4453, "region": "eu", "name": "Pyrewood Village", "slug": "pyrew │
│ ood-village"}, │
│ + {"id": 4454, "region": "eu", "name": "Mirage Raceway", "slug": "mirage- │
│ raceway"}, │
│ + {"id": 4455, "region": "eu", "name": "Razorfen", "slug": "razorfen"}, │
│ + {"id": 4456, "region": "eu", "name": "Nethergarde Keep", "slug": "nethe │
│ rgarde-keep"}, │
│ + {"id": 4464, "region": "eu", "name": "Sulfuron", "slug": "sulfuron"}, │
│ + {"id": 4465, "region": "eu", "name": "Golemagg", "slug": "golemagg"}, │
│ + {"id": 4466, "region": "eu", "name": "Patchwerk", "slug": "patchwerk"}, │
│ + {"id": 4467, "region": "eu", "name": "Firemaw", "slug": "firemaw"}, │
│ + {"id": 4474, "region": "eu", "name": "Flamegor", "slug": "flamegor"}, │
│ + {"id": 4476, "region": "eu", "name": "Gehennas", "slug": "gehennas"}, │
│ + {"id": 4477, "region": "eu", "name": "Venoxis", "slug": "venoxis"}, │
│ + {"id": 4678, "region": "eu", "name": "Hydraxian Waterlords", "slug": "h │
│ ydraxian-waterlords"}, │
│ + {"id": 4701, "region": "eu", "name": "Mograine", "slug": "mograine"}, │
│ + {"id": 4703, "region": "eu", "name": "Amnennar", "slug": "amnennar"}, │
│ + {"id": 4742, "region": "eu", "name": "Ashbringer", "slug": "ashbringer" │
│ }, │
│ + {"id": 4745, "region": "eu", "name": "Transcendence", "slug": "transcen │
│ dence"}, │
│ + {"id": 4749, "region": "eu", "name": "Earthshaker", "slug": "earthshake │
│ r"}, │
│ + {"id": 4811, "region": "eu", "name": "Giantstalker", "slug": "giantstal │
│ ker"}, │
│ + {"id": 4813, "region": "eu", "name": "Mandokir", "slug": "mandokir"}, │
│ + {"id": 4815, "region": "eu", "name": "Thekal", "slug": "thekal"}, │
│ + {"id": 4816, "region": "eu", "name": "Jin'do", "slug": "jindo"}, │
│ + │
│ + # KR Realms │
│ + {"id": 4417, "region": "kr", "name": "Shimmering Flats", "slug": "shimm │
│ ering-flats"}, │
│ + {"id": 4419, "region": "kr", "name": "Lokholar", "slug": "lokholar"}, │
│ + {"id": 4420, "region": "kr", "name": "Iceblood", "slug": "iceblood"}, │
│ + {"id": 4421, "region": "kr", "name": "Ragnaros", "slug": "ragnaros"}, │
│ + {"id": 4840, "region": "kr", "name": "Frostmourne", "slug": "frostmourn │
│ e"}, │
│ + ] │
│ + │
│ + API_TOKEN = os.getenv("BLIZZARD_API_TOKEN") │
│ + PERIOD_RANGE = range(1026, 1030) # Test periods 1026-1028 │
│ + MAX_CONCURRENT = 50 # Reduced concurrency for comprehensive testing │
│ + BATCH_SIZE = 50 │
│ + │
│ + async def test_realm_dungeon_period(session, semaphore, realm, dungeon, per │
│ iod_id): │
│ + """Test a specific realm/dungeon/period combination asynchronously""" │
│ + async with semaphore: │
│ + region = realm["region"] │
│ + realm_id = realm["id"] │
│ + dungeon_id = dungeon["id"] │
│ + namespace = f"dynamic-classic-{region}" │
│ + │
│ + url = ( │
│ + f"https://{region}.api.blizzard.com/data/wow/connected-realm/" │
│ + f"{realm_id}/mythic-leaderboard/{dungeon_id}/period/" │
│ + f"{period_id}?namespace={namespace}" │
│ + ) │
│ + │
│ + try: │
│ + async with session.get(url, timeout=10) as response: │
│ + result = { │
│ + "realm_name": realm["name"], │
│ + "realm_slug": realm["slug"], │
│ + "realm_id": realm_id, │
│ + "region": region, │
│ + "dungeon_name": dungeon["name"], │
│ + "dungeon_slug": dungeon["slug"], │
│ + "dungeon_id": dungeon_id, │
│ + "period_id": period_id, │
│ + "status_code": response.status, │
│ + "url": url │
│ + } │
│ + │
│ + if response.status == 404: │
│ + result["success"] = False │
│ + result["error"] = "No data (404)" │
│ + return result │
│ + │
│ + if response.status != 200: │
│ + result["success"] = False │
│ + result["error"] = f"HTTP {response.status}" │
│ + return result │
│ + │
│ + data = await response.json() │
│ + leading_groups = data.get("leading_groups", []) │
│ + │
│ + result["success"] = True │
│ + result["run_count"] = len(leading_groups) │
│ + result["has_runs"] = len(leading_groups) > 0 │
│ + │
│ + if leading_groups: │
│ + result["best_time"] = min(run.get("duration", float("in │
│ f")) for run in leading_groups) │
│ + result["most_recent"] = max(run.get("completed_timestam │
│ p", 0) for run in leading_groups) │
│ + │
│ + return result │
│ + │
│ + except Exception as e: │
│ + return { │
│ + "realm_name": realm["name"], │
│ + "realm_slug": realm["slug"], │
│ + "realm_id": realm_id, │
│ + "region": region, │
│ + "dungeon_name": dungeon["name"], │
│ + "dungeon_slug": dungeon["slug"], │
│ + "dungeon_id": dungeon_id, │
│ + "period_id": period_id, │
│ + "status_code": None, │
│ + "url": url, │
│ + "success": False, │
│ + "error": str(e) │
│ + } │
│ + │
│ + async def main(): │
│ + if not API_TOKEN: │
│ + print("FATAL: BLIZZARD_API_TOKEN environment variable not set.") │
│ + sys.exit(1) │
│ + │
│ + print("=== WoW Challenge Mode Multi-Period Analyzer ===") │
│ + print(f"Testing {len(REALMS)} realms across {len(DUNGEONS)} dungeons") │
│ + print(f"Testing periods: {PERIOD_RANGE.start} to {PERIOD_RANGE.stop - 1 │
│ }") │
│ + print(f"Max concurrent requests: {MAX_CONCURRENT}") │
│ + print() │
│ + │
│ + # Create all request combinations │
│ + all_requests = [] │
│ + for realm in REALMS: │
│ + for dungeon in DUNGEONS: │
│ + for period_id in PERIOD_RANGE: │
│ + all_requests.append((realm, dungeon, period_id)) │
│ + │
│ + total_tests = len(all_requests) │
│ + print(f"Total API endpoint tests: {total_tests}") │
│ + print(f"({len(REALMS)} realms ? {len(DUNGEONS)} dungeons ? {len(PERIOD_ │
│ RANGE)} periods)") │
│ + print() │
│ + │
│ + # Store all results for analysis │
│ + all_results = [] │
│ + failed_results = [] │
│ + successful_results = [] │
│ + │
│ + # Create semaphore to limit concurrent requests │
│ + semaphore = asyncio.Semaphore(MAX_CONCURRENT) │
│ + │
│ + # Setup async HTTP session │
│ + timeout = aiohttp.ClientTimeout(total=15) │
│ + headers = {"Authorization": f"Bearer {API_TOKEN}"} │
│ + │
│ + async with aiohttp.ClientSession(timeout=timeout, headers=headers) as s │
│ ession: │
│ + print("Starting comprehensive API endpoint testing...") │
│ + start_time = time.time() │
│ + │
│ + # Process in batches │
│ + completed_count = 0 │
│ + for batch_idx in range(0, len(all_requests), BATCH_SIZE): │
│ + batch = all_requests[batch_idx:batch_idx + BATCH_SIZE] │
│ + batch_num = batch_idx // BATCH_SIZE + 1 │
│ + total_batches = (len(all_requests) + BATCH_SIZE - 1) // BATCH_S │
│ IZE │
│ + │
│ + print(f"Processing batch {batch_num}/{total_batches} ({len(batc │
│ h)} requests)") │
│ + │
│ + # Create tasks for this batch │
│ + tasks = [] │
│ + for realm, dungeon, period_id in batch: │
│ + task = test_realm_dungeon_period(session, semaphore, realm, │
│ dungeon, period_id) │
│ + tasks.append(task) │
│ + │
│ + # Execute batch │
│ + batch_results = await asyncio.gather(*tasks, return_exceptions= │
│ True) │
│ + │
│ + # Process results │
│ + for result in batch_results: │
│ + completed_count += 1 │
│ + if result and not isinstance(result, Exception): │
│ + all_results.append(result) │
│ + if result["success"]: │
│ + successful_results.append(result) │
│ + else: │
│ + failed_results.append(result) │
│ + elif isinstance(result, Exception): │
│ + print(f" Unexpected error in batch: {result}") │
│ + │
│ + # Progress update │
│ + elapsed = time.time() - start_time │
│ + progress = (completed_count / total_tests) * 100 │
│ + rate = completed_count / elapsed if elapsed > 0 else 0 │
│ + print(f" Batch complete: {progress:.1f}% total ({completed_cou │
│ nt}/{total_tests}) - {rate:.1f} req/s avg") │
│ + print(f" Success: {len(successful_results)}, Failed: {len(fail │
│ ed_results)}") │
│ + │
│ + # Sleep between batches to be API-friendly │
│ + if batch_idx + BATCH_SIZE < len(all_requests): │
│ + await asyncio.sleep(2.0) │
│ + │
│ + elapsed = time.time() - start_time │
│ + print(f"\\nCompleted all {total_tests} requests in {elapsed:.1f}s ( │
│ {total_tests/elapsed:.1f} req/s avg)") │
│ + │
│ + # Generate comprehensive report │
│ + print(f"\n=== COMPREHENSIVE API ENDPOINT ANALYSIS ===") │
│ + print(f"Total endpoints tested: {len(all_results)}") │
│ + print(f"Successful endpoints: {len(successful_results)} ({len(successfu │
│ l_results)/len(all_results)*100:.1f}%)") │
│ + print(f"Failed endpoints: {len(failed_results)} ({len(failed_results)/l │
│ en(all_results)*100:.1f}%)") │
│ + │
│ + # Analyze failure patterns │
│ + if failed_results: │
│ + print(f"\n=== BROKEN ENDPOINT ANALYSIS ===") │
│ + │
│ + # Group by error type │
│ + error_types = defaultdict(int) │
│ + for result in failed_results: │
│ + error_types[result["error"]] += 1 │
│ + │
│ + print("Failure breakdown by error type:") │
│ + for error, count in sorted(error_types.items(), key=lambda x: x[1], │
│ reverse=True): │
│ + print(f" {error}: {count} endpoints") │
│ + │
│ + # Group by realm │
│ + realm_failures = defaultdict(int) │
│ + for result in failed_results: │
│ + realm_failures[f"{result['realm_name']} ({result['region'].uppe │
│ r()})"] += 1 │
│ + │
│ + print(f"\nTop 10 realms with most failures:") │
│ + for realm, count in sorted(realm_failures.items(), key=lambda x: x[ │
│ 1], reverse=True)[:10]: │
│ + print(f" {realm}: {count} failed endpoints") │
│ + │
│ + # Group by dungeon │
│ + dungeon_failures = defaultdict(int) │
│ + for result in failed_results: │
│ + dungeon_failures[result["dungeon_name"]] += 1 │
│ + │
│ + print(f"\nDungeons with most failures:") │
│ + for dungeon, count in sorted(dungeon_failures.items(), key=lambda x │
│ : x[1], reverse=True): │
│ + print(f" {dungeon}: {count} failed endpoints") │
│ + │
│ + # Group by period │
│ + period_failures = defaultdict(int) │
│ + for result in failed_results: │
│ + period_failures[result["period_id"]] += 1 │
│ + │
│ + print(f"\nPeriods with most failures:") │
│ + for period, count in sorted(period_failures.items(), key=lambda x: │
│ x[1], reverse=True): │
│ + print(f" Period {period}: {count} failed endpoints") │
│ + │
│ + # Focus on realm/dungeon combinations with no data across all perio │
│ ds │
│ + print(f"\n=== REALM/DUNGEON COMBINATIONS WITH NO DATA ===") │
│ + │
│ + # Group failed results by realm+dungeon combination │
│ + realm_dungeon_failures = defaultdict(set) # key: (realm_slug, dung │
│ eon_slug), value: set of failed periods │
│ + │
│ + for result in failed_results: │
│ + key = (result["realm_slug"], result["dungeon_slug"]) │
│ + realm_dungeon_failures[key].add(result["period_id"]) │
│ + │
│ + # Find combinations that failed across ALL tested periods │
│ + all_periods = set(PERIOD_RANGE) │
│ + completely_broken = [] │
│ + partially_broken = [] │
│ + │
│ + for (realm_slug, dungeon_slug), failed_periods in realm_dungeon_fai │
│ lures.items(): │
│ + if failed_periods == all_periods: │
│ + # Failed in ALL periods │
│ + completely_broken.append((realm_slug, dungeon_slug)) │
│ + else: │
│ + # Failed in some periods │
│ + partially_broken.append((realm_slug, dungeon_slug, failed_p │
│ eriods)) │
│ + │
│ + print(f"\nCompletely broken realm/dungeon combinations (no data in │
│ ANY period {min(PERIOD_RANGE)}-{max(PERIOD_RANGE)}):") │
│ + if completely_broken: │
│ + for i, (realm_slug, dungeon_slug) in enumerate(sorted(completel │
│ y_broken), 1): │
│ + # Find region for this realm │
│ + realm_info = next((r for r in REALMS if r["slug"] == realm_ │
│ slug), None) │
│ + region = realm_info["region"].upper() if realm_info else "? │
│ ?" │
│ + print(f" {i:2d}. {realm_slug} ({region}) + {dungeon_slug}" │
│ ) │
│ + else: │
│ + print(" None! All realm/dungeon combinations have data in at l │
│ east one period.") │
│ + │
│ + print(f"\nPartially broken realm/dungeon combinations:") │
│ + if partially_broken: │
│ + for i, (realm_slug, dungeon_slug, failed_periods) in enumerate( │
│ sorted(partially_broken), 1): │
│ + # Find region for this realm │
│ + realm_info = next((r for r in REALMS if r["slug"] == realm_ │
│ slug), None) │
│ + region = realm_info["region"].upper() if realm_info else "? │
│ ?" │
│ + working_periods = all_periods - failed_periods │
│ + print(f" {i:2d}. {realm_slug} ({region}) + {dungeon_slug}" │
│ ) │
│ + print(f" Missing data in periods: {sorted(failed_perio │
│ ds)}") │
│ + print(f" Has data in periods: {sorted(working_periods) │
│ }") │
│ + else: │
│ + print(" None! All realm/dungeon combinations work in all perio │
│ ds.") │
│ + │
│ + else: │
│ + print("\\nAll endpoints are working! ?") │
│ + │
│ + # Summary of known problematic cases │
│ + print(f"\n=== SPECIFIC PROBLEMATIC CASES ===") │
│ + gehennas_mogushan = [r for r in failed_results if r["realm_slug"] == "g │
│ ehennas" and r["dungeon_slug"] == "mogu-shan-palace"] │
│ + if gehennas_mogushan: │
│ + print(f"Gehennas + Mogu'shan Palace failures: {len(gehennas_mogusha │
│ n)}") │
│ + for result in gehennas_mogushan: │
│ + print(f" Period {result['period_id']}: {result['error']}") │
│ + else: │
│ + print("Gehennas + Mogu'shan Palace: All working!") │
│ + │
│ + if __name__ == "__main__": │
│ + asyncio.run(main()) │
│ + ''; │
│ +in │
│ + multiPeriodAnalyzerScript │
│ + │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ils/multi-period-analyzer.nix ───┐
│ diff --git a/nix/apps/utils/multi-period-ana │
│ lyzer.nix b/nix/apps/utils/multi-period-anal │
│ yzer.nix │
│ new file mode 100644 │
│ index 0000000..eb5986a │
│ --- /dev/null │
│ +++ b/nix/apps/utils/multi-period-analyzer.n │
│ ix │
│ @@ -0,0 +1,365 @@ │
│ +{ │
│ + writers, │
│ + python3Packages, │
│ + api, │
│ + ... │
│ +}: let │
│ + inherit (api) realm; │
│ + │
│ + multiPeriodAnalyzerScript = │
│ + writers.writePython3Bin "multi-period-a │
│ nalyzer" { │
│ + libraries = [python3Packages.aiohttp] │
│ ; │
│ + doCheck = false; │
│ + } │
│ + '' │
│ + import os │
│ + import aiohttp │
│ + import asyncio │
│ + import json │
│ + import sys │
│ + import time │
│ + from collections import defaultdict │
│ + import csv │
│ + │
│ + # All Challenge Mode dungeons (from c │
│ onstants.go) │
│ + DUNGEONS = [ │
│ + {"id": 2, "name": "Temple of the │
│ Jade Serpent", "slug": "temple-of-the-jade-s │
│ erpent"}, │
│ + {"id": 56, "name": "Stormstout Br │
│ ewery", "slug": "stormstout-brewery"}, │
│ + {"id": 57, "name": "Gate of the S │
│ etting Sun", "slug": "gate-of-the-setting-su │
│ n"}, │
│ + {"id": 58, "name": "Shado-Pan Mon │
│ astery", "slug": "shado-pan-monastery"}, │
│ + {"id": 59, "name": "Siege of Niuz │
│ ao Temple", "slug": "siege-of-niuzao-temple" │
│ }, │
│ + {"id": 60, "name": "Mogu'shan Pal │
│ ace", "slug": "mogu-shan-palace"}, │
│ + {"id": 76, "name": "Scholomance", │
│ "slug": "scholomance"}, │
│ + {"id": 77, "name": "Scarlet Halls │
│ ", "slug": "scarlet-halls"}, │
│ + {"id": 78, "name": "Scarlet Monas │
│ tery", "slug": "scarlet-monastery"}, │
│ + ] │
│ + │
│ + # All realms (from constants.go and n │
│ ix/api/realm.nix) │
│ + REALMS = [ │
│ + # US Realms │
│ + {"id": 4372, "region": "us", "nam │
│ e": "Atiesh", "slug": "atiesh"}, │
│ + {"id": 4373, "region": "us", "nam │
│ e": "Myzrael", "slug": "myzrael"}, │
│ + {"id": 4374, "region": "us", "nam │
│ e": "Old Blanchy", "slug": "old-blanchy"}, │
│ + {"id": 4376, "region": "us", "nam │
│ e": "Azuresong", "slug": "azuresong"}, │
│ + {"id": 4384, "region": "us", "nam │
│ e": "Mankrik", "slug": "mankrik"}, │
│ + {"id": 4385, "region": "us", "nam │
│ e": "Pagle", "slug": "pagle"}, │
│ + {"id": 4387, "region": "us", "nam │
│ e": "Ashkandi", "slug": "ashkandi"}, │
│ + {"id": 4388, "region": "us", "nam │
│ e": "Westfall", "slug": "westfall"}, │
│ + {"id": 4395, "region": "us", "nam │
│ e": "Whitemane", "slug": "whitemane"}, │
│ + {"id": 4408, "region": "us", "nam │
│ e": "Faerlina", "slug": "faerlina"}, │
│ + {"id": 4647, "region": "us", "nam │
│ e": "Grobbulus", "slug": "grobbulus"}, │
│ + {"id": 4648, "region": "us", "nam │
│ e": "Bloodsail Buccaneers", "slug": "bloodsa │
│ il-buccaneers"}, │
│ + {"id": 4667, "region": "us", "nam │
│ e": "Remulos", "slug": "remulos"}, │
│ + {"id": 4669, "region": "us", "nam │
│ e": "Arugal", "slug": "arugal"}, │
│ + {"id": 4670, "region": "us", "nam │
│ e": "Yojamba", "slug": "yojamba"}, │
│ + {"id": 4725, "region": "us", "nam │
│ e": "Skyfury", "slug": "skyfury"}, │
│ + {"id": 4726, "region": "us", "nam │
│ e": "Sulfuras", "slug": "sulfuras"}, │
│ + {"id": 4727, "region": "us", "nam │
│ e": "Windseeker", "slug": "windseeker"}, │
│ + {"id": 4728, "region": "us", "nam │
│ e": "Benediction", "slug": "benediction"}, │
│ + {"id": 4731, "region": "us", "nam │
│ e": "Earthfury", "slug": "earthfury"}, │
│ + {"id": 4738, "region": "us", "nam │
│ e": "Maladath", "slug": "maladath"}, │
│ + {"id": 4795, "region": "us", "nam │
│ e": "Angerforge", "slug": "angerforge"}, │
│ + {"id": 4800, "region": "us", "nam │
│ e": "Eranikus", "slug": "eranikus"}, │
│ + │
│ + # EU Realms │
│ + {"id": 4440, "region": "eu", "nam │
│ e": "Everlook", "slug": "everlook"}, │
│ + {"id": 4441, "region": "eu", "nam │
│ e": "Auberdine", "slug": "auberdine"}, │
│ + {"id": 4442, "region": "eu", "nam │
│ e": "Lakeshire", "slug": "lakeshire"}, │
│ + {"id": 4452, "region": "eu", "nam │
│ e": "Chromie", "slug": "chromie"}, │
│ + {"id": 4453, "region": "eu", "nam │
│ e": "Pyrewood Village", "slug": "pyrewood-vi │
│ llage"}, │
│ + {"id": 4454, "region": "eu", "nam │
│ e": "Mirage Raceway", "slug": "mirage-racewa │
│ y"}, │
│ + {"id": 4455, "region": "eu", "nam │
│ e": "Razorfen", "slug": "razorfen"}, │
│ + {"id": 4456, "region": "eu", "nam │
│ e": "Nethergarde Keep", "slug": "nethergarde │
│ -keep"}, │
│ + {"id": 4464, "region": "eu", "nam │
│ e": "Sulfuron", "slug": "sulfuron"}, │
│ + {"id": 4465, "region": "eu", "nam │
│ e": "Golemagg", "slug": "golemagg"}, │
│ + {"id": 4466, "region": "eu", "nam │
│ e": "Patchwerk", "slug": "patchwerk"}, │
│ + {"id": 4467, "region": "eu", "nam │
│ e": "Firemaw", "slug": "firemaw"}, │
│ + {"id": 4474, "region": "eu", "nam │
│ e": "Flamegor", "slug": "flamegor"}, │
│ + {"id": 4476, "region": "eu", "nam │
│ e": "Gehennas", "slug": "gehennas"}, │
│ + {"id": 4477, "region": "eu", "nam │
│ e": "Venoxis", "slug": "venoxis"}, │
│ + {"id": 4678, "region": "eu", "nam │
│ e": "Hydraxian Waterlords", "slug": "hydraxi │
│ an-waterlords"}, │
│ + {"id": 4701, "region": "eu", "nam │
│ e": "Mograine", "slug": "mograine"}, │
│ + {"id": 4703, "region": "eu", "nam │
│ e": "Amnennar", "slug": "amnennar"}, │
│ + {"id": 4742, "region": "eu", "nam │
│ e": "Ashbringer", "slug": "ashbringer"}, │
│ + {"id": 4745, "region": "eu", "nam │
│ e": "Transcendence", "slug": "transcendence" │
│ }, │
│ + {"id": 4749, "region": "eu", "nam │
│ e": "Earthshaker", "slug": "earthshaker"}, │
│ + {"id": 4811, "region": "eu", "nam │
│ e": "Giantstalker", "slug": "giantstalker"}, │
│ + {"id": 4813, "region": "eu", "nam │
│ e": "Mandokir", "slug": "mandokir"}, │
│ + {"id": 4815, "region": "eu", "nam │
│ e": "Thekal", "slug": "thekal"}, │
│ + {"id": 4816, "region": "eu", "nam │
│ e": "Jin'do", "slug": "jindo"}, │
│ + │
│ + # KR Realms │
│ + {"id": 4417, "region": "kr", "nam │
│ e": "Shimmering Flats", "slug": "shimmering- │
│ flats"}, │
│ + {"id": 4419, "region": "kr", "nam │
│ e": "Lokholar", "slug": "lokholar"}, │
│ + {"id": 4420, "region": "kr", "nam │
│ e": "Iceblood", "slug": "iceblood"}, │
│ + {"id": 4421, "region": "kr", "nam │
│ e": "Ragnaros", "slug": "ragnaros"}, │
│ + {"id": 4840, "region": "kr", "nam │
│ e": "Frostmourne", "slug": "frostmourne"}, │
│ + ] │
│ + │
│ + API_TOKEN = os.getenv("BLIZZARD_API_T │
│ OKEN") │
│ + PERIOD_RANGE = range(1026, 1030) # T │
│ est periods 1026-1028 │
│ + MAX_CONCURRENT = 50 # Reduced concur │
│ rency for comprehensive testing │
│ + BATCH_SIZE = 50 │
│ + │
│ + async def test_realm_dungeon_period(s │
│ ession, semaphore, realm, dungeon, period_id │
│ ): │
│ + """Test a specific realm/dungeon/ │
│ period combination asynchronously""" │
│ + async with semaphore: │
│ + region = realm["region"] │
│ + realm_id = realm["id"] │
│ + dungeon_id = dungeon["id"] │
│ + namespace = f"dynamic-classic │
│ -{region}" │
│ + │
│ + url = ( │
│ + f"https://{region}.api.bl │
│ izzard.com/data/wow/connected-realm/" │
│ + f"{realm_id}/mythic-leade │
│ rboard/{dungeon_id}/period/" │
│ + f"{period_id}?namespace={ │
│ namespace}" │
│ + ) │
│ + │
│ + try: │
│ + async with session.get(ur │
│ l, timeout=10) as response: │
│ + result = { │
│ + "realm_name": rea │
│ lm["name"], │
│ + "realm_slug": rea │
│ lm["slug"], │
│ + "realm_id": realm │
│ _id, │
│ + "region": region, │
│ + "dungeon_name": d │
│ ungeon["name"], │
│ + "dungeon_slug": d │
│ ungeon["slug"], │
│ + "dungeon_id": dun │
│ geon_id, │
│ + "period_id": peri │
│ od_id, │
│ + "status_code": re │
│ sponse.status, │
│ + "url": url │
│ + } │
│ + │
│ + if response.status == │
│ 404: │
│ + result["success"] │
│ = False │
│ + result["error"] = │
│ "No data (404)" │
│ + return result │
│ + │
│ + if response.status != │
│ 200: │
│ + result["success"] │
│ = False │
│ + result["error"] = │
│ f"HTTP {response.status}" │
│ + return result │
│ + │
│ + data = await response │
│ .json() │
│ + leading_groups = data │
│ .get("leading_groups", []) │
│ + │
│ + result["success"] = T │
│ rue │
│ + result["run_count"] = │
│ len(leading_groups) │
│ + result["has_runs"] = │
│ len(leading_groups) > 0 │
│ + │
│ + if leading_groups: │
│ + result["best_time │
│ "] = min(run.get("duration", float("inf")) f │
│ or run in leading_groups) │
│ + result["most_rece │
│ nt"] = max(run.get("completed_timestamp", 0) │
│ for run in leading_groups) │
│ + │
│ + return result │
│ + │
│ + except Exception as e: │
│ + return { │
│ + "realm_name": realm[" │
│ name"], │
│ + "realm_slug": realm[" │
│ slug"], │
│ + "realm_id": realm_id, │
│ + "region": region, │
│ + "dungeon_name": dunge │
│ on["name"], │
│ + "dungeon_slug": dunge │
│ on["slug"], │
│ + "dungeon_id": dungeon │
│ _id, │
│ + "period_id": period_i │
│ d, │
│ + "status_code": None, │
│ + "url": url, │
│ + "success": False, │
│ + "error": str(e) │
│ + } │
│ + │
│ + async def main(): │
│ + if not API_TOKEN: │
│ + print("FATAL: BLIZZARD_API_TO │
│ KEN environment variable not set.") │
│ + sys.exit(1) │
│ + │
│ + print("=== WoW Challenge Mode Mul │
│ ti-Period Analyzer ===") │
│ + print(f"Testing {len(REALMS)} rea │
│ lms across {len(DUNGEONS)} dungeons") │
│ + print(f"Testing periods: {PERIOD_ │
│ RANGE.start} to {PERIOD_RANGE.stop - 1}") │
│ + print(f"Max concurrent requests: │
│ {MAX_CONCURRENT}") │
│ + print() │
│ + │
│ + # Create all request combinations │
│ + all_requests = [] │
│ + for realm in REALMS: │
│ + for dungeon in DUNGEONS: │
│ + for period_id in PERIOD_R │
│ ANGE: │
│ + all_requests.append(( │
│ realm, dungeon, period_id)) │
│ + │
│ + total_tests = len(all_requests) │
│ + print(f"Total API endpoint tests: │
│ {total_tests}") │
│ + print(f"({len(REALMS)} realms ? { │
│ len(DUNGEONS)} dungeons ? {len(PERIOD_RANGE) │
│ } periods)") │
│ + print() │
│ + │
│ + # Store all results for analysis │
│ + all_results = [] │
│ + failed_results = [] │
│ + successful_results = [] │
│ + │
│ + # Create semaphore to limit concu │
│ rrent requests │
│ + semaphore = asyncio.Semaphore(MAX │
│ _CONCURRENT) │
│ + │
│ + # Setup async HTTP session │
│ + timeout = aiohttp.ClientTimeout(t │
│ otal=15) │
│ + headers = {"Authorization": f"Bea │
│ rer {API_TOKEN}"} │
│ + │
│ + async with aiohttp.ClientSession( │
│ timeout=timeout, headers=headers) as session │
│ : │
│ + print("Starting comprehensive │
│ API endpoint testing...") │
│ + start_time = time.time() │
│ + │
│ + # Process in batches │
│ + completed_count = 0 │
│ + for batch_idx in range(0, len │
│ (all_requests), BATCH_SIZE): │
│ + batch = all_requests[batc │
│ h_idx:batch_idx + BATCH_SIZE] │
│ + batch_num = batch_idx // │
│ BATCH_SIZE + 1 │
│ + total_batches = (len(all_ │
│ requests) + BATCH_SIZE - 1) // BATCH_SIZE │
│ + │
│ + print(f"Processing batch │
│ {batch_num}/{total_batches} ({len(batch)} re │
│ quests)") │
│ + │
│ + # Create tasks for this b │
│ atch │
│ + tasks = [] │
│ + for realm, dungeon, perio │
│ d_id in batch: │
│ + task = test_realm_dun │
│ geon_period(session, semaphore, realm, dunge │
│ on, period_id) │
│ + tasks.append(task) │
│ + │
│ + # Execute batch │
│ + batch_results = await asy │
│ ncio.gather(*tasks, return_exceptions=True) │
│ + │
│ + # Process results │
│ + for result in batch_resul │
│ ts: │
│ + completed_count += 1 │
│ + if result and not isi │
│ nstance(result, Exception): │
│ + all_results.appen │
│ d(result) │
│ + if result["succes │
│ s"]: │
│ + successful_re │
│ sults.append(result) │
│ + else: │
│ + failed_result │
│ s.append(result) │
│ + elif isinstance(resul │
│ t, Exception): │
│ + print(f" Unexp │
│ ected error in batch: {result}") │
│ + │
│ + # Progress update │
│ + elapsed = time.time() - s │
│ tart_time │
│ + progress = (completed_cou │
│ nt / total_tests) * 100 │
│ + rate = completed_count / │
│ elapsed if elapsed > 0 else 0 │
│ + print(f" Batch complete: │
│ {progress:.1f}% total ({completed_count}/{t │
│ otal_tests}) - {rate:.1f} req/s avg") │
│ + print(f" Success: {len(s │
│ uccessful_results)}, Failed: {len(failed_res │
│ ults)}") │
│ + │
│ + # Sleep between batches t │
│ o be API-friendly │
│ + if batch_idx + BATCH_SIZE │
│ < len(all_requests): │
│ + await asyncio.sleep(2 │
│ .0) │
│ + │
│ + elapsed = time.time() - start │
│ _time │
│ + print(f"\\nCompleted all {tot │
│ al_tests} requests in {elapsed:.1f}s ({total │
│ _tests/elapsed:.1f} req/s avg)") │
│ + │
│ + # Generate comprehensive report │
│ + print(f"\n=== COMPREHENSIVE API E │
│ NDPOINT ANALYSIS ===") │
│ + print(f"Total endpoints tested: { │
│ len(all_results)}") │
│ + print(f"Successful endpoints: {le │
│ n(successful_results)} ({len(successful_resu │
│ lts)/len(all_results)*100:.1f}%)") │
│ + print(f"Failed endpoints: {len(fa │
│ iled_results)} ({len(failed_results)/len(all │
│ _results)*100:.1f}%)") │
│ + │
│ + # Analyze failure patterns │
│ + if failed_results: │
│ + print(f"\n=== BROKEN ENDPOINT │
│ ANALYSIS ===") │
│ + │
│ + # Group by error type │
│ + error_types = defaultdict(int │
│ ) │
│ + for result in failed_results: │
│ + error_types[result["error │
│ "]] += 1 │
│ + │
│ + print("Failure breakdown by e │
│ rror type:") │
│ + for error, count in sorted(er │
│ ror_types.items(), key=lambda x: x[1], rever │
│ se=True): │
│ + print(f" {error}: {count │
│ } endpoints") │
│ + │
│ + # Group by realm │
│ + realm_failures = defaultdict( │
│ int) │
│ + for result in failed_results: │
│ + realm_failures[f"{result[ │
│ 'realm_name']} ({result['region'].upper()})" │
│ ] += 1 │
│ + │
│ + print(f"\nTop 10 realms with │
│ most failures:") │
│ + for realm, count in sorted(re │
│ alm_failures.items(), key=lambda x: x[1], re │
│ verse=True)[:10]: │
│ + print(f" {realm}: {count │
│ } failed endpoints") │
│ + │
│ + # Group by dungeon │
│ + dungeon_failures = defaultdic │
│ t(int) │
│ + for result in failed_results: │
│ + dungeon_failures[result[" │
│ dungeon_name"]] += 1 │
│ + │
│ + print(f"\nDungeons with most │
│ failures:") │
│ + for dungeon, count in sorted( │
│ dungeon_failures.items(), key=lambda x: x[1] │
│ , reverse=True): │
│ + print(f" {dungeon}: {cou │
│ nt} failed endpoints") │
│ + │
│ + # Group by period │
│ + period_failures = defaultdict │
│ (int) │
│ + for result in failed_results: │
│ + period_failures[result["p │
│ eriod_id"]] += 1 │
│ + │
│ + print(f"\nPeriods with most f │
│ ailures:") │
│ + for period, count in sorted(p │
│ eriod_failures.items(), key=lambda x: x[1], │
│ reverse=True): │
│ + print(f" Period {period} │
│ : {count} failed endpoints") │
│ + │
│ + # Focus on realm/dungeon comb │
│ inations with no data across all periods │
│ + print(f"\n=== REALM/DUNGEON C │
│ OMBINATIONS WITH NO DATA ===") │
│ + │
│ + # Group failed results by rea │
│ lm+dungeon combination │
│ + realm_dungeon_failures = defa │
│ ultdict(set) # key: (realm_slug, dungeon_sl │
│ ug), value: set of failed periods │
│ + │
│ + for result in failed_results: │
│ + key = (result["realm_slug │
│ "], result["dungeon_slug"]) │
│ + realm_dungeon_failures[ke │
│ y].add(result["period_id"]) │
│ + │
│ + # Find combinations that fail │
│ ed across ALL tested periods │
│ + all_periods = set(PERIOD_RANG │
│ E) │
│ + completely_broken = [] │
│ + partially_broken = [] │
│ + │
│ + for (realm_slug, dungeon_slug │
│ ), failed_periods in realm_dungeon_failures. │
│ items(): │
│ + if failed_periods == all_ │
│ periods: │
│ + # Failed in ALL perio │
│ ds │
│ + completely_broken.app │
│ end((realm_slug, dungeon_slug)) │
│ + else: │
│ + # Failed in some peri │
│ ods │
│ + partially_broken.appe │
│ nd((realm_slug, dungeon_slug, failed_periods │
│ )) │
│ + │
│ + print(f"\nCompletely broken r │
│ ealm/dungeon combinations (no data in ANY pe │
│ riod {min(PERIOD_RANGE)}-{max(PERIOD_RANGE)} │
│ ):") │
│ + if completely_broken: │
│ + for i, (realm_slug, dunge │
│ on_slug) in enumerate(sorted(completely_brok │
│ en), 1): │
│ + # Find region for thi │
│ s realm │
│ + realm_info = next((r │
│ for r in REALMS if r["slug"] == realm_slug), │
│ None) │
│ + region = realm_info[" │
│ region"].upper() if realm_info else "??" │
│ + print(f" {i:2d}. {re │
│ alm_slug} ({region}) + {dungeon_slug}") │
│ + else: │
│ + print(" None! All realm/ │
│ dungeon combinations have data in at least o │
│ ne period.") │
│ + │
│ + print(f"\nPartially broken re │
│ alm/dungeon combinations:") │
│ + if partially_broken: │
│ + for i, (realm_slug, dunge │
│ on_slug, failed_periods) in enumerate(sorted │
│ (partially_broken), 1): │
│ + # Find region for thi │
│ s realm │
│ + realm_info = next((r │
│ for r in REALMS if r["slug"] == realm_slug), │
│ None) │
│ + region = realm_info[" │
│ region"].upper() if realm_info else "??" │
│ + working_periods = all │
│ _periods - failed_periods │
│ + print(f" {i:2d}. {re │
│ alm_slug} ({region}) + {dungeon_slug}") │
│ + print(f" Missing │
│ data in periods: {sorted(failed_periods)}") │
│ + print(f" Has dat │
│ a in periods: {sorted(working_periods)}") │
│ + else: │
│ + print(" None! All realm/ │
│ dungeon combinations work in all periods.") │
│ + │
│ + else: │
│ + print("\\nAll endpoints are w │
│ orking! ?") │
│ + │
│ + # Summary of known problematic ca │
│ ses │
│ + print(f"\n=== SPECIFIC PROBLEMATI │
│ C CASES ===") │
│ + gehennas_mogushan = [r for r in f │
│ ailed_results if r["realm_slug"] == "gehenna │
│ s" and r["dungeon_slug"] == "mogu-shan-palac │
│ e"] │
│ + if gehennas_mogushan: │
│ + print(f"Gehennas + Mogu'shan │
│ Palace failures: {len(gehennas_mogushan)}") │
│ + for result in gehennas_mogush │
│ an: │
│ + print(f" Period {result[ │
│ 'period_id']}: {result['error']}") │
│ + else: │
│ + print("Gehennas + Mogu'shan P │
│ alace: All working!") │
│ + │
│ + if __name__ == "__main__": │
│ + asyncio.run(main()) │
│ + ''; │
│ +in │
│ + multiPeriodAnalyzerScript │
│ + │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/cmd/fetch.go ───────────────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/fetch.go b/nix/pkgs/ookstats/src/cmd/fetch. │
│ go │
│ index 8743e7d..36b1de6 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/fetch.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/fetch.go │
│ @@ -124,17 +124,20 @@ var fetchCMCmd = &cobra.Command{ │
│ } │
│ } │
│ } │
│ - if strings.TrimSpace(realmsCSV) != "" { │
│ - allowed := map[string]bool{} │
│ - for _, s := range strings.Split(realmsCSV, ",") { │
│ - allowed[strings.TrimSpace(s)] = true │
│ - } │
│ - for slug := range allRealms { │
│ - if !allowed[slug] { │
│ - delete(allRealms, slug) │
│ - } │
│ - } │
│ - } │
│ + if strings.TrimSpace(realmsCSV) != "" { │
│ + allowed := map[string]bool{} │
│ + for _, s := range strings.Split(realmsCSV, ",") { │
│ + s = strings.TrimSpace(s) │
│ + if s != "" { allowed[s] = true } │
│ + } │
│ + filtered := make(map[string]blizzard.RealmInfo) │
│ + for key, info := range allRealms { │
│ + if allowed[key] || allowed[info.Slug] { │
│ + filtered[key] = info │
│ + } │
│ + } │
│ + allRealms = filtered │
│ + } │
│ if strings.TrimSpace(dungeonsCSV) != "" { │
│ // parse list of ids or slugs │
│ allowed := map[string]bool{} │
│ @@ -153,14 +156,9 @@ var fetchCMCmd = &cobra.Command{ │
│ } │
│ } │
│ │
│ - // pre-populate reference data (optimized): insert all dungeons once, then batc │
│ h insert realms │
│ - fmt.Printf("Pre-populating reference data...\n") │
│ - fmt.Printf(" - Ensuring dungeons (%d)\n", len(dungeons)) │
│ - // ensure slugs are set on realmInfo entries │
│ - for realmSlug, realmInfo := range allRealms { │
│ - realmInfo.Slug = realmSlug │
│ - allRealms[realmSlug] = realmInfo │
│ - } │
│ + // pre-populate reference data (optimized): insert all dungeons once, the │
│ n batch insert realms │
│ + fmt.Printf("Pre-populating reference data...\n") │
│ + fmt.Printf(" - Ensuring dungeons (%d)\n", len(dungeons)) │
│ if err := dbService.EnsureDungeonsOnce(dungeons); err != nil { │
│ return fmt.Errorf("failed to ensure dungeons: %w", err) │
│ } │
│ @@ -403,7 +401,7 @@ func init() { │
│ fetchCMCmd.Flags().Int("fallback-depth", 0, "Limit number of fallback periods to │
│ try per dungeon (0 = default)") │
│ fetchCMCmd.Flags().Int("concurrency", 20, "Max concurrent API requests") │
│ fetchCMCmd.Flags().Int("api-timeout-seconds", 15, "HTTP client timeout in second │
│ s") │
│ - fetchCMCmd.Flags().String("regions", "", "Comma-separated regions to include (us │
│ ,eu,kr)") │
│ + fetchCMCmd.Flags().String("regions", "", "Comma-separated regions to include │
│ (us,eu,kr,tw)") │
│ fetchCMCmd.Flags().String("realms", "", "Comma-separated realm slugs to include" │
│ ) │
│ fetchCMCmd.Flags().String("dungeons", "", "Comma-separated dungeon IDs or slugs │
│ to include") │
│ // period strategy │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...kgs/ookstats/src/cmd/fetch.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/fetch │
│ .go b/nix/pkgs/ookstats/src/cmd/fetch.go │
│ index 8743e7d..36b1de6 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/fetch.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/fetch.go │
│ @@ -124,17 +124,20 @@ var fetchCMCmd = &cobr │
│ a.Command{ │
│ } │
│ } │
│ } │
│ - if strings.TrimSpace(realmsCSV) != "" { │
│ - allowed := map[string]bool{} │
│ - for _, s := range strings.Split(realmsCS │
│ V, ",") { │
│ - allowed[strings.TrimSpace(s)] = true │
│ - } │
│ - for slug := range allRealms { │
│ - if !allowed[slug] { │
│ - delete(allRealms, slug) │
│ - } │
│ - } │
│ - } │
│ + if strings.TrimSpace(realmsCSV) != │
│ "" { │
│ + allowed := map[string]bool{} │
│ + for _, s := range strings.Split │
│ (realmsCSV, ",") { │
│ + s = strings.TrimSpace(s) │
│ + if s != "" { allowed[s] = t │
│ rue } │
│ + } │
│ + filtered := make(map[string]bli │
│ zzard.RealmInfo) │
│ + for key, info := range allRealm │
│ s { │
│ + if allowed[key] || allowed[ │
│ info.Slug] { │
│ + filtered[key] = info │
│ + } │
│ + } │
│ + allRealms = filtered │
│ + } │
│ if strings.TrimSpace(dungeonsCSV) != "" { │
│ // parse list of ids or slugs │
│ allowed := map[string]bool{} │
│ @@ -153,14 +156,9 @@ var fetchCMCmd = &cobra │
│ .Command{ │
│ } │
│ } │
│ │
│ - // pre-populate reference data (optimized │
│ ): insert all dungeons once, then batch inse │
│ rt realms │
│ - fmt.Printf("Pre-populating reference data │
│ ...\n") │
│ - fmt.Printf(" - Ensuring dungeons (%d)\n" │
│ , len(dungeons)) │
│ - // ensure slugs are set on realmInfo entr │
│ ies │
│ - for realmSlug, realmInfo := range allReal │
│ ms { │
│ - realmInfo.Slug = realmSlug │
│ - allRealms[realmSlug] = realmInfo │
│ - } │
│ + // pre-populate reference data (opt │
│ imized): insert all dungeons once, then batc │
│ h insert realms │
│ + fmt.Printf("Pre-populating referenc │
│ e data...\n") │
│ + fmt.Printf(" - Ensuring dungeons ( │
│ %d)\n", len(dungeons)) │
│ if err := dbService.EnsureDungeonsOnce(du │
│ ngeons); err != nil { │
│ return fmt.Errorf("failed to ensure dung │
│ eons: %w", err) │
│ } │
│ @@ -403,7 +401,7 @@ func init() { │
│ fetchCMCmd.Flags().Int("fallback-depth", 0 │
│ , "Limit number of fallback periods to try p │
│ er dungeon (0 = default)") │
│ fetchCMCmd.Flags().Int("concurrency", 20, │
│ "Max concurrent API requests") │
│ fetchCMCmd.Flags().Int("api-timeout-second │
│ s", 15, "HTTP client timeout in seconds") │
│ - fetchCMCmd.Flags().String("regions", "", " │
│ Comma-separated regions to include (us,eu,kr │
│ )") │
│ + fetchCMCmd.Flags().String("regions", "" │
│ , "Comma-separated regions to include (us,eu │
│ ,kr,tw)") │
│ fetchCMCmd.Flags().String("realms", "", "C │
│ omma-separated realm slugs to include") │
│ fetchCMCmd.Flags().String("dungeons", "", │
│ "Comma-separated dungeon IDs or slugs to inc │
│ lude") │
│ // period strategy │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/cmd/generate.go ────────────────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/generate.go b/nix/pkgs/ookstats/src/cmd/gen │
│ erate.go │
│ index 3fd926f..25fadc8 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/generate.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/generate.go │
│ @@ -955,7 +955,7 @@ func generateLeaderboards(db *sql.DB, out string, pageSize int │
│ , regions []string │
│ for _, d := range dungeons { if err := writeGlobal(d); err != nil { return er │
│ r } } │
│ │
│ // Regions │
│ - if len(regions) == 0 { regions = []string{"us","eu","kr"} } │
│ + if len(regions) == 0 { regions = []string{"us","eu","kr","tw"} } │
│ for _, reg := range regions { │
│ fmt.Printf("Generating %s leaderboards...\n", strings.ToUpper(reg)) │
│ for _, d := range dungeons { if err := writeRegional(reg, d); err != nil │
│ { return err } } │
│ @@ -1215,7 +1215,7 @@ func generatePlayerLeaderboards(db *sql.DB, out string, page │
│ Size int, regions [] │
│ // global │
│ if err := writeScope("global", ""); err != nil { return err } │
│ // regional │
│ - if len(regions) == 0 { regions = []string{"us","eu","kr"} } │
│ + if len(regions) == 0 { regions = []string{"us","eu","kr","tw"} } │
│ for _, reg := range regions { │
│ if err := writeScope("regional", reg); err != nil { return err } │
│ } │
│ @@ -1541,5 +1541,5 @@ func init() { │
│ generateAPICmd.Flags().Bool("search", true, "Generate search index JSON shard │
│ s") │
│ generateAPICmd.Flags().Int("page-size", 25, "Leaderboard page size") │
│ generateAPICmd.Flags().Int("shard-size", 5000, "Search index shard size") │
│ - generateAPICmd.Flags().String("regions", "us,eu,kr", "Regions to include for │
│ regional leaderboards") │
│ + generateAPICmd.Flags().String("regions", "us,eu,kr,tw", "Regions to include f │
│ or regional leaderboards") │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../ookstats/src/cmd/generate.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/gener │
│ ate.go b/nix/pkgs/ookstats/src/cmd/generate. │
│ go │
│ index 3fd926f..25fadc8 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/generate.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/generate.go │
│ @@ -955,7 +955,7 @@ func generateLeaderboard │
│ s(db *sql.DB, out string, pageSize int, regi │
│ ons []string │
│ for _, d := range dungeons { if err := │
│ writeGlobal(d); err != nil { return err } } │
│ │
│ // Regions │
│ - if len(regions) == 0 { regions = []stri │
│ ng{"us","eu","kr"} } │
│ + if len(regions) == 0 { regions = []stri │
│ ng{"us","eu","kr","tw"} } │
│ for _, reg := range regions { │
│ fmt.Printf("Generating %s leaderboa │
│ rds...\n", strings.ToUpper(reg)) │
│ for _, d := range dungeons { if err │
│ := writeRegional(reg, d); err != nil { retu │
│ rn err } } │
│ @@ -1215,7 +1215,7 @@ func generatePlayerLea │
│ derboards(db *sql.DB, out string, pageSize i │
│ nt, regions [] │
│ // global │
│ if err := writeScope("global", ""); err │
│ != nil { return err } │
│ // regional │
│ - if len(regions) == 0 { regions = []stri │
│ ng{"us","eu","kr"} } │
│ + if len(regions) == 0 { regions = []stri │
│ ng{"us","eu","kr","tw"} } │
│ for _, reg := range regions { │
│ if err := writeScope("regional", re │
│ g); err != nil { return err } │
│ } │
│ @@ -1541,5 +1541,5 @@ func init() { │
│ generateAPICmd.Flags().Bool("search", t │
│ rue, "Generate search index JSON shards") │
│ generateAPICmd.Flags().Int("page-size", │
│ 25, "Leaderboard page size") │
│ generateAPICmd.Flags().Int("shard-size" │
│ , 5000, "Search index shard size") │
│ - generateAPICmd.Flags().String("regions" │
│ , "us,eu,kr", "Regions to include for region │
│ al leaderboards") │
│ + generateAPICmd.Flags().String("regions" │
│ , "us,eu,kr,tw", "Regions to include for reg │
│ ional leaderboards") │
│ } │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/blizzard/constants.go ─────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/blizzard/constants.go b/nix/pkgs/ookst │
│ ats/src/internal/blizzard/constants.go │
│ index a163d3d..c656fe0 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/blizzard/constants.go │
│ +++ b/nix/pkgs/ookstats/src/internal/blizzard/constants.go │
│ @@ -34,25 +34,28 @@ func GetGlobalPeriods() []string { │
│ // GetRegionFallbackPeriods prioritizes the period order per region based on obse │
│ rved data │
│ // This reduces 404 churn before finding data. │
│ func GetRegionFallbackPeriods(region string) []string { │
│ - switch region { │
│ - case "eu": │
│ - // EU often has most data in 1025, then 1026 │
│ - return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ - case "us": │
│ - // US commonly 1026 first │
│ - return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ - case "kr": │
│ - // KR follows US pattern in our observations │
│ - return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ - default: │
│ - return GetFallbackPeriods() │
│ - } │
│ + switch region { │
│ + case "eu": │
│ + // EU often has most data in 1025, then 1026 │
│ + return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", " │
│ 1022", "1021", "1020"} │
│ + case "us": │
│ + // US commonly 1026 first │
│ + return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", " │
│ 1022", "1021", "1020"} │
│ + case "kr": │
│ + // KR follows US pattern in our observations │
│ + return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", " │
│ 1022", "1021", "1020"} │
│ + case "tw": │
│ + // TW expected to mirror US/KR period availability │
│ + return []string{"1029", "1028", "1027", "1026", "1025", "1024", "1023", " │
│ 1022", "1021", "1020"} │
│ + default: │
│ + return GetFallbackPeriods() │
│ + } │
│ } │
│ │
│ // GetAllRealms returns the complete realm configuration │
│ // this data comes from nix/api/realm.nix │
│ func GetAllRealms() map[string]RealmInfo { │
│ - return map[string]RealmInfo{ │
│ + return map[string]RealmInfo{ │
│ // us realms │
│ "atiesh": {ID: 4372, Region: "us", Name: "Atiesh", Slug: "atiesh" │
│ }, │
│ "myzrael": {ID: 4373, Region: "us", Name: "Myzrael", Slug: "myzrae │
│ l"}, │
│ @@ -105,11 +108,22 @@ func GetAllRealms() map[string]RealmInfo { │
│ "thekal": {ID: 4815, Region: "eu", Name: "Thekal", Slug: "thekal" │
│ }, │
│ "jindo": {ID: 4816, Region: "eu", Name: "Jin'do", Slug: "jindo"} │
│ , │
│ │
│ - // kr realms │
│ - "shimmering-flats": {ID: 4417, Region: "kr", Name: "Shimmering Flats", Slug: "s │
│ himmering-flats"}, │
│ - "lokholar": {ID: 4419, Region: "kr", Name: "Lokholar", Slug: "lokholar" │
│ }, │
│ - "iceblood": {ID: 4420, Region: "kr", Name: "Iceblood", Slug: "iceblood" │
│ }, │
│ - "ragnaros": {ID: 4421, Region: "kr", Name: "Ragnaros", Slug: "ragnaros" │
│ }, │
│ - "frostmourne": {ID: 4840, Region: "kr", Name: "Frostmourne", Slug: "frostm │
│ ourne"}, │
│ - } │
│ + // kr realms │
│ + "shimmering-flats": {ID: 4417, Region: "kr", Name: "Shimmering Flats", Sl │
│ ug: "shimmering-flats"}, │
│ + "lokholar": {ID: 4419, Region: "kr", Name: "Lokholar", Slug: "lok │
│ holar"}, │
│ + "iceblood": {ID: 4420, Region: "kr", Name: "Iceblood", Slug: "ice │
│ blood"}, │
│ + "ragnaros": {ID: 4421, Region: "kr", Name: "Ragnaros", Slug: "rag │
│ naros"}, │
│ + "frostmourne": {ID: 4840, Region: "kr", Name: "Frostmourne", Slug: " │
│ frostmourne"}, │
│ + │
│ + // tw realms (exclude PTR realms TW4 CWOW GMSS 1/2) │
│ + "maraudon": {ID: 4485, Region: "tw", Name: "Maraudon", Slug: "maraudo │
│ n"}, │
│ + "ivus": {ID: 4487, Region: "tw", Name: "Ivus", Slug: "ivus"}, │
│ + "wushoolay": {ID: 4488, Region: "tw", Name: "Wushoolay", Slug: "wushoo │
│ lay"}, │
│ + "zeliek": {ID: 4489, Region: "tw", Name: "Zeliek", Slug: "zeliek"}, │
│ + "arathi-basin": {ID: 5740, Region: "tw", Name: "Arathi Basin", Slug: "ara │
│ thi-basin"}, │
│ + "murloc": {ID: 5741, Region: "tw", Name: "Murloc", Slug: "murloc"}, │
│ + // Use disambiguated map keys for slugs colliding across regions; Slug fi │
│ eld remains the Blizzard slug. │
│ + "golemagg-tw": {ID: 5742, Region: "tw", Name: "Golemagg", Slug: "golema │
│ gg"}, │
│ + "windseeker-tw": {ID: 5743, Region: "tw", Name: "Windseeker", Slug: "wind │
│ seeker"}, │
│ + } │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...nternal/blizzard/constants.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ blizzard/constants.go b/nix/pkgs/ookstats/sr │
│ c/internal/blizzard/constants.go │
│ index a163d3d..c656fe0 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/blizzar │
│ d/constants.go │
│ +++ b/nix/pkgs/ookstats/src/internal/blizzar │
│ d/constants.go │
│ @@ -34,25 +34,28 @@ func GetGlobalPeriods() │
│ []string { │
│ // GetRegionFallbackPeriods prioritizes the │
│ period order per region based on observed d │
│ ata │
│ // This reduces 404 churn before finding da │
│ ta. │
│ func GetRegionFallbackPeriods(region string │
│ ) []string { │
│ - switch region { │
│ - case "eu": │
│ - // EU often has most data in 1025, then 1 │
│ 026 │
│ - return []string{"1029", "1028", "1027", " │
│ 1026", "1025", "1024", "1023", "1022", "1021 │
│ ", "1020"} │
│ - case "us": │
│ - // US commonly 1026 first │
│ - return []string{"1029", "1028", "1027", " │
│ 1026", "1025", "1024", "1023", "1022", "1021 │
│ ", "1020"} │
│ - case "kr": │
│ - // KR follows US pattern in our observati │
│ ons │
│ - return []string{"1029", "1028", "1027", " │
│ 1026", "1025", "1024", "1023", "1022", "1021 │
│ ", "1020"} │
│ - default: │
│ - return GetFallbackPeriods() │
│ - } │
│ + switch region { │
│ + case "eu": │
│ + // EU often has most data in 1025, │
│ then 1026 │
│ + return []string{"1029", "1028", "10 │
│ 27", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ + case "us": │
│ + // US commonly 1026 first │
│ + return []string{"1029", "1028", "10 │
│ 27", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ + case "kr": │
│ + // KR follows US pattern in our obs │
│ ervations │
│ + return []string{"1029", "1028", "10 │
│ 27", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ + case "tw": │
│ + // TW expected to mirror US/KR peri │
│ od availability │
│ + return []string{"1029", "1028", "10 │
│ 27", "1026", "1025", "1024", "1023", "1022", │
│ "1021", "1020"} │
│ + default: │
│ + return GetFallbackPeriods() │
│ + } │
│ } │
│ │
│ // GetAllRealms returns the complete realm │
│ configuration │
│ // this data comes from nix/api/realm.nix │
│ func GetAllRealms() map[string]RealmInfo { │
│ - return map[string]RealmInfo{ │
│ + return map[string]RealmInfo{ │
│ // us realms │
│ "atiesh": {ID: 4372, Region │
│ : "us", Name: "Atiesh", Slug: "atiesh"}, │
│ "myzrael": {ID: 4373, Region │
│ : "us", Name: "Myzrael", Slug: "myzrael"}, │
│ @@ -105,11 +108,22 @@ func GetAllRealms() ma │
│ p[string]RealmInfo { │
│ "thekal": {ID: 4815, Region │
│ : "eu", Name: "Thekal", Slug: "thekal"}, │
│ "jindo": {ID: 4816, Region │
│ : "eu", Name: "Jin'do", Slug: "jindo"}, │
│ │
│ - // kr realms │
│ - "shimmering-flats": {ID: 4417, Region: "k │
│ r", Name: "Shimmering Flats", Slug: "shimmer │
│ ing-flats"}, │
│ - "lokholar": {ID: 4419, Region: "k │
│ r", Name: "Lokholar", Slug: "lokholar"}, │
│ - "iceblood": {ID: 4420, Region: "k │
│ r", Name: "Iceblood", Slug: "iceblood"}, │
│ - "ragnaros": {ID: 4421, Region: "k │
│ r", Name: "Ragnaros", Slug: "ragnaros"}, │
│ - "frostmourne": {ID: 4840, Region: "k │
│ r", Name: "Frostmourne", Slug: "frostmourne" │
│ }, │
│ - } │
│ + // kr realms │
│ + "shimmering-flats": {ID: 4417, Regi │
│ on: "kr", Name: "Shimmering Flats", Slug: "s │
│ himmering-flats"}, │
│ + "lokholar": {ID: 4419, Regi │
│ on: "kr", Name: "Lokholar", Slug: "lokholar" │
│ }, │
│ + "iceblood": {ID: 4420, Regi │
│ on: "kr", Name: "Iceblood", Slug: "iceblood" │
│ }, │
│ + "ragnaros": {ID: 4421, Regi │
│ on: "kr", Name: "Ragnaros", Slug: "ragnaros" │
│ }, │
│ + "frostmourne": {ID: 4840, Regi │
│ on: "kr", Name: "Frostmourne", Slug: "frostm │
│ ourne"}, │
│ + │
│ + // tw realms (exclude PTR realms TW │
│ 4 CWOW GMSS 1/2) │
│ + "maraudon": {ID: 4485, Region: │
│ "tw", Name: "Maraudon", Slug: "maraudon"}, │
│ + "ivus": {ID: 4487, Region: │
│ "tw", Name: "Ivus", Slug: "ivus"}, │
│ + "wushoolay": {ID: 4488, Region: │
│ "tw", Name: "Wushoolay", Slug: "wushoolay"}, │
│ + "zeliek": {ID: 4489, Region: │
│ "tw", Name: "Zeliek", Slug: "zeliek"}, │
│ + "arathi-basin": {ID: 5740, Region: │
│ "tw", Name: "Arathi Basin", Slug: "arathi-ba │
│ sin"}, │
│ + "murloc": {ID: 5741, Region: │
│ "tw", Name: "Murloc", Slug: "murloc"}, │
│ + // Use disambiguated map keys for s │
│ lugs colliding across regions; Slug field re │
│ mains the Blizzard slug. │
│ + "golemagg-tw": {ID: 5742, Region: │
│ "tw", Name: "Golemagg", Slug: "golemagg"}, │
│ + "windseeker-tw": {ID: 5743, Region: │
│ "tw", Name: "Windseeker", Slug: "windseeker │
│ "}, │
│ + } │
│ } │
└──────────────────────────────────────────────┘
┌─ 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 6f42807..f5410c9 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/client.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/client.go │
│ @@ -232,13 +232,13 @@ func EnsureCompleteSchema(db *sql.DB) error { │
│ map_challenge_mode_id INTEGER UNIQUE │
│ )`, │
│ │
│ - `CREATE TABLE IF NOT EXISTS realms ( │
│ - id INTEGER PRIMARY KEY, │
│ - slug TEXT UNIQUE, │
│ - name TEXT, │
│ - region TEXT, │
│ - connected_realm_id INTEGER UNIQUE │
│ - )`, │
│ + `CREATE TABLE IF NOT EXISTS realms ( │
│ + id INTEGER PRIMARY KEY, │
│ + slug TEXT, │
│ + name TEXT, │
│ + region TEXT, │
│ + connected_realm_id INTEGER UNIQUE │
│ + )`, │
│ │
│ // Core leaderboard data │
│ `CREATE TABLE IF NOT EXISTS challenge_runs ( │
│ @@ -405,13 +405,20 @@ func EnsureCompleteSchema(db *sql.DB) error { │
│ │
│ fmt.Printf("[OK] All tables created\n") │
│ │
│ - // Create indexes │
│ - return ensureRecommendedIndexes(db) │
│ + // Migrate realms schema if needed (ensure (region, slug) composite uniquenes │
│ s) │
│ + if err := migrateRealmsCompositeSlug(db); err != nil { │
│ + return err │
│ + } │
│ + │
│ + // Create indexes │
│ + return ensureRecommendedIndexes(db) │
│ } │
│ │
│ // ensureRecommendedIndexes creates indexes used by hot paths if missing │
│ func ensureRecommendedIndexes(db *sql.DB) error { │
│ stmts := []string{ │
│ + // Ensure composite uniqueness for realms │
│ + "CREATE UNIQUE INDEX IF NOT EXISTS idx_realms_region_slug ON realms(regio │
│ n, slug)", │
│ // Fast path for high-water checks │
│ "CREATE INDEX IF NOT EXISTS idx_runs_realm_dungeon_ct ON challenge_runs(r │
│ ealm_id, dungeon_id, completed_timestamp)", │
│ // Uniqueness key to avoid duplicates │
│ @@ -435,3 +442,52 @@ func ensureRecommendedIndexes(db *sql.DB) error { │
│ fmt.Printf("[OK] All indexes ensured\n") │
│ return nil │
│ } │
│ + │
│ +// migrateRealmsCompositeSlug upgrades the realms table from slug-unique to (regi │
│ on,slug)-unique if necessary. │
│ +func migrateRealmsCompositeSlug(db *sql.DB) error { │
│ + // Inspect table SQL │
│ + var createSQL string │
│ + _ = db.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='r │
│ ealms'`).Scan(&createSQL) │
│ + // If table already created without UNIQUE on slug, nothing to do │
│ + if !strings.Contains(strings.ToLower(createSQL), "slug text unique") { │
│ + return nil │
│ + } │
│ + │
│ + fmt.Printf("[MIGRATE] Upgrading realms table to composite (region,slug) uniqu │
│ eness...\n") │
│ + tx, err := db.Begin() │
│ + if err != nil { return err } │
│ + defer tx.Rollback() │
│ + │
│ + // Create new table without UNIQUE on slug │
│ + if _, err := tx.Exec(` │
│ + CREATE TABLE IF NOT EXISTS realms_new ( │
│ + id INTEGER PRIMARY KEY, │
│ + slug TEXT, │
│ + name TEXT, │
│ + region TEXT, │
│ + connected_realm_id INTEGER UNIQUE │
│ + ) │
│ + `); err != nil { return fmt.Errorf("create realms_new: %w", err) } │
│ + │
│ + // Copy data │
│ + if _, err := tx.Exec(`INSERT INTO realms_new (id, slug, name, region, connect │
│ ed_realm_id) │
│ + SELECT id, slug, name, region, connected_realm_id FROM │
│ realms`); err != nil { │
│ + return fmt.Errorf("copy realms: %w", err) │
│ + } │
│ + │
│ + // Rename old and new │
│ + if _, err := tx.Exec(`ALTER TABLE realms RENAME TO realms_old`); err != nil { │
│ return fmt.Errorf("rename old: %w", err) } │
│ + if _, err := tx.Exec(`ALTER TABLE realms_new RENAME TO realms`); err != nil { │
│ return fmt.Errorf("rename new: %w", err) } │
│ + │
│ + // Drop old │
│ + if _, err := tx.Exec(`DROP TABLE realms_old`); err != nil { return fmt.Errorf │
│ ("drop old: %w", err) } │
│ + │
│ + // Ensure composite unique index │
│ + if _, err := tx.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_realms_region_slu │
│ g ON realms(region, slug)`); err != nil { │
│ + return fmt.Errorf("create composite unique index: %w", err) │
│ + } │
│ + │
│ + if err := tx.Commit(); err != nil { return err } │
│ + fmt.Printf("[OK] Realms table migrated\n") │
│ + return nil │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...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 6f42807..f5410c9 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/client.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/client.go │
│ @@ -232,13 +232,13 @@ func EnsureCompleteSch │
│ ema(db *sql.DB) error { │
│ map_challenge_mode_id INTEGER UNIQUE │
│ )`, │
│ │
│ - `CREATE TABLE IF NOT EXISTS realms ( │
│ - id INTEGER PRIMARY KEY, │
│ - slug TEXT UNIQUE, │
│ - name TEXT, │
│ - region TEXT, │
│ - connected_realm_id INTEGER UNIQUE │
│ - )`, │
│ + `CREATE TABLE IF NOT EXISTS realms │
│ ( │
│ + id INTEGER PRIMARY KEY, │
│ + slug TEXT, │
│ + name TEXT, │
│ + region TEXT, │
│ + connected_realm_id INTEGER UNIQ │
│ UE │
│ + )`, │
│ │
│ // Core leaderboard data │
│ `CREATE TABLE IF NOT EXISTS challenge_run │
│ s ( │
│ @@ -405,13 +405,20 @@ func EnsureCompleteSch │
│ ema(db *sql.DB) error { │
│ │
│ fmt.Printf("[OK] All tables created\n") │
│ │
│ - // Create indexes │
│ - return ensureRecommendedIndexes(db) │
│ + // Migrate realms schema if needed (ens │
│ ure (region, slug) composite uniqueness) │
│ + if err := migrateRealmsCompositeSlug(db │
│ ); err != nil { │
│ + return err │
│ + } │
│ + │
│ + // Create indexes │
│ + return ensureRecommendedIndexes(db) │
│ } │
│ │
│ // ensureRecommendedIndexes creates indexes │
│ used by hot paths if missing │
│ func ensureRecommendedIndexes(db *sql.DB) e │
│ rror { │
│ stmts := []string{ │
│ + // Ensure composite uniqueness for │
│ realms │
│ + "CREATE UNIQUE INDEX IF NOT EXISTS │
│ idx_realms_region_slug ON realms(region, slu │
│ g)", │
│ // Fast path for high-water checks │
│ "CREATE INDEX IF NOT EXISTS idx_run │
│ s_realm_dungeon_ct ON challenge_runs(realm_i │
│ d, dungeon_id, completed_timestamp)", │
│ // Uniqueness key to avoid duplicat │
│ es │
│ @@ -435,3 +442,52 @@ func ensureRecommendedI │
│ ndexes(db *sql.DB) error { │
│ fmt.Printf("[OK] All indexes ensured\n" │
│ ) │
│ return nil │
│ } │
│ + │
│ +// migrateRealmsCompositeSlug upgrades the │
│ realms table from slug-unique to (region,slu │
│ g)-unique if necessary. │
│ +func migrateRealmsCompositeSlug(db *sql.DB) │
│ error { │
│ + // Inspect table SQL │
│ + var createSQL string │
│ + _ = db.QueryRow(`SELECT sql FROM sqlite │
│ _master WHERE type='table' AND name='realms' │
│ `).Scan(&createSQL) │
│ + // If table already created without UNI │
│ QUE on slug, nothing to do │
│ + if !strings.Contains(strings.ToLower(cr │
│ eateSQL), "slug text unique") { │
│ + return nil │
│ + } │
│ + │
│ + fmt.Printf("[MIGRATE] Upgrading realms │
│ table to composite (region,slug) uniqueness. │
│ ..\n") │
│ + tx, err := db.Begin() │
│ + if err != nil { return err } │
│ + defer tx.Rollback() │
│ + │
│ + // Create new table without UNIQUE on s │
│ lug │
│ + if _, err := tx.Exec(` │
│ + CREATE TABLE IF NOT EXISTS realms_n │
│ ew ( │
│ + id INTEGER PRIMARY KEY, │
│ + slug TEXT, │
│ + name TEXT, │
│ + region TEXT, │
│ + connected_realm_id INTEGER UNIQ │
│ UE │
│ + ) │
│ + `); err != nil { return fmt.Errorf("cre │
│ ate realms_new: %w", err) } │
│ + │
│ + // Copy data │
│ + if _, err := tx.Exec(`INSERT INTO realm │
│ s_new (id, slug, name, region, connected_rea │
│ lm_id) │
│ + SELECT id, slug, │
│ name, region, connected_realm_id FROM realms │
│ `); err != nil { │
│ + return fmt.Errorf("copy realms: %w" │
│ , err) │
│ + } │
│ + │
│ + // Rename old and new │
│ + if _, err := tx.Exec(`ALTER TABLE realm │
│ s RENAME TO realms_old`); err != nil { retur │
│ n fmt.Errorf("rename old: %w", err) } │
│ + if _, err := tx.Exec(`ALTER TABLE realm │
│ s_new RENAME TO realms`); err != nil { retur │
│ n fmt.Errorf("rename new: %w", err) } │
│ + │
│ + // Drop old │
│ + if _, err := tx.Exec(`DROP TABLE realms │
│ _old`); err != nil { return fmt.Errorf("drop │
│ old: %w", err) } │
│ + │
│ + // Ensure composite unique index │
│ + if _, err := tx.Exec(`CREATE UNIQUE IND │
│ EX IF NOT EXISTS idx_realms_region_slug ON r │
│ ealms(region, slug)`); err != nil { │
│ + return fmt.Errorf("create composite │
│ unique index: %w", err) │
│ + } │
│ + │
│ + if err := tx.Commit(); err != nil { ret │
│ urn err } │
│ + fmt.Printf("[OK] Realms table migrated\ │
│ n") │
│ + return nil │
│ +} │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/ookstats/src/internal/database/models.go ────────────────────────┐
│ diff --git a/nix/pkgs/ookstats/src/internal/database/models.go b/nix/pkgs/ookstats │
│ /src/internal/database/models.go │
│ index 604f2b6..a35ad09 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/models.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/models.go │
│ @@ -130,7 +130,8 @@ func (ds *DatabaseService) UpdateFetchMetadata(fetchType strin │
│ g, runsFetched, pl │
│ │
│ // GetRealmID retrieves realm ID by slug, returns 0 if not found │
│ func (ds *DatabaseService) GetRealmID(slug string) (int, error) { │
│ - query := `SELECT id FROM realms WHERE slug = ?` │
│ + // Deprecated: prefer GetRealmIDByRegionAndSlug │
│ + query := `SELECT id FROM realms WHERE slug = ?` │
│ │
│ var realmID int │
│ err := ds.db.QueryRow(query, slug).Scan(&realmID) │
│ @@ -144,6 +145,21 @@ func (ds *DatabaseService) GetRealmID(slug string) (int, erro │
│ r) { │
│ return realmID, nil │
│ } │
│ │
│ +// GetRealmIDByRegionAndSlug retrieves realm ID by composite (region, slug), retu │
│ rns 0 if not found │
│ +func (ds *DatabaseService) GetRealmIDByRegionAndSlug(region, slug string) (int, e │
│ rror) { │
│ + query := `SELECT id FROM realms WHERE region = ? AND slug = ?` │
│ + │
│ + var realmID int │
│ + err := ds.db.QueryRow(query, region, slug).Scan(&realmID) │
│ + if err == sql.ErrNoRows { │
│ + return 0, nil │
│ + } │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + return realmID, nil │
│ +} │
│ + │
│ // GetDungeonID retrieves dungeon ID by slug, returns 0 if not found │
│ func (ds *DatabaseService) GetDungeonID(slug string) (int, error) { │
│ query := `SELECT id FROM dungeons WHERE slug = ?` │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...c/internal/database/models.go ───┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ database/models.go b/nix/pkgs/ookstats/src/i │
│ nternal/database/models.go │
│ index 604f2b6..a35ad09 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/models.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/models.go │
│ @@ -130,7 +130,8 @@ func (ds *DatabaseServic │
│ e) UpdateFetchMetadata(fetchType string, run │
│ sFetched, pl │
│ │
│ // GetRealmID retrieves realm ID by slug, r │
│ eturns 0 if not found │
│ func (ds *DatabaseService) GetRealmID(slug │
│ string) (int, error) { │
│ - query := `SELECT id FROM realms WHERE slug │
│ = ?` │
│ + // Deprecated: prefer GetRealmIDByRegio │
│ nAndSlug │
│ + query := `SELECT id FROM realms WHERE s │
│ lug = ?` │
│ │
│ var realmID int │
│ err := ds.db.QueryRow(query, slug).Scan(&r │
│ ealmID) │
│ @@ -144,6 +145,21 @@ func (ds *DatabaseServi │
│ ce) GetRealmID(slug string) (int, error) { │
│ return realmID, nil │
│ } │
│ │
│ +// GetRealmIDByRegionAndSlug retrieves real │
│ m ID by composite (region, slug), returns 0 │
│ if not found │
│ +func (ds *DatabaseService) GetRealmIDByRegi │
│ onAndSlug(region, slug string) (int, error) │
│ { │
│ + query := `SELECT id FROM realms WHERE r │
│ egion = ? AND slug = ?` │
│ + │
│ + var realmID int │
│ + err := ds.db.QueryRow(query, region, sl │
│ ug).Scan(&realmID) │
│ + if err == sql.ErrNoRows { │
│ + return 0, nil │
│ + } │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + return realmID, nil │
│ +} │
│ + │
│ // GetDungeonID retrieves dungeon ID by slu │
│ g, returns 0 if not found │
│ func (ds *DatabaseService) GetDungeonID(slu │
│ g string) (int, error) { │
│ query := `SELECT id FROM dungeons WHERE sl │
│ ug = ?` │
└──────────────────────────────────────────────┘
┌─ 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 06083a2..1444492 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/operations.go │
│ @@ -118,8 +118,8 @@ func (ds *DatabaseService) InsertLeaderboardData(leaderboard * │
│ blizzard.Leaderboa │
│ return 0, 0, nil │
│ } │
│ │
│ - // Get realm and dungeon IDs │
│ - realmID, err := ds.GetRealmID(realmInfo.Slug) │
│ + // Get realm and dungeon IDs │
│ + realmID, err := ds.GetRealmIDByRegionAndSlug(realmInfo.Region, realmInfo.Slug │
│ ) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to get realm ID: %w", err) │
│ } │
│ @@ -213,7 +213,7 @@ func (ds *DatabaseService) InsertLeaderboardData(leaderboard * │
│ blizzard.Leaderboa │
│ // get or create player realm │
│ var playerRealmID int │
│ if hasRealmSlug { │
│ - playerRealmID, err = ds.getOrCreateRealm(tx, playerRealmSlug, allRealms) │
│ + playerRealmID, err = ds.getOrCreateRealmByRegion(tx, playerRealmSlug, realmIn │
│ fo.Region, allRealms) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to get/create player realm: %w", err) │
│ } │
│ @@ -462,7 +462,7 @@ func (ds *DatabaseService) processBatch(batch []blizzard.Fetch │
│ Result) (int, int, │
│ continue │
│ } │
│ // resolve IDs (read-only) │
│ - realmID, err := ds.GetRealmID(res.RealmInfo.Slug) │
│ + realmID, err := ds.GetRealmIDByRegionAndSlug(res.RealmInfo.Region, res.Re │
│ almInfo.Slug) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to resolve realm id: %w", err) │
│ } │
│ @@ -573,7 +573,7 @@ func (ds *DatabaseService) ensureReferenceDataTx(tx *sql.Tx, r │
│ ealmInfo blizzard. │
│ // insertLeaderboardDataTx inserts leaderboard data within a transaction │
│ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.Tx, leaderboard *blizz │
│ ard.LeaderboardResponse, realmInfo blizzard.RealmInfo, dungeon blizzard.DungeonInf │
│ o) (int, int, error) { │
│ // Resolve realm and dungeon IDs (read-only; realms/dungeons are pre-populate │
│ d) │
│ - realmID, err := ds.getRealmIDTx(tx, realmInfo.Slug) │
│ + realmID, err := ds.getRealmIDTx(tx, realmInfo.Slug, realmInfo.Region) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to get realm ID: %w", err) │
│ } │
│ @@ -681,7 +681,7 @@ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.Tx, │
│ leaderboard *bliz │
│ var playerRealmID int │
│ if hasRealmSlug { │
│ // resolve player realm id (realms are pre-populated; create only │
│ if truly unknown) │
│ - playerRealmID, err = ds.getRealmIDTx(tx, playerRealmSlug) │
│ + playerRealmID, err = ds.getRealmIDTx(tx, playerRealmSlug, realmIn │
│ fo.Region) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to resolve player realm: %w", │
│ err) │
│ } │
│ @@ -690,7 +690,7 @@ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.Tx, │
│ leaderboard *bliz │
│ playerRealmSlug, playerRealmSlug, realmInfo.Region, 0); e │
│ rr != nil { │
│ return 0, 0, fmt.Errorf("failed to create placeholder rea │
│ lm: %w", err) │
│ } │
│ - id2, err := ds.getRealmIDTx(tx, playerRealmSlug) │
│ + id2, err := ds.getRealmIDTx(tx, playerRealmSlug, realmInfo.Re │
│ gion) │
│ if err != nil || id2 == 0 { │
│ return 0, 0, fmt.Errorf("failed to resolve placeholder re │
│ alm id: %w", err) │
│ } │
│ @@ -761,13 +761,13 @@ func (ds *DatabaseService) insertLeaderboardDataTx(tx *sql.T │
│ x, leaderboard *bliz │
│ } │
│ │
│ // helper functions for transaction-based operations │
│ -func (ds *DatabaseService) getRealmIDTx(tx *sql.Tx, slug string) (int, error) { │
│ - var realmID int │
│ - err := tx.QueryRow("SELECT id FROM realms WHERE slug = ?", slug).Scan(&realmID) │
│ - if err == sql.ErrNoRows { │
│ - return 0, fmt.Errorf("realm not found: %s", slug) │
│ - } │
│ - return realmID, err │
│ +func (ds *DatabaseService) getRealmIDTx(tx *sql.Tx, slug string, region string) ( │
│ int, error) { │
│ + var realmID int │
│ + err := tx.QueryRow("SELECT id FROM realms WHERE slug = ? AND region = ?", slu │
│ g, region).Scan(&realmID) │
│ + if err == sql.ErrNoRows { │
│ + return 0, fmt.Errorf("realm not found: %s/%s", region, slug) │
│ + } │
│ + return realmID, err │
│ } │
│ │
│ func (ds *DatabaseService) getDungeonIDTx(tx *sql.Tx, slug string) (int, error) { │
│ @@ -816,6 +816,43 @@ func (ds *DatabaseService) getOrCreateRealmTx(tx *sql.Tx, rea │
│ lmSlug string, allR │
│ } │
│ } │
│ │
│ +// getOrCreateRealmByRegion resolves a realm by region+slug or creates a placehol │
│ der/known realm │
│ +func (ds *DatabaseService) getOrCreateRealmByRegion(tx *sql.Tx, realmSlug string, │
│ region string, allRealms map[string]blizzard.RealmInfo) (int, error) { │
│ + // try to get existing realm by composite key │
│ + var realmID int │
│ + err := tx.QueryRow("SELECT id FROM realms WHERE slug = ? AND region = ?", rea │
│ lmSlug, region).Scan(&realmID) │
│ + if err == nil { │
│ + return realmID, nil │
│ + } │
│ + if err != sql.ErrNoRows { │
│ + return 0, err │
│ + } │
│ + │
│ + // If we know this realm (by slug) from constants, use its canonical region/n │
│ ame/id │
│ + if realmInfo, ok := allRealms[realmSlug]; ok { │
│ + result, err := tx.Exec(` │
│ + INSERT INTO realms (slug, name, region, connected_realm_id) │
│ + VALUES (?, ?, ?, ?) │
│ + `, realmSlug, realmInfo.Name, realmInfo.Region, realmInfo.ID) │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + newID, err := result.LastInsertId() │
│ + return int(newID), err │
│ + } │
│ + │
│ + // otherwise create a placeholder scoped to the provided region │
│ + result, err := tx.Exec(` │
│ + INSERT INTO realms (slug, name, region, connected_realm_id) │
│ + VALUES (?, ?, ?, ?) │
│ + `, realmSlug, utils.Slugify(realmSlug), region, 0) │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + newID, err := result.LastInsertId() │
│ + return int(newID), err │
│ +} │
│ + │
│ // player profile database operations │
│ │
│ // GetEligiblePlayersForProfileFetch returns players with complete coverage (9/9 │
│ dungeons) │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...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 06083a2..1444492 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ @@ -118,8 +118,8 @@ func (ds *DatabaseServic │
│ e) InsertLeaderboardData(leaderboard *blizza │
│ rd.Leaderboa │
│ return 0, 0, nil │
│ } │
│ │
│ - // Get realm and dungeon IDs │
│ - realmID, err := ds.GetRealmID(realmInfo.Sl │
│ ug) │
│ + // Get realm and dungeon IDs │
│ + realmID, err := ds.GetRealmIDByRegionAn │
│ dSlug(realmInfo.Region, realmInfo.Slug) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to get re │
│ alm ID: %w", err) │
│ } │
│ @@ -213,7 +213,7 @@ func (ds *DatabaseServic │
│ e) InsertLeaderboardData(leaderboard *blizza │
│ rd.Leaderboa │
│ // get or create player realm │
│ var playerRealmID int │
│ if hasRealmSlug { │
│ - playerRealmID, err = ds.getOrCreateReal │
│ m(tx, playerRealmSlug, allRealms) │
│ + playerRealmID, err = ds.getOrCreateReal │
│ mByRegion(tx, playerRealmSlug, realmInfo.Reg │
│ ion, allRealms) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to get │
│ /create player realm: %w", err) │
│ } │
│ @@ -462,7 +462,7 @@ func (ds *DatabaseServic │
│ e) processBatch(batch []blizzard.FetchResult │
│ ) (int, int, │
│ continue │
│ } │
│ // resolve IDs (read-only) │
│ - realmID, err := ds.GetRealmID(res.R │
│ ealmInfo.Slug) │
│ + realmID, err := ds.GetRealmIDByRegi │
│ onAndSlug(res.RealmInfo.Region, res.RealmInf │
│ o.Slug) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed │
│ to resolve realm id: %w", err) │
│ } │
│ @@ -573,7 +573,7 @@ func (ds *DatabaseServic │
│ e) ensureReferenceDataTx(tx *sql.Tx, realmIn │
│ fo blizzard. │
│ // insertLeaderboardDataTx inserts leaderbo │
│ ard data within a transaction │
│ func (ds *DatabaseService) insertLeaderboar │
│ dDataTx(tx *sql.Tx, leaderboard *blizzard.Le │
│ aderboardResponse, realmInfo blizzard.RealmI │
│ nfo, dungeon blizzard.DungeonInfo) (int, int │
│ , error) { │
│ // Resolve realm and dungeon IDs (read- │
│ only; realms/dungeons are pre-populated) │
│ - realmID, err := ds.getRealmIDTx(tx, rea │
│ lmInfo.Slug) │
│ + realmID, err := ds.getRealmIDTx(tx, rea │
│ lmInfo.Slug, realmInfo.Region) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf("failed to │
│ get realm ID: %w", err) │
│ } │
│ @@ -681,7 +681,7 @@ func (ds *DatabaseServic │
│ e) insertLeaderboardDataTx(tx *sql.Tx, leade │
│ rboard *bliz │
│ var playerRealmID int │
│ if hasRealmSlug { │
│ // resolve player realm id │
│ (realms are pre-populated; create only if tr │
│ uly unknown) │
│ - playerRealmID, err = ds.get │
│ RealmIDTx(tx, playerRealmSlug) │
│ + playerRealmID, err = ds.get │
│ RealmIDTx(tx, playerRealmSlug, realmInfo.Reg │
│ ion) │
│ if err != nil { │
│ return 0, 0, fmt.Errorf │
│ ("failed to resolve player realm: %w", err) │
│ } │
│ @@ -690,7 +690,7 @@ func (ds *DatabaseServic │
│ e) insertLeaderboardDataTx(tx *sql.Tx, leade │
│ rboard *bliz │
│ playerRealmSlug, pl │
│ ayerRealmSlug, realmInfo.Region, 0); err != │
│ nil { │
│ return 0, 0, fmt.Er │
│ rorf("failed to create placeholder realm: %w │
│ ", err) │
│ } │
│ - id2, err := ds.getRealm │
│ IDTx(tx, playerRealmSlug) │
│ + id2, err := ds.getRealm │
│ IDTx(tx, playerRealmSlug, realmInfo.Region) │
│ if err != nil || id2 == │
│ 0 { │
│ return 0, 0, fmt.Er │
│ rorf("failed to resolve placeholder realm id │
│ : %w", err) │
│ } │
│ @@ -761,13 +761,13 @@ func (ds *DatabaseServ │
│ ice) insertLeaderboardDataTx(tx *sql.Tx, lea │
│ derboard *bliz │
│ } │
│ │
│ // helper functions for transaction-based o │
│ perations │
│ -func (ds *DatabaseService) getRealmIDTx(tx │
│ *sql.Tx, slug string) (int, error) { │
│ - var realmID int │
│ - err := tx.QueryRow("SELECT id FROM realms │
│ WHERE slug = ?", slug).Scan(&realmID) │
│ - if err == sql.ErrNoRows { │
│ - return 0, fmt.Errorf("realm not found: %s │
│ ", slug) │
│ - } │
│ - return realmID, err │
│ +func (ds *DatabaseService) getRealmIDTx(tx │
│ *sql.Tx, slug string, region string) (int, e │
│ rror) { │
│ + var realmID int │
│ + err := tx.QueryRow("SELECT id FROM real │
│ ms WHERE slug = ? AND region = ?", slug, reg │
│ ion).Scan(&realmID) │
│ + if err == sql.ErrNoRows { │
│ + return 0, fmt.Errorf("realm not fou │
│ nd: %s/%s", region, slug) │
│ + } │
│ + return realmID, err │
│ } │
│ │
│ func (ds *DatabaseService) getDungeonIDTx(t │
│ x *sql.Tx, slug string) (int, error) { │
│ @@ -816,6 +816,43 @@ func (ds *DatabaseServi │
│ ce) getOrCreateRealmTx(tx *sql.Tx, realmSlug │
│ string, allR │
│ } │
│ } │
│ │
│ +// getOrCreateRealmByRegion resolves a real │
│ m by region+slug or creates a placeholder/kn │
│ own realm │
│ +func (ds *DatabaseService) getOrCreateRealm │
│ ByRegion(tx *sql.Tx, realmSlug string, regio │
│ n string, allRealms map[string]blizzard.Real │
│ mInfo) (int, error) { │
│ + // try to get existing realm by composi │
│ te key │
│ + var realmID int │
│ + err := tx.QueryRow("SELECT id FROM real │
│ ms WHERE slug = ? AND region = ?", realmSlug │
│ , region).Scan(&realmID) │
│ + if err == nil { │
│ + return realmID, nil │
│ + } │
│ + if err != sql.ErrNoRows { │
│ + return 0, err │
│ + } │
│ + │
│ + // If we know this realm (by slug) from │
│ constants, use its canonical region/name/id │
│ + if realmInfo, ok := allRealms[realmSlug │
│ ]; ok { │
│ + result, err := tx.Exec(` │
│ + INSERT INTO realms (slug, name, │
│ region, connected_realm_id) │
│ + VALUES (?, ?, ?, ?) │
│ + `, realmSlug, realmInfo.Name, realm │
│ Info.Region, realmInfo.ID) │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + newID, err := result.LastInsertId() │
│ + return int(newID), err │
│ + } │
│ + │
│ + // otherwise create a placeholder scope │
│ d to the provided region │
│ + result, err := tx.Exec(` │
│ + INSERT INTO realms (slug, name, reg │
│ ion, connected_realm_id) │
│ + VALUES (?, ?, ?, ?) │
│ + `, realmSlug, utils.Slugify(realmSlug), │
│ region, 0) │
│ + if err != nil { │
│ + return 0, err │
│ + } │
│ + newID, err := result.LastInsertId() │
│ + return int(newID), err │
│ +} │
│ + │
│ // player profile database operations │
│ │
│ // GetEligiblePlayersForProfileFetch return │
│ s players with complete coverage (9/9 dungeo │
│ ns) │
└──────────────────────────────────────────────┘
┌─ web/.astro/data-store.json ───────────────────────────────────────────────┐
│ diff --git a/web/.astro/data-store.json b/web/.astro/data-store.json │
│ index 3aef997..11f4145 100644 │
│ --- a/web/.astro/data-store.json │
│ +++ b/web/.astro/data-store.json │
│ @@ -1 +1 @@ │
│ -[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config- │
│ digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{} │
│ ,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail │
│ ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\", │
│ \"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr │
│ o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto │
│ \",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":30711,\"s │
│ treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r │
│ oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\" │
│ config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de │
│ vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\ │
│ ",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th │
│ eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl │
│ ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t │
│ rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\ │
│ ":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false │
│ ,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\ │
│ ":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}" │
│ ] │
│ \ No newline at end of file │
│ +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config- │
│ digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{} │
│ ,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail │
│ ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\", │
│ \"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr │
│ o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto │
│ \",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":29567,\"s │
│ treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r │
│ oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\" │
│ config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de │
│ vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\ │
│ ",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th │
│ eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl │
│ ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t │
│ rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\ │
│ ":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false │
│ ,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\ │
│ ":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}" │
│ ] │
│ \ No newline at end of file │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ web/.astro/data-store.json ─────────┐
│ diff --git a/web/.astro/data-store.json b/we │
│ b/.astro/data-store.json │
│ index 3aef997..11f4145 100644 │
│ --- a/web/.astro/data-store.json │
│ +++ b/web/.astro/data-store.json │
│ @@ -1 +1 @@ │
│ -[["Map",1,2],"meta::meta",["Map",3,4,5,6]," │
│ astro-version","5.12.0","astro-config-digest │
│ ","{\"root\":{},\"srcDir\":{},\"publicDir\": │
│ {},\"outDir\":{},\"cacheDir\":{},\"site\":\" │
│ https://wowsimstats.com\",\"compressHTML\":t │
│ rue,\"base\":\"/\",\"trailingSlash\":\"ignor │
│ e\",\"output\":\"server\",\"scopedStyleStrat │
│ egy\":\"attribute\",\"build\":{\"format\":\" │
│ directory\",\"client\":{},\"server\":{},\"as │
│ sets\":\"_astro\",\"serverEntry\":\"entry.mj │
│ s\",\"redirects\":false,\"inlineStylesheets\ │
│ ":\"auto\",\"concurrency\":1},\"server\":{\" │
│ open\":false,\"host\":false,\"port\":30711,\ │
│ "streaming\":true,\"allowedHosts\":[]},\"red │
│ irects\":{},\"image\":{\"endpoint\":{\"route │
│ \":\"/_image\"},\"service\":{\"entrypoint\": │
│ \"astro/assets/services/sharp\",\"config\":{ │
│ }},\"domains\":[],\"remotePatterns\":[],\"re │
│ sponsiveStyles\":false},\"devToolbar\":{\"en │
│ abled\":true},\"markdown\":{\"syntaxHighligh │
│ t\":{\"type\":\"shiki\",\"excludeLangs\":[\" │
│ math\"]},\"shikiConfig\":{\"langs\":[],\"lan │
│ gAlias\":{},\"theme\":\"github-dark\",\"them │
│ es\":{},\"wrap\":false,\"transformers\":[]}, │
│ \"remarkPlugins\":[],\"rehypePlugins\":[],\" │
│ remarkRehype\":{},\"gfm\":true,\"smartypants │
│ \":true},\"security\":{\"checkOrigin\":true} │
│ ,\"env\":{\"schema\":{},\"validateSecrets\": │
│ false},\"experimental\":{\"clientPrerender\" │
│ :false,\"contentIntellisense\":false,\"headi │
│ ngIdCompat\":false,\"preserveScriptOrder\":f │
│ alse,\"liveContentCollections\":false,\"csp\ │
│ ":false,\"rawEnvValues\":false},\"legacy\":{ │
│ \"collections\":false}}"] │
│ \ No newline at end of file │
│ +[["Map",1,2],"meta::meta",["Map",3,4,5,6]," │
│ astro-version","5.12.0","astro-config-digest │
│ ","{\"root\":{},\"srcDir\":{},\"publicDir\": │
│ {},\"outDir\":{},\"cacheDir\":{},\"site\":\" │
│ https://wowsimstats.com\",\"compressHTML\":t │
│ rue,\"base\":\"/\",\"trailingSlash\":\"ignor │
│ e\",\"output\":\"server\",\"scopedStyleStrat │
│ egy\":\"attribute\",\"build\":{\"format\":\" │
│ directory\",\"client\":{},\"server\":{},\"as │
│ sets\":\"_astro\",\"serverEntry\":\"entry.mj │
│ s\",\"redirects\":false,\"inlineStylesheets\ │
│ ":\"auto\",\"concurrency\":1},\"server\":{\" │
│ open\":false,\"host\":false,\"port\":29567,\ │
│ "streaming\":true,\"allowedHosts\":[]},\"red │
│ irects\":{},\"image\":{\"endpoint\":{\"route │
│ \":\"/_image\"},\"service\":{\"entrypoint\": │
│ \"astro/assets/services/sharp\",\"config\":{ │
│ }},\"domains\":[],\"remotePatterns\":[],\"re │
│ sponsiveStyles\":false},\"devToolbar\":{\"en │
│ abled\":true},\"markdown\":{\"syntaxHighligh │
│ t\":{\"type\":\"shiki\",\"excludeLangs\":[\" │
│ math\"]},\"shikiConfig\":{\"langs\":[],\"lan │
│ gAlias\":{},\"theme\":\"github-dark\",\"them │
│ es\":{},\"wrap\":false,\"transformers\":[]}, │
│ \"remarkPlugins\":[],\"rehypePlugins\":[],\" │
│ remarkRehype\":{},\"gfm\":true,\"smartypants │
│ \":true},\"security\":{\"checkOrigin\":true} │
│ ,\"env\":{\"schema\":{},\"validateSecrets\": │
│ false},\"experimental\":{\"clientPrerender\" │
│ :false,\"contentIntellisense\":false,\"headi │
│ ngIdCompat\":false,\"preserveScriptOrder\":f │
│ alse,\"liveContentCollections\":false,\"csp\ │
│ ":false,\"rawEnvValues\":false},\"legacy\":{ │
│ \"collections\":false}}"] │
│ \ No newline at end of file │
└──────────────────────────────────────────────┘
┌─ web/src/components/LeaderboardControls.astro ─────────────────────────────┐
│ diff --git a/web/src/components/LeaderboardControls.astro b/web/src/components/Lea │
│ derboardControls.astro │
│ index 92d3433..2db15bc 100644 │
│ --- a/web/src/components/LeaderboardControls.astro │
│ +++ b/web/src/components/LeaderboardControls.astro │
│ @@ -47,6 +47,7 @@ const { │
│ { value: "us", label: "US", selected: initialRegion === "us" }, │
│ { value: "eu", label: "EU", selected: initialRegion === "eu" }, │
│ { value: "kr", label: "KR", selected: initialRegion === "kr" }, │
│ + { value: "tw", label: "TW", selected: initialRegion === "tw" }, │
│ { │
│ value: "global", │
│ label: "Global", │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...nts/LeaderboardControls.astro ───┐
│ diff --git a/web/src/components/LeaderboardC │
│ ontrols.astro b/web/src/components/Leaderboa │
│ rdControls.astro │
│ index 92d3433..2db15bc 100644 │
│ --- a/web/src/components/LeaderboardControls │
│ .astro │
│ +++ b/web/src/components/LeaderboardControls │
│ .astro │
│ @@ -47,6 +47,7 @@ const { │
│ { value: "us", label: "US", selecte │
│ d: initialRegion === "us" }, │
│ { value: "eu", label: "EU", selecte │
│ d: initialRegion === "eu" }, │
│ { value: "kr", label: "KR", selecte │
│ d: initialRegion === "kr" }, │
│ + { value: "tw", label: "TW", selecte │
│ d: initialRegion === "tw" }, │
│ { │
│ value: "global", │
│ label: "Global", │
└──────────────────────────────────────────────┘
┌─ web/src/components/islands/FilterPanel.astro ─────────────────────────────┐
│ diff --git a/web/src/components/islands/FilterPanel.astro b/web/src/components/isl │
│ ands/FilterPanel.astro │
│ index d9a8131..cf734bc 100644 │
│ --- a/web/src/components/islands/FilterPanel.astro │
│ +++ b/web/src/components/islands/FilterPanel.astro │
│ @@ -32,6 +32,7 @@ const { │
│ <option value="us" selected={initialRegion === "us"}>US</option> │
│ <option value="eu" selected={initialRegion === "eu"}>EU</option> │
│ <option value="kr" selected={initialRegion === "kr"}>KR</option> │
│ + <option value="tw" selected={initialRegion === "tw"}>TW</option> │
│ </select> │
│ </div> │
│ │
│ @@ -71,6 +72,15 @@ const { │
│ </option> │
│ )) │
│ } │
│ + { │
│ + Object.entries(REALM_DATA.tw) │
│ + .sort((a, b) => a[1].localeCompare(b[1])) │
│ + .map(([slug, name]) => ( │
│ + <option value={slug} data-region="tw" style="display: none;"> │
│ + {name} │
│ + </option> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...nts/islands/FilterPanel.astro ───┐
│ diff --git a/web/src/components/islands/Filt │
│ erPanel.astro b/web/src/components/islands/F │
│ ilterPanel.astro │
│ index d9a8131..cf734bc 100644 │
│ --- a/web/src/components/islands/FilterPanel │
│ .astro │
│ +++ b/web/src/components/islands/FilterPanel │
│ .astro │
│ @@ -32,6 +32,7 @@ const { │
│ <option value="us" selected={initia │
│ lRegion === "us"}>US</option> │
│ <option value="eu" selected={initia │
│ lRegion === "eu"}>EU</option> │
│ <option value="kr" selected={initia │
│ lRegion === "kr"}>KR</option> │
│ + <option value="tw" selected={initia │
│ lRegion === "tw"}>TW</option> │
│ </select> │
│ </div> │
│ │
│ @@ -71,6 +72,15 @@ const { │
│ </option> │
│ )) │
│ } │
│ + { │
│ + Object.entries(REALM_DATA.tw) │
│ + .sort((a, b) => a[1].localeComp │
│ are(b[1])) │
│ + .map(([slug, name]) => ( │
│ + <option value={slug} data-reg │
│ ion="tw" style="display: none;"> │
│ + {name} │
│ + </option> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
└──────────────────────────────────────────────┘
┌─ web/src/components/islands/PlayerFilterPanel.astro ───────────────────────┐
│ diff --git a/web/src/components/islands/PlayerFilterPanel.astro b/web/src/componen │
│ ts/islands/PlayerFilterPanel.astro │
│ index 417247d..c31092c 100644 │
│ --- a/web/src/components/islands/PlayerFilterPanel.astro │
│ +++ b/web/src/components/islands/PlayerFilterPanel.astro │
│ @@ -31,6 +31,7 @@ const { │
│ <option value="us" selected={initialRegion === "us"}>US</option> │
│ <option value="eu" selected={initialRegion === "eu"}>EU</option> │
│ <option value="kr" selected={initialRegion === "kr"}>KR</option> │
│ + <option value="tw" selected={initialRegion === "tw"}>TW</option> │
│ </select> │
│ </div> │
│ │
│ @@ -59,6 +60,13 @@ const { │
│ <option value={slug} data-region="kr" style="display: none;">{name} │
│ </option> │
│ )) │
│ } │
│ + { │
│ + Object.entries(REALM_DATA.tw) │
│ + .sort((a, b) => a[1].localeCompare(b[1])) │
│ + .map(([slug, name]) => ( │
│ + <option value={slug} data-region="tw" style="display: none;">{name} │
│ </option> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
│ @@ -95,4 +103,3 @@ const { │
│ .filter-select { background: var(--bg-primary); color: var(--text-primary); bor │
│ der: 1px solid var(--border-color); border-radius: 4px; padding: 8px 10px; } │
│ @media (max-width: 768px) { .filter-row { grid-template-columns: 1fr; } } │
│ </style> │
│ - │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...lands/PlayerFilterPanel.astro ───┐
│ diff --git a/web/src/components/islands/Play │
│ erFilterPanel.astro b/web/src/components/isl │
│ ands/PlayerFilterPanel.astro │
│ index 417247d..c31092c 100644 │
│ --- a/web/src/components/islands/PlayerFilte │
│ rPanel.astro │
│ +++ b/web/src/components/islands/PlayerFilte │
│ rPanel.astro │
│ @@ -31,6 +31,7 @@ const { │
│ <option value="us" selected={initia │
│ lRegion === "us"}>US</option> │
│ <option value="eu" selected={initia │
│ lRegion === "eu"}>EU</option> │
│ <option value="kr" selected={initia │
│ lRegion === "kr"}>KR</option> │
│ + <option value="tw" selected={initia │
│ lRegion === "tw"}>TW</option> │
│ </select> │
│ </div> │
│ │
│ @@ -59,6 +60,13 @@ const { │
│ <option value={slug} data-reg │
│ ion="kr" style="display: none;">{name}</opti │
│ on> │
│ )) │
│ } │
│ + { │
│ + Object.entries(REALM_DATA.tw) │
│ + .sort((a, b) => a[1].localeComp │
│ are(b[1])) │
│ + .map(([slug, name]) => ( │
│ + <option value={slug} data-reg │
│ ion="tw" style="display: none;">{name}</opti │
│ on> │
│ + )) │
│ + } │
│ </select> │
│ </div> │
│ │
│ @@ -95,4 +103,3 @@ const { │
│ .filter-select { background: var(--bg-pri │
│ mary); color: var(--text-primary); border: 1 │
│ px solid var(--border-color); border-radius: │
│ 4px; padding: 8px 10px; } │
│ @media (max-width: 768px) { .filter-row { │
│ grid-template-columns: 1fr; } } │
│ </style> │
│ - │
└──────────────────────────────────────────────┘
┌─ web/src/lib/realms.ts ────────────────────────────────────────────────────┐
│ diff --git a/web/src/lib/realms.ts b/web/src/lib/realms.ts │
│ index 1bdb222..fd2e987 100644 │
│ --- a/web/src/lib/realms.ts │
│ +++ b/web/src/lib/realms.ts │
│ @@ -60,6 +60,16 @@ export const REALM_DATA = { │
│ ragnaros: "Ragnaros", │
│ frostmourne: "Frostmourne", │
│ }, │
│ + tw: { │
│ + maraudon: "Maraudon", │
│ + ivus: "Ivus", │
│ + wushoolay: "Wushoolay", │
│ + zeliek: "Zeliek", │
│ + "arathi-basin": "Arathi Basin", │
│ + murloc: "Murloc", │
│ + golemagg: "Golemagg", │
│ + windseeker: "Windseeker", │
│ + }, │
│ } as const; │
│ │
│ export type Region = keyof typeof REALM_DATA; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ web/src/lib/realms.ts ──────────────┐
│ diff --git a/web/src/lib/realms.ts b/web/src │
│ /lib/realms.ts │
│ index 1bdb222..fd2e987 100644 │
│ --- a/web/src/lib/realms.ts │
│ +++ b/web/src/lib/realms.ts │
│ @@ -60,6 +60,16 @@ export const REALM_DATA = │
│ { │
│ ragnaros: "Ragnaros", │
│ frostmourne: "Frostmourne", │
│ }, │
│ + tw: { │
│ + maraudon: "Maraudon", │
│ + ivus: "Ivus", │
│ + wushoolay: "Wushoolay", │
│ + zeliek: "Zeliek", │
│ + "arathi-basin": "Arathi Basin", │
│ + murloc: "Murloc", │
│ + golemagg: "Golemagg", │
│ + windseeker: "Windseeker", │
│ + }, │
│ } as const; │
│ │
│ export type Region = keyof typeof REALM_DAT │
│ A; │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET