OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A private fantasy league for men's college basketball. Managers draft real
players, set a lineup that locks per game at tip-off, and play weekly head-to-head
matchups scored on the CBB Player-Score model. Waivers run as a blind FAAB
auction, trades go through a review window, and the commissioner tunes every
league setting from a screen.檢視完整 README ↗A private fantasy league for men's college basketball. Managers draft real players, set a lineup that locks per game at tip-off, and play weekly head-to-head matchups scored on the CBB Player-Score model. Waivers run as a blind FAAB auction, trades go through a review window, and the commissioner tunes every league setting from a screen.
There is no background worker. The draft clock, waiver runs, and trade executions are all settled on read — the first request past a deadline does the work that was due, at the time it was due — so the league stays correct whether someone refreshed all night or nobody looked for a week.
For the phase-by-phase design history and the reasoning behind each decision, see DEVLOG.md.
apps/webpackages/*, imported directly by
both the web app and the CLI scripts, so a screen cannot disagree with a
settlementpg drivertsx for scripts and tests, tsc -b for
typecheckpackages/scoring the six-block Player-Score model; weights and bounds are data
packages/sources clients for Barttorvik, CBBD, RotoWire
packages/crosswalk resolves one source's player id to another's
packages/db migrations and the data layer
packages/ingest pulls a night of games into the database
packages/league draft, lineups, settlement, waivers, trades, settings
scripts/* CLI entry points for every package (see Commands)
apps/web the Next.js app
.cache/ and tracks the
remaining quota. Not needed just to run the app against an already-populated
database.npm install
Create .env.local (gitignored) with:
DATABASE_URL=postgresql://… # pooled connection
DATABASE_URL_UNPOOLED=postgresql://… # direct connection; migrations use this
CBBD_API_KEY=… # only for ingest
AUTH_SECRET=… # any long random string
AUTH_URL=http://localhost:3000 # must match the port you run dev on
Migrations run against any Postgres. A disposable local one:
docker run -d --name illini-pg -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=illini \
-p 55432:5432 postgres:18-alpine
export DATABASE_URL=postgresql://postgres:dev@localhost:55432/illini
npm run migrate # applies pending migrations; idempotent
The db test suite creates and drops its own illini_test database.
Production is a Neon project linked with neon link, which writes and keeps
.env.local current. Migrations prefer DATABASE_URL_UNPOOLED because Neon's
pooler multiplexes sessions and DDL wants a dedicated connection.
npm run dev # http://localhost:3000
Two things that will otherwise cost you an afternoon:
AUTH_URL pins the port. next dev falls through to the next free port if
3000 is taken, but the invite link on /commissioner is built from AUTH_URL.
Free port 3000 or move AUTH_URL to match.next dev per directory. A second one exits and names the PID of the
first. To run a second copy, npm run build then npx next start --port 3006.The sample data is February 2026, so against the real clock every game has tipped off and every lineup is frozen. Two env vars unpin the clock:
ILLINI_TODAY=2026-02-14 # the date the app treats as today
ILLINI_NOW=2026-02-14T18:30:00Z # the instant the lineup lock measures against
ILLINI_TODAY alone keeps the real time of day. Set both to freeze a specific
moment — useful for screenshots and for seeing the lineup controls at all.
| route | |
|---|---|
/home | the live matchup, tonight's slate, and what needs you |
/team | tonight's startable players, per-game locks, slot validation |
/league | the week's matchup, scored live from stored player scores |
/players · /players/:id | the pool ranked by season Player-Score; per-night game log |
/standings · /playoffs | settled weeks only |
/waivers | the wire, your sealed claims, every team's budget |
/trades | propose, answer, review, history |
/draft | the draft room — clock, best available, your queue, the board |
/commissioner · /commissioner/settings | invites, seats, and every league number — commissioner only |
/signin · /join/:token | sign in; redeem an invite |
| command | |
|---|---|
npm run dev | web app |
npm run build | production build |
npm test | test suite (needs a local Postgres) |
npm run typecheck | tsc -b across packages and scripts |
npm run migrate | apply pending migrations |
npm run ingest -- <verb> | pull source data (setup, schedule, night, range, link, injuries) |
npm run league -- <verb> | league operations (create, draft, lineups, settle, standings, invite, waivers, trades, settings, passwd, …) |
npm run season | build and play a full 2025-26 league over ingested data |
npm run backtest | the scoring-model validation checks |
npm run budget | CBBD quota against projected usage |
npm run pool | draft-pool coverage report |
npm run crosswalk | crosswalk accuracy against live RotoWire injuries |
Passwords and usernames are managed by the commissioner from the CLI — there is no self-serve reset and nothing emails anybody:
npm run league -- passwd <username|email> <password>
A username and a password, stored in app_user (scrypt, parameters travel with
the hash). Sessions are rows in auth_session keyed by a random httpOnly
cookie; signing out is a DELETE. The invite link hands over a team, not a
session — redeeming one is where a manager picks their username. The whole of it
is apps/web/lib/auth.ts.