refactor: project architecture refactor, container splitting

This commit is contained in:
3252a8
2026-05-17 00:01:28 +03:00
parent e0b5218037
commit 30fb774d93
367 changed files with 2609 additions and 864 deletions
+199
View File
@@ -0,0 +1,199 @@
from typing import List, Optional
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql import func
from db.models import UserBilling, UserPaymentMethod
async def get_user_billing(session: AsyncSession, user_id: int) -> Optional[UserBilling]:
stmt = select(UserBilling).where(UserBilling.user_id == user_id)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def upsert_yk_payment_method(
session: AsyncSession,
*,
user_id: int,
payment_method_id: str,
card_last4: Optional[str] = None,
card_network: Optional[str] = None,
) -> UserBilling:
existing = await get_user_billing(session, user_id)
if existing:
existing.yookassa_payment_method_id = payment_method_id
existing.card_last4 = card_last4
existing.card_network = card_network
existing.updated_at = func.now()
await session.flush()
await session.refresh(existing)
return existing
record = UserBilling(
user_id=user_id,
yookassa_payment_method_id=payment_method_id,
card_last4=card_last4,
card_network=card_network,
)
session.add(record)
await session.flush()
await session.refresh(record)
return record
async def delete_yk_payment_method(session: AsyncSession, user_id: int) -> bool:
existing = await get_user_billing(session, user_id)
if not existing:
return False
existing.yookassa_payment_method_id = None
existing.card_last4 = None
existing.card_network = None
existing.updated_at = func.now()
await session.flush()
await session.refresh(existing)
return True
# Multi-card support API
async def upsert_user_payment_method(
session: AsyncSession,
*,
user_id: int,
provider_payment_method_id: str,
provider: str = "yookassa",
card_last4: Optional[str] = None,
card_network: Optional[str] = None,
set_default: bool = False,
) -> UserPaymentMethod:
existing_stmt = select(UserPaymentMethod).where(
UserPaymentMethod.provider_payment_method_id == provider_payment_method_id
)
result = await session.execute(existing_stmt)
existing: Optional[UserPaymentMethod] = result.scalar_one_or_none()
if existing:
existing.card_last4 = card_last4
existing.card_network = card_network
if set_default:
# unset previous defaults
await session.execute(
update(UserPaymentMethod)
.where(UserPaymentMethod.user_id == user_id)
.values(is_default=False)
)
existing.is_default = True
existing.updated_at = func.now()
await session.flush()
await session.refresh(existing)
return existing
if set_default:
await session.execute(
update(UserPaymentMethod)
.where(UserPaymentMethod.user_id == user_id)
.values(is_default=False)
)
record = UserPaymentMethod(
user_id=user_id,
provider=provider,
provider_payment_method_id=provider_payment_method_id,
card_last4=card_last4,
card_network=card_network,
is_default=set_default,
)
session.add(record)
await session.flush()
await session.refresh(record)
return record
async def list_user_payment_methods(
session: AsyncSession, user_id: int, provider: Optional[str] = None
) -> List[UserPaymentMethod]:
stmt = select(UserPaymentMethod).where(UserPaymentMethod.user_id == user_id)
if provider:
stmt = stmt.where(UserPaymentMethod.provider == provider)
stmt = stmt.order_by(UserPaymentMethod.is_default.desc(), UserPaymentMethod.created_at.desc())
result = await session.execute(stmt)
return result.scalars().all()
async def get_user_default_payment_method(
session: AsyncSession, user_id: int, provider: str = "yookassa"
) -> Optional[UserPaymentMethod]:
stmt = select(UserPaymentMethod).where(
UserPaymentMethod.user_id == user_id,
UserPaymentMethod.provider == provider,
UserPaymentMethod.is_default == True,
)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def set_user_default_payment_method(
session: AsyncSession, user_id: int, method_id: int
) -> bool:
methods = await list_user_payment_methods(session, user_id)
if not any(m.method_id == method_id for m in methods):
return False
await session.execute(
update(UserPaymentMethod)
.where(UserPaymentMethod.user_id == user_id)
.values(is_default=False)
)
await session.execute(
update(UserPaymentMethod)
.where(UserPaymentMethod.method_id == method_id)
.values(is_default=True)
)
return True
async def delete_user_payment_method(session: AsyncSession, user_id: int, method_id: int) -> bool:
stmt = select(UserPaymentMethod).where(
UserPaymentMethod.method_id == method_id, UserPaymentMethod.user_id == user_id
)
result = await session.execute(stmt)
method = result.scalar_one_or_none()
if not method:
return False
await session.delete(method)
await session.flush()
return True
async def delete_user_payment_method_by_provider_id(
session: AsyncSession,
user_id: int,
provider_payment_method_id: str,
) -> bool:
"""Delete a saved payment method by its provider payment_method.id for a specific user.
Useful when callbacks pass the provider id (e.g., YooKassa pm_...) instead of our internal method_id.
""" # noqa: E501
stmt = select(UserPaymentMethod).where(
UserPaymentMethod.user_id == user_id,
UserPaymentMethod.provider_payment_method_id == provider_payment_method_id,
)
result = await session.execute(stmt)
method: Optional[UserPaymentMethod] = result.scalar_one_or_none()
if not method:
return False
await session.delete(method)
await session.flush()
return True
async def user_has_saved_payment_method(
session: AsyncSession,
user_id: int,
provider: str = "yookassa",
) -> bool:
"""Return True if the user has at least one saved payment method."""
try:
methods = await list_user_payment_methods(session, user_id, provider)
if methods:
return True
billing = await get_user_billing(session, user_id)
return bool(billing and billing.yookassa_payment_method_id)
except Exception:
return False