Real teams missing from TEAM_ISO: Bosnia-Herzegovina (ba), Kosovo (xk),
New Caledonia (nc), Suriname (sr). Defunct/dissolved with no flag-icons
code: Serbia and Montenegro (cs retired), Zaire (zr retired) → null.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setting fontSize on the container element caused em-based width/height to
collapse relative to the shrunken font size. Move font-size to an inner
span so the outer container stays at 1.33em × 1em — matching .fi dimensions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The DB still holds old codes (su, yu) from before the null mapping was
added. TeamFlag now checks TEAM_ISO by name first so the registry wins
over any stale value the DB returns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Soviet Union (su), Yugoslavia (yu), East Germany, Germany DR, FR Yugoslavia,
and Czechoslovakia have no valid entry in flag-icons. Map them to null in
TEAM_ISO so getIso() returns null, and render a muted initials badge in
TeamFlag instead of a broken/empty sprite. Also drop the buggy 2-char
substring fallback that generated random valid codes for unknown teams.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scripts/seed.ts: one-time import of Kaggle FIFA dataset (matches_1930_2022.csv,
world_cup.csv) covering all 964 matches and 2720 goals from 1930-2022 with full
scorer names, minutes, penalties, and own goals for every tournament
- scripts/sync.ts: stripped to 2026 only (openfootball live data); historical years
removed since Kaggle is now authoritative for 1930-2022
- Dockerfile: copy app/data into runner image; CMD runs seed.ts before server.js so
a fresh deployment auto-seeds on first start (skips if already seeded)
- package.json: add 'seed' script; use --force to re-import from updated CSV files
- app/data/kaggle/: bundle Kaggle CSV files in repo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
syncGoals() was calling DELETE FROM goals WHERE match_id=X at the top,
so processing goals2 (away team) wiped out goals1 (home team) that were
just inserted. Every match with goals from both sides lost all home-team
goals — Ronaldo's hat-trick vs Spain, Kane's vs Panama, and many others.
Fix: move DELETE above the goals1/goals2 loop, executed once per match.
Result: 2018 goal count corrected from 107 → 169; hat tricks from 8 → 18.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setHours() used the server's local timezone, so "15:00 UTC-5" on a UTC server
was treated as 15:00 UTC instead of 20:00 UTC — causing wrong live detection.
Now parses the UTC offset from the time string and converts to actual UTC
kickoff before comparing: UTC = local_time - offset (15:00 UTC-5 = 20:00 UTC).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openfootball uses 'West Germany' for 1954–1990 era matches. All DB references
(matches, goals, group_standings, squads) have been merged into the Germany
team on both local and VPS. TEAM_ALIASES map prevents re-creation on re-sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drizzle ORM mutates client.options (parsers/serializers) after the postgres
client is created, which causes the separately-passed password option to be
lost on the actual connection attempt. Root cause confirmed on VPS: raw
postgres.js query succeeded while drizzle.execute() failed with auth error.
Fix: encode the password directly in DATABASE_URL (%23 = #, %5D = ], %3D = =).
postgres.js decodes percent-encoding correctly. No separate DB_PASSWORD env
var needed in the app container anymore.
DB_PASSWORD is still used by the Postgres container (POSTGRES_PASSWORD).
Coolify env var to set: DATABASE_URL=postgres://wc:<encoded-pass>@db:5432/worldcup
Also adds resolver-level isMissingTable() guards so the app returns empty
results instead of GraphQL errors on a fresh deploy before sync runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sync script created its own postgres client and only read DATABASE_URL,
bypassing the DB_PASSWORD override that lib/db/index.ts already applied.
Since DATABASE_URL has no password embedded, auth always failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- @theme inline → @theme so tokens generate utility classes (bg-bg,
text-text, font-display etc.) rather than being inlined as vars only
- Wrap base resets, keyframes and custom classes in @layer base for
correct Tailwind cascade ordering
- Remove broken @source directives (Tailwind v4 auto-detects sources
from the project root when using @tailwindcss/postcss with Next.js)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tailwind v4 @source does not support ** recursive globs — each
directory level must be listed explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without explicit @source paths, Tailwind v4 auto-detection can miss
app/, components/, and lib/ directories in production builds, causing
utility classes to be stripped from the output CSS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Coolify overrides container_name, so the DB service is only reachable
via its compose service name ("db"), not "worldcup_db". Also, passwords
containing URL-special characters (#, ], =) break postgres URL parsing
because the driver uses new URL() internally.
- docker-compose.yml: DATABASE_URL now uses "db" hostname with no
embedded password; DB_PASSWORD is passed as a separate env var
- lib/db/index.ts: when DB_PASSWORD env var is set it is passed as a
postgres driver option, bypassing URL parsing entirely
- .env.example: documents production vs local dev env var usage;
removes DATABASE_URL from the Coolify section (not needed there)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- container_name: worldcup / worldcup_db for predictable exec/log targets
- DATABASE_URL hostname updated from db to worldcup_db to match
- Remove no-index@file from Traefik middleware chain (not configured)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>