From 2be7aea46e4f8640f1abc470b5767ab53e045e55 Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 26 Jun 2026 21:12:46 +0400 Subject: [PATCH] Feat: Migrate database to PostgreSQL backend and add migrate_to_pg.py utility --- config.py | 4 ++ database/db.py | 12 ++-- database/init_db.py | 4 +- database/migrate_to_pg.py | 117 ++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 database/migrate_to_pg.py diff --git a/config.py b/config.py index 5dd0b80..afb1940 100644 --- a/config.py +++ b/config.py @@ -16,6 +16,10 @@ ADMIN_USER_ID = int(os.getenv('ADMIN_USER_ID', '0')) # База данных BASE_DIR = Path(__file__).resolve().parent DATABASE_PATH = os.getenv('DATABASE_PATH', str(BASE_DIR / 'database' / 'domovoy.db')) +DB_TYPE = os.getenv('DB_TYPE', 'sqlite').lower() +DATABASE_URL = os.getenv('DATABASE_URL', f'sqlite+aiosqlite:///{DATABASE_PATH}') +DATABASE_URL_SYNC = os.getenv('DATABASE_URL_SYNC', f'sqlite:///{DATABASE_PATH}') + # Экспорт EXPORT_DIR = os.getenv('EXPORT_DIR', str(BASE_DIR / 'exports')) diff --git a/database/db.py b/database/db.py index 5c0d49b..fdfb0ed 100644 --- a/database/db.py +++ b/database/db.py @@ -6,17 +6,16 @@ from sqlalchemy.orm import sessionmaker, Session from sqlalchemy import create_engine from typing import AsyncGenerator, Generator -from config import DATABASE_PATH +from config import DATABASE_URL, DATABASE_URL_SYNC - -# Синхронный движок (для простых скриптов) -sync_engine = create_engine(f'sqlite:///{DATABASE_PATH}') +# Синхронный движок +sync_engine = create_engine(DATABASE_URL_SYNC) SyncSessionLocal = sessionmaker(bind=sync_engine, class_=Session) -# Асинхронный движок (для бота) +# Асинхронный движок async_engine = create_async_engine( - f'sqlite+aiosqlite:///{DATABASE_PATH}', + DATABASE_URL, echo=False ) AsyncSessionLocal = async_sessionmaker( @@ -26,6 +25,7 @@ AsyncSessionLocal = async_sessionmaker( ) + def get_db() -> Generator[Session, None, None]: """Получить синхронную сессию БД""" db = SyncSessionLocal() diff --git a/database/init_db.py b/database/init_db.py index c7e212e..89da7ab 100644 --- a/database/init_db.py +++ b/database/init_db.py @@ -5,7 +5,9 @@ """ import asyncio import sys -sys.path.insert(0, '/home/matrixhasyou/domovoy_bot') +from pathlib import Path +BASE_DIR = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(BASE_DIR)) from database.db import init_db, AsyncSessionLocal from database.models import User, Service, Announcement diff --git a/database/migrate_to_pg.py b/database/migrate_to_pg.py new file mode 100644 index 0000000..2096af7 --- /dev/null +++ b/database/migrate_to_pg.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Скрипт для миграции данных из SQLite в PostgreSQL. +1. Считывает данные из sqlite:////home/matrixhasyou/swarm-services/domovoy-bot/database/domovoy.db +2. Записывает данные в postgresql://gemini_admin:secure_swarm_pass_2026@192.168.10.105:5433/domovoy_db +""" +import sys +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(BASE_DIR)) + +from sqlalchemy import create_engine, MetaData, Table +from sqlalchemy.orm import sessionmaker + +SQLITE_URL = f"sqlite:///{BASE_DIR / 'database' / 'domovoy.db'}" +POSTGRES_URL = "postgresql://gemini_admin:secure_swarm_pass_2026@192.168.10.105:5433/domovoy_db" + +def migrate(): + print(f"🔄 Подключение к SQLite: {SQLITE_URL}") + sqlite_engine = create_engine(SQLITE_URL) + sqlite_meta = MetaData() + sqlite_meta.reflect(bind=sqlite_engine) + + print(f"🔄 Подключение к PostgreSQL: {POSTGRES_URL}") + pg_engine = create_engine(POSTGRES_URL) + pg_meta = MetaData() + pg_meta.reflect(bind=pg_engine) + + SqliteSession = sessionmaker(bind=sqlite_engine) + PgSession = sessionmaker(bind=pg_engine) + + sqlite_session = SqliteSession() + pg_session = PgSession() + + # Список таблиц в порядке зависимостей (сначала родители, потом потомки) + # Порядок важен из-за внешних ключей (Foreign Keys) + tables_order = [ + 'users', + 'initiative_group', + 'messages', + 'services', + 'verification_requests', + 'spy_logs', + 'announcements', + 'polls', + 'spam_warnings', + 'message_rates', + 'schedules', + 'ads', + 'payment_reminders', + 'profile_history', + 'achievements', + 'thanks', + 'events', + 'event_participants', + 'scheduled_posts', + 'broadcasts', + 'broadcast_reads', + 'digests', + 'email_audit', + 'email_export_logs', + 'artifacts' + ] + + # Сначала очистим таблицы в PostgreSQL в обратном порядке (чтобы не нарушать Foreign Keys) + print("🧹 Очистка целевых таблиц в PostgreSQL...") + for table_name in reversed(tables_order): + if table_name in pg_meta.tables: + pg_table = pg_meta.tables[table_name] + try: + pg_session.execute(pg_table.delete()) + print(f" Удалены старые записи из {table_name}") + except Exception as e: + print(f" ⚠️ Ошибка очистки {table_name}: {e}") + pg_session.commit() + + print("\n🚀 Начало переноса данных...") + for table_name in tables_order: + if table_name not in sqlite_meta.tables: + print(f" ⚠️ Таблица {table_name} отсутствует в SQLite, пропускаем.") + continue + + sqlite_table = sqlite_meta.tables[table_name] + pg_table = pg_meta.tables[table_name] + + # Получаем данные из SQLite + rows = sqlite_session.execute(sqlite_table.select()).all() + if not rows: + print(f" ℹ️ Таблица {table_name} пуста в SQLite") + continue + + print(f" 📦 Перенос {len(rows)} строк для таблицы {table_name}...") + + # Подготавливаем данные для вставки + data_to_insert = [] + for r in rows: + # Превращаем строку Row в словарь с именами колонок + data_to_insert.append(dict(r._mapping)) + + # Вставляем данные в PostgreSQL + try: + pg_session.execute(pg_table.insert(), data_to_insert) + pg_session.commit() + print(f" ✅ Перенесено {table_name}") + except Exception as e: + pg_session.rollback() + print(f" ❌ Ошибка переноса таблицы {table_name}: {e}") + sys.exit(1) + + sqlite_session.close() + pg_session.close() + print("\n🎉 Миграция успешно завершена!") + +if __name__ == "__main__": + migrate() diff --git a/requirements.txt b/requirements.txt index c404393..66f7169 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,5 @@ jinja2>=3.1.3 python-multipart>=0.0.6 # faster-whisper # Moved to MacMini M4 API pydub +psycopg2-binary>=2.9.9 +asyncpg>=0.29.0