feat: проверка существования канала при привязке
This commit is contained in:
@@ -4,3 +4,4 @@ LOGGER__APP_NAME="tgex-backend"
|
||||
LOGGER__PRETTY_CONSOLE=true
|
||||
TELEGRAM__TOKEN=your_bot_token_here
|
||||
JWT__SECRET_KEY=your_secret_key_here_please_generate_a_strong_random_string
|
||||
PARSER__URL=http://localhost:8080
|
||||
@@ -10,9 +10,9 @@ async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"telegram_id" BIGINT UNIQUE,
|
||||
"title" VARCHAR(255),
|
||||
"username" VARCHAR(255),
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"username" VARCHAR(255) NOT NULL UNIQUE,
|
||||
"access_hash" BIGINT,
|
||||
"pts" INT
|
||||
);
|
||||
@@ -253,65 +253,65 @@ MODELS_STATE = (
|
||||
"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"
|
||||
"HuO2Ldj/vOfUe41vuO1vYR3o2bjX7/H/FMJEI="
|
||||
"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"
|
||||
"37jnCt9x2t7SO8Gzcb/f4/gXYk2A=="
|
||||
)
|
||||
90
migrations/models/1_20251215220358_update.py
Normal file
90
migrations/models/1_20251215220358_update.py
Normal file
@@ -0,0 +1,90 @@
|
||||
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 = (
|
||||
"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"
|
||||
)
|
||||
63
src/adapter/parser.py
Normal file
63
src/adapter/parser.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FetchChannelResponse(BaseModel):
|
||||
id: str
|
||||
telegram_id: int
|
||||
username: str
|
||||
title: str
|
||||
access_hash: int
|
||||
pts: int
|
||||
|
||||
|
||||
class ParserClient:
|
||||
def __init__(self, base_url: str, timeout: float = 5.0):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.timeout = timeout
|
||||
|
||||
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None:
|
||||
"""
|
||||
Проверяет существование канала в Telegram и получает его метаданные.
|
||||
|
||||
Returns:
|
||||
FetchChannelResponse если канал найден
|
||||
None если канал не найден (404)
|
||||
|
||||
Raises:
|
||||
httpx.HTTPError для других ошибок (таймаут, network error, 5xx)
|
||||
"""
|
||||
# Убираем @ если пользователь его указал
|
||||
username = username.lstrip('@')
|
||||
|
||||
url = f'{self.base_url}/fetch-telegram-channel'
|
||||
params = {'username': username}
|
||||
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
try:
|
||||
response = await client.get(url, params=params)
|
||||
|
||||
if response.status_code == 404:
|
||||
log.info('Channel @%s not found in Telegram', username)
|
||||
return None
|
||||
|
||||
response.raise_for_status()
|
||||
data: dict[str, Any] = response.json()
|
||||
return FetchChannelResponse.model_validate(data)
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
return None
|
||||
log.error('Parser HTTP error for @%s: %s', username, e)
|
||||
raise
|
||||
except httpx.TimeoutException:
|
||||
log.error('Parser timeout for @%s', username)
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error('Parser unexpected error for @%s: %s', username, e)
|
||||
raise
|
||||
@@ -12,6 +12,10 @@ class AppConfig(BaseModel):
|
||||
URL: str
|
||||
|
||||
|
||||
class ParserConfig(BaseModel):
|
||||
URL: str
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file='.env', case_sensitive=False, env_nested_delimiter='__')
|
||||
|
||||
@@ -20,6 +24,7 @@ class Settings(BaseSettings):
|
||||
logger: LoggerConfig
|
||||
telegram: TelegramConfig
|
||||
jwt: JWTConfig
|
||||
parser: ParserConfig
|
||||
|
||||
|
||||
settings: Settings = load_settings(Settings)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import uuid
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Query
|
||||
from fastapi.routing import APIRouter
|
||||
|
||||
from src import deps, dto
|
||||
@@ -44,11 +44,12 @@ async def get_creative(
|
||||
|
||||
@creatives_router.post('')
|
||||
async def create_creative(
|
||||
request: dto.CreateCreativeInput,
|
||||
workspace_id: uuid.UUID,
|
||||
request: dto.CreateCreativeInput,
|
||||
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
project_id: uuid.UUID = Query(),
|
||||
) -> dto.CreativeOutput:
|
||||
return await deps.get_usecase().create_creative(request, current_user.user_id, workspace_id)
|
||||
return await deps.get_usecase().create_creative(request, project_id, current_user.user_id, workspace_id)
|
||||
|
||||
|
||||
@creatives_router.patch('/{creative_id}')
|
||||
|
||||
@@ -49,6 +49,7 @@ __all__ = (
|
||||
'ChannelNotFound',
|
||||
'ChannelAlreadyExists',
|
||||
'ChannelNoAdminRights',
|
||||
'TelegramChannelNotFound',
|
||||
'CreativeNotFound',
|
||||
'CreativeInUse',
|
||||
'PlacementNotFound',
|
||||
@@ -70,6 +71,7 @@ from .error import (
|
||||
ProjectNotFound,
|
||||
PurchasePlanChannelNotFound,
|
||||
PurchasePlanNotFound,
|
||||
TelegramChannelNotFound,
|
||||
UserByUsernameNotFound,
|
||||
UserNotFound,
|
||||
WorkspaceAccessDenied,
|
||||
|
||||
@@ -4,9 +4,9 @@ from .base import TimestampedModel
|
||||
|
||||
|
||||
class Channel(TimestampedModel):
|
||||
telegram_id = fields.BigIntField(null=True, unique=True, index=True)
|
||||
title = fields.CharField(null=True, max_length=255)
|
||||
username = fields.CharField(null=True, max_length=255, index=True)
|
||||
telegram_id = fields.BigIntField(unique=True, index=True)
|
||||
title = fields.CharField(max_length=255)
|
||||
username = fields.CharField(max_length=255, unique=True, index=True)
|
||||
|
||||
access_hash = fields.BigIntField(null=True)
|
||||
pts = fields.IntField(null=True)
|
||||
|
||||
@@ -31,6 +31,12 @@ def ChannelNotFound(channel_id: uuid.UUID | None = None) -> HTTPException:
|
||||
return HTTPException(status.HTTP_404_NOT_FOUND, f'Channel {channel_id} not found')
|
||||
|
||||
|
||||
def TelegramChannelNotFound(username: str) -> HTTPException:
|
||||
return HTTPException(
|
||||
status.HTTP_404_NOT_FOUND, f'Telegram channel @{username} not found or is not a public channel'
|
||||
)
|
||||
|
||||
|
||||
def ProjectNotFound(project_id: uuid.UUID | None = None) -> HTTPException:
|
||||
if project_id is None:
|
||||
return HTTPException(status.HTTP_404_NOT_FOUND, 'Project not found')
|
||||
|
||||
@@ -15,9 +15,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class PlacementStatus(str, enum.Enum):
|
||||
PENDING = 'pending' # Ожидается публикация поста
|
||||
ACTIVE = 'active' # Пост найден и отслеживается
|
||||
DELETED = 'deleted' # Пост удален из канала
|
||||
ARCHIVED = 'archived' # Вручную архивировано
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class GetCreativeInput(pydantic.BaseModel):
|
||||
class CreateCreativeInput(pydantic.BaseModel):
|
||||
name: str
|
||||
text: str
|
||||
project_id: uuid.UUID
|
||||
|
||||
|
||||
class UpdateCreativeInput(pydantic.BaseModel):
|
||||
|
||||
@@ -22,9 +22,6 @@ class PlacementOutput(pydantic.BaseModel):
|
||||
invite_link: str
|
||||
status: domain.PlacementStatus
|
||||
subscriptions_count: int
|
||||
views_count: int | None
|
||||
views_availability: domain.PostViewsAvailability
|
||||
last_views_fetch_at: datetime.datetime | None
|
||||
created_at: datetime.datetime
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class GetPurchasePlanChannelsOutput(pydantic.BaseModel):
|
||||
|
||||
|
||||
class AttachChannelToPurchasePlanInput(pydantic.BaseModel):
|
||||
username: str
|
||||
username: str = pydantic.Field(pattern=r'^[^@]')
|
||||
status: PurchasePlanChannelStatus | None = None
|
||||
planned_cost: float | None = None
|
||||
comment: str | None = None
|
||||
|
||||
@@ -8,6 +8,7 @@ from shared import logger
|
||||
from shared.worker_base import WorkerConfig
|
||||
from src import deps
|
||||
from src.adapter.jwt import JWT
|
||||
from src.adapter.parser import ParserClient
|
||||
from src.adapter.postgres import Postgres
|
||||
from src.adapter.telegram_bot import TelegramBot
|
||||
from src.config import settings
|
||||
@@ -36,11 +37,13 @@ logger.init(settings.logger)
|
||||
postgres = Postgres(settings.db)
|
||||
telegram = TelegramBot(settings.telegram, telegram_callback_router)
|
||||
jwt_encoder = JWT(settings.jwt)
|
||||
parser_client = ParserClient(base_url=settings.parser.URL)
|
||||
|
||||
usecase = Usecase(
|
||||
database=postgres,
|
||||
telegram_bot=telegram,
|
||||
jwt_encoder=jwt_encoder,
|
||||
parser_client=parser_client,
|
||||
)
|
||||
deps.set_usecase(usecase)
|
||||
|
||||
|
||||
@@ -279,11 +279,22 @@ class JWTEncoder(typing.Protocol):
|
||||
def encode_access_token(self, user_id: UUID, telegram_id: int, username: str | None = None) -> str: ...
|
||||
|
||||
|
||||
class FetchChannelResponse(typing.Protocol):
|
||||
telegram_id: int
|
||||
username: str
|
||||
title: str
|
||||
|
||||
|
||||
class ParserClient(typing.Protocol):
|
||||
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class Usecase:
|
||||
database: Database
|
||||
telegram_bot: TelegramBotWriter
|
||||
jwt_encoder: JWTEncoder
|
||||
parser_client: ParserClient
|
||||
|
||||
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)
|
||||
|
||||
@@ -11,19 +11,19 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def create_creative(
|
||||
self: 'Usecase', input: dto.CreateCreativeInput, user_id: uuid.UUID, workspace_id: uuid.UUID
|
||||
self: 'Usecase', input: dto.CreateCreativeInput, project_id: uuid.UUID, user_id: uuid.UUID, workspace_id: uuid.UUID
|
||||
) -> dto.CreativeOutput:
|
||||
await self.ensure_workspace_permission(
|
||||
workspace_id,
|
||||
user_id,
|
||||
domain.PermissionKey.PROJECTS_WRITE,
|
||||
for_project_id=input.project_id,
|
||||
for_project_id=project_id,
|
||||
)
|
||||
|
||||
project = await self.database.get_project(workspace_id, project_id=input.project_id)
|
||||
project = await self.database.get_project(workspace_id, project_id=project_id)
|
||||
if not project:
|
||||
log.warning('User %s attempted to create creative for unavailable project %s', user_id, input.project_id)
|
||||
raise domain.ProjectNotFound(input.project_id)
|
||||
log.warning('User %s attempted to create creative for unavailable project %s', user_id, project_id)
|
||||
raise domain.ProjectNotFound(project_id)
|
||||
|
||||
creative = domain.Creative(
|
||||
name=input.name,
|
||||
|
||||
@@ -53,7 +53,7 @@ async def create_placement(
|
||||
comment=input.comment,
|
||||
invite_link_type=input.invite_link_type,
|
||||
invite_link=invite_link,
|
||||
status=domain.PlacementStatus.PENDING,
|
||||
status=domain.PlacementStatus.ACTIVE,
|
||||
)
|
||||
|
||||
async with self.database.transaction():
|
||||
@@ -78,8 +78,5 @@ async def create_placement(
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=None,
|
||||
views_availability=domain.PostViewsAvailability.UNKNOWN,
|
||||
last_views_fetch_at=None,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def fetch_placement_post_cycle(self: 'Usecase', interval_seconds: int) -> None:
|
||||
log.info('Starting fetch_placement_post_cycle')
|
||||
log.debug('Starting fetch_placement_post_cycle')
|
||||
|
||||
# Получаем все placement без привязанного поста
|
||||
placements_without_post = (
|
||||
|
||||
@@ -54,8 +54,5 @@ async def get_placement(self: 'Usecase', input: dto.GetPlacementInput) -> dto.Pl
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -28,25 +28,12 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
allowed_project_ids=allowed_project_ids,
|
||||
)
|
||||
|
||||
# Batch fetch views data for all posts
|
||||
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 {}
|
||||
|
||||
# Build output DTOs
|
||||
placement_outputs = []
|
||||
for placement in placements:
|
||||
# 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)
|
||||
views_data = views_map.get(placement.post.id)
|
||||
if views_data:
|
||||
views_count, last_views_fetch_at = views_data
|
||||
views_availability = domain.PostViewsAvailability.AVAILABLE
|
||||
|
||||
placement_outputs.append(
|
||||
dto.PlacementOutput(
|
||||
@@ -65,9 +52,6 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -39,21 +39,11 @@ async def update_placement(
|
||||
|
||||
await self.database.update_placement(placement)
|
||||
|
||||
# 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
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
project_id=placement.project_id,
|
||||
@@ -70,8 +60,5 @@ async def update_placement(
|
||||
invite_link=placement.invite_link,
|
||||
status=placement.status,
|
||||
subscriptions_count=placement.subscriptions_count,
|
||||
views_count=views_count,
|
||||
views_availability=views_availability,
|
||||
last_views_fetch_at=last_views_fetch_at,
|
||||
created_at=placement.created_at,
|
||||
)
|
||||
|
||||
@@ -27,16 +27,20 @@ async def attach_channel_to_purchase_plan(
|
||||
|
||||
plan = await self.ensure_active_purchase_plan(project)
|
||||
|
||||
# Поиск канала по username
|
||||
channel = await self.database.get_channel(username=input.username)
|
||||
|
||||
if not channel:
|
||||
# Создать канал с nullable telegram_id/title (заполнятся парсером позже)
|
||||
channel = domain.Channel(username=input.username)
|
||||
parser_response = await self.parser_client.fetch_telegram_channel(input.username)
|
||||
if not parser_response:
|
||||
raise domain.TelegramChannelNotFound(input.username)
|
||||
|
||||
channel = domain.Channel(
|
||||
username=input.username,
|
||||
telegram_id=parser_response.telegram_id,
|
||||
title=parser_response.title,
|
||||
)
|
||||
await self.database.create_channel(channel)
|
||||
log.info('Created channel with username @%s (pending parser update)', input.username)
|
||||
else:
|
||||
log.info('Found existing channel %s for username @%s', channel.id, input.username)
|
||||
log.info('Created channel @%s with telegram_id=%s', input.username, parser_response.telegram_id)
|
||||
|
||||
plan_channel = await self.database.add_channel_to_purchase_plan(
|
||||
plan.id,
|
||||
|
||||
Reference in New Issue
Block a user