BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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 схема данных и движок веса