refactor: split backend domains and add API behavior coverage

This commit is contained in:
3252a8
2026-05-13 18:38:10 +03:00
parent a96007d48a
commit 7401649841
103 changed files with 10558 additions and 9832 deletions
+3 -3
View File
@@ -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,
},
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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()
+2 -2
View File
@@ -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()
+5 -5
View File
@@ -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}"
+26 -28
View File
@@ -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}"
)
+4 -5
View File
@@ -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: