A Node.js application for setting LiveHeats round durations from the athlete count in each heat.
The browser renders the interface and calls same-origin /api routes. OAuth
authentication, access tokens, GraphQL event queries, calculations, validation,
and mutations are handled by the Node server.
- Node.js 20 or newer
- LiveHeats API client credentials with the required permissions
With Docker Desktop or Docker Engine and the Compose plugin installed:
docker compose up -dDuring the image build, the current Git commit is appended to the local CSS
and JavaScript URLs as ?v=<commit>. This invalidates previously cached assets
after each committed deployment. The build stage reads .git, but Git metadata
is not copied into the final image.
Open http://localhost:3000.
Useful commands:
docker compose logs -f
docker compose downTo expose the application on a different host port:
$env:PORT=8080
docker compose up -dThen open http://localhost:8080.
The Compose file defaults the public origin to
https://heatmath.abw.com.br. To deploy on a different domain, override it:
$env:APP_ORIGIN="https://heatmath.abw.com.br"
docker compose up -dAPP_ORIGIN may contain a comma-separated list when more than one public origin
is required. The server also recognizes X-Forwarded-Host and
X-Forwarded-Proto from a trusted reverse proxy.
npm install
npm startOpen http://localhost:3000.
For automatic server restarts while developing:
npm run devSet a different port with the PORT environment variable.
POST /api/connectauthenticates with LiveHeats and starts an HTTP-only server session.GET /api/organisations/:shortName/eventsloads organization details and returns events with scheduled/published events first, then all other statuses; each group is sorted by descending date.GET /api/events/:eventIdloads the event, divisions, heats, competitor counts, and configured heat sizes.POST /api/events/:eventId/apply-durationsreloads the event, independently validates the requested minutes-per-athlete calculation, applies every eligible round update sequentially, and returns the refreshed event.
- OAuth uses
https://liveheats.com/oauth/token. - GraphQL uses
https://liveheats.com/api/graphql. - The requested scopes are
public events/director organisations/manage. - LiveHeats exposes duration changes through
updateRound. When athlete counts differ within a division/round, ABW HeatMath calculates each heat's time, averages those times, rounds to the nearest whole minute, and applies that shared duration to every heat in the round. When a future heat has no placed athletes yet, its configured LiveHeats heat size is used instead. An empty heat only blocks the operation when it also has no valid configured size. - Event selection uses
organisationByShortName; the organization short name defaults tocbeawand can be changed in the interface.
- Client credentials and access tokens are held only in server memory.
- The browser receives an opaque, HTTP-only, SameSite session cookie.
- Sessions expire after two hours and disappear whenever the server restarts.
- State-changing routes reject cross-origin browser requests.
- API responses never include credentials or access tokens.
- In production, run the app behind HTTPS and set
NODE_ENV=production. - Session cookies automatically use the
Secureflag when the request arrives over HTTPS. The server trusts one reverse-proxy hop, so an HTTPS proxy should forwardX-Forwarded-Proto.
This in-memory session store is appropriate for a small, single-instance operations tool. For multiple Node instances, replace it with a shared encrypted session store.