feat(bot): модель данных, миграции Alembic, FSM в БД, TTL-чистка

- app/db/models.py: 13 таблиц (ARCHITECTURE §2.10); перечисления — VARCHAR + CHECK,
  JSON → JSONB на Postgres, BIGINT/INTEGER-ключи для SQLite
- миграция 0001 (автогенерация + ручная чистка: импорт sa.Text, дубли CHECK)
- миграции применяются при старте бота программно (app.db.migrate), script_location из пакета
- SqlStorage: FSM aiogram поверх fsm_state — состояние переживает рестарт (перенесено из #4)
- cleanup_loop: истёкшие черновики, снимки контейнеров и попытки входа старше суток
- тесты: upgrade/downgrade, дрейф моделей и миграций, CHECK, CASCADE, FSM, чистка

Refs #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 17:47:22 +03:00
co-authored by Claude Opus 5.5
parent 15f354d06b
commit f022731a5d
24 changed files with 1071 additions and 16 deletions
+18
View File
@@ -1,8 +1,12 @@
from collections.abc import AsyncIterator
from pathlib import Path
import pytest
from sqlalchemy.ext.asyncio import AsyncEngine
from app.config import AccessSettings, RuntimeSettings, Settings, TelegramSettings
from app.db.migrate import upgrade
from app.db.session import SessionMaker, make_engine, make_sessionmaker
from app.texts import Texts, load_texts
ROOT = Path(__file__).resolve().parent.parent
@@ -22,3 +26,17 @@ def settings(tmp_path: Path) -> Settings:
access=AccessSettings(admin_ids=frozenset({ADMIN})),
runtime=RuntimeSettings(config_dir=ROOT / "config", heartbeat_file=tmp_path / "hb"),
)
@pytest.fixture
async def engine() -> AsyncIterator[AsyncEngine]:
"""Fresh in-memory SQLite with all migrations applied — the same path the bot takes at startup."""
eng = make_engine("sqlite+aiosqlite://")
await upgrade(eng)
yield eng
await eng.dispose()
@pytest.fixture
def sessions(engine: AsyncEngine) -> SessionMaker:
return make_sessionmaker(engine)