diff --git a/app/webapi/app.py b/app/webapi/app.py index 6e87aa69..b881bf03 100644 --- a/app/webapi/app.py +++ b/app/webapi/app.py @@ -15,7 +15,6 @@ from .routes import ( main_menu_buttons, media, miniapp, - partners, polls, promocodes, promo_groups, @@ -107,10 +106,6 @@ OPENAPI_TAGS = [ "name": "miniapp", "description": "Endpoint для Telegram Mini App с информацией о подписке пользователя.", }, - { - "name": "partners", - "description": "Просмотр участников реферальной программы, их доходов и рефералов.", - }, { "name": "polls", "description": "Создание опросов, удаление, статистика и ответы пользователей.", @@ -178,7 +173,6 @@ def create_web_api_app() -> FastAPI: app.include_router(remnawave.router, prefix="/remnawave", tags=["remnawave"]) app.include_router(media.router, tags=["media"]) app.include_router(miniapp.router, prefix="/miniapp", tags=["miniapp"]) - app.include_router(partners.router, prefix="/partners", tags=["partners"]) app.include_router(polls.router, prefix="/polls", tags=["polls"]) app.include_router(logs.router, prefix="/logs", tags=["logs"]) app.include_router( diff --git a/app/webapi/routes/__init__.py b/app/webapi/routes/__init__.py index 4484462a..b11bf586 100644 --- a/app/webapi/routes/__init__.py +++ b/app/webapi/routes/__init__.py @@ -4,7 +4,6 @@ from . import ( main_menu_buttons, media, miniapp, - partners, polls, promo_offers, pages, @@ -27,7 +26,6 @@ __all__ = [ "main_menu_buttons", "media", "miniapp", - "partners", "polls", "promo_offers", "pages", diff --git a/app/webapi/routes/partners.py b/app/webapi/routes/partners.py deleted file mode 100644 index 2e047919..00000000 --- a/app/webapi/routes/partners.py +++ /dev/null @@ -1,189 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -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 selectinload - -from app.database.crud.referral import get_detailed_referral_list, get_user_referral_stats -from app.database.crud.user import get_user_by_id, get_user_by_telegram_id -from app.database.models import User -from app.utils.user_utils import get_effective_referral_commission_percent - -from ..dependencies import get_db_session, require_api_token -from ..schemas.partners import ( - PartnerReferralItem, - PartnerReferralList, - PartnerReferrerCommissionUpdateRequest, - PartnerReferrerDetail, - PartnerReferrerItem, - PartnerReferrerListResponse, -) - -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: - base_query = select(User).options(selectinload(User.referrer)).where( - or_(User.referral_code.isnot(None), User.referrals.any()) - ) - - 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.put( - "/referrers/{user_id}/commission", - response_model=PartnerReferrerItem, -) -async def update_referrer_commission( - user_id: int, - payload: PartnerReferrerCommissionUpdateRequest, - _: 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") - - user.referral_commission_percent = payload.percent - user.updated_at = datetime.utcnow() - - await db.commit() - await db.refresh(user) - - stats = await get_user_referral_stats(db, user.id) - return _serialize_referrer(user, stats) diff --git a/app/webapi/schemas/partners.py b/app/webapi/schemas/partners.py deleted file mode 100644 index d16c9fab..00000000 --- a/app/webapi/schemas/partners.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class PartnerReferrerItem(BaseModel): - id: int - telegram_id: int - username: Optional[str] = None - first_name: Optional[str] = None - last_name: Optional[str] = None - referral_code: Optional[str] = None - referral_commission_percent: Optional[int] = None - effective_referral_commission_percent: int - invited_count: int - active_referrals: int - total_earned_kopeks: int - total_earned_rubles: float - month_earned_kopeks: int - month_earned_rubles: float - created_at: datetime - last_activity: Optional[datetime] = None - - -class PartnerReferrerListResponse(BaseModel): - items: List[PartnerReferrerItem] = Field(default_factory=list) - total: int - limit: int - offset: int - - -class PartnerReferralItem(BaseModel): - id: int - telegram_id: int - full_name: str - username: Optional[str] = None - created_at: datetime - last_activity: Optional[datetime] = None - has_made_first_topup: bool - balance_kopeks: int - balance_rubles: float - total_earned_kopeks: int - total_earned_rubles: float - topups_count: int - days_since_registration: int - days_since_activity: Optional[int] = None - status: str - - -class PartnerReferralList(BaseModel): - items: List[PartnerReferralItem] = Field(default_factory=list) - total: int - limit: int - offset: int - has_next: bool - has_prev: bool - current_page: int - total_pages: int - - -class PartnerReferrerDetail(BaseModel): - referrer: PartnerReferrerItem - referrals: PartnerReferralList - - -class PartnerReferrerCommissionUpdateRequest(BaseModel): - percent: Optional[int] = Field( - default=None, - ge=0, - le=100, - description=( - "Индивидуальный процент комиссии реферала. Значение None сбрасывает на " - "стандартный процент из настроек." - ), - )