From 10d08d5b40143f1e4f500ea47ee0412edb12b803 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 6 Dec 2025 15:32:01 +0300 Subject: [PATCH] Add full statistics API endpoint --- app/webapi/routes/stats.py | 253 +++++++++++++++++++++++++++++++------ 1 file changed, 214 insertions(+), 39 deletions(-) diff --git a/app/webapi/routes/stats.py b/app/webapi/routes/stats.py index 70ebef64..032c14ce 100644 --- a/app/webapi/routes/stats.py +++ b/app/webapi/routes/stats.py @@ -2,6 +2,11 @@ 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 @@ -22,43 +27,11 @@ from ..dependencies import get_db_session, require_api_token router = APIRouter() -@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]: +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) @@ -103,7 +76,7 @@ async def stats_overview( "active": active_users, "blocked": blocked_users, "balance_kopeks": int(total_balance_kopeks), - "balance_rubles": round(total_balance_kopeks / 100, 2), + "balance_rubles": _kopeks_to_rubles(total_balance_kopeks), }, "subscriptions": { "active": active_subscriptions, @@ -114,6 +87,208 @@ async def stats_overview( }, "payments": { "today_kopeks": int(today_transactions), - "today_rubles": round(today_transactions / 100, 2), + "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, + }