Personal Telegram training tracker with a FastAPI backend, PostgreSQL, aiogram bot, and Telegram Mini App frontend.
The app is built for one allowed Telegram user, not as a public SaaS product.
- Telegram Mini App authentication through signed Telegram
initData. - JWT-protected workout API.
- Exercise import from
olegbal/exercises-dataset. - Curated exercise metadata and default weekly workout templates.
- Today's workout, session start/complete/skip, exercise complete/skip/replace.
- Set logging, set update/delete, workout history.
- React Mini App screens: Today, Workout, History, Exercise Library, GIF modal.
- Telegram bot commands:
/start,/today,/history,/help. - AI/OpenClaw placeholder routes and
WorkoutValidator.
Docker Compose starts:
postgres- PostgreSQL database.api- FastAPI backend.bot- Telegram bot.mini-app- Vite React Mini App.caddy- local reverse proxy.
Local URLs:
- Mini App through Caddy:
http://localhost:8080 - API through Caddy:
http://localhost:8080/api/health - API direct:
http://localhost:8000/health - Vite direct:
http://localhost:5173
To test the Mini App inside Telegram without configuring a custom domain, follow the Cloudflare Quick Tunnel runbook.
Create .env from the example:
cp .env.example .envSet at least:
APP_URL=https://training.example.com
API_URL=https://training.example.com/api
MINI_APP_URL=https://training.example.com
POSTGRES_PASSWORD=change_me
DATABASE_URL=postgresql+psycopg://training:change_me@postgres:5432/training
TELEGRAM_BOT_TOKEN=123456:telegram_bot_token
TELEGRAM_ALLOWED_USER_IDS=123456789
JWT_SECRET=replace_me_with_long_random_secret
ADMIN_SECRET=replace_me
ALLOW_ADMIN_ENDPOINTS=falseWith TELEGRAM_BOT_TOKEN=replace_me, the bot container starts in idle mode and does not poll Telegram.
The raw dataset comes from:
https://github.com/olegbal/exercises-dataset
Clone it into the expected local path:
git clone https://github.com/olegbal/exercises-dataset data/exercises-datasetThe API container mounts ./data as read-only at /data, so the dataset JSON is expected at:
/data/exercises-dataset/data/exercises.json
Start the stack and initialize the database:
docker compose up -d --build
docker compose exec api alembic upgrade headImport exercises:
docker compose exec api python -m app.scripts.import_exercises \
--input /data/exercises-dataset/data/exercises.jsonApply curated metadata:
docker compose exec api python -m app.scripts.seed_curated_exercises \
--input /data/curated_exercises.seed.jsonSeed workout templates:
docker compose exec api python -m app.scripts.seed_workout_templatesThe import is idempotent. Media URLs are derived from dataset image and gif_url fields using:
RAW_MEDIA_BASE=https://raw.githubusercontent.com/olegbal/exercises-dataset/main/Do not manually invent exercise media URLs.
Yes, this app can run on an existing OpenClaw VM.
For the current MVP, OpenClaw is not required as an active AI engine. The VM can simply host the Docker Compose stack:
git clone <this-repo-url>
cd Training-program-app
cp .env.example .envFill .env, clone the dataset, then run:
docker compose up -d --build
docker compose exec api alembic upgrade head
docker compose exec api python -m app.scripts.import_exercises \
--input /data/exercises-dataset/data/exercises.json
docker compose exec api python -m app.scripts.seed_curated_exercises \
--input /data/curated_exercises.seed.json
docker compose exec api python -m app.scripts.seed_workout_templatesThe current compose file exposes Caddy on local port 8080. For a real Telegram Mini App URL, put your existing OpenClaw VM reverse proxy in front of http://127.0.0.1:8080, or adjust Compose/Caddy to expose HTTPS on ports 80 and 443.
Telegram Mini Apps require HTTPS in production.
OpenClaw will sit behind the backend, not inside the Mini App.
Prepared routes:
POST /ai/generate-workout
POST /ai/replace-exercise
POST /ai/explain-technique
POST /ai/analyze-progress
POST /ai/validate-workoutCurrent behavior:
/ai/validate-workoutruns deterministicWorkoutValidatorchecks.- Other
/ai/*routes return structured placeholders.
Future behavior:
- Mini App asks backend for an AI action.
- Backend gathers workout, history, curated exercises, and user rules.
- Backend calls OpenClaw.
- OpenClaw returns structured JSON.
- Backend validates the result with
WorkoutValidator. - Backend returns the result to Mini App.
OpenClaw should prefer curated exercises, include dataset media URLs, and must not choose hip thrust or glute bridge as a main exercise.
See also: docs/openclaw.md.
- User opens the Telegram bot.
- Bot checks
TELEGRAM_ALLOWED_USER_IDS. - Bot sends a button that opens the Mini App.
- Mini App sends Telegram
initDatatoPOST /auth/telegram. - Backend validates the Telegram HMAC with
TELEGRAM_BOT_TOKEN. - Backend checks the Telegram ID allowlist.
- Backend returns a JWT.
- Mini App uses
Authorization: Bearer <jwt>for workout API calls.
Never trust initDataUnsafe from the frontend.
After database setup and seeds:
curl http://localhost:8000/health
curl "http://localhost:8000/exercises/search?q=leg%20press"
curl "http://localhost:8000/workouts/today"Expected:
/healthreturns{"status":"ok"}.- exercise search returns imported exercises with
image_urlandgif_urlwhen dataset media exists. /workouts/todayreturns the correct template for the current weekday.
Full authenticated workout smoke requires signed Telegram Mini App initData. In production, the Mini App receives it from Telegram automatically.
API:
cd apps/api
.venv/bin/python -m ruff check app tests
.venv/bin/python -m pytest tests -qBot:
cd apps/bot
.venv/bin/python -m ruff check app tests
.venv/bin/python -m pytest tests -qMini App:
cd apps/mini-app
npm test -- --run
npm run buildDocker:
docker compose config
docker compose build api bot mini-appStart:
docker compose up -d --buildLogs:
docker compose logs -f api
docker compose logs -f botStop:
docker compose downReset local database:
docker compose down -vThis deletes the PostgreSQL volume.