harden telegram webapp production readiness

This commit is contained in:
VPN SaaS Dev
2026-05-12 19:14:21 +09:00
parent e75697f83e
commit 2ba2e88432
27 changed files with 931 additions and 155 deletions

144
README.md
View File

@@ -1,68 +1,138 @@
# Drivers Bot
Telegram mini app для учета расходов автовладельца: заправки, ремонты, обслуживание, жидкости, статистика стоимости владения и расхода топлива.
Telegram bot + Telegram Mini App для учета автомобилей, заправок, сервиса, жидкостей, напоминаний и стоимости владения.
## Состав
- `app/` - FastAPI сервис. Через него работают и бот, и HTML5 mini app.
- `bot/` - aiogram 3 бот, который регистрирует пользователя, открывает mini app и показывает быстрые команды.
- `web/` - HTML5 Telegram WebApp фронт.
- `app/` - FastAPI API, статика Mini App, бизнес-логика и Alembic.
- `bot/` - aiogram 3 бот, который открывает Mini App и работает с API через внутренний токен.
- `web/` - статический frontend Telegram WebApp.
- `alembic/` - миграции PostgreSQL.
- `tests/` - базовые security/API тесты.
## Основные таблицы
## Production Mini App
- `users` - пользователь Telegram.
- `cars` - автомобили пользователя.
- `fuel_entries` - заправки: дата, одометр, литры, цена, стоимость, АЗС.
- `service_entries` - обслуживание, ремонты, жидкости, шины, страховка, налоги и прочие расходы.
Для production Mini App должен открываться только по публичному HTTPS-домену. Для текущего проекта:
Связи: `users 1:N cars`, `cars 1:N fuel_entries`, `cars 1:N service_entries`.
```text
https://drivers.smartsoltech.kr
```
В BotFather нужно выполнить:
```text
/setdomain
@seoulmate_officialbot
drivers.smartsoltech.kr
```
Важно:
- в BotFather указывается домен без `https://`;
- `WEBAPP_URL` или `PUBLIC_WEBAPP_URL` в `.env` должны быть `https://drivers.smartsoltech.kr`;
- нельзя использовать `localhost`, `127.0.0.1`, внутренний IP или `http://` для Telegram Mini App в production;
- если появляется `Bot domain invalid`, сначала проверь `/setdomain` и значение `WEBAPP_URL` в контейнере бота.
## Production .env
```dotenv
POSTGRES_DB=drivers
POSTGRES_USER=drivers
POSTGRES_PASSWORD=change-this-db-password
POSTGRES_PORT=5433
DATABASE_URL=postgresql+asyncpg://drivers:change-this-db-password@db:5432/drivers
BOT_TOKEN=123456:telegram-token
BOT_USERNAME=seoulmate_officialbot
WEBAPP_URL=https://drivers.smartsoltech.kr
PUBLIC_WEBAPP_URL=https://drivers.smartsoltech.kr
API_BASE_URL=http://api:8000
CORS_ORIGINS=https://drivers.smartsoltech.kr,https://t.me
INTERNAL_API_TOKEN=change-this-long-random-token
APP_ENV=production
ALLOW_DEV_AUTH=false
VAPID_PUBLIC_KEY=
```
`BOT_TOKEN`, `DATABASE_URL`, `WEBAPP_URL`, `API_BASE_URL`, `CORS_ORIGINS`, `INTERNAL_API_TOKEN` читаются только из env. Секреты не хранятся в коде.
## Nginx
Пример reverse proxy:
```nginx
server {
listen 80;
server_name drivers.smartsoltech.kr;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name drivers.smartsoltech.kr;
ssl_certificate /etc/letsencrypt/live/drivers.smartsoltech.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/drivers.smartsoltech.kr/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
```
## Запуск
1. Создай `.env`:
```bash
cp .env.example .env
docker compose up -d --build
```
2. Заполни `BOT_TOKEN` и `WEBAPP_URL`. Для Telegram mini app `WEBAPP_URL` должен быть HTTPS URL, доступный Telegram.
API локально: `http://localhost:8000`.
3. Подними сервисы:
Локальные проверки:
```bash
docker compose up --build
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q
.venv/bin/ruff check app bot tests
docker compose build
docker compose up -d db api
curl http://127.0.0.1:8000/health
docker compose down
```
API будет доступен на `http://localhost:8000`, документация - `http://localhost:8000/docs`.
## Авторизация API
## Локальный запуск без Docker
Пользовательские endpoint-ы требуют Telegram WebApp `initData` в заголовке:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
alembic upgrade head
uvicorn app.main:app --reload
```http
X-Telegram-Init-Data: query_id=...&user=...&auth_date=...&hash=...
```
В отдельном терминале:
Backend проверяет подпись Telegram, создает/обновляет пользователя и разрешает операции только с объектами владельца. Бот использует `INTERNAL_API_TOKEN` и `X-Telegram-User-Id`.
```bash
python -m bot.main
```
Публичное `/api/users` закрыто внутренним токеном. Для Mini App создание пользователя выполняется через `/api/users/webapp-auth`.
## API
## Основные endpoint-ы
Ключевые endpoint-ы:
- `GET /api/users/me`
- `POST /api/cars`, `GET /api/cars`, `GET/PATCH/DELETE /api/cars/{id}`
- `POST /api/fuel`, `GET /api/cars/{car_id}/fuel?limit=50&offset=0`
- `PATCH /api/fuel/{id}`, `DELETE /api/fuel/{id}`
- `POST /api/service`, `GET /api/cars/{car_id}/service?limit=50&offset=0`
- `PATCH /api/service/{id}`, `DELETE /api/service/{id}`
- `GET /api/cars/{car_id}/stats`
- `GET /api/users/{user_id}/reminders?limit=50&offset=0`
- `POST /api/ocr/parse-text-receipt`
- `POST /api/users` - создать или обновить пользователя Telegram.
- `POST /api/cars`, `GET /api/cars?owner_id=...` - автомобили.
- `POST /api/fuel`, `GET /api/cars/{car_id}/fuel` - заправки.
- `POST /api/service`, `GET /api/cars/{car_id}/service` - сервисные записи.
- `GET /api/cars/{car_id}/stats` - стоимость владения, топливо, пробег, расход л/100 км, цена 1 км.
- `GET /api/cars/{car_id}/charts/expenses.png` - график расходов через pandas/matplotlib.
Расход топлива считается по интервалам между полными баками (`is_full_tank=true`). Если данных мало, API возвращает `null`, а не выдуманную цифру.
## Что дальше
## OCR
Практичные следующие шаги: авторизация WebApp через проверку `initData`, CRUD редактирование записей, напоминания по `next_due_date` и `next_due_odometer`, экспорт в CSV/XLSX, валюта и единицы измерения на уровне пользователя.
Настоящий OCR по фото/PDF пока не подключен. Endpoint `POST /api/ocr/parse-text-receipt` честно разбирает только текстовый чек. Старый `/api/ocr/fuel-receipt` оставлен как deprecated-совместимость.