mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-08 21:20:26 +00:00
377 lines
13 KiB
Python
377 lines
13 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
|
|
from sqlalchemy import func, or_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import aliased, selectinload
|
|
|
|
from app.database.crud.referral import get_user_referral_stats
|
|
from app.database.crud.user import (
|
|
get_user_by_id,
|
|
get_user_by_telegram_id,
|
|
update_user,
|
|
)
|
|
from app.database.models import User
|
|
from app.services.partner_stats_service import PartnerStatsService
|
|
from app.utils.user_utils import (
|
|
get_detailed_referral_list,
|
|
get_effective_referral_commission_percent,
|
|
)
|
|
|
|
from ..dependencies import get_db_session, require_api_token
|
|
from ..schemas.partners import (
|
|
ChangeData,
|
|
DailyStats,
|
|
DailyStatsResponse,
|
|
EarningsByPeriod,
|
|
GlobalPartnerStats,
|
|
GlobalPartnerSummary,
|
|
NewReferralsByPeriod,
|
|
PartnerReferralCommissionUpdate,
|
|
PartnerReferralItem,
|
|
PartnerReferralList,
|
|
PartnerReferrerDetail,
|
|
PartnerReferrerItem,
|
|
PartnerReferrerListResponse,
|
|
PayoutsByPeriod,
|
|
PeriodChange,
|
|
PeriodComparisonResponse,
|
|
PeriodData,
|
|
ReferralsCountByPeriod,
|
|
ReferrerDetailedStats,
|
|
ReferrerSummary,
|
|
TopReferralItem,
|
|
TopReferralsResponse,
|
|
TopReferrerItem,
|
|
TopReferrersResponse,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _apply_search_filter(query, search: str):
|
|
search_lower = f"%{search.lower()}%"
|
|
conditions = [
|
|
func.lower(User.username).like(search_lower),
|
|
func.lower(User.first_name).like(search_lower),
|
|
func.lower(User.last_name).like(search_lower),
|
|
func.lower(User.referral_code).like(search_lower),
|
|
]
|
|
|
|
if search.isdigit():
|
|
conditions.append(User.telegram_id == int(search))
|
|
conditions.append(User.id == int(search))
|
|
|
|
return query.where(or_(*conditions))
|
|
|
|
|
|
def _serialize_referrer(user: User, stats: dict) -> PartnerReferrerItem:
|
|
total_earned_kopeks = int(stats.get("total_earned_kopeks") or 0)
|
|
month_earned_kopeks = int(stats.get("month_earned_kopeks") or 0)
|
|
|
|
return PartnerReferrerItem(
|
|
id=user.id,
|
|
telegram_id=user.telegram_id,
|
|
username=user.username,
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
referral_code=user.referral_code,
|
|
referral_commission_percent=getattr(user, "referral_commission_percent", None),
|
|
effective_referral_commission_percent=get_effective_referral_commission_percent(user),
|
|
invited_count=int(stats.get("invited_count") or 0),
|
|
active_referrals=int(stats.get("active_referrals") or 0),
|
|
total_earned_kopeks=total_earned_kopeks,
|
|
total_earned_rubles=round(total_earned_kopeks / 100, 2),
|
|
month_earned_kopeks=month_earned_kopeks,
|
|
month_earned_rubles=round(month_earned_kopeks / 100, 2),
|
|
created_at=user.created_at,
|
|
last_activity=user.last_activity,
|
|
)
|
|
|
|
|
|
def _serialize_referral_item(referral: dict) -> PartnerReferralItem:
|
|
balance_kopeks = int(referral.get("balance_kopeks") or 0)
|
|
total_earned_kopeks = int(referral.get("total_earned_kopeks") or 0)
|
|
|
|
return PartnerReferralItem(
|
|
id=int(referral.get("id")),
|
|
telegram_id=int(referral.get("telegram_id")),
|
|
full_name=str(referral.get("full_name")),
|
|
username=referral.get("username"),
|
|
created_at=referral.get("created_at"),
|
|
last_activity=referral.get("last_activity"),
|
|
has_made_first_topup=bool(referral.get("has_made_first_topup", False)),
|
|
balance_kopeks=balance_kopeks,
|
|
balance_rubles=round(balance_kopeks / 100, 2),
|
|
total_earned_kopeks=total_earned_kopeks,
|
|
total_earned_rubles=round(total_earned_kopeks / 100, 2),
|
|
topups_count=int(referral.get("topups_count") or 0),
|
|
days_since_registration=int(referral.get("days_since_registration") or 0),
|
|
days_since_activity=referral.get("days_since_activity"),
|
|
status=str(referral.get("status") or "inactive"),
|
|
)
|
|
|
|
|
|
@router.get("/referrers", response_model=PartnerReferrerListResponse)
|
|
async def list_referrers(
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
limit: int = Query(50, ge=1, le=200),
|
|
offset: int = Query(0, ge=0),
|
|
search: Optional[str] = Query(default=None),
|
|
) -> PartnerReferrerListResponse:
|
|
referral_alias = aliased(User)
|
|
has_referrals = (
|
|
select(referral_alias.id)
|
|
.where(referral_alias.referred_by_id == User.id)
|
|
.exists()
|
|
)
|
|
|
|
base_query = select(User).options(selectinload(User.referrer)).where(
|
|
or_(User.referral_code.isnot(None), has_referrals)
|
|
)
|
|
|
|
if search:
|
|
base_query = _apply_search_filter(base_query, search)
|
|
|
|
total_query = base_query.with_only_columns(func.count()).order_by(None)
|
|
total = await db.scalar(total_query) or 0
|
|
|
|
result = await db.execute(
|
|
base_query.order_by(User.created_at.desc()).offset(offset).limit(limit)
|
|
)
|
|
referrers = result.scalars().unique().all()
|
|
|
|
items: list[PartnerReferrerItem] = []
|
|
for referrer in referrers:
|
|
stats = await get_user_referral_stats(db, referrer.id)
|
|
items.append(_serialize_referrer(referrer, stats))
|
|
|
|
return PartnerReferrerListResponse(
|
|
items=items,
|
|
total=int(total),
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
|
|
@router.get("/referrers/{user_id}", response_model=PartnerReferrerDetail)
|
|
async def get_referrer_detail(
|
|
user_id: int,
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
limit: int = Query(50, ge=1, le=200),
|
|
offset: int = Query(0, ge=0),
|
|
) -> PartnerReferrerDetail:
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
stats = await get_user_referral_stats(db, user.id)
|
|
referrer_item = _serialize_referrer(user, stats)
|
|
|
|
referrals_data = await get_detailed_referral_list(db, user.id, limit=limit, offset=offset)
|
|
referral_items = [
|
|
_serialize_referral_item(referral) for referral in referrals_data.get("referrals", [])
|
|
]
|
|
|
|
referrals_list = PartnerReferralList(
|
|
items=referral_items,
|
|
total=int(referrals_data.get("total_count") or 0),
|
|
limit=limit,
|
|
offset=offset,
|
|
has_next=bool(referrals_data.get("has_next")),
|
|
has_prev=bool(referrals_data.get("has_prev")),
|
|
current_page=int(referrals_data.get("current_page") or 1),
|
|
total_pages=int(referrals_data.get("total_pages") or 1),
|
|
)
|
|
|
|
return PartnerReferrerDetail(referrer=referrer_item, referrals=referrals_list)
|
|
|
|
|
|
@router.patch("/referrers/{user_id}/commission", response_model=PartnerReferrerItem)
|
|
async def update_referrer_commission(
|
|
user_id: int,
|
|
payload: PartnerReferralCommissionUpdate,
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> PartnerReferrerItem:
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
await update_user(
|
|
db,
|
|
user,
|
|
referral_commission_percent=payload.referral_commission_percent,
|
|
)
|
|
|
|
stats = await get_user_referral_stats(db, user.id)
|
|
return _serialize_referrer(user, stats)
|
|
|
|
|
|
# ============================================================================
|
|
# РАСШИРЕННАЯ СТАТИСТИКА ПАРТНЁРОВ
|
|
# ============================================================================
|
|
|
|
|
|
@router.get("/stats", response_model=GlobalPartnerStats)
|
|
async def get_global_partner_stats(
|
|
days: int = Query(30, ge=1, le=365),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> GlobalPartnerStats:
|
|
"""Глобальная статистика партнёрской программы."""
|
|
data = await PartnerStatsService.get_global_partner_stats(db, days)
|
|
|
|
return GlobalPartnerStats(
|
|
summary=GlobalPartnerSummary(**data["summary"]),
|
|
payouts=PayoutsByPeriod(**data["payouts"]),
|
|
new_referrals=NewReferralsByPeriod(**data["new_referrals"]),
|
|
)
|
|
|
|
|
|
@router.get("/stats/daily", response_model=DailyStatsResponse)
|
|
async def get_global_daily_stats(
|
|
days: int = Query(30, ge=1, le=365),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> DailyStatsResponse:
|
|
"""Глобальная статистика по дням."""
|
|
data = await PartnerStatsService.get_global_daily_stats(db, days)
|
|
|
|
return DailyStatsResponse(
|
|
items=[DailyStats(**item) for item in data],
|
|
days=days,
|
|
user_id=None,
|
|
)
|
|
|
|
|
|
@router.get("/stats/top-referrers", response_model=TopReferrersResponse)
|
|
async def get_top_referrers(
|
|
limit: int = Query(10, ge=1, le=100),
|
|
days: Optional[int] = Query(None, ge=1, le=365),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> TopReferrersResponse:
|
|
"""Топ рефереров по заработку."""
|
|
data = await PartnerStatsService.get_top_referrers(db, limit, days)
|
|
|
|
return TopReferrersResponse(
|
|
items=[TopReferrerItem(**item) for item in data],
|
|
days=days,
|
|
)
|
|
|
|
|
|
@router.get("/referrers/{user_id}/stats", response_model=ReferrerDetailedStats)
|
|
async def get_referrer_detailed_stats(
|
|
user_id: int,
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> ReferrerDetailedStats:
|
|
"""Детальная статистика реферера."""
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
data = await PartnerStatsService.get_referrer_detailed_stats(db, user.id)
|
|
|
|
return ReferrerDetailedStats(
|
|
user_id=data["user_id"],
|
|
summary=ReferrerSummary(**data["summary"]),
|
|
earnings=EarningsByPeriod(**data["earnings"]),
|
|
referrals_count=ReferralsCountByPeriod(**data["referrals_count"]),
|
|
)
|
|
|
|
|
|
@router.get("/referrers/{user_id}/stats/daily", response_model=DailyStatsResponse)
|
|
async def get_referrer_daily_stats(
|
|
user_id: int,
|
|
days: int = Query(30, ge=1, le=365),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> DailyStatsResponse:
|
|
"""Статистика реферера по дням."""
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
data = await PartnerStatsService.get_referrer_daily_stats(db, user.id, days)
|
|
|
|
return DailyStatsResponse(
|
|
items=[DailyStats(**item) for item in data],
|
|
days=days,
|
|
user_id=user.id,
|
|
)
|
|
|
|
|
|
@router.get("/referrers/{user_id}/stats/top-referrals", response_model=TopReferralsResponse)
|
|
async def get_referrer_top_referrals(
|
|
user_id: int,
|
|
limit: int = Query(10, ge=1, le=100),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> TopReferralsResponse:
|
|
"""Топ рефералов реферера по принесённому доходу."""
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
data = await PartnerStatsService.get_referrer_top_referrals(db, user.id, limit)
|
|
|
|
return TopReferralsResponse(
|
|
items=[TopReferralItem(**item) for item in data],
|
|
user_id=user.id,
|
|
)
|
|
|
|
|
|
@router.get("/referrers/{user_id}/stats/compare", response_model=PeriodComparisonResponse)
|
|
async def get_referrer_period_comparison(
|
|
user_id: int,
|
|
current_days: int = Query(7, ge=1, le=365),
|
|
previous_days: int = Query(7, ge=1, le=365),
|
|
_: Any = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> PeriodComparisonResponse:
|
|
"""Сравнение периодов для реферера."""
|
|
user = await get_user_by_telegram_id(db, user_id)
|
|
if not user:
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
|
|
|
|
data = await PartnerStatsService.get_referrer_period_comparison(
|
|
db, user.id, current_days, previous_days
|
|
)
|
|
|
|
return PeriodComparisonResponse(
|
|
current_period=PeriodData(**data["current_period"]),
|
|
previous_period=PeriodData(**data["previous_period"]),
|
|
change=PeriodChange(
|
|
referrals=ChangeData(**data["change"]["referrals"]),
|
|
earnings=ChangeData(**data["change"]["earnings"]),
|
|
),
|
|
user_id=user.id,
|
|
)
|