58b4114159
Full-stack World Cup web app (1930–2026): - Next.js 16 + TailwindCSS 4 + GraphQL Yoga + Apollo Client 4 + Drizzle + PostgreSQL 16 - 23 tournaments synced from openfootball/worldcup.json (matches, goals, teams, stadiums, squads, standings) - Pages: home (live), groups, stats, history, search, /tournaments/[year], /teams/[slug], /players/[name] - Live match detection via isLive() + Apollo 60 s poll - pnpm with node-linker=hoisted for Docker compatibility - docker-compose.yml with Traefik labels (HTTPS redirect, TLS, security middleware) - docker-compose.dev.yml for local dev (DB only, port 5432 exposed) - Dockerfile: multi-stage pnpm build, standalone Next.js output, sync script bundled - .env.example with all required variables documented - Comprehensive README with local dev, deployment, schema, and GraphQL API reference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
200 lines
3.7 KiB
TypeScript
200 lines
3.7 KiB
TypeScript
export const typeDefs = /* GraphQL */ `
|
|
type Tournament {
|
|
year: Int!
|
|
host: String!
|
|
winner: String
|
|
runnerUp: String
|
|
thirdPlace: String
|
|
fourthPlace: String
|
|
teamsCount: Int
|
|
matchesCount: Int
|
|
totalGoals: Int
|
|
avgGoalsPerGame: Float
|
|
topScorers: [ScorerEntry!]!
|
|
matches: [Match!]!
|
|
}
|
|
|
|
type Team {
|
|
id: Int!
|
|
name: String!
|
|
slug: String!
|
|
iso2: String
|
|
fifaCode: String
|
|
continent: String
|
|
confederation: String
|
|
stats: TeamStats
|
|
}
|
|
|
|
type TeamStats {
|
|
appearances: Int!
|
|
wins: Int!
|
|
draws: Int!
|
|
losses: Int!
|
|
goalsFor: Int!
|
|
goalsAgainst: Int!
|
|
goalDiff: Int!
|
|
titles: Int!
|
|
winPct: Float!
|
|
}
|
|
|
|
type Stadium {
|
|
id: Int!
|
|
tournamentYear: Int
|
|
name: String!
|
|
city: String
|
|
countryCode: String
|
|
capacity: Int
|
|
timezone: String
|
|
coordinates: String
|
|
matchCount: Int
|
|
}
|
|
|
|
type Match {
|
|
id: Int!
|
|
year: Int!
|
|
round: String!
|
|
group: String
|
|
date: String
|
|
time: String
|
|
stadium: String
|
|
team1: Team!
|
|
team2: Team!
|
|
scoreFt: [Int!]
|
|
scoreHt: [Int!]
|
|
scoreEt: [Int!]
|
|
scoreP: [Int!]
|
|
goals: [Goal!]!
|
|
isLive: Boolean!
|
|
isQualiPlayoff: Boolean!
|
|
margin: Int
|
|
totalGoals: Int
|
|
}
|
|
|
|
type Goal {
|
|
id: Int!
|
|
team: Team!
|
|
playerName: String!
|
|
minute: Int
|
|
minuteOffset: Int
|
|
isPenalty: Boolean!
|
|
isOwnGoal: Boolean!
|
|
}
|
|
|
|
type ScorerEntry {
|
|
playerName: String!
|
|
team: Team
|
|
goals: Int!
|
|
penalties: Int!
|
|
ownGoals: Int!
|
|
tournaments: Int!
|
|
}
|
|
|
|
type GroupStanding {
|
|
groupName: String!
|
|
pos: Int
|
|
team: Team!
|
|
played: Int!
|
|
won: Int!
|
|
drawn: Int!
|
|
lost: Int!
|
|
goalsFor: Int!
|
|
goalsAgainst: Int!
|
|
goalDiff: Int!
|
|
pts: Int!
|
|
}
|
|
|
|
type SquadPlayer {
|
|
playerName: String!
|
|
shirtNumber: Int
|
|
position: String
|
|
dateOfBirth: String
|
|
team: Team!
|
|
age: Int
|
|
}
|
|
|
|
type GlobalStats {
|
|
totalTournaments: Int!
|
|
totalMatches: Int!
|
|
totalGoals: Int!
|
|
avgGoalsPerGame: Float!
|
|
mostGoalsInTournament: TournamentGoalRecord
|
|
highestScoringMatch: Match
|
|
biggestWin: Match
|
|
mostTitles: ScorerEntry
|
|
}
|
|
|
|
type TournamentGoalRecord {
|
|
year: Int!
|
|
host: String!
|
|
totalGoals: Int!
|
|
}
|
|
|
|
type HatTrick {
|
|
playerName: String!
|
|
team: Team
|
|
year: Int!
|
|
round: String!
|
|
opponent: Team
|
|
goals: Int!
|
|
}
|
|
|
|
type MinuteBucket {
|
|
bucket: String!
|
|
count: Int!
|
|
}
|
|
|
|
type ConfederationStat {
|
|
confederation: String!
|
|
appearances: Int!
|
|
titles: Int!
|
|
totalGoals: Int!
|
|
}
|
|
|
|
type SearchResults {
|
|
tournaments: [Tournament!]!
|
|
teams: [Team!]!
|
|
players: [ScorerEntry!]!
|
|
matches: [Match!]!
|
|
}
|
|
|
|
type Query {
|
|
tournaments: [Tournament!]!
|
|
tournament(year: Int!): Tournament
|
|
|
|
matches(year: Int, group: String, round: String, isQuali: Boolean): [Match!]!
|
|
match(id: Int!): Match
|
|
liveMatches: [Match!]!
|
|
recentMatches(limit: Int): [Match!]!
|
|
upcomingMatches(limit: Int): [Match!]!
|
|
|
|
teams: [Team!]!
|
|
team(slug: String!): Team
|
|
|
|
topScorers(year: Int, limit: Int): [ScorerEntry!]!
|
|
player(name: String!): ScorerEntry
|
|
hatTricks(year: Int): [HatTrick!]!
|
|
|
|
groupStandings(year: Int!): [GroupStanding!]!
|
|
|
|
stadiums(year: Int): [Stadium!]!
|
|
squads(year: Int!, team: String): [SquadPlayer!]!
|
|
|
|
tournamentStats: GlobalStats!
|
|
goalsByMinute: [MinuteBucket!]!
|
|
confederationStats: [ConfederationStat!]!
|
|
biggestWins(limit: Int): [Match!]!
|
|
highestScoringMatches(limit: Int): [Match!]!
|
|
extraTimeStats: ExtraTimeStats!
|
|
|
|
search(query: String!): SearchResults!
|
|
}
|
|
|
|
type ExtraTimeStats {
|
|
totalKnockoutMatches: Int!
|
|
wentToExtraTime: Int!
|
|
wentToPenalties: Int!
|
|
extraTimePct: Float!
|
|
penaltiesPct: Float!
|
|
}
|
|
`
|