feat: migrate from RapidAPI to TheSportsDB with SvelteKit dashboard
- Replace free-api-live-football-data (RapidAPI) backend with TheSportsDB - Add PostgreSQL cache layer for permanent data (teams, players, leagues, events) - Replace Bootstrap dashboard with SvelteKit-based interactive dashboard - Restructure MCP tools around TheSportsDB capabilities (get_team_info, get_roster, get_fixtures, get_standings, etc.) - Expose tool registry via GET /api/tools so dashboard stays in sync - Remove legacy modules and references (api_football, sync, RapidAPI env vars)
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Apply Nike schema to Portia PostgreSQL."""
|
||||
"""Apply the Nike cache schema (schema.sql) to PostgreSQL."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import psycopg2
|
||||
|
||||
load_dotenv('/home/robert/gitea/nike/.env')
|
||||
from nike import config
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
host=os.getenv('DB_HOST'),
|
||||
port=int(os.getenv('DB_PORT', 5432)),
|
||||
user=os.getenv('DB_USER'),
|
||||
password=os.getenv('DB_PASSWORD'),
|
||||
dbname='nike',
|
||||
host=config.DB_HOST,
|
||||
port=config.DB_PORT,
|
||||
user=config.DB_USER,
|
||||
password=config.DB_PASSWORD,
|
||||
dbname=config.DB_NAME,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"❌ Cannot connect to DB: {e}")
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
API Discovery Script — walk the RapidAPI top-down, save raw responses.
|
||||
|
||||
Workflow:
|
||||
1. Search for leagues (MLS, Premier League) → get league IDs
|
||||
2. Search for teams (Toronto FC, Arsenal) → get team IDs
|
||||
3. Get league matches → find recent past + upcoming event IDs
|
||||
4. Get match detail, score, stats, location for a finished match
|
||||
5. Get match highlights (goals, cards, events)
|
||||
6. Get lineups for a finished match
|
||||
7. Get squad roster + sample player detail
|
||||
8. Get standings
|
||||
|
||||
Saves every response to docs/api_samples/{step}_{endpoint}.json
|
||||
Cost: ~18 API calls
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from nike import rapidapi as rapi
|
||||
|
||||
SAMPLES_DIR = Path(__file__).resolve().parent.parent / "docs" / "api_samples"
|
||||
SAMPLES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def save(name: str, data) -> None:
|
||||
"""Save raw API response to a JSON file."""
|
||||
path = SAMPLES_DIR / f"{name}.json"
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2, default=str)
|
||||
size_kb = path.stat().st_size / 1024
|
||||
print(f" ✓ Saved {path.name} ({size_kb:.1f} KB)")
|
||||
|
||||
|
||||
def extract_id(data, label="item") -> int | None:
|
||||
"""Try to pull an ID from the first search result."""
|
||||
resp = data.get("response") if isinstance(data, dict) else None
|
||||
|
||||
# Handle nested response structures
|
||||
items = []
|
||||
if isinstance(resp, dict):
|
||||
for key in ("suggestions", "teams", "leagues", "players", "matches"):
|
||||
if key in resp and isinstance(resp[key], list) and resp[key]:
|
||||
items = resp[key]
|
||||
break
|
||||
if not items:
|
||||
items = [resp]
|
||||
elif isinstance(resp, list):
|
||||
items = resp
|
||||
else:
|
||||
# Try alternate envelope keys
|
||||
for key in ("data", "result"):
|
||||
val = data.get(key)
|
||||
if isinstance(val, list) and val:
|
||||
items = val
|
||||
break
|
||||
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
eid = item.get("id") or item.get("primaryId")
|
||||
if eid:
|
||||
name = item.get("name") or item.get("title") or "?"
|
||||
print(f" → {label}: {name} (ID: {eid})")
|
||||
return int(eid)
|
||||
|
||||
print(f" ⚠ Could not extract ID from {label} response")
|
||||
# Dump top-level keys for debugging
|
||||
if isinstance(data, dict):
|
||||
print(f" Top-level keys: {list(data.keys())}")
|
||||
if isinstance(resp, dict):
|
||||
print(f" response keys: {list(resp.keys())}")
|
||||
return None
|
||||
|
||||
|
||||
def find_event_ids(data, limit=2):
|
||||
"""Extract event IDs from a matches/fixtures response."""
|
||||
resp = data.get("response") if isinstance(data, dict) else None
|
||||
events = {"past": [], "upcoming": []}
|
||||
|
||||
items = []
|
||||
if isinstance(resp, dict):
|
||||
for key in ("matches", "events", "fixtures", "allMatches"):
|
||||
if key in resp and isinstance(resp[key], list):
|
||||
items = resp[key]
|
||||
break
|
||||
if not items:
|
||||
# Flatten if resp itself contains sub-lists
|
||||
for key, val in resp.items():
|
||||
if isinstance(val, list) and val and isinstance(val[0], dict):
|
||||
items = val
|
||||
break
|
||||
elif isinstance(resp, list):
|
||||
items = resp
|
||||
else:
|
||||
for key in ("data", "result"):
|
||||
val = data.get(key)
|
||||
if isinstance(val, list):
|
||||
items = val
|
||||
break
|
||||
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
eid = item.get("id") or item.get("eventId") or item.get("primaryId")
|
||||
if not eid:
|
||||
continue
|
||||
|
||||
# Detect finished vs upcoming
|
||||
status = item.get("status", {})
|
||||
if isinstance(status, dict):
|
||||
finished = (status.get("finished", False) or
|
||||
status.get("short") == "FT" or
|
||||
status.get("type") == "finished")
|
||||
elif isinstance(status, str):
|
||||
finished = status.lower() in ("ft", "aet", "pen", "finished")
|
||||
else:
|
||||
finished = False
|
||||
|
||||
bucket = "past" if finished else "upcoming"
|
||||
events[bucket].append(int(eid))
|
||||
|
||||
return {k: v[:limit] for k, v in events.items()}
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(" Nike API Discovery — Mapping Response Structures")
|
||||
print("=" * 60)
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 1: League discovery
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[1/8] Searching for leagues...")
|
||||
|
||||
mls = rapi.search_leagues("MLS")
|
||||
save("01_search_leagues_mls", mls)
|
||||
mls_id = extract_id(mls, "MLS")
|
||||
|
||||
epl = rapi.search_leagues("Premier League")
|
||||
save("01_search_leagues_epl", epl)
|
||||
epl_id = extract_id(epl, "Premier League")
|
||||
|
||||
popular = rapi.get_popular_leagues()
|
||||
save("01_popular_leagues", popular)
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 2: Team discovery
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[2/8] Searching for teams...")
|
||||
|
||||
tfc = rapi.search_teams("Toronto FC")
|
||||
save("02_search_teams_tfc", tfc)
|
||||
tfc_id = extract_id(tfc, "Toronto FC")
|
||||
|
||||
ars = rapi.search_teams("Arsenal")
|
||||
save("02_search_teams_arsenal", ars)
|
||||
ars_id = extract_id(ars, "Arsenal")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 3: League matches (fixtures)
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[3/8] Fetching league matches...")
|
||||
|
||||
mls_events = {"past": [], "upcoming": []}
|
||||
if mls_id:
|
||||
mls_matches = rapi.get_league_matches(mls_id)
|
||||
save("03_league_matches_mls", mls_matches)
|
||||
mls_events = find_event_ids(mls_matches)
|
||||
print(f" → Events found: {len(mls_events['past'])} past, "
|
||||
f"{len(mls_events['upcoming'])} upcoming")
|
||||
if not mls_events["past"] and not mls_events["upcoming"]:
|
||||
# Dump structure hints to help debug
|
||||
resp = mls_matches.get("response")
|
||||
if isinstance(resp, dict):
|
||||
print(f" response keys: {list(resp.keys())}")
|
||||
for k, v in resp.items():
|
||||
if isinstance(v, list) and v:
|
||||
print(f" response.{k}[0] keys: "
|
||||
f"{list(v[0].keys()) if isinstance(v[0], dict) else type(v[0])}")
|
||||
elif isinstance(resp, list) and resp:
|
||||
print(f" response[0] keys: "
|
||||
f"{list(resp[0].keys()) if isinstance(resp[0], dict) else type(resp[0])}")
|
||||
else:
|
||||
print(" ⚠ No MLS ID, skipping")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 4: Match detail (finished match)
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[4/8] Fetching match detail...")
|
||||
|
||||
event_id = None
|
||||
if mls_events["past"]:
|
||||
event_id = mls_events["past"][0]
|
||||
elif mls_events["upcoming"]:
|
||||
event_id = mls_events["upcoming"][0]
|
||||
|
||||
if event_id:
|
||||
print(f" Using event ID: {event_id}")
|
||||
|
||||
detail = rapi.get_match_detail(event_id)
|
||||
save("04_match_detail", detail)
|
||||
|
||||
score = rapi.get_match_score(event_id)
|
||||
save("04_match_score", score)
|
||||
|
||||
status = rapi.get_match_status(event_id)
|
||||
save("04_match_status", status)
|
||||
|
||||
location = rapi.get_match_location(event_id)
|
||||
save("04_match_location", location)
|
||||
else:
|
||||
print(" ⚠ No event ID found — skipping match detail")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 5: Match stats + highlights (goals, cards, events)
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[5/8] Fetching match stats & highlights...")
|
||||
|
||||
if event_id:
|
||||
stats = rapi.get_match_stats(event_id)
|
||||
save("05_match_stats", stats)
|
||||
|
||||
highlights = rapi.get_match_highlights(event_id)
|
||||
save("05_match_highlights", highlights)
|
||||
|
||||
referee = rapi.get_match_referee(event_id)
|
||||
save("05_match_referee", referee)
|
||||
else:
|
||||
print(" ⚠ Skipping (no event ID)")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 6: Lineups
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[6/8] Fetching lineups...")
|
||||
|
||||
if event_id:
|
||||
home_lineup = rapi.get_home_lineup(event_id)
|
||||
save("06_lineup_home", home_lineup)
|
||||
|
||||
away_lineup = rapi.get_away_lineup(event_id)
|
||||
save("06_lineup_away", away_lineup)
|
||||
else:
|
||||
print(" ⚠ Skipping (no event ID)")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 7: Squad & player detail
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[7/8] Fetching squad & player detail...")
|
||||
|
||||
player_id = None
|
||||
if tfc_id:
|
||||
squad = rapi.get_squad(tfc_id)
|
||||
save("07_squad_tfc", squad)
|
||||
|
||||
# Extract a player ID from the squad response
|
||||
resp = squad.get("response") if isinstance(squad, dict) else None
|
||||
|
||||
if isinstance(resp, list) and resp:
|
||||
for p in resp:
|
||||
if isinstance(p, dict):
|
||||
pid = p.get("id") or p.get("primaryId")
|
||||
if pid:
|
||||
player_id = int(pid)
|
||||
print(f" → Sample player: {p.get('name', '?')} (ID: {player_id})")
|
||||
break
|
||||
elif isinstance(resp, dict):
|
||||
# May be nested under squad/players/roster
|
||||
for key in ("squad", "players", "roster", "members"):
|
||||
if key in resp and isinstance(resp[key], list):
|
||||
for p in resp[key]:
|
||||
if isinstance(p, dict):
|
||||
pid = p.get("id") or p.get("primaryId")
|
||||
if pid:
|
||||
player_id = int(pid)
|
||||
print(f" → Sample player: {p.get('name', '?')} "
|
||||
f"(ID: {player_id})")
|
||||
break
|
||||
break
|
||||
if not player_id:
|
||||
print(f" response keys: {list(resp.keys())}")
|
||||
|
||||
if player_id:
|
||||
player = rapi.get_player_detail(player_id)
|
||||
save("07_player_detail", player)
|
||||
else:
|
||||
print(" ⚠ Could not find a player ID in squad response")
|
||||
else:
|
||||
print(" ⚠ No TFC ID, skipping")
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# STEP 8: Standings
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n[8/8] Fetching standings...")
|
||||
|
||||
if mls_id:
|
||||
standings = rapi.get_standings(mls_id)
|
||||
save("08_standings_mls", standings)
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# Summary
|
||||
# ══════════════════════════════════════════════════════
|
||||
print("\n" + "=" * 60)
|
||||
files = sorted(SAMPLES_DIR.glob("*.json"))
|
||||
print(f" Saved {len(files)} response samples to docs/api_samples/")
|
||||
for f in files:
|
||||
size_kb = f.stat().st_size / 1024
|
||||
print(f" {f.name:.<50} {size_kb:.1f} KB")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n Discovered IDs:")
|
||||
print(f" MLS league ID : {mls_id}")
|
||||
print(f" EPL league ID : {epl_id}")
|
||||
print(f" Toronto FC team ID : {tfc_id}")
|
||||
print(f" Arsenal team ID : {ars_id}")
|
||||
if event_id:
|
||||
print(f" Sample event ID : {event_id}")
|
||||
if player_id:
|
||||
print(f" Sample player ID : {player_id}")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pull Toronto FC squad and fixtures and store in Portia DB.
|
||||
|
||||
Delegates all API calls and DB writes to nike.sync.sync_team_data()
|
||||
so this script stays thin and the real logic lives in one place.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Make sure the project root is on the path when run directly
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from datetime import date
|
||||
|
||||
from nike import config, db
|
||||
from nike.sync import sync_team_data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not config.API_FOOTBALL_KEY:
|
||||
print("❌ API_FOOTBALL_KEY not set in .env")
|
||||
sys.exit(1)
|
||||
|
||||
print("=" * 60)
|
||||
print(" NIKE — Toronto FC Data Pull")
|
||||
print(f" API: {config.API_FOOTBALL_BASE}")
|
||||
print("=" * 60)
|
||||
|
||||
db.create_pool()
|
||||
|
||||
try:
|
||||
results = sync_team_data(
|
||||
team_search=config.TFC_SEARCH,
|
||||
seasons=config.SEASONS,
|
||||
)
|
||||
finally:
|
||||
db.close_pool()
|
||||
|
||||
# ── Summary ──────────────────────────────────────────
|
||||
if results["errors"]:
|
||||
print("\n⚠️ Errors encountered:")
|
||||
for e in results["errors"]:
|
||||
print(f" • {e}")
|
||||
|
||||
print(f"\n✅ Team: {results.get('team', 'unknown')}")
|
||||
print(f" Players: {results['players']}")
|
||||
print(f" API calls: {results['api_calls']}")
|
||||
for season, count in results.get("seasons", {}).items():
|
||||
print(f" Fixtures {season}: {count}")
|
||||
|
||||
if results.get("squad"):
|
||||
_print_squad(results["squad"])
|
||||
|
||||
if results.get("all_fixtures"):
|
||||
_print_fixtures(results["all_fixtures"])
|
||||
|
||||
|
||||
def _print_squad(squad: list[dict]) -> None:
|
||||
print("\n" + "=" * 60)
|
||||
print(" SQUAD")
|
||||
print("=" * 60)
|
||||
pos_order = ["Goalkeeper", "Defender", "Midfielder", "Attacker"]
|
||||
by_pos: dict = {}
|
||||
for p in squad:
|
||||
by_pos.setdefault(p.get("position") or "Unknown", []).append(p)
|
||||
|
||||
for pos in pos_order + [k for k in by_pos if k not in pos_order]:
|
||||
if pos not in by_pos:
|
||||
continue
|
||||
print(f"\n {pos.upper()}S:")
|
||||
for p in sorted(by_pos[pos],
|
||||
key=lambda x: x["number"] if isinstance(x.get("number"), int) else 99):
|
||||
print(f" #{str(p.get('number', '-')).rjust(2)} {p['name']}")
|
||||
|
||||
|
||||
def _print_fixtures(fixtures: list[dict]) -> None:
|
||||
print("\n" + "=" * 60)
|
||||
print(" FIXTURES")
|
||||
print("=" * 60)
|
||||
today = date.today().isoformat()
|
||||
|
||||
today_f = [f for f in fixtures if f["date"][:10] == today]
|
||||
completed = [f for f in fixtures if f["status"] in ("FT", "AET", "PEN")]
|
||||
upcoming = [f for f in fixtures if f["status"] in ("NS", "TBD", "PST")]
|
||||
|
||||
if today_f:
|
||||
print("\n 🔴 TODAY:")
|
||||
for f in today_f:
|
||||
score = f" {f['score']}" if f.get("score") else ""
|
||||
print(f" {f['home']}{score} {f['away']} @ {f['venue']} [{f['status']}]")
|
||||
if completed:
|
||||
print("\n RECENT RESULTS (last 5):")
|
||||
for f in completed[-5:]:
|
||||
print(f" {f['date'][:10]} {f['home']} {f['score']} {f['away']}")
|
||||
if upcoming:
|
||||
print("\n UPCOMING (next 5):")
|
||||
for f in sorted(upcoming, key=lambda x: x["date"])[:5]:
|
||||
print(f" {f['date'][:10]} {f['home']} vs {f['away']} [{f.get('round', '')}]")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test API connectivity, find Toronto FC, and list popular leagues
|
||||
so we can confirm the correct MLS league ID for this API.
|
||||
|
||||
Uses 2 API quota calls.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from nike import config, api_football
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not config.API_FOOTBALL_KEY:
|
||||
print("❌ API_FOOTBALL_KEY not set in .env")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"API: {config.API_FOOTBALL_BASE}\n")
|
||||
|
||||
# ── 1. Popular leagues (connectivity probe + league ID discovery) ──
|
||||
print("── Popular Leagues ──────────────────────────")
|
||||
try:
|
||||
t0 = time.time()
|
||||
data = api_football._get("football-popular-leagues", timeout=8)
|
||||
latency_ms = round((time.time() - t0) * 1000, 1)
|
||||
except Exception as e:
|
||||
print(f"❌ Not connected: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"✅ Connected latency={latency_ms}ms "
|
||||
f"quota_remaining={api_football.last_quota_remaining()}")
|
||||
|
||||
leagues = (data.get('response', {}).get('popular') or
|
||||
data.get('response', {}).get('leagues') or [])
|
||||
if not isinstance(leagues, list):
|
||||
print(f" Raw response structure:\n{json.dumps(data, indent=2)[:1000]}")
|
||||
else:
|
||||
print(f" {len(leagues)} league(s) returned:")
|
||||
for lg in leagues:
|
||||
lg_id = lg.get('id', '?')
|
||||
lg_name = lg.get('name', str(lg))
|
||||
ccode = lg.get('ccode', '')
|
||||
print(f" [{str(lg_id):>5}] {lg_name} ({ccode})")
|
||||
print(f"\n ↳ Current config.MLS_LEAGUE_ID = {config.MLS_LEAGUE_ID}")
|
||||
print(" Update nike/config.py if the ID above doesn't match MLS.\n")
|
||||
|
||||
# ── 2. Search for Toronto FC ───────────────────────────────────
|
||||
print("── Search: Toronto ──────────────────────────")
|
||||
raw_search = api_football._get("football-teams-search", {"search": "Toronto"})
|
||||
print(f" quota_remaining={api_football.last_quota_remaining()}")
|
||||
|
||||
# Try all common envelope keys; fall back to full raw dump
|
||||
raw_list = (raw_search.get('response', {}).get('suggestions') or
|
||||
raw_search.get('response', {}).get('teams') or
|
||||
raw_search.get('data') or raw_search.get('result'))
|
||||
if not isinstance(raw_list, list) or not raw_list:
|
||||
print(f" 0 results — raw response:\n{json.dumps(raw_search, indent=2)[:1500]}")
|
||||
else:
|
||||
teams = [api_football._normalise_team_item(t)
|
||||
for t in raw_list if t.get('type', 'team') == 'team']
|
||||
print(f" {len(teams)} team result(s):")
|
||||
for item in teams:
|
||||
t = item['team']
|
||||
v = item.get('venue') or {}
|
||||
print(f" [{str(t.get('id')):>6}] {t.get('name')} ({t.get('country')})")
|
||||
if v.get('name'):
|
||||
print(f" Venue: {v.get('name')}, {v.get('city')} cap={v.get('capacity')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test connection to Portia PostgreSQL."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import psycopg2
|
||||
|
||||
load_dotenv('/home/robert/gitea/nike/.env')
|
||||
|
||||
try:
|
||||
t0 = time.time()
|
||||
conn = psycopg2.connect(
|
||||
host=os.getenv('DB_HOST'),
|
||||
port=int(os.getenv('DB_PORT', 5432)),
|
||||
user=os.getenv('DB_USER'),
|
||||
password=os.getenv('DB_PASSWORD'),
|
||||
dbname='nike',
|
||||
connect_timeout=5,
|
||||
)
|
||||
latency_ms = round((time.time() - t0) * 1000, 1)
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT version();')
|
||||
version = cur.fetchone()[0]
|
||||
cur.execute("SELECT current_database(), current_user;")
|
||||
db, user = cur.fetchone()
|
||||
cur.close()
|
||||
conn.close()
|
||||
print(f"✅ Connected in {latency_ms}ms")
|
||||
print(f" Database : {db}")
|
||||
print(f" User : {user}")
|
||||
print(f" Version : {version.split(',')[0]}")
|
||||
except Exception as e:
|
||||
print(f"❌ Connection failed: {e}")
|
||||
sys.exit(1)
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Smoke test for the RapidAPI (free-api-live-football-data) backend.
|
||||
|
||||
Verifies connectivity, finds MLS, fetches standings and TFC squad.
|
||||
Run: python scripts/test_rapidapi.py
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure project root is on the path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from nike import config
|
||||
from nike import rapidapi as rapi
|
||||
|
||||
|
||||
def _pp(label: str, data) -> None:
|
||||
"""Pretty-print a section."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {label}")
|
||||
print(f"{'='*60}")
|
||||
print(json.dumps(data, indent=2, default=str)[:3000]) # trim for readability
|
||||
|
||||
|
||||
def main():
|
||||
if not config.RAPIDAPI_KEY:
|
||||
print("ERROR: RAPIDAPI_KEY is not set in .env")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"RapidAPI key: {config.RAPIDAPI_KEY[:8]}...{config.RAPIDAPI_KEY[-4:]}")
|
||||
print(f"Base URL: {config.RAPIDAPI_BASE}")
|
||||
|
||||
# 1. Connectivity
|
||||
print("\n1) Checking connectivity...")
|
||||
status = rapi.check_connection()
|
||||
print(f" Connected: {status['connected']}")
|
||||
if not status["connected"]:
|
||||
print(f" Error: {status.get('error')}")
|
||||
sys.exit(1)
|
||||
print(f" Latency: {status['latency_ms']} ms")
|
||||
|
||||
# 2. Search for MLS
|
||||
print("\n2) Searching for 'MLS'...")
|
||||
mls_data = rapi.search_leagues("MLS")
|
||||
_pp("MLS search results", mls_data)
|
||||
|
||||
# Try to extract league ID
|
||||
mls_id = None
|
||||
resp = mls_data.get("response") if isinstance(mls_data, dict) else None
|
||||
if isinstance(resp, list):
|
||||
for item in resp:
|
||||
if isinstance(item, dict):
|
||||
mls_id = item.get("id") or item.get("primaryId")
|
||||
if mls_id:
|
||||
mls_id = int(mls_id)
|
||||
break
|
||||
print(f"\n MLS League ID: {mls_id}")
|
||||
|
||||
# 3. Standings
|
||||
if mls_id:
|
||||
print("\n3) Fetching MLS standings...")
|
||||
standings = rapi.get_standings(mls_id)
|
||||
_pp("MLS Standings", standings)
|
||||
|
||||
# 4. Search for Toronto FC
|
||||
print("\n4) Searching for 'Toronto FC'...")
|
||||
tfc_data = rapi.search_teams("Toronto FC")
|
||||
_pp("TFC search results", tfc_data)
|
||||
|
||||
tfc_id = None
|
||||
resp = tfc_data.get("response") if isinstance(tfc_data, dict) else None
|
||||
if isinstance(resp, list):
|
||||
for item in resp:
|
||||
if isinstance(item, dict):
|
||||
tfc_id = item.get("id") or item.get("primaryId")
|
||||
if tfc_id:
|
||||
tfc_id = int(tfc_id)
|
||||
break
|
||||
print(f"\n TFC Team ID: {tfc_id}")
|
||||
|
||||
# 5. Squad
|
||||
if tfc_id:
|
||||
print("\n5) Fetching TFC squad...")
|
||||
squad = rapi.get_squad(tfc_id)
|
||||
_pp("TFC Squad", squad)
|
||||
|
||||
# 6. Live matches (just check it works)
|
||||
print("\n6) Checking live matches endpoint...")
|
||||
live = rapi.get_live_matches()
|
||||
resp = live.get("response") if isinstance(live, dict) else None
|
||||
count = len(resp) if isinstance(resp, list) else 0
|
||||
print(f" Live matches right now: {count}")
|
||||
|
||||
# 7. Trending news
|
||||
print("\n7) Fetching trending news...")
|
||||
news_data = rapi.get_trending_news()
|
||||
_pp("Trending News", news_data)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(" ALL CHECKS PASSED")
|
||||
print("="*60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Quick verification of Nike DB contents."""
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import psycopg2
|
||||
|
||||
load_dotenv('/home/robert/gitea/nike/.env')
|
||||
|
||||
conn = psycopg2.connect(
|
||||
host=os.getenv('DB_HOST'),
|
||||
port=int(os.getenv('DB_PORT', 5432)),
|
||||
user=os.getenv('DB_USER'),
|
||||
password=os.getenv('DB_PASSWORD'),
|
||||
dbname='nike',
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
tables = [
|
||||
'leagues', 'teams', 'players', 'fixtures', 'standings',
|
||||
'match_stats', 'match_events', 'player_season_stats',
|
||||
'player_match_stats', 'followed_entities',
|
||||
]
|
||||
|
||||
print("Nike DB Contents")
|
||||
print("-" * 40)
|
||||
for table in tables:
|
||||
cur.execute(f"SELECT COUNT(*) FROM {table}")
|
||||
count = cur.fetchone()[0]
|
||||
status = "✅" if count > 0 else "•"
|
||||
print(f" {status} {table:<28} {count:>6} rows")
|
||||
|
||||
# TFC summary
|
||||
print()
|
||||
cur.execute("""
|
||||
SELECT t.name, COUNT(p.id) AS players
|
||||
FROM teams t
|
||||
LEFT JOIN players p ON p.current_team_id = t.id
|
||||
WHERE t.is_followed = TRUE
|
||||
GROUP BY t.name
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ⚽ {row[0]}: {row[1]} players in roster")
|
||||
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) FROM fixtures f
|
||||
JOIN teams t ON (t.id = f.home_team_id OR t.id = f.away_team_id)
|
||||
WHERE t.is_followed = TRUE
|
||||
""")
|
||||
fix_count = cur.fetchone()[0]
|
||||
print(f" 📅 Followed team fixtures: {fix_count}")
|
||||
|
||||
cur.execute("""
|
||||
SELECT match_date::date, home.name, away.name, home_goals, away_goals, status
|
||||
FROM fixtures f
|
||||
JOIN teams home ON home.id = f.home_team_id
|
||||
JOIN teams away ON away.id = f.away_team_id
|
||||
JOIN teams ft ON (ft.id = f.home_team_id OR ft.id = f.away_team_id)
|
||||
WHERE ft.is_followed = TRUE AND f.match_date >= NOW()
|
||||
ORDER BY f.match_date ASC
|
||||
LIMIT 3
|
||||
""")
|
||||
upcoming = cur.fetchall()
|
||||
if upcoming:
|
||||
print()
|
||||
print(" Next fixtures:")
|
||||
for row in upcoming:
|
||||
print(f" {row[0]} {row[1]} vs {row[2]} ({row[5]})")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user