Production-oriented Telegram bot and Mini App for the Velina Cosmetic loyalty programme. The legacy product deep links and owner support chat remain available; the new system records loyalty purchases in PostgreSQL and offers a customer Mini App.
- configurable all-time turnover tiers (initially
0 → 3%,1000 → 5%,2000 → 7%); - a birthday promotion: a configurable 10% cashback overrides the tier rate from three calendar days before through three calendar days after the birthday;
- one-hour, one-use six-digit customer codes, stored only as HMAC digests;
- automatic bonus redemption capped at 10% of the full order and cashback on the cash-paid remainder;
- immutable purchase and bonus ledger, optional multi-product catalogue selection, reliable customer notifications through an outbox;
- owner/sales roles, owner search, XLSX exports, dashboard statistics and audit log;
- sales workspace in the Mini App and
/salein the bot use the same purchase preview and confirmation service; owner configuration is available in both the Mini App and bot commands (/stats,/find,/admins,/addsales,/tiers,/exportcustomers,/exportpurchases); - customer-visible immutable bonus ledger and tier-progress data for the Mini App;
- Google Sheets catalogue synchronization, support-chat block/unblock controls;
- FastAPI API, React/Vite Telegram Mini App, PostgreSQL, Redis, Caddy HTTPS, daily backup configuration with 180-day retention.
-
Install Python 3.14+ and uv.
-
Copy the environment template and replace every placeholder secret:
cp .env.example .env
-
Install the locked dependency set and run quality checks:
uv sync --group dev uv run ruff check app tests uv run ruff format --check app tests uv run mypy app uv run pytest
-
For the Mini App:
cd frontend bun install --frozen-lockfile bun run dev
The Mini App requires Telegram initData; it deliberately does not authenticate
in a regular browser.
For a local test bot, set APP_ENV=development and TELEGRAM_MODE=polling in
your ignored .env. Polling never registers a public webhook and is intended
only for a test token. The production configuration must use TELEGRAM_MODE=webhook.
No server deployment is performed by this repository or its CI automatically.
-
Create a deployment-only
.envfrom.env.example. Generate long random values forPOSTGRES_PASSWORD,REDIS_PASSWORD,WEBHOOK_SECRET, andLOYALTY_CODE_PEPPER; never reuse the bot token as another secret. -
Place the Google service-account JSON outside the repository and set
GOOGLE_SHEETS_CREDENTIALS_FILE_HOSTto that absolute host path. Its in-container counterpart remains/run/secrets/google-service-account.json. -
Point
romanandr.ruandwww.romanandr.ruas appropriate to the host before starting Caddy; setDOMAINandACME_EMAIL. -
Verify the manifest without starting services:
ENV_FILE=.env docker compose config --quiet
-
The compose stack has an explicit migration service, internal-only PostgreSQL and Redis, automatic TLS via Caddy, and daily PostgreSQL backups retained for 180 days in the
postgres-backupsDocker volume. Copy that volume to independent storage as part of operational disaster-recovery policy.
The CI workflow publishes backend, frontend, and Hysteria images to GHCR only after all backend,
frontend, and container checks pass on master. It deploys exact attested image digests through a
protected GitHub production environment and a forced-command SSH key. Application secrets remain
only on the server. See deploy/README.md for provisioning, rollback, VPN, TLS,
and backup details.
.env, credential JSON files, keys, backups and runtime state are ignored by Git.- Rotate any Telegram token or service-account key that was ever committed before using this public repository.
- Purchase corrections and returns are intentionally out of scope for the first release: confirmed purchases are immutable and do not alter loyalty balances.