feat: purchases added

This commit is contained in:
Artem Tsyrulnikov
2025-11-10 16:43:20 +03:00
parent f8edd81395
commit 2549242b06
20 changed files with 1429 additions and 8 deletions

View File

@@ -4,8 +4,11 @@ __all__ = (
'TargetChannel',
'ExternalChannel',
'Creative',
'Purchase',
'TargetChannelStatus',
'CreativeStatus',
'PurchaseStatus',
'InviteLinkType',
'LoginToken',
'UserNotFound',
'LoginTokenNotFound',
@@ -18,6 +21,7 @@ __all__ = (
'ExternalChannelAlreadyExists',
'CreativeNotFound',
'CreativeInUse',
'PurchaseNotFound',
)
from .base import Base
@@ -32,10 +36,12 @@ from .error import (
LoginTokenAlreadyUsed,
LoginTokenExpired,
LoginTokenNotFound,
PurchaseNotFound,
TargetChannelNotFound,
UserNotFound,
)
from .external_channel import ExternalChannel
from .login_token import LoginToken
from .purchase import InviteLinkType, Purchase, PurchaseStatus
from .target_channel import TargetChannel, TargetChannelStatus
from .user import User

View File

@@ -58,3 +58,9 @@ def CreativeInUse(creative_id: uuid.UUID) -> HTTPException:
status.HTTP_400_BAD_REQUEST,
f'Creative {creative_id} is used in active purchases and cannot be deleted',
)
def PurchaseNotFound(purchase_id: uuid.UUID | None = None) -> HTTPException:
if purchase_id is None:
return HTTPException(status.HTTP_404_NOT_FOUND, 'Purchase not found')
return HTTPException(status.HTTP_404_NOT_FOUND, f'Purchase {purchase_id} not found')

52
src/domain/purchase.py Normal file
View File

@@ -0,0 +1,52 @@
import datetime
import uuid
from enum import StrEnum
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src import domain
if TYPE_CHECKING:
from .creative import Creative
from .external_channel import ExternalChannel
from .target_channel import TargetChannel
class PurchaseStatus(StrEnum):
ACTIVE = 'active'
ARCHIVED = 'archived'
class InviteLinkType(StrEnum):
PUBLIC = 'public' # открытая ссылка
APPROVAL = 'approval' # с одобрением ботом
class Purchase(domain.Base):
# Основные поля
target_channel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('targetchannels.id'), index=True)
external_channel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('externalchannels.id'), index=True)
creative_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('creatives.id'), index=True)
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('users.id'), index=True)
# Данные закупа
placement_date: Mapped[datetime.datetime] # дата размещения (планируемая или фактическая)
cost: Mapped[float | None] # стоимость закупа
comment: Mapped[str | None] # комментарий
ad_post_url: Mapped[str | None] # ссылка на рекламное сообщение во внешнем канале
invite_link_type: Mapped[InviteLinkType] # тип пригласительной ссылки
invite_link: Mapped[str] # уникальная пригласительная ссылка
# Статус
status: Mapped[PurchaseStatus] = mapped_column(default=PurchaseStatus.ACTIVE)
# Метрики (будут обновляться из Telegram API)
subscriptions_count: Mapped[int] = mapped_column(default=0, server_default='0')
views_count: Mapped[int | None] # просмотры рекламного поста (если доступны)
# Relationships
target_channel: Mapped['TargetChannel'] = relationship()
external_channel: Mapped['ExternalChannel'] = relationship()
creative: Mapped['Creative'] = relationship()