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。 以下為已儲存的摘錄。
Running-club PWA for the Madregot club. Coaches parse training plans from text or
images with AI and push structured workouts to athletes' Garmin watches; athletes
get a Hebrew-first mobile app with their program, a Strava-style activity feed,
attendance, academy reports and push notifications.檢視完整 README ↗Running-club PWA for the Madregot club. Coaches parse training plans from text or images with AI and push structured workouts to athletes' Garmin watches; athletes get a Hebrew-first mobile app with their program, a Strava-style activity feed, attendance, academy reports and push notifications.
next-intl (he/en, RTL)garmin-connect packagemaxDuration = 300)Requires Node >= 22.12.
npm install
Create a project at supabase.com, then run the SQL in
this order in the SQL Editor. Both steps are required — schema.sql creates
the base tables (coaches, groups, athletes, weekly_plans,
workout_deliveries, athlete_activities) and the migrations build everything
else on top of them. Running only one of the two leaves the app unable to boot.
supabase/schema.sql — the base tables.supabase/migrations/ in numeric order, from
000_extensions.sql through the highest-numbered file (currently
076_notification_image.sql, 72 files).Migrations are applied by hand in the SQL Editor; there is no supabase db push
wired up in this repo. To concatenate them in order for a single paste:
for f in supabase/migrations/*.sql; do echo "-- $f"; cat "$f"; echo; done > /tmp/all-migrations.sql
Then copy your project URL and API keys from Settings → API.
cp .env.local.example .env.local
.env.local.example documents every variable the app reads, grouped by the
feature it unlocks and annotated with what breaks when it's missing. Only the
first block ("Core") plus ENCRYPTION_KEY is needed to boot; the rest are
per-feature and degrade gracefully when blank.
Two that catch people out:
ENCRYPTION_KEY must be 64 hex characters, and is not rotatable in
place — a new key cannot decrypt existing Garmin/Strava connections. Generate
a fresh one only for an empty database.NEXT_PUBLIC_VAPID_PUBLIC_KEY /
VAPID_PRIVATE_KEY pair (npx web-push generate-vapid-keys). With either
missing, sending silently no-ops rather than erroring.npm run dev
Open http://localhost:3000.
npx vercel
Add the same environment variables in Vercel → Settings → Environment
Variables. The cron schedules in vercel.json are registered automatically on
deploy.
When pulling Vercel env vars locally, always target a temp path —
vercel env pull /tmp/mc-vercel.env --environment=production. The default destination is.env.local, which overwrites your own file.
npm run check # typecheck + tests + lint + build (run before pushing)
npm run typecheck
npm run test # vitest
npm run lint
Bump src/lib/version.ts and package.json together for each change (patch
bumps by default).
This trips up every change that touches a timestamp, so it's documented in
src/lib/utils.ts as well:
athlete_activities.start_time) store Garmin's
startTimeLocal wall-clock in a TIMESTAMPTZ column that Postgres reads as
UTC. The athlete's real local time therefore is the timestamp's UTC
wall-clock — accessors must read UTC parts, and comparing these to Date.now()
silently loses Israel's offset.israelNow() / israelToday() / israelDateAnchor().Do not try to fix this by setting a global TZ — that was tried and
reverted; TZ is deliberately unset outside of individual tests.
Further design notes live in docs/ (academy-feature.md, feed-plan.md,
run-chat-architecture.md, competitor-research.md).