feat: переименование доменной области
This commit is contained in:
@@ -8,14 +8,15 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
from src import domain
|
||||
|
||||
from .analytics.get_channel_analytics import get_channel_analytics
|
||||
from .analytics.get_creatives_analytics import get_creatives_analytics
|
||||
from .analytics.get_external_channels_analytics import get_external_channels_analytics
|
||||
from .analytics.get_placements_analytics import get_placements_analytics
|
||||
from .analytics.get_spending_analytics import get_spending_analytics
|
||||
from .auth.get_me import get_me
|
||||
from .auth.telegram_login import telegram_login
|
||||
from .auth.telegram_start import telegram_start
|
||||
from .auth.validate_login_token import validate_login_token
|
||||
from .channel.get_channels import get_channels
|
||||
from .creative.create_creative import create_creative
|
||||
from .creative.delete_creative import delete_creative
|
||||
from .creative.get_creative import get_creative
|
||||
@@ -25,23 +26,22 @@ from .creative.tg_create_creative_2 import tg_create_creative_2
|
||||
from .creative.tg_create_creative_3 import tg_create_creative_3
|
||||
from .creative.tg_create_creative_4 import tg_create_creative_4
|
||||
from .creative.update_creative import update_creative
|
||||
from .external_channel.create_external_channel import create_external_channel
|
||||
from .external_channel.delete_external_channel import delete_external_channel
|
||||
from .external_channel.get_external_channels import get_external_channels
|
||||
from .external_channel.update_external_channel import update_external_channel
|
||||
from .external_channel.update_external_channel_links import update_external_channel_links
|
||||
from .placement.create_placement import create_placement
|
||||
from .placement.delete_placement import delete_placement
|
||||
from .placement.get_placement import get_placement
|
||||
from .placement.get_placements import get_placements
|
||||
from .placement.update_placement import update_placement
|
||||
from .project.disconnect_project_by_tg_id import disconnect_project_by_tg_id
|
||||
from .project.get_workspace_projects import get_workspace_projects
|
||||
from .project.tg_add_project_1 import tg_add_project_1
|
||||
from .project.tg_add_project_2 import tg_add_project_2
|
||||
from .project.update_project_permissions import update_project_permissions
|
||||
from .purchase_plan.attach_channel_to_purchase_plan import attach_channel_to_purchase_plan
|
||||
from .purchase_plan.get_purchase_plan_channels import get_purchase_plan_channels
|
||||
from .purchase_plan.remove_purchase_plan_channel import remove_purchase_plan_channel
|
||||
from .purchase_plan.update_purchase_plan_channel import update_purchase_plan_channel
|
||||
from .subscription.handle_subscription import handle_subscription
|
||||
from .subscription.handle_unsubscription import handle_unsubscription
|
||||
from .target_channel.disconnect_target_chan_by_tg_id import disconnect_target_chan_by_tg_id
|
||||
from .target_channel.get_user_target_chans import get_user_target_chans
|
||||
from .target_channel.tg_add_target_chan_1 import tg_add_target_chan_1
|
||||
from .target_channel.tg_add_target_chan_2 import tg_add_target_chan_2
|
||||
from .target_channel.update_target_chan_permissions import update_target_chan_permissions
|
||||
from .views.get_views_history import get_views_history
|
||||
from .workspace.create_workspace import create_workspace
|
||||
from .workspace.delete_workspace import delete_workspace
|
||||
@@ -82,46 +82,74 @@ class Database(typing.Protocol):
|
||||
|
||||
async def mark_token_as_used(self, token: str) -> None: ...
|
||||
|
||||
async def get_target_channel(
|
||||
self, workspace_id: UUID, channel_id: UUID | None = None, telegram_id: int | None = None
|
||||
) -> domain.TargetChannel | None: ...
|
||||
async def update_user(self, user: domain.User) -> None: ...
|
||||
|
||||
async def get_target_channel_for_user_by_telegram(
|
||||
self, user_id: UUID, telegram_id: int
|
||||
) -> domain.TargetChannel | None: ...
|
||||
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 upsert_target_channel(self, channel: domain.TargetChannel) -> None: ...
|
||||
async def get_workspace_channels(self, workspace_id: UUID) -> list[domain.Channel]: ...
|
||||
|
||||
async def get_workspace_target_channels(self, workspace_id: UUID) -> list[domain.TargetChannel]: ...
|
||||
async def create_channel(self, channel: domain.Channel) -> None: ...
|
||||
|
||||
async def update_target_channel(self, channel: domain.TargetChannel) -> None: ...
|
||||
async def update_channel(self, channel: domain.Channel) -> None: ...
|
||||
|
||||
async def get_external_channel(
|
||||
self, workspace_id: UUID, channel_id: UUID | None = None, telegram_id: int | None = None
|
||||
) -> domain.ExternalChannel | None: ...
|
||||
async def delete_channel(self, channel_id: UUID) -> None: ...
|
||||
|
||||
async def create_external_channel(self, channel: domain.ExternalChannel) -> None: ...
|
||||
async def search_channels(self, username_query: str | None = None) -> list[domain.Channel]: ...
|
||||
|
||||
async def get_external_channels_for_target(self, target_channel_id: UUID) -> list[domain.ExternalChannel]: ...
|
||||
async def get_project(
|
||||
self, workspace_id: UUID, project_id: UUID | None = None, channel_id: UUID | None = None
|
||||
) -> domain.Project | None: ...
|
||||
|
||||
async def get_workspace_external_channels(self, workspace_id: UUID) -> list[domain.ExternalChannel]: ...
|
||||
async def get_project_for_user_by_telegram(
|
||||
self, user_id: UUID, channel_telegram_id: int
|
||||
) -> domain.Project | None: ...
|
||||
|
||||
async def add_external_channel_to_targets(
|
||||
self, external_channel: domain.ExternalChannel, target_channel_ids: list[UUID]
|
||||
) -> None: ...
|
||||
async def get_project_by_channel_telegram(self, channel_telegram_id: int) -> domain.Project | None: ...
|
||||
|
||||
async def remove_external_channel_from_target(self, external_channel_id: UUID, target_channel_id: UUID) -> None: ...
|
||||
async def create_project(self, project: domain.Project) -> None: ...
|
||||
|
||||
async def delete_external_channel(self, channel_id: UUID) -> None: ...
|
||||
async def update_project(self, project: domain.Project) -> None: ...
|
||||
|
||||
async def update_external_channel(self, channel: domain.ExternalChannel) -> None: ...
|
||||
async def get_workspace_projects(self, workspace_id: UUID) -> list[domain.Project]: ...
|
||||
|
||||
async def get_active_purchase_plan(self, project_id: UUID) -> domain.PurchasePlan | None: ...
|
||||
|
||||
async def get_purchase_plan(self, workspace_id: UUID, plan_id: UUID) -> domain.PurchasePlan | None: ...
|
||||
|
||||
async def create_purchase_plan(self, plan: domain.PurchasePlan) -> None: ...
|
||||
|
||||
async def get_purchase_plan_channels(self, plan_id: UUID) -> list[domain.PurchasePlanChannel]: ...
|
||||
|
||||
async def get_purchase_plan_channel(
|
||||
self, plan_channel_id: UUID, plan_id: UUID
|
||||
) -> domain.PurchasePlanChannel | None: ...
|
||||
|
||||
async def add_channel_to_purchase_plan(
|
||||
self,
|
||||
plan_id: UUID,
|
||||
channel_id: UUID,
|
||||
*,
|
||||
status: domain.PurchasePlanChannelStatus | None = None,
|
||||
planned_cost: float | None = None,
|
||||
comment: str | None = None,
|
||||
) -> domain.PurchasePlanChannel: ...
|
||||
|
||||
async def update_purchase_plan_channel(self, plan_channel: domain.PurchasePlanChannel) -> None: ...
|
||||
|
||||
async def remove_channel_from_purchase_plan(self, plan_id: UUID, channel_id: UUID) -> None: ...
|
||||
|
||||
async def get_creative(self, workspace_id: UUID, creative_id: UUID) -> domain.Creative | None: ...
|
||||
|
||||
async def update_creative(self, creative: domain.Creative) -> None: ...
|
||||
|
||||
async def get_workspace_creatives(
|
||||
self, workspace_id: UUID, target_channel_id: UUID | None = None, include_archived: bool = False
|
||||
self, workspace_id: UUID, project_id: UUID | None = None, include_archived: bool = False
|
||||
) -> list[domain.Creative]: ...
|
||||
|
||||
async def delete_creative(self, creative_id: UUID) -> None: ...
|
||||
@@ -135,8 +163,8 @@ class Database(typing.Protocol):
|
||||
async def get_workspace_placements(
|
||||
self,
|
||||
workspace_id: UUID,
|
||||
target_channel_id: UUID | None = None,
|
||||
external_channel_id: UUID | None = None,
|
||||
project_id: UUID | None = None,
|
||||
placement_channel_id: UUID | None = None,
|
||||
creative_id: UUID | None = None,
|
||||
include_archived: bool = False,
|
||||
) -> list[domain.Placement]: ...
|
||||
@@ -145,10 +173,6 @@ class Database(typing.Protocol):
|
||||
|
||||
async def get_placement_by_invite_link(self, invite_link: str) -> domain.Placement | None: ...
|
||||
|
||||
async def get_subscriber(self, telegram_id: int) -> domain.Subscriber | None: ...
|
||||
|
||||
async def upsert_subscriber(self, subscriber: domain.Subscriber) -> None: ...
|
||||
|
||||
async def create_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||
|
||||
async def get_subscription_by_subscriber_and_placement(
|
||||
@@ -157,12 +181,12 @@ class Database(typing.Protocol):
|
||||
|
||||
async def update_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||
|
||||
async def get_active_subscriptions_by_subscriber_and_channel(
|
||||
self, subscriber_id: UUID, channel_telegram_id: int
|
||||
async def get_active_subscriptions_by_subscriber_and_project(
|
||||
self, subscriber_id: UUID, project_id: UUID
|
||||
) -> list[domain.Subscription]: ...
|
||||
|
||||
async def get_active_subscription_by_subscriber_and_channel(
|
||||
self, subscriber_id: UUID, channel_telegram_id: int
|
||||
async def get_active_subscription_by_subscriber_and_project(
|
||||
self, subscriber_id: UUID, project_id: UUID
|
||||
) -> domain.Subscription | None: ...
|
||||
|
||||
async def create_placement_views_history(self, history: domain.PlacementViewsHistory) -> None: ...
|
||||
@@ -229,20 +253,29 @@ class Usecase:
|
||||
|
||||
return workspace
|
||||
|
||||
async def ensure_active_purchase_plan(self, project: domain.Project) -> domain.PurchasePlan:
|
||||
plan = await self.database.get_active_purchase_plan(project.id)
|
||||
if plan:
|
||||
return plan
|
||||
|
||||
plan = domain.PurchasePlan(workspace_id=project.workspace_id, project_id=project.id)
|
||||
await self.database.create_purchase_plan(plan)
|
||||
return plan
|
||||
|
||||
validate_login_token = validate_login_token
|
||||
telegram_login = telegram_login
|
||||
telegram_start = telegram_start
|
||||
get_me = get_me
|
||||
tg_add_target_chan_1 = tg_add_target_chan_1
|
||||
tg_add_target_chan_2 = tg_add_target_chan_2
|
||||
get_user_target_chans = get_user_target_chans
|
||||
disconnect_target_chan_by_tg_id = disconnect_target_chan_by_tg_id
|
||||
update_target_chan_permissions = update_target_chan_permissions
|
||||
get_external_channels = get_external_channels
|
||||
create_external_channel = create_external_channel
|
||||
delete_external_channel = delete_external_channel
|
||||
update_external_channel = update_external_channel
|
||||
update_external_channel_links = update_external_channel_links
|
||||
tg_add_project_1 = tg_add_project_1
|
||||
tg_add_project_2 = tg_add_project_2
|
||||
get_workspace_projects = get_workspace_projects
|
||||
disconnect_project_by_tg_id = disconnect_project_by_tg_id
|
||||
update_project_permissions = update_project_permissions
|
||||
get_channels = get_channels
|
||||
attach_channel_to_purchase_plan = attach_channel_to_purchase_plan
|
||||
get_purchase_plan_channels = get_purchase_plan_channels
|
||||
update_purchase_plan_channel = update_purchase_plan_channel
|
||||
remove_purchase_plan_channel = remove_purchase_plan_channel
|
||||
get_creatives = get_creatives
|
||||
get_creative = get_creative
|
||||
create_creative = create_creative
|
||||
@@ -262,7 +295,7 @@ class Usecase:
|
||||
get_views_history = get_views_history
|
||||
get_placements_analytics = get_placements_analytics
|
||||
get_creatives_analytics = get_creatives_analytics
|
||||
get_external_channels_analytics = get_external_channels_analytics
|
||||
get_channel_analytics = get_channel_analytics
|
||||
get_spending_analytics = get_spending_analytics
|
||||
get_workspaces = get_workspaces
|
||||
create_workspace = create_workspace
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from .get_creatives_analytics import get_creatives_analytics
|
||||
from .get_external_channels_analytics import get_external_channels_analytics
|
||||
from .get_placements_analytics import get_placements_analytics
|
||||
from .get_spending_analytics import get_spending_analytics
|
||||
|
||||
__all__ = (
|
||||
'get_placements_analytics',
|
||||
'get_creatives_analytics',
|
||||
'get_external_channels_analytics',
|
||||
'get_spending_analytics',
|
||||
)
|
||||
@@ -11,21 +11,21 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_external_channels_analytics(
|
||||
self: 'Usecase', input: dto.GetExternalChannelsAnalyticsInput
|
||||
) -> dto.GetExternalChannelsAnalyticsOutput:
|
||||
async def get_channel_analytics(self: 'Usecase', input: dto.GetChannelAnalyticsInput) -> dto.GetChannelAnalyticsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
if input.target_channel_id:
|
||||
target_channel = await self.database.get_target_channel(input.workspace_id, input.target_channel_id)
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
external_channels = await self.database.get_external_channels_for_target(input.target_channel_id)
|
||||
if input.project_id:
|
||||
project = await self.database.get_project(input.workspace_id, input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
plan_channels = await self.database.get_purchase_plan_channels(plan.id)
|
||||
channels = [pc.channel for pc in plan_channels]
|
||||
else:
|
||||
external_channels = await self.database.get_workspace_external_channels(input.workspace_id)
|
||||
channels = await self.database.get_workspace_channels(input.workspace_id)
|
||||
|
||||
placements = await self.database.get_workspace_placements(
|
||||
input.workspace_id, input.target_channel_id, include_archived=False
|
||||
input.workspace_id, input.project_id, include_archived=False
|
||||
)
|
||||
|
||||
@dataclass
|
||||
@@ -35,10 +35,10 @@ async def get_external_channels_analytics(
|
||||
total_views: int = 0
|
||||
placements_count: int = 0
|
||||
|
||||
channel_stats: dict[UUID, ChannelStats] = {ch.id: ChannelStats() for ch in external_channels}
|
||||
channel_stats: dict[UUID, ChannelStats] = {ch.id: ChannelStats() for ch in channels}
|
||||
|
||||
for p in placements:
|
||||
stats = channel_stats.get(p.external_channel_id)
|
||||
stats = channel_stats.get(p.placement_channel_id)
|
||||
if stats is None:
|
||||
continue
|
||||
|
||||
@@ -48,7 +48,7 @@ async def get_external_channels_analytics(
|
||||
stats.placements_count += 1
|
||||
|
||||
result = []
|
||||
for ch in external_channels:
|
||||
for ch in channels:
|
||||
stats = channel_stats[ch.id]
|
||||
|
||||
avg_cpf = (
|
||||
@@ -61,7 +61,7 @@ async def get_external_channels_analytics(
|
||||
)
|
||||
|
||||
result.append(
|
||||
dto.ExternalChannelAnalyticsOutput(
|
||||
dto.ChannelAnalyticsOutput(
|
||||
id=ch.id,
|
||||
title=ch.title,
|
||||
username=ch.username,
|
||||
@@ -74,4 +74,4 @@ async def get_external_channels_analytics(
|
||||
)
|
||||
)
|
||||
|
||||
return dto.GetExternalChannelsAnalyticsOutput(external_channels=result)
|
||||
return dto.GetChannelAnalyticsOutput(channels=result)
|
||||
@@ -16,16 +16,16 @@ async def get_creatives_analytics(
|
||||
) -> dto.GetCreativesAnalyticsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
if input.target_channel_id:
|
||||
target_channel = await self.database.get_target_channel(input.workspace_id, input.target_channel_id)
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
if input.project_id:
|
||||
project = await self.database.get_project(input.workspace_id, input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
creatives = await self.database.get_workspace_creatives(
|
||||
input.workspace_id, input.target_channel_id, include_archived=False
|
||||
input.workspace_id, input.project_id, include_archived=False
|
||||
)
|
||||
placements = await self.database.get_workspace_placements(
|
||||
input.workspace_id, input.target_channel_id, include_archived=False
|
||||
input.workspace_id, input.project_id, include_archived=False
|
||||
)
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -26,22 +26,22 @@ async def get_placements_analytics(
|
||||
) -> dto.GetPlacementsAnalyticsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
if input.target_channel_id:
|
||||
target_channel = await self.database.get_target_channel(input.workspace_id, input.target_channel_id)
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
if input.project_id:
|
||||
project = await self.database.get_project(input.workspace_id, input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
placements = await self.database.get_workspace_placements(
|
||||
input.workspace_id, input.target_channel_id, include_archived=False
|
||||
input.workspace_id, input.project_id, include_archived=False
|
||||
)
|
||||
|
||||
result = [
|
||||
dto.PlacementAnalyticsOutput(
|
||||
id=p.id,
|
||||
target_channel_id=p.target_channel_id,
|
||||
target_channel_title=p.target_channel.title,
|
||||
external_channel_id=p.external_channel_id,
|
||||
external_channel_title=p.external_channel.title,
|
||||
project_id=p.project_id,
|
||||
project_channel_title=p.project.channel.title,
|
||||
placement_channel_id=p.placement_channel_id,
|
||||
placement_channel_title=p.placement_channel.title,
|
||||
creative_id=p.creative_id,
|
||||
creative_name=p.creative.name,
|
||||
placement_date=p.placement_date,
|
||||
|
||||
@@ -35,13 +35,13 @@ async def get_spending_analytics(
|
||||
) -> dto.GetSpendingAnalyticsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
if input.target_channel_id:
|
||||
target_channel = await self.database.get_target_channel(input.workspace_id, input.target_channel_id)
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
if input.project_id:
|
||||
project = await self.database.get_project(input.workspace_id, input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
placements = await self.database.get_workspace_placements(
|
||||
input.workspace_id, input.target_channel_id, include_archived=False
|
||||
input.workspace_id, input.project_id, include_archived=False
|
||||
)
|
||||
|
||||
filtered = [
|
||||
|
||||
28
src/usecase/channel/get_channels.py
Normal file
28
src/usecase/channel/get_channels.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_channels(self: 'Usecase', input: dto.GetChannelsInput) -> dto.GetChannelsOutput:
|
||||
channels = await self.database.search_channels(username_query=input.username)
|
||||
|
||||
log.debug('Found %s channels for username query: %s', len(channels), input.username)
|
||||
|
||||
return dto.GetChannelsOutput(
|
||||
channels=[
|
||||
dto.ChannelOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
)
|
||||
for channel in channels
|
||||
]
|
||||
)
|
||||
@@ -15,16 +15,16 @@ async def create_creative(
|
||||
) -> dto.CreativeOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
target_channel = await self.database.get_target_channel(workspace_id, channel_id=input.target_channel_id)
|
||||
if not target_channel:
|
||||
log.warning('User %s attempted to create creative for unavailable target %s', user_id, input.target_channel_id)
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
project = await self.database.get_project(workspace_id, project_id=input.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)
|
||||
|
||||
creative = domain.Creative(
|
||||
name=input.name,
|
||||
text=input.text,
|
||||
status=domain.CreativeStatus.ACTIVE,
|
||||
target_channel_id=target_channel.id,
|
||||
project_id=project.id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
await self.database.create_creative(creative)
|
||||
@@ -33,8 +33,8 @@ async def create_creative(
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
target_channel_id=target_channel.id,
|
||||
target_channel_title=target_channel.title,
|
||||
project_id=project.id,
|
||||
project_channel_title=project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
status=creative.status,
|
||||
placements_count=creative.placements_count,
|
||||
|
||||
@@ -20,8 +20,8 @@ async def get_creative(self: 'Usecase', input: dto.GetCreativeInput) -> dto.Crea
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
target_channel_id=creative.target_channel_id,
|
||||
target_channel_title=creative.target_channel.title,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
status=creative.status,
|
||||
placements_count=creative.placements_count,
|
||||
|
||||
@@ -10,7 +10,7 @@ async def get_creatives(self: 'Usecase', input: dto.GetCreativesInput) -> dto.Ge
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
creatives = await self.database.get_workspace_creatives(
|
||||
input.workspace_id, input.target_channel_id, input.include_archived
|
||||
input.workspace_id, input.project_id, input.include_archived
|
||||
)
|
||||
|
||||
return dto.GetCreativesOutput(
|
||||
@@ -19,8 +19,8 @@ async def get_creatives(self: 'Usecase', input: dto.GetCreativesInput) -> dto.Ge
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
target_channel_id=creative.target_channel_id,
|
||||
target_channel_title=creative.target_channel.title,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
status=creative.status,
|
||||
placements_count=creative.placements_count,
|
||||
|
||||
@@ -36,18 +36,23 @@ async def tg_create_creative_1(self: 'Usecase', telegram_id: int, chat_id: int)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
channels = await self.database.get_workspace_target_channels(workspace.id)
|
||||
if not channels:
|
||||
projects = await self.database.get_workspace_projects(workspace.id)
|
||||
if not projects:
|
||||
await self.telegram_bot.send_message(
|
||||
'❌ В выбранном рабочем пространстве нет подключенных каналов. Добавьте канал и попробуйте снова.',
|
||||
'❌ В выбранном рабочем пространстве нет подключенных проектов. Добавьте канал и попробуйте снова.',
|
||||
chat_id,
|
||||
)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text=channel.title, callback_data=f'tg_create_creative_3:{channel.id}')]
|
||||
for channel in channels
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=project.channel.title or project.channel.username or 'Unnamed Channel',
|
||||
callback_data=f'tg_create_creative_3:{project.id}',
|
||||
)
|
||||
]
|
||||
for project in projects
|
||||
]
|
||||
|
||||
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||
|
||||
@@ -47,18 +47,23 @@ async def tg_create_creative_2(
|
||||
|
||||
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
||||
|
||||
channels = await self.database.get_workspace_target_channels(workspace.id)
|
||||
if not channels:
|
||||
projects = await self.database.get_workspace_projects(workspace.id)
|
||||
if not projects:
|
||||
await self.telegram_bot.send_message(
|
||||
'❌ В выбранном рабочем пространстве нет подключенных каналов. Добавьте канал и попробуйте снова.',
|
||||
'❌ В выбранном рабочем пространстве нет подключенных проектов. Добавьте канал и попробуйте снова.',
|
||||
chat_id,
|
||||
)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text=channel.title, callback_data=f'tg_create_creative_3:{channel.id}')]
|
||||
for channel in channels
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=project.channel.title or project.channel.username or 'Unnamed Channel',
|
||||
callback_data=f'tg_create_creative_3:{project.id}',
|
||||
)
|
||||
]
|
||||
for project in projects
|
||||
]
|
||||
|
||||
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||
|
||||
@@ -22,11 +22,11 @@ async def tg_create_creative_3(
|
||||
log.warning('Invalid callback data format: %s', callback_data)
|
||||
return
|
||||
|
||||
channel_id_str = callback_data.split(':', 1)[1]
|
||||
project_id_str = callback_data.split(':', 1)[1]
|
||||
try:
|
||||
channel_id = uuid.UUID(channel_id_str)
|
||||
project_id = uuid.UUID(project_id_str)
|
||||
except ValueError:
|
||||
log.error('Invalid channel UUID in callback data: %s', channel_id_str)
|
||||
log.error('Invalid project UUID in callback data: %s', project_id_str)
|
||||
return
|
||||
|
||||
user = await self.database.get_user(telegram_id=telegram_id)
|
||||
@@ -53,13 +53,13 @@ async def tg_create_creative_3(
|
||||
workspace_id = workspace.id
|
||||
|
||||
if not workspace_id:
|
||||
log.error('Workspace ID missing for user %s when selecting channel %s', telegram_id, channel_id)
|
||||
log.error('Workspace ID missing for user %s when selecting project %s', telegram_id, project_id)
|
||||
await self.telegram_bot.send_message('❌ Ошибка: рабочее пространство не найдено. Начните заново.', chat_id)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
channel = await self.database.get_target_channel(workspace_id, channel_id=channel_id)
|
||||
if not channel:
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
await self.telegram_bot.send_message('❌ Канал не найден или не принадлежит вам.', chat_id)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
@@ -68,12 +68,14 @@ async def tg_create_creative_3(
|
||||
telegram_id,
|
||||
domain.TelegramStateEnum.CREATIVE_WAITING_NAME,
|
||||
{
|
||||
'target_channel_id': str(channel_id),
|
||||
'channel_title': channel.title,
|
||||
'project_id': str(project_id),
|
||||
'channel_title': project.channel.title,
|
||||
'workspace_id': str(workspace_id),
|
||||
},
|
||||
)
|
||||
|
||||
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
||||
|
||||
await self.telegram_bot.send_message(f'✅ Канал выбран: {channel.title}\n\n📝 Введите название креатива:', chat_id)
|
||||
await self.telegram_bot.send_message(
|
||||
f'✅ Канал выбран: {project.channel.title}\n\n📝 Введите название креатива:', chat_id
|
||||
)
|
||||
|
||||
@@ -38,20 +38,20 @@ async def tg_create_creative_4(
|
||||
)
|
||||
|
||||
elif state.state == domain.TelegramStateEnum.CREATIVE_WAITING_TEXT:
|
||||
target_channel_id_str = state.context.get('target_channel_id')
|
||||
project_id_str = state.context.get('project_id')
|
||||
creative_name = state.context.get('creative_name')
|
||||
workspace_id_str = state.context.get('workspace_id')
|
||||
|
||||
if not target_channel_id_str or not creative_name:
|
||||
if not project_id_str or not creative_name:
|
||||
log.error('Missing context data for creative creation: %s', state.context)
|
||||
await self.telegram_bot.send_message('❌ Ошибка: данные не найдены. Начните заново.', chat_id)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
try:
|
||||
target_channel_id = uuid.UUID(target_channel_id_str)
|
||||
project_id = uuid.UUID(project_id_str)
|
||||
except ValueError:
|
||||
log.error('Invalid target_channel_id in context: %s', target_channel_id_str)
|
||||
log.error('Invalid project_id in context: %s', project_id_str)
|
||||
await self.telegram_bot.send_message('❌ Ошибка: неверный формат данных.', chat_id)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
@@ -78,8 +78,8 @@ async def tg_create_creative_4(
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
channel = await self.database.get_target_channel(workspace_id, channel_id=target_channel_id)
|
||||
if not channel:
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
await self.telegram_bot.send_message('❌ Канал не найден.', chat_id)
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
@@ -92,7 +92,7 @@ async def tg_create_creative_4(
|
||||
text=creative_text,
|
||||
status=domain.CreativeStatus.ACTIVE,
|
||||
workspace_id=workspace_id,
|
||||
target_channel_id=target_channel_id,
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
async with self.database.transaction():
|
||||
@@ -104,7 +104,7 @@ async def tg_create_creative_4(
|
||||
await self.telegram_bot.send_message(
|
||||
f'✅ Креатив успешно создан!\n\n'
|
||||
f'📝 Название: {creative.name}\n'
|
||||
f'📢 Канал: {channel.title}\n\n'
|
||||
f'📢 Канал: {project.channel.title}\n\n'
|
||||
f'💬 Текст:\n{text_preview}',
|
||||
chat_id,
|
||||
)
|
||||
|
||||
@@ -37,8 +37,8 @@ async def update_creative(
|
||||
id=creative.id,
|
||||
name=creative.name,
|
||||
text=creative.text,
|
||||
target_channel_id=creative.target_channel_id,
|
||||
target_channel_title=creative.target_channel.title,
|
||||
project_id=creative.project_id,
|
||||
project_channel_title=creative.project.channel.title,
|
||||
created_at=creative.created_at,
|
||||
status=creative.status,
|
||||
placements_count=creative.placements_count,
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def create_external_channel(
|
||||
self: 'Usecase', input: dto.CreateExternalChannelInput, user_id: uuid.UUID, workspace_id: uuid.UUID
|
||||
) -> dto.ExternalChannelOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
existing_channel = await self.database.get_external_channel(workspace_id, telegram_id=input.telegram_id)
|
||||
if existing_channel:
|
||||
raise domain.ExternalChannelAlreadyExists(input.telegram_id)
|
||||
|
||||
for target_id in input.target_channel_ids:
|
||||
target_channel = await self.database.get_target_channel(workspace_id, channel_id=target_id)
|
||||
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(target_id)
|
||||
|
||||
async with self.database.transaction():
|
||||
channel = domain.ExternalChannel(
|
||||
telegram_id=input.telegram_id,
|
||||
title=input.title,
|
||||
username=input.username,
|
||||
description=input.description,
|
||||
subscribers_count=input.subscribers_count,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
await self.database.create_external_channel(channel)
|
||||
|
||||
if input.target_channel_ids:
|
||||
await self.database.add_external_channel_to_targets(channel, input.target_channel_ids)
|
||||
|
||||
log.info(
|
||||
'External channel %s created and linked to %s target channels', input.telegram_id, len(input.target_channel_ids)
|
||||
)
|
||||
|
||||
return dto.ExternalChannelOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
description=channel.description,
|
||||
subscribers_count=channel.subscribers_count,
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def delete_external_channel(self: 'Usecase', input: dto.DeleteExternalChannelInput) -> None:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
channel = await self.database.get_external_channel(input.workspace_id, channel_id=input.channel_id)
|
||||
if not channel:
|
||||
raise domain.ExternalChannelNotFound(input.channel_id)
|
||||
|
||||
await self.database.delete_external_channel(input.channel_id)
|
||||
log.info('User %s deleted external channel %s', input.user_id, input.channel_id)
|
||||
@@ -1,34 +0,0 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_external_channels(self: 'Usecase', input: dto.GetExternalChannelsInput) -> dto.GetExternalChannelsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
target_channel = await self.database.get_target_channel(input.workspace_id, channel_id=input.target_channel_id)
|
||||
if not target_channel:
|
||||
log.warning('Target channel %s not found or not accessible for user %s', input.target_channel_id, input.user_id)
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
|
||||
channels = await self.database.get_external_channels_for_target(input.target_channel_id)
|
||||
|
||||
return dto.GetExternalChannelsOutput(
|
||||
external_channels=[
|
||||
dto.ExternalChannelOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
description=channel.description,
|
||||
subscribers_count=channel.subscribers_count,
|
||||
)
|
||||
for channel in channels
|
||||
]
|
||||
)
|
||||
@@ -1,47 +0,0 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def update_external_channel(
|
||||
self: 'Usecase',
|
||||
channel_id: uuid.UUID,
|
||||
input: dto.UpdateExternalChannelInput,
|
||||
user_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
) -> dto.ExternalChannelOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
external_channel = await self.database.get_external_channel(workspace_id, channel_id=channel_id)
|
||||
if not external_channel:
|
||||
log.warning('External channel %s not found', channel_id)
|
||||
raise domain.ExternalChannelNotFound(channel_id)
|
||||
|
||||
if input.title is not None:
|
||||
external_channel.title = input.title
|
||||
if input.username is not None:
|
||||
external_channel.username = input.username
|
||||
if input.description is not None:
|
||||
external_channel.description = input.description
|
||||
if input.subscribers_count is not None:
|
||||
external_channel.subscribers_count = input.subscribers_count
|
||||
|
||||
await self.database.update_external_channel(external_channel)
|
||||
|
||||
log.info('External channel %s updated', channel_id)
|
||||
|
||||
return dto.ExternalChannelOutput(
|
||||
id=external_channel.id,
|
||||
telegram_id=external_channel.telegram_id,
|
||||
title=external_channel.title,
|
||||
username=external_channel.username,
|
||||
description=external_channel.description,
|
||||
subscribers_count=external_channel.subscribers_count,
|
||||
)
|
||||
@@ -1,54 +0,0 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def update_external_channel_links(
|
||||
self: 'Usecase',
|
||||
channel_id: uuid.UUID,
|
||||
input: dto.UpdateExternalChannelLinksInput,
|
||||
user_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
) -> dto.ExternalChannelOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
external_channel = await self.database.get_external_channel(workspace_id, channel_id=channel_id)
|
||||
if not external_channel:
|
||||
log.warning('External channel %s not found', channel_id)
|
||||
raise domain.ExternalChannelNotFound(channel_id)
|
||||
|
||||
for target_id in input.add_target_channel_ids:
|
||||
target_channel = await self.database.get_target_channel(workspace_id, channel_id=target_id)
|
||||
if not target_channel:
|
||||
log.warning('User %s attempted to link external channel to target %s they do not own', user_id, target_id)
|
||||
raise domain.TargetChannelNotFound(target_id)
|
||||
|
||||
async with self.database.transaction():
|
||||
if input.add_target_channel_ids:
|
||||
await self.database.add_external_channel_to_targets(external_channel, input.add_target_channel_ids)
|
||||
|
||||
for target_id in input.remove_target_channel_ids:
|
||||
await self.database.remove_external_channel_from_target(channel_id, target_id)
|
||||
|
||||
log.info(
|
||||
'External channel %s links updated: %s added, %s removed',
|
||||
channel_id,
|
||||
len(input.add_target_channel_ids),
|
||||
len(input.remove_target_channel_ids),
|
||||
)
|
||||
|
||||
return dto.ExternalChannelOutput(
|
||||
id=external_channel.id,
|
||||
telegram_id=external_channel.telegram_id,
|
||||
title=external_channel.title,
|
||||
username=external_channel.username,
|
||||
description=external_channel.description,
|
||||
subscribers_count=external_channel.subscribers_count,
|
||||
)
|
||||
@@ -15,24 +15,30 @@ async def create_placement(
|
||||
) -> dto.PlacementOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
target_channel = await self.database.get_target_channel(workspace_id, channel_id=input.target_channel_id)
|
||||
if not target_channel:
|
||||
raise domain.TargetChannelNotFound(input.target_channel_id)
|
||||
project = await self.database.get_project(workspace_id, project_id=input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
external_channel = await self.database.get_external_channel(workspace_id, channel_id=input.external_channel_id)
|
||||
if not external_channel:
|
||||
raise domain.ExternalChannelNotFound(input.external_channel_id)
|
||||
placement_channel = await self.database.get_channel(workspace_id, channel_id=input.placement_channel_id)
|
||||
if not placement_channel:
|
||||
raise domain.ChannelNotFound(input.placement_channel_id)
|
||||
|
||||
creative = await self.database.get_creative(workspace_id, input.creative_id)
|
||||
if not creative:
|
||||
raise domain.CreativeNotFound(input.creative_id)
|
||||
if creative.project_id != project.id:
|
||||
raise domain.CreativeNotFound(input.creative_id)
|
||||
|
||||
requires_approval = input.invite_link_type == domain.InviteLinkType.APPROVAL
|
||||
invite_link = await self.telegram_bot.create_chat_invite_link(target_channel.telegram_id, requires_approval)
|
||||
|
||||
if project.channel.telegram_id is None:
|
||||
raise domain.ChannelNotFound(project.channel.id)
|
||||
|
||||
invite_link = await self.telegram_bot.create_chat_invite_link(project.channel.telegram_id, requires_approval)
|
||||
|
||||
placement = domain.Placement(
|
||||
target_channel_id=input.target_channel_id,
|
||||
external_channel_id=input.external_channel_id,
|
||||
project_id=input.project_id,
|
||||
placement_channel_id=input.placement_channel_id,
|
||||
creative_id=input.creative_id,
|
||||
workspace_id=workspace_id,
|
||||
placement_date=input.placement_date,
|
||||
@@ -51,10 +57,10 @@ async def create_placement(
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
target_channel_id=placement.target_channel_id,
|
||||
target_channel_title=target_channel.title,
|
||||
external_channel_id=placement.external_channel_id,
|
||||
external_channel_title=external_channel.title,
|
||||
project_id=placement.project_id,
|
||||
project_channel_title=project.channel.title,
|
||||
placement_channel_id=placement.placement_channel_id,
|
||||
placement_channel_title=placement_channel.title,
|
||||
creative_id=placement.creative_id,
|
||||
creative_name=creative.name,
|
||||
placement_date=placement.placement_date,
|
||||
|
||||
@@ -19,10 +19,10 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
target_channel_id=placement.target_channel_id,
|
||||
target_channel_title=placement.target_channel.title,
|
||||
external_channel_id=placement.external_channel_id,
|
||||
external_channel_title=placement.external_channel.title,
|
||||
project_id=placement.project_id,
|
||||
project_channel_title=placement.project.channel.title,
|
||||
placement_channel_id=placement.placement_channel_id,
|
||||
placement_channel_title=placement.placement_channel.title,
|
||||
creative_id=placement.creative_id,
|
||||
creative_name=placement.creative.name,
|
||||
placement_date=placement.placement_date,
|
||||
|
||||
@@ -11,8 +11,8 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
|
||||
placements = await self.database.get_workspace_placements(
|
||||
input.workspace_id,
|
||||
target_channel_id=input.target_channel_id,
|
||||
external_channel_id=input.external_channel_id,
|
||||
project_id=input.project_id,
|
||||
placement_channel_id=input.placement_channel_id,
|
||||
creative_id=input.creative_id,
|
||||
include_archived=input.include_archived,
|
||||
)
|
||||
@@ -21,10 +21,10 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
placements=[
|
||||
dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
target_channel_id=placement.target_channel_id,
|
||||
target_channel_title=placement.target_channel.title,
|
||||
external_channel_id=placement.external_channel_id,
|
||||
external_channel_title=placement.external_channel.title,
|
||||
project_id=placement.project_id,
|
||||
project_channel_title=placement.project.channel.title,
|
||||
placement_channel_id=placement.placement_channel_id,
|
||||
placement_channel_title=placement.placement_channel.title,
|
||||
creative_id=placement.creative_id,
|
||||
creative_name=placement.creative.name,
|
||||
placement_date=placement.placement_date,
|
||||
|
||||
@@ -37,10 +37,10 @@ async def update_placement(
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
target_channel_id=placement.target_channel_id,
|
||||
target_channel_title=placement.target_channel.title,
|
||||
external_channel_id=placement.external_channel_id,
|
||||
external_channel_title=placement.external_channel.title,
|
||||
project_id=placement.project_id,
|
||||
project_channel_title=placement.project.channel.title,
|
||||
placement_channel_id=placement.placement_channel_id,
|
||||
placement_channel_title=placement.placement_channel.title,
|
||||
creative_id=placement.creative_id,
|
||||
creative_name=placement.creative.name,
|
||||
placement_date=placement.placement_date,
|
||||
|
||||
26
src/usecase/project/disconnect_project_by_tg_id.py
Normal file
26
src/usecase/project/disconnect_project_by_tg_id.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def disconnect_project_by_tg_id(self: 'Usecase', input: dto.DisconnectProjectByTgIdInput) -> None:
|
||||
user = await self.database.get_user(telegram_id=input.user_telegram_id)
|
||||
if not user:
|
||||
log.warning(f'User with telegram_id {input.user_telegram_id} not found when disconnecting channel')
|
||||
return
|
||||
|
||||
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
||||
if not project:
|
||||
log.warning(f'Project channel {input.telegram_id} not found')
|
||||
raise domain.ProjectNotFound()
|
||||
|
||||
project.status = domain.ProjectStatus.ARCHIVED
|
||||
await self.database.update_project(project)
|
||||
|
||||
log.info('Project %s archived for channel %s', project.id, input.telegram_id)
|
||||
27
src/usecase/project/get_workspace_projects.py
Normal file
27
src/usecase/project/get_workspace_projects.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
|
||||
async def get_workspace_projects(
|
||||
self: 'Usecase', input: dto.GetWorkspaceProjectsInput
|
||||
) -> dto.GetWorkspaceProjectsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
projects = await self.database.get_workspace_projects(input.workspace_id)
|
||||
|
||||
return dto.GetWorkspaceProjectsOutput(
|
||||
projects=[
|
||||
dto.ProjectOutput(
|
||||
id=project.id,
|
||||
telegram_id=project.channel.telegram_id,
|
||||
title=project.channel.title,
|
||||
username=project.channel.username,
|
||||
status=project.status,
|
||||
)
|
||||
for project in projects
|
||||
]
|
||||
)
|
||||
@@ -11,9 +11,7 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def tg_add_target_chan_1(
|
||||
self: 'Usecase', input: dto.ConnectTargetChanInput
|
||||
) -> dto.ConnectTargetChanOutput | None:
|
||||
async def tg_add_project_1(self: 'Usecase', input: dto.ConnectProjectInput) -> dto.ProjectOutput | None:
|
||||
permissions = input.bot_permissions
|
||||
|
||||
if not permissions.is_admin:
|
||||
@@ -71,17 +69,37 @@ async def tg_add_target_chan_1(
|
||||
|
||||
if len(workspaces) == 1:
|
||||
workspace = workspaces[0]
|
||||
channel = domain.TargetChannel(
|
||||
telegram_id=input.telegram_id,
|
||||
title=input.title,
|
||||
username=input.username,
|
||||
workspace_id=workspace.id,
|
||||
status=domain.TargetChannelStatus.ACTIVE,
|
||||
)
|
||||
|
||||
await self.database.upsert_target_channel(channel)
|
||||
channel = await self.database.get_channel(workspace.id, telegram_id=input.telegram_id)
|
||||
if channel:
|
||||
channel.title = input.title
|
||||
channel.username = input.username
|
||||
await self.database.update_channel(channel)
|
||||
else:
|
||||
channel = domain.Channel(
|
||||
telegram_id=input.telegram_id,
|
||||
title=input.title,
|
||||
username=input.username,
|
||||
workspace_id=workspace.id,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
|
||||
project = await self.database.get_project(workspace.id, channel_id=channel.id)
|
||||
if project:
|
||||
project.status = domain.ProjectStatus.ACTIVE
|
||||
await self.database.update_project(project)
|
||||
else:
|
||||
project = domain.Project(
|
||||
workspace_id=workspace.id,
|
||||
channel_id=channel.id,
|
||||
status=domain.ProjectStatus.ACTIVE,
|
||||
)
|
||||
await self.database.create_project(project)
|
||||
|
||||
await self.ensure_active_purchase_plan(project)
|
||||
|
||||
log.info(
|
||||
'Channel %s connected/updated successfully in workspace %s by user %s',
|
||||
'Project for channel %s connected/updated successfully in workspace %s by user %s',
|
||||
input.telegram_id,
|
||||
workspace.id,
|
||||
input.user_telegram_id,
|
||||
@@ -91,17 +109,16 @@ async def tg_add_target_chan_1(
|
||||
f'✅ Канал "{input.title}" добавлен в рабочее пространство "{workspace.name}".', input.user_telegram_id
|
||||
)
|
||||
|
||||
return dto.ConnectTargetChanOutput(
|
||||
id=channel.id,
|
||||
return dto.ProjectOutput(
|
||||
id=project.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
status=channel.status,
|
||||
is_active=channel.status == domain.TargetChannelStatus.ACTIVE,
|
||||
status=project.status,
|
||||
)
|
||||
|
||||
current_state = await self.database.get_telegram_state(user.telegram_id)
|
||||
if current_state and current_state.state == domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE:
|
||||
if current_state and current_state.state == domain.TelegramStateEnum.PROJECT_WAITING_WORKSPACE:
|
||||
await self.telegram_bot.send_message(
|
||||
'⚠️ Сначала завершите выбор рабочего пространства для предыдущего канала.', user.telegram_id
|
||||
)
|
||||
@@ -122,20 +139,17 @@ async def tg_add_target_chan_1(
|
||||
|
||||
await self.database.set_telegram_state(
|
||||
user.telegram_id,
|
||||
domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE,
|
||||
domain.TelegramStateEnum.PROJECT_WAITING_WORKSPACE,
|
||||
context,
|
||||
)
|
||||
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text=workspace.name, callback_data=f'tg_add_target_chan_2:{workspace.id}')]
|
||||
[InlineKeyboardButton(text=workspace.name, callback_data=f'tg_add_project_2:{workspace.id}')]
|
||||
for workspace in workspaces
|
||||
]
|
||||
|
||||
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||
(
|
||||
'✨ Подключение канала\n\n'
|
||||
f'Выберите рабочее пространство, в которое добавить канал "{input.title}":'
|
||||
),
|
||||
(f'✨ Подключение канала\n\nВыберите рабочее пространство, в которое добавить канал "{input.title}":'),
|
||||
user.telegram_id,
|
||||
buttons,
|
||||
)
|
||||
@@ -10,12 +10,12 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def tg_add_target_chan_2(
|
||||
async def tg_add_project_2(
|
||||
self: 'Usecase', telegram_id: int, chat_id: int, callback_data: str, message_id: int
|
||||
) -> None:
|
||||
state = await self.database.get_telegram_state(telegram_id)
|
||||
if not state or state.state != domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE:
|
||||
log.debug('User %s has no active target channel workspace selection flow', telegram_id)
|
||||
if not state or state.state != domain.TelegramStateEnum.PROJECT_WAITING_WORKSPACE:
|
||||
log.debug('User %s has no active project workspace selection flow', telegram_id)
|
||||
return
|
||||
|
||||
context = state.context or {}
|
||||
@@ -28,8 +28,8 @@ async def tg_add_target_chan_2(
|
||||
except ValueError:
|
||||
previous_state = None
|
||||
|
||||
if not callback_data.startswith('tg_add_target_chan_2:'):
|
||||
log.warning('Invalid target channel workspace callback data: %s', callback_data)
|
||||
if not callback_data.startswith('tg_add_project_2:'):
|
||||
log.warning('Invalid project workspace callback data: %s', callback_data)
|
||||
return
|
||||
|
||||
workspace_id_str = callback_data.split(':', 1)[1]
|
||||
@@ -44,9 +44,9 @@ async def tg_add_target_chan_2(
|
||||
await self.database.clear_telegram_state(telegram_id)
|
||||
return
|
||||
|
||||
channel_data = state.context.get('channel_data') if state.context else None
|
||||
channel_data = context.get('channel_data')
|
||||
if not channel_data:
|
||||
log.error('Missing channel data for user %s in target channel flow', telegram_id)
|
||||
log.error('Missing channel data for user %s in project flow', telegram_id)
|
||||
await self.telegram_bot.send_message('❌ Не удалось подключить канал. Попробуйте снова.', chat_id)
|
||||
if previous_state:
|
||||
await self.database.set_telegram_state(telegram_id, previous_state, previous_context)
|
||||
@@ -65,15 +65,31 @@ async def tg_add_target_chan_2(
|
||||
await self.telegram_bot.send_message('❌ У вас нет доступа к выбранному рабочему пространству.', chat_id)
|
||||
return
|
||||
|
||||
channel = domain.TargetChannel(
|
||||
telegram_id=int(channel_data['telegram_id']),
|
||||
title=str(channel_data['title']),
|
||||
username=channel_data.get('username'),
|
||||
workspace_id=workspace.id,
|
||||
status=domain.TargetChannelStatus.ACTIVE,
|
||||
)
|
||||
telegram_channel_id = int(channel_data['telegram_id'])
|
||||
channel = await self.database.get_channel(workspace.id, telegram_id=telegram_channel_id)
|
||||
if channel:
|
||||
channel.title = str(channel_data['title'])
|
||||
channel.username = channel_data.get('username')
|
||||
await self.database.update_channel(channel)
|
||||
else:
|
||||
channel = domain.Channel(
|
||||
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)
|
||||
|
||||
project = await self.database.get_project(workspace.id, channel_id=channel.id)
|
||||
if project:
|
||||
project.status = domain.ProjectStatus.ACTIVE
|
||||
await self.database.update_project(project)
|
||||
else:
|
||||
project = domain.Project(workspace_id=workspace.id, channel_id=channel.id, status=domain.ProjectStatus.ACTIVE)
|
||||
await self.database.create_project(project)
|
||||
|
||||
await self.ensure_active_purchase_plan(project)
|
||||
|
||||
await self.database.upsert_target_channel(channel)
|
||||
log.info(
|
||||
'Channel %s connected/updated successfully in workspace %s by user %s',
|
||||
channel.telegram_id,
|
||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def update_target_chan_permissions(self: 'Usecase', input: dto.UpdateTargetChanPermissionsInput) -> None:
|
||||
async def update_project_permissions(self: 'Usecase', input: dto.UpdateProjectPermissionsInput) -> None:
|
||||
missing_permissions = []
|
||||
if not input.permissions.can_invite_users:
|
||||
missing_permissions.append('Создание инвайт-ссылок')
|
||||
@@ -22,31 +22,37 @@ async def update_target_chan_permissions(self: 'Usecase', input: dto.UpdateTarge
|
||||
log.warning(f'User with telegram_id {input.user_telegram_id} not found when updating channel permissions')
|
||||
return
|
||||
|
||||
channel = await self.database.get_target_channel_for_user_by_telegram(user.id, input.telegram_id)
|
||||
if not channel:
|
||||
log.warning(f'Target channel {input.telegram_id} not found when permissions changed')
|
||||
raise domain.TargetChannelNotFound()
|
||||
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
||||
if not project:
|
||||
log.warning(f'Project channel {input.telegram_id} not found when permissions changed')
|
||||
raise domain.ProjectNotFound()
|
||||
|
||||
if not missing_permissions:
|
||||
if channel.status != domain.TargetChannelStatus.ACTIVE:
|
||||
channel.status = domain.TargetChannelStatus.ACTIVE
|
||||
await self.database.update_target_channel(channel)
|
||||
if project.status != domain.ProjectStatus.ACTIVE:
|
||||
project.status = domain.ProjectStatus.ACTIVE
|
||||
await self.database.update_project(project)
|
||||
|
||||
await self.telegram_bot.send_message(
|
||||
f'✅ Канал "{input.chat_title}" был активирован.\n\nВсе необходимые права боту предоставлены!',
|
||||
input.user_telegram_id,
|
||||
)
|
||||
log.info(f'Target channel {input.telegram_id} reactivated - all permissions granted')
|
||||
log.info(f'Project channel {input.telegram_id} reactivated - all permissions granted')
|
||||
return
|
||||
|
||||
if channel.status == domain.TargetChannelStatus.ACTIVE:
|
||||
channel.status = domain.TargetChannelStatus.INACTIVE
|
||||
await self.database.update_target_channel(channel)
|
||||
if project.status == domain.ProjectStatus.ACTIVE:
|
||||
project.status = domain.ProjectStatus.INACTIVE
|
||||
await self.database.update_project(project)
|
||||
|
||||
missed_permissions = '\n'.join(f'• {p}' for p in missing_permissions)
|
||||
await self.telegram_bot.send_message(
|
||||
f'⚠️ Канал "{input.chat_title}" был деактивирован.\
|
||||
\n\nБоту убрали необходимые права:\n{missed_permissions}',
|
||||
(
|
||||
f'⚠️ Канал "{input.chat_title}" был деактивирован.\n\n'
|
||||
f'Боту убрали необходимые права:\n{missed_permissions}'
|
||||
),
|
||||
input.user_telegram_id,
|
||||
)
|
||||
log.warning(f'Target channel {input.telegram_id} deactivated due to missing permissions: {missing_permissions}')
|
||||
log.warning(
|
||||
'Project channel %s deactivated due to missing permissions: %s',
|
||||
input.telegram_id,
|
||||
missing_permissions,
|
||||
)
|
||||
67
src/usecase/purchase_plan/attach_channel_to_purchase_plan.py
Normal file
67
src/usecase/purchase_plan/attach_channel_to_purchase_plan.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def attach_channel_to_purchase_plan(
|
||||
self: 'Usecase',
|
||||
project_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
input: dto.AttachChannelToPurchasePlanInput,
|
||||
) -> dto.PurchasePlanChannelOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
# Поиск канала по username
|
||||
channel = await self.database.get_channel(workspace_id, 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,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
log.info('Created unverified channel with username %s', input.username)
|
||||
else:
|
||||
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,
|
||||
channel.id,
|
||||
status=input.status,
|
||||
planned_cost=input.planned_cost,
|
||||
comment=input.comment,
|
||||
)
|
||||
|
||||
log.info('Channel %s attached to purchase plan %s (project %s)', channel.id, plan.id, project.id)
|
||||
|
||||
return dto.PurchasePlanChannelOutput(
|
||||
id=plan_channel.id,
|
||||
status=plan_channel.status,
|
||||
planned_cost=plan_channel.planned_cost,
|
||||
comment=plan_channel.comment,
|
||||
channel=dto.ChannelOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
),
|
||||
)
|
||||
43
src/usecase/purchase_plan/get_purchase_plan_channels.py
Normal file
43
src/usecase/purchase_plan/get_purchase_plan_channels.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_purchase_plan_channels(
|
||||
self: 'Usecase', input: dto.GetPurchasePlanChannelsInput
|
||||
) -> dto.GetPurchasePlanChannelsOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
project = await self.database.get_project(input.workspace_id, project_id=input.project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
plan_channels = await self.database.get_purchase_plan_channels(plan.id)
|
||||
log.debug('Fetched %s channels for purchase plan %s (project %s)', len(plan_channels), plan.id, project.id)
|
||||
|
||||
return dto.GetPurchasePlanChannelsOutput(
|
||||
channels=[
|
||||
dto.PurchasePlanChannelOutput(
|
||||
id=plan_channel.id,
|
||||
status=plan_channel.status,
|
||||
planned_cost=plan_channel.planned_cost,
|
||||
comment=plan_channel.comment,
|
||||
channel=dto.ChannelOutput(
|
||||
id=plan_channel.channel.id,
|
||||
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
|
||||
]
|
||||
)
|
||||
33
src/usecase/purchase_plan/remove_purchase_plan_channel.py
Normal file
33
src/usecase/purchase_plan/remove_purchase_plan_channel.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def remove_purchase_plan_channel(
|
||||
self: 'Usecase',
|
||||
channel_id: uuid.UUID,
|
||||
project_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
) -> None:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
plan_channel = await self.database.get_purchase_plan_channel(channel_id, plan.id)
|
||||
if not plan_channel:
|
||||
raise domain.PurchasePlanChannelNotFound(channel_id)
|
||||
|
||||
await self.database.remove_channel_from_purchase_plan(plan.id, plan_channel.channel_id)
|
||||
log.info('Removed channel %s from purchase plan %s (project %s)', plan_channel.channel_id, plan.id, project.id)
|
||||
71
src/usecase/purchase_plan/update_purchase_plan_channel.py
Normal file
71
src/usecase/purchase_plan/update_purchase_plan_channel.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def update_purchase_plan_channel(
|
||||
self: 'Usecase',
|
||||
channel_id: uuid.UUID,
|
||||
project_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
input: dto.UpdatePurchasePlanChannelInput,
|
||||
) -> dto.PurchasePlanChannelOutput:
|
||||
await self.ensure_workspace_access(workspace_id, user_id)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
plan_channel = await self.database.get_purchase_plan_channel(channel_id, plan.id)
|
||||
if not plan_channel:
|
||||
raise domain.PurchasePlanChannelNotFound(channel_id)
|
||||
|
||||
updated_fields: dict[str, object | None] = {}
|
||||
if input.status is not None:
|
||||
plan_channel.status = input.status
|
||||
updated_fields['status'] = input.status
|
||||
if input.planned_cost is not None:
|
||||
plan_channel.planned_cost = input.planned_cost
|
||||
updated_fields['planned_cost'] = input.planned_cost
|
||||
if input.comment is not None:
|
||||
plan_channel.comment = input.comment
|
||||
updated_fields['comment'] = input.comment
|
||||
|
||||
if updated_fields:
|
||||
await self.database.update_purchase_plan_channel(plan_channel)
|
||||
log.info(
|
||||
'Updated purchase plan channel %s for project %s fields %s',
|
||||
channel_id,
|
||||
project.id,
|
||||
', '.join(updated_fields.keys()),
|
||||
)
|
||||
|
||||
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)
|
||||
if channel is None:
|
||||
raise domain.ChannelNotFound(plan_channel.channel_id)
|
||||
|
||||
return dto.PurchasePlanChannelOutput(
|
||||
id=plan_channel.id,
|
||||
status=plan_channel.status,
|
||||
planned_cost=plan_channel.planned_cost,
|
||||
comment=plan_channel.comment,
|
||||
channel=dto.ChannelOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
verification_status=channel.verification_status,
|
||||
),
|
||||
)
|
||||
@@ -22,16 +22,23 @@ async def handle_subscription(
|
||||
log.warning('Placement not found for invite_link: %s', invite_link)
|
||||
return
|
||||
|
||||
subscriber = domain.Subscriber(
|
||||
telegram_id=user_telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
)
|
||||
await self.database.upsert_subscriber(subscriber)
|
||||
subscriber = await self.database.get_user(telegram_id=user_telegram_id)
|
||||
if not subscriber:
|
||||
subscriber = domain.User(
|
||||
telegram_id=user_telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
)
|
||||
await self.database.create_user(subscriber)
|
||||
else:
|
||||
subscriber.username = username or subscriber.username
|
||||
subscriber.first_name = first_name or subscriber.first_name
|
||||
subscriber.last_name = last_name or subscriber.last_name
|
||||
await self.database.update_user(subscriber)
|
||||
|
||||
active_subscription = await self.database.get_active_subscription_by_subscriber_and_channel(
|
||||
subscriber.id, placement.target_channel.telegram_id
|
||||
active_subscription = await self.database.get_active_subscription_by_subscriber_and_project(
|
||||
subscriber.id, placement.project_id
|
||||
)
|
||||
|
||||
if active_subscription:
|
||||
@@ -43,7 +50,7 @@ async def handle_subscription(
|
||||
'ignoring new subscription attempt via placement %s',
|
||||
subscriber.id,
|
||||
user_telegram_id,
|
||||
placement.target_channel_id,
|
||||
placement.project_id,
|
||||
active_subscription.placement_id,
|
||||
placement.id,
|
||||
)
|
||||
@@ -72,7 +79,6 @@ async def handle_subscription(
|
||||
subscription = domain.Subscription(
|
||||
placement_id=placement.id,
|
||||
subscriber_id=subscriber.id,
|
||||
target_channel_id=placement.target_channel_id,
|
||||
invite_link=invite_link,
|
||||
)
|
||||
async with self.database.transaction():
|
||||
|
||||
@@ -12,14 +12,17 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_unsubscription(self: 'Usecase', user_telegram_id: int, channel_telegram_id: int) -> None:
|
||||
subscriber = await self.database.get_subscriber(user_telegram_id)
|
||||
subscriber = await self.database.get_user(telegram_id=user_telegram_id)
|
||||
if not subscriber:
|
||||
log.warning('Subscriber not found for telegram_id: %s', user_telegram_id)
|
||||
return
|
||||
|
||||
subscriptions = await self.database.get_active_subscriptions_by_subscriber_and_channel(
|
||||
subscriber.id, channel_telegram_id
|
||||
)
|
||||
project = await self.database.get_project_by_channel_telegram(channel_telegram_id)
|
||||
if not project:
|
||||
log.warning('Project not found for channel telegram_id: %s', channel_telegram_id)
|
||||
return
|
||||
|
||||
subscriptions = await self.database.get_active_subscriptions_by_subscriber_and_project(subscriber.id, project.id)
|
||||
|
||||
if not subscriptions:
|
||||
log.info(
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def disconnect_target_chan_by_tg_id(self: 'Usecase', input: dto.DisconnectTargetChanByTgIdInput) -> None:
|
||||
user = await self.database.get_user(telegram_id=input.user_telegram_id)
|
||||
if not user:
|
||||
log.warning(f'User with telegram_id {input.user_telegram_id} not found when disconnecting channel')
|
||||
return
|
||||
|
||||
channel = await self.database.get_target_channel_for_user_by_telegram(user.id, input.telegram_id)
|
||||
if not channel:
|
||||
log.warning(f'Target channel {input.telegram_id} not found')
|
||||
raise domain.TargetChannelNotFound()
|
||||
|
||||
channel.status = domain.TargetChannelStatus.INACTIVE
|
||||
await self.database.update_target_channel(channel)
|
||||
|
||||
log.info(f'Target channel {input.telegram_id} deactivated')
|
||||
@@ -1,26 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import Usecase
|
||||
|
||||
|
||||
async def get_user_target_chans(self: 'Usecase', input: dto.GetUserTargetChansInput) -> dto.GetUserTargetChansOutput:
|
||||
await self.ensure_workspace_access(input.workspace_id, input.user_id)
|
||||
|
||||
channels = await self.database.get_workspace_target_channels(input.workspace_id)
|
||||
|
||||
return dto.GetUserTargetChansOutput(
|
||||
target_channels=[
|
||||
dto.ConnectTargetChanOutput(
|
||||
id=channel.id,
|
||||
telegram_id=channel.telegram_id,
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
status=channel.status,
|
||||
is_active=channel.status == domain.TargetChannelStatus.ACTIVE,
|
||||
)
|
||||
for channel in channels
|
||||
]
|
||||
)
|
||||
Reference in New Issue
Block a user