feat: явно указаны зависимости к Protocol для перехода к имплементации
This commit is contained in:
12
shared/jwt_base.py
Normal file
12
shared/jwt_base.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class JWTConfig(BaseModel):
|
||||||
|
SECRET_KEY: str
|
||||||
|
ALGORITHM: str = 'HS256'
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
|
||||||
|
|
||||||
|
|
||||||
|
class JWTBase:
|
||||||
|
def __init__(self, config: JWTConfig) -> None:
|
||||||
|
self.config = config
|
||||||
@@ -4,11 +4,8 @@ import uuid
|
|||||||
import jwt
|
import jwt
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
|
from shared.jwt_base import JWTBase
|
||||||
class JWTConfig(pydantic.BaseModel):
|
from src.usecase import JWTEncoder
|
||||||
SECRET_KEY: str
|
|
||||||
ALGORITHM: str = 'HS256'
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
|
|
||||||
|
|
||||||
|
|
||||||
class JWTPayload(pydantic.BaseModel):
|
class JWTPayload(pydantic.BaseModel):
|
||||||
@@ -17,10 +14,7 @@ class JWTPayload(pydantic.BaseModel):
|
|||||||
username: str | None
|
username: str | None
|
||||||
|
|
||||||
|
|
||||||
class JWT:
|
class JWT(JWTBase, JWTEncoder):
|
||||||
def __init__(self, config: JWTConfig) -> None:
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def encode_access_token(
|
def encode_access_token(
|
||||||
self,
|
self,
|
||||||
user_id: uuid.UUID,
|
user_id: uuid.UUID,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from typing import Any
|
|||||||
import httpx
|
import httpx
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.usecase import Parser
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -16,23 +18,12 @@ class FetchChannelResponse(BaseModel):
|
|||||||
pts: int
|
pts: int
|
||||||
|
|
||||||
|
|
||||||
class ParserClient:
|
class ParserClient(Parser):
|
||||||
def __init__(self, base_url: str, timeout: float = 5.0):
|
def __init__(self, base_url: str, timeout: float = 5.0):
|
||||||
self.base_url = base_url.rstrip('/')
|
self.base_url = base_url.rstrip('/')
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None:
|
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None:
|
||||||
"""
|
|
||||||
Проверяет существование канала в Telegram и получает его метаданные.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
FetchChannelResponse если канал найден
|
|
||||||
None если канал не найден (404)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
httpx.HTTPError для других ошибок (таймаут, network error, 5xx)
|
|
||||||
"""
|
|
||||||
# Убираем @ если пользователь его указал
|
|
||||||
username = username.lstrip('@')
|
username = username.lstrip('@')
|
||||||
|
|
||||||
url = f'{self.base_url}/fetch-telegram-channel'
|
url = f'{self.base_url}/fetch-telegram-channel'
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ from tortoise.transactions import in_transaction
|
|||||||
|
|
||||||
from shared.datebase_base import DatabaseBase
|
from shared.datebase_base import DatabaseBase
|
||||||
from src import domain
|
from src import domain
|
||||||
|
from usecase import Database
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Postgres(DatabaseBase):
|
class Postgres(DatabaseBase, Database):
|
||||||
def transaction(self) -> typing.AsyncContextManager[None]:
|
def transaction(self) -> typing.AsyncContextManager[None]:
|
||||||
return in_transaction()
|
return in_transaction()
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import logging
|
|||||||
from aiogram.types import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
from shared.telegram_base import TelegramBase
|
from shared.telegram_base import TelegramBase
|
||||||
|
from usecase import TelegramBotWriter
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TelegramBot(TelegramBase):
|
class TelegramBot(TelegramBase, TelegramBotWriter):
|
||||||
async def send_message(self, text: str, chat_id: int) -> None:
|
async def send_message(self, text: str, chat_id: int) -> None:
|
||||||
await self.bot.send_message(chat_id=chat_id, text=text)
|
await self.bot.send_message(chat_id=chat_id, text=text)
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
|
|
||||||
from shared.config_helper import load_settings
|
from shared.config_helper import load_settings
|
||||||
from shared.datebase_base import DatabaseConfig
|
from shared.datebase_base import DatabaseConfig
|
||||||
|
from shared.jwt_base import JWTConfig
|
||||||
from shared.logger import LoggerConfig
|
from shared.logger import LoggerConfig
|
||||||
from shared.telegram_base import TelegramConfig
|
from shared.telegram_base import TelegramConfig
|
||||||
from src.adapter.jwt import JWTConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AppConfig(BaseModel):
|
class AppConfig(BaseModel):
|
||||||
|
|||||||
@@ -17,15 +17,12 @@ async def on_chat_join_request(event: ChatJoinRequest) -> None:
|
|||||||
log.error('Failed to get user data from chat join request')
|
log.error('Failed to get user data from chat join request')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Получаем invite_link, по которой пользователь отправил запрос
|
|
||||||
invite_link = event.invite_link
|
invite_link = event.invite_link
|
||||||
if not invite_link or not invite_link.invite_link:
|
if not invite_link or not invite_link.invite_link:
|
||||||
log.debug('No invite_link in chat join request event')
|
log.debug('No invite_link in chat join request event')
|
||||||
return
|
return
|
||||||
|
|
||||||
usecase = deps.get_usecase()
|
await deps.get_usecase().handle_subscription(
|
||||||
|
|
||||||
await usecase.handle_subscription(
|
|
||||||
user_telegram_id=event.from_user.id,
|
user_telegram_id=event.from_user.id,
|
||||||
username=event.from_user.username,
|
username=event.from_user.username,
|
||||||
invite_link=invite_link.invite_link,
|
invite_link=invite_link.invite_link,
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ async def on_chat_member_updated(event: ChatMemberUpdated) -> None:
|
|||||||
is_now_not_member = new_status in ('left', 'kicked')
|
is_now_not_member = new_status in ('left', 'kicked')
|
||||||
user_left = was_member and is_now_not_member
|
user_left = was_member and is_now_not_member
|
||||||
|
|
||||||
usecase = deps.get_usecase()
|
|
||||||
|
|
||||||
if user_joined:
|
if user_joined:
|
||||||
invite_link = event.invite_link.invite_link if event.invite_link else None
|
invite_link = event.invite_link.invite_link if event.invite_link else None
|
||||||
if not invite_link:
|
if not invite_link:
|
||||||
log.debug('No invite_link in chat member update event')
|
log.debug('No invite_link in chat member update event')
|
||||||
return
|
return
|
||||||
|
|
||||||
await usecase.handle_subscription(
|
await deps.get_usecase().handle_subscription(
|
||||||
user_telegram_id=event.from_user.id, username=event.from_user.username, invite_link=invite_link
|
user_telegram_id=event.from_user.id, username=event.from_user.username, invite_link=invite_link
|
||||||
)
|
)
|
||||||
|
|
||||||
elif user_left:
|
elif user_left:
|
||||||
await usecase.handle_unsubscription(user_telegram_id=event.from_user.id, channel_telegram_id=event.chat.id)
|
await deps.get_usecase().handle_unsubscription(
|
||||||
|
user_telegram_id=event.from_user.id, channel_telegram_id=event.chat.id
|
||||||
|
)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ usecase = Usecase(
|
|||||||
database=postgres,
|
database=postgres,
|
||||||
telegram_bot=telegram,
|
telegram_bot=telegram,
|
||||||
jwt_encoder=jwt_encoder,
|
jwt_encoder=jwt_encoder,
|
||||||
parser_client=parser_client,
|
parser=parser_client,
|
||||||
)
|
)
|
||||||
deps.set_usecase(usecase)
|
deps.set_usecase(usecase)
|
||||||
|
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class FetchChannelResponse(typing.Protocol):
|
|||||||
title: str
|
title: str
|
||||||
|
|
||||||
|
|
||||||
class ParserClient(typing.Protocol):
|
class Parser(typing.Protocol):
|
||||||
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None: ...
|
async def fetch_telegram_channel(self, username: str) -> FetchChannelResponse | None: ...
|
||||||
|
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ class Usecase:
|
|||||||
database: Database
|
database: Database
|
||||||
telegram_bot: TelegramBotWriter
|
telegram_bot: TelegramBotWriter
|
||||||
jwt_encoder: JWTEncoder
|
jwt_encoder: JWTEncoder
|
||||||
parser_client: ParserClient
|
parser: Parser
|
||||||
|
|
||||||
async def ensure_workspace_access(self, workspace_id: UUID, user_id: UUID) -> domain.Workspace:
|
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)
|
workspace = await self.database.get_workspace_for_user(workspace_id, user_id)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async def attach_channel_to_purchase_plan(
|
|||||||
channel = await self.database.get_channel(username=input.username)
|
channel = await self.database.get_channel(username=input.username)
|
||||||
|
|
||||||
if not channel:
|
if not channel:
|
||||||
parser_response = await self.parser_client.fetch_telegram_channel(input.username)
|
parser_response = await self.parser.fetch_telegram_channel(input.username)
|
||||||
if not parser_response:
|
if not parser_response:
|
||||||
raise domain.TelegramChannelNotFound(input.username)
|
raise domain.TelegramChannelNotFound(input.username)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user