План тренировок и питания на неделю.
Веб-приложение на одного пользователя: недельный план питания и тренировок, журнал фактически выполненного, замеры тела и графики прогресса.
- Сегодня — приёмы пищи по времени, тренировка, добавки, кольца «калории и белок к плану дня», заметка дня.
- Неделя — обзор семи дней: выполнение, калории, белок, тренировка.
- План недели — чем питаться, чем заниматься и какие добавки принимать в каждый день недели.
- Справочники (раскрываются по клику) — три раздела:
- Питание — блюда с ккал и БЖУ, рецептами и порциями.
- Тренировки — шаблоны и каталог упражнений. В самой тренировке ведётся журнал подходов (вес, повторы, RPE) с таймером отдыха.
- Добавки — что принимать и в какой дозировке.
- Прогресс — измерения, состав тела, калории и белок по дням, соблюдение плана, тоннаж, объём по группам мышц, кардио, прогрессия по упражнению.
- Настройки — часовой пояс, тема, бэкап и восстановление базы, смена пароля.
Шаблон отделён от факта. «План недели» — это шаблон. Когда вы открываете дату, из него один раз создаются строки журнала за этот день. Дальше день живёт сам: правка плана или блюда не меняет прошлое.
Из этого следуют три правила:
- БЖУ в журнале — снимок, а не ссылка. Исправили калорийность блюда — история за прошлые месяцы осталась прежней, метрики не «поехали».
- Цель на день — это план на день. Цель считается как сумма блюд, назначенных на этот день недели.
- Просмотр прошлой даты. Автоматически заводится только сегодняшний день. Пропущенный день открывается кнопкой «Заполнить день».
Первый вход: если INIT_USERNAME/INIT_PASSWORD не заданы, приложение покажет
экран первичной настройки и предложит придумать логин и пароль.
База поднимается пустой — никаких заготовленных блюд, упражнений и планов. Порядок заполнения такой:
- Справочники → Питание → Приёмы пищи — завтрак, обед, ужин: из них собирается день.
- Справочники → Питание → Блюда — что едите, с калориями и БЖУ.
- Справочники → Тренировки → Упражнения — каталог движений: из них собираются шаблоны тренировок.
- Справочники → Тренировки → Шаблоны — готовые тренировки, которые потом назначаются на дни недели.
- Справочники → Добавки — если принимаете.
- План недели — расставить всё это по дням.
Сервер: Node + TypeScript + Fastify + SQLite (libsql) + Drizzle ORM
Клиент: React + Vite + Tailwind + Recharts
shared/ zod-схемы и типы, общие для сервера и клиента
server/ Fastify API, Drizzle-схема, миграции, тесты
web/ React-приложение
npm installcp .env.example .envnpm run build && npm startОткрыть приложение на http://localhost:8080.
Готовый образ лежит во встроенном registry GitHub — ghcr.io/dkrut/fitweek.
echo "SESSION_SECRET=$(openssl rand -hex 32)" > .envdocker compose up -dСобрать образ из этого чекаута вместо загрузки — раскомментировать build: .
у сервиса app в docker-compose.yml и запустить:
docker compose up --build -dПоднимаются два контейнера: приложение и Caddy перед ним. Наружу смотрит только
Caddy — приложение доступно ему по внутренней сети и напрямую не публикуется.
Порты меняются переменными HTTP_PORT и HTTPS_PORT.
База лежит в томе fitweek_data (/data/app.db внутри контейнера).
| Переменная | По умолчанию | Назначение |
|---|---|---|
SESSION_SECRET |
— | Обязателен. Подпись сессионной куки |
SITE_ADDRESS |
:80 |
Домен для HTTPS; по умолчанию — обычный http |
HTTP_PORT / HTTPS_PORT |
80 / 443 |
Порты, которые слушает Caddy |
DATA_DIR |
/data |
Каталог файла БД |
INIT_USERNAME / INIT_PASSWORD |
— | Создать учётку при первом запуске |
PORT |
8080 |
Порт приложения внутри сети docker |
TZ |
Europe/Moscow |
Часовой пояс: от него зависит, какой день «сегодня» |
IMAGE_TAG |
latest |
Тег образа ghcr.io/dkrut/fitweek, который тянет compose |
Границу суток определяет сервер: журнал хранится по его календарю, и клиент спрашивает «какое сегодня» у него, а не у браузера. Если часовой пояс контейнера не совпадает с вашим, несколько часов в сутки «сегодня» в приложении будет означать соседнюю дату. Данные при этом не портятся, но путает.
Настройки → Часовой пояс показывают дату сервера и дату браузера рядом и предупреждают о расхождении. Если оно есть — задайте свой пояс и перезапустите контейнер:
echo "TZ=Asia/Tbilisi" >> .envПо умолчанию SITE_ADDRESS не задан, и приложение отдаётся по обычному http
на порту 80.
Впишите домен:
echo "SITE_ADDRESS=fitweek.example.com" >> .env && docker compose up -dДальше Caddy делает всё сам: получает сертификат, продлевает его без простоя и перенаправляет http на https.
Сертификат бесплатный, от Let's Encrypt по протоколу ACME (если тот недоступен,
Caddy идёт к резервному центру) — регистрироваться и что-то настраивать не надо.
Владение доменом подтверждается запросом на ваш же сервер, поэтому нужны
A-запись на его адрес и открытые снаружи порты 80 и 443: центр выдачи
стучится строго на них, и сдвинутые HTTP_PORT/HTTPS_PORT выдачу сломают.
На голый IP-адрес сертификат не выдают, только на доменное имя.
Сертификаты лежат в томе caddy-data, и том обязателен: без него каждый
перезапуск запрашивал бы новые и упёрся бы в недельный лимит выдачи. Чтобы
получать письма об истечении, задайте почту — как, написано в комментарии
в начале Caddyfile.
Флаг Secure у сессионной куки приложение ставит само, по протоколу запроса:
пришёл по https — кука с флагом, пришёл по http в локальной сети — без него.
Настройка прокси живёт в Caddyfile и монтируется в контейнер.
Настройки → Данные → Выгрузить бэкап — JSON со всеми данными: справочники, план недели, журнал, замеры. Восстановить из бэкапа в том же разделе заменяет текущее содержимое целиком.
Учётная запись и сессии в файл не попадают: при восстановлении в чистый экземпляр логин и пароль придётся задать заново, данные от этого не пострадают.