NotBigGhostandClaude Opus 5 94e2c09952 Simplify: общие хелперы в роутерах
- IP клиента для аудита брался инлайном в 19 местах шести модулей; теперь
  security.client_ip — за привратником адрес придётся читать из
  X-Forwarded-For, и одна точка правки для этого обязательна.
- Чтение загруженной картинки (лимит размера + sniff формата) было скопировано
  в четыре обработчика; вынесено в user_service.read_capped_image.
- Лимит размера вложения жил двумя одинаковыми константами в игроцком и
  админском роутере — перенесён к самим вложениям.
- update_nickname и set_active_group переиспользуют nickname_format_ok и
  group_service.get_membership вместо собственных копий проверки.
- Убраны осиротевшие импорты и комментарий-заготовка о вложениях, которые
  давно реализованы (MatchAttachment).

#8

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
2026-09-09 18:48:29 +03:00

Forbidden Stars — учёт партий

Мобильное веб-приложение для учёта партий настольной игры Forbidden Stars: профили игроков (вход через Telegram, пока — dev-заглушка), группы, создание партий с рандомом фракций и фильтром по дополнениям группы, статистика и общий топ, админ-панель.

  • Бэкенд / ядро + API: Python · FastAPI · SQLModel · SQLite
  • Фронтенд: React · Vite · TypeScript (SPA, общается с ядром только по REST API)
  • Хостинг: Raspberry Pi 4 (ARM64) в Docker

Структура

backend/    FastAPI: ядро, REST API, БД, миграции, seed
frontend/   React + Vite SPA
Dockerfile  multi-stage сборка (фронт собирается node, отдаётся FastAPI)
docker-compose.yml
.env.example

Локальная разработка

Единый лаунчер (run.ps1 / run.sh)

После разовой настройки (ниже) dev и test запускаются одной командой — что именно, решает APP_ENV в корневом .env:

.\run.ps1        # Windows   (Linux / macOS / Git Bash:  ./run.sh)
APP_ENV в .env что делает лаунчер
development uvicorn --reload (бэк) + vite (фронт) нативно, в двух окнах
test docker compose прод-клон на :8080 (со сборкой образа)
production не запускает — прод деплоится отдельно (см. «Git и деплой»)

Разовая настройка перед первым запуском — поднять venv бэка и зависимости фронта (после неё повседневный цикл — просто .\run.ps1). Vite проксирует /api на бэкенд.

1) Бэкенд

Windows PowerShell (команды по одной — в PowerShell 5.1 нет &&):

cd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1     # если ругается на политику: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
pip install -e ".[dev]"          # .[dev] — один аргумент (пакет + dev-зависимости)
Copy-Item ..\.env.example ..\.env  # ЕДИНЫЙ .env лежит в КОРНЕ репозитория
alembic upgrade head             # применит миграции и сидинг
python -m app.bootstrap          # создаст/синхронизирует администратора из .env
uvicorn app.main:app --reload    # http://localhost:8000  (Swagger: /api/docs)

Windows cmd.exe (здесь && поддерживается):

cd backend
python -m venv .venv
.venv\Scripts\activate.bat
pip install -e ".[dev]"
copy ..\.env.example ..\.env
alembic upgrade head
python -m app.bootstrap
uvicorn app.main:app --reload

Linux / macOS / Git Bash:

cd backend
python -m venv .venv && . .venv/Scripts/activate   # на *nix: . .venv/bin/activate
pip install -e ".[dev]"
cp ../.env.example ../.env
alembic upgrade head
python -m app.bootstrap
uvicorn app.main:app --reload

Единый .env — в корне репозитория (ForbidenStarsApp/.env), рядом с .env.example. Его читают и бэкенд (через абсолютный путь, независимо от рабочей папки), и docker compose. Файл — локальный, на каждой машине свой (dev/prod различаются строкой APP_ENV).

2) Фронт (в отдельном терминале)

cd frontend
npm install
npm run gen:api                # сгенерирует типы из живого OpenAPI (бэкенд должен быть запущен)
npm run dev                    # http://127.0.0.1:5173 или http://localhost:5173 (оба стека)

Вход в dev-режиме — экран /login: в деве доступны оба метода (Telegram + вход по нику), в проде — только Telegram (см. раздел «Аутентификация»).

Production (Docker на Pi)

cp .env.example .env           # заполните SECRET_KEY, ADMIN_PASSWORD и пр.
docker compose build           # на ARM64 собирается нативно
docker compose up -d           # приложение на :8000, БД на томе

FastAPI отдаёт собранный SPA и API с одного origin. Миграции и бутстрап админа выполняются автоматически при старте (entrypoint.sh).

Test — локальный прод-клон в контейнере

Тот же образ и поведение, что и прод (FastAPI отдаёт SPA, БД на томе, вход игроков только через Telegram), но на своей машине — для проверки прод-сборки до выката на Pi. Изолированные тома и порт 8080 (не конфликтует с dev-uvicorn на :8000).

Проще всего — через лаунчер: поставить APP_ENV=test в .env и запустить .\run.ps1. Вручную (тот же эффект):

docker compose -f docker-compose.test.yml up -d --build
# открыть http://localhost:8080  (Swagger: /api/docs)
docker compose -f docker-compose.test.yml down -v   # остановить и стереть тестовые данные
  • Окружение test (прод-клон), но COOKIE_SECURE=false (локально по HTTP).
  • Читает тот же .env, что dev/prod (отдельного .env.test больше нет); внутри контейнера APP_ENV форсится в test (см. docker-compose.test.yml).
  • Данные — на отдельных томах db-data-test / uploads-data-test (не пересекаются с dev и Pi).
  • Вход: админ-панель (/admin/login) работает сразу по логину/паролю; вход игроков — только через Telegram (нужен бот + публичный HTTPS/туннель на localhost:8080).
  • Не используйте $ в секретах. Единый .env читают и pydantic (dev — $ дословно), и docker compose (test/prod — $ = подстановка переменной). Чтобы значение совпадало везде, в SECRET_KEY/ADMIN_PASSWORD не должно быть $. Удобно генерировать так: python -c "import secrets;print(secrets.token_urlsafe(48))" (даёт только [A-Za-z0-9_-]).

Аутентификация

Методы входа зависят от окружения (APP_ENV):

dev test / prod
Telegram Login Widget ✓ ✓ (единственный)
Вход по нику (stub) ✓ ✗ (физически отсутствует)
  • Stub-вход (по нику) — только для разработки. Его код физически не попадает в прод: файлы backend/app/auth/dev_stub.py и backend/app/routers/dev_auth.py исключены из Docker-образа (.dockerignore), роутер подключается лишь при APP_ENV=development (app/main.py), а на фронте dev-блок вырезается из прод-сборки (import.meta.env.DEV).
  • Telegram: сервер проверяет подпись виджета (HMAC по TELEGRAM_BOT_TOKEN). GET /api/auth/config отдаёт доступные методы и telegram_bot_username для виджета.

Настройка Telegram (когда будете подключать реальный вход):

  1. Создать бота у @BotFather → получить токен и username.
  2. /setdomain у BotFather → оба домена: forbiddenstars.ru (prod) и forbidden-stars.ru (dev/test).
  3. В .env: TELEGRAM_BOT_TOKEN=..., TELEGRAM_BOT_USERNAME=... (без @).
  4. Виджету нужен HTTPS-домен (см. «Домен и публикация») — по голому HTTP/localhost он не работает.

Админ-вход (секретная панель, логин+пароль) — отдельный механизм, доступен во всех окружениях.

Окружения (dev / test / prod)

Один и тот же код; контур задаёт APP_ENV в едином .env (его читает лаунчер):

dev test (прод-клон локально) prod (Pi)
Запуск .\run.ps1 → uvicorn --reload + vite .\run.ps1 → docker compose -f docker-compose.test.yml docker compose up -d
APP_ENV development test (форсится в compose) production (форсится в compose)
Env-файл единый .env единый .env единый .env (на Pi)
Раздача SPA Vite (HMR), :5173 FastAPI, :8080 FastAPI, :8000
База данных backend/data/dev/… том db-data-test (/data) том db-data (/data)
Вход игроков Telegram + ник (stub) только Telegram только Telegram
  • Один .env на машину в корне (рядом с .env.example). APP_ENV в нём решает, что запустит лаунчер (development/test); прод-контейнер это значение игнорирует и всегда production. Отдельного .env.test больше нет.
  • Структура БД одна (общие миграции Alembic), файлы разные: dev → DEV_DATABASE_URL (backend/data/dev/), test и prod → PROD_DATABASE_URL (том /data; у test и prod это РАЗНЫЕ тома). Данные дева в образ не попадают (data/ в .dockerignore).
  • В Docker идёт только прод-код: dev-вход (stub) и тесты физически исключены из образа (.dockerignore); test — тот же образ, что и прод, просто локально и с APP_ENV=test.

Git и деплой

  • Ветка dev — рабочая: весь код, лаунчер, тесты. Повседневная разработка и test здесь.
  • Ветка main — релиз прода: готовое промоутишь из dev через merge dev→main. Файлы во всех ветках одинаковы (окружение задаёт .env/compose, а не ветка) → merge безболезненный; чистоту прод-образа обеспечивает .dockerignore, а не разные наборы файлов.
  • Деплой на Pi: git pull ветки main → docker compose up -d --build.
  • Чистая выгрузка в папку без git (опц.): scripts/export-prod.sh <dir> main (через git archive + export-ignore — в папку идёт ровно прод-набор, без лаунчера/тестов/dev-входа).

Секреты (.env) и данные (data/, *.db) в git не идут — см. .gitignore.

Домен и публикация

Публичные адреса отдаёт VPS-привратник (Caddy + HTTPS твоими сертификатами), а приложение само открывает к нему SSH reverse-туннель (дома белого IP нет — CGNAT). У test/prod туннель — отдельный контейнер в их docker-compose, и портов на хост они не публикуют (доступны только через домен):

домен как выставляется слот VPS
prod forbiddenstars.ru туннель-контейнер (постоянно) → app:8000 9000
test forbidden-stars.ru туннель-контейнер → app:8000 9001
dev forbidden-stars.ru run.ps1 при LOCAL_PUBLIC=vps → localhost:5173 9001
  • Прод (9000) и dev/test (9001) на разных слотах/доменах → прод и (dev|test) работают одновременно. Dev и test делят слот 9001 → по очереди.
  • Dev по умолчанию только на localhost; LOCAL_PUBLIC=vps + run.ps1 выставляет его на домен.
  • COOKIE_SECURE выводится автоматически (HTTPS-домен ⇒ Secure-cookie; dev+localhost ⇒ нет).
  • Пошаговая настройка — в deploy/: vps/ (Caddy, сертификаты, юзер tunnel), pi/ (app + туннель в Docker), образ туннеля — deploy/tunnel/. Ключ — deploy/tunnel/id_tunnel.

Дополнения и фракции

Дополнение Фракции
База (всем) Орки, Ультрамарины, Эльдары, Хаоситы
Forgotten Worlds Имперская гвардия, Тау, Некроны, Тираниды
Forsaken Voids Инквизиция, Сёстры битвы, Друкхари, Адептус Механикус

Группа отмечает имеющиеся дополнения; при создании партии доступны только фракции этих дополнений (база — всегда).

S
Description
Веб-приложение для учёта партий в настольной игре "Forbidden Stars"
https://forbiddenstars.ru
Readme
1.2 MiB
Languages
Python 57.6%
TypeScript 30.1%
Shell 7.3%
CSS 2.7%
PowerShell 1.5%
Other 0.7%