Skip to content
JoshEbersolPublic

About

A tool for building and sharing double-elimination tournament brackets.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Brackets

A tool for building and sharing double-elimination tournament brackets.

  • The Admin (/admin) page allows administrators to create, edit, and manage brackets: seed the field, set the rest rule, draw the bracket, pick winners, and assign per-game dates/times/venues. Every change autosaves to the server.

    A screenshot of a seven-team bracket in admin mode (dark).
  • The View (/b/<id>) page allows the public a read-only view of a single bracket. Anyone with the link can see the bracket and schedule, but they can't edit or pick winners:

    A screenshot of a seven-team bracket in view mode (light). A screenshot of a seven-team schedule in view mode (light).

Architecture

server/
  server.js          Express REST API + static/page routes
  storage.js         pluggable storage backend (Azure Blob or filesystem)
public/              browser-served assets (everything here is public)
  bracket-core.js    shared engine + renderer (read-only aware)
  admin.html         editable admin page (talks to the API, autosaves)
  view.html          read-only viewer page
  styles.css         shared styles
data/                filesystem backend: one JSON file per bracket (git-ignored)

Each bracket has a server-assigned, unguessable id (crypto.randomBytes → base64url). A record stores the raw inputs plus the per-game pick / date / time / venue overrides, keyed by stable match id.

API

Method Route Auth Purpose
GET /api/brackets token list brackets (id, name, time)
POST /api/brackets token create, returns the new record
GET /api/brackets/:id public read one (used by the viewer)
PUT /api/brackets/:id token update
DELETE /api/brackets/:id token delete

Reading a single bracket is public so read-only links work without a login. All writes (and the full list) require the admin token via the x-admin-token header, when ADMIN_TOKEN is set. If ADMIN_TOKEN is unset/empty, the server runs open (handy for local dev).

Run with Docker (recommended)

docker compose up --build

Then open http://localhost:3000. The compose file sets ADMIN_TOKEN=dev-secret-token — change it for anything but local use. Brackets are stored in ./data on the host (bind-mounted), so they survive container restarts and rebuilds.

The first time you open the admin page it will ask for the admin token (paste the value of ADMIN_TOKEN). It's remembered in your browser afterward.

Run with Node (no Docker)

Requires Node 18+.

npm install
# PowerShell:  $env:ADMIN_TOKEN='dev-secret-token'; npm start
# bash:        ADMIN_TOKEN=dev-secret-token npm start

Configuration

Env var Default Meaning
PORT 3000 HTTP port
ADMIN_TOKEN (empty) required for writes; empty = open (no auth)
DATA_DIR ./data filesystem backend: where bracket JSON files are stored
AZURE_STORAGE_ACCOUNT (empty) Blob backend via managed identity (set this in Azure)
AZURE_STORAGE_CONNECTION_STRING (empty) Blob backend via key/Azurite (alternative to the above)
AZURE_STORAGE_CONTAINER brackets Blob container name (auto-created)

The storage backend is chosen automatically: if either AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_CONNECTION_STRING is set it uses Azure Blob, otherwise it falls back to the local filesystem. One blob per bracket (<id>.json); the bracket name and updatedAt are mirrored into blob metadata so listing doesn't download every blob.

Deploy to Azure (Blob storage + managed identity)

Stores brackets in an Azure Storage account you already have — no mounted disk, so the app is stateless and runs well on Azure Container Apps (scale-to-zero).

Quick path: deploy.azure.ps1 does all of the below in one idempotent run (builds the image in ACR, creates the app with a managed identity, wires the env vars, and grants the blob role). All parameters are explicit — nothing deployment-specific is baked into the script. From the repo root:

./deploy.azure.ps1 `
  -TenantId         <tenant-guid> `
  -ResourceGroup    <rg> `
  -Location         eastus `
  -AcrName          <globally-unique-acr> `
  -StorageAccount   <account> `
  -StorageContainer brackets `
  -AppName          brackets `
  -Environment      brackets-env `
  -ImageTag         brackets:latest

It scales 0..1, generates an admin token if you don't pass -AdminToken, and prints the app URL + token when done. The manual steps below are the same thing spelled out.

  1. Push the image to a registry the host can pull (ACR or GHCR).

  2. Create the app with a managed identity (system-assigned shown here) and point it at your account. With Azure Container Apps:

    az containerapp create \
      --name brackets --resource-group <rg> --environment <aca-env> \
      --image <registry>/brackets-app:latest \
      --system-assigned \
      --ingress external --target-port 3000 \
      --env-vars ADMIN_TOKEN=<your-token> AZURE_STORAGE_ACCOUNT=<account> \
      --min-replicas 0 --max-replicas 1
  3. Grant the identity access to the storage account (data-plane RBAC):

    PRINCIPAL=$(az containerapp show -n brackets -g <rg> --query identity.principalId -o tsv)
    ACCOUNT_ID=$(az storage account show -n <account> --query id -o tsv)
    az role assignment create --assignee $PRINCIPAL \
      --role "Storage Blob Data Contributor" --scope $ACCOUNT_ID

Notes:

  • Keep --max-replicas 1: storage is one blob per bracket with last-write-wins, not built for concurrent writers across instances.
  • DefaultAzureCredential is used, so the same code authenticates locally via your az login if you ever run against the real account from your machine.
  • The container (brackets) is created automatically on first write; the identity needs Storage Blob Data Contributor (it covers creating the container and read/write).

Sharing a bracket

Open a bracket in the admin page and copy its Read-only link (/b/<id>) from the left rail. Send that link to anyone — they get the read-only viewer for that bracket. Picking winners and editing the schedule stays on the admin side, behind the token.

About

A tool for building and sharing double-elimination tournament brackets.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages