import hashlib import re from .database import BotDatabase from .domain import Binding TOKEN_PATTERN = re.compile(r"^[A-Za-z0-9_-]{20,100}$") def hash_token(token: str) -> bytes: return hashlib.sha256(token.encode("utf-8")).digest() class BotBindingRepository: """Доступ бота только к функциям БД схемы binding. Обертка над хранимыми функциями БД.""" def __init__(self, database: BotDatabase) -> None: self.database = database async def consume( self, token_hash: bytes, telegram_user_id: int, telegram_chat_id: int ) -> Binding | None: async with self.database.transaction() as connection: cursor = await connection.execute( """ SELECT * FROM binding.consume_v1(%s, %s, %s) """, (token_hash, telegram_user_id, telegram_chat_id) ) row = await cursor.fetchone() return self._binding(row) if row else None async def find( self, telegram_user_id: int, member_id: str | None = None, ) -> Binding | None: async with self.database.transaction() as connection: cursor = await connection.execute( """ SELECT * FROM binding.find_by_telegram_v1(%s, %s) """, (telegram_user_id, member_id) ) row = await cursor.fetchone() return self._binding(row) if row else None @staticmethod def _binding(row: dict) -> Binding: return Binding( member_id=str(row["member_id"]), domain=str(row["domain"]), bitrix_user_id=int(row["bitrix_user_id"]), telegram_user_id=int(row["telegram_user_id"]) ) class BindingService: """Служба управления привязками пользователей.""" def __init__( self, repository: BotBindingRepository ) -> None: self.repository = repository async def consume( self, token: str, telegram_user_id: int, telegram_chat_id: int ) -> Binding | None: if not TOKEN_PATTERN.fullmatch(token): return None return await self.repository.consume( hash_token(token), telegram_user_id, telegram_chat_id ) async def find(self, telegram_user_id: int) -> Binding | None: return await self.repository.find(telegram_user_id)