refactor: split backend domains and add API behavior coverage
This commit is contained in:
@@ -160,7 +160,7 @@ async def change_broadcast_target_handler(
|
||||
return
|
||||
|
||||
await state.update_data(broadcast_target=new_target)
|
||||
user_fsm_data = await state.get_data()
|
||||
await state.get_data()
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
confirmation_prompt = _("admin_broadcast_confirm_prompt_short")
|
||||
try:
|
||||
@@ -254,7 +254,7 @@ async def confirm_broadcast_callback_handler(
|
||||
failed_count = 0
|
||||
admin_user = callback.from_user
|
||||
logging.info(
|
||||
f"Admin {admin_user.id} broadcasting '{(content.text or '')[:50]}...' to {len(user_ids)} users."
|
||||
f"Admin {admin_user.id} broadcasting '{(content.text or '')[:50]}...' to {len(user_ids)} users." # noqa: E501
|
||||
)
|
||||
|
||||
# Get message queue manager
|
||||
@@ -297,7 +297,7 @@ async def confirm_broadcast_callback_handler(
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_queued",
|
||||
"content": f"To user {uid}: [{content.content_type}] {(content.text or '')[:70]}...",
|
||||
"content": f"To user {uid}: [{content.content_type}] {(content.text or '')[:70]}...", # noqa: E501
|
||||
"is_admin_event": True,
|
||||
"target_user_id": uid,
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@ async def admin_panel_actions_callback_handler(
|
||||
|
||||
if not callback.message:
|
||||
logging.error(
|
||||
f"CallbackQuery {callback.id} from {callback.from_user.id} has no message for admin_action {action}"
|
||||
f"CallbackQuery {callback.id} from {callback.from_user.id} has no message for admin_action {action}" # noqa: E501
|
||||
)
|
||||
await callback.answer("Error processing action: message context lost.", show_alert=True)
|
||||
return
|
||||
|
||||
@@ -148,7 +148,7 @@ async def _display_formatted_logs(
|
||||
)
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"Failed to edit message for logs display (len: {len(text)}): {e}. Sending new message(s)."
|
||||
f"Failed to edit message for logs display (len: {len(text)}): {e}. Sending new message(s)." # noqa: E501
|
||||
)
|
||||
|
||||
max_chunk_size = 4000
|
||||
|
||||
@@ -138,7 +138,7 @@ async def view_payments_handler(
|
||||
|
||||
for i, payment in enumerate(payments, 1):
|
||||
text_parts.append(
|
||||
f"<b>{page * page_size + i}.</b> {format_payment_text(payment, i18n, current_lang, settings)}"
|
||||
f"<b>{page * page_size + i}.</b> {format_payment_text(payment, i18n, current_lang, settings)}" # noqa: E501
|
||||
)
|
||||
text_parts.append("") # Empty line between payments
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ async def create_bulk_promo_codes_final(
|
||||
|
||||
# Send CSV file if created
|
||||
if csv_file:
|
||||
csv_caption = f"📄 Промокоды для массового создания\n💫 Всего: {len(created_codes)} промокодов\n🎁 Бонус: {data['bonus_days']} дней каждый"
|
||||
csv_caption = f"📄 Промокоды для массового создания\n💫 Всего: {len(created_codes)} промокодов\n🎁 Бонус: {data['bonus_days']} дней каждый" # noqa: E501
|
||||
await message_obj.answer_document(csv_file, caption=csv_caption)
|
||||
|
||||
await state.clear()
|
||||
|
||||
@@ -113,7 +113,7 @@ async def view_promo_codes_handler(
|
||||
else "\n".join(
|
||||
[_("admin_active_promos_list_header"), ""]
|
||||
+ [
|
||||
f"{get_promo_status_emoji_and_text(p, i18n, current_lang)[0]} <code>{p.code}</code> | 🎁 {p.bonus_days}д | 📊 {p.current_activations}/{p.max_activations} | ⏰ {p.valid_until.strftime('%d.%m.%Y') if p.valid_until else _('admin_promo_valid_indefinitely')}"
|
||||
f"{get_promo_status_emoji_and_text(p, i18n, current_lang)[0]} <code>{p.code}</code> | 🎁 {p.bonus_days}д | 📊 {p.current_activations}/{p.max_activations} | ⏰ {p.valid_until.strftime('%d.%m.%Y') if p.valid_until else _('admin_promo_valid_indefinitely')}" # noqa: E501
|
||||
for p in promo_models
|
||||
]
|
||||
)
|
||||
@@ -204,7 +204,7 @@ async def promo_management_handler(
|
||||
# Формируем заголовок с информацией о страницах
|
||||
title = _("admin_promo_management_title")
|
||||
if total_pages > 1:
|
||||
title += f"\n{_('admin_promo_list_page_info', current=page + 1, total=total_pages, count=total_count)}"
|
||||
title += f"\n{_('admin_promo_list_page_info', current=page + 1, total=total_pages, count=total_count)}" # noqa: E501
|
||||
|
||||
await callback.message.edit_text(title, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await callback.answer()
|
||||
|
||||
@@ -97,7 +97,7 @@ async def show_statistics_handler(
|
||||
nodes_stats = await panel_service.get_nodes_statistics()
|
||||
|
||||
logging.info(
|
||||
f"Panel stats response: system={system_stats}, bandwidth={bandwidth_stats}, nodes={nodes_stats}"
|
||||
f"Panel stats response: system={system_stats}, bandwidth={bandwidth_stats}, nodes={nodes_stats}" # noqa: E501
|
||||
)
|
||||
|
||||
if system_stats:
|
||||
@@ -173,7 +173,7 @@ async def show_statistics_handler(
|
||||
total_nodes_count = len(unique_nodes)
|
||||
# Assume all nodes are active since we don't have status info
|
||||
stats_text_parts.append(
|
||||
f"🔗 {_('admin_panel_nodes_label')}: <b>{total_nodes_count}/{total_nodes_count}</b>"
|
||||
f"🔗 {_('admin_panel_nodes_label')}: <b>{total_nodes_count}/{total_nodes_count}</b>" # noqa: E501
|
||||
)
|
||||
else:
|
||||
# Use nodes total from system stats as fallback
|
||||
@@ -191,7 +191,7 @@ async def show_statistics_handler(
|
||||
|
||||
stats_text_parts.append(f"\n<b>💰 {_('admin_financial_stats_header')}</b>")
|
||||
stats_text_parts.append(
|
||||
f"📅 {_('admin_financial_today_label')}: <b>{financial_stats['today_revenue']:.2f} RUB</b> ({financial_stats['today_payments_count']} {_('admin_financial_payments_label')})"
|
||||
f"📅 {_('admin_financial_today_label')}: <b>{financial_stats['today_revenue']:.2f} RUB</b> ({financial_stats['today_payments_count']} {_('admin_financial_payments_label')})" # noqa: E501
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"📅 {_('admin_financial_week_label')}: <b>{financial_stats['week_revenue']:.2f} RUB</b>"
|
||||
@@ -200,7 +200,7 @@ async def show_statistics_handler(
|
||||
f"📅 {_('admin_financial_month_label')}: <b>{financial_stats['month_revenue']:.2f} RUB</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"🏆 {_('admin_financial_all_time_label')}: <b>{financial_stats['all_time_revenue']:.2f} RUB</b>"
|
||||
f"🏆 {_('admin_financial_all_time_label')}: <b>{financial_stats['all_time_revenue']:.2f} RUB</b>" # noqa: E501
|
||||
)
|
||||
|
||||
last_payments_models: List[Payment] = await payment_dal.get_recent_payment_logs_with_user(
|
||||
@@ -264,7 +264,7 @@ async def show_statistics_handler(
|
||||
stats_text_parts.append(f" {_('admin_stats_sync_time')}: {sync_time_str}")
|
||||
stats_text_parts.append(f" {_('admin_stats_sync_status')}: {sync_status_model.status}")
|
||||
stats_text_parts.append(
|
||||
f" {_('admin_stats_sync_users_processed')}: {sync_status_model.users_processed_from_panel}"
|
||||
f" {_('admin_stats_sync_users_processed')}: {sync_status_model.users_processed_from_panel}" # noqa: E501
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f" {_('admin_stats_sync_subs_synced')}: {sync_status_model.subscriptions_synced}"
|
||||
|
||||
@@ -79,7 +79,7 @@ async def _bind_panel_email_to_user(
|
||||
if not merged_user.email_verified_at:
|
||||
merged_user.email_verified_at = datetime.now(timezone.utc)
|
||||
logging.info(
|
||||
"Merged email-only user %s into user %s while binding panel email %s for panel UUID %s.",
|
||||
"Merged email-only user %s into user %s while binding panel email %s for panel UUID %s.", # noqa: E501
|
||||
user_with_email.user_id,
|
||||
merged_user.user_id,
|
||||
email_from_panel,
|
||||
@@ -169,9 +169,7 @@ async def perform_sync(
|
||||
try:
|
||||
panel_records_checked += 1
|
||||
panel_uuid = panel_user_dict.get("uuid")
|
||||
panel_subscription_uuid = panel_user_dict.get(
|
||||
"subscriptionUuid"
|
||||
) or panel_user_dict.get("shortUuid")
|
||||
panel_user_dict.get("subscriptionUuid") or panel_user_dict.get("shortUuid")
|
||||
telegram_id_from_panel = panel_user_dict.get("telegramId")
|
||||
email_from_panel = _normalize_panel_email(panel_user_dict.get("email"))
|
||||
|
||||
@@ -204,8 +202,8 @@ async def perform_sync(
|
||||
if not existing_user:
|
||||
existing_user = await user_dal.get_user_by_panel_uuid(session, panel_uuid)
|
||||
if existing_user:
|
||||
logging.info(
|
||||
f"Found user by panel UUID {panel_uuid}, telegramId: {existing_user.user_id}"
|
||||
logging.debug(
|
||||
f"Found user by panel UUID {panel_uuid}, telegramId: {existing_user.user_id}" # noqa: E501
|
||||
)
|
||||
# Update telegram ID if it was missing in panel data but we have local user
|
||||
if (
|
||||
@@ -213,7 +211,7 @@ async def perform_sync(
|
||||
and existing_user.user_id != telegram_id_from_panel
|
||||
):
|
||||
logging.warning(
|
||||
f"TelegramId mismatch: panel={telegram_id_from_panel}, local={existing_user.user_id}"
|
||||
f"TelegramId mismatch: panel={telegram_id_from_panel}, local={existing_user.user_id}" # noqa: E501
|
||||
)
|
||||
|
||||
# Finally, fall back to email. This mainly catches panel users that
|
||||
@@ -235,7 +233,7 @@ async def perform_sync(
|
||||
"email_verified_at": (
|
||||
datetime.now(timezone.utc) if email_from_panel else None
|
||||
),
|
||||
"username": None, # Username will be updated when user interacts with bot
|
||||
"username": None, # Username will be updated when user interacts with bot # noqa: E501
|
||||
"first_name": None, # Panel doesn't provide this info
|
||||
"last_name": None, # Panel doesn't provide this info
|
||||
"language_code": "ru", # Default language
|
||||
@@ -248,7 +246,7 @@ async def perform_sync(
|
||||
if was_created:
|
||||
users_created += 1
|
||||
logging.info(
|
||||
f"Created new user {telegram_id_from_panel} from panel sync with UUID {panel_uuid}"
|
||||
f"Created new user {telegram_id_from_panel} from panel sync with UUID {panel_uuid}" # noqa: E501
|
||||
)
|
||||
|
||||
existing_user = new_user
|
||||
@@ -272,12 +270,12 @@ async def perform_sync(
|
||||
if was_created:
|
||||
users_created += 1
|
||||
logging.info(
|
||||
f"Created new email user {new_user.user_id} from panel sync with UUID {panel_uuid}"
|
||||
f"Created new email user {new_user.user_id} from panel sync with UUID {panel_uuid}" # noqa: E501
|
||||
)
|
||||
existing_user = new_user
|
||||
except Exception as e_create_email:
|
||||
sync_errors.append(
|
||||
f"Error creating email user {email_from_panel}: {str(e_create_email)}"
|
||||
f"Error creating email user {email_from_panel}: {str(e_create_email)}" # noqa: E501
|
||||
)
|
||||
logging.error(
|
||||
f"Error creating email user {email_from_panel}: {e_create_email}"
|
||||
@@ -285,7 +283,7 @@ async def perform_sync(
|
||||
continue
|
||||
else:
|
||||
logging.debug(
|
||||
f"Panel user with UUID {panel_uuid} (no telegramId) not found in local DB - skipping"
|
||||
f"Panel user with UUID {panel_uuid} (no telegramId) not found in local DB - skipping" # noqa: E501
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -359,7 +357,7 @@ async def perform_sync(
|
||||
)
|
||||
except Exception as e_desc:
|
||||
logging.warning(
|
||||
f"Sync: Failed to update description for panel user {panel_uuid} (tg {actual_user_id}): {e_desc}"
|
||||
f"Sync: Failed to update description for panel user {panel_uuid} (tg {actual_user_id}): {e_desc}" # noqa: E501
|
||||
)
|
||||
|
||||
# Sync subscription data
|
||||
@@ -378,7 +376,7 @@ async def perform_sync(
|
||||
) or panel_user_dict.get("shortUuid")
|
||||
|
||||
if subscription_uuid_from_panel:
|
||||
# Если панель говорит, что подписка ACTIVE — сначала деактивируем все другие активные
|
||||
# Если панель говорит, что подписка ACTIVE — сначала деактивируем все другие активные # noqa: E501
|
||||
if panel_status == "ACTIVE":
|
||||
await session.execute(
|
||||
update(Subscription)
|
||||
@@ -397,7 +395,7 @@ async def perform_sync(
|
||||
)
|
||||
)
|
||||
|
||||
# Try to find subscription by its panel_subscription_uuid first (idempotent)
|
||||
# Try to find subscription by its panel_subscription_uuid first (idempotent) # noqa: E501
|
||||
existing_sub_by_uuid = (
|
||||
await subscription_dal.get_subscription_by_panel_subscription_uuid(
|
||||
session, subscription_uuid_from_panel
|
||||
@@ -420,12 +418,12 @@ async def perform_sync(
|
||||
subscriptions_synced_count += 1
|
||||
subscriptions_updated += 1
|
||||
user_was_updated = True
|
||||
logging.info(
|
||||
f"Synced existing subscription {existing_sub_by_uuid.subscription_id} "
|
||||
f"for user {actual_user_id}: expires {panel_expire_at}, status {panel_status}"
|
||||
logging.debug(
|
||||
f"Synced existing subscription {existing_sub_by_uuid.subscription_id} " # noqa: E501
|
||||
f"for user {actual_user_id}: expires {panel_expire_at}, status {panel_status}" # noqa: E501
|
||||
)
|
||||
else:
|
||||
# Create a new subscription only when we have a concrete subscription UUID
|
||||
# Create a new subscription only when we have a concrete subscription UUID # noqa: E501
|
||||
sub_payload = {
|
||||
"user_id": actual_user_id,
|
||||
"panel_user_uuid": panel_uuid,
|
||||
@@ -445,12 +443,12 @@ async def perform_sync(
|
||||
subscriptions_synced_count += 1
|
||||
subscriptions_created += 1
|
||||
user_was_updated = True
|
||||
logging.info(
|
||||
logging.debug(
|
||||
f"Created subscription {created_sub.subscription_id} "
|
||||
f"for user {actual_user_id} by panel_sub_uuid {subscription_uuid_from_panel}"
|
||||
f"for user {actual_user_id} by panel_sub_uuid {subscription_uuid_from_panel}" # noqa: E501
|
||||
)
|
||||
else:
|
||||
# No subscription UUID from panel: only update an already active subscription for this user/panel UUID
|
||||
# No subscription UUID from panel: only update an already active subscription for this user/panel UUID # noqa: E501
|
||||
active_sub = await subscription_dal.get_active_subscription_by_user_id(
|
||||
session, actual_user_id, panel_uuid
|
||||
)
|
||||
@@ -467,14 +465,14 @@ async def perform_sync(
|
||||
subscriptions_synced_count += 1
|
||||
subscriptions_updated += 1
|
||||
user_was_updated = True
|
||||
logging.info(
|
||||
logging.debug(
|
||||
f"Updated active subscription {active_sub.subscription_id} "
|
||||
f"for user {actual_user_id}: expires {panel_expire_at}, status {panel_status}"
|
||||
f"for user {actual_user_id}: expires {panel_expire_at}, status {panel_status}" # noqa: E501
|
||||
)
|
||||
else:
|
||||
# Without a concrete subscription UUID we avoid creating new records to keep sync idempotent
|
||||
# Without a concrete subscription UUID we avoid creating new records to keep sync idempotent # noqa: E501
|
||||
logging.debug(
|
||||
f"No subscriptionUuid for panel user {panel_uuid}; skipped creation for user {actual_user_id}"
|
||||
f"No subscriptionUuid for panel user {panel_uuid}; skipped creation for user {actual_user_id}" # noqa: E501
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -488,7 +486,7 @@ async def perform_sync(
|
||||
|
||||
except Exception as e_user:
|
||||
sync_errors.append(
|
||||
f"Error processing panel user {panel_user_dict.get('uuid', 'unknown')}: {str(e_user)}"
|
||||
f"Error processing panel user {panel_user_dict.get('uuid', 'unknown')}: {str(e_user)}" # noqa: E501
|
||||
)
|
||||
logging.error(f"Error syncing user: {e_user}")
|
||||
|
||||
@@ -685,7 +683,7 @@ async def sync_status_command_handler(
|
||||
f"<b>{_('admin_stats_last_sync_header')}</b>\n"
|
||||
f" {_('admin_stats_sync_time')}: {last_time_str}\n"
|
||||
f" {_('admin_stats_sync_status')}: {status_record_model.status}\n"
|
||||
f" {_('admin_stats_sync_users_processed')}: {status_record_model.users_processed_from_panel}\n"
|
||||
f" {_('admin_stats_sync_users_processed')}: {status_record_model.users_processed_from_panel}\n" # noqa: E501
|
||||
f" {_('admin_stats_sync_subs_synced')}: {status_record_model.subscriptions_synced}\n"
|
||||
f" {_('admin_stats_sync_details_label')}: {details_str}"
|
||||
)
|
||||
|
||||
@@ -319,7 +319,7 @@ async def format_user_card(
|
||||
# Panel info
|
||||
if user.panel_user_uuid:
|
||||
card_parts.append(
|
||||
f"{_('admin_user_panel_uuid_label')} {hcode(user.panel_user_uuid[:8] + '...' if len(user.panel_user_uuid) > 8 else user.panel_user_uuid)}"
|
||||
f"{_('admin_user_panel_uuid_label')} {hcode(user.panel_user_uuid[:8] + '...' if len(user.panel_user_uuid) > 8 else user.panel_user_uuid)}" # noqa: E501
|
||||
)
|
||||
|
||||
card_parts.append("") # Empty line
|
||||
@@ -371,12 +371,12 @@ async def format_user_card(
|
||||
premium_bonus_bytes = int(subscription_details.get("premium_bonus_bytes") or 0)
|
||||
if premium_unlimited:
|
||||
card_parts.append(
|
||||
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_unlimited'))}"
|
||||
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_unlimited'))}" # noqa: E501
|
||||
)
|
||||
elif premium_bonus_bytes > 0:
|
||||
bonus_gb = premium_bonus_bytes / (1024**3)
|
||||
card_parts.append(
|
||||
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_bonus_value', gb=f'{bonus_gb:.2f}'))}"
|
||||
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_bonus_value', gb=f'{bonus_gb:.2f}'))}" # noqa: E501
|
||||
)
|
||||
else:
|
||||
card_parts.append(
|
||||
@@ -929,7 +929,6 @@ async def handle_toggle_ban(
|
||||
|
||||
# Update on panel if user has panel UUID
|
||||
if user.panel_user_uuid:
|
||||
panel_status = "DISABLED" if new_ban_status else "ACTIVE"
|
||||
await panel_service.update_user_status_on_panel(
|
||||
user.panel_user_uuid, not new_ban_status
|
||||
)
|
||||
@@ -1431,7 +1430,7 @@ async def process_direct_message_handler(
|
||||
await message.answer(_("admin_direct_empty_message"))
|
||||
return
|
||||
|
||||
caption_with_signature = (content.text + admin_signature) if content.text else None
|
||||
(content.text + admin_signature) if content.text else None
|
||||
|
||||
# Send to target user using our fancy match/case function
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user