Отедельные таблицы User и TelegramUser
This commit is contained in:
151
migrations/models/5_20260107135803_update.py
Normal file
151
migrations/models/5_20260107135803_update.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
from tortoise import BaseDBAsyncClient
|
||||||
|
|
||||||
|
RUN_IN_TRANSACTION = True
|
||||||
|
|
||||||
|
|
||||||
|
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
DROP INDEX IF EXISTS "uid_user_telegra_66ffbd";
|
||||||
|
ALTER TABLE "subscription" DROP CONSTRAINT IF EXISTS "uid_subscriptio_placeme_8fcb8f";
|
||||||
|
ALTER TABLE "subscription" DROP CONSTRAINT IF EXISTS "fk_subscrip_user_6b9f4b8e";
|
||||||
|
CREATE TABLE IF NOT EXISTS "telegram_user" (
|
||||||
|
"id" UUID NOT NULL PRIMARY KEY,
|
||||||
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"deleted_at" TIMESTAMPTZ,
|
||||||
|
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||||
|
"username" VARCHAR(255),
|
||||||
|
"first_name" VARCHAR(255),
|
||||||
|
"last_name" VARCHAR(255)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS "idx_telegram_us_telegra_3a3100" ON "telegram_user" ("telegram_id");
|
||||||
|
INSERT INTO "telegram_user" (
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
"telegram_id",
|
||||||
|
"username",
|
||||||
|
"first_name",
|
||||||
|
"last_name"
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
"telegram_id",
|
||||||
|
"username",
|
||||||
|
"first_name",
|
||||||
|
"last_name"
|
||||||
|
FROM "user"
|
||||||
|
WHERE "telegram_id" IS NOT NULL
|
||||||
|
ON CONFLICT ("id") DO NOTHING;
|
||||||
|
ALTER TABLE "subscription" RENAME COLUMN "subscriber_id" TO "telegram_user_id";
|
||||||
|
ALTER TABLE "user" ADD "telegram_user_id" UUID UNIQUE;
|
||||||
|
UPDATE "user" SET "telegram_user_id" = "id";
|
||||||
|
ALTER TABLE "user" ALTER COLUMN "telegram_user_id" SET NOT NULL;
|
||||||
|
ALTER TABLE "user" DROP COLUMN "first_name";
|
||||||
|
ALTER TABLE "user" DROP COLUMN "username";
|
||||||
|
ALTER TABLE "user" DROP COLUMN "telegram_id";
|
||||||
|
ALTER TABLE "user" DROP COLUMN "last_name";
|
||||||
|
ALTER TABLE "subscription" ADD CONSTRAINT "fk_subscrip_telegram_ab72b5bf" FOREIGN KEY ("telegram_user_id") REFERENCES "telegram_user" ("id") ON DELETE CASCADE;
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "uid_subscriptio_placeme_75b40b" ON "subscription" ("placement_id", "telegram_user_id");
|
||||||
|
ALTER TABLE "user" ADD CONSTRAINT "fk_user_telegram_548d951e" FOREIGN KEY ("telegram_user_id") REFERENCES "telegram_user" ("id") ON DELETE CASCADE;"""
|
||||||
|
|
||||||
|
|
||||||
|
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
DROP INDEX IF EXISTS "uid_subscriptio_placeme_75b40b";
|
||||||
|
ALTER TABLE "subscription" DROP CONSTRAINT IF EXISTS "fk_subscrip_telegram_ab72b5bf";
|
||||||
|
ALTER TABLE "user" DROP CONSTRAINT IF EXISTS "fk_user_telegram_548d951e";
|
||||||
|
ALTER TABLE "user" ADD "first_name" VARCHAR(255);
|
||||||
|
ALTER TABLE "user" ADD "username" VARCHAR(255);
|
||||||
|
ALTER TABLE "user" ADD "telegram_id" BIGINT NOT NULL UNIQUE;
|
||||||
|
ALTER TABLE "user" ADD "last_name" VARCHAR(255);
|
||||||
|
ALTER TABLE "user" DROP COLUMN "telegram_user_id";
|
||||||
|
ALTER TABLE "subscription" RENAME COLUMN "telegram_user_id" TO "subscriber_id";
|
||||||
|
DROP TABLE IF EXISTS "telegram_user";
|
||||||
|
ALTER TABLE "subscription" ADD CONSTRAINT "fk_subscrip_user_6b9f4b8e" FOREIGN KEY ("subscriber_id") REFERENCES "user" ("id") ON DELETE CASCADE;
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "uid_subscriptio_placeme_8fcb8f" ON "subscription" ("placement_id", "subscriber_id");"""
|
||||||
|
|
||||||
|
|
||||||
|
MODELS_STATE = (
|
||||||
|
"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"
|
||||||
|
"1lQhHbSyJe3k/3n5/X8nR5KJ"
|
||||||
|
)
|
||||||
@@ -23,6 +23,24 @@ class Postgres(DatabaseBase, Database):
|
|||||||
async def update_user(self, user: domain.User) -> None:
|
async def update_user(self, user: domain.User) -> None:
|
||||||
await user.save()
|
await user.save()
|
||||||
|
|
||||||
|
async def get_telegram_user(
|
||||||
|
self,
|
||||||
|
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)
|
||||||
|
if telegram_id:
|
||||||
|
return await domain.TelegramUser.get_or_none(telegram_id=telegram_id)
|
||||||
|
|
||||||
|
raise ValueError('Either telegram_user_id or telegram_id must be provided')
|
||||||
|
|
||||||
|
async def create_telegram_user(self, telegram_user: domain.TelegramUser) -> None:
|
||||||
|
await telegram_user.save()
|
||||||
|
|
||||||
|
async def update_telegram_user(self, telegram_user: domain.TelegramUser) -> None:
|
||||||
|
await telegram_user.save()
|
||||||
|
|
||||||
async def create_workspace(self, workspace: domain.Workspace) -> None:
|
async def create_workspace(self, workspace: domain.Workspace) -> None:
|
||||||
await workspace.save()
|
await workspace.save()
|
||||||
|
|
||||||
@@ -75,14 +93,14 @@ class Postgres(DatabaseBase, Database):
|
|||||||
) -> domain.WorkspaceUser | None:
|
) -> domain.WorkspaceUser | None:
|
||||||
return (
|
return (
|
||||||
await domain.WorkspaceUser.filter(workspace_id=workspace_id, user_id=user_id)
|
await domain.WorkspaceUser.filter(workspace_id=workspace_id, user_id=user_id)
|
||||||
.prefetch_related('workspace', 'user', 'permissions', 'permission_scopes')
|
.prefetch_related('workspace', 'user', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_workspace_members(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
async def get_workspace_members(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceUser]:
|
||||||
return (
|
return (
|
||||||
await domain.WorkspaceUser.filter(workspace_id=workspace_id)
|
await domain.WorkspaceUser.filter(workspace_id=workspace_id)
|
||||||
.prefetch_related('workspace', 'user', 'permissions', 'permission_scopes')
|
.prefetch_related('workspace', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||||
.order_by('created_at')
|
.order_by('created_at')
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@@ -90,7 +108,7 @@ class Postgres(DatabaseBase, Database):
|
|||||||
async def get_workspace_member(self, workspace_user_id: uuid.UUID) -> domain.WorkspaceUser | None:
|
async def get_workspace_member(self, workspace_user_id: uuid.UUID) -> domain.WorkspaceUser | None:
|
||||||
return (
|
return (
|
||||||
await domain.WorkspaceUser.filter(id=workspace_user_id)
|
await domain.WorkspaceUser.filter(id=workspace_user_id)
|
||||||
.prefetch_related('workspace', 'user', 'permissions', 'permission_scopes')
|
.prefetch_related('workspace', 'user__telegram_user', 'permissions', 'permission_scopes')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,7 +121,7 @@ class Postgres(DatabaseBase, Database):
|
|||||||
async def get_workspace_invite(self, invite_id: uuid.UUID) -> domain.WorkspaceInvite | None:
|
async def get_workspace_invite(self, invite_id: uuid.UUID) -> domain.WorkspaceInvite | None:
|
||||||
return (
|
return (
|
||||||
await domain.WorkspaceInvite.filter(id=invite_id)
|
await domain.WorkspaceInvite.filter(id=invite_id)
|
||||||
.prefetch_related('workspace', 'user', 'invited_by')
|
.prefetch_related('workspace', 'user__telegram_user', 'invited_by__telegram_user')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,7 +133,7 @@ class Postgres(DatabaseBase, Database):
|
|||||||
async def get_workspace_invites(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceInvite]:
|
async def get_workspace_invites(self, workspace_id: uuid.UUID) -> list[domain.WorkspaceInvite]:
|
||||||
return (
|
return (
|
||||||
await domain.WorkspaceInvite.filter(workspace_id=workspace_id)
|
await domain.WorkspaceInvite.filter(workspace_id=workspace_id)
|
||||||
.prefetch_related('user', 'invited_by')
|
.prefetch_related('user__telegram_user', 'invited_by__telegram_user')
|
||||||
.order_by('created_at')
|
.order_by('created_at')
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@@ -165,14 +183,24 @@ class Postgres(DatabaseBase, Database):
|
|||||||
|
|
||||||
async def get_user(self, user_id: uuid.UUID | None = None, telegram_id: int | None = None) -> domain.User | None:
|
async def get_user(self, user_id: uuid.UUID | None = None, telegram_id: int | None = None) -> domain.User | None:
|
||||||
if user_id:
|
if user_id:
|
||||||
return await domain.User.get_or_none(id=user_id)
|
return (
|
||||||
|
await domain.User.filter(id=user_id).prefetch_related('telegram_user').first()
|
||||||
|
)
|
||||||
elif telegram_id:
|
elif telegram_id:
|
||||||
return await domain.User.get_or_none(telegram_id=telegram_id)
|
return (
|
||||||
|
await domain.User.filter(telegram_user__telegram_id=telegram_id)
|
||||||
|
.prefetch_related('telegram_user')
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
raise ValueError('Either user_id or telegram_id must be provided')
|
raise ValueError('Either user_id or telegram_id must be provided')
|
||||||
|
|
||||||
async def get_user_by_username(self, username: str) -> domain.User | None:
|
async def get_user_by_username(self, username: str) -> domain.User | None:
|
||||||
return await domain.User.get_or_none(username__iexact=username)
|
return (
|
||||||
|
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:
|
async def get_login_token(self, token: str) -> domain.LoginToken | None:
|
||||||
return await domain.LoginToken.get_or_none(token=token)
|
return await domain.LoginToken.get_or_none(token=token)
|
||||||
@@ -444,9 +472,11 @@ class Postgres(DatabaseBase, Database):
|
|||||||
await subscription.save()
|
await subscription.save()
|
||||||
|
|
||||||
async def get_subscription_by_subscriber_and_placement(
|
async def get_subscription_by_subscriber_and_placement(
|
||||||
self, subscriber_id: uuid.UUID, placement_id: uuid.UUID
|
self, telegram_user_id: uuid.UUID, placement_id: uuid.UUID
|
||||||
) -> domain.Subscription | None:
|
) -> domain.Subscription | None:
|
||||||
return await domain.Subscription.get_or_none(subscriber_id=subscriber_id, placement_id=placement_id)
|
return await domain.Subscription.get_or_none(
|
||||||
|
telegram_user_id=telegram_user_id, placement_id=placement_id
|
||||||
|
)
|
||||||
|
|
||||||
async def update_subscription(self, subscription: domain.Subscription) -> None:
|
async def update_subscription(self, subscription: domain.Subscription) -> None:
|
||||||
await subscription.save()
|
await subscription.save()
|
||||||
@@ -471,28 +501,28 @@ class Postgres(DatabaseBase, Database):
|
|||||||
return await query.all()
|
return await query.all()
|
||||||
|
|
||||||
async def get_active_subscriptions_by_subscriber_and_project(
|
async def get_active_subscriptions_by_subscriber_and_project(
|
||||||
self, subscriber_id: uuid.UUID, project_id: uuid.UUID
|
self, telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||||
) -> list[domain.Subscription]:
|
) -> list[domain.Subscription]:
|
||||||
return (
|
return (
|
||||||
await domain.Subscription.filter(
|
await domain.Subscription.filter(
|
||||||
subscriber_id=subscriber_id,
|
telegram_user_id=telegram_user_id,
|
||||||
placement__project_id=project_id,
|
placement__project_id=project_id,
|
||||||
status=domain.SubscriptionStatus.ACTIVE,
|
status=domain.SubscriptionStatus.ACTIVE,
|
||||||
)
|
)
|
||||||
.prefetch_related('placement', 'subscriber')
|
.prefetch_related('placement', 'telegram_user')
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_active_subscription_by_subscriber_and_project(
|
async def get_active_subscription_by_subscriber_and_project(
|
||||||
self, subscriber_id: uuid.UUID, project_id: uuid.UUID
|
self, telegram_user_id: uuid.UUID, project_id: uuid.UUID
|
||||||
) -> domain.Subscription | None:
|
) -> domain.Subscription | None:
|
||||||
return (
|
return (
|
||||||
await domain.Subscription.filter(
|
await domain.Subscription.filter(
|
||||||
subscriber_id=subscriber_id,
|
telegram_user_id=telegram_user_id,
|
||||||
placement__project_id=project_id,
|
placement__project_id=project_id,
|
||||||
status=domain.SubscriptionStatus.ACTIVE,
|
status=domain.SubscriptionStatus.ACTIVE,
|
||||||
)
|
)
|
||||||
.prefetch_related('placement', 'subscriber')
|
.prefetch_related('placement', 'telegram_user')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'User',
|
'User',
|
||||||
|
'TelegramUser',
|
||||||
'Workspace',
|
'Workspace',
|
||||||
'WorkspaceUser',
|
'WorkspaceUser',
|
||||||
'WorkspaceInvite',
|
'WorkspaceInvite',
|
||||||
@@ -101,6 +102,7 @@ from .purchase import (
|
|||||||
)
|
)
|
||||||
from .subscription import Subscription, SubscriptionStatus
|
from .subscription import Subscription, SubscriptionStatus
|
||||||
from .user import User
|
from .user import User
|
||||||
|
from .telegram_user import TelegramUser
|
||||||
from .workspace import (
|
from .workspace import (
|
||||||
PermissionKey,
|
PermissionKey,
|
||||||
PermissionScopeType,
|
PermissionScopeType,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from .base import TimestampedModel
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .placement import Placement
|
from .placement import Placement
|
||||||
from .user import User
|
from .telegram_user import TelegramUser
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionStatus(str, enum.Enum):
|
class SubscriptionStatus(str, enum.Enum):
|
||||||
@@ -24,14 +24,14 @@ class Subscription(TimestampedModel):
|
|||||||
placement: fields.ForeignKeyRelation['Placement'] = fields.ForeignKeyField(
|
placement: fields.ForeignKeyRelation['Placement'] = fields.ForeignKeyField(
|
||||||
'models.Placement', related_name='subscriptions', on_delete=fields.CASCADE, index=True
|
'models.Placement', related_name='subscriptions', on_delete=fields.CASCADE, index=True
|
||||||
)
|
)
|
||||||
subscriber: fields.ForeignKeyRelation['User'] = fields.ForeignKeyField(
|
telegram_user: fields.ForeignKeyRelation['TelegramUser'] = fields.ForeignKeyField(
|
||||||
'models.User', related_name='subscriptions', on_delete=fields.CASCADE, index=True
|
'models.TelegramUser', related_name='subscriptions', on_delete=fields.CASCADE, index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
placement_id: UUID
|
placement_id: UUID
|
||||||
subscriber_id: UUID
|
telegram_user_id: UUID
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table = 'subscription'
|
table = 'subscription'
|
||||||
unique_together = (('placement_id', 'subscriber_id'),)
|
unique_together = (('placement_id', 'telegram_user_id'),)
|
||||||
|
|||||||
13
src/domain/telegram_user.py
Normal file
13
src/domain/telegram_user.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from tortoise import fields
|
||||||
|
|
||||||
|
from .base import TimestampedModel
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramUser(TimestampedModel):
|
||||||
|
telegram_id = fields.BigIntField(unique=True, index=True)
|
||||||
|
username = fields.CharField(max_length=255, null=True)
|
||||||
|
first_name = fields.CharField(max_length=255, null=True)
|
||||||
|
last_name = fields.CharField(max_length=255, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table = 'telegram_user'
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from tortoise import fields
|
from tortoise import fields
|
||||||
|
|
||||||
from .base import TimestampedModel
|
from .base import TimestampedModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .telegram_user import TelegramUser
|
||||||
|
|
||||||
|
|
||||||
class User(TimestampedModel):
|
class User(TimestampedModel):
|
||||||
telegram_id = fields.BigIntField(unique=True, index=True)
|
telegram_user: fields.ForeignKeyRelation['TelegramUser'] = fields.OneToOneField(
|
||||||
username = fields.CharField(max_length=255, null=True)
|
'models.TelegramUser', related_name='user', on_delete=fields.CASCADE, index=True
|
||||||
first_name = fields.CharField(max_length=255, null=True)
|
)
|
||||||
last_name = fields.CharField(max_length=255, null=True)
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
telegram_user_id: UUID
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table = 'user'
|
table = 'user'
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ class WorkspaceMemberOutput(pydantic.BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_domain(cls, member: domain.WorkspaceUser) -> 'WorkspaceMemberOutput':
|
def from_domain(cls, member: domain.WorkspaceUser) -> 'WorkspaceMemberOutput':
|
||||||
if member.user is None:
|
if member.user is None or member.user.telegram_user is None:
|
||||||
raise ValueError('Workspace member user relation is not loaded')
|
raise ValueError('Workspace member user relation is not fully loaded')
|
||||||
|
|
||||||
|
telegram_user = member.user.telegram_user
|
||||||
|
|
||||||
permissions_map: dict[domain.PermissionKey, list[WorkspacePermissionScopeOutput]] = {}
|
permissions_map: dict[domain.PermissionKey, list[WorkspacePermissionScopeOutput]] = {}
|
||||||
|
|
||||||
@@ -67,8 +69,8 @@ class WorkspaceMemberOutput(pydantic.BaseModel):
|
|||||||
status=member.status,
|
status=member.status,
|
||||||
user=WorkspaceMemberUserOutput(
|
user=WorkspaceMemberUserOutput(
|
||||||
id=member.user.id,
|
id=member.user.id,
|
||||||
telegram_id=member.user.telegram_id,
|
telegram_id=telegram_user.telegram_id,
|
||||||
username=member.user.username,
|
username=telegram_user.username,
|
||||||
),
|
),
|
||||||
permissions=[
|
permissions=[
|
||||||
WorkspacePermissionOutput(key=key, scopes=scopes)
|
WorkspacePermissionOutput(key=key, scopes=scopes)
|
||||||
@@ -150,21 +152,26 @@ class WorkspaceInviteOutput(pydantic.BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_domain(cls, invite: domain.WorkspaceInvite) -> 'WorkspaceInviteOutput':
|
def from_domain(cls, invite: domain.WorkspaceInvite) -> 'WorkspaceInviteOutput':
|
||||||
if invite.user is None or invite.invited_by is None:
|
if (
|
||||||
raise ValueError('Workspace invite relations are not loaded')
|
invite.user is None
|
||||||
|
or invite.invited_by is None
|
||||||
|
or invite.user.telegram_user is None
|
||||||
|
or invite.invited_by.telegram_user is None
|
||||||
|
):
|
||||||
|
raise ValueError('Workspace invite relations are not fully loaded')
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
id=invite.id,
|
id=invite.id,
|
||||||
status=invite.status,
|
status=invite.status,
|
||||||
user=WorkspaceMemberUserOutput(
|
user=WorkspaceMemberUserOutput(
|
||||||
id=invite.user.id,
|
id=invite.user.id,
|
||||||
telegram_id=invite.user.telegram_id,
|
telegram_id=invite.user.telegram_user.telegram_id,
|
||||||
username=invite.user.username,
|
username=invite.user.telegram_user.username,
|
||||||
),
|
),
|
||||||
invited_by=WorkspaceMemberUserOutput(
|
invited_by=WorkspaceMemberUserOutput(
|
||||||
id=invite.invited_by.id,
|
id=invite.invited_by.id,
|
||||||
telegram_id=invite.invited_by.telegram_id,
|
telegram_id=invite.invited_by.telegram_user.telegram_id,
|
||||||
username=invite.invited_by.username,
|
username=invite.invited_by.telegram_user.username,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ class Database(typing.Protocol):
|
|||||||
|
|
||||||
async def create_user(self, user: 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 create_workspace(self, workspace: domain.Workspace) -> None: ...
|
||||||
|
|
||||||
async def update_workspace(self, workspace: domain.Workspace) -> None: ...
|
async def update_workspace(self, workspace: domain.Workspace) -> None: ...
|
||||||
@@ -222,7 +232,7 @@ class Database(typing.Protocol):
|
|||||||
async def create_subscription(self, subscription: domain.Subscription) -> None: ...
|
async def create_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||||
|
|
||||||
async def get_subscription_by_subscriber_and_placement(
|
async def get_subscription_by_subscriber_and_placement(
|
||||||
self, subscriber_id: UUID, placement_id: UUID
|
self, telegram_user_id: UUID, placement_id: UUID
|
||||||
) -> domain.Subscription | None: ...
|
) -> domain.Subscription | None: ...
|
||||||
|
|
||||||
async def update_subscription(self, subscription: domain.Subscription) -> None: ...
|
async def update_subscription(self, subscription: domain.Subscription) -> None: ...
|
||||||
@@ -236,11 +246,11 @@ class Database(typing.Protocol):
|
|||||||
) -> list[domain.Subscription]: ...
|
) -> list[domain.Subscription]: ...
|
||||||
|
|
||||||
async def get_active_subscriptions_by_subscriber_and_project(
|
async def get_active_subscriptions_by_subscriber_and_project(
|
||||||
self, subscriber_id: UUID, project_id: UUID
|
self, telegram_user_id: UUID, project_id: UUID
|
||||||
) -> list[domain.Subscription]: ...
|
) -> list[domain.Subscription]: ...
|
||||||
|
|
||||||
async def get_active_subscription_by_subscriber_and_project(
|
async def get_active_subscription_by_subscriber_and_project(
|
||||||
self, subscriber_id: UUID, project_id: UUID
|
self, telegram_user_id: UUID, project_id: UUID
|
||||||
) -> domain.Subscription | None: ...
|
) -> domain.Subscription | None: ...
|
||||||
|
|
||||||
# Count methods
|
# Count methods
|
||||||
@@ -365,7 +375,11 @@ class Usecase:
|
|||||||
if workspace:
|
if workspace:
|
||||||
return workspace
|
return workspace
|
||||||
|
|
||||||
workspace_name = user.username or f'Workspace {user.telegram_id}'
|
if not user.telegram_user:
|
||||||
|
raise domain.UserNotFound(user.id)
|
||||||
|
|
||||||
|
telegram_user = user.telegram_user
|
||||||
|
workspace_name = telegram_user.username or f'Workspace {telegram_user.telegram_id}'
|
||||||
workspace = domain.Workspace(name=workspace_name)
|
workspace = domain.Workspace(name=workspace_name)
|
||||||
|
|
||||||
async with self.database.transaction():
|
async with self.database.transaction():
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
async def create_telegram_login_token(self: 'Usecase', telegram_id: int, username: str | None) -> str:
|
async def create_telegram_login_token(self: 'Usecase', telegram_id: int, username: str | None) -> str:
|
||||||
|
telegram_user = await self.database.get_telegram_user(telegram_id=telegram_id)
|
||||||
|
if not telegram_user:
|
||||||
|
telegram_user = domain.TelegramUser(telegram_id=telegram_id, username=username)
|
||||||
|
await self.database.create_telegram_user(telegram_user)
|
||||||
|
elif username and telegram_user.username != username:
|
||||||
|
telegram_user.username = username
|
||||||
|
await self.database.update_telegram_user(telegram_user)
|
||||||
|
|
||||||
user = await self.database.get_user(telegram_id=telegram_id)
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
if not user:
|
if not user:
|
||||||
user = domain.User(
|
user = domain.User(telegram_user=telegram_user)
|
||||||
telegram_id=telegram_id,
|
|
||||||
username=username,
|
|
||||||
)
|
|
||||||
await self.database.create_user(user)
|
await self.database.create_user(user)
|
||||||
|
|
||||||
token = secrets.token_urlsafe(32)
|
token = secrets.token_urlsafe(32)
|
||||||
|
|||||||
@@ -13,33 +13,38 @@ async def get_jwt_by_telegram_id(
|
|||||||
first_name: str | None = None,
|
first_name: str | None = None,
|
||||||
last_name: str | None = None,
|
last_name: str | None = None,
|
||||||
) -> dto.ValidateLoginTokenOutput:
|
) -> dto.ValidateLoginTokenOutput:
|
||||||
user = await self.database.get_user(telegram_id=telegram_id)
|
telegram_user = await self.database.get_telegram_user(telegram_id=telegram_id)
|
||||||
if not user:
|
if not telegram_user:
|
||||||
user = domain.User(
|
telegram_user = domain.TelegramUser(
|
||||||
telegram_id=telegram_id,
|
telegram_id=telegram_id,
|
||||||
username=username,
|
username=username,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
)
|
)
|
||||||
await self.database.create_user(user)
|
await self.database.create_telegram_user(telegram_user)
|
||||||
else:
|
else:
|
||||||
updated = False
|
updated = False
|
||||||
if username is not None and user.username != username:
|
if username is not None and telegram_user.username != username:
|
||||||
user.username = username
|
telegram_user.username = username
|
||||||
updated = True
|
updated = True
|
||||||
if first_name is not None and user.first_name != first_name:
|
if first_name is not None and telegram_user.first_name != first_name:
|
||||||
user.first_name = first_name
|
telegram_user.first_name = first_name
|
||||||
updated = True
|
updated = True
|
||||||
if last_name is not None and user.last_name != last_name:
|
if last_name is not None and telegram_user.last_name != last_name:
|
||||||
user.last_name = last_name
|
telegram_user.last_name = last_name
|
||||||
updated = True
|
updated = True
|
||||||
if updated:
|
if updated:
|
||||||
await self.database.update_user(user)
|
await self.database.update_telegram_user(telegram_user)
|
||||||
|
|
||||||
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
|
if not user:
|
||||||
|
user = domain.User(telegram_user=telegram_user)
|
||||||
|
await self.database.create_user(user)
|
||||||
|
|
||||||
access_token = self.jwt_encoder.encode_access_token(
|
access_token = self.jwt_encoder.encode_access_token(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
telegram_id=user.telegram_id,
|
telegram_id=telegram_user.telegram_id,
|
||||||
username=user.username,
|
username=telegram_user.username,
|
||||||
)
|
)
|
||||||
|
|
||||||
return dto.ValidateLoginTokenOutput(
|
return dto.ValidateLoginTokenOutput(
|
||||||
|
|||||||
@@ -13,10 +13,15 @@ async def get_me(self: 'Usecase', user_id: uuid.UUID) -> dto.UserOutput:
|
|||||||
if not user:
|
if not user:
|
||||||
raise domain.UserNotFound(user_id)
|
raise domain.UserNotFound(user_id)
|
||||||
|
|
||||||
|
if not user.telegram_user:
|
||||||
|
raise domain.UserNotFound(user_id)
|
||||||
|
|
||||||
|
telegram_user = user.telegram_user
|
||||||
|
|
||||||
return dto.UserOutput(
|
return dto.UserOutput(
|
||||||
id=user.id,
|
id=user.id,
|
||||||
telegram_id=user.telegram_id,
|
telegram_id=telegram_user.telegram_id,
|
||||||
username=user.username,
|
username=telegram_user.username,
|
||||||
first_name=user.first_name,
|
first_name=telegram_user.first_name,
|
||||||
last_name=user.last_name,
|
last_name=telegram_user.last_name,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ async def validate_login_token(self: 'Usecase', input: dto.ValidateLoginTokenInp
|
|||||||
|
|
||||||
await self.database.mark_token_as_used(input.token)
|
await self.database.mark_token_as_used(input.token)
|
||||||
|
|
||||||
|
telegram_user = user.telegram_user
|
||||||
|
if telegram_user is None:
|
||||||
|
raise domain.UserNotFound(login_token.user_id)
|
||||||
|
|
||||||
access_token = self.jwt_encoder.encode_access_token(
|
access_token = self.jwt_encoder.encode_access_token(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
telegram_id=user.telegram_id,
|
telegram_id=telegram_user.telegram_id,
|
||||||
username=user.username,
|
username=telegram_user.username,
|
||||||
)
|
)
|
||||||
|
|
||||||
return dto.ValidateLoginTokenOutput(
|
return dto.ValidateLoginTokenOutput(
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ async def disconnect_project_by_tg_id(self: 'Usecase', input: dto.DisconnectProj
|
|||||||
if not user:
|
if not user:
|
||||||
log.warning(f'User with telegram_id {input.user_telegram_id} not found when disconnecting channel')
|
log.warning(f'User with telegram_id {input.user_telegram_id} not found when disconnecting channel')
|
||||||
return
|
return
|
||||||
|
if user.telegram_user is None:
|
||||||
|
log.warning('User %s missing telegram profile when disconnecting channel', user.id)
|
||||||
|
return
|
||||||
|
|
||||||
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
||||||
if not project:
|
if not project:
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ async def tg_add_project(self: 'Usecase', input: dto.ConnectProjectInput) -> dto
|
|||||||
input.user_telegram_id,
|
input.user_telegram_id,
|
||||||
)
|
)
|
||||||
raise domain.UserNotFound()
|
raise domain.UserNotFound()
|
||||||
|
if user.telegram_user is None:
|
||||||
|
log.warning('User %s missing telegram profile when connecting channel', user.id)
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
'⚠️ Произошла ошибка при обработке вашего профиля. Пожалуйста, повторите авторизацию.',
|
||||||
|
input.user_telegram_id,
|
||||||
|
)
|
||||||
|
raise domain.UserNotFound()
|
||||||
|
|
||||||
|
if user.telegram_user is None:
|
||||||
|
raise domain.UserNotFound(user.id)
|
||||||
|
|
||||||
|
telegram_user = user.telegram_user
|
||||||
|
|
||||||
memberships = await self.database.get_user_workspaces(user.id)
|
memberships = await self.database.get_user_workspaces(user.id)
|
||||||
workspaces: list[domain.Workspace] = []
|
workspaces: list[domain.Workspace] = []
|
||||||
@@ -154,10 +166,10 @@ async def tg_add_project(self: 'Usecase', input: dto.ConnectProjectInput) -> dto
|
|||||||
f'Бот добавлен в канал "{input.title}".\n\n'
|
f'Бот добавлен в канал "{input.title}".\n\n'
|
||||||
f'У вас {len(workspaces)} рабочих пространств. '
|
f'У вас {len(workspaces)} рабочих пространств. '
|
||||||
'Нажмите кнопку ниже чтобы выбрать, где создать проект.',
|
'Нажмите кнопку ниже чтобы выбрать, где создать проект.',
|
||||||
user.telegram_id,
|
telegram_user.telegram_id,
|
||||||
buttons,
|
buttons,
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info('Pending channel notification sent to user %s for channel %s', user.telegram_id, channel.id)
|
log.info('Pending channel notification sent to user %s for channel %s', telegram_user.telegram_id, channel.id)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ async def update_project_permissions(self: 'Usecase', input: dto.UpdateProjectPe
|
|||||||
if not user:
|
if not user:
|
||||||
log.warning(f'User with telegram_id {input.user_telegram_id} not found when updating channel permissions')
|
log.warning(f'User with telegram_id {input.user_telegram_id} not found when updating channel permissions')
|
||||||
return
|
return
|
||||||
|
if user.telegram_user is None:
|
||||||
|
log.warning(f'User %s missing telegram profile when updating permissions', user.id)
|
||||||
|
return
|
||||||
|
|
||||||
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
project = await self.database.get_project_for_user_by_telegram(user.id, input.telegram_id)
|
||||||
if not project:
|
if not project:
|
||||||
@@ -34,7 +37,7 @@ async def update_project_permissions(self: 'Usecase', input: dto.UpdateProjectPe
|
|||||||
|
|
||||||
await self.telegram_bot.send_message(
|
await self.telegram_bot.send_message(
|
||||||
f'✅ Канал "{input.chat_title}" был активирован.\n\nВсе необходимые права боту предоставлены!',
|
f'✅ Канал "{input.chat_title}" был активирован.\n\nВсе необходимые права боту предоставлены!',
|
||||||
input.user_telegram_id,
|
user.telegram_user.telegram_id,
|
||||||
)
|
)
|
||||||
log.info(f'Project channel {input.telegram_id} reactivated - all permissions granted')
|
log.info(f'Project channel {input.telegram_id} reactivated - all permissions granted')
|
||||||
return
|
return
|
||||||
@@ -49,7 +52,7 @@ async def update_project_permissions(self: 'Usecase', input: dto.UpdateProjectPe
|
|||||||
f'⚠️ Канал "{input.chat_title}" был деактивирован.\n\n'
|
f'⚠️ Канал "{input.chat_title}" был деактивирован.\n\n'
|
||||||
f'Боту убрали необходимые права:\n{missed_permissions}'
|
f'Боту убрали необходимые права:\n{missed_permissions}'
|
||||||
),
|
),
|
||||||
input.user_telegram_id,
|
user.telegram_user.telegram_id,
|
||||||
)
|
)
|
||||||
log.warning(
|
log.warning(
|
||||||
'Project channel %s deactivated due to missing permissions: %s',
|
'Project channel %s deactivated due to missing permissions: %s',
|
||||||
|
|||||||
@@ -22,20 +22,20 @@ async def handle_subscription(
|
|||||||
log.warning('Placement not found for invite_link: %s', invite_link)
|
log.warning('Placement not found for invite_link: %s', invite_link)
|
||||||
return
|
return
|
||||||
|
|
||||||
subscriber = await self.database.get_user(telegram_id=user_telegram_id)
|
subscriber = await self.database.get_telegram_user(telegram_id=user_telegram_id)
|
||||||
if not subscriber:
|
if not subscriber:
|
||||||
subscriber = domain.User(
|
subscriber = domain.TelegramUser(
|
||||||
telegram_id=user_telegram_id,
|
telegram_id=user_telegram_id,
|
||||||
username=username,
|
username=username,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
)
|
)
|
||||||
await self.database.create_user(subscriber)
|
await self.database.create_telegram_user(subscriber)
|
||||||
else:
|
else:
|
||||||
subscriber.username = username or subscriber.username
|
subscriber.username = username or subscriber.username
|
||||||
subscriber.first_name = first_name or subscriber.first_name
|
subscriber.first_name = first_name or subscriber.first_name
|
||||||
subscriber.last_name = last_name or subscriber.last_name
|
subscriber.last_name = last_name or subscriber.last_name
|
||||||
await self.database.update_user(subscriber)
|
await self.database.update_telegram_user(subscriber)
|
||||||
|
|
||||||
active_subscription = await self.database.get_active_subscription_by_subscriber_and_project(
|
active_subscription = await self.database.get_active_subscription_by_subscriber_and_project(
|
||||||
subscriber.id, placement.project_id
|
subscriber.id, placement.project_id
|
||||||
@@ -74,7 +74,7 @@ async def handle_subscription(
|
|||||||
|
|
||||||
subscription = domain.Subscription(
|
subscription = domain.Subscription(
|
||||||
placement_id=placement.id,
|
placement_id=placement.id,
|
||||||
subscriber_id=subscriber.id,
|
telegram_user_id=subscriber.id,
|
||||||
invite_link=invite_link,
|
invite_link=invite_link,
|
||||||
)
|
)
|
||||||
await self.database.create_subscription(subscription)
|
await self.database.create_subscription(subscription)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
async def handle_unsubscription(self: 'Usecase', user_telegram_id: int, channel_telegram_id: int) -> None:
|
async def handle_unsubscription(self: 'Usecase', user_telegram_id: int, channel_telegram_id: int) -> None:
|
||||||
subscriber = await self.database.get_user(telegram_id=user_telegram_id)
|
subscriber = await self.database.get_telegram_user(telegram_id=user_telegram_id)
|
||||||
if not subscriber:
|
if not subscriber:
|
||||||
log.warning('Subscriber not found for telegram_id: %s', user_telegram_id)
|
log.warning('Subscriber not found for telegram_id: %s', user_telegram_id)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ async def create_workspace_invite(
|
|||||||
if invited_by_user is None:
|
if invited_by_user is None:
|
||||||
raise domain.UserNotFound(user_id)
|
raise domain.UserNotFound(user_id)
|
||||||
|
|
||||||
|
if invited_user.telegram_user is None:
|
||||||
|
raise domain.UserNotFound(invited_user.id)
|
||||||
|
if invited_by_user.telegram_user is None:
|
||||||
|
raise domain.UserNotFound(invited_by_user.id)
|
||||||
|
|
||||||
invite.user = invited_user
|
invite.user = invited_user
|
||||||
invite.invited_by = invited_by_user
|
invite.invited_by = invited_by_user
|
||||||
|
|
||||||
@@ -68,7 +73,7 @@ async def create_workspace_invite(
|
|||||||
)
|
)
|
||||||
await self.telegram_bot.send_message_with_inline_keyboard(
|
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||||
invite_message,
|
invite_message,
|
||||||
chat_id=invited_user.telegram_id,
|
chat_id=invited_user.telegram_user.telegram_id,
|
||||||
buttons=[[accept_button]],
|
buttons=[[accept_button]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ async def tg_accept_workspace_invite(
|
|||||||
if not user:
|
if not user:
|
||||||
await self.telegram_bot.send_message('❌ Вы не авторизованы. Используйте /start для входа.', chat_id)
|
await self.telegram_bot.send_message('❌ Вы не авторизованы. Используйте /start для входа.', chat_id)
|
||||||
return
|
return
|
||||||
|
if user.telegram_user is None:
|
||||||
|
await self.telegram_bot.send_message('❌ Ваш Telegram профиль не найден. Авторизуйтесь заново.', chat_id)
|
||||||
|
return
|
||||||
|
|
||||||
parts = callback_data.split(':', 1)
|
parts = callback_data.split(':', 1)
|
||||||
if len(parts) != 2 or parts[0] != INVITE_ACCEPT_CALLBACK_PREFIX:
|
if len(parts) != 2 or parts[0] != INVITE_ACCEPT_CALLBACK_PREFIX:
|
||||||
@@ -67,7 +70,7 @@ async def tg_accept_workspace_invite(
|
|||||||
await self.telegram_bot.send_message(
|
await self.telegram_bot.send_message(
|
||||||
f'✅ Вы присоединились к рабочему пространству «{workspace_name}».\n\n'
|
f'✅ Вы присоединились к рабочему пространству «{workspace_name}».\n\n'
|
||||||
'Администратор сможет выдать вам необходимые права в веб-интерфейсе.',
|
'Администратор сможет выдать вам необходимые права в веб-интерфейсе.',
|
||||||
chat_id,
|
user.telegram_user.telegram_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if message_id:
|
if message_id:
|
||||||
|
|||||||
Reference in New Issue
Block a user