Skip to content

About

Bracket Spread Picking Competition Website

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Spread Dread

A head-to-head NCAA Tournament bracket app where two players compete using point spreads. One player picks a team per game; the other automatically gets the opponent. Picks are evaluated against the spread, not outright winners.

Built with Symfony 7, Tailwind CSS, and vanilla JavaScript.

Features

  • 63-game bracket automatically wired with correct NCAA seed matchups across all six rounds
  • Point spread competition — picks are evaluated against the spread, not straight-up winners
  • Turn-based picking — players alternate picks within each round, with pick progress tracking
  • ESPN integration — pull tournament teams, spreads, and final scores directly from ESPN's API
  • Live score updates — pull scores for completed games and automatically advance winners
  • Per-user authentication — each player logs in with their own account
  • Admin dashboard — invite-only account creation, with per-user enable/disable and admin management
  • Mobile-friendly — responsive card-based UI with sticky navigation and region tabs

Requirements

  • DDEV (Docker-based local development)
  • No Node.js required — Tailwind CSS is compiled via a standalone CLI binary

Setup

git clone <repo-url> spread-dread
cd spread-dread
ddev start
ddev composer install
ddev exec php bin/console doctrine:migrations:migrate
ddev launch

MAILER_DSN and MAILER_FROM both default to a discard-mail configuration (null://null) if left unset, so the app still boots without them. To actually send invitation and password-reset emails, set a real MAILER_DSN (and, in production, a MAILER_FROM you control) in .env.local (not committed); leaving them at their defaults is fine for local development, where mail is silently discarded.

Accounts

Accounts are created by invitation, not from the command line. An admin logs in, opens /admin, and sends an invite to an email address; the recipient follows the link to set a username and password.

The app:user command still exists as a break-glass path for when there's no admin yet (bootstrapping the first account) or SMTP is down. Creating a new account this way requires --email; updating an existing one does not:

ddev exec php bin/console app:user <username> <password> --email=<address> [--admin]

Development

Tailwind CSS

CSS is compiled from assets/styles/app.css using symfonycasts/tailwind-bundle. After changing Tailwind classes in templates or assets:

ddev exec php bin/console tailwind:build

For automatic rebuilds during development:

ddev exec php bin/console tailwind:build --watch

The built CSS (var/tailwind/app.built.css) is committed to the repo since the production server can't run the Tailwind binary. Always run tailwind:build locally before committing template changes.

Database Changes

After modifying Doctrine entities:

ddev exec php bin/console doctrine:migrations:diff
ddev exec php bin/console doctrine:migrations:migrate

Clear Cache

ddev exec php bin/console cache:clear

How It Works

  1. Selection Sunday — an admin sets the Final Four pairing (which region plays East) in /admin. The tournament year is chosen automatically.
  2. Create a bracket — brackets open once the pairing is set and ESPN lists the field; pick an opponent and the first-round teams load automatically. "Load missing teams" fills the First Four slots once those games are decided.
  3. Make picks — players take turns picking teams; the opponent automatically gets the other team
  4. Pull spreads — fetch point spreads from ESPN for each round
  5. Update scores — pull final scores from ESPN; the app evaluates picks against the spread and advances winners to the next round
  6. Track the score — a sticky scoreboard shows each player's running total

Each tournament

  1. Selection Sunday: in /admin, set which region plays East in the Final Four. Brackets open once this is set and ESPN lists the field.
  2. After the First Four (Tue/Wed): the sync job fills the four play-in slots; the "Load missing teams" button on a bracket does the same on demand.
  3. Before each round's first pick: spreads keep refreshing (sync or "Pull Spreads"). The first pick in a round locks the spreads already set in that round; games that get their teams later (First Four winners, the second semifinal) still receive their first line.

Deployment

Deploys automatically to Dreamhost via GitHub Actions on push to main. The workflow SSHs into the server, pulls the latest code, installs dependencies, runs migrations, and compiles assets.

Scheduled sync (March–April)

app:tournament:sync pulls missing teams, spreads (refreshed for rounds with no picks yet, filled in for games still without a line), and final scores for every bracket of the current tournament. Add it in the Dreamhost panel (Advanced → Cron Jobs) as the site's user:

0 * * 3,4 *   cd <app path> && php bin/console app:tournament:sync >> var/log/sync.log 2>&1

Cron may use a different PHP than your shell; if the log shows a version error, use the full path shown by which php in an SSH session. Outside March and April the job doesn't run; the buttons on each bracket page still work at any time.

Tech Stack

  • Backend: PHP 8.4, Symfony 7, Doctrine ORM, MySQL 8.4
  • Frontend: Tailwind CSS (compiled via standalone CLI), Symfony AssetMapper, vanilla JavaScript
  • Infrastructure: DDEV (local), Dreamhost (production)
  • External APIs: ESPN Scoreboard & Summary APIs
  • CI/CD: GitHub Actions

About

Bracket Spread Picking Competition Website

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages