feat: проверка существования канала при привязке
This commit is contained in:
@@ -279,11 +279,22 @@ class JWTEncoder(typing.Protocol):
|
||||
def encode_access_token(self, user_id: UUID, telegram_id: int, username: str | None = None) -> str: ...
|
||||
|
||||
|
||||
class FetchChannelResponse(typing.Protocol):
|
||||
telegram_id: int
|
||||
username: str
|
||||
title: str
|
||||
|
||||
|
||||
class ParserClient(typing.Protocol):
|
||||
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class Usecase:
|
||||
database: Database
|
||||
telegram_bot: TelegramBotWriter
|
||||
jwt_encoder: JWTEncoder
|
||||
parser_client: ParserClient
|
||||
|
||||
async def ensure_workspace_access(self, workspace_id: UUID, user_id: UUID) -> domain.Workspace:
|
||||
workspace = await self.database.get_workspace_for_user(workspace_id, user_id)
|
||||
|
||||
@@ -11,19 +11,19 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def create_creative(
|
||||
self: 'Usecase', input: dto.CreateCreativeInput, user_id: uuid.UUID, workspace_id: uuid.UUID
|
||||
self: 'Usecase', input: dto.CreateCreativeInput, project_id: uuid.UUID, user_id: uuid.UUID, workspace_id: uuid.UUID
|
||||
) -> dto.CreativeOutput:
|
||||
await self.ensure_workspace_permission(
|
||||
workspace_id,
|
||||
user_id,
|
||||
domain.PermissionKey.PROJECTS_WRITE,
|
||||
for_project_id=input.project_id,
|
||||
for_project_id=project_id,
|
||||
)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=input.project_id)
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
log.warning('User %s attempted to create creative for unavailable project %s', user_id, input.project_id)
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
log.warning('User %s attempted to create creative for unavailable project %s', user_id, project_id)
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
creative = domain.Creative(
|
||||
name=input.name,
|
||||
|
||||
@@ -53,7 +53,7 @@ async def create_placement(
|
||||
comment=input.comment,
|
||||
invite_link_type=input.invite_link_type,
|
||||
invite_link=invite_link,
|
||||
status=domain.PlacementStatus.PENDING,
|
||||
status=domain.PlacementStatus.ACTIVE,
|
||||
)
|
||||
|
||||
async with self.database.transaction():
|
||||
@@ -78,8 +78,5 @@ async def create_placement(
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=None,
|
||||
views_availability=domain.PostViewsAvailability.UNKNOWN,
|
||||
last_views_fetch_at=None,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def fetch_placement_post_cycle(self: 'Usecase', interval_seconds: int) -> None:
|
||||
log.info('Starting fetch_placement_post_cycle')
|
||||
log.debug('Starting fetch_placement_post_cycle')
|
||||
|
||||
# Получаем все placement без привязанного поста
|
||||
placements_without_post = (
|
||||
|
||||
@@ -54,8 +54,5 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -28,25 +28,12 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
allowed_project_ids=allowed_project_ids,
|
||||
)
|
||||
|
||||
# Batch fetch views data for all posts
|
||||
post_ids = [p.post.id for p in placements if p.post]
|
||||
views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {}
|
||||
|
||||
# Build output DTOs
|
||||
placement_outputs = []
|
||||
for placement in placements:
|
||||
# Compute post-related fields
|
||||
ad_post_url = None
|
||||
views_count = None
|
||||
views_availability = domain.PostViewsAvailability.UNKNOWN
|
||||
last_views_fetch_at = None
|
||||
|
||||
if placement.post:
|
||||
ad_post_url = generate_post_url(placement.post.channel.username, placement.post.message_id)
|
||||
views_data = views_map.get(placement.post.id)
|
||||
if views_data:
|
||||
views_count, last_views_fetch_at = views_data
|
||||
views_availability = domain.PostViewsAvailability.AVAILABLE
|
||||
|
||||
placement_outputs.append(
|
||||
dto.PlacementOutput(
|
||||
@@ -65,9 +52,6 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -39,21 +39,11 @@ async def update_placement(
|
||||
|
||||
await self.database.update_placement(placement)
|
||||
|
||||
# Compute post-related fields
|
||||
ad_post_url = None
|
||||
views_count = None
|
||||
views_availability = domain.PostViewsAvailability.UNKNOWN
|
||||
last_views_fetch_at = None
|
||||
|
||||
if placement.post:
|
||||
ad_post_url = generate_post_url(placement.post.channel.username, placement.post.message_id)
|
||||
|
||||
# Get latest views data
|
||||
views_data = await self.database.get_latest_views_data(placement.post.id)
|
||||
if views_data:
|
||||
views_count, last_views_fetch_at = views_data
|
||||
views_availability = domain.PostViewsAvailability.AVAILABLE
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
project_id=placement.project_id,
|
||||
@@ -70,8 +60,5 @@ async def update_placement(
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -27,16 +27,20 @@ async def attach_channel_to_purchase_plan(
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
# Поиск канала по username
|
||||
channel = await self.database.get_channel(username=input.username)
|
||||
|
||||
if not channel:
|
||||
# Создать канал с nullable telegram_id/title (заполнятся парсером позже)
|
||||
channel = domain.Channel(username=input.username)
|
||||
parser_response = await self.parser_client.fetch_telegram_channel(input.username)
|
||||
if not parser_response:
|
||||
raise domain.TelegramChannelNotFound(input.username)
|
||||
|
||||
channel = domain.Channel(
|
||||
username=input.username,
|
||||
telegram_id=parser_response.telegram_id,
|
||||
title=parser_response.title,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
log.info('Created channel with username @%s (pending parser update)', input.username)
|
||||
else:
|
||||
log.info('Found existing channel %s for username @%s', channel.id, input.username)
|
||||
log.info('Created channel @%s with telegram_id=%s', input.username, parser_response.telegram_id)
|
||||
|
||||
plan_channel = await self.database.add_channel_to_purchase_plan(
|
||||
plan.id,
|
||||
|
||||
Reference in New Issue
Block a user