From b23490586040d31c34720dce84c356161e875612 Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 24 Sep 2025 04:04:20 +0300 Subject: [PATCH] Fix admin user deletion and improve backups --- app/services/backup_service.py | 178 ++++++++++++++++++++------------- app/services/user_service.py | 68 ++++++++++++- 2 files changed, 175 insertions(+), 71 deletions(-) diff --git a/app/services/backup_service.py b/app/services/backup_service.py index 8c5f6b8d..a1fbee35 100644 --- a/app/services/backup_service.py +++ b/app/services/backup_service.py @@ -21,7 +21,9 @@ from app.database.models import ( ReferralEarning, Squad, ServiceRule, SystemSetting, MonitoringLog, SubscriptionConversion, SentNotification, BroadcastHistory, ServerSquad, SubscriptionServer, UserMessage, YooKassaPayment, - CryptoBotPayment, WelcomeText, Base + CryptoBotPayment, WelcomeText, Base, PromoGroup, AdvertisingCampaign, + AdvertisingCampaignRegistration, SupportAuditLog, Ticket, TicketMessage, + MulenPayPayment, Pal24Payment ) logger = logging.getLogger(__name__) @@ -61,29 +63,34 @@ class BackupService: self._settings = self._load_settings() self.backup_models_ordered = [ - ServiceRule, SystemSetting, + ServiceRule, Squad, - PromoCode, ServerSquad, - - User, - - WelcomeText, + PromoGroup, + User, + PromoCode, + WelcomeText, + UserMessage, Subscription, + SubscriptionServer, + SubscriptionConversion, Transaction, YooKassaPayment, CryptoBotPayment, + MulenPayPayment, + Pal24Payment, PromoCodeUse, ReferralEarning, - SubscriptionConversion, + SentNotification, BroadcastHistory, - UserMessage, - - SentNotification, - SubscriptionServer, + AdvertisingCampaign, + AdvertisingCampaignRegistration, + Ticket, + TicketMessage, + SupportAuditLog, ] - + if self._settings.include_logs: self.backup_models_ordered.append(MonitoringLog) @@ -329,60 +336,47 @@ class BackupService: await self._clear_database_tables(db) models_by_table = {model.__tablename__: model for model in self.backup_models_ordered} - - await self._restore_users_without_referrals(db, backup_data, models_by_table) - - for model in self.backup_models_ordered: - table_name = model.__tablename__ - - if table_name == "users": + + pre_restore_tables = {"promo_groups"} + for table_name in pre_restore_tables: + model = models_by_table.get(table_name) + if not model: continue - + records = backup_data.get(table_name, []) if not records: continue - + logger.info(f"🔥 Восстанавливаем таблицу {table_name} ({len(records)} записей)") - - for record_data in records: - try: - processed_data = self._process_record_data(record_data, model, table_name) - - primary_key_col = self._get_primary_key_column(model) - - if primary_key_col and primary_key_col in processed_data: - existing_record = await db.execute( - select(model).where( - getattr(model, primary_key_col) == processed_data[primary_key_col] - ) - ) - existing = existing_record.scalar_one_or_none() - - if existing and not clear_existing: - for key, value in processed_data.items(): - if key != primary_key_col: - setattr(existing, key, value) - logger.debug(f"Обновлена существующая запись {primary_key_col}={processed_data[primary_key_col]} в {table_name}") - else: - instance = model(**processed_data) - db.add(instance) - else: - instance = model(**processed_data) - db.add(instance) - - restored_records += 1 - - except Exception as e: - logger.error(f"Ошибка восстановления записи в {table_name}: {e}") - logger.error(f"Проблемные данные: {record_data}") - await db.rollback() - raise e - - restored_tables += 1 - logger.info(f"✅ Таблица {table_name} восстановлена") - + restored = await self._restore_table_records(db, model, table_name, records, clear_existing) + restored_records += restored + + if restored: + restored_tables += 1 + logger.info(f"✅ Таблица {table_name} восстановлена") + + await self._restore_users_without_referrals(db, backup_data, models_by_table) + + for model in self.backup_models_ordered: + table_name = model.__tablename__ + + if table_name == "users" or table_name in pre_restore_tables: + continue + + records = backup_data.get(table_name, []) + if not records: + continue + + logger.info(f"🔥 Восстанавливаем таблицу {table_name} ({len(records)} записей)") + restored = await self._restore_table_records(db, model, table_name, records, clear_existing) + restored_records += restored + + if restored: + restored_tables += 1 + logger.info(f"✅ Таблица {table_name} восстановлена") + await self._update_user_referrals(db, backup_data) - + await db.commit() break @@ -549,14 +543,64 @@ class BackupService: return col.name return None + async def _restore_table_records( + self, + db: AsyncSession, + model, + table_name: str, + records: List[Dict[str, Any]], + clear_existing: bool + ) -> int: + restored_count = 0 + + for record_data in records: + try: + processed_data = self._process_record_data(record_data, model, table_name) + + primary_key_col = self._get_primary_key_column(model) + + if primary_key_col and primary_key_col in processed_data: + existing_record = await db.execute( + select(model).where( + getattr(model, primary_key_col) == processed_data[primary_key_col] + ) + ) + existing = existing_record.scalar_one_or_none() + + if existing and not clear_existing: + for key, value in processed_data.items(): + if key != primary_key_col: + setattr(existing, key, value) + else: + instance = model(**processed_data) + db.add(instance) + else: + instance = model(**processed_data) + db.add(instance) + + restored_count += 1 + + except Exception as e: + logger.error(f"Ошибка восстановления записи в {table_name}: {e}") + logger.error(f"Проблемные данные: {record_data}") + await db.rollback() + raise e + + return restored_count + async def _clear_database_tables(self, db: AsyncSession): tables_order = [ - "subscription_servers", "sent_notifications", - "user_messages", "broadcast_history", "subscription_conversions", - "referral_earnings", "promocode_uses", "transactions", - "yookassa_payments", "cryptobot_payments", "welcome_texts", - "subscriptions", "users", "promocodes", "server_squads", - "squads", "service_rules", "system_settings", "monitoring_logs" + "ticket_messages", "tickets", "support_audit_logs", + "advertising_campaign_registrations", "advertising_campaigns", + "subscription_servers", "sent_notifications", + "user_messages", "broadcast_history", "subscription_conversions", + "referral_earnings", "promocode_uses", + "yookassa_payments", "cryptobot_payments", + "mulenpay_payments", "pal24_payments", + "transactions", "welcome_texts", "subscriptions", + "promocodes", "users", "promo_groups", + "server_squads", "squads", "service_rules", + "system_settings", "monitoring_logs" ] for table_name in tables_order: diff --git a/app/services/user_service.py b/app/services/user_service.py index dedd672a..5b7f57b4 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -17,7 +17,8 @@ from app.database.models import ( User, UserStatus, Subscription, Transaction, PromoCode, PromoCodeUse, ReferralEarning, SubscriptionServer, YooKassaPayment, BroadcastHistory, CryptoBotPayment, SubscriptionConversion, UserMessage, WelcomeText, - SentNotification, PromoGroup + SentNotification, PromoGroup, MulenPayPayment, Pal24Payment, + AdvertisingCampaign ) from app.config import settings @@ -493,7 +494,7 @@ class UserService: select(CryptoBotPayment).where(CryptoBotPayment.user_id == user_id) ) cryptobot_payments = cryptobot_result.scalars().all() - + if cryptobot_payments: logger.info(f"🔄 Удаляем {len(cryptobot_payments)} CryptoBot платежей") await db.execute( @@ -508,7 +509,49 @@ class UserService: await db.flush() except Exception as e: logger.error(f"❌ Ошибка удаления CryptoBot платежей: {e}") - + + try: + mulenpay_result = await db.execute( + select(MulenPayPayment).where(MulenPayPayment.user_id == user_id) + ) + mulenpay_payments = mulenpay_result.scalars().all() + + if mulenpay_payments: + logger.info(f"🔄 Удаляем {len(mulenpay_payments)} MulenPay платежей") + await db.execute( + update(MulenPayPayment) + .where(MulenPayPayment.user_id == user_id) + .values(transaction_id=None) + ) + await db.flush() + await db.execute( + delete(MulenPayPayment).where(MulenPayPayment.user_id == user_id) + ) + await db.flush() + except Exception as e: + logger.error(f"❌ Ошибка удаления MulenPay платежей: {e}") + + try: + pal24_result = await db.execute( + select(Pal24Payment).where(Pal24Payment.user_id == user_id) + ) + pal24_payments = pal24_result.scalars().all() + + if pal24_payments: + logger.info(f"🔄 Удаляем {len(pal24_payments)} Pal24 платежей") + await db.execute( + update(Pal24Payment) + .where(Pal24Payment.user_id == user_id) + .values(transaction_id=None) + ) + await db.flush() + await db.execute( + delete(Pal24Payment).where(Pal24Payment.user_id == user_id) + ) + await db.flush() + except Exception as e: + logger.error(f"❌ Ошибка удаления Pal24 платежей: {e}") + try: transactions_result = await db.execute( select(Transaction).where(Transaction.user_id == user_id) @@ -589,7 +632,7 @@ class UserService: select(BroadcastHistory).where(BroadcastHistory.admin_id == user_id) ) broadcast_history = broadcast_history_result.scalars().all() - + if broadcast_history: logger.info(f"🔄 Удаляем {len(broadcast_history)} записей истории рассылок") await db.execute( @@ -598,6 +641,23 @@ class UserService: await db.flush() except Exception as e: logger.error(f"❌ Ошибка удаления истории рассылок: {e}") + + try: + campaigns_result = await db.execute( + select(AdvertisingCampaign).where(AdvertisingCampaign.created_by == user_id) + ) + campaigns = campaigns_result.scalars().all() + + if campaigns: + logger.info(f"🔄 Очищаем создателя у {len(campaigns)} рекламных кампаний") + await db.execute( + update(AdvertisingCampaign) + .where(AdvertisingCampaign.created_by == user_id) + .values(created_by=None) + ) + await db.flush() + except Exception as e: + logger.error(f"❌ Ошибка обновления рекламных кампаний: {e}") try: if user.subscription: