diff --git a/migrations/models/0_20251215212038_init.py b/migrations/models/0_20251215212038_init.py index 9fde9ac..6266598 100644 --- a/migrations/models/0_20251215212038_init.py +++ b/migrations/models/0_20251215212038_init.py @@ -1,3 +1,5 @@ +# ruff: noqa +# mypy: ignore-errors from tortoise import BaseDBAsyncClient RUN_IN_TRANSACTION = True @@ -244,74 +246,74 @@ async def downgrade(db: BaseDBAsyncClient) -> str: MODELS_STATE = ( - "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" - "37jnCt9x2t7SO8Gzcb/f4/gXYk2A==" + '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' + '37jnCt9x2t7SO8Gzcb/f4/gXYk2A==' ) diff --git a/migrations/models/1_20251215220358_update.py b/migrations/models/1_20251215220358_update.py deleted file mode 100644 index 7f19adc..0000000 --- a/migrations/models/1_20251215220358_update.py +++ /dev/null @@ -1,90 +0,0 @@ -from tortoise import BaseDBAsyncClient - -RUN_IN_TRANSACTION = True - - -async def upgrade(db: BaseDBAsyncClient) -> str: - return """ - COMMENT ON COLUMN "placement"."status" IS 'ACTIVE: active -ARCHIVED: archived';""" - - -async def downgrade(db: BaseDBAsyncClient) -> str: - return """ - COMMENT ON COLUMN "placement"."status" IS 'PENDING: pending -ACTIVE: active -DELETED: deleted -ARCHIVED: archived';""" - - -MODELS_STATE = ( - "eJztXWtv2zgW/SuGP3WAbJE4zqPBYgHHUVNNHduwnXR2mkJQZMbRRpY8eiQNiv73JfWkJE" - "rRy5Zs3y9Fw5elc0mK5/Dy8ld7qc2RYnzsP4mqipT2RetXWxWXCP8nmnXQaourVZBBEkzx" - "QbHLSlShB8PURcnEyY+iYiCcNEeGpMsrU9ZUnKpaikISNQkXlNVFkGSp8j8WEkxtgcwnpO" - "OM7z9wsqzO0U9keH+unoVHGSnz0MPKc/Lbdrpgvq3stNtb/uqzXZL83IMgaYq1VIPSqzfz" - "SVP94pYlzz+SOiRvgVSkiyaaU69BntJ9YS/JeWKcYOoW8h91HiTM0aNoKQSM9r8fLVUiGL" - "TsXyL/dP/TzgGPpKkEWlk1CRa/fjtvFbyzndomP9X/0pt8OD79w35LzTAXup1pI9L+bVcU" - "TdGpauMaACnpiLy2IJpxQK9wjikvERvUcM0IuHO36kfvP0VA9hIClIMe5sHswVcM0zZ+h/" - "lIVd5cC6ZgPONvuOmsdzMmb7I0jH8UG6LejCM5HTv1LZL6wTGJhseHM278Rlrf+NmXFvmz" - "9fdoyEUN55eb/d0mzyRapiao2qsgzqnO5qV6wOCSgWGt1bygYcM1wbC1GtZ9+MCueC5Gxe" - "warlmBXd2n3aBZt8SM3munDlATm2Ohi0uB9S27lBe8arLtGKkYMSSGaj0ftJIDckF+5V+f" - "Op3j47PO4fHp+Un37Ozk/PAcl7UfKZ51lmLuS/6aH87C1iMJv8MYy6aC4ujiNY6egK1XIY" - "IqfpWGTnRL8aegIHVhPuE/OycnKZjd9Sb2SgGXivT7oZvVcfLCIFoG0u3/58CRrlMNlGvv" - "oOsHUpQkZBjCk2g85RvxkYqFRvzmJ+t6hvzKNOLgJiLrlt4uRDtH3bPu+fFp1wfST0nDz8" - "GKUKrHZ4oLkIQHUXp+FfW5EMqhQFVECS2RysL20q37+esEKaL9SnFEXXI59tpp5mT62+se" - "XiprAUbWAWVhwE1sMwK69j8klQbBaWWbcbB0Cc/JSMDDQxVcTaQsKm6beKSolBSzRQiR+U" - "TraEkzTDxr2VlGU0RVXNhPTX6b/JInThHNQX5BbZZw5eUdpCpXdCmQrkC6AoUDpCswLEhX" - "IF1ll67yqgHVKgE7IaqY6CdjFMxwapLo95PV9xsLYVp35/6ahXq6B9SHm95ff4R6+2A0vP" - "aKU8D2B6PLCJ6GKZoWY+1NOiWnWksbVR4/oqhKKIZuUHtz+LbxytNdiIYxbvf6M/6Ou2g5" - "Be5Vgg5OuMIpmBzgJKd75OzG5xk68XliFz6PduBAEsA4WiqjMyeLLoyq1ajYWXA/LDEvVK" - "C/MIg0cwcgmRKEa1VJDeLwVSyyliECAWyvmv5srHAfyglctN6eQBdT/BhIxmH8rOlIXqhf" - "0Vts6mQrF9/otpoKX0yuwMm6+OrT9Fgfwe/pLD5toHvTfu+Ka7MGcQUIVqqI1YRfeHJiow" - "eKc70q2kBbyOpMe0Zqm6GjUbkHaUqaQsoJpl8QxDQQ00BzATENDAtiGohpOfzAvC9oZh8l" - "rwI41rgQop8rGVukwFgI19zOOW6XBoNlFPtUGTCfNciEek5hhqqyXk1mg3YrLsoQNCpQE2" - "7dZhqL2rtaAtUt8goJ66TPgaTAYM8hvSGZPK9CxYA6A3Xe1tXHzjAsoM47aligzru61PS/" - "ogKBPK8x47W3c6DukkUl1zM+svZVNDHBl8CrEDHdI6mxbePvanR7OeBa4wnX56f8aBg2j5" - "1JknCC7CyEJ1xvEJFCJG3pLSuzuhZRVQopSo3CcC3ORbL6ghEXFFl9drCJoZvNzYjVTs0O" - "Xe0x7lZ8/6K1sh4UWbpXe+PxZHTXG1y0MHvRtRfRPn9Qs7MRBVwepTRSbVt858JQnhx1Mo" - "CJSyXCaeeBu1ytPdiwHvyHzu8xl1B7L53mvBM8OTXGSLU98f1ir5bdo2p5/Q4T6u8jlDg7" - "L3pBlRKAvb/WaiZe4OhaBDZwdK1qTwWcNPM6aaZ+OSrAsdLDzXXhmPBFfB9R+hhyWSCppr" - "YWycjq7H0AwXM9CmEBz3W22JZ7RswWzaKupcv7ozhYmYUwm3Kz1vB2MMjmsB6iaHFU8/is" - "T6mmmqlQ1OK2bvcz1pa72/9Sdtu9EpVutH9vh2f9JTIM/OTkrx+wCb+WpTFswu/4Xi1swu" - "+oYWETfle3bKnPXsyQiYJ2uNLmdOySo7FiKRuCQFS7T+vNDI+6tvQpefEJhtEMzDZ1O4gU" - "2rrYyw2LFDkUZLyQ+JRBvGvqmfXaZpP00JgvMno1hCfZMDVdRhWES70jDX6x23tr9Mdu85" - "pICJoEfSQKX7pWItDme6teOQF1ZA3T/QGoI7tOokEd2VHDgjqyq3zF+ZLm9feL1NpXfeQR" - "mdJToVEQrrmZ2W1zxG/rRkFt/nKNMlsJ96WN7tQ3l6knbdXXfSLc9Qljka/AXSyFc1GFKt" - "6ijnqFUFoHbFIDDYPVOtAwMCzQsF1fgO7yeTd+6KXIaqPPwMHeHZwCqZVGgdP4QWmncdg6" - "XsfWsXf+oeROabVnQWrZOd73wO+JF0xWeLHklkGyVumGhoWl30RgSxFxaGOtfc/8Oy0aue" - "vTH6DggIIDRB8UnP01LCg4oOBsn4LTSLUGYnaAWgNqTaPUBricrqT7QEWX07mqT4WMvFIt" - "bOeIuYfOO/ycAjEjTafj2VTteRH+IfC+AO4OFA+4OxgWuHvZObBJZtxV7k4+2qrLwyMBsw" - "e94ZBwdbeIFzLbpu92yGySNuH+5PozkqYjsuYkafxQwAWvJ9x0Snw1BFyWoGzcq/3RzXjA" - "2cUlbbmyO28RCeDoKIMGcHSUKAKQrFjQWPKSQu7A/NGKEKAfAvTbr7mOwA/gWVRIq2JRlM" - "xCH6PunkCYdlAjuglcVnWpfNe+LumF0V3A2WiTzkbrFGtCESsZKk00omWyPGNES0LgAdBc" - "gJqD5gKGBc0FNJfsmkuzr8yqeAUFN2aV8j+5HU5vL6f9CX9JFChLdRdhDwVFqCzop4Afx5" - "5+oiJfnHh1mJ7qjgjhX51R9BKqvZIcGFfJPSA9J3axinsCXppeQ58SKavVVHvipPY7bbKK" - "NEG3qgDEWwM1c4mRFb/YIGuSTjPDD7LQxeUUL0dQmyHUhAscpCk1pltUMPyyoNWAVgOUHr" - "QaMCxoNUCGspMh/1PK+pZdyovEOJGRitXEiXz/g1ZyQDpRIj91OsfHZ53D49Pzk+7Z2cn5" - "oR8uMp6VFjfykr8moSND1ovHkvRXKUXlG9aidY3qDRPjdn/C9Yh2I3zr8TN+eC18G02+Ts" - "e9PnfR8u9QfBVlExtA8I8L3KuxenidMBxyA0Ytd5eRUWfYu2H9DEGYUZp4bjBKkztM7tXx" - "ZET8o1iv4Tnpx96iiBbVOc2gRXWiYz/QokhW1H9HZV/b8ud0NEzy31ETbm65VbFdv89lyT" - "xoKbJh/libFhgsQR8sWcHAGh/Jz65pFUqwCE2+MSefqD9PZFYlDVyyqHs9vMlmpQy65LHV" - "ZJZkeSWAGwE3giU0cCMwLHAj4EbAjZrEjcgixf4/kx4lTH9UnS3xSo9Qg5OTLNzg5CSZHJ" - "C8yI0Fsm6YQl4sw7UATQ9NRSwAZqjSfmOZgz1RoGsLGSOmPaOyIecGpKUZaaiZq5/Ec+2M" - "3U278ZJ4RL2gtxURe3PY9hMrCYgflYO3W9tiTKjIHAAMGxiyYKgKluq8BDYGyjrlqSC6DU" - "OjCoW+SRaqQjIrqFWgVsXBBVED1CowLKhVoFYlDtC8ZLUUT61jCDaIqELMfIiZz8DCcR8o" - "i0SV0Q7rwQHuDohCAtQcqHlt1NztLmkEPehRGWi6KzRVz9bjd4aS7gEhK4HIA98DIg+GBS" - "K/D0R+C4/7r5A6J+aJGbA95oZX/PD6ouUWuVd7/T43tuNNipKEVqYTsvJu9NWJWPmiPRc7" - "+1/xHRTOGmcuPLzlPOocq7gnR52jXj05YaOq7CFgcHVHVcfq4eqOg9JXdwRTWAUgbv2x+t" - "iE/j6A3tGLfYeOmtSbFIsgrBil6SLvn7IJS1mgiYAmAppIozgXaCJ7Y1jQREATaYwmkjkE" - "Ij+84209xF1o3quXg1HflkMeFE0qKIekHc3x5JCzRDnkLBYHERg9MHpg9M2gVfkZPRDS0o" - "SU8m9B+lI2jPKnckI0c+y32sxVYjbXH/8lBEPSVpV5vIQBmpKmtwyljekZVD96T9kId7ms" - "GoewCldbm9xBLyCCnwTZA2QPYMcge4BhQfbYddkj/KUtIn2EW6g7hmDv6oYfCp9vB4OLlj" - "hfyqrwiMv5MfmmwoTrXflx+AyBjFAq99uEn3FU9qsum1T8P6+6fzrDrR/kuw0EBdwWxoNe" - "n7vhhsET+McavGcISnhPERRxW+kNe4P/zvi+1wherilvpix5bQT5pOeMbmdCf8D3v07pkq" - "8ytpBlCpIiS8/kelYnWKL/ak6ERP/NvFzvxbxs+4mKyEadbpZjNd3kUzXdqHLEXM/lV0P2" - "T03KIokIFRH7NfjQ166NbMPmc5TTZmdsPgcuQNscbl4HecN/2b/t9D//L9jNBloHq3+gdW" - "BYoHVA64DWAa1rPK0LL+MKeWWEWqi7D7u90e+GQf8KOhbVX6iO4tvLN1QRC33KYKBPifb5" - "xDZPzkv6qDrr5dgb/DgUctkApQKUClAqekiXpac2Q5Nwcw7S1AcxKNOYkH6JEbuzBup27V" - "hOHyjJb5w43Z2j7ln3/Pi064fn9lPSXP+8CNzJssAL0pNXpmz0qCp7Hk2K/pyQoZEDRLf4" - "dgJ4dHiYAUBcKvnqbZLHuO+IdT/uO/cdqbXed7S2j/Bu3Gz0+/9WjBjY" -) diff --git a/migrations/models/1_20251216100730_update.py b/migrations/models/1_20251216100730_update.py new file mode 100644 index 0000000..60adb3a --- /dev/null +++ b/migrations/models/1_20251216100730_update.py @@ -0,0 +1,98 @@ +# ruff: noqa +# mypy: ignore-errors +from tortoise import BaseDBAsyncClient + +RUN_IN_TRANSACTION = True + + +async def upgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "creative" DROP COLUMN "placements_count"; + ALTER TABLE "placement" DROP COLUMN "subscriptions_count"; + COMMENT ON COLUMN "placement"."status" IS 'ACTIVE: active +ARCHIVED: archived'; + CREATE UNIQUE INDEX IF NOT EXISTS "uid_subscriptio_placeme_8fcb8f" ON "subscription" ("placement_id", "subscriber_id");""" + + +async def downgrade(db: BaseDBAsyncClient) -> str: + return """ + DROP INDEX IF EXISTS "uid_subscriptio_placeme_8fcb8f"; + ALTER TABLE "creative" ADD "placements_count" INT NOT NULL DEFAULT 0; + ALTER TABLE "placement" ADD "subscriptions_count" INT NOT NULL DEFAULT 0; + COMMENT ON COLUMN "placement"."status" IS 'PENDING: pending +ACTIVE: active +DELETED: deleted +ARCHIVED: archived';""" + + +MODELS_STATE = ( + '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' + 'aq33Ha3tI7wbNxv9/j9vKFN0' +) diff --git a/src/adapter/postgres.py b/src/adapter/postgres.py index 9099334..8c1b078 100644 --- a/src/adapter/postgres.py +++ b/src/adapter/postgres.py @@ -497,3 +497,45 @@ class Postgres(DatabaseBase): async def clear_telegram_state(self, telegram_id: int) -> None: await domain.TelegramState.filter(telegram_id=telegram_id).delete() + + # Count methods + async def count_placements_by_creative(self, creative_id: uuid.UUID) -> int: + return await domain.Placement.filter(creative_id=creative_id).count() + + async def count_subscriptions_by_placement(self, placement_id: uuid.UUID) -> int: + return await domain.Subscription.filter(placement_id=placement_id).count() + + async def count_placements_by_creative_batch(self, creative_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]: + if not creative_ids: + return {} + + from tortoise.functions import Count + + results = ( + await domain.Placement.filter(creative_id__in=creative_ids) + .group_by('creative_id') + .annotate(count=Count('id')) + .values('creative_id', 'count') + ) + + counts = {row['creative_id']: row['count'] for row in results} + return {cid: counts.get(cid, 0) for cid in creative_ids} + + async def count_subscriptions_by_placement_batch(self, placement_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]: + if not placement_ids: + return {} + + from tortoise.functions import Count + + results = ( + await domain.Subscription.filter(placement_id__in=placement_ids) + .group_by('placement_id') + .annotate(count=Count('id')) + .values('placement_id', 'count') + ) + + counts = {row['placement_id']: row['count'] for row in results} + return {pid: counts.get(pid, 0) for pid in placement_ids} + + async def has_placements_for_creative(self, creative_id: uuid.UUID) -> bool: + return await domain.Placement.filter(creative_id=creative_id).exists() diff --git a/src/domain/creative.py b/src/domain/creative.py index 6721a81..eed0e6c 100644 --- a/src/domain/creative.py +++ b/src/domain/creative.py @@ -20,7 +20,6 @@ class Creative(TimestampedModel): name = fields.CharField(max_length=255) text = fields.TextField() status = fields.CharEnumField(CreativeStatus, default=CreativeStatus.ACTIVE) - placements_count = fields.IntField(default=0) workspace: fields.ForeignKeyRelation['Workspace'] = fields.ForeignKeyField( 'models.Workspace', related_name='creatives', on_delete=fields.CASCADE, index=True diff --git a/src/domain/placement.py b/src/domain/placement.py index b649b70..43d39bd 100644 --- a/src/domain/placement.py +++ b/src/domain/placement.py @@ -39,7 +39,6 @@ class Placement(TimestampedModel): invite_link = fields.CharField(max_length=512) status = fields.CharEnumField(PlacementStatus, default=PlacementStatus.ACTIVE) - subscriptions_count = fields.IntField(default=0) project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField( 'models.Project', related_name='placements', on_delete=fields.CASCADE, index=True diff --git a/src/domain/subscription.py b/src/domain/subscription.py index ad6582e..54e133e 100644 --- a/src/domain/subscription.py +++ b/src/domain/subscription.py @@ -34,3 +34,4 @@ class Subscription(TimestampedModel): class Meta: table = 'subscription' + unique_together = (('placement_id', 'subscriber_id'),) diff --git a/src/usecase/__init__.py b/src/usecase/__init__.py index 5a56b92..b3162b7 100644 --- a/src/usecase/__init__.py +++ b/src/usecase/__init__.py @@ -230,6 +230,17 @@ class Database(typing.Protocol): self, subscriber_id: UUID, project_id: UUID ) -> domain.Subscription | None: ... + # Count methods + async def count_placements_by_creative(self, creative_id: UUID) -> int: ... + + async def count_subscriptions_by_placement(self, placement_id: UUID) -> int: ... + + async def count_placements_by_creative_batch(self, creative_ids: list[UUID]) -> dict[UUID, int]: ... + + async def count_subscriptions_by_placement_batch(self, placement_ids: list[UUID]) -> dict[UUID, int]: ... + + async def has_placements_for_creative(self, creative_id: UUID) -> bool: ... + # Post methods async def create_post(self, post: domain.Post) -> None: ... diff --git a/src/usecase/analytics/get_channel_analytics.py b/src/usecase/analytics/get_channel_analytics.py index 03707ea..37e6350 100644 --- a/src/usecase/analytics/get_channel_analytics.py +++ b/src/usecase/analytics/get_channel_analytics.py @@ -65,13 +65,17 @@ async def get_channel_analytics( post_ids = [p.post.id for p in placements if p.post] views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {} + # Batch fetch subscriptions counts + placement_ids = [p.id for p in placements] + subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids) + for p in placements: stats = channel_stats.get(p.placement_channel_id) if stats is None: continue stats.total_cost += p.cost if p.cost is not None else 0 - stats.total_subscriptions += p.subscriptions_count + stats.total_subscriptions += subscriptions_counts.get(p.id, 0) # Get views from batch data if p.post and p.post.id in views_map: diff --git a/src/usecase/analytics/get_creatives_analytics.py b/src/usecase/analytics/get_creatives_analytics.py index d6506f9..8e8ce34 100644 --- a/src/usecase/analytics/get_creatives_analytics.py +++ b/src/usecase/analytics/get_creatives_analytics.py @@ -53,13 +53,17 @@ async def get_creatives_analytics( post_ids = [p.post.id for p in placements if p.post] views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {} + # Batch fetch subscriptions counts + placement_ids = [p.id for p in placements] + subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids) + for p in placements: stats = creative_stats.get(p.creative_id) if stats is None: continue stats.total_cost += p.cost if p.cost is not None else 0 - stats.total_subscriptions += p.subscriptions_count + stats.total_subscriptions += subscriptions_counts.get(p.id, 0) # Get views from batch data if p.post and p.post.id in views_map: diff --git a/src/usecase/analytics/get_placements_analytics.py b/src/usecase/analytics/get_placements_analytics.py index f462d1f..d40bfa4 100644 --- a/src/usecase/analytics/get_placements_analytics.py +++ b/src/usecase/analytics/get_placements_analytics.py @@ -48,6 +48,10 @@ async def get_placements_analytics( post_ids = [p.post.id for p in placements if p.post] views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {} + # Batch fetch subscriptions counts + placement_ids = [p.id for p in placements] + subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids) + return [ dto.PlacementAnalyticsOutput( id=p.id, @@ -59,9 +63,9 @@ async def get_placements_analytics( creative_name=p.creative.name, placement_date=p.placement_date, cost=p.cost, - subscriptions_count=p.subscriptions_count, + subscriptions_count=subscriptions_counts.get(p.id, 0), views_count=views_map[p.post.id][0] if p.post and p.post.id in views_map else None, - cpf=_calculate_cpf(p.cost, p.subscriptions_count), + cpf=_calculate_cpf(p.cost, subscriptions_counts.get(p.id, 0)), cpm=_calculate_cpm(p.cost, views_map[p.post.id][0] if p.post and p.post.id in views_map else None), ) for p in placements diff --git a/src/usecase/analytics/get_spending_analytics.py b/src/usecase/analytics/get_spending_analytics.py index 053a7a4..5feec20 100644 --- a/src/usecase/analytics/get_spending_analytics.py +++ b/src/usecase/analytics/get_spending_analytics.py @@ -74,6 +74,10 @@ async def get_spending_analytics( post_ids = [p.post.id for p in filtered if p.post] views_map = await self.database.get_latest_views_data_batch(post_ids) if post_ids else {} + # Batch fetch subscriptions counts + placement_ids = [p.id for p in filtered] + subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids) + # Группировка по периодам period_data: dict[str, PeriodData] = defaultdict(PeriodData) @@ -85,8 +89,9 @@ async def get_spending_analytics( total_cost += p.cost pd.cost += p.cost - total_subs += p.subscriptions_count - pd.subscriptions += p.subscriptions_count + subs_count = subscriptions_counts.get(p.id, 0) + total_subs += subs_count + pd.subscriptions += subs_count # Get views from batch data if p.post and p.post.id in views_map: diff --git a/src/usecase/creative/create_creative.py b/src/usecase/creative/create_creative.py index 0de7724..8214421 100644 --- a/src/usecase/creative/create_creative.py +++ b/src/usecase/creative/create_creative.py @@ -42,5 +42,5 @@ async def create_creative( project_channel_title=project.channel.title, created_at=creative.created_at, status=creative.status, - placements_count=creative.placements_count, + placements_count=0, ) diff --git a/src/usecase/creative/delete_creative.py b/src/usecase/creative/delete_creative.py index cfb326e..8a8be2a 100644 --- a/src/usecase/creative/delete_creative.py +++ b/src/usecase/creative/delete_creative.py @@ -21,7 +21,8 @@ async def delete_creative(self: 'Usecase', input: dto.DeleteCreativeInput) -> No context.ensure_project_permission(domain.PermissionKey.PROJECTS_WRITE, creative.project_id) - if creative.placements_count > 0: + has_placements = await self.database.has_placements_for_creative(creative.id) + if has_placements: log.warning('Creative %s is used in placements and cannot be deleted', input.creative_id) raise domain.CreativeInUse(input.creative_id) diff --git a/src/usecase/creative/get_creative.py b/src/usecase/creative/get_creative.py index 5a77c7a..ed62a53 100644 --- a/src/usecase/creative/get_creative.py +++ b/src/usecase/creative/get_creative.py @@ -20,6 +20,8 @@ async def get_creative(self: 'Usecase', input: dto.GetCreativeInput) -> dto.Crea context.ensure_project_permission(domain.PermissionKey.PROJECTS_READ, creative.project_id) + placements_count = await self.database.count_placements_by_creative(creative.id) + return dto.CreativeOutput( id=creative.id, name=creative.name, @@ -28,5 +30,5 @@ async def get_creative(self: 'Usecase', input: dto.GetCreativeInput) -> dto.Crea project_channel_title=creative.project.channel.title, created_at=creative.created_at, status=creative.status, - placements_count=creative.placements_count, + placements_count=placements_count, ) diff --git a/src/usecase/creative/get_creatives.py b/src/usecase/creative/get_creatives.py index 97e5ed4..c9ec8e6 100644 --- a/src/usecase/creative/get_creatives.py +++ b/src/usecase/creative/get_creatives.py @@ -24,6 +24,9 @@ async def get_creatives(self: 'Usecase', input: dto.GetCreativesInput) -> list[d allowed_project_ids=allowed_project_ids, ) + creative_ids = [c.id for c in creatives] + placements_counts = await self.database.count_placements_by_creative_batch(creative_ids) + return [ dto.CreativeOutput( id=creative.id, @@ -33,7 +36,7 @@ async def get_creatives(self: 'Usecase', input: dto.GetCreativesInput) -> list[d project_channel_title=creative.project.channel.title, created_at=creative.created_at, status=creative.status, - placements_count=creative.placements_count, + placements_count=placements_counts.get(creative.id, 0), ) for creative in creatives ] diff --git a/src/usecase/creative/update_creative.py b/src/usecase/creative/update_creative.py index b56e814..17c9720 100644 --- a/src/usecase/creative/update_creative.py +++ b/src/usecase/creative/update_creative.py @@ -35,6 +35,8 @@ async def update_creative( await self.database.update_creative(creative) + placements_count = await self.database.count_placements_by_creative(creative.id) + return dto.CreativeOutput( id=creative.id, name=creative.name, @@ -43,5 +45,5 @@ async def update_creative( project_channel_title=creative.project.channel.title, created_at=creative.created_at, status=creative.status, - placements_count=creative.placements_count, + placements_count=placements_count, ) diff --git a/src/usecase/placement/create_placement.py b/src/usecase/placement/create_placement.py index bb33af3..1d51e2c 100644 --- a/src/usecase/placement/create_placement.py +++ b/src/usecase/placement/create_placement.py @@ -39,8 +39,6 @@ async def create_placement( if project.channel.telegram_id is None: raise domain.ChannelNotFound(project.channel.id) - print(f'Телега id: {project.channel.telegram_id}') - invite_link = await self.telegram_bot.create_chat_invite_link(project.channel.telegram_id, requires_approval) placement = domain.Placement( @@ -56,11 +54,7 @@ async def create_placement( status=domain.PlacementStatus.ACTIVE, ) - async with self.database.transaction(): - await self.database.create_placement(placement) - - creative.placements_count += 1 - await self.database.update_creative(creative) + await self.database.create_placement(placement) return dto.PlacementOutput( id=placement.id, @@ -77,6 +71,6 @@ async def create_placement( invite_link_type=placement.invite_link_type, invite_link=placement.invite_link, status=placement.status, - subscriptions_count=placement.subscriptions_count, + subscriptions_count=0, created_at=placement.created_at, ) diff --git a/src/usecase/placement/delete_placement.py b/src/usecase/placement/delete_placement.py index 0bb1bfa..21735d6 100644 --- a/src/usecase/placement/delete_placement.py +++ b/src/usecase/placement/delete_placement.py @@ -21,14 +21,4 @@ async def delete_placement(self: 'Usecase', input: dto.DeletePlacementInput) -> context.ensure_project_permission(domain.PermissionKey.PLACEMENTS_WRITE, placement.project_id) - if placement.status != domain.PlacementStatus.ACTIVE: - await self.database.delete_placement(input.placement_id) - return - - async with self.database.transaction(): - creative = await self.database.get_creative(input.workspace_id, placement.creative_id) - if creative and creative.placements_count > 0: - creative.placements_count -= 1 - await self.database.update_creative(creative) - - await self.database.delete_placement(input.placement_id) + await self.database.delete_placement(input.placement_id) diff --git a/src/usecase/placement/fetch_placement_post_cycle.py b/src/usecase/placement/fetch_placement_post_cycle.py index 107e756..1d49c17 100644 --- a/src/usecase/placement/fetch_placement_post_cycle.py +++ b/src/usecase/placement/fetch_placement_post_cycle.py @@ -12,7 +12,6 @@ log = logging.getLogger(__name__) async def fetch_placement_post_cycle(self: 'Usecase', interval_seconds: int) -> None: log.debug('Starting fetch_placement_post_cycle') - # Получаем все placement без привязанного поста placements_without_post = ( await domain.Placement.filter( post_id__isnull=True, diff --git a/src/usecase/placement/get_placement.py b/src/usecase/placement/get_placement.py index 4ee265c..6808d51 100644 --- a/src/usecase/placement/get_placement.py +++ b/src/usecase/placement/get_placement.py @@ -25,18 +25,11 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl # Compute post-related fields ad_post_url = None - views_count = None - views_availability = domain.PostViewsAvailability.UNKNOWN - last_views_fetch_at = None if placement.post: ad_post_url = generate_post_url(placement.post.channel.username, placement.post.message_id) - # Get latest views data - views_data = await self.database.get_latest_views_data(placement.post.id) - if views_data: - views_count, last_views_fetch_at = views_data - views_availability = domain.PostViewsAvailability.AVAILABLE + subscriptions_count = await self.database.count_subscriptions_by_placement(placement.id) return dto.PlacementOutput( id=placement.id, @@ -53,6 +46,6 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl invite_link_type=placement.invite_link_type, invite_link=placement.invite_link, status=placement.status, - subscriptions_count=placement.subscriptions_count, + subscriptions_count=subscriptions_count, created_at=placement.created_at, ) diff --git a/src/usecase/placement/get_placements.py b/src/usecase/placement/get_placements.py index 334cc89..2f00575 100644 --- a/src/usecase/placement/get_placements.py +++ b/src/usecase/placement/get_placements.py @@ -28,6 +28,9 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> list allowed_project_ids=allowed_project_ids, ) + placement_ids = [p.id for p in placements] + subscriptions_counts = await self.database.count_subscriptions_by_placement_batch(placement_ids) + placement_outputs = [] for placement in placements: ad_post_url = None @@ -51,7 +54,7 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> list invite_link_type=placement.invite_link_type, invite_link=placement.invite_link, status=placement.status, - subscriptions_count=placement.subscriptions_count, + subscriptions_count=subscriptions_counts.get(placement.id, 0), created_at=placement.created_at, ) ) diff --git a/src/usecase/placement/update_placement.py b/src/usecase/placement/update_placement.py index 93f45a5..1b9b397 100644 --- a/src/usecase/placement/update_placement.py +++ b/src/usecase/placement/update_placement.py @@ -44,6 +44,8 @@ async def update_placement( if placement.post: ad_post_url = generate_post_url(placement.post.channel.username, placement.post.message_id) + subscriptions_count = await self.database.count_subscriptions_by_placement(placement.id) + return dto.PlacementOutput( id=placement.id, project_id=placement.project_id, @@ -59,6 +61,6 @@ async def update_placement( invite_link_type=placement.invite_link_type, invite_link=placement.invite_link, status=placement.status, - subscriptions_count=placement.subscriptions_count, + subscriptions_count=subscriptions_count, created_at=placement.created_at, ) diff --git a/src/usecase/project/tg_add_project_1.py b/src/usecase/project/tg_add_project_1.py index 9df2d37..5f63d86 100644 --- a/src/usecase/project/tg_add_project_1.py +++ b/src/usecase/project/tg_add_project_1.py @@ -73,9 +73,18 @@ async def tg_add_project_1(self: 'Usecase', input: dto.ConnectProjectInput) -> d channel = await self.database.get_channel(telegram_id=input.telegram_id) if channel: channel.title = input.title - channel.username = input.username + if input.username is not None: + channel.username = input.username await self.database.update_channel(channel) else: + if input.username is None: + log.warning('Cannot create channel %s without username', input.telegram_id) + await self.telegram_bot.send_message( + f'⚠️ Канал "{input.title}" не может быть подключен.\n\nУ канала отсутствует публичный username.', + input.user_telegram_id, + ) + return None + channel = domain.Channel( telegram_id=input.telegram_id, title=input.title, diff --git a/src/usecase/subscription/handle_subscription.py b/src/usecase/subscription/handle_subscription.py index a8df615..918c6f3 100644 --- a/src/usecase/subscription/handle_subscription.py +++ b/src/usecase/subscription/handle_subscription.py @@ -60,13 +60,9 @@ async def handle_subscription( existing_sub = await self.database.get_subscription_by_subscriber_and_placement(subscriber.id, placement.id) if existing_sub and existing_sub.status == domain.SubscriptionStatus.UNSUBSCRIBED: - async with self.database.transaction(): - existing_sub.status = domain.SubscriptionStatus.ACTIVE - existing_sub.unsubscribed_at = None - await self.database.update_subscription(existing_sub) - - placement.subscriptions_count += 1 - await self.database.update_placement(placement) + existing_sub.status = domain.SubscriptionStatus.ACTIVE + existing_sub.unsubscribed_at = None + await self.database.update_subscription(existing_sub) log.info( 'Subscription reactivated: subscriber %s (telegram_id: %s) resubscribed via same placement %s', @@ -81,11 +77,7 @@ async def handle_subscription( subscriber_id=subscriber.id, invite_link=invite_link, ) - async with self.database.transaction(): - await self.database.create_subscription(subscription) - - placement.subscriptions_count += 1 - await self.database.update_placement(placement) + await self.database.create_subscription(subscription) log.info( 'Subscription created: subscriber %s (telegram_id: %s) subscribed via placement %s (invite_link: %s)', diff --git a/src/usecase/subscription/handle_unsubscription.py b/src/usecase/subscription/handle_unsubscription.py index 83575d0..1b7b8b1 100644 --- a/src/usecase/subscription/handle_unsubscription.py +++ b/src/usecase/subscription/handle_unsubscription.py @@ -34,19 +34,13 @@ async def handle_unsubscription(self: 'Usecase', user_telegram_id: int, channel_ return for subscription in subscriptions: - async with self.database.transaction(): - subscription.status = domain.SubscriptionStatus.UNSUBSCRIBED - subscription.unsubscribed_at = timezone.now() - await self.database.update_subscription(subscription) + subscription.status = domain.SubscriptionStatus.UNSUBSCRIBED + subscription.unsubscribed_at = timezone.now() + await self.database.update_subscription(subscription) - placement = subscription.placement - if placement.subscriptions_count > 0: - placement.subscriptions_count -= 1 - await self.database.update_placement(placement) - - log.info( - 'Subscription marked as unsubscribed: subscriber %s (telegram_id: %s) unsubscribed from placement %s', - subscriber.id, - user_telegram_id, - placement.id, - ) + log.info( + 'Subscription marked as unsubscribed: subscriber %s (telegram_id: %s) unsubscribed from placement %s', + subscriber.id, + user_telegram_id, + subscription.placement_id, + )