feat: initial commit — World Cup stats app with pnpm, Traefik, Docker
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>
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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!
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user