feat: миграция с булевых переменных на status для target_channel

This commit is contained in:
Artem Tsyrulnikov
2025-12-01 13:34:26 +03:00
parent f7754b707d
commit d117776e65
16 changed files with 56 additions and 67 deletions

View File

@@ -10,7 +10,7 @@ from src import domain
class Postgres(DatabaseBase): class Postgres(DatabaseBase):
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 self.session.get(domain.User, user_id) return await self.session.get(domain.User, user_id)
@@ -85,7 +85,7 @@ class Postgres(DatabaseBase):
existing.title = channel.title existing.title = channel.title
existing.username = channel.username existing.username = channel.username
existing.user_id = channel.user_id existing.user_id = channel.user_id
existing.is_active = channel.is_active existing.status = channel.status
existing.deleted_at = None existing.deleted_at = None
existing.updated_at = datetime.datetime.now(datetime.UTC) existing.updated_at = datetime.datetime.now(datetime.UTC)

View File

@@ -10,11 +10,6 @@ log = logging.getLogger(__name__)
@telegram_callback_router.chat_join_request() @telegram_callback_router.chat_join_request()
async def on_chat_join_request(event: ChatJoinRequest) -> None: async def on_chat_join_request(event: ChatJoinRequest) -> None:
"""
Обрабатывает запросы на вступление в канал через приватную invite_link с одобрением.
Событие приходит когда пользователь отправляет запрос на вступление в канал.
"""
if event.chat.type not in {'channel', 'supergroup'}: if event.chat.type not in {'channel', 'supergroup'}:
return return

View File

@@ -10,12 +10,9 @@ if TYPE_CHECKING:
class Subscriber(domain.Base): class Subscriber(domain.Base):
"""Пользователь Telegram, подписавшийся через invite links."""
telegram_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True) telegram_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True)
username: Mapped[str | None] username: Mapped[str | None]
first_name: Mapped[str | None] first_name: Mapped[str | None]
last_name: Mapped[str | None] last_name: Mapped[str | None]
# Relationships
subscriptions: Mapped[list['Subscription']] = relationship(back_populates='subscriber') subscriptions: Mapped[list['Subscription']] = relationship(back_populates='subscriber')

View File

@@ -25,22 +25,12 @@ class TargetChannel(domain.Base):
username: Mapped[str | None] = mapped_column(index=True) username: Mapped[str | None] = mapped_column(index=True)
status: Mapped[TargetChannelStatus] = mapped_column(default=TargetChannelStatus.ACTIVE) status: Mapped[TargetChannelStatus] = mapped_column(default=TargetChannelStatus.ACTIVE)
# Relationship with user
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('user.id'), index=True) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('user.id'), index=True)
user: Mapped['User'] = relationship(back_populates='target_channels') user: Mapped['User'] = relationship(back_populates='target_channels')
# M2M relationship with external channels
external_channels: Mapped[list['ExternalChannel']] = relationship( external_channels: Mapped[list['ExternalChannel']] = relationship(
secondary='target_external_channel', secondary='target_external_channel',
back_populates='target_channels', back_populates='target_channels',
) )
creatives: Mapped[list['Creative']] = relationship(back_populates='target_channel') creatives: Mapped[list['Creative']] = relationship(back_populates='target_channel')
@property
def is_active(self) -> bool:
return self.status == TargetChannelStatus.ACTIVE
@is_active.setter
def is_active(self, value: bool) -> None:
self.status = TargetChannelStatus.ACTIVE if value else TargetChannelStatus.INACTIVE

View File

@@ -2,6 +2,8 @@ import uuid
import pydantic import pydantic
from src.domain.target_channel import TargetChannelStatus
class ChannelBotPermissions(pydantic.BaseModel): class ChannelBotPermissions(pydantic.BaseModel):
is_admin: bool is_admin: bool
@@ -28,6 +30,8 @@ class ConnectTargetChanOutput(pydantic.BaseModel):
telegram_id: int telegram_id: int
title: str title: str
username: str | None username: str | None
status: TargetChannelStatus
# Deprecated: используйте status. Оставлено для обратной совместимости с фронтендом
is_active: bool is_active: bool

View File

@@ -57,7 +57,7 @@ async def connect_target_chan(self: 'Usecase', input: dto.ConnectTargetChanInput
title=input.title, title=input.title,
username=input.username, username=input.username,
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
upserted_channel = await self.database.upsert_target_channel(channel) upserted_channel = await self.database.upsert_target_channel(channel)
@@ -73,5 +73,6 @@ async def connect_target_chan(self: 'Usecase', input: dto.ConnectTargetChanInput
telegram_id=upserted_channel.telegram_id, telegram_id=upserted_channel.telegram_id,
title=upserted_channel.title, title=upserted_channel.title,
username=upserted_channel.username, username=upserted_channel.username,
is_active=upserted_channel.is_active, status=upserted_channel.status,
is_active=upserted_channel.status == domain.TargetChannelStatus.ACTIVE,
) )

View File

@@ -15,7 +15,7 @@ async def disconnect_target_chan(self: 'Usecase', input: dto.DisconnectTargetCha
if not channel: if not channel:
raise domain.TargetChannelNotFound(input.channel_id) raise domain.TargetChannelNotFound(input.channel_id)
channel.is_active = False channel.status = domain.TargetChannelStatus.INACTIVE
await self.database.update_target_channel(channel) await self.database.update_target_channel(channel)
log.info(f'Target channel {input.channel_id} deactivated') log.info(f'Target channel {input.channel_id} deactivated')

View File

@@ -21,7 +21,7 @@ async def disconnect_target_chan_by_tg_id(self: 'Usecase', input: dto.Disconnect
log.warning(f'Target channel {input.telegram_id} not found') log.warning(f'Target channel {input.telegram_id} not found')
raise domain.TargetChannelNotFound() raise domain.TargetChannelNotFound()
channel.is_active = False channel.status = domain.TargetChannelStatus.INACTIVE
await self.database.update_target_channel(channel) await self.database.update_target_channel(channel)
log.info(f'Target channel {input.telegram_id} deactivated') log.info(f'Target channel {input.telegram_id} deactivated')

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from src import dto from src import domain, dto
if TYPE_CHECKING: if TYPE_CHECKING:
from .. import Usecase from .. import Usecase
@@ -17,7 +17,8 @@ async def get_user_target_chans(self: 'Usecase', input: dto.GetUserTargetChansIn
telegram_id=channel.telegram_id, telegram_id=channel.telegram_id,
title=channel.title, title=channel.title,
username=channel.username, username=channel.username,
is_active=channel.is_active, status=channel.status,
is_active=channel.status == domain.TargetChannelStatus.ACTIVE,
) )
for channel in channels for channel in channels
] ]

View File

@@ -29,8 +29,8 @@ async def update_target_chan_permissions(self: 'Usecase', input: dto.UpdateTarge
raise domain.TargetChannelNotFound() raise domain.TargetChannelNotFound()
if not missing_permissions: if not missing_permissions:
if not channel.is_active: if channel.status != domain.TargetChannelStatus.ACTIVE:
channel.is_active = True channel.status = domain.TargetChannelStatus.ACTIVE
await self.database.update_target_channel(channel) await self.database.update_target_channel(channel)
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
@@ -40,8 +40,8 @@ async def update_target_chan_permissions(self: 'Usecase', input: dto.UpdateTarge
log.info(f'Target channel {input.telegram_id} reactivated - all permissions granted') log.info(f'Target channel {input.telegram_id} reactivated - all permissions granted')
return return
if channel.is_active: if channel.status == domain.TargetChannelStatus.ACTIVE:
channel.is_active = False channel.status = domain.TargetChannelStatus.INACTIVE
await self.database.update_target_channel(channel) await self.database.update_target_channel(channel)
missed_permissions = '\n'.join(f'{p}' for p in missing_permissions) missed_permissions = '\n'.join(f'{p}' for p in missing_permissions)

View File

@@ -18,7 +18,7 @@ async def test_create_creative_success(usecases: usecase.Usecase, mock_database:
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -70,7 +70,7 @@ async def test_get_user_creatives(usecases: usecase.Usecase, mock_database: Mock
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -107,14 +107,14 @@ async def test_get_creatives_filtered_by_target_channel(usecases: usecase.Usecas
title='Target 1', title='Target 1',
username='target1', username='target1',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
target2 = domain.TargetChannel( target2 = domain.TargetChannel(
telegram_id=-1001234567891, telegram_id=-1001234567891,
title='Target 2', title='Target 2',
username='target2', username='target2',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target1) await mock_database.upsert_target_channel(target1)
await mock_database.upsert_target_channel(target2) await mock_database.upsert_target_channel(target2)
@@ -153,7 +153,7 @@ async def test_archive_creative(usecases: usecase.Usecase, mock_database: MockDa
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -192,7 +192,7 @@ async def test_update_creative(usecases: usecase.Usecase, mock_database: MockDat
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -223,7 +223,7 @@ async def test_delete_creative(usecases: usecase.Usecase, mock_database: MockDat
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)

View File

@@ -18,7 +18,7 @@ async def test_create_external_channel_with_targets(usecases: usecase.Usecase, m
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -72,7 +72,7 @@ async def test_get_external_channels_for_target(usecases: usecase.Usecase, mock_
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -106,7 +106,7 @@ async def test_delete_external_channel(usecases: usecase.Usecase, mock_database:
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -139,14 +139,14 @@ async def test_update_external_channel_links(usecases: usecase.Usecase, mock_dat
title='Target 1', title='Target 1',
username='target1', username='target1',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
target2 = domain.TargetChannel( target2 = domain.TargetChannel(
telegram_id=-1001234567891, telegram_id=-1001234567891,
title='Target 2', title='Target 2',
username='target2', username='target2',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target1) await mock_database.upsert_target_channel(target1)
await mock_database.upsert_target_channel(target2) await mock_database.upsert_target_channel(target2)

View File

@@ -20,7 +20,7 @@ async def test_create_placement_with_public_invite_link(usecases: usecase.Usecas
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -86,7 +86,7 @@ async def test_create_placement_with_approval_invite_link(
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -160,7 +160,7 @@ async def test_get_user_placements(usecases: usecase.Usecase, mock_database: Moc
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -229,14 +229,14 @@ async def test_get_placements_filtered_by_target_channel(
title='Target 1', title='Target 1',
username='target1', username='target1',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
target2 = domain.TargetChannel( target2 = domain.TargetChannel(
telegram_id=-1001234567891, telegram_id=-1001234567891,
title='Target 2', title='Target 2',
username='target2', username='target2',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target1) await mock_database.upsert_target_channel(target1)
await mock_database.upsert_target_channel(target2) await mock_database.upsert_target_channel(target2)
@@ -308,7 +308,7 @@ async def test_get_placements_filtered_by_creative(usecases: usecase.Usecase, mo
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -380,7 +380,7 @@ async def test_archive_placement(usecases: usecase.Usecase, mock_database: MockD
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -441,7 +441,7 @@ async def test_update_placement(usecases: usecase.Usecase, mock_database: MockDa
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -505,7 +505,7 @@ async def test_delete_placement(usecases: usecase.Usecase, mock_database: MockDa
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -564,7 +564,7 @@ async def test_multiple_placements_same_creative(usecases: usecase.Usecase, mock
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)

View File

@@ -19,7 +19,7 @@ async def test_handle_subscription_success(usecases: usecase.Usecase, mock_datab
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -93,7 +93,7 @@ async def test_handle_subscription_duplicate_ignored(usecases: usecase.Usecase,
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -193,7 +193,7 @@ async def test_multiple_users_subscription_same_placement(
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -267,7 +267,7 @@ async def test_subscription_tracking_across_different_placements(
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)

View File

@@ -29,7 +29,8 @@ async def test_connect_new_channel_success(usecases: usecase.Usecase, mock_datab
assert result.telegram_id == -1001234567890 assert result.telegram_id == -1001234567890
assert result.title == 'Test Channel' assert result.title == 'Test Channel'
assert result.is_active is True assert result.status == domain.TargetChannelStatus.ACTIVE
assert result.is_active is True # проверка обратной совместимости
# Проверяем, что канал сохранён # Проверяем, что канал сохранён
channel = await mock_database.get_target_channel(user.id, telegram_id=-1001234567890) channel = await mock_database.get_target_channel(user.id, telegram_id=-1001234567890)
@@ -128,14 +129,14 @@ async def test_get_user_target_channels(usecases: usecase.Usecase, mock_database
title='Channel 1', title='Channel 1',
username='channel1', username='channel1',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
channel2 = domain.TargetChannel( channel2 = domain.TargetChannel(
telegram_id=-1001234567891, telegram_id=-1001234567891,
title='Channel 2', title='Channel 2',
username='channel2', username='channel2',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(channel1) await mock_database.upsert_target_channel(channel1)
await mock_database.upsert_target_channel(channel2) await mock_database.upsert_target_channel(channel2)
@@ -156,7 +157,7 @@ async def test_disconnect_target_channel(usecases: usecase.Usecase, mock_databas
title='Test Channel', title='Test Channel',
username='testchannel', username='testchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(channel) await mock_database.upsert_target_channel(channel)
@@ -165,4 +166,4 @@ async def test_disconnect_target_channel(usecases: usecase.Usecase, mock_databas
# Проверяем, что канал стал неактивным # Проверяем, что канал стал неактивным
updated_channel = await mock_database.get_target_channel(user.id, channel_id=channel.id) updated_channel = await mock_database.get_target_channel(user.id, channel_id=channel.id)
assert updated_channel is not None assert updated_channel is not None
assert updated_channel.is_active is False assert updated_channel.status == domain.TargetChannelStatus.INACTIVE

View File

@@ -19,7 +19,7 @@ async def test_fetch_views_no_ad_post_url(usecases: usecase.Usecase, mock_databa
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -78,7 +78,7 @@ async def test_fetch_views_api_unavailable(usecases: usecase.Usecase, mock_datab
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -144,7 +144,7 @@ async def test_update_views_manually(usecases: usecase.Usecase, mock_database: M
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -216,7 +216,7 @@ async def test_get_views_history_empty(usecases: usecase.Usecase, mock_database:
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -272,7 +272,7 @@ async def test_get_views_history_with_snapshots(usecases: usecase.Usecase, mock_
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)
@@ -353,7 +353,7 @@ async def test_get_views_history_with_date_filter(usecases: usecase.Usecase, moc
title='Target Channel', title='Target Channel',
username='targetchannel', username='targetchannel',
user_id=user.id, user_id=user.id,
is_active=True, status=domain.TargetChannelStatus.ACTIVE,
) )
await mock_database.upsert_target_channel(target_channel) await mock_database.upsert_target_channel(target_channel)