Compare commits

...
6 Commits
Author SHA1 Message Date
machka paslaandGitHub cdfd94c814 Merge pull request #108 from machka-pasla/dev
fixed locales and traffic after adding days
2025-10-24 10:34:25 +03:00
machka pasla 6513681125 fixed locales and traffic after adding days 2025-10-24 10:33:10 +03:00
machka paslaandGitHub f321fd04cf Merge pull request #107
fixed ad
2025-10-21 09:45:32 +03:00
machka pasla baede17adf fixed ad 2025-10-21 09:44:21 +03:00
machka paslaandGitHub 17bf8720a3 Merge pull request #105 from machka-pasla/dev
fixed my devices keyboard
2025-10-20 22:28:36 +03:00
machka pasla 280aced20e fixed my devices keyboard 2025-10-20 22:26:50 +03:00
5 changed files with 97 additions and 22 deletions
+30 -1
View File
@@ -183,9 +183,38 @@ async def my_subscription_command_handler(
max_devices_display = str(max_devices_int) max_devices_display = str(max_devices_int)
except (TypeError, ValueError): except (TypeError, ValueError):
max_devices_display = str(max_devices_value) max_devices_display = str(max_devices_value)
current_devices_display = "?"
user_uuid = active.get("user_id")
devices_response = None
if user_uuid:
try:
devices_response = await panel_service.get_user_devices(user_uuid)
except Exception:
logging.exception("Failed to load devices for user %s", user_uuid)
if devices_response:
devices_count: Optional[int] = None
if isinstance(devices_response, dict):
devices_list = devices_response.get("devices")
if isinstance(devices_list, list):
devices_count = len(devices_list)
elif isinstance(devices_list, int):
devices_count = devices_list
else:
try:
devices_count = len(devices_list) # type: ignore[arg-type]
except Exception:
devices_count = None
if devices_count is None:
total_value = devices_response.get("total")
if isinstance(total_value, int):
devices_count = total_value
elif isinstance(devices_response, list):
devices_count = len(devices_response)
if devices_count is not None:
current_devices_display = str(devices_count)
devices_button_text = get_text( devices_button_text = get_text(
"devices_button", "devices_button",
current_devices="?", current_devices=current_devices_display,
max_devices=max_devices_display, max_devices=max_devices_display,
) )
prepend_rows.append([ prepend_rows.append([
+23 -3
View File
@@ -568,6 +568,11 @@ class SubscriptionService:
bonus_days: int, bonus_days: int,
reason: str = "bonus", reason: str = "bonus",
) -> Optional[datetime]: ) -> Optional[datetime]:
reason_lower = (reason or "").lower()
apply_main_traffic_limit = any(
keyword in reason_lower for keyword in ("admin", "promo code", "referral", "bonus")
)
user = await user_dal.get_user_by_id(session, user_id) user = await user_dal.get_user_by_id(session, user_id)
if not user: if not user:
logging.warning( logging.warning(
@@ -594,8 +599,12 @@ class SubscriptionService:
start_date = datetime.now(timezone.utc) start_date = datetime.now(timezone.utc)
new_end_date_obj = start_date + timedelta(days=bonus_days) new_end_date_obj = start_date + timedelta(days=bonus_days)
# For promo code activations, use the configured user traffic limit # Apply main traffic limit for admin/referral/promo bonuses, fallback to trial limit otherwise
traffic_limit = self.settings.user_traffic_limit_bytes if "promo code" in reason.lower() else self.settings.trial_traffic_limit_bytes traffic_limit = (
self.settings.user_traffic_limit_bytes
if apply_main_traffic_limit
else self.settings.trial_traffic_limit_bytes
)
bonus_sub_payload = { bonus_sub_payload = {
"user_id": user_id, "user_id": user_id,
@@ -626,12 +635,23 @@ class SubscriptionService:
session, active_sub.subscription_id, new_end_date_obj session, active_sub.subscription_id, new_end_date_obj
) )
if (
apply_main_traffic_limit
and updated_sub_model
and updated_sub_model.traffic_limit_bytes != self.settings.user_traffic_limit_bytes
):
updated_sub_model = await subscription_dal.update_subscription(
session,
updated_sub_model.subscription_id,
{"traffic_limit_bytes": self.settings.user_traffic_limit_bytes},
)
if updated_sub_model: if updated_sub_model:
# Prepare panel update payload # Prepare panel update payload
panel_update_payload = self._build_panel_update_payload( panel_update_payload = self._build_panel_update_payload(
expire_at=new_end_date_obj, expire_at=new_end_date_obj,
traffic_limit_bytes=( traffic_limit_bytes=(
self.settings.user_traffic_limit_bytes if "promo code" in reason.lower() else None self.settings.user_traffic_limit_bytes if apply_main_traffic_limit else None
), ),
include_uuid=False, include_uuid=False,
) )
+36 -12
View File
@@ -98,23 +98,37 @@ async def get_campaign_stats(session: AsyncSession, campaign_id: int) -> Dict[st
trials = (await session.execute(trials_stmt)).scalar() or 0 trials = (await session.execute(trials_stmt)).scalar() or 0
# Payers (unique users with succeeded payments) # Payers (unique users with succeeded payments)
payers_stmt = select(func.count(func.distinct(Payment.user_id))).select_from(Payment).where( attrib_subq = (
select(
AdAttribution.user_id.label("user_id"),
AdAttribution.first_start_at.label("first_start_at"),
)
.where(AdAttribution.ad_campaign_id == campaign_id)
.subquery()
)
payers_stmt = (
select(func.count(func.distinct(Payment.user_id)))
.select_from(Payment)
.join(attrib_subq, Payment.user_id == attrib_subq.c.user_id)
.where(
and_( and_(
Payment.status == "succeeded", Payment.status == "succeeded",
Payment.user_id.in_( Payment.created_at >= attrib_subq.c.first_start_at,
select(AdAttribution.user_id).where(AdAttribution.ad_campaign_id == campaign_id) )
),
) )
) )
payers = (await session.execute(payers_stmt)).scalar() or 0 payers = (await session.execute(payers_stmt)).scalar() or 0
# Revenue sum # Revenue sum
revenue_stmt = select(func.coalesce(func.sum(Payment.amount), 0.0)).select_from(Payment).where( revenue_stmt = (
select(func.coalesce(func.sum(Payment.amount), 0.0))
.select_from(Payment)
.join(attrib_subq, Payment.user_id == attrib_subq.c.user_id)
.where(
and_( and_(
Payment.status == "succeeded", Payment.status == "succeeded",
Payment.user_id.in_( Payment.created_at >= attrib_subq.c.first_start_at,
select(AdAttribution.user_id).where(AdAttribution.ad_campaign_id == campaign_id) )
),
) )
) )
revenue = float((await session.execute(revenue_stmt)).scalar() or 0.0) revenue = float((await session.execute(revenue_stmt)).scalar() or 0.0)
@@ -151,10 +165,22 @@ async def get_totals(session: AsyncSession) -> Dict[str, float]:
total_cost = float((await session.execute(total_cost_stmt)).scalar() or 0.0) total_cost = float((await session.execute(total_cost_stmt)).scalar() or 0.0)
# Total revenue from all attributed users (unique users counted across all campaigns) # Total revenue from all attributed users (unique users counted across all campaigns)
revenue_stmt = select(func.coalesce(func.sum(Payment.amount), 0.0)).select_from(Payment).where( attrib_subq = (
select(
AdAttribution.user_id.label("user_id"),
AdAttribution.first_start_at.label("first_start_at"),
)
.subquery()
)
revenue_stmt = (
select(func.coalesce(func.sum(Payment.amount), 0.0))
.select_from(Payment)
.join(attrib_subq, Payment.user_id == attrib_subq.c.user_id)
.where(
and_( and_(
Payment.status == "succeeded", Payment.status == "succeeded",
Payment.user_id.in_(select(AdAttribution.user_id)), Payment.created_at >= attrib_subq.c.first_start_at,
)
) )
) )
total_revenue = float((await session.execute(revenue_stmt)).scalar() or 0.0) total_revenue = float((await session.execute(revenue_stmt)).scalar() or 0.0)
@@ -178,5 +204,3 @@ async def delete_campaign(session: AsyncSession, campaign_id: int) -> bool:
except Exception as e: except Exception as e:
logging.error(f"Failed to delete AdCampaign id={campaign_id}: {e}", exc_info=True) logging.error(f"Failed to delete AdCampaign id={campaign_id}: {e}", exc_info=True)
raise raise
+1
View File
@@ -257,6 +257,7 @@
"admin_user_invalid_days": "❌ Invalid number of days. Enter number from 1 to 3650.", "admin_user_invalid_days": "❌ Invalid number of days. Enter number from 1 to 3650.",
"admin_user_subscription_added_success": "✅ Successfully added {days} days to user {user_id}", "admin_user_subscription_added_success": "✅ Successfully added {days} days to user {user_id}",
"admin_user_subscription_added_error": "❌ Error adding subscription days", "admin_user_subscription_added_error": "❌ Error adding subscription days",
"admin_panel_user_creation_failed": "❌ Failed to create or link panel user for ID {user_id}. Manual intervention required.",
"admin_user_ban_toggle_success": "✅ User {status}", "admin_user_ban_toggle_success": "✅ User {status}",
"admin_user_ban_toggle_error": "❌ Error changing ban status", "admin_user_ban_toggle_error": "❌ Error changing ban status",
"admin_user_ban_success": "✅ User {input} has been banned", "admin_user_ban_success": "✅ User {input} has been banned",
+1
View File
@@ -257,6 +257,7 @@
"admin_user_invalid_days": "❌ Неверное количество дней. Введите число от 1 до 3650.", "admin_user_invalid_days": "❌ Неверное количество дней. Введите число от 1 до 3650.",
"admin_user_subscription_added_success": "✅ Успешно добавлено {days} дней подписки пользователю {user_id}", "admin_user_subscription_added_success": "✅ Успешно добавлено {days} дней подписки пользователю {user_id}",
"admin_user_subscription_added_error": "❌ Ошибка добавления дней подписки", "admin_user_subscription_added_error": "❌ Ошибка добавления дней подписки",
"admin_panel_user_creation_failed": "❌ Не удалось создать или привязать пользователя в панели для ID {user_id}. Требуется ручная проверка.",
"admin_user_ban_toggle_success": "✅ Пользователь {status}", "admin_user_ban_toggle_success": "✅ Пользователь {status}",
"admin_user_ban_toggle_error": "❌ Ошибка изменения статуса блокировки", "admin_user_ban_toggle_error": "❌ Ошибка изменения статуса блокировки",
"admin_user_ban_success": "✅ Пользователь {input} заблокирован", "admin_user_ban_success": "✅ Пользователь {input} заблокирован",