My Gym My Way
Free home-gym workouts built from the equipment you own. No paywall, no account, no tracking.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证NFL weekly pick-em app for friends
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
A free, friends-only NFL weekly pick'em application built with Next.js 14, TypeScript, and Vercel Postgres.查看完整 README ↗A free, friends-only NFL weekly pick'em application built with Next.js 14, TypeScript, and Vercel Postgres.
git clone <your-repo>
cd football-locks-project
npm install
Copy env.example to .env.local and configure:
cp env.example .env.local
Required environment variables:
DATABASE_URL: Vercel Postgres connection stringLEAGUE_PASSCODE: Shared passcode for league accessJWT_SECRET: Secret key for JWT tokensADMIN_PASSCODE: Admin passcode for manual resultsCRON_SECRET: Secret for cron job authentication# Generate and run migrations
npm run db:generate
npm run db:migrate
npm run dev
Visit http://localhost:3000 and log in with any of the 6 user names and the league passcode.
# Deploy migrations
npm run db:migrate
Cron jobs are declared in vercel.json and deploy automatically. Vercel runs
them in UTC, and Hobby-plan jobs fire within the hour they are scheduled
for (a 7:00 PM job can run as late as 7:59 PM).
| Job | Expression | Eastern | Purpose |
|---|---|---|---|
/api/cron/fetch-schedule | 0 13 * * 1 | Mon 9:00 AM | Refresh next week's kickoff times |
/api/cron/fetch-odds | 0 23 * * * | Daily 7:00 PM | Refresh lines for the current and next week |
/api/cron/resolve-results | 0 8 * * * | Daily 4:00 AM | Pull results and rescore the week |
/api/cron/lock-reminder | 0 21 * * * | Daily 5:00 PM | Nudge anyone without picks in, if kickoff is close |
All three require Authorization: Bearer $CRON_SECRET, which Vercel sends
automatically when CRON_SECRET is set on the project.
Vercel triggers cron jobs with a GET request, so each route exports GET.
A route exporting only POST returns 405 and the job silently never runs.
Eastern times shift an hour during standard time (Nov–Mar); every job still lands well inside its window.
One ESPN scoreboard request returns moneyline, spread and total for the whole slate, so no betting API key is needed. Lines refresh once a day for the current and next week, so the board you see while picking is live.
A game's line stops updating the moment it kicks off. That freeze is the point:
the last value written before kickoff is the closing line, which is both what
the analytics want and the sharpest available read on true win probability.
Without it a later run could overwrite a settled line with in-play numbers.
One row per game in gameodds, overwritten until kickoff, no history kept.
Results land on their own. The nightly cron is the safety net, and the pages
call /api/results/refresh themselves, which pulls fresh results whenever a
game has kicked off but has no result yet. Hobby crons cannot run more than
once a day, so that on-demand call is what keeps things current mid-slate.
Live scores work the same way: while any game is in progress the gameday view polls every 30 seconds. A shorter cooldown on the refresh endpoint means a room full of viewers still costs one upstream fetch per cycle. The admin page remains as a manual fallback.
Web Push, so alerts land on a phone's lock screen with no SMS provider and no
cost. Generate a VAPID pair once and set NEXT_PUBLIC_VAPID_PUBLIC_KEY,
VAPID_PRIVATE_KEY and VAPID_SUBJECT:
node -e "console.log(require('web-push').generateVAPIDKeys())"
Preferences are per device, not per player, so someone can take bust alerts on their phone and nothing on their laptop. Four kinds: a locked game kicking off, a locked game finishing, a rival busting, a rival hitting. Tapping any of them opens that week's live gameday screen.
Alerts fire from state transitions during a results refresh, never from current state, so a result announces itself exactly once however often the refresh runs. Rival alerts are grouped per game rather than per pick — this league picks alike, and one message per pick would mean a game six people locked fires thirty notifications for a single result.
Scoring is all or nothing, so a player carrying a loss is out for the week and nothing further about their locks is sent: not to them, not to anyone else. The bust that eliminated them still goes out. A fifth alert reminds anyone who has not submitted, anchored on the week's real first kickoff rather than a fixed weekday — week 1 of 2026 opens on a Wednesday.
Alert titles are one-liners rather than descriptions, since the title is what gets read on a lock screen. They are drawn from a pool weighted toward whatever is specific about the result — the team that let someone down, a Thursday elimination, a blowout, a one-score loss — and chosen from a per-event seed, so a result picked up by two refreshes always reads the same way while different results spread across the pool. The facts live in the body.
Results are also refreshed from GitHub Actions (.github/workflows/refresh-results.yml)
every five minutes during the hours games actually end. Hobby crons run once a
day and in-app polling only runs while a page is open, so without it a Thursday
night game finishing at 11pm went unannounced until the 4am sweep. The workflow
needs CRON_SECRET set as a repository secret.
iOS requires the site to be installed to the Home Screen first. Android and desktop do not. The setup card walks through the Safari steps when it detects an iPhone that has not installed yet.
.github/workflows/backup.yml dumps the database every night and keeps 30 days
as workflow artifacts. The repo is public, so dumps are encrypted with the
BACKUP_PASSPHRASE repo secret — keep a copy of it outside the repo, since the
artifacts are useless without it. Restore:
gpg --decrypt backup.sql.gz.gpg | gunzip | psql "$DATABASE_URL"
POST /api/login - User loginGET /api/me - Get current userPOST /api/logout - User logoutGET /api/week - Get current NFL weekGET /api/schedule - Get games for a weekPOST /api/picks/submit - Submit weekly picksGET /api/picks/my - Get user's picksGET /api/picks/all - Get all picks (after submission)GET /api/scoreboard - Get season scoreboardPOST /api/admin/manual-results - Update game results manuallyPOST /api/results/refresh - Pull fresh results for the live week (used by the scoreboard)POST /api/push/subscribe - Register this browser, or update its preferencesDELETE /api/push/subscribe - Turn alerts off for this browserGET /api/push/preferences - What this browser is signed up forPOST /api/push/preferences - Send a test alertPOST /api/cron/fetch-schedule - Fetch next week's schedulePOST /api/cron/fetch-odds - Snapshot this week's betting linesPOST /api/cron/resolve-results - Update game results and scoresIf ESPN API fails, use the admin panel to manually enter game results.
Ensure DATABASE_URL is correctly set in Vercel environment variables.
Check Vercel cron job configuration and ensure CRON_SECRET is set.
Clear browser cookies and ensure JWT_SECRET is set.
This is a friends-only application, but feel free to fork for your own league!
MIT License - feel free to use for your own projects.