refactor, удалены интерфейс БД из-за слабой необходимости
This commit is contained in:
@@ -83,9 +83,27 @@ async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
op.creative_id, op.purchase_channel_id, op.post_id, op.project_id
|
||||
FROM "old_placement" op;
|
||||
|
||||
-- Шаг 6: Обновить subscription (placement_id -> publication_id)
|
||||
-- Шаг 6: Обновить foreign keys для таблиц, ссылающихся на old_placement
|
||||
|
||||
-- 6.1: Обновить subscription (placement_id -> publication_id)
|
||||
-- Удалить constraint ДО переименования колонки
|
||||
ALTER TABLE "subscription"
|
||||
DROP CONSTRAINT IF EXISTS "subscription_placement_id_fkey";
|
||||
|
||||
-- Переименовать колонку
|
||||
ALTER TABLE "subscription" RENAME COLUMN "placement_id" TO "publication_id";
|
||||
|
||||
-- 6.2: Обновить workspace_user_permission_scope.placement_id
|
||||
-- Удалить старый foreign key constraint ДО обновления данных
|
||||
ALTER TABLE "workspace_user_permission_scope"
|
||||
DROP CONSTRAINT IF EXISTS "workspace_user_permission_scope_placement_id_fkey";
|
||||
|
||||
-- Обновить данные: старые placement_id -> новые placement_id (через purchase_channel_id)
|
||||
UPDATE "workspace_user_permission_scope" wups
|
||||
SET placement_id = op.purchase_channel_id
|
||||
FROM "old_placement" op
|
||||
WHERE wups.placement_id = op.id;
|
||||
|
||||
-- Шаг 7: Создать индексы и constraints
|
||||
CREATE INDEX "idx_placement_project_4155ce"
|
||||
ON "placement" ("project_id", "status");
|
||||
@@ -108,6 +126,11 @@ async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
CREATE UNIQUE INDEX "uid_subscriptio_publica_578bfb"
|
||||
ON "subscription" ("publication_id", "telegram_user_id");
|
||||
|
||||
ALTER TABLE "workspace_user_permission_scope"
|
||||
ADD CONSTRAINT "fk_workspac_placemen_8c5d3f2a"
|
||||
FOREIGN KEY ("placement_id")
|
||||
REFERENCES "placement" ("id") ON DELETE CASCADE;
|
||||
|
||||
-- Шаг 8: Добавить комментарии
|
||||
COMMENT ON TABLE "placement"
|
||||
IS 'Размещение, управляемое пользователем (бывший PurchaseChannel + Purchase)';
|
||||
@@ -262,10 +285,28 @@ async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
pub.creative_id, pub.placement_id, pub.post_id, pub.project_id
|
||||
FROM "publication" pub;
|
||||
|
||||
-- Шаг 8: Обновить subscription (publication_id -> placement_id)
|
||||
-- Шаг 8: Обновить foreign keys обратно
|
||||
|
||||
-- 8.1: Обновить subscription (publication_id -> placement_id)
|
||||
-- Удалить новый foreign key constraint
|
||||
ALTER TABLE "subscription"
|
||||
DROP CONSTRAINT IF EXISTS "fk_subscrip_publicat_1959be9e";
|
||||
|
||||
-- Переименовать колонку обратно
|
||||
ALTER TABLE "subscription" RENAME COLUMN "publication_id" TO "placement_id";
|
||||
|
||||
-- Шаг 9: Создать индексы
|
||||
-- 8.2: Обновить workspace_user_permission_scope.placement_id обратно
|
||||
-- Удалить constraint на новый placement
|
||||
ALTER TABLE "workspace_user_permission_scope"
|
||||
DROP CONSTRAINT IF EXISTS "fk_workspac_placemen_8c5d3f2a";
|
||||
|
||||
-- Обновить данные обратно: новые placement_id -> старые placement_id
|
||||
UPDATE "workspace_user_permission_scope" wups
|
||||
SET placement_id = pub.id
|
||||
FROM "publication" pub
|
||||
WHERE wups.placement_id = pub.placement_id;
|
||||
|
||||
-- Шаг 9: Создать индексы и constraints
|
||||
CREATE INDEX "idx_purchase_project_status" ON "purchase" ("project_id", "status");
|
||||
CREATE INDEX "idx_placement_purchas_61f9e6"
|
||||
ON "placement" ("purchase_channel_id");
|
||||
@@ -279,6 +320,11 @@ async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
CREATE UNIQUE INDEX "uid_subscriptio_placeme_75b40b"
|
||||
ON "subscription" ("placement_id", "telegram_user_id");
|
||||
|
||||
ALTER TABLE "workspace_user_permission_scope"
|
||||
ADD CONSTRAINT "fk_workspac_placemen_8c5d3f2a"
|
||||
FOREIGN KEY ("placement_id")
|
||||
REFERENCES "placement" ("id") ON DELETE CASCADE;
|
||||
|
||||
-- Шаг 10: Удалить новые таблицы
|
||||
DROP TABLE IF EXISTS "publication";
|
||||
DROP TABLE IF EXISTS "new_placement";
|
||||
|
||||
@@ -8,25 +8,22 @@ from tortoise.transactions import in_transaction
|
||||
|
||||
from shared.datebase_base import DatabaseBase
|
||||
from src import domain
|
||||
from src.usecase import Database
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Postgres(DatabaseBase, Database):
|
||||
def transaction(self) -> typing.AsyncContextManager[None]:
|
||||
class Postgres(DatabaseBase):
|
||||
@staticmethod
|
||||
def transaction() -> typing.AsyncContextManager[None]:
|
||||
return in_transaction()
|
||||
|
||||
async def create_user(self, user: domain.User) -> None:
|
||||
await user.save()
|
||||
|
||||
async def update_user(self, user: domain.User) -> None:
|
||||
@staticmethod
|
||||
async def create_user(user: domain.User) -> None:
|
||||
await user.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_telegram_user(
|
||||
self,
|
||||
telegram_user_id: uuid.UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
telegram_user_id: uuid.UUID | None = None, telegram_id: int | None = None
|
||||
) -> domain.TelegramUser | None:
|
||||
if telegram_user_id:
|
||||
return await domain.TelegramUser.get_or_none(id=telegram_user_id)
|
||||
@@ -35,32 +32,40 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
raise ValueError('Either telegram_user_id or telegram_id must be provided')
|
||||
|
||||
async def create_telegram_user(self, telegram_user: domain.TelegramUser) -> None:
|
||||
@staticmethod
|
||||
async def create_telegram_user(telegram_user: domain.TelegramUser) -> None:
|
||||
await telegram_user.save()
|
||||
|
||||
async def update_telegram_user(self, telegram_user: domain.TelegramUser) -> None:
|
||||
@staticmethod
|
||||
async def update_telegram_user(telegram_user: domain.TelegramUser) -> None:
|
||||
await telegram_user.save()
|
||||
|
||||
async def create_workspace(self, workspace: domain.Workspace) -> None:
|
||||
@staticmethod
|
||||
async def create_workspace(workspace: domain.Workspace) -> None:
|
||||
await workspace.save()
|
||||
|
||||
async def update_workspace(self, workspace: domain.Workspace) -> None:
|
||||
@staticmethod
|
||||
async def update_workspace(workspace: domain.Workspace) -> None:
|
||||
await workspace.save()
|
||||
|
||||
async def delete_workspace(self, workspace_id: uuid.UUID) -> None:
|
||||
@staticmethod
|
||||
async def delete_workspace(workspace_id: uuid.UUID) -> None:
|
||||
await domain.Workspace.filter(id=workspace_id).delete()
|
||||
|
||||
async def add_user_to_workspace(self, workspace_id: uuid.UUID, user_id: uuid.UUID) -> domain.WorkspaceUser:
|
||||
@staticmethod
|
||||
async def add_user_to_workspace(workspace_id: uuid.UUID, user_id: uuid.UUID) -> domain.WorkspaceUser:
|
||||
return await domain.WorkspaceUser.create(
|
||||
workspace_id=workspace_id,
|
||||
user_id=user_id,
|
||||
status=domain.WorkspaceUserStatus.ACTIVE,
|
||||
)
|
||||
|
||||
async def update_workspace_user(self, workspace_user: domain.WorkspaceUser) -> None:
|
||||
@staticmethod
|
||||
async def update_workspace_user(workspace_user: domain.WorkspaceUser) -> None:
|
||||
await workspace_user.save()
|
||||
|
||||
async def get_user_workspaces(self, user_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
||||
@staticmethod
|
||||
async def get_user_workspaces(user_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
||||
return (
|
||||
await domain.WorkspaceUser.filter(user_id=user_id)
|
||||
.prefetch_related('workspace')
|
||||
@@ -68,10 +73,12 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
async def get_workspace(self, workspace_id: uuid.UUID) -> domain.Workspace | None:
|
||||
@staticmethod
|
||||
async def get_workspace(workspace_id: uuid.UUID) -> domain.Workspace | None:
|
||||
return await domain.Workspace.get_or_none(id=workspace_id)
|
||||
|
||||
async def get_workspace_for_user(self, workspace_id: uuid.UUID, user_id: uuid.UUID) -> domain.Workspace | None:
|
||||
@staticmethod
|
||||
async def get_workspace_for_user(workspace_id: uuid.UUID, user_id: uuid.UUID) -> domain.Workspace | None:
|
||||
membership = (
|
||||
await domain.WorkspaceUser.filter(workspace_id=workspace_id, user_id=user_id)
|
||||
.prefetch_related('workspace')
|
||||
@@ -79,7 +86,8 @@ class Postgres(DatabaseBase, Database):
|
||||
)
|
||||
return membership.workspace if membership else None
|
||||
|
||||
async def get_default_workspace_for_user(self, user_id: uuid.UUID) -> domain.Workspace | None:
|
||||
@staticmethod
|
||||
async def get_default_workspace_for_user(user_id: uuid.UUID) -> domain.Workspace | None:
|
||||
membership = (
|
||||
await domain.WorkspaceUser.filter(user_id=user_id)
|
||||
.prefetch_related('workspace')
|
||||
@@ -88,16 +96,16 @@ class Postgres(DatabaseBase, Database):
|
||||
)
|
||||
return membership.workspace if membership else None
|
||||
|
||||
async def get_workspace_membership(
|
||||
self, workspace_id: uuid.UUID, user_id: uuid.UUID
|
||||
) -> domain.WorkspaceUser | None:
|
||||
@staticmethod
|
||||
async def get_workspace_membership(workspace_id: uuid.UUID, user_id: uuid.UUID) -> domain.WorkspaceUser | None:
|
||||
return (
|
||||
await domain.WorkspaceUser.filter(workspace_id=workspace_id, user_id=user_id)
|
||||
.prefetch_related('workspace', 'user', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||
.first()
|
||||
)
|
||||
|
||||
async def get_workspace_members(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
||||
@staticmethod
|
||||
async def get_workspace_members(workspace_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
||||
return (
|
||||
await domain.WorkspaceUser.filter(workspace_id=workspace_id)
|
||||
.prefetch_related('workspace', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||
@@ -105,32 +113,38 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
async def get_workspace_member(self, workspace_user_id: uuid.UUID) -> domain.WorkspaceUser | None:
|
||||
@staticmethod
|
||||
async def get_workspace_member(workspace_user_id: uuid.UUID) -> domain.WorkspaceUser | None:
|
||||
return (
|
||||
await domain.WorkspaceUser.filter(id=workspace_user_id)
|
||||
.prefetch_related('workspace', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||
.first()
|
||||
)
|
||||
|
||||
async def create_workspace_invite(self, invite: domain.WorkspaceInvite) -> None:
|
||||
@staticmethod
|
||||
async def create_workspace_invite(invite: domain.WorkspaceInvite) -> None:
|
||||
await invite.save()
|
||||
|
||||
async def update_workspace_invite(self, invite: domain.WorkspaceInvite) -> None:
|
||||
@staticmethod
|
||||
async def update_workspace_invite(invite: domain.WorkspaceInvite) -> None:
|
||||
await invite.save()
|
||||
|
||||
async def get_workspace_invite(self, invite_id: uuid.UUID) -> domain.WorkspaceInvite | None:
|
||||
@staticmethod
|
||||
async def get_workspace_invite(invite_id: uuid.UUID) -> domain.WorkspaceInvite | None:
|
||||
return (
|
||||
await domain.WorkspaceInvite.filter(id=invite_id)
|
||||
.prefetch_related('workspace', 'user__telegram_user', 'invited_by__telegram_user')
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def get_workspace_invite_by_user(
|
||||
self, workspace_id: uuid.UUID, user_id: uuid.UUID
|
||||
workspace_id: uuid.UUID, user_id: uuid.UUID
|
||||
) -> domain.WorkspaceInvite | None:
|
||||
return await domain.WorkspaceInvite.get_or_none(workspace_id=workspace_id, user_id=user_id)
|
||||
|
||||
async def get_workspace_invites(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceInvite]:
|
||||
@staticmethod
|
||||
async def get_workspace_invites(workspace_id: uuid.UUID) -> list[domain.WorkspaceInvite]:
|
||||
return (
|
||||
await domain.WorkspaceInvite.filter(workspace_id=workspace_id)
|
||||
.prefetch_related('user__telegram_user', 'invited_by__telegram_user')
|
||||
@@ -138,8 +152,8 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def set_workspace_user_permissions(
|
||||
self,
|
||||
workspace_user_id: uuid.UUID,
|
||||
global_permissions: set[domain.PermissionKey],
|
||||
scoped_permissions: list[tuple[domain.PermissionKey, domain.PermissionScopeType, uuid.UUID]],
|
||||
@@ -175,17 +189,18 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
await domain.WorkspaceUserPermissionScope.bulk_create(scope_objects)
|
||||
|
||||
async def create_login_token(self, login_token: domain.LoginToken) -> None:
|
||||
@staticmethod
|
||||
async def create_login_token(login_token: domain.LoginToken) -> None:
|
||||
await login_token.save()
|
||||
|
||||
async def create_creative(self, creative: domain.Creative) -> None:
|
||||
@staticmethod
|
||||
async def create_creative(creative: domain.Creative) -> None:
|
||||
await creative.save()
|
||||
|
||||
async def get_user(self, user_id: uuid.UUID | None = None, telegram_id: int | None = None) -> domain.User | None:
|
||||
@staticmethod
|
||||
async def get_user(user_id: uuid.UUID | None = None, telegram_id: int | None = None) -> domain.User | None:
|
||||
if user_id:
|
||||
return (
|
||||
await domain.User.filter(id=user_id).prefetch_related('telegram_user').first()
|
||||
)
|
||||
return await domain.User.filter(id=user_id).prefetch_related('telegram_user').first()
|
||||
elif telegram_id:
|
||||
return (
|
||||
await domain.User.filter(telegram_user__telegram_id=telegram_id)
|
||||
@@ -195,27 +210,26 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
raise ValueError('Either user_id or telegram_id must be provided')
|
||||
|
||||
async def get_user_by_username(self, username: str) -> domain.User | None:
|
||||
@staticmethod
|
||||
async def get_user_by_username(username: str) -> domain.User | None:
|
||||
return (
|
||||
await domain.User.filter(telegram_user__username__iexact=username)
|
||||
.prefetch_related('telegram_user')
|
||||
.first()
|
||||
await domain.User.filter(telegram_user__username__iexact=username).prefetch_related('telegram_user').first()
|
||||
)
|
||||
|
||||
async def get_login_token(self, token: str) -> domain.LoginToken | None:
|
||||
@staticmethod
|
||||
async def get_login_token(token: str) -> domain.LoginToken | None:
|
||||
return await domain.LoginToken.get_or_none(token=token)
|
||||
|
||||
async def mark_token_as_used(self, token: str) -> None:
|
||||
@staticmethod
|
||||
async def mark_token_as_used(token: str) -> None:
|
||||
updated = await domain.LoginToken.filter(token=token, used_at__isnull=True).update(used_at=timezone.now())
|
||||
|
||||
if updated == 0:
|
||||
raise domain.LoginTokenAlreadyUsed() # либо нет токена, либо он уже использован
|
||||
|
||||
@staticmethod
|
||||
async def get_channel(
|
||||
self,
|
||||
channel_id: uuid.UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
username: str | None = None,
|
||||
channel_id: uuid.UUID | None = None, telegram_id: int | None = None, username: str | None = None
|
||||
) -> domain.Channel | None:
|
||||
if channel_id:
|
||||
return await domain.Channel.get_or_none(id=channel_id)
|
||||
@@ -225,16 +239,16 @@ class Postgres(DatabaseBase, Database):
|
||||
return await domain.Channel.filter(username__iexact=username).first()
|
||||
return None
|
||||
|
||||
async def create_channel(self, channel: domain.Channel) -> None:
|
||||
@staticmethod
|
||||
async def create_channel(channel: domain.Channel) -> None:
|
||||
await channel.save()
|
||||
|
||||
async def update_channel(self, channel: domain.Channel) -> None:
|
||||
@staticmethod
|
||||
async def update_channel(channel: domain.Channel) -> None:
|
||||
await channel.save()
|
||||
|
||||
async def delete_channel(self, channel_id: uuid.UUID) -> None:
|
||||
await domain.Channel.filter(id=channel_id).delete()
|
||||
|
||||
async def search_channels(self, username_query: str | None = None) -> list[domain.Channel]:
|
||||
@staticmethod
|
||||
async def search_channels(username_query: str | None = None) -> list[domain.Channel]:
|
||||
query = domain.Channel.all()
|
||||
|
||||
if username_query:
|
||||
@@ -243,8 +257,9 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
return await query.all()
|
||||
|
||||
@staticmethod
|
||||
async def get_project(
|
||||
self, workspace_id: uuid.UUID, project_id: uuid.UUID | None = None, channel_id: uuid.UUID | None = None
|
||||
workspace_id: uuid.UUID, project_id: uuid.UUID | None = None, channel_id: uuid.UUID | None = None
|
||||
) -> domain.Project | None:
|
||||
query = domain.Project.filter(workspace_id=workspace_id, deleted_at__isnull=True).prefetch_related('channel')
|
||||
if project_id:
|
||||
@@ -253,9 +268,8 @@ class Postgres(DatabaseBase, Database):
|
||||
query = query.filter(channel_id=channel_id)
|
||||
return await query.first()
|
||||
|
||||
async def get_project_for_user_by_telegram(
|
||||
self, user_id: uuid.UUID, channel_telegram_id: int
|
||||
) -> domain.Project | None:
|
||||
@staticmethod
|
||||
async def get_project_for_user_by_telegram(user_id: uuid.UUID, channel_telegram_id: int) -> domain.Project | None:
|
||||
return (
|
||||
await domain.Project.filter(
|
||||
channel__telegram_id=channel_telegram_id, workspace__workspace_users__user_id=user_id
|
||||
@@ -264,17 +278,20 @@ class Postgres(DatabaseBase, Database):
|
||||
.first()
|
||||
)
|
||||
|
||||
async def get_project_by_channel_telegram(self, channel_telegram_id: int) -> domain.Project | None:
|
||||
@staticmethod
|
||||
async def get_project_by_channel_telegram(channel_telegram_id: int) -> domain.Project | None:
|
||||
return await domain.Project.filter(channel__telegram_id=channel_telegram_id).prefetch_related('channel').first()
|
||||
|
||||
async def create_project(self, project: domain.Project) -> None:
|
||||
@staticmethod
|
||||
async def create_project(project: domain.Project) -> None:
|
||||
await project.save()
|
||||
|
||||
async def update_project(self, project: domain.Project) -> None:
|
||||
@staticmethod
|
||||
async def update_project(project: domain.Project) -> None:
|
||||
await project.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_workspace_projects(
|
||||
self,
|
||||
workspace_id: uuid.UUID,
|
||||
allowed_project_ids: set[uuid.UUID] | None = None,
|
||||
include_archived: bool = False,
|
||||
@@ -291,14 +308,16 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
return await query.order_by('created_at').all()
|
||||
|
||||
async def archive_project(self, workspace_id: uuid.UUID, project_id: uuid.UUID) -> None:
|
||||
@staticmethod
|
||||
async def archive_project(workspace_id: uuid.UUID, project_id: uuid.UUID) -> None:
|
||||
project = await domain.Project.get_or_none(id=project_id, workspace_id=workspace_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound()
|
||||
project.status = domain.ProjectStatus.ARCHIVED
|
||||
await project.save()
|
||||
|
||||
async def delete_project(self, workspace_id: uuid.UUID, project_id: uuid.UUID) -> None:
|
||||
@staticmethod
|
||||
async def delete_project(workspace_id: uuid.UUID, project_id: uuid.UUID) -> None:
|
||||
project = await domain.Project.get_or_none(id=project_id, workspace_id=workspace_id)
|
||||
if not project:
|
||||
raise domain.ProjectNotFound()
|
||||
@@ -307,20 +326,23 @@ class Postgres(DatabaseBase, Database):
|
||||
project.deleted_at = datetime.datetime.now()
|
||||
await project.save()
|
||||
|
||||
async def check_channel_exists_in_workspace(self, channel_id: uuid.UUID, workspace_id: uuid.UUID) -> bool:
|
||||
@staticmethod
|
||||
async def check_channel_exists_in_workspace(channel_id: uuid.UUID, workspace_id: uuid.UUID) -> bool:
|
||||
project = await domain.Project.get_or_none(channel_id=channel_id, workspace_id=workspace_id)
|
||||
return project is not None
|
||||
|
||||
async def get_creative(self, workspace_id: uuid.UUID, creative_id: uuid.UUID) -> domain.Creative | None:
|
||||
return await domain.Creative.get_or_none(
|
||||
id=creative_id, project__workspace_id=workspace_id
|
||||
).prefetch_related('project', 'project__channel')
|
||||
@staticmethod
|
||||
async def get_creative(workspace_id: uuid.UUID, creative_id: uuid.UUID) -> domain.Creative | None:
|
||||
return await domain.Creative.get_or_none(id=creative_id, project__workspace_id=workspace_id).prefetch_related(
|
||||
'project', 'project__channel'
|
||||
)
|
||||
|
||||
async def update_creative(self, creative: domain.Creative) -> None:
|
||||
@staticmethod
|
||||
async def update_creative(creative: domain.Creative) -> None:
|
||||
await creative.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_workspace_creatives(
|
||||
self,
|
||||
workspace_id: uuid.UUID,
|
||||
project_id: uuid.UUID | None = None,
|
||||
include_archived: bool = False,
|
||||
@@ -340,24 +362,23 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
return await query.prefetch_related('project', 'project__channel').order_by('-created_at').all()
|
||||
|
||||
async def delete_creative(self, creative_id: uuid.UUID) -> None:
|
||||
@staticmethod
|
||||
async def delete_creative(creative_id: uuid.UUID) -> None:
|
||||
await domain.Creative.filter(id=creative_id).delete()
|
||||
|
||||
# Placement methods (user-managed, запланированные размещения)
|
||||
async def create_placement(self, placement: domain.Placement) -> None:
|
||||
@staticmethod
|
||||
async def create_placement(placement: domain.Placement) -> None:
|
||||
await placement.save()
|
||||
|
||||
async def create_placements(self, placements: list[domain.Placement]) -> None:
|
||||
if placements:
|
||||
await domain.Placement.bulk_create(placements)
|
||||
|
||||
async def get_placement(self, workspace_id: uuid.UUID, placement_id: uuid.UUID) -> domain.Placement | None:
|
||||
return await domain.Placement.get_or_none(
|
||||
id=placement_id, project__workspace_id=workspace_id
|
||||
).prefetch_related('project', 'project__channel', 'channel', 'creative')
|
||||
@staticmethod
|
||||
async def get_placement(workspace_id: uuid.UUID, placement_id: uuid.UUID) -> domain.Placement | None:
|
||||
return await domain.Placement.get_or_none(id=placement_id, project__workspace_id=workspace_id).prefetch_related(
|
||||
'project', 'project__channel', 'channel', 'creative'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def get_project_placements(
|
||||
self, workspace_id: uuid.UUID, project_id: uuid.UUID, include_archived: bool = False
|
||||
workspace_id: uuid.UUID, project_id: uuid.UUID, include_archived: bool = False
|
||||
) -> list[domain.Placement]:
|
||||
query = domain.Placement.filter(project__workspace_id=workspace_id, project_id=project_id)
|
||||
|
||||
@@ -370,11 +391,8 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
async def update_placement_user(self, placement: domain.Placement) -> None:
|
||||
await placement.save()
|
||||
|
||||
# Publication methods (system-managed, фактически размещенные посты)
|
||||
async def get_publication(self, workspace_id: uuid.UUID, publication_id: uuid.UUID) -> domain.Publication | None:
|
||||
@staticmethod
|
||||
async def get_publication(workspace_id: uuid.UUID, publication_id: uuid.UUID) -> domain.Publication | None:
|
||||
return await domain.Publication.get_or_none(
|
||||
id=publication_id, placement__project__workspace_id=workspace_id
|
||||
).prefetch_related(
|
||||
@@ -387,8 +405,8 @@ class Postgres(DatabaseBase, Database):
|
||||
'post__channel',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def get_workspace_publications(
|
||||
self,
|
||||
workspace_id: uuid.UUID,
|
||||
project_id: uuid.UUID | None = None,
|
||||
placement_channel_id: uuid.UUID | None = None,
|
||||
@@ -433,16 +451,12 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
async def create_publication(self, publication: domain.Publication) -> None:
|
||||
@staticmethod
|
||||
async def create_publication(publication: domain.Publication) -> None:
|
||||
await publication.save()
|
||||
|
||||
async def update_publication(self, publication: domain.Publication) -> None:
|
||||
await publication.save()
|
||||
|
||||
async def delete_publication(self, publication_id: uuid.UUID) -> None:
|
||||
await domain.Publication.filter(id=publication_id).delete()
|
||||
|
||||
async def get_publication_by_invite_link(self, invite_link: str) -> domain.Publication | None:
|
||||
@staticmethod
|
||||
async def get_publication_by_invite_link(invite_link: str) -> domain.Publication | None:
|
||||
return await domain.Publication.get_or_none(placement__invite_link=invite_link).prefetch_related(
|
||||
'placement',
|
||||
'placement__project',
|
||||
@@ -452,21 +466,22 @@ class Postgres(DatabaseBase, Database):
|
||||
)
|
||||
|
||||
# Subscription methods
|
||||
async def create_subscription(self, subscription: domain.Subscription) -> None:
|
||||
@staticmethod
|
||||
async def create_subscription(subscription: domain.Subscription) -> None:
|
||||
await subscription.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_subscription_by_subscriber_and_publication(
|
||||
self, telegram_user_id: uuid.UUID, publication_id: uuid.UUID
|
||||
telegram_user_id: uuid.UUID, publication_id: uuid.UUID
|
||||
) -> domain.Subscription | None:
|
||||
return await domain.Subscription.get_or_none(
|
||||
telegram_user_id=telegram_user_id, publication_id=publication_id
|
||||
)
|
||||
return await domain.Subscription.get_or_none(telegram_user_id=telegram_user_id, publication_id=publication_id)
|
||||
|
||||
async def update_subscription(self, subscription: domain.Subscription) -> None:
|
||||
@staticmethod
|
||||
async def update_subscription(subscription: domain.Subscription) -> None:
|
||||
await subscription.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_subscriptions_for_publications(
|
||||
self,
|
||||
publication_ids: list[uuid.UUID],
|
||||
*,
|
||||
date_from: datetime.datetime | None = None,
|
||||
@@ -484,8 +499,9 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
return await query.all()
|
||||
|
||||
@staticmethod
|
||||
async def get_active_subscriptions_by_subscriber_and_project(
|
||||
self, telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||
telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||
) -> list[domain.Subscription]:
|
||||
return (
|
||||
await domain.Subscription.filter(
|
||||
@@ -497,8 +513,9 @@ class Postgres(DatabaseBase, Database):
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def get_active_subscription_by_subscriber_and_project(
|
||||
self, telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||
telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||
) -> domain.Subscription | None:
|
||||
return (
|
||||
await domain.Subscription.filter(
|
||||
@@ -510,26 +527,9 @@ class Postgres(DatabaseBase, Database):
|
||||
.first()
|
||||
)
|
||||
|
||||
# Post methods
|
||||
async def create_post(self, post: domain.Post) -> None:
|
||||
await post.save()
|
||||
|
||||
async def get_post(self, post_id: uuid.UUID) -> domain.Post | None:
|
||||
return await domain.Post.get_or_none(id=post_id).prefetch_related('channel')
|
||||
|
||||
async def get_post_by_channel_and_message(self, channel_id: uuid.UUID, message_id: int) -> domain.Post | None:
|
||||
return await domain.Post.get_or_none(channel_id=channel_id, message_id=message_id).prefetch_related('channel')
|
||||
|
||||
# Post views history
|
||||
async def create_post_views_history(self, history: domain.PostViewsHistory) -> None:
|
||||
await history.save()
|
||||
|
||||
@staticmethod
|
||||
async def get_views_history(
|
||||
self,
|
||||
post_id: uuid.UUID,
|
||||
*,
|
||||
from_date: datetime.datetime | None = None,
|
||||
to_date: datetime.datetime | None = None,
|
||||
post_id: uuid.UUID, *, from_date: datetime.datetime | None = None, to_date: datetime.datetime | None = None
|
||||
) -> list[domain.PostViewsHistory]:
|
||||
query = domain.PostViewsHistory.filter(post_id=post_id)
|
||||
|
||||
@@ -540,15 +540,8 @@ class Postgres(DatabaseBase, Database):
|
||||
|
||||
return await query.order_by('fetched_at').all()
|
||||
|
||||
async def get_latest_views_data(self, post_id: uuid.UUID) -> tuple[int, datetime.datetime] | None:
|
||||
latest = await domain.PostViewsHistory.filter(post_id=post_id).order_by('-fetched_at').first()
|
||||
if latest:
|
||||
return (latest.views_count, latest.fetched_at)
|
||||
return None
|
||||
|
||||
async def get_latest_views_data_batch(
|
||||
self, post_ids: list[uuid.UUID]
|
||||
) -> dict[uuid.UUID, tuple[int, datetime.datetime]]:
|
||||
@staticmethod
|
||||
async def get_latest_views_data_batch(post_ids: list[uuid.UUID]) -> dict[uuid.UUID, tuple[int, datetime.datetime]]:
|
||||
if not post_ids:
|
||||
return {}
|
||||
|
||||
@@ -561,13 +554,16 @@ class Postgres(DatabaseBase, Database):
|
||||
return results
|
||||
|
||||
# Count methods
|
||||
async def count_publications_by_creative(self, creative_id: uuid.UUID) -> int:
|
||||
@staticmethod
|
||||
async def count_publications_by_creative(creative_id: uuid.UUID) -> int:
|
||||
return await domain.Publication.filter(placement__creative_id=creative_id).count()
|
||||
|
||||
async def count_subscriptions_by_publication(self, publication_id: uuid.UUID) -> int:
|
||||
@staticmethod
|
||||
async def count_subscriptions_by_publication(publication_id: uuid.UUID) -> int:
|
||||
return await domain.Subscription.filter(publication_id=publication_id).count()
|
||||
|
||||
async def count_publications_by_creative_batch(self, creative_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]:
|
||||
@staticmethod
|
||||
async def count_publications_by_creative_batch(creative_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]:
|
||||
if not creative_ids:
|
||||
return {}
|
||||
|
||||
@@ -583,7 +579,8 @@ class Postgres(DatabaseBase, Database):
|
||||
counts = {row['placement__creative_id']: row['count'] for row in results}
|
||||
return {cid: counts.get(cid, 0) for cid in creative_ids}
|
||||
|
||||
async def count_subscriptions_by_publication_batch(self, publication_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]:
|
||||
@staticmethod
|
||||
async def count_subscriptions_by_publication_batch(publication_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]:
|
||||
if not publication_ids:
|
||||
return {}
|
||||
|
||||
@@ -599,5 +596,6 @@ class Postgres(DatabaseBase, Database):
|
||||
counts = {row['publication_id']: row['count'] for row in results}
|
||||
return {pid: counts.get(pid, 0) for pid in publication_ids}
|
||||
|
||||
async def has_publications_for_creative(self, creative_id: uuid.UUID) -> bool:
|
||||
@staticmethod
|
||||
async def has_publications_for_creative(creative_id: uuid.UUID) -> bool:
|
||||
return await domain.Publication.filter(placement__creative_id=creative_id).exists()
|
||||
|
||||
@@ -12,10 +12,6 @@ class TelegramBot(TelegramBase, TelegramBotWriter):
|
||||
async def send_message(self, text: str, chat_id: int) -> None:
|
||||
await self.bot.send_message(chat_id=chat_id, text=text)
|
||||
|
||||
async def send_message_with_button(self, text: str, chat_id: int, button_text: str, button_url: str) -> None:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=button_text, url=button_url)]])
|
||||
await self.bot.send_message(chat_id=chat_id, text=text, reply_markup=keyboard)
|
||||
|
||||
async def create_chat_invite_link(self, chat_id: int, requires_approval: bool = False) -> str:
|
||||
invite_link = await self.bot.create_chat_invite_link(chat_id=chat_id, creates_join_request=requires_approval)
|
||||
return invite_link.invite_link
|
||||
@@ -28,5 +24,3 @@ class TelegramBot(TelegramBase, TelegramBotWriter):
|
||||
|
||||
async def edit_message_reply_markup(self, chat_id: int, message_id: int) -> None:
|
||||
await self.bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=None)
|
||||
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@ async def _handle_chat_member_updated(event: dict[str, Any]) -> None:
|
||||
if chat.get('type') not in ['channel', 'supergroup']:
|
||||
return
|
||||
|
||||
from_user = event.get('from')
|
||||
member_user = event.get('new_chat_member', {}).get('user')
|
||||
if not member_user:
|
||||
log.error('No user in chat_member_updated event')
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CreateLoginTokenRequest(BaseModel):
|
||||
telegram_id: int
|
||||
username: str | None = None
|
||||
@@ -7,8 +7,6 @@ from src import domain
|
||||
|
||||
|
||||
class PublicationOutput(pydantic.BaseModel):
|
||||
"""Публикация - фактический пост, мониторимый системой"""
|
||||
|
||||
id: uuid.UUID
|
||||
project_id: uuid.UUID
|
||||
project_channel_title: str | None
|
||||
@@ -45,23 +43,3 @@ class GetPublicationInput(pydantic.BaseModel):
|
||||
publication_id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
workspace_id: uuid.UUID
|
||||
|
||||
|
||||
class CreatePublicationInput(pydantic.BaseModel):
|
||||
placement_id: uuid.UUID
|
||||
placement_date: datetime.datetime
|
||||
cost: float | None = None
|
||||
comment: str | None = None
|
||||
|
||||
|
||||
class UpdatePublicationInput(pydantic.BaseModel):
|
||||
placement_date: datetime.datetime | None = None
|
||||
cost: float | None = None
|
||||
comment: str | None = None
|
||||
status: domain.PublicationStatus | None = None
|
||||
|
||||
|
||||
class DeletePublicationInput(pydantic.BaseModel):
|
||||
publication_id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
workspace_id: uuid.UUID
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import datetime
|
||||
import typing
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
@@ -6,6 +5,8 @@ from uuid import UUID
|
||||
if typing.TYPE_CHECKING:
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
|
||||
from src.adapter.postgres import Postgres
|
||||
|
||||
from src import domain
|
||||
|
||||
from .analytics.get_channel_analytics import get_channel_analytics
|
||||
@@ -56,237 +57,9 @@ from .workspace.update_workspace import update_workspace
|
||||
from .workspace.update_workspace_member_permissions import update_workspace_member_permissions
|
||||
|
||||
|
||||
class Database(typing.Protocol):
|
||||
def transaction(self) -> typing.AsyncContextManager[None]: ...
|
||||
|
||||
async def get_user(
|
||||
self,
|
||||
user_id: UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
) -> domain.User | None: ...
|
||||
|
||||
async def get_user_by_username(self, username: str) -> domain.User | None: ...
|
||||
|
||||
async def create_user(self, user: domain.User) -> None: ...
|
||||
|
||||
async def get_telegram_user(
|
||||
self,
|
||||
telegram_user_id: UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
) -> domain.TelegramUser | None: ...
|
||||
|
||||
async def create_telegram_user(self, telegram_user: domain.TelegramUser) -> None: ...
|
||||
|
||||
async def update_telegram_user(self, telegram_user: domain.TelegramUser) -> None: ...
|
||||
|
||||
async def create_workspace(self, workspace: domain.Workspace) -> None: ...
|
||||
|
||||
async def update_workspace(self, workspace: domain.Workspace) -> None: ...
|
||||
|
||||
async def delete_workspace(self, workspace_id: UUID) -> None: ...
|
||||
|
||||
async def add_user_to_workspace(self, workspace_id: UUID, user_id: UUID) -> domain.WorkspaceUser: ...
|
||||
|
||||
async def set_workspace_user_permissions(
|
||||
self,
|
||||
workspace_user_id: UUID,
|
||||
global_permissions: set[domain.PermissionKey],
|
||||
scoped_permissions: list[tuple[domain.PermissionKey, domain.PermissionScopeType, UUID]],
|
||||
) -> None: ...
|
||||
|
||||
async def update_workspace_user(self, workspace_user: domain.WorkspaceUser) -> None: ...
|
||||
|
||||
async def get_user_workspaces(self, user_id: UUID) -> list[domain.WorkspaceUser]: ...
|
||||
|
||||
async def get_workspace(self, workspace_id: UUID) -> domain.Workspace | None: ...
|
||||
|
||||
async def get_workspace_for_user(self, workspace_id: UUID, user_id: UUID) -> domain.Workspace | None: ...
|
||||
|
||||
async def get_default_workspace_for_user(self, user_id: UUID) -> domain.Workspace | None: ...
|
||||
|
||||
async def get_workspace_membership(self, workspace_id: UUID, user_id: UUID) -> domain.WorkspaceUser | None: ...
|
||||
|
||||
async def get_workspace_members(self, workspace_id: UUID) -> list[domain.WorkspaceUser]: ...
|
||||
|
||||
async def get_workspace_member(self, workspace_user_id: UUID) -> domain.WorkspaceUser | None: ...
|
||||
|
||||
async def create_workspace_invite(self, invite: domain.WorkspaceInvite) -> None: ...
|
||||
|
||||
async def update_workspace_invite(self, invite: domain.WorkspaceInvite) -> None: ...
|
||||
|
||||
async def get_workspace_invite(self, invite_id: UUID) -> domain.WorkspaceInvite | None: ...
|
||||
|
||||
async def get_workspace_invite_by_user(
|
||||
self, workspace_id: UUID, user_id: UUID
|
||||
) -> domain.WorkspaceInvite | None: ...
|
||||
|
||||
async def get_workspace_invites(self, workspace_id: UUID) -> list[domain.WorkspaceInvite]: ...
|
||||
|
||||
async def create_login_token(self, login_token: domain.LoginToken) -> None: ...
|
||||
|
||||
async def create_creative(self, creative: domain.Creative) -> None: ...
|
||||
|
||||
async def get_login_token(self, token: str) -> domain.LoginToken | None: ...
|
||||
|
||||
async def mark_token_as_used(self, token: str) -> None: ...
|
||||
|
||||
async def update_user(self, user: domain.User) -> None: ...
|
||||
|
||||
async def get_channel(
|
||||
self,
|
||||
channel_id: UUID | None = None,
|
||||
telegram_id: int | None = None,
|
||||
username: str | None = None,
|
||||
) -> domain.Channel | None: ...
|
||||
|
||||
async def create_channel(self, channel: domain.Channel) -> None: ...
|
||||
|
||||
async def update_channel(self, channel: domain.Channel) -> None: ...
|
||||
|
||||
async def delete_channel(self, channel_id: UUID) -> None: ...
|
||||
|
||||
async def search_channels(self, username_query: str | None = None) -> list[domain.Channel]: ...
|
||||
|
||||
async def get_project(
|
||||
self, workspace_id: UUID, project_id: UUID | None = None, channel_id: UUID | None = None
|
||||
) -> domain.Project | None: ...
|
||||
|
||||
async def get_project_for_user_by_telegram(
|
||||
self, user_id: UUID, channel_telegram_id: int
|
||||
) -> domain.Project | None: ...
|
||||
|
||||
async def get_project_by_channel_telegram(self, channel_telegram_id: int) -> domain.Project | None: ...
|
||||
|
||||
async def create_project(self, project: domain.Project) -> None: ...
|
||||
|
||||
async def update_project(self, project: domain.Project) -> None: ...
|
||||
|
||||
async def get_workspace_projects(
|
||||
self, workspace_id: UUID, allowed_project_ids: set[UUID] | None = None, include_archived: bool = False
|
||||
) -> list[domain.Project]: ...
|
||||
|
||||
async def archive_project(self, workspace_id: UUID, project_id: UUID) -> None: ...
|
||||
|
||||
async def delete_project(self, workspace_id: UUID, project_id: UUID) -> None: ...
|
||||
|
||||
async def check_channel_exists_in_workspace(self, channel_id: UUID, workspace_id: UUID) -> bool: ...
|
||||
|
||||
# Placement methods (user-managed, бывший PurchaseChannel)
|
||||
async def create_placement(self, placement: domain.Placement) -> None: ...
|
||||
|
||||
async def create_placements(self, placements: list[domain.Placement]) -> None: ...
|
||||
|
||||
async def get_placement(self, workspace_id: UUID, placement_id: UUID) -> domain.Placement | None: ...
|
||||
|
||||
async def get_project_placements(
|
||||
self, workspace_id: UUID, project_id: UUID, include_archived: bool = False
|
||||
) -> list[domain.Placement]: ...
|
||||
|
||||
async def update_placement_user(self, placement: domain.Placement) -> 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,
|
||||
project_id: UUID | None = None,
|
||||
include_archived: bool = False,
|
||||
allowed_project_ids: set[UUID] | None = None,
|
||||
) -> list[domain.Creative]: ...
|
||||
|
||||
async def delete_creative(self, creative_id: UUID) -> None: ...
|
||||
|
||||
# Publication methods (system-managed, бывший Placement)
|
||||
async def get_publication(self, workspace_id: UUID, publication_id: UUID) -> domain.Publication | None: ...
|
||||
|
||||
async def get_workspace_publications(
|
||||
self,
|
||||
workspace_id: UUID,
|
||||
project_id: UUID | None = None,
|
||||
placement_channel_id: UUID | None = None,
|
||||
creative_id: UUID | None = None,
|
||||
include_archived: bool = False,
|
||||
allowed_project_ids: set[UUID] | None = None,
|
||||
date_from: datetime.datetime | None = None,
|
||||
date_to: datetime.datetime | None = None,
|
||||
) -> list[domain.Publication]: ...
|
||||
|
||||
async def create_publication(self, publication: domain.Publication) -> None: ...
|
||||
|
||||
async def update_publication(self, publication: domain.Publication) -> None: ...
|
||||
|
||||
async def delete_publication(self, publication_id: UUID) -> None: ...
|
||||
|
||||
async def get_publication_by_invite_link(self, invite_link: str) -> domain.Publication | None: ...
|
||||
|
||||
# Subscription methods
|
||||
async def create_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||
|
||||
async def get_subscription_by_subscriber_and_publication(
|
||||
self, telegram_user_id: UUID, publication_id: UUID
|
||||
) -> domain.Subscription | None: ...
|
||||
|
||||
async def update_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||
|
||||
async def get_subscriptions_for_publications(
|
||||
self,
|
||||
publication_ids: list[UUID],
|
||||
*,
|
||||
date_from: datetime.datetime | None = None,
|
||||
date_to: datetime.datetime | None = None,
|
||||
) -> list[domain.Subscription]: ...
|
||||
|
||||
async def get_active_subscriptions_by_subscriber_and_project(
|
||||
self, telegram_user_id: UUID, project_id: UUID
|
||||
) -> list[domain.Subscription]: ...
|
||||
|
||||
async def get_active_subscription_by_subscriber_and_project(
|
||||
self, telegram_user_id: UUID, project_id: UUID
|
||||
) -> domain.Subscription | None: ...
|
||||
|
||||
# Count methods
|
||||
async def count_publications_by_creative(self, creative_id: UUID) -> int: ...
|
||||
|
||||
async def count_subscriptions_by_publication(self, publication_id: UUID) -> int: ...
|
||||
|
||||
async def count_publications_by_creative_batch(self, creative_ids: list[UUID]) -> dict[UUID, int]: ...
|
||||
|
||||
async def count_subscriptions_by_publication_batch(
|
||||
self, publication_ids: list[UUID]
|
||||
) -> dict[UUID, int]: ...
|
||||
|
||||
async def has_publications_for_creative(self, creative_id: UUID) -> bool: ...
|
||||
|
||||
# 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,
|
||||
post_id: UUID,
|
||||
*,
|
||||
from_date: datetime.datetime | None = None,
|
||||
to_date: datetime.datetime | None = None,
|
||||
) -> 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]]: ...
|
||||
|
||||
|
||||
class TelegramBotWriter(typing.Protocol):
|
||||
async def send_message(self, text: str, chat_id: int) -> None: ...
|
||||
|
||||
async def send_message_with_button(self, text: str, chat_id: int, button_text: str, button_url: str) -> None: ...
|
||||
|
||||
async def send_message_with_inline_keyboard(
|
||||
self, text: str, chat_id: int, buttons: list[list['InlineKeyboardButton']]
|
||||
) -> None: ...
|
||||
@@ -311,10 +84,6 @@ class Parser(typing.Protocol):
|
||||
|
||||
|
||||
class S3Storage(typing.Protocol):
|
||||
async def connect(self) -> None: ...
|
||||
|
||||
async def close(self) -> None: ...
|
||||
|
||||
async def upload(self, key: str, data: bytes, content_type: str) -> None: ...
|
||||
|
||||
async def get(self, key: str) -> bytes: ...
|
||||
@@ -324,26 +93,14 @@ class S3Storage(typing.Protocol):
|
||||
|
||||
@dataclass
|
||||
class Usecase:
|
||||
database: Database
|
||||
database: 'Postgres'
|
||||
telegram_bot: TelegramBotWriter
|
||||
jwt_encoder: JWTEncoder
|
||||
parser: Parser
|
||||
s3: S3Storage
|
||||
|
||||
async def ensure_workspace_access(self, workspace_id: UUID, user_id: UUID) -> domain.Workspace:
|
||||
workspace = await self.database.get_workspace_for_user(workspace_id, user_id)
|
||||
if not workspace:
|
||||
raise domain.WorkspaceNotFound(workspace_id)
|
||||
|
||||
return workspace
|
||||
|
||||
async def ensure_workspace_permission(
|
||||
self,
|
||||
workspace_id: UUID,
|
||||
user_id: UUID,
|
||||
permission: domain.PermissionKey,
|
||||
*,
|
||||
for_project_id: UUID | None = None,
|
||||
self, workspace_id: UUID, user_id: UUID, permission: domain.PermissionKey, *, for_project_id: UUID | None = None
|
||||
) -> domain.WorkspacePermissionContext:
|
||||
membership = await self.database.get_workspace_membership(workspace_id, user_id)
|
||||
if not membership or not membership.workspace:
|
||||
|
||||
@@ -233,7 +233,6 @@ async def get_projects_analytics(
|
||||
total_metrics.total_subscriptions += subs_count
|
||||
total_metrics.clicks_count += subs_count
|
||||
|
||||
views_count = 0
|
||||
if publication.post and publication.post.id in views_map:
|
||||
views_count = views_map[publication.post.id][0]
|
||||
pd.total_views += views_count
|
||||
@@ -245,7 +244,9 @@ async def get_projects_analytics(
|
||||
placement = publication.placement
|
||||
if placement and placement.cost_before_bargain and placement.cost_before_bargain > cost:
|
||||
discount = placement.cost_before_bargain - cost
|
||||
discount_percent = (discount / placement.cost_before_bargain) * 100 if placement.cost_before_bargain > 0 else 0.0
|
||||
discount_percent = (
|
||||
(discount / placement.cost_before_bargain) * 100 if placement.cost_before_bargain > 0 else 0.0
|
||||
)
|
||||
|
||||
pd.total_discounts += discount
|
||||
pd.discount_count += 1
|
||||
@@ -296,4 +297,3 @@ async def get_projects_analytics(
|
||||
totals = _calculate_metrics(total_metrics, input.metrics)
|
||||
|
||||
return dto.GetProjectsAnalyticsOutput(periods=periods, totals=totals)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user