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>
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
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'),
|
|
})
|