Feat: Migrate database to PostgreSQL backend and add migrate_to_pg.py utility

This commit is contained in:
Admin 2026-06-26 21:12:46 +04:00
parent 3ad7651cd8
commit 2be7aea46e
5 changed files with 132 additions and 7 deletions

View file

@ -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'))

View file

@ -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()

View file

@ -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

117
database/migrate_to_pg.py Normal file
View file

@ -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()

View file

@ -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