StatsHub Docs

admin

The internal admin surface, on port 3003.

apps/statshub-admin is the data-ops console behind the product: where the feeds are loaded, the corrections are made, the bot is configured and a running backfill is watched. It is a view over api and nothing else — no database client, no API client of its own.

bun run dev:admin      # http://localhost:3003
bun run build:admin
Next.js 16App Router, standaloneReact 19TypeScriptTailwind CSS 4Go APIthe only backendSupabasethe superadmin gateSWRevery requestChart.js

What it does

Loaders and backfills

Pull leagues, tournaments, events, shotmaps and images in, and watch a backfill job while it runs. src/components/dashboard is one file per control, which is why the list of them reads like the list of jobs.

Data quality rules

Stats rules, position-change avoid rules, player-assignment maintenance and team-name management — the corrections that keep a feed's idea of a player and ours pointing at the same person.

The Telegram bot

Routing, tournament scope, alerts, custom messages, and a test send. What the bot posts is decided here rather than in its own config.

Models and prompts

Model management and an OpenRouter prompt editor, so a prompt change does not need a deploy.

Daily spreadsheet

The prop screener as a sheet, for the people who work in one.

Social export and widgets

/social-export renders posts as images; /widget-showcase is every rich response the agent can return, rendered on one page so a widget can be checked without asking a model for it.

How it is built

Every /api request is a rewrite

There is no API client here and no database client either. next.config.mjs rewrites /api/:path* to API_ORIGIN server-side, so the browser stays on the admin origin and the Supabase session cookie rides along unchanged.

Except /api/health

That one is the container's liveness probe and answers from the filesystem route. afterFiles rewrites — which is where rewrites() puts these — never reach a route that already exists, which is exactly why it works.

Nothing here is indexed

X-Robots-Tag: noindex, nofollow on /:path*, set in the config rather than per page, so a new route cannot forget it.

API_ORIGIN picks the target

It decides which deployment you are administering and defaults to http://127.0.0.1:8080. The dashboard is full of destructive buttons and none of them ask twice — check what the API is attached to first.

Types, not a generated client

src/types/api.ts holds the response shapes read from the Go API, hand-kept against the reference. The screener domain types sit beside them because in legacy they were exported from the screener page, so getting a type meant importing a page component.

SWR everywhere

No server components fetching, no React Query. Every panel polls or revalidates on focus, which is the behaviour you want from a console watching a job that is still running.

Layout

DirectoryWhat is in it
src/appApp Router routes
src/typesResponse shapes read from the Go API, and the screener domain types
src/serverServer-only helpers
src/components, src/hooks, src/contextsApp-local UI

Environment

apps/statshub-admin/.env.example lists what it needs. Like web, it starts without those values and fails at request time rather than at boot.

It reads whatever the API points at

API_ORIGIN decides which deployment admin is administering, and it defaults to http://127.0.0.1:8080. Start the API against the local Postgres (bun run db:dev:up, then bun run dev:api) before trying anything destructive.

On this page