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,91 @@
|
||||
import { pgTable, serial, integer, text, boolean, numeric, date, primaryKey } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const tournaments = pgTable('tournaments', {
|
||||
year: integer('year').primaryKey(),
|
||||
host: text('host').notNull(),
|
||||
winner: text('winner'),
|
||||
runnerUp: text('runner_up'),
|
||||
thirdPlace: text('third_place'),
|
||||
fourthPlace: text('fourth_place'),
|
||||
teamsCount: integer('teams_count'),
|
||||
matchesCount: integer('matches_count'),
|
||||
totalGoals: integer('total_goals'),
|
||||
avgGoalsPerGame: numeric('avg_goals_per_game', { precision: 4, scale: 2 }),
|
||||
})
|
||||
|
||||
export const teams = pgTable('teams', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').unique().notNull(),
|
||||
iso2: text('iso2'),
|
||||
fifaCode: text('fifa_code'),
|
||||
continent: text('continent'),
|
||||
confederation: text('confederation'),
|
||||
})
|
||||
|
||||
export const stadiums = pgTable('stadiums', {
|
||||
id: serial('id').primaryKey(),
|
||||
tournamentYear: integer('tournament_year'),
|
||||
name: text('name').notNull(),
|
||||
city: text('city'),
|
||||
countryCode: text('country_code'),
|
||||
capacity: integer('capacity'),
|
||||
timezone: text('timezone'),
|
||||
coordinates: text('coordinates'),
|
||||
})
|
||||
|
||||
export const matches = pgTable('matches', {
|
||||
id: serial('id').primaryKey(),
|
||||
tournamentYear: integer('tournament_year').notNull(),
|
||||
round: text('round').notNull(),
|
||||
groupName: text('group_name'),
|
||||
date: date('date'),
|
||||
timeLocal: text('time_local'),
|
||||
stadiumId: integer('stadium_id'),
|
||||
team1Id: integer('team1_id').notNull(),
|
||||
team2Id: integer('team2_id').notNull(),
|
||||
scoreFtHome: integer('score_ft_home'),
|
||||
scoreFtAway: integer('score_ft_away'),
|
||||
scoreHtHome: integer('score_ht_home'),
|
||||
scoreHtAway: integer('score_ht_away'),
|
||||
scoreEtHome: integer('score_et_home'),
|
||||
scoreEtAway: integer('score_et_away'),
|
||||
scorePHome: integer('score_p_home'),
|
||||
scorePAway: integer('score_p_away'),
|
||||
isQualiPlayoff: boolean('is_quali_playoff').default(false),
|
||||
})
|
||||
|
||||
export const goals = pgTable('goals', {
|
||||
id: serial('id').primaryKey(),
|
||||
matchId: integer('match_id').notNull(),
|
||||
teamId: integer('team_id').notNull(),
|
||||
playerName: text('player_name').notNull(),
|
||||
minute: integer('minute'),
|
||||
minuteOffset: integer('minute_offset').default(0),
|
||||
isPenalty: boolean('is_penalty').default(false),
|
||||
isOwnGoal: boolean('is_own_goal').default(false),
|
||||
})
|
||||
|
||||
export const groupStandings = pgTable('group_standings', {
|
||||
tournamentYear: integer('tournament_year').notNull(),
|
||||
groupName: text('group_name').notNull(),
|
||||
teamId: integer('team_id').notNull(),
|
||||
pos: integer('pos'),
|
||||
played: integer('played').default(0),
|
||||
won: integer('won').default(0),
|
||||
drawn: integer('drawn').default(0),
|
||||
lost: integer('lost').default(0),
|
||||
goalsFor: integer('goals_for').default(0),
|
||||
goalsAgainst: integer('goals_against').default(0),
|
||||
goalDiff: integer('goal_diff').default(0),
|
||||
pts: integer('pts').default(0),
|
||||
}, (t) => [primaryKey({ columns: [t.tournamentYear, t.groupName, t.teamId] })])
|
||||
|
||||
export const squads = pgTable('squads', {
|
||||
id: serial('id').primaryKey(),
|
||||
tournamentYear: integer('tournament_year').notNull(),
|
||||
teamId: integer('team_id').notNull(),
|
||||
playerName: text('player_name').notNull(),
|
||||
shirtNumber: integer('shirt_number'),
|
||||
position: text('position'),
|
||||
dateOfBirth: date('date_of_birth'),
|
||||
})
|
||||
Reference in New Issue
Block a user