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。 以下为已保存的摘录。
Трекер тренировок для тех, кто ходит по разным залам. Килограммы на грузоблоке одного производителя не сравнимы с килограммами на другом. Поэтому anygym никогда не пересчитывает вес между железками: история висит на упражнении, а упражнение привязано к одной модели оборудования. Между залами переносится не число, а сама модель — если тот же тренажёр стоит в двух залах, это одна железка и одна история. Плюс приложение помнит, как настроить каждую под тебя, и считает, какой вес поставить прямо сейчас. Проектные решения и алгоритм движка веса: docs/DESIGN.md.查看完整 README ↗
Трекер тренировок для тех, кто ходит по разным залам.
Килограммы на грузоблоке одного производителя не сравнимы с килограммами на другом. Поэтому anygym никогда не пересчитывает вес между железками: история висит на упражнении, а упражнение привязано к одной модели оборудования. Между залами переносится не число, а сама модель — если тот же тренажёр стоит в двух залах, это одна железка и одна история. Плюс приложение помнит, как настроить каждую под тебя, и считает, какой вес поставить прямо сейчас.
Проектные решения и алгоритм движка веса: docs/DESIGN.md.
| Слой | Что |
|---|---|
| Приложение | Next.js 16 (App Router), React 19, Tailwind 4 |
| База | Neon Postgres + Drizzle ORM |
| Вход | Auth.js v5, Google, доступ по allowlist |
| Хостинг | Vercel, деплой через Git-интеграцию |
npm install
npm run dev
Нужен заполненный .env.local — образец в .env.example.
DATABASE_URL и DATABASE_URL_UNPOOLED подтягиваются автоматически:
neon link --project-id <id> --branch production
AUTH_GOOGLE_ID и AUTH_GOOGLE_SECRET заводятся вручную в Google Cloud Console
(APIs & Services → Credentials → OAuth client ID → Web application). Redirect URI:
http://localhost:3000/api/auth/callback/google
https://<домен>/api/auth/callback/google
| Команда | Что делает |
|---|---|
npm run dev | Дев-сервер |
npm run build | Продакшн-сборка |
npm run typecheck | Проверка типов без сборки |
npm run lint | ESLint |
npm run test | Тесты движка веса (node:test) |
npm run db:generate | Сгенерировать миграцию из src/db/schema.ts |
npm run db:migrate | Применить миграции |
npm run db:backup | Дамп всех таблиц в backups/*.json |
npm run db:studio | Drizzle Studio |
npm run dry-run | Прогнать движок по плану на реальных данных, ничего не записывая |
Vercel собирает по git push в main — GitHub Actions не нужны, секретов
в GitHub нет.
Vercel → New Project → импортировать ArtsiomKazlouski/anygym.
Next.js определится сам, настройки сборки менять не надо.
Переменные окружения проекта (Settings → Environment Variables), для всех сред:
| Переменная | Кто читает | Откуда взять |
|---|---|---|
DATABASE_URL | приложение | из .env.local — та же база Neon |
AUTH_SECRET | Auth.js | тот же, что локально (см. ниже) |
AUTH_GOOGLE_ID | Auth.js | из .env.local |
AUTH_GOOGLE_SECRET | Auth.js | из .env.local |
ALLOWED_EMAILS | проверка входа | почты через запятую |
Больше ничего не нужно. DATABASE_URL_UNPOOLED читает только
drizzle.config.ts, то есть миграции, а они запускаются локально.
NEON_BRANCH не используется нигде — это метаданные neon link для
собственных нужд CLI. Обе переменные на Vercel мёртвые.
AUTH_SECRET — ключ подписи сессионной куки. Кто им владеет, тот может
выписать себе валидную сессию в обход Google: allowlist проверяется только
в момент входа, а не на каждом запросе.
Разводить прод и локаль разными секретами смысла нет, пока база одна:
DATABASE_URL всё равно общий и даёт прямой доступ ко всем данным. Запирать
форточку при открытой двери. Если понадобится настоящее разделение — завести
отдельную ветку Neon под разработку и переключить на неё .env.local; тогда
и секреты имеет смысл разводить.
ALLOWED_EMAILS обязателен. Пустой список не пускает никого — приложение
при потерянной переменной закрывается, а не открывается.
Google Cloud Console → APIs & Services → Credentials → OAuth client → Authorized redirect URIs, добавить рядом с локальным:
https://<домен>.vercel.app/api/auth/callback/google
Redeploy. Если вход падает на колбэке — добавить AUTH_TRUST_HOST=true.
.env.local смотрит на ту же ветку Neon, что и прод. Значит миграции
применяются локально и попадают на прод сразу:
npm run db:generate # сгенерировать из src/db/schema.ts
npm run db:migrate # применить
Отдельного шага в сборке нет намеренно: билд запускается на каждый деплой, и прогонять DDL оттуда — лишний способ выстрелить себе в ногу.
Локальная разработка идёт на боевой базе. Для приложения на одного человека
это осознанный выбор: одна история тренировок, никакой синхронизации между
средами. Если понадобится разделить — завести отдельную ветку Neon для локали
(neon branch create) и переключить на неё .env.local.
Перед миграцией, которая трогает данные:
npm run db:backup
Дамп всех таблиц в один JSON (backups/, в git не уходит). Не экспорт для
человека, а снимок ровно того, что лежит в базе, в том же виде, в каком его
можно залить обратно.
Со стороны Neon окно мгновенного восстановления на бесплатном тарифе — 6 часов
(history_retention_seconds: 21600). Откатиться «на вчера» без снимка нельзя.
neon snapshots create --name "manual-$(date +%Y%m%d)" # снять копию
neon snapshots list # что есть
neon snapshots restore <id> # восстановить
Снимок на бесплатном тарифе держится один: create на втором отвечает
snapshots limit exceeded. Поэтому рабочая страховка — db:backup, а снимок
Neon имеет смысл обновлять перед крупными изменениями, удалив предыдущий.
Автоматическое расписание не настроено. Включается так:
neon snapshots schedule set --frequency daily --hour 3 --retention 2592000
Приложение отдаёт манифест и иконки, ставится с домашнего экрана как обычное приложение и открывается без адресной строки. iOS: Поделиться → На экран «Домой». Android: меню → Установить приложение.
Вход ограничен списком ALLOWED_EMAILS. Пустой список не пускает никого — это
намеренно: при потерянной переменной окружения приложение закрывается, а не открывается.
src/
app/ маршруты App Router
auth.ts Auth.js с адаптером Drizzle
auth.config.ts edge-безопасная часть конфигурации (её берёт proxy.ts)
proxy.ts проверка входа на каждом запросе (бывший middleware)
db/
schema.ts схема БД — реализует docs/DESIGN.md, раздел 4
index.ts клиент Drizzle поверх Neon
lib/
muscles.ts мышечные группы — список и подписи
engine/ движок веса: сетка железки и назначение веса, чистые функции
session/ сбор истории, план пункта, действия тренировки
equipment/ каталог залов, железа и упражнений
templates/ планы тренировок
components/ разметка экранов
drizzle/ сгенерированные миграции
scripts/ обслуживающие скрипты
backups/ дампы базы (в git не уходят)
docs/DESIGN.md схема данных и движок веса