Feat: Migrate database to PostgreSQL backend and add migrate_to_pg.py utility
This commit is contained in:
parent
3ad7651cd8
commit
2be7aea46e
5 changed files with 132 additions and 7 deletions
|
|
@ -16,6 +16,10 @@ ADMIN_USER_ID = int(os.getenv('ADMIN_USER_ID', '0'))
|
||||||
# База данных
|
# База данных
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
DATABASE_PATH = os.getenv('DATABASE_PATH', str(BASE_DIR / 'database' / 'domovoy.db'))
|
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'))
|
EXPORT_DIR = os.getenv('EXPORT_DIR', str(BASE_DIR / 'exports'))
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,16 @@ from sqlalchemy.orm import sessionmaker, Session
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from typing import AsyncGenerator, Generator
|
from typing import AsyncGenerator, Generator
|
||||||
|
|
||||||
from config import DATABASE_PATH
|
from config import DATABASE_URL, DATABASE_URL_SYNC
|
||||||
|
|
||||||
|
# Синхронный движок
|
||||||
# Синхронный движок (для простых скриптов)
|
sync_engine = create_engine(DATABASE_URL_SYNC)
|
||||||
sync_engine = create_engine(f'sqlite:///{DATABASE_PATH}')
|
|
||||||
SyncSessionLocal = sessionmaker(bind=sync_engine, class_=Session)
|
SyncSessionLocal = sessionmaker(bind=sync_engine, class_=Session)
|
||||||
|
|
||||||
|
|
||||||
# Асинхронный движок (для бота)
|
# Асинхронный движок
|
||||||
async_engine = create_async_engine(
|
async_engine = create_async_engine(
|
||||||
f'sqlite+aiosqlite:///{DATABASE_PATH}',
|
DATABASE_URL,
|
||||||
echo=False
|
echo=False
|
||||||
)
|
)
|
||||||
AsyncSessionLocal = async_sessionmaker(
|
AsyncSessionLocal = async_sessionmaker(
|
||||||
|
|
@ -26,6 +25,7 @@ AsyncSessionLocal = async_sessionmaker(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_db() -> Generator[Session, None, None]:
|
def get_db() -> Generator[Session, None, None]:
|
||||||
"""Получить синхронную сессию БД"""
|
"""Получить синхронную сессию БД"""
|
||||||
db = SyncSessionLocal()
|
db = SyncSessionLocal()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
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.db import init_db, AsyncSessionLocal
|
||||||
from database.models import User, Service, Announcement
|
from database.models import User, Service, Announcement
|
||||||
|
|
|
||||||
117
database/migrate_to_pg.py
Normal file
117
database/migrate_to_pg.py
Normal 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()
|
||||||
|
|
@ -12,3 +12,5 @@ jinja2>=3.1.3
|
||||||
python-multipart>=0.0.6
|
python-multipart>=0.0.6
|
||||||
# faster-whisper # Moved to MacMini M4 API
|
# faster-whisper # Moved to MacMini M4 API
|
||||||
pydub
|
pydub
|
||||||
|
psycopg2-binary>=2.9.9
|
||||||
|
asyncpg>=0.29.0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue