Add complete Nike football data platform with: - FastMCP server exposing football data tools over HTTP - RapidAPI client for free-api-live-football-data integration - Bootstrap web dashboard with live match/standings views - REST API endpoints for dashboard consumption - Docker support with multi-stage build - Comprehensive README with architecture docs - Minimal .gitignore replacing verbose Python template
195 lines
12 KiB
SQL
195 lines
12 KiB
SQL
-- Nike Football Database Schema — TheSportsDB cache layer
|
|
--
|
|
-- Caching strategy:
|
|
-- PERMANENT: leagues, teams, players (change rarely, cache aggressively)
|
|
-- SEMI-PERMANENT: finished match results, lineups, stats, timeline
|
|
-- VOLATILE (NOT cached): standings, live scores, upcoming fixtures
|
|
--
|
|
-- All IDs come from TheSportsDB (idLeague, idTeam, idEvent, idPlayer).
|
|
-- No surrogate keys — TheSportsDB IDs are the primary keys.
|
|
|
|
-- ══════════════════════════════════════════════════════════
|
|
-- Core reference tables (permanent cache)
|
|
-- ══════════════════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS leagues (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idLeague
|
|
name VARCHAR(255) NOT NULL, -- strLeague
|
|
sport VARCHAR(50) DEFAULT 'Soccer', -- strSport
|
|
country VARCHAR(100), -- strCountry
|
|
badge_url VARCHAR(500), -- strBadge
|
|
banner_url VARCHAR(500), -- strBanner
|
|
description TEXT, -- strDescriptionEN
|
|
is_followed BOOLEAN DEFAULT FALSE,
|
|
cached_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS teams (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idTeam
|
|
name VARCHAR(255) NOT NULL, -- strTeam
|
|
short_name VARCHAR(10), -- strTeamShort
|
|
alternate_name VARCHAR(255), -- strTeamAlternate
|
|
league_id INTEGER, -- idLeague (primary), no FK — cached opportunistically
|
|
league_name VARCHAR(255), -- strLeague
|
|
formed_year INTEGER, -- intFormedYear
|
|
sport VARCHAR(50) DEFAULT 'Soccer',
|
|
country VARCHAR(100), -- strCountry
|
|
stadium VARCHAR(255), -- strStadium
|
|
stadium_capacity INTEGER, -- intStadiumCapacity
|
|
location VARCHAR(255), -- strLocation
|
|
venue_id INTEGER, -- idVenue
|
|
badge_url VARCHAR(500), -- strBadge
|
|
logo_url VARCHAR(500), -- strLogo
|
|
banner_url VARCHAR(500), -- strBanner
|
|
equipment_url VARCHAR(500), -- strEquipment
|
|
colour1 VARCHAR(20), -- strColour1
|
|
colour2 VARCHAR(20), -- strColour2
|
|
colour3 VARCHAR(20), -- strColour3
|
|
website VARCHAR(255), -- strWebsite
|
|
description TEXT, -- strDescriptionEN
|
|
is_followed BOOLEAN DEFAULT FALSE,
|
|
cached_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS players (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idPlayer
|
|
team_id INTEGER, -- idTeam, no FK — cached opportunistically
|
|
team_name VARCHAR(255), -- strTeam
|
|
name VARCHAR(255) NOT NULL, -- strPlayer
|
|
nationality VARCHAR(100), -- strNationality
|
|
date_of_birth DATE, -- dateBorn
|
|
position VARCHAR(50), -- strPosition
|
|
squad_number VARCHAR(10), -- strNumber
|
|
height VARCHAR(20), -- strHeight
|
|
weight VARCHAR(20), -- strWeight
|
|
gender VARCHAR(10), -- strGender
|
|
status VARCHAR(30), -- strStatus (Active/Retired)
|
|
thumb_url VARCHAR(500), -- strThumb
|
|
cutout_url VARCHAR(500), -- strCutout
|
|
description TEXT, -- strDescriptionEN
|
|
cached_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ══════════════════════════════════════════════════════════
|
|
-- Match results (cached when strStatus = 'Match Finished')
|
|
-- ══════════════════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idEvent
|
|
name VARCHAR(255), -- strEvent
|
|
league_id INTEGER, -- idLeague, no FK — cached opportunistically
|
|
league_name VARCHAR(255), -- strLeague
|
|
season VARCHAR(20), -- strSeason
|
|
round VARCHAR(20), -- intRound
|
|
home_team_id INTEGER, -- idHomeTeam, no FK — cached opportunistically
|
|
away_team_id INTEGER, -- idAwayTeam, no FK — cached opportunistically
|
|
home_team VARCHAR(255), -- strHomeTeam
|
|
away_team VARCHAR(255), -- strAwayTeam
|
|
home_score INTEGER, -- intHomeScore
|
|
away_score INTEGER, -- intAwayScore
|
|
event_date DATE, -- dateEvent
|
|
event_time TIME, -- strTime
|
|
event_timestamp TIMESTAMPTZ, -- strTimestamp
|
|
venue VARCHAR(255), -- strVenue
|
|
venue_id INTEGER, -- idVenue
|
|
city VARCHAR(100), -- strCity
|
|
country VARCHAR(100), -- strCountry
|
|
referee VARCHAR(255), -- strOfficial
|
|
spectators INTEGER, -- intSpectators
|
|
status VARCHAR(50), -- strStatus
|
|
postponed VARCHAR(5) DEFAULT 'no', -- strPostponed
|
|
poster_url VARCHAR(500), -- strPoster
|
|
thumb_url VARCHAR(500), -- strThumb
|
|
video_url VARCHAR(500), -- strVideo
|
|
cached_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Match detail sub-tables (V2, cached for finished matches) ──
|
|
|
|
CREATE TABLE IF NOT EXISTS event_stats (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idStatistic
|
|
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
id_api_football VARCHAR(20), -- idApiFootball
|
|
event_name VARCHAR(255), -- strEvent
|
|
stat_name VARCHAR(100) NOT NULL, -- strStat (e.g. "Shots on Goal")
|
|
home_value VARCHAR(50), -- intHome
|
|
away_value VARCHAR(50), -- intAway
|
|
UNIQUE(event_id, stat_name)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS event_lineup (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idLineup
|
|
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
id_api_football VARCHAR(20), -- idAPIfootball
|
|
event_name VARCHAR(255), -- strEvent
|
|
player_id INTEGER, -- idPlayer
|
|
player_name VARCHAR(255), -- strPlayer
|
|
team_id INTEGER, -- idTeam
|
|
team_name VARCHAR(255), -- strTeam
|
|
is_home VARCHAR(5), -- strHome ("Yes"/"No")
|
|
position VARCHAR(50), -- strPosition
|
|
position_short VARCHAR(5), -- strPositionShort (G/D/M/F)
|
|
formation VARCHAR(20), -- strFormation
|
|
squad_number INTEGER, -- intSquadNumber
|
|
is_substitute VARCHAR(5) DEFAULT 'No', -- strSubstitute ("Yes"/"No")
|
|
cutout_url VARCHAR(500), -- strCutout
|
|
country VARCHAR(100), -- strCountry
|
|
season VARCHAR(20), -- strSeason
|
|
UNIQUE(event_id, player_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS event_timeline (
|
|
id INTEGER PRIMARY KEY, -- TheSportsDB idTimeline
|
|
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
id_api_football VARCHAR(20), -- idAPIfootball
|
|
event_name VARCHAR(255), -- strEvent
|
|
event_type VARCHAR(100), -- strTimeline ("Goal", "subst", "Yellow Card")
|
|
event_detail VARCHAR(255), -- strTimelineDetail ("Substitution 1")
|
|
is_home VARCHAR(5), -- strHome ("Yes"/"No")
|
|
minute INTEGER, -- intTime
|
|
period VARCHAR(20), -- strPeriod
|
|
player_id INTEGER, -- idPlayer
|
|
player_name VARCHAR(255), -- strPlayer
|
|
cutout_url VARCHAR(500), -- strCutout
|
|
assist_id INTEGER, -- idAssist
|
|
assist_name VARCHAR(255), -- strAssist
|
|
team_id INTEGER, -- idTeam
|
|
team_name VARCHAR(255), -- strTeam
|
|
comment TEXT, -- strComment
|
|
event_date DATE, -- dateEvent
|
|
season VARCHAR(20), -- strSeason
|
|
UNIQUE(event_id, minute, player_id, event_type)
|
|
);
|
|
|
|
-- ══════════════════════════════════════════════════════════
|
|
-- Cache management
|
|
-- ══════════════════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
cache_key VARCHAR(255) PRIMARY KEY, -- e.g. "standings:4346:2026"
|
|
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
ttl_seconds INTEGER NOT NULL DEFAULT 3600,
|
|
data_json JSONB -- optional: store raw response
|
|
);
|
|
|
|
-- ══════════════════════════════════════════════════════════
|
|
-- Indexes
|
|
-- ══════════════════════════════════════════════════════════
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_teams_league ON teams(league_id);
|
|
CREATE INDEX IF NOT EXISTS idx_teams_followed ON teams(is_followed) WHERE is_followed;
|
|
CREATE INDEX IF NOT EXISTS idx_players_team ON players(team_id);
|
|
CREATE INDEX IF NOT EXISTS idx_events_date ON events(event_date);
|
|
CREATE INDEX IF NOT EXISTS idx_events_home_team ON events(home_team_id);
|
|
CREATE INDEX IF NOT EXISTS idx_events_away_team ON events(away_team_id);
|
|
CREATE INDEX IF NOT EXISTS idx_events_league ON events(league_id);
|
|
CREATE INDEX IF NOT EXISTS idx_events_status ON events(status);
|
|
CREATE INDEX IF NOT EXISTS idx_event_stats_event ON event_stats(event_id);
|
|
CREATE INDEX IF NOT EXISTS idx_event_lineup_event ON event_lineup(event_id);
|
|
CREATE INDEX IF NOT EXISTS idx_event_tl_event ON event_timeline(event_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cache_meta_fetched ON cache_meta(fetched_at);
|