Files
remnawave-bedolaga-telegram…/app/webapi/routes/subscription_events.py
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
2026-01-24 17:45:27 +03:00

139 lines
4.3 KiB
Python

from __future__ import annotations
from collections.abc import Iterable
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.crud.subscription_event import (
create_subscription_event,
list_subscription_events,
)
from app.database.models import Subscription, SubscriptionEvent, Transaction, User
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: int | None) -> 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: int | None) -> 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: Iterable[str] | None = Query(default=None, alias='event_type'),
user_id: int | None = 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,
)