119 lines
5.1 KiB
Python
119 lines
5.1 KiB
Python
"""
|
||
Надёжный сервис экспорта всей истории чата в JSON и Git.
|
||
"""
|
||
import json
|
||
import logging
|
||
import subprocess
|
||
import os
|
||
from pathlib import Path
|
||
from datetime import datetime, timezone, timedelta
|
||
from sqlalchemy import select, func
|
||
from database.models import User, Message as DBMessage
|
||
|
||
logger = logging.getLogger("ChatExporter")
|
||
|
||
class ChatExporter:
|
||
def __init__(self, session):
|
||
self.session = session
|
||
self.base_dir = Path(__file__).parent.parent
|
||
self.export_dir = self.base_dir / "exports"
|
||
self.export_dir.mkdir(exist_ok=True)
|
||
self.uly_tz = timezone(timedelta(hours=4))
|
||
|
||
async def run_full_export(self):
|
||
"""Полный цикл: Генерация -> Запись в БД -> Push в Git"""
|
||
try:
|
||
now_local = datetime.now(self.uly_tz)
|
||
timestamp = now_local.strftime("%Y-%m-%d_%H-%M")
|
||
filename = f"chat_history_{timestamp}.json"
|
||
filepath = self.export_dir / filename
|
||
|
||
# 1. Собираем данные
|
||
users_res = await self.session.execute(select(User))
|
||
users = users_res.scalars().all()
|
||
|
||
msg_res = await self.session.execute(select(DBMessage).order_by(DBMessage.timestamp.asc()))
|
||
messages = msg_res.scalars().all()
|
||
|
||
export_data = {
|
||
"info": {
|
||
"exported_at": now_local.isoformat(),
|
||
"total_users": len(list(users)),
|
||
"total_messages": len(list(messages))
|
||
},
|
||
"users": [
|
||
{
|
||
"id": u.user_id, "username": u.username, "name": u.full_name,
|
||
"apt": u.apartment, "verified": u.verified
|
||
} for u in users
|
||
],
|
||
"messages": [
|
||
{
|
||
"id": m.id, "user_id": m.user_id, "text": m.text,
|
||
"date": m.timestamp.isoformat() if m.timestamp else None,
|
||
"topic": m.topic
|
||
} for m in messages
|
||
]
|
||
}
|
||
|
||
# 2. Сохраняем файл
|
||
with open(filepath, "w", encoding="utf-8") as f:
|
||
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||
|
||
# 3. Пушим в Forgejo
|
||
git_success = self._push_to_forgejo(filepath)
|
||
|
||
# 4. Логируем в БД
|
||
await self._log_to_db(filename, len(messages), git_success)
|
||
|
||
return {"success": True, "filename": filename, "messages": len(messages), "git": git_success}
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ Критическая ошибка экспорта: {e}", exc_info=True)
|
||
return {"success": False, "error": str(e)}
|
||
|
||
def _push_to_forgejo(self, filepath):
|
||
"""Отправка файла в Forgejo через системный git"""
|
||
try:
|
||
# Напрямую, без прокси
|
||
env = os.environ.copy()
|
||
for var in ['http_proxy', 'https_proxy', 'all_proxy', 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY']:
|
||
env.pop(var, None)
|
||
|
||
# Переходим в корень проекта для git команд
|
||
os.chdir(str(self.base_dir))
|
||
|
||
subprocess.run(["git", "add", "-f", str(filepath)], check=True, env=env)
|
||
subprocess.run(["git", "commit", "-m", f"📦 Авто-экспорт чата: {filepath.name}"], check=True, env=env)
|
||
subprocess.run(["git", "push", "origin", "main"], check=True, env=env)
|
||
|
||
logger.info(f"✅ Файл {filepath.name} успешно отправлен в Forgejo")
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка Git Push: {e}")
|
||
return False
|
||
|
||
async def _log_to_db(self, filename, count, git_pushed):
|
||
from sqlalchemy import text
|
||
try:
|
||
# Используем текстовый запрос, так как у нас нет модели для этой таблицы
|
||
sql = text("INSERT INTO chat_exports (filename, total_messages, git_pushed) VALUES (:f, :c, :g)")
|
||
await self.session.execute(sql, {"f": filename, "c": count, "g": 1 if git_pushed else 0})
|
||
await self.session.commit()
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка записи в таблицу экспортов: {e}")
|
||
|
||
async def get_stats(self):
|
||
"""Получить статистику для веб-панели"""
|
||
from sqlalchemy import text
|
||
try:
|
||
sql = text("SELECT COUNT(*), MAX(export_date) FROM chat_exports")
|
||
res = await self.session.execute(sql)
|
||
row = res.first()
|
||
if row:
|
||
count, last_date = row
|
||
return {"total": count or 0, "last_date": last_date}
|
||
return {"total": 0, "last_date": None}
|
||
except Exception as e:
|
||
logger.error(f"Error getting stats: {e}")
|
||
return {"total": 0, "last_date": None}
|