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 two-person NFL league scored against the spread. Each week a coin flip
decides who drafts first; the two of you draft 5 sides from that week's slate,
and whoever finishes the season with the most ATS wins takes it.查看完整 README ↗A two-person NFL league scored against the spread. Each week a coin flip decides who drafts first; the two of you draft 5 sides from that week's slate, and whoever finishes the season with the most ATS wins takes it.
SEA -3.5, not "Seattle."SEA -3.5, NE +3.5 isn't available to your
opponent either.5 - P.Next.js (App Router) + TypeScript + Tailwind, Prisma + Postgres (Neon), hosted on Vercel. Slate, spreads, and final scores come from ESPN's public scoreboard endpoint — no API key.
You need a free Neon Postgres database — there's no local SQLite file any more, so local development talks to the same database as production.
npm install
cp .env.example .env # paste your Neon URLs + pick a league password
npx prisma migrate dev
npm run db:seed # creates the two players
npm run dev
Open http://localhost:3000. Player names are set in
prisma/seed.ts — edit them before seeding.
DATABASE_URL must be Neon's pooled string (the host contains -pooler)
and DIRECT_URL the unpooled one. Serverless functions open a connection per
invocation, so queries go through the pooler, while prisma migrate needs a
direct connection.
DATABASE_URL, DIRECT_URL,
ADMIN_PASSWORD, and CRON_SECRET (generate with openssl rand -hex 32).CRON_SECRET value as a GitHub Actions repository secret
(Settings → Secrets and variables → Actions → New repository secret) so the
automated spread sync can authenticate — see How spreads freeze below.npx prisma migrate deploy and npm run db:seed once against Neon.package.json runs prisma generate on postinstall because Vercel caches
node_modules — without it the deployed Prisma Client can silently go stale
against a changed schema.
ESPN drops the odds once a game goes final — a line that isn't captured before kickoff is gone for good. So each game's spread stays live — re-syncing keeps it current with the market — right up until that game's own kickoff, then it locks permanently. Both players are always graded against that one frozen line, which guarantees exactly one side covers (or it's a push); no after-the-fact line movement can change that.
If ESPN has no line for a game, that game isn't draftable until you set one by hand under Games missing a spread in Admin.
Nothing needs to stay open for this — the site and database are fully hosted and run on their own 24/7. Re-syncing does need to be triggered, though, which .github/workflows/sync-spreads.yml does automatically:
Both call /api/cron/sync-spreads, authenticated with CRON_SECRET. This runs
on GitHub Actions rather than Vercel's own Cron Jobs because Vercel's Hobby
plan caps cron jobs at once per day — nowhere near frequent enough here.
You can still click Sync slate in Admin any time; it does the same thing manually, and is the only way to load a week that hasn't been synced yet (the automation only refreshes weeks that already exist).
With homeSpread stored as the home team's line (negative = home favored):
adjusted = homeScore + homeSpread - awayScore
adjusted > 0 → home side covers
adjusted < 0 → away side covers
adjusted = 0 → push
ADMIN_PASSWORD is a single shared league password. It gates entering picks
and every admin action; viewing the week and the standings is open to anyone
with the link. It keeps strangers out, but it does not stop the two of you
from entering each other's picks — there are no per-player logins.