креативы
This commit is contained in:
@@ -29,8 +29,18 @@ class ValidateTokenRequest(BaseModel):
|
||||
|
||||
|
||||
@internal_router.get('/auth/jwt')
|
||||
async def get_jwt_by_telegram_id(telegram_id: int) -> dto.ValidateLoginTokenOutput:
|
||||
return await deps.get_usecase().get_jwt_by_telegram_id(telegram_id)
|
||||
async def get_jwt_by_telegram_id(
|
||||
telegram_id: int,
|
||||
username: str | None = None,
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
) -> dto.ValidateLoginTokenOutput:
|
||||
return await deps.get_usecase().get_jwt_by_telegram_id(
|
||||
telegram_id=telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
)
|
||||
|
||||
|
||||
@internal_router.get('/projects/{workspace_id}/{project_id}')
|
||||
|
||||
@@ -21,6 +21,8 @@ __all__ = (
|
||||
'PurchaseChannelStatus',
|
||||
'Creative',
|
||||
'replace_invite_link_with_tag',
|
||||
'validate_media_size',
|
||||
'MAX_CREATIVE_MEDIA_BYTES',
|
||||
'Placement',
|
||||
'Post',
|
||||
'Subscription',
|
||||
@@ -53,18 +55,20 @@ __all__ = (
|
||||
'CreativeInUse',
|
||||
'CreativeInviteLinkNotFound',
|
||||
'CreativeMultipleInviteLinks',
|
||||
'CreativeMediaTooLarge',
|
||||
'PlacementNotFound',
|
||||
'UserByUsernameNotFound',
|
||||
)
|
||||
|
||||
from .channel import Channel
|
||||
from .creative import Creative, CreativeStatus, replace_invite_link_with_tag
|
||||
from .creative import Creative, CreativeStatus, MAX_CREATIVE_MEDIA_BYTES, replace_invite_link_with_tag, validate_media_size
|
||||
from .error import (
|
||||
ChannelAlreadyExists,
|
||||
ChannelNoAdminRights,
|
||||
ChannelNotFound,
|
||||
CreativeInUse,
|
||||
CreativeInviteLinkNotFound,
|
||||
CreativeMediaTooLarge,
|
||||
CreativeMultipleInviteLinks,
|
||||
CreativeNotFound,
|
||||
LoginTokenAlreadyUsed,
|
||||
|
||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
||||
from tortoise import fields
|
||||
|
||||
from .base import TimestampedModel
|
||||
from .error import CreativeInviteLinkNotFound, CreativeMultipleInviteLinks
|
||||
from .error import CreativeInviteLinkNotFound, CreativeMediaTooLarge, CreativeMultipleInviteLinks
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .project import Project
|
||||
@@ -21,6 +21,10 @@ class CreativeStatus(str, enum.Enum):
|
||||
class Creative(TimestampedModel):
|
||||
name = fields.CharField(max_length=255)
|
||||
text = fields.TextField()
|
||||
media_type = fields.CharField(max_length=32, null=True)
|
||||
media_file_id = fields.CharField(max_length=512, null=True)
|
||||
media_data = fields.BinaryField(null=True)
|
||||
buttons = fields.JSONField(default=list)
|
||||
status = fields.CharEnumField(CreativeStatus, default=CreativeStatus.ACTIVE)
|
||||
|
||||
workspace: fields.ForeignKeyRelation['Workspace'] = fields.ForeignKeyField(
|
||||
@@ -38,6 +42,9 @@ class Creative(TimestampedModel):
|
||||
table = 'creative'
|
||||
|
||||
|
||||
MAX_CREATIVE_MEDIA_BYTES = 20 * 1024 * 1024
|
||||
|
||||
|
||||
_INVITE_LINK_HTML = re.compile(r'<a\s+href=["\']https?://t\.me/\+[a-zA-Z0-9_-]+["\']>([^<]*)</a>', re.IGNORECASE)
|
||||
_INVITE_LINK_PLAIN = re.compile(r'https?://t\.me/\+[a-zA-Z0-9_-]+', re.IGNORECASE)
|
||||
|
||||
@@ -57,3 +64,10 @@ def replace_invite_link_with_tag(text: str) -> str:
|
||||
if html_count == 1:
|
||||
return _INVITE_LINK_HTML.sub(r'<a class="tg-link">\1</a>', text)
|
||||
return _INVITE_LINK_PLAIN.sub('<a class="tg-link"></a>', text)
|
||||
|
||||
|
||||
def validate_media_size(media_data: bytes | None) -> None:
|
||||
if media_data is None:
|
||||
return
|
||||
if len(media_data) > MAX_CREATIVE_MEDIA_BYTES:
|
||||
raise CreativeMediaTooLarge(MAX_CREATIVE_MEDIA_BYTES)
|
||||
|
||||
@@ -79,6 +79,10 @@ def CreativeMultipleInviteLinks() -> HTTPException:
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST, 'Creative text must contain only one invite link')
|
||||
|
||||
|
||||
def CreativeMediaTooLarge(max_bytes: int) -> HTTPException:
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST, f'Creative media is too large (max {max_bytes} bytes)')
|
||||
|
||||
|
||||
def CreativeInUse(creative_id: uuid.UUID) -> HTTPException:
|
||||
return HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
|
||||
@@ -22,6 +22,7 @@ __all__ = (
|
||||
'GetPurchasesInput',
|
||||
'GetPurchasesOutput',
|
||||
'GetPurchaseInput',
|
||||
'CreativeButton',
|
||||
'CreativeOutput',
|
||||
'GetCreativesInput',
|
||||
'GetCreativesOutput',
|
||||
@@ -95,6 +96,7 @@ from .channel import (
|
||||
)
|
||||
from .creative import (
|
||||
CreateCreativeInput,
|
||||
CreativeButton,
|
||||
CreativeOutput,
|
||||
DeleteCreativeInput,
|
||||
GetCreativeInput,
|
||||
|
||||
@@ -6,10 +6,18 @@ import pydantic
|
||||
from src import domain
|
||||
|
||||
|
||||
class CreativeButton(pydantic.BaseModel):
|
||||
text: str
|
||||
url: str
|
||||
|
||||
|
||||
class CreativeOutput(pydantic.BaseModel):
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
text: str
|
||||
media_type: str | None
|
||||
media_file_id: str | None
|
||||
buttons: list[CreativeButton]
|
||||
project_id: uuid.UUID
|
||||
project_channel_title: str
|
||||
created_at: datetime.datetime
|
||||
@@ -37,11 +45,19 @@ class GetCreativeInput(pydantic.BaseModel):
|
||||
class CreateCreativeInput(pydantic.BaseModel):
|
||||
name: str
|
||||
text: str
|
||||
media_type: str | None = None
|
||||
media_file_id: str | None = None
|
||||
media_data: bytes | None = None
|
||||
buttons: list[CreativeButton] = pydantic.Field(default_factory=list)
|
||||
|
||||
|
||||
class UpdateCreativeInput(pydantic.BaseModel):
|
||||
name: str | None = None
|
||||
text: str | None = None
|
||||
media_type: str | None = None
|
||||
media_file_id: str | None = None
|
||||
media_data: bytes | None = None
|
||||
buttons: list[CreativeButton] | None = None
|
||||
status: domain.CreativeStatus | None = None
|
||||
|
||||
|
||||
|
||||
@@ -6,11 +6,35 @@ if typing.TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
|
||||
async def get_jwt_by_telegram_id(self: 'Usecase', telegram_id: int) -> dto.ValidateLoginTokenOutput:
|
||||
async def get_jwt_by_telegram_id(
|
||||
self: 'Usecase',
|
||||
telegram_id: int,
|
||||
username: str | None = None,
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
) -> dto.ValidateLoginTokenOutput:
|
||||
user = await self.database.get_user(telegram_id=telegram_id)
|
||||
|
||||
if not user:
|
||||
raise domain.UserNotFound()
|
||||
user = domain.User(
|
||||
telegram_id=telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
)
|
||||
await self.database.create_user(user)
|
||||
else:
|
||||
updated = False
|
||||
if username is not None and user.username != username:
|
||||
user.username = username
|
||||
updated = True
|
||||
if first_name is not None and user.first_name != first_name:
|
||||
user.first_name = first_name
|
||||
updated = True
|
||||
if last_name is not None and user.last_name != last_name:
|
||||
user.last_name = last_name
|
||||
updated = True
|
||||
if updated:
|
||||
await self.database.update_user(user)
|
||||
|
||||
access_token = self.jwt_encoder.encode_access_token(
|
||||
user_id=user.id,
|
||||
|
||||
@@ -22,11 +22,17 @@ async def create_creative(
|
||||
log.warning('User %s attempted to create creative for unavailable project %s', user_id, project_id)
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
domain.validate_media_size(input.media_data)
|
||||
|
||||
creative_text = domain.replace_invite_link_with_tag(input.text)
|
||||
|
||||
creative = domain.Creative(
|
||||
name=input.name,
|
||||
text=creative_text,
|
||||
media_type=input.media_type,
|
||||
media_file_id=input.media_file_id,
|
||||
media_data=input.media_data,
|
||||
buttons=[button.model_dump() for button in input.buttons],
|
||||
status=domain.CreativeStatus.ACTIVE,
|
||||
project_id=project.id,
|
||||
workspace_id=workspace_id,
|
||||
@@ -37,6 +43,9 @@ async def create_creative(
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
media_type=creative.media_type,
|
||||
media_file_id=creative.media_file_id,
|
||||
buttons=creative.buttons,
|
||||
project_id=project.id,
|
||||
project_channel_title=project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
|
||||
@@ -26,6 +26,9 @@ async def get_creative(self: 'Usecase', input: dto.GetCreativeInput) -> dto.Crea
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
media_type=creative.media_type,
|
||||
media_file_id=creative.media_file_id,
|
||||
buttons=creative.buttons,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
|
||||
@@ -32,6 +32,9 @@ async def get_creatives(self: 'Usecase', input: dto.GetCreativesInput) -> list[d
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
media_type=creative.media_type,
|
||||
media_file_id=creative.media_file_id,
|
||||
buttons=creative.buttons,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
|
||||
@@ -30,6 +30,15 @@ async def update_creative(
|
||||
creative.name = input.name
|
||||
if input.text:
|
||||
creative.text = domain.replace_invite_link_with_tag(input.text)
|
||||
if input.media_data is not None:
|
||||
domain.validate_media_size(input.media_data)
|
||||
creative.media_data = input.media_data
|
||||
if input.media_type is not None:
|
||||
creative.media_type = input.media_type
|
||||
if input.media_file_id is not None:
|
||||
creative.media_file_id = input.media_file_id
|
||||
if input.buttons is not None:
|
||||
creative.buttons = [button.model_dump() for button in input.buttons]
|
||||
if input.status:
|
||||
creative.status = input.status
|
||||
|
||||
@@ -41,6 +50,9 @@ async def update_creative(
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
media_type=creative.media_type,
|
||||
media_file_id=creative.media_file_id,
|
||||
buttons=creative.buttons,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
|
||||
Reference in New Issue
Block a user