90% сеньоров плачат на таких коммитах
This commit is contained in:
@@ -122,14 +122,11 @@ class Database(typing.Protocol):
|
||||
|
||||
async def get_channel(
|
||||
self,
|
||||
workspace_id: UUID,
|
||||
channel_id: UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
username: str | None = None,
|
||||
) -> domain.Channel | None: ...
|
||||
|
||||
async def get_workspace_channels(self, workspace_id: UUID) -> list[domain.Channel]: ...
|
||||
|
||||
async def create_channel(self, channel: domain.Channel) -> None: ...
|
||||
|
||||
async def update_channel(self, channel: domain.Channel) -> None: ...
|
||||
@@ -232,15 +229,27 @@ class Database(typing.Protocol):
|
||||
self, subscriber_id: UUID, project_id: UUID
|
||||
) -> domain.Subscription | None: ...
|
||||
|
||||
async def create_placement_views_history(self, history: domain.PlacementViewsHistory) -> None: ...
|
||||
# Post methods
|
||||
async def create_post(self, post: domain.Post) -> None: ...
|
||||
|
||||
async def get_post(self, post_id: UUID) -> domain.Post | None: ...
|
||||
|
||||
async def get_post_by_channel_and_message(self, channel_id: UUID, message_id: int) -> domain.Post | None: ...
|
||||
|
||||
# Post views history
|
||||
async def create_post_views_history(self, history: domain.PostViewsHistory) -> None: ...
|
||||
|
||||
async def get_views_history(
|
||||
self,
|
||||
placement_id: UUID,
|
||||
post_id: UUID,
|
||||
*,
|
||||
from_date: datetime.datetime | None = None,
|
||||
to_date: datetime.datetime | None = None,
|
||||
) -> list[domain.PlacementViewsHistory]: ...
|
||||
) -> list[domain.PostViewsHistory]: ...
|
||||
|
||||
async def get_latest_views_data(self, post_id: UUID) -> tuple[int, datetime.datetime] | None: ...
|
||||
|
||||
async def get_latest_views_data_batch(self, post_ids: list[UUID]) -> dict[UUID, tuple[int, datetime.datetime]]: ...
|
||||
|
||||
async def get_telegram_state(self, telegram_id: int) -> domain.TelegramState | None: ...
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ async def get_channel_analytics(self: 'Usecase', input: dto.GetChannelAnalyticsI
|
||||
else:
|
||||
allowed_project_filter = allowed_project_ids
|
||||
if allowed_project_ids is None:
|
||||
channels = await self.database.get_workspace_channels(input.workspace_id)
|
||||
# Получить каналы через Projects
|
||||
projects = await self.database.get_workspace_projects(input.workspace_id)
|
||||
channels = [p.channel for p in projects]
|
||||
else:
|
||||
channels = []
|
||||
|
||||
@@ -57,6 +59,10 @@ async def get_channel_analytics(self: 'Usecase', input: dto.GetChannelAnalyticsI
|
||||
|
||||
channel_stats: dict[UUID, ChannelStats] = {ch.id: ChannelStats() for ch in channels}
|
||||
|
||||
# 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 {}
|
||||
|
||||
for p in placements:
|
||||
stats = channel_stats.get(p.placement_channel_id)
|
||||
if stats is None:
|
||||
@@ -64,7 +70,12 @@ async def get_channel_analytics(self: 'Usecase', input: dto.GetChannelAnalyticsI
|
||||
|
||||
stats.total_cost += p.cost if p.cost is not None else 0
|
||||
stats.total_subscriptions += p.subscriptions_count
|
||||
stats.total_views += p.views_count if p.views_count is not None else 0
|
||||
|
||||
# Get views from batch data
|
||||
if p.post and p.post.id in views_map:
|
||||
views_count = views_map[p.post.id][0]
|
||||
stats.total_views += views_count
|
||||
|
||||
stats.placements_count += 1
|
||||
|
||||
result = []
|
||||
|
||||
@@ -49,6 +49,10 @@ async def get_creatives_analytics(
|
||||
|
||||
creative_stats: dict[UUID, CreativeStats] = {cr.id: CreativeStats() for cr in creatives}
|
||||
|
||||
# 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 {}
|
||||
|
||||
for p in placements:
|
||||
stats = creative_stats.get(p.creative_id)
|
||||
if stats is None:
|
||||
@@ -56,7 +60,12 @@ async def get_creatives_analytics(
|
||||
|
||||
stats.total_cost += p.cost if p.cost is not None else 0
|
||||
stats.total_subscriptions += p.subscriptions_count
|
||||
stats.total_views += p.views_count if p.views_count is not None else 0
|
||||
|
||||
# Get views from batch data
|
||||
if p.post and p.post.id in views_map:
|
||||
views_count = views_map[p.post.id][0]
|
||||
stats.total_views += views_count
|
||||
|
||||
stats.placements_count += 1
|
||||
|
||||
result = []
|
||||
|
||||
@@ -44,6 +44,10 @@ async def get_placements_analytics(
|
||||
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 {}
|
||||
|
||||
result = [
|
||||
dto.PlacementAnalyticsOutput(
|
||||
id=p.id,
|
||||
@@ -56,9 +60,9 @@ async def get_placements_analytics(
|
||||
placement_date=p.placement_date,
|
||||
cost=p.cost,
|
||||
subscriptions_count=p.subscriptions_count,
|
||||
views_count=p.views_count,
|
||||
views_count=views_map[p.post.id][0] if p.post and p.post.id in views_map else None,
|
||||
cpf=_calculate_cpf(p.cost, p.subscriptions_count),
|
||||
cpm=_calculate_cpm(p.cost, p.views_count),
|
||||
cpm=_calculate_cpm(p.cost, views_map[p.post.id][0] if p.post and p.post.id in views_map else None),
|
||||
)
|
||||
for p in placements
|
||||
]
|
||||
|
||||
@@ -70,6 +70,10 @@ async def get_spending_analytics(
|
||||
subscriptions: int = 0
|
||||
views: int = 0
|
||||
|
||||
# Batch fetch views data for all posts
|
||||
post_ids = [p.post.id for p in filtered if p.post]
|
||||
views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {}
|
||||
|
||||
# Группировка по периодам
|
||||
period_data: dict[str, PeriodData] = defaultdict(PeriodData)
|
||||
|
||||
@@ -84,9 +88,11 @@ async def get_spending_analytics(
|
||||
total_subs += p.subscriptions_count
|
||||
pd.subscriptions += p.subscriptions_count
|
||||
|
||||
if p.views_count is not None:
|
||||
total_views += p.views_count
|
||||
pd.views += p.views_count
|
||||
# Get views from batch data
|
||||
if p.post and p.post.id in views_map:
|
||||
views_count = views_map[p.post.id][0]
|
||||
total_views += views_count
|
||||
pd.views += views_count
|
||||
|
||||
avg_cpf = total_cost / total_subs if total_subs > 0 and total_cost > 0 else None
|
||||
avg_cpm = (total_cost / total_views * 1000) if total_views > 0 and total_cost > 0 else None
|
||||
|
||||
@@ -21,7 +21,6 @@ async def get_channels(self: 'Usecase', input: dto.GetChannelsInput) -> dto.GetC
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
)
|
||||
for channel in channels
|
||||
]
|
||||
|
||||
@@ -17,9 +17,7 @@ async def update_creative(
|
||||
user_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
) -> dto.CreativeOutput:
|
||||
context = await self.ensure_workspace_permission(
|
||||
workspace_id, user_id, domain.PermissionKey.PROJECTS_WRITE
|
||||
)
|
||||
context = await self.ensure_workspace_permission(workspace_id, user_id, domain.PermissionKey.PROJECTS_WRITE)
|
||||
|
||||
creative = await self.database.get_creative(workspace_id, creative_id)
|
||||
if not creative:
|
||||
|
||||
@@ -24,7 +24,7 @@ async def create_placement(
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
placement_channel = await self.database.get_channel(workspace_id, channel_id=input.placement_channel_id)
|
||||
placement_channel = await self.database.get_channel(channel_id=input.placement_channel_id)
|
||||
if not placement_channel:
|
||||
raise domain.ChannelNotFound(input.placement_channel_id)
|
||||
|
||||
@@ -39,6 +39,8 @@ async def create_placement(
|
||||
if project.channel.telegram_id is None:
|
||||
raise domain.ChannelNotFound(project.channel.id)
|
||||
|
||||
print(f'Телега id: {project.channel.telegram_id}')
|
||||
|
||||
invite_link = await self.telegram_bot.create_chat_invite_link(project.channel.telegram_id, requires_approval)
|
||||
|
||||
placement = domain.Placement(
|
||||
@@ -71,13 +73,13 @@ async def create_placement(
|
||||
placement_date=placement.placement_date,
|
||||
cost=placement.cost,
|
||||
comment=placement.comment,
|
||||
ad_post_url=placement.ad_post_url,
|
||||
ad_post_url=None,
|
||||
invite_link_type=placement.invite_link_type,
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=placement.views_count,
|
||||
views_availability=placement.views_availability,
|
||||
last_views_fetch_at=placement.last_views_fetch_at,
|
||||
views_count=None,
|
||||
views_availability=domain.PostViewsAvailability.UNKNOWN,
|
||||
last_views_fetch_at=None,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,8 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
from .helpers import generate_post_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
@@ -21,6 +23,21 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl
|
||||
|
||||
context.ensure_project_permission(domain.PermissionKey.PLACEMENTS_READ, placement.project_id)
|
||||
|
||||
# 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,
|
||||
@@ -32,13 +49,13 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl
|
||||
placement_date=placement.placement_date,
|
||||
cost=placement.cost,
|
||||
comment=placement.comment,
|
||||
ad_post_url=placement.ad_post_url,
|
||||
ad_post_url=ad_post_url,
|
||||
invite_link_type=placement.invite_link_type,
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=placement.views_count,
|
||||
views_availability=placement.views_availability,
|
||||
last_views_fetch_at=placement.last_views_fetch_at,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
from .helpers import generate_post_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
@@ -26,8 +28,27 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
allowed_project_ids=allowed_project_ids,
|
||||
)
|
||||
|
||||
return dto.GetPlacementsOutput(
|
||||
placements=[
|
||||
# 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(
|
||||
id=placement.id,
|
||||
project_id=placement.project_id,
|
||||
@@ -39,16 +60,16 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
placement_date=placement.placement_date,
|
||||
cost=placement.cost,
|
||||
comment=placement.comment,
|
||||
ad_post_url=placement.ad_post_url,
|
||||
ad_post_url=ad_post_url,
|
||||
invite_link_type=placement.invite_link_type,
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=placement.views_count,
|
||||
views_availability=placement.views_availability,
|
||||
last_views_fetch_at=placement.last_views_fetch_at,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
for placement in placements
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return dto.GetPlacementsOutput(placements=placement_outputs)
|
||||
|
||||
7
src/usecase/placement/helpers.py
Normal file
7
src/usecase/placement/helpers.py
Normal file
@@ -0,0 +1,7 @@
|
||||
def generate_post_url(channel_username: str | None, message_id: int) -> str | None:
|
||||
"""Generate Telegram post URL from channel username and message ID."""
|
||||
if not channel_username:
|
||||
return None
|
||||
# Remove @ prefix if present
|
||||
username = channel_username.lstrip('@')
|
||||
return f'https://t.me/{username}/{message_id}'
|
||||
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
from .helpers import generate_post_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
@@ -17,9 +19,7 @@ async def update_placement(
|
||||
user_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
) -> dto.PlacementOutput:
|
||||
context = await self.ensure_workspace_permission(
|
||||
workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE
|
||||
)
|
||||
context = await self.ensure_workspace_permission(workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE)
|
||||
|
||||
placement = await self.database.get_placement(workspace_id, placement_id)
|
||||
if not placement:
|
||||
@@ -39,6 +39,21 @@ 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,
|
||||
@@ -50,13 +65,13 @@ async def update_placement(
|
||||
placement_date=placement.placement_date,
|
||||
cost=placement.cost,
|
||||
comment=placement.comment,
|
||||
ad_post_url=placement.ad_post_url,
|
||||
ad_post_url=ad_post_url,
|
||||
invite_link_type=placement.invite_link_type,
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=placement.views_count,
|
||||
views_availability=placement.views_availability,
|
||||
last_views_fetch_at=placement.last_views_fetch_at,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -15,9 +15,7 @@ async def get_workspace_projects(
|
||||
|
||||
allowed_project_ids = context.allowed_project_ids(domain.PermissionKey.PROJECTS_READ)
|
||||
|
||||
projects = await self.database.get_workspace_projects(
|
||||
input.workspace_id, allowed_project_ids=allowed_project_ids
|
||||
)
|
||||
projects = await self.database.get_workspace_projects(input.workspace_id, allowed_project_ids=allowed_project_ids)
|
||||
|
||||
return dto.GetWorkspaceProjectsOutput(
|
||||
projects=[
|
||||
|
||||
@@ -70,7 +70,7 @@ async def tg_add_project_1(self: 'Usecase', input: dto.ConnectProjectInput) -> d
|
||||
if len(workspaces) == 1:
|
||||
workspace = workspaces[0]
|
||||
|
||||
channel = await self.database.get_channel(workspace.id, telegram_id=input.telegram_id)
|
||||
channel = await self.database.get_channel(telegram_id=input.telegram_id)
|
||||
if channel:
|
||||
channel.title = input.title
|
||||
channel.username = input.username
|
||||
@@ -80,8 +80,6 @@ async def tg_add_project_1(self: 'Usecase', input: dto.ConnectProjectInput) -> d
|
||||
telegram_id=input.telegram_id,
|
||||
title=input.title,
|
||||
username=input.username,
|
||||
verification_status=domain.ChannelVerificationStatus.VERIFIED,
|
||||
workspace_id=workspace.id,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ async def tg_add_project_2(
|
||||
return
|
||||
|
||||
telegram_channel_id = int(channel_data['telegram_id'])
|
||||
channel = await self.database.get_channel(workspace.id, telegram_id=telegram_channel_id)
|
||||
channel = await self.database.get_channel(telegram_id=telegram_channel_id)
|
||||
if channel:
|
||||
channel.title = str(channel_data['title'])
|
||||
channel.username = channel_data.get('username')
|
||||
@@ -76,7 +76,6 @@ async def tg_add_project_2(
|
||||
telegram_id=telegram_channel_id,
|
||||
title=str(channel_data['title']),
|
||||
username=channel_data.get('username'),
|
||||
workspace_id=workspace.id,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ async def attach_channel_to_purchase_plan(
|
||||
user_id: uuid.UUID,
|
||||
input: dto.AttachChannelToPurchasePlanInput,
|
||||
) -> dto.PurchasePlanChannelOutput:
|
||||
context = await self.ensure_workspace_permission(
|
||||
workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE
|
||||
)
|
||||
context = await self.ensure_workspace_permission(workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
@@ -30,21 +28,15 @@ async def attach_channel_to_purchase_plan(
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
# Поиск канала по username
|
||||
channel = await self.database.get_channel(workspace_id, username=input.username)
|
||||
channel = await self.database.get_channel(username=input.username)
|
||||
|
||||
if not channel:
|
||||
# Создать неподтвержденный канал
|
||||
channel = domain.Channel(
|
||||
workspace_id=workspace_id,
|
||||
username=input.username,
|
||||
telegram_id=None,
|
||||
title=None,
|
||||
verification_status=domain.ChannelVerificationStatus.UNVERIFIED,
|
||||
)
|
||||
# Создать канал с nullable telegram_id/title (заполнятся парсером позже)
|
||||
channel = domain.Channel(username=input.username)
|
||||
await self.database.create_channel(channel)
|
||||
log.info('Created unverified channel with username %s', input.username)
|
||||
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('Found existing channel %s for username @%s', channel.id, input.username)
|
||||
|
||||
plan_channel = await self.database.add_channel_to_purchase_plan(
|
||||
plan.id,
|
||||
@@ -66,6 +58,5 @@ async def attach_channel_to_purchase_plan(
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -39,7 +39,6 @@ async def get_purchase_plan_channels(
|
||||
telegram_id=plan_channel.channel.telegram_id,
|
||||
title=plan_channel.channel.title,
|
||||
username=plan_channel.channel.username,
|
||||
verification_status=plan_channel.channel.verification_status,
|
||||
),
|
||||
)
|
||||
for plan_channel in plan_channels
|
||||
|
||||
@@ -17,9 +17,7 @@ async def remove_purchase_plan_channel(
|
||||
workspace_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
) -> None:
|
||||
context = await self.ensure_workspace_permission(
|
||||
workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE
|
||||
)
|
||||
context = await self.ensure_workspace_permission(workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
|
||||
@@ -18,9 +18,7 @@ async def update_purchase_plan_channel(
|
||||
user_id: uuid.UUID,
|
||||
input: dto.UpdatePurchasePlanChannelInput,
|
||||
) -> dto.PurchasePlanChannelOutput:
|
||||
context = await self.ensure_workspace_permission(
|
||||
workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE
|
||||
)
|
||||
context = await self.ensure_workspace_permission(workspace_id, user_id, domain.PermissionKey.PLACEMENTS_WRITE)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
@@ -56,7 +54,7 @@ async def update_purchase_plan_channel(
|
||||
|
||||
channel: domain.Channel | None = plan_channel.channel
|
||||
if channel is None:
|
||||
channel = await self.database.get_channel(workspace_id, channel_id=plan_channel.channel_id)
|
||||
channel = await self.database.get_channel(channel_id=plan_channel.channel_id)
|
||||
if channel is None:
|
||||
raise domain.ChannelNotFound(plan_channel.channel_id)
|
||||
|
||||
@@ -70,6 +68,5 @@ async def update_purchase_plan_channel(
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -21,17 +21,20 @@ async def get_views_history(self: 'Usecase', input: dto.GetViewsHistoryInput) ->
|
||||
|
||||
context.ensure_project_permission(domain.PermissionKey.PLACEMENTS_READ, placement.project_id)
|
||||
|
||||
if not placement.post:
|
||||
return dto.GetViewsHistoryOutput(histories=[])
|
||||
|
||||
histories = await self.database.get_views_history(
|
||||
input.placement_id,
|
||||
placement.post.id,
|
||||
from_date=input.from_date,
|
||||
to_date=input.to_date,
|
||||
)
|
||||
|
||||
return dto.GetViewsHistoryOutput(
|
||||
histories=[
|
||||
dto.PlacementViewsHistoryOutput(
|
||||
dto.PostViewsHistoryOutput(
|
||||
id=history.id,
|
||||
placement_id=history.placement_id,
|
||||
post_id=history.post_id,
|
||||
views_count=history.views_count,
|
||||
fetched_at=history.fetched_at,
|
||||
created_at=history.created_at,
|
||||
|
||||
@@ -17,6 +17,4 @@ async def get_workspace_invites(
|
||||
|
||||
invites = await self.database.get_workspace_invites(workspace_id)
|
||||
|
||||
return dto.GetWorkspaceInvitesOutput(
|
||||
invites=[workspace_invite_to_dto(invite) for invite in invites]
|
||||
)
|
||||
return dto.GetWorkspaceInvitesOutput(invites=[workspace_invite_to_dto(invite) for invite in invites])
|
||||
|
||||
@@ -17,6 +17,4 @@ async def get_workspace_members(
|
||||
|
||||
members = await self.database.get_workspace_members(workspace_id)
|
||||
|
||||
return dto.GetWorkspaceMembersOutput(
|
||||
members=[workspace_member_to_dto(member) for member in members]
|
||||
)
|
||||
return dto.GetWorkspaceMembersOutput(members=[workspace_member_to_dto(member) for member in members])
|
||||
|
||||
Reference in New Issue
Block a user