A microservices platform for managing mentoring relationships between employees and new hires. Includes a Telegram bot for day-to-day interactions and a Next.js admin dashboard for HR / team leads.
- Backend: 8 FastAPI services (
Python 3.14,async SQLAlchemy 2.0,uv) - Bot:
aiogram 3(Telegram) with Google OAuth callback - Frontend:
Next.js 16(React 19,next-intl,Tailwind 4,shadcn/ui) built with Bun - Infra:
PostgreSQL 18,Redis 8,MinIO(S3-compatible),Mox(mail server),pgAdmin,RedisInsight - Orchestration:
Docker Compose(dev + prod),GitHub Actions(CI + image publish)
- Architecture
- Quickstart (local dev)
- Environment variables
- Services and ports
- Development workflows
- Database and migrations
- Testing and coverage
- Production deployment
- CI/CD (GitHub Actions)
- Troubleshooting
- Repository layout
- License
Eight FastAPI services + one Next.js admin dashboard communicate over a shared Docker network. Each service owns its data (dedicated PostgreSQL database per service, its own Redis DB).
- Async-first: All DB I/O uses SQLAlchemy 2.0 async with
asyncpg. - Inter-service auth: Services authenticate each other with a shared
SERVICE_API_KEYheader. - Redis databases: Each service owns a dedicated Redis DB (auth=0, checklists=1, knowledge=2, telegram=3, meeting=4).
- DB-per-service: Each service has its own PostgreSQL database (auth_db, checklists_db, knowledge_db, notification_db, escalation_db, meeting_db, feedback_db, telegram_db, admin_web_db). Databases are created automatically on first start via init script.
- Rate limiting:
slowapion public API routes. - Dependency management:
uvfor Python services, Bun for admin web. - Docker builds: Single parameterized
DockerfilewithSERVICE_NAMEbuild arg shared across all Python services; admin web has its ownadmin_web/Dockerfile.
| Service | Port (host) | Responsibility |
|---|---|---|
auth_service |
8001 | Users, departments, mentor↔mentee relationships, JWT auth, invitations |
checklists_service |
8002 | Onboarding task templates and per-user progress tracking |
knowledge_service |
8003 | KB articles, categories, tags, file uploads, Q&A dialogues |
notification_service |
8004 | Telegram + SMTP notifications, scheduled alerts |
escalation_service |
8005 | Escalation workflow and status tracking |
meeting_service |
8006 | Meeting scheduling with Google Calendar |
feedback_service |
8007 | Feedback collection |
telegram_bot |
5670 | Telegram interface (aiogram 3) + Google OAuth callback |
admin_web |
3000 | Next.js admin dashboard (i18n: en / ru) |
mox |
8025 | Mail server (SMTP/IMAP) for romanandr.ru |
Infra ports: PostgreSQL 5432, Redis 6379, pgAdmin 5050, RedisInsight 5540, MinIO S3 9000 / console 9001, Mox SMTP 25 / SMTPS 465 / submission 587 / IMAPS 993.
Every Python service follows the same structure:
service_name/
service_name/
main.py # FastAPI app + lifespan hooks
config.py # Pydantic settings (reads from .env)
api/ # Route handlers
models/ # SQLAlchemy 2.0 async ORM models
repositories/ # Data access layer
schemas/ # Pydantic request/response schemas
services/ # Business logic
database/ # DB init / session management
utils/ # Caching helpers, misc
migrations/ # Alembic migrations (when applicable)
tests/
pyproject.toml
uv.lock- Docker + Docker Compose (v2)
make(optional, but all commands assume it)- For local frontend dev: Bun
- For local Python dev: uv
git clone https://github.com/Roman-Andr/mentor-bot.git
cd mentor-bot
cp .env.example .envEdit .env and set at minimum:
POSTGRES_PASSWORD— any value for local devTELEGRAM_BOT_TOKEN— from @BotFatherTELEGRAM_BOT_USERNAME— the bot's @usernameSERVICE_API_KEY— any string; shared between servicesGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET— only if you need Google Calendar / OAuthSMTP_*— only if you need email notifications
make start # docker compose up -d --build
make status # verify all containers are healthy
make logs # tail logs for all servicesFirst boot takes ~1–2 min: Postgres initializes, services run migrations via the entrypoint, MinIO warms up.
make mock-data # requires services to be running; see scripts/setup_mock_data.py
# or start fresh with mock data:
make full-reboot # reset-db + start + mock-data- Admin dashboard: http://localhost:3000
- Telegram bot: open your bot in Telegram
- pgAdmin: http://localhost:5050
- RedisInsight: http://localhost:5540
- MinIO console: http://localhost:9001 (root creds from
.env) - Mox admin: http://localhost:8025 (mail server for romanandr.ru)
- Mox mailbox: http://localhost:8025/mailbox
- Service health:
http://localhost:8001/health…8007/health
Copy .env.example → .env. Highlights:
| Variable | Purpose |
|---|---|
POSTGRES_USER |
DB user (default postgres) |
POSTGRES_PASSWORD |
DB password |
REDIS_URL |
e.g. redis://redis:6379 |
TELEGRAM_BOT_TOKEN |
From BotFather |
TELEGRAM_BOT_USERNAME |
Bot @username (no @) |
TELEGRAM_API_KEY |
Token used by services to call the bot |
SERVICE_API_KEY |
Shared secret for inter-service calls |
| Variable | Purpose |
|---|---|
GOOGLE_CLIENT_ID |
OAuth 2.0 client ID |
GOOGLE_CLIENT_SECRET |
OAuth 2.0 secret |
GOOGLE_REDIRECT_URI |
e.g. http://localhost:5670/api/v1/calendar/callback (dev) |
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_USE_TLS, DEFAULT_FROM_EMAIL.
S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_REGION, S3_USE_SSL, S3_SECURE_MODE, S3_PRESIGNED_URL_EXPIRY, MINIO_ROOT_USER, MINIO_ROOT_PASSWORD, KNOWLEDGE_S3_BUCKET, CHECKLISTS_S3_BUCKET.
| Variable | Notes |
|---|---|
CORS_ORIGINS |
JSON array — '["https://admin.example.com"]' in prod |
ALLOWED_HOSTS |
JSON array — Host header whitelist |
SECRET_KEY |
App secret |
JWT_SECRET_KEY |
JWT signing key |
| Variable | Notes |
|---|---|
DOCKER_USERNAME |
Docker Hub namespace used in image names |
IMAGE_TAG |
Tag to deploy (git short SHA or latest) |
REGISTRY |
Optional, defaults to docker.io |
IMAGE_PREFIX |
Optional, defaults to mentor-bot |
TZ=UTC, LOG_LEVEL=INFO, PGADMIN_DEFAULT_EMAIL, PGADMIN_DEFAULT_PASSWORD.
| Purpose | Port |
|---|---|
| admin_web | 3000 |
| auth_service | 8001 |
| checklists | 8002 |
| knowledge | 8003 |
| notification | 8004 |
| escalation | 8005 |
| meeting | 8006 |
| feedback | 8007 |
| telegram_bot | 5670 |
| PostgreSQL | 5432 |
| Redis | 6379 |
| pgAdmin | 5050 |
| RedisInsight | 5540 |
| MinIO S3 API | 9000 |
| MinIO console | 9001 |
| Mox SMTP | 25 |
| Mox SMTPS | 465 |
| Mox submission | 587 |
| Mox IMAPS | 993 |
| Debugger ports | 5670-5678, 9229 (admin_web) |
Each FastAPI service exposes OpenAPI docs at http://localhost:<port>/docs.
http://auth_service:8000, http://checklists_service:8000, http://knowledge_service:8000, etc. The admin web proxies all API calls server-side using these URLs.
make start # Build + start everything
make stop # Stop all containers
make restart # Full rebuild (down + up --build)
make full-reboot # reset-db + start + mock-data
make status # Container status table
make logs # Tail all service logs
make logs-<service> # Tail a specific service (auth, checklist, knowledge, meeting, etc.)
make reboot-<service> # Quick restart without rebuild
make restart-<service> # Rebuild + restart one service
make shell-<service> # Exec into container (bash/sh)
make prune # Clean dangling images / volumes / build cache
make clean # Full teardown (down -v --rmi all)make dev-admin # Spins up deps in Docker, runs `bun dev` on host
make dev-meeting # Spins up deps in Docker, follows meeting_service logscd auth_service
uv sync # Install deps
uv run pytest # Tests
uv run pytest tests/test_x.py # Single file
uv run ruff check . # Lint
uv run mypy . # Type check
uv run alembic upgrade head # Apply migrationscd admin_web
bun install
bun dev # Dev server with Turbopack
bun build # Production build
bun lint # ESLint
bun run test # Vitest (unit tests)
bun run test:watch
bun run test:coveragemake reset-locks # Remove + regenerate every uv.lock
make update-deps # Check PyPI + upgrade outdated Python depsAll Python services expose a debugpy listener when DEBUG=true is set in the environment (see docker-entrypoint.sh). Host-mapped debug ports are defined in docker-compose.override.yml (telegram=5670, auth=5672, … meeting=5678). admin_web exposes 9229 for Node inspector.
Every data-holding service uses Alembic. The entrypoint runs alembic upgrade head automatically on container start; if no alembic_version row exists (baseline DB), it stamps head instead of re-creating the schema.
# Create a revision from ORM changes
make migrate-revision SERVICE=auth_service MSG="add user avatar column"
# Apply all pending migrations for one service
make migrate-upgrade SERVICE=auth_service
# Downgrade one step (or to specific revision)
make migrate-downgrade SERVICE=auth_service REV=-1
# Inspect history / current revision
make migrate-history SERVICE=auth_service
make migrate-current SERVICE=auth_service
# Stamp a revision without running it
make migrate-stamp SERVICE=auth_service REV=headmake migrate-all # alembic upgrade head for every DB-backed servicemake backup-db # Dump to backups/backup_<timestamp>.sql
make restore-db FILE=backups/foo.sql # Restore from file
make reset-db # Drop volumes + build cache (destructive)make shell-postgres # psql
make shell-redis # redis-cli
make shell-miniomake test # All Python services
make test-admin # admin_web (Vitest)
make coverage # Run all + generate unified HTML dashboard
make coverage-serve # Re-serve existing reports at :8765
make coverage-cleancd <service> && uv run pytest
cd admin_web && bun run testEvery push/PR against main runs the full Python matrix (all 8 services), plus admin web lint + tests, on GitHub Actions. See .github/workflows/ci.yml.
There are two compose files:
docker-compose.yml+docker-compose.override.yml(auto-loaded) — local dev, builds images from source, bind-mounts code.docker-compose.prod.yml— production, pulls prebuilt images from a registry, adds resource limits, binds DB/Redis/MinIO ports to127.0.0.1only, and rotates logs (10 MB × 3).
Images are expected at docker.io/<DOCKER_USERNAME>/mentor-bot-<service>:<tag>.
- Add repo secrets
DOCKERHUB_USERNAMEandDOCKERHUB_TOKEN. - Push to
main(or tagv*). TheBuild and push imagesworkflow runs the test gate, then builds and pushes all 9 images to Docker Hub (sha-<short>,latest, and any branch/tag names). - On the VPS, follow Deploy on VPS.
make docker-login DOCKER_USERNAME=<yourname>
# Tag = current git short SHA (override with TAG=...)
DOCKER_USERNAME=<yourname> make build-push
DOCKER_USERNAME=<yourname> TAG=v1.2.3 make build-pushUnder the hood, scripts/build-and-push.sh uses docker buildx on linux/amd64 and pushes both <tag> and latest.
- Docker + Docker Compose v2
- Port 80/443 open (and whatever reverse proxy you run)
- Logged into Docker Hub (
docker login) for private images
# 1. Clone repo (you only need the compose + Makefile files, not source)
git clone https://github.com/Roman-Andr/mentor-bot.git
cd mentor-bot
# 2. Create production .env
# Option A: Interactive generator (recommended)
make generate-env
# This will prompt for domain, server IP, Telegram credentials, Google OAuth, SMTP, and Docker username,
# and auto-generate secure secrets for passwords and API keys.
# Option B: Manual
cp .env.example .env
# Edit: real secrets, CORS_ORIGINS, ALLOWED_HOSTS, GOOGLE_REDIRECT_URI (HTTPS),
# DOCKER_USERNAME, IMAGE_TAG=<sha or latest>
# Set S3_USE_SSL=true, S3_SECURE_MODE=true if S3 endpoint is HTTPS
# Change MINIO_ROOT_PASSWORD, SERVICE_API_KEY, JWT_SECRET_KEY, SECRET_KEY, POSTGRES_PASSWORD
# 3. Pull + start
make prod-pull
make prod-up
make prod-logs # Verify health
# One-shot pull + up
make prod-deploy TAG=<sha>IMAGE_TAG=<new_sha> docker compose -f docker-compose.prod.yml pull
IMAGE_TAG=<new_sha> docker compose -f docker-compose.prod.yml up -d
# Compose recreates only the services whose image digest changed.make prod-down
# Roll back: set IMAGE_TAG=<previous_sha> in .env, then `make prod-up`docker-compose.prod.yml binds admin_web to 127.0.0.1:3000, Postgres / Redis / MinIO to 127.0.0.1 as well. Terminate TLS in front (nginx, Caddy, Traefik) and proxy to http://127.0.0.1:3000. Forward the telegram bot's OAuth callback (:5670/auth/google/callback) if you rely on it.
- Rotate all secrets in
.env(SERVICE_API_KEY,JWT_SECRET_KEY,SECRET_KEY, Postgres password, MinIO root). - Set
CORS_ORIGINSandALLOWED_HOSTSto your real domains (no["*"]). - Set
GOOGLE_REDIRECT_URIto your public HTTPS callback URL and update it in Google Cloud Console. - Update
TELEGRAM_BOT_TOKENto a production bot. - Configure SMTP to a real provider.
- Set up automated backups:
make backup-dbvia cron → off-box storage (S3, etc.). - TLS-terminating reverse proxy in front of admin_web.
- Monitor service health endpoints (
/health) and containerHEALTHCHECKstates. - Set
LOG_LEVEL=INFOorWARNING(notDEBUG).
docker-compose.prod.yml sets conservative limits suitable for a 2 GB VPS:
| Service | mem_limit |
Notes |
|---|---|---|
| Python svc | 220 MB each | |
| admin_web | 350 MB | |
| Postgres | 700 MB | shared_buffers=256MB, 60 max conns |
| Redis | 100 MB | maxmemory 64mb, allkeys-lru |
| MinIO | 256 MB |
Adjust in docker-compose.prod.yml per your host.
To enable email delivery for your domain (e.g., example.com), configure the following DNS records. Replace example.com with your actual domain and 10.0.0.1 with your server's public IP.
| Type | Name | Value / Priority |
|---|---|---|
| A | @ | 10.0.0.1 |
| A | 10.0.0.1 | |
| A | vm-xxxxxx | 10.0.0.1 |
| CAA | @ | 0 issue letsencrypt.org |
| CNAME | autoconfig | mail.example.com. |
| CNAME | mta-sts | mail.example.com. |
| MX | @ | mail.example.com. (10) |
| SRV | _autodiscover._tcp | mail.example.com. (0) |
| TXT | @ | v=spf1 ip4:10.0.0.1 ip4:172.17.0.1 ip4:172.18.0.1 mx ~all |
| TXT | v=spf1 a -all | |
| TXT | _dmarc | v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com!10m |
| TXT | mta-sts | v=STSv1; id=20260101T000000Z |
| TXT | _smtp._tls | v=TLSRPTv1; rua=mailto:tls-reports@example.com |
Generate two DKIM key pairs in Mox (see Mox admin UI at http://localhost:8025) and add the corresponding TXT records:
| Type | Name | Value |
|---|---|---|
| TXT | 2026a._domainkey | v=DKIM1; h=sha256; p=... |
| TXT | 2026b._domainkey | v=DKIM1; h=sha256; p=... |
After updating DNS records, verify with:
dig A mail.example.com
dig MX example.com
dig TXT _dmarc.example.com
dig TXT 2026a._domainkey.example.comTest email delivery via Mox admin panel at http://localhost:8025/mailbox.
Located in .github/workflows/:
build-and-push.yml— on push tomainorv*tag: re-runs the test gate, then builds + pushes every service image to Docker Hub with tagssha-<short>, branch/tag name, andlatest(onmainonly). Manual runs viaworkflow_dispatchaccept an extra tag.image-retention.yml— manual (workflow_dispatch) cleanup of old SHA tags on Docker Hub. Keepslatest, anyv*tag, and theNnewest SHA tags (default 5).
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN(Docker Hub access token with read/write/delete scope)
make status
make logs-<service>
docker compose psThe entrypoint stamps head automatically if alembic upgrade fails on an existing schema. If you need a clean slate:
make reset-db && make startStop whatever owns the port, or remap in docker-compose.yml / docker-compose.override.yml.
Services resolve each other via Docker DNS (http://auth_service:8000, etc.). Verify docker network inspect mentor-bot_mentor_network lists all containers.
Check TELEGRAM_BOT_TOKEN, TELEGRAM_API_KEY, and make logs-telegram. The bot uses long polling; it does not need a public webhook for dev.
Verify S3_ACCESS_KEY / S3_SECRET_KEY match MINIO_ROOT_USER / MINIO_ROOT_PASSWORD. In production set S3_USE_SSL=true and S3_SECURE_MODE=true when the endpoint is HTTPS.
Expected — the entrypoint falls back to alembic stamp head. See docker-entrypoint.sh.
mentor-bot/
├── admin_web/ # Next.js 16 admin dashboard
├── auth_service/ # Users, departments, JWT, invitations
├── checklists_service/ # Onboarding task templates + progress
├── escalation_service/ # Escalation workflow
├── feedback_service/ # Feedback collection
├── knowledge_service/ # KB articles, tags, Q&A, files
├── meeting_service/ # Meeting scheduling + Google Calendar
├── notification_service/ # Telegram + email notifications
├── telegram_bot/ # aiogram 3 bot + Google OAuth callback
│
├── docker-compose.yml # Base compose (dev + prod source)
├── docker-compose.override.yml # Auto-loaded in dev (bind mounts, debug ports)
├── docker-compose.prod.yml # Standalone prod compose (pulls images)
├── Dockerfile # Shared Python services Dockerfile
├── docker-entrypoint.sh # Runs migrations + starts app
├── Makefile # Every workflow shortcut
│
├── scripts/
│ ├── build-and-push.sh # Build + push all images
│ ├── setup_mock_data.py # Populate services with demo data
│ ├── aggregate_coverage.py # Unified coverage dashboard
│ ├── serve_coverage.py
│ ├── run_tests.py
│ ├── update_deps.py
│ └── mock_data/ # JSON fixtures
│
├── .github/workflows/
│ ├── ci.yml # Lint + test on PR / push
│ ├── build-and-push.yml # Build + publish images
│ └── image-retention.yml # Prune old Docker Hub tags
│
├── .env.example
└── pyproject.toml # Shared dev tooling (ruff, mypy, pytest)MIT. See pyproject.toml for author and project metadata.
