Files
remnawave-bedolaga-telegram…/app/webapi/routes/subscription_events.py

140 lines
4.3 KiB
Python

from __future__ import annotations
from typing import Any, Iterable, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import Subscription, SubscriptionEvent, Transaction, User
from app.database.crud.subscription_event import (
create_subscription_event,
list_subscription_events,
)
from ..dependencies import get_db_session, require_api_token
from ..schemas.subscription_events import (
SubscriptionEventCreate,
SubscriptionEventListResponse,
SubscriptionEventResponse,
)
router = APIRouter()
async def _get_user_or_error(db: AsyncSession, user_id: int) -> User:
user = await db.get(User, user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
return user
async def _ensure_subscription_exists(
db: AsyncSession, subscription_id: Optional[int]
) -> None:
if not subscription_id:
return
subscription = await db.get(Subscription, subscription_id)
if not subscription:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Subscription not found",
)
async def _ensure_transaction_exists(db: AsyncSession, transaction_id: Optional[int]) -> None:
if not transaction_id:
return
transaction_exists = await db.scalar(
select(Transaction.id).where(Transaction.id == transaction_id)
)
if not transaction_exists:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Transaction not found",
)
def _serialize_event(event: SubscriptionEvent) -> SubscriptionEventResponse:
user = event.user
extra = event.extra or {}
if event.event_type == "promocode_activation":
extra = {**extra}
extra.setdefault("balance_before_kopeks", None)
extra.setdefault("balance_after_kopeks", None)
return SubscriptionEventResponse(
id=event.id,
event_type=event.event_type,
user_id=event.user_id,
user_full_name=user.full_name if user else "",
user_username=user.username if user else None,
user_telegram_id=user.telegram_id if user else 0,
subscription_id=event.subscription_id,
transaction_id=event.transaction_id,
amount_kopeks=event.amount_kopeks,
currency=event.currency,
message=event.message,
occurred_at=event.occurred_at,
created_at=event.created_at,
extra=extra,
)
@router.post("", response_model=SubscriptionEventResponse, status_code=status.HTTP_201_CREATED)
async def receive_subscription_event(
payload: SubscriptionEventCreate,
_: Any = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> SubscriptionEventResponse:
user = await _get_user_or_error(db, payload.user_id)
await _ensure_subscription_exists(db, payload.subscription_id)
await _ensure_transaction_exists(db, payload.transaction_id)
event = await create_subscription_event(
db,
user_id=payload.user_id,
event_type=payload.event_type,
subscription_id=payload.subscription_id,
transaction_id=payload.transaction_id,
amount_kopeks=payload.amount_kopeks,
currency=payload.currency,
message=payload.message,
occurred_at=payload.occurred_at,
extra=payload.extra or None,
)
await db.refresh(event, attribute_names=["user"])
event.user = user
return _serialize_event(event)
@router.get("", response_model=SubscriptionEventListResponse)
async def list_subscription_event_logs(
_: 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),
event_types: Optional[Iterable[str]] = Query(default=None, alias="event_type"),
user_id: Optional[int] = Query(default=None),
) -> SubscriptionEventListResponse:
events, total = await list_subscription_events(
db,
limit=limit,
offset=offset,
event_types=event_types,
user_id=user_id,
)
return SubscriptionEventListResponse(
items=[_serialize_event(event) for event in events],
total=total,
limit=limit,
offset=offset,
)