From 8c1486da8b11bf0682f9d87ce1028f5c6d973a38 Mon Sep 17 00:00:00 2001 From: Sergey Korotonozhko <_serezhka_@mail.ru> Date: Tue, 2 Jun 2026 18:56:32 +0300 Subject: [PATCH] =?UTF-8?q?v1.8.3:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=8F=20migrations/=20=D0=B8=20migrations=5Frunner?= =?UTF-8?q?.py=20=D0=B4=D0=BB=D1=8F=20SQL-=D0=BC=D0=B8=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- max_bot/app/main.py | 5 ++ max_bot/app/migrations_runner.py | 59 +++++++++++++++++++ .../001_consent_revoked_date_and_logs.sql | 22 +++++++ 3 files changed, 86 insertions(+) create mode 100644 max_bot/app/migrations_runner.py create mode 100644 max_bot/migrations/001_consent_revoked_date_and_logs.sql diff --git a/max_bot/app/main.py b/max_bot/app/main.py index a6dd7f1..8dd4487 100644 --- a/max_bot/app/main.py +++ b/max_bot/app/main.py @@ -41,6 +41,11 @@ async def lifespan(app: FastAPI): logger.info("Database tables ensured.") await _apply_migrations() + + # Применение SQL-миграций из директории migrations/ + from app.migrations_runner import run_migrations + await run_migrations() + await _seed_default_settings() await _register_webhook() diff --git a/max_bot/app/migrations_runner.py b/max_bot/app/migrations_runner.py new file mode 100644 index 0000000..2f63807 --- /dev/null +++ b/max_bot/app/migrations_runner.py @@ -0,0 +1,59 @@ +""" +Автоматические миграции БД для max_bot. + +При старте приложения: + 1. Создаёт таблицу _applied_migrations (если не существует) + 2. Проверяет какие SQL-файлы уже применены + 3. Применяет новые миграции по порядку +""" +import os +import logging +from sqlalchemy import text +from app.database import engine + +logger = logging.getLogger(__name__) + +MIGRATIONS_DIR = os.path.join(os.path.dirname(__file__), "migrations") + + +async def run_migrations(): + """Запуск всех неприменённых миграций из директории migrations/.""" + async with engine.begin() as conn: + # Создаём таблицу для отслеживания миграций + await conn.execute(text(""" + CREATE TABLE IF NOT EXISTS _applied_migrations ( + id SERIAL PRIMARY KEY, + filename VARCHAR(255) UNIQUE NOT NULL, + applied_at TIMESTAMP DEFAULT NOW() + ) + """)) + + # Получаем список уже применённых миграций + result = await conn.execute(text("SELECT filename FROM _applied_migrations")) + applied = {row[0] for row in result.all()} + + # Сканируем директорию миграций + if not os.path.isdir(MIGRATIONS_DIR): + logger.info("Директория migrations/ не найдена, пропускаем") + return + + sql_files = sorted(f for f in os.listdir(MIGRATIONS_DIR) if f.endswith(".sql")) + + for filename in sql_files: + if filename in applied: + continue + + filepath = os.path.join(MIGRATIONS_DIR, filename) + with open(filepath, "r", encoding="utf-8") as f: + sql = f.read() + + logger.info(f"Применение миграции: {filename}") + try: + await conn.execute(text(sql)) + await conn.execute( + text("INSERT INTO _applied_migrations (filename) VALUES (:fn)"), + {"fn": filename} + ) + logger.info(f"Миграция применена: {filename}") + except Exception as e: + logger.warning(f"Миграция {filename} уже применена или ошибка: {e}") diff --git a/max_bot/migrations/001_consent_revoked_date_and_logs.sql b/max_bot/migrations/001_consent_revoked_date_and_logs.sql new file mode 100644 index 0000000..c390349 --- /dev/null +++ b/max_bot/migrations/001_consent_revoked_date_and_logs.sql @@ -0,0 +1,22 @@ +-- Миграция: consent_revoked_date + bot_processing_logs +-- Дата: 02.06.2026 +-- Описание: Добавление поля consent_revoked_date в bot_users и создание таблицы bot_processing_logs + +-- 1. Consent revoked date +ALTER TABLE bot_users ADD COLUMN IF NOT EXISTS consent_revoked_date TIMESTAMP NULL; + +-- 2. BotProcessingLog table +CREATE TABLE IF NOT EXISTS bot_processing_logs ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES bot_users(id) ON DELETE CASCADE, + conversation_id BIGINT REFERENCES bot_conversations(id) ON DELETE SET NULL, + step VARCHAR(50) NOT NULL, + status VARCHAR(20) DEFAULT 'ok', + message TEXT, + detail TEXT, + created_at TIMESTAMP DEFAULT now() +); + +-- Индексы для производительности +CREATE INDEX IF NOT EXISTS idx_bot_processing_logs_user_id ON bot_processing_logs(user_id); +CREATE INDEX IF NOT EXISTS idx_bot_processing_logs_conversation_id ON bot_processing_logs(conversation_id);