feat: implement MCP server and dashboard for football data platform
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
This commit is contained in:
194
schema.sql
Normal file
194
schema.sql
Normal file
@@ -0,0 +1,194 @@
|
||||
-- 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);
|
||||
Reference in New Issue
Block a user