from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, HTTPException, Query, Security, status from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from app.database.crud.campaign import ( create_campaign, delete_campaign, get_campaign_by_id, get_campaigns_count, get_campaigns_list, update_campaign, ) from ..dependencies import get_db_session, require_api_token from ..schemas.campaigns import ( CampaignCreateRequest, CampaignListResponse, CampaignResponse, CampaignUpdateRequest, ) router = APIRouter() def _serialize_campaign(campaign) -> CampaignResponse: registrations_attr = None if isinstance(getattr(campaign, "__dict__", None), dict): registrations_attr = campaign.__dict__.get("registrations") registrations = registrations_attr or [] squads = list(campaign.subscription_squads or []) # Получаем название тарифа если есть tariff_name = None if campaign.tariff_id and hasattr(campaign, 'tariff') and campaign.tariff: tariff_name = campaign.tariff.name return CampaignResponse( id=campaign.id, name=campaign.name, start_parameter=campaign.start_parameter, bonus_type=campaign.bonus_type, balance_bonus_kopeks=campaign.balance_bonus_kopeks or 0, balance_bonus_rubles=round((campaign.balance_bonus_kopeks or 0) / 100, 2), subscription_duration_days=campaign.subscription_duration_days, subscription_traffic_gb=campaign.subscription_traffic_gb, subscription_device_limit=campaign.subscription_device_limit, subscription_squads=squads, tariff_id=campaign.tariff_id, tariff_duration_days=campaign.tariff_duration_days, tariff_name=tariff_name, is_active=campaign.is_active, created_by=campaign.created_by, created_at=campaign.created_at, updated_at=campaign.updated_at, registrations_count=len(registrations), ) @router.post( "", response_model=CampaignResponse, status_code=status.HTTP_201_CREATED, summary="Создать рекламную кампанию", ) async def create_campaign_endpoint( payload: CampaignCreateRequest, token: Any = Security(require_api_token), db: AsyncSession = Depends(get_db_session), ) -> CampaignResponse: created_by = getattr(token, "id", None) try: campaign = await create_campaign( db, name=payload.name, start_parameter=payload.start_parameter, bonus_type=payload.bonus_type, created_by=created_by, balance_bonus_kopeks=payload.balance_bonus_kopeks, subscription_duration_days=payload.subscription_duration_days, subscription_traffic_gb=payload.subscription_traffic_gb, subscription_device_limit=payload.subscription_device_limit, subscription_squads=payload.subscription_squads, tariff_id=payload.tariff_id, tariff_duration_days=payload.tariff_duration_days, is_active=payload.is_active, ) except IntegrityError as exc: # duplicate start_parameter await db.rollback() raise HTTPException( status.HTTP_400_BAD_REQUEST, "Campaign with this start_parameter already exists", ) from exc return _serialize_campaign(campaign) @router.get( "", response_model=CampaignListResponse, summary="Список рекламных кампаний", ) async def list_campaigns( _: 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), include_inactive: bool = Query(True, description="Включать неактивные кампании"), ) -> CampaignListResponse: total = await get_campaigns_count(db, is_active=None if include_inactive else True) campaigns = await get_campaigns_list( db, offset=offset, limit=limit, include_inactive=include_inactive, ) return CampaignListResponse( items=[_serialize_campaign(campaign) for campaign in campaigns], total=total, limit=limit, offset=offset, ) @router.delete( "/{campaign_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Удалить рекламную кампанию", ) async def delete_campaign_endpoint( campaign_id: int, _: Any = Security(require_api_token), db: AsyncSession = Depends(get_db_session), ): campaign = await get_campaign_by_id(db, campaign_id) if not campaign: raise HTTPException(status.HTTP_404_NOT_FOUND, "Campaign not found") await delete_campaign(db, campaign) return None @router.patch( "/{campaign_id}", response_model=CampaignResponse, summary="Обновить рекламную кампанию", ) async def update_campaign_endpoint( campaign_id: int, payload: CampaignUpdateRequest, _: Any = Security(require_api_token), db: AsyncSession = Depends(get_db_session), ) -> CampaignResponse: campaign = await get_campaign_by_id(db, campaign_id) if not campaign: raise HTTPException(status.HTTP_404_NOT_FOUND, "Campaign not found") update_fields = payload.dict(exclude_unset=True) if not update_fields: return _serialize_campaign(campaign) try: campaign = await update_campaign(db, campaign, **update_fields) except IntegrityError as exc: await db.rollback() if "start_parameter" in str(exc.orig): raise HTTPException( status.HTTP_400_BAD_REQUEST, "Campaign with this start_parameter already exists", ) from exc raise return _serialize_campaign(campaign)