Files
remnawave-bedolaga-telegram…/app/webapi/routes/stats.py
2025-12-06 15:32:01 +03:00

295 lines
11 KiB
Python

from __future__ import annotations
from datetime import datetime
from app.database.crud.referral import get_referral_statistics
from app.database.crud.subscription import get_subscriptions_statistics, get_trial_statistics
from app.database.crud.transaction import get_transactions_statistics
from app.database.crud.user import get_users_statistics
from fastapi import APIRouter, Depends, Security
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import (
Subscription,
SubscriptionStatus,
Ticket,
TicketStatus,
Transaction,
TransactionType,
User,
UserStatus,
)
from ..dependencies import get_db_session, require_api_token
router = APIRouter()
def _kopeks_to_rubles(value: int | float | None) -> float:
return round((value or 0) / 100, 2)
async def _get_overview(db: AsyncSession) -> dict[str, object]:
total_users = await db.scalar(select(func.count()).select_from(User)) or 0
active_users = await db.scalar(
select(func.count()).select_from(User).where(User.status == UserStatus.ACTIVE.value)
) or 0
blocked_users = await db.scalar(
select(func.count()).select_from(User).where(User.status == UserStatus.BLOCKED.value)
) or 0
total_balance_kopeks = await db.scalar(
select(func.coalesce(func.sum(User.balance_kopeks), 0))
) or 0
active_subscriptions = await db.scalar(
select(func.count()).select_from(Subscription).where(
Subscription.status == SubscriptionStatus.ACTIVE.value,
)
) or 0
expired_subscriptions = await db.scalar(
select(func.count()).select_from(Subscription).where(
Subscription.status == SubscriptionStatus.EXPIRED.value,
)
) or 0
pending_tickets = await db.scalar(
select(func.count()).select_from(Ticket).where(
Ticket.status.in_([TicketStatus.OPEN.value, TicketStatus.ANSWERED.value])
)
) or 0
today = datetime.utcnow().date()
today_transactions = await db.scalar(
select(func.coalesce(func.sum(Transaction.amount_kopeks), 0)).where(
func.date(Transaction.created_at) == today,
Transaction.type == TransactionType.DEPOSIT.value,
)
) or 0
return {
"users": {
"total": total_users,
"active": active_users,
"blocked": blocked_users,
"balance_kopeks": int(total_balance_kopeks),
"balance_rubles": _kopeks_to_rubles(total_balance_kopeks),
},
"subscriptions": {
"active": active_subscriptions,
"expired": expired_subscriptions,
},
"support": {
"open_tickets": pending_tickets,
},
"payments": {
"today_kopeks": int(today_transactions),
"today_rubles": _kopeks_to_rubles(today_transactions),
},
}
@router.get(
"/overview",
summary="Общая статистика",
response_description="Агрегированные показатели пользователей, подписок, саппорта и платежей",
responses={
200: {
"content": {
"application/json": {
"example": {
"users": {
"total": 12345,
"active": 9876,
"blocked": 321,
"balance_kopeks": 1234567,
"balance_rubles": 12345.67,
},
"subscriptions": {
"active": 4321,
"expired": 210,
},
"support": {
"open_tickets": 42,
},
"payments": {
"today_kopeks": 654321,
"today_rubles": 6543.21,
},
}
}
}
}
},
)
async def stats_overview(
_: object = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> dict[str, object]:
return await _get_overview(db)
@router.get(
"/full",
summary="Полная статистика",
response_description="Расширенные показатели пользователей, подписок, платежей и рефералов",
responses={
200: {
"content": {
"application/json": {
"example": {
"overview": {
"users": {
"total": 12345,
"active": 9876,
"blocked": 321,
"balance_kopeks": 1234567,
"balance_rubles": 12345.67,
},
"subscriptions": {
"active": 4321,
"expired": 210,
},
"support": {
"open_tickets": 42,
},
"payments": {
"today_kopeks": 654321,
"today_rubles": 6543.21,
},
},
"users": {
"total_users": 12345,
"active_users": 9876,
"blocked_users": 321,
"new_today": 12,
"new_week": 345,
"new_month": 1234,
},
"subscriptions": {
"total_subscriptions": 9876,
"active_subscriptions": 8765,
"trial_subscriptions": 321,
"paid_subscriptions": 8444,
"purchased_today": 12,
"purchased_week": 210,
"purchased_month": 765,
"trial_to_paid_conversion": 42.5,
"renewals_count": 123,
"trial_statistics": {
"used_trials": 555,
"active_trials": 210,
"resettable_trials": 42,
},
},
"transactions": {
"period": {
"start_date": "2024-06-01T00:00:00Z",
"end_date": "2024-06-30T23:59:59Z",
},
"totals": {
"income_kopeks": 1234567,
"income_rubles": 12345.67,
"expenses_kopeks": 21000,
"expenses_rubles": 210,
"profit_kopeks": 1213567,
"profit_rubles": 12135.67,
"subscription_income_kopeks": 987654,
"subscription_income_rubles": 9876.54,
},
"today": {
"transactions_count": 42,
"income_kopeks": 654321,
"income_rubles": 6543.21,
},
"by_type": {
"deposit": {"count": 123, "amount": 1234567},
"withdrawal": {"count": 10, "amount": 21000},
},
"by_payment_method": {
"card": {"count": 100, "amount": 1000000}
},
},
"referrals": {
"users_with_referrals": 4321,
"active_referrers": 123,
"total_paid_kopeks": 765432,
"total_paid_rubles": 7654.32,
"today_earnings_kopeks": 12345,
"today_earnings_rubles": 123.45,
"week_earnings_kopeks": 23456,
"week_earnings_rubles": 234.56,
"month_earnings_kopeks": 34567,
"month_earnings_rubles": 345.67,
"top_referrers": [
{
"user_id": 123456789,
"display_name": "@testuser",
"username": "testuser",
"telegram_id": 123456789,
"total_earned_kopeks": 54321,
"referrals_count": 42,
}
],
},
}
}
}
}
},
)
async def stats_full(
_: object = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> dict[str, object]:
overview = await _get_overview(db)
users_stats = await get_users_statistics(db)
subscriptions_stats = await get_subscriptions_statistics(db)
trial_stats = await get_trial_statistics(db)
transactions_stats = await get_transactions_statistics(db)
referral_stats = await get_referral_statistics(db)
transactions_totals = transactions_stats.get("totals", {})
transactions_today = transactions_stats.get("today", {})
transactions_totals = {
**transactions_totals,
"income_rubles": _kopeks_to_rubles(transactions_totals.get("income_kopeks")),
"expenses_rubles": _kopeks_to_rubles(transactions_totals.get("expenses_kopeks")),
"profit_rubles": _kopeks_to_rubles(transactions_totals.get("profit_kopeks")),
"subscription_income_rubles": _kopeks_to_rubles(
transactions_totals.get("subscription_income_kopeks")
),
}
transactions_today = {
**transactions_today,
"income_rubles": _kopeks_to_rubles(transactions_today.get("income_kopeks")),
}
referral_stats = {
**referral_stats,
"total_paid_rubles": _kopeks_to_rubles(referral_stats.get("total_paid_kopeks")),
"today_earnings_rubles": _kopeks_to_rubles(
referral_stats.get("today_earnings_kopeks")
),
"week_earnings_rubles": _kopeks_to_rubles(referral_stats.get("week_earnings_kopeks")),
"month_earnings_rubles": _kopeks_to_rubles(referral_stats.get("month_earnings_kopeks")),
}
return {
"overview": overview,
"users": users_stats,
"subscriptions": {**subscriptions_stats, "trial_statistics": trial_stats},
"transactions": {
**transactions_stats,
"totals": transactions_totals,
"today": transactions_today,
},
"referrals": referral_stats,
}