109 lines
4.5 KiB
Python
109 lines
4.5 KiB
Python
import logging
|
|
import uuid
|
|
from typing import TYPE_CHECKING
|
|
|
|
from tortoise import timezone
|
|
|
|
from src import domain, dto
|
|
|
|
from .create_placements import _build_placement_output
|
|
from .get_placement import _build_placement_post_output
|
|
|
|
if TYPE_CHECKING:
|
|
from .. import Usecase
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.GetPlacementsOutput:
|
|
"""Get all placements for a project (formerly get_purchases)"""
|
|
context = await self.ensure_workspace_permission(
|
|
input.workspace_id, input.user_id, domain.PermissionKey.PLACEMENTS_READ
|
|
)
|
|
|
|
project = await self.database.get_project(input.workspace_id, project_id=input.project_id)
|
|
if not project:
|
|
raise domain.ProjectNotFound(input.project_id)
|
|
|
|
context.ensure_project_permission(domain.PermissionKey.PLACEMENTS_READ, project.id)
|
|
|
|
# Prefetch project channel for output building
|
|
if project.channel is None:
|
|
project_channel = await self.database.get_channel(channel_id=project.channel_id)
|
|
project.channel = project_channel
|
|
|
|
placements = await self.database.get_project_placements(input.workspace_id, project.id)
|
|
log.debug('Fetched %s placements for project %s', len(placements), project.id)
|
|
|
|
placement_ids = [placement.id for placement in placements]
|
|
placement_posts = await self.database.get_placement_posts_by_placement_ids(
|
|
input.workspace_id,
|
|
placement_ids,
|
|
include_archived=True,
|
|
)
|
|
# Подсчёт подписок по placement_id (один Placement = одна ссылка = один счётчик подписок)
|
|
subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids)
|
|
post_ids = [placement_post.post.id for placement_post in placement_posts if placement_post.post]
|
|
views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {}
|
|
|
|
# Collect (channel_id, message_id) pairs for batch next post lookup
|
|
channel_message_pairs = [
|
|
(placement_post.post.channel_id, placement_post.post.message_id)
|
|
for placement_post in placement_posts
|
|
if placement_post.post
|
|
]
|
|
next_posts_map = await self.database.get_next_posts_after_batch(channel_message_pairs)
|
|
|
|
# Calculate time_on_top for each placement_post
|
|
time_on_top_map: dict[uuid.UUID, int] = {}
|
|
now = timezone.now()
|
|
for placement_post in placement_posts:
|
|
post = placement_post.post
|
|
if not post or not post.published_at:
|
|
continue
|
|
published_at = post.published_at
|
|
key = (post.channel_id, post.message_id)
|
|
next_post = next_posts_map.get(key)
|
|
if next_post and next_post.published_at:
|
|
time_on_top_map[placement_post.id] = int((next_post.published_at - published_at).total_seconds())
|
|
else:
|
|
time_on_top_map[placement_post.id] = int((now - published_at).total_seconds())
|
|
|
|
placement_posts_by_placement_id: dict[uuid.UUID, list[dto.PlacementPostOutput]] = {}
|
|
for placement_post in placement_posts:
|
|
views_count = None
|
|
time_on_top = time_on_top_map.get(placement_post.id)
|
|
if placement_post.post:
|
|
views_count = views_map.get(placement_post.post.id, (None,))[0]
|
|
placement_post_output = _build_placement_post_output(
|
|
placement_post,
|
|
subscriptions_counts.get(placement_post.placement_id, 0),
|
|
views_count,
|
|
time_on_top,
|
|
)
|
|
if placement_post_output is None:
|
|
continue
|
|
placement_posts_by_placement_id.setdefault(placement_post.placement_id, []).append(placement_post_output)
|
|
|
|
placement_outputs = []
|
|
for placement in placements:
|
|
placement_output = _build_placement_output(placement, project=project)
|
|
placement_post_output = None
|
|
placement_posts_for_placement = placement_posts_by_placement_id.get(placement.id, [])
|
|
if placement_posts_for_placement:
|
|
if len(placement_posts_for_placement) > 1:
|
|
log.warning(
|
|
'Placement %s has %s placement_posts, returning latest',
|
|
placement.id,
|
|
len(placement_posts_for_placement),
|
|
)
|
|
placement_post_output = placement_posts_for_placement[0]
|
|
placement_outputs.append(
|
|
dto.PlacementWithPostsOutput(
|
|
**placement_output.model_dump(),
|
|
placement_post=placement_post_output,
|
|
)
|
|
)
|
|
|
|
return dto.GetPlacementsOutput(placements=placement_outputs)
|