Untitled

 avatar
4ae4d
plain_text
2 months ago
27 kB
5
Indexable
f=services/bot/core/services/base.py
f=services/bot/core/services/event.py
f=services/bot/core/services/file.py
f=services/bot/core/services/__init__.py
f=services/bot/core/services/__pycache__
f=services/bot/core/services/registry
f=services/bot/core/services/report.py
f=services/bot/core/services/requests.py
f=services/bot/core/services/tag.py
f=services/bot/core/services/terbank.py
--- services/bot/core/services/base.py ---
from core.services.registry import BaseServiceRegistry


class BaseService:  # legacy
    def __init__(self, registry: BaseServiceRegistry):  # legacy
        self.registry = registry

--- services/bot/core/services/event.py ---
from datetime import datetime, timezone
from typing import Any

from requests import HTTPError

from core.clients.event import EventClient
from core.schemas import EventCardSchema, ParticipationSchema
from core.services import BaseService
from core.services.registry import BaseServiceRegistry
from core.utils.logger import logger


class EventService(BaseService):  # legacy
    """
    сервис мероприятий на стороне бота.

    на данный момент:
    - ходит в backend через EventClient
    - умеет получать список мероприятий по городу
    - умеет получать одно мероприятие по id
    - создает мероприятие через клиент используя мастера создания
    """

    def __init__(self, registry: BaseServiceRegistry, client: EventClient):  # legacy
        # DI может вызвать клиент без аргументов.
        super().__init__(registry)
        self.client = client

    def get_event_cards_page(
        self,
        *,
        requester_messenger_id: int,
        page: int = 1,
        limit: int = 10,
        title_str: str | None = None,
        tag_ids: list[str] | None = None,
        mine: bool = False,
    ) -> tuple[list[EventCardSchema], int, int]:  # меняем тип возвращаемого списка
        resp = (
            self.client.get_my_event_cards_page(
                requester_messenger_id=requester_messenger_id,
                page=page,
                limit=limit,
                title_str=title_str,
                tag_ids=tag_ids,
            )
            if mine
            else self.client.get_event_cards_page(
                requester_messenger_id=requester_messenger_id,
                page=page,
                limit=limit,
                title_str=title_str,
                tag_ids=tag_ids,
            )
        )

        match resp.status_code:
            case 200:
                pass
            case 400:
                raise HTTPError("Error while getting event cards list: invalid parameters")
            case 401:
                raise HTTPError("Error while getting event cards list: unauthorized")
            case 500:
                raise HTTPError("Error while getting event cards list: internal server error")
            case _:
                raise HTTPError(
                    f"Error while getting event cards list: unexpected status {resp.status_code}"
                )

        body = resp.json() or {}
        items = body.get("event_cards") or []
        page_meta = body.get("page_meta") or {}

        result: list[EventCardSchema] = []
        for item in items:
            if not isinstance(item, dict):
                continue
            result.append(self._map_event_card(item))

        current_page = int(page_meta.get("current_page") or page)
        total_pages = int(page_meta.get("total_pages") or current_page)

        return result, current_page, total_pages

    def sign_up(self, event_id: str, messenger_id: int) -> bool:  # legacy
        """messenger_id = peer.id (как на бэкенде: user.get_by_messenger_id)"""
        resp = self.client.sign_up(event_id=event_id, messenger_id=messenger_id)
        return resp.status_code == 200

    def get_event_by_id(
        self,
        event_id: str,
        requester_messenger_id: int,
        *,
        mine: bool = False,
    ) -> EventCardSchema | None:
        logger.debug(
            "[EventService] get_event_by_id: event_id=%s requester=%s mine=%s",
            event_id,
            requester_messenger_id,
            mine,
        )
        resp = (
            self.client.get_my_event_info(
                event_id=event_id,
                requester_messenger_id=requester_messenger_id,
            )
            if mine
            else self.client.get_event_info(
                event_id=event_id,
                requester_messenger_id=requester_messenger_id,
            )
        )

        match resp.status_code:
            case 200:
                pass
            case 400:
                raise HTTPError("Error while getting event card: invalid parameters")
            case 401:
                raise HTTPError("Error while getting event card: unauthorized")
            case 404:
                return None
            case 500:
                raise HTTPError("Error while getting event card: internal server error")
            case _:
                raise HTTPError(
                    f"Error while getting event card: unexpected status {resp.status_code}"
                )

        logger.debug(
            "[EventService] get_event_by_id: body=%s",
            resp.json(),
        )

        return self._map_event_card(resp.json() or {})

    def sign_out_by_event(self, event_id: str, messenger_id: int) -> bool:
        resp = self.client.sign_out(event_id=event_id, messenger_id=messenger_id)
        return resp.status_code == 200

    def enter_code_by_event(
        self,
        event_id: str,
        messenger_id: int,
        code: str,
    ) -> ParticipationSchema | None:
        resp = self.client.confirm_participation(
            event_id=event_id,
            messenger_id=messenger_id,
            code=code,
        )

        match resp.status_code:
            case 200:
                pass
            case 400:
                return None
            case 401:
                raise HTTPError("Error while confirming participation: unauthorized")
            case 404:
                return None
            case 500:
                raise HTTPError("Error while confirming participation: internal server error")
            case _:
                raise HTTPError(
                    f"Error while confirming participation: unexpected status {resp.status_code}"
                )

        return self.get_participation_status(
            event_id=event_id,
            requester_messenger_id=messenger_id,
        )

    def get_participation_status(self, event_id: str, requester_messenger_id: int):
        resp = self.client.get_event_info(event_id, requester_messenger_id)
        card = self._map_event_card(resp.json() or {})
        return card.participation

    def create_event(
        self,
        *,
        title: str,
        description: str,
        date_obj: datetime,
        gosp_id: str,
        project_id: str,
        verification_code: str,
        hours: int,
        requester_messenger_id: int,
        tags: list[str],
    ):
        """
        создает мероприятие через backend и возвращает dict из backend'a
        - ожидает, что все данные уже провалидированы и разобраны
        - метод не мапит ответ backend'a в EventSchema и не делает никакой обработки, а возвращает ровно тот json, который вернул backend (dect)
        - может бросить Exception, если status_code не 201/200
        """
        date_iso = date_obj.isoformat()

        resp = self.client.create_event(
            title=title,
            description=description,
            date_time=date_iso,
            gosp_id=gosp_id,
            project_id=project_id,
            verification_code=verification_code,
            hours=hours,
            requester_messenger_id=requester_messenger_id,
            tags=tags,
        )

        if resp.status_code not in [200, 201]:
            raise Exception(f"Error while creating event: {resp.status_code}\n{resp.text}")

        return resp.json()

    def create_from_wizard(self, data: dict[str, Any], peer) -> dict[str, Any]:
        title = data.get("event_name", "")
        description = data.get("event_description", "")
        date_obj = data.get("event_date", "")
        time_obj = data.get("event_time", "")
        gosp_id = data.get("gosp_id", "")
        project_id = data.get("project_id", "")
        hours = data.get("hours", "")

        verification_code = data.get("event_bonus_code", "")
        # Теги: массив ID, сформированный в хендлере
        tags: list[str] = data.get("event_app_tag_ids") or []
        # === Формирование RFC3339-совместимой строки ===
        event_dt = datetime.combine(date_obj, time_obj).replace(tzinfo=timezone.utc)
        date_time = event_dt.isoformat().replace("+00:00", "Z")

        resp = self.client.create_event(
            title=title,
            description=description,
            date_time=date_time,
            gosp_id=gosp_id,
            project_id=project_id,
            verification_code=verification_code,
            hours=hours,
            requester_messenger_id=peer.id,
            tags=tags if tags else None,
        )

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while creating event: unauthorized")
            case 500:
                raise HTTPError("Error while creating event: internal server error")
            case _:
                raise HTTPError(f"Error while creating event: unexpected status {resp.status_code}")

        return {"TYPE": resp.json().get("code")}  # показывать карточку мероприятия

    def _map_event_card(self, body: dict[str, Any]) -> EventCardSchema:
        """преобразует json event card в EventCardSchema"""
        return EventCardSchema.from_dict(body)

--- services/bot/core/services/file.py ---
from __future__ import annotations

from concurrent.futures import TimeoutError

from core.config import bot
from core.utils.logger import logger


class FileService:  # legacy
    @staticmethod
    def send_file(  # legacy
        peer,
        *,
        file_bytes,
        filename: str,
        uid: str,
        text: str,
        reply_mid=None,
        interactive_media_groups=None,
    ) -> bool:
        logger.info("FileService:")
        try:
            logger.info(f"sending file {uid}, name={filename}, size={len(file_bytes)} bytes")

            task = (
                bot.messaging.send_file(
                    peer=peer,
                    file=file_bytes,
                    name=filename,
                    text=text,
                )
                if interactive_media_groups is None
                else bot.messaging.send_file(
                    peer=peer,
                    file=file_bytes,
                    name=filename,
                    text=text,
                    interactive_media_groups=interactive_media_groups,
                )
            )

            result = task.wait()

            if isinstance(result, Exception):
                logger.error(f"Sending file failed. uid={uid}, name={filename}: {result!r}")
                raise result

            logger.info(f"file sent successfully: {result!r}")
            return True

        except TimeoutError as e:
            logger.error(f"Timeout sending file {uid}, name={filename}: {e}")
            return False
        except Exception as e:
            logger.error(f"Error sending file {uid}, name={filename}: {e}")
            return False

--- services/bot/core/services/__init__.py ---
from .base import BaseService
from .event import EventService
from .file import FileService
from .report import ReportService
from .requests import RequestService
from .tag import TagService
from .terbank import TerbankService
from .user import UserService

__all__ = [
    "BaseService",
    "EventService",
    "FileService",
    "ReportService",
    "RequestService",
    "TagService",
    "TerbankService",
    "UserService",
]

Error: 'services/bot/core/services/__pycache__' is not a valid file
Error: 'services/bot/core/services/registry' is not a valid file
--- services/bot/core/services/report.py ---
from __future__ import annotations

from urllib.parse import unquote

from requests import HTTPError

from core.clients.report import ReportClient
from core.services import BaseService
from core.services.registry import BaseServiceRegistry


class ReportService(BaseService):  # legacy
    def __init__(self, registry: BaseServiceRegistry, client: ReportClient):  # legacy
        super().__init__(registry)
        self.client = client

    def create_report(
        self,
        *,
        event_id: str,
        requester_messenger_id: int,
        description: str,
        beneficiaries: int,
    ) -> None:
        resp = self.client.create_report(
            event_id=event_id,
            requester_messenger_id=requester_messenger_id,
            description=description,
            beneficiaries=beneficiaries,
        )

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while creating report: unauthorized")
            case 403:
                raise HTTPError("Error while creating report: forbidden")
            case 404:
                raise HTTPError("Error while creating report: event not found")
            case 500:
                raise HTTPError("Error while creating report: internal server error")
            case _:
                raise HTTPError(
                    f"Error while creating report: unexpected status {resp.status_code}"
                )

    def get_report(
        self,
        *,
        event_id: str,
        requester_messenger_id: int,
    ) -> tuple[bytes, str]:
        resp = self.client.get_report(
            event_id=event_id,
            requester_messenger_id=requester_messenger_id,
        )

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while getting report: unauthorized")
            case 403:
                raise HTTPError("Error while getting report: forbidden")
            case 404:
                raise HTTPError("Error while getting report: event not found")
            case 500:
                raise HTTPError("Error while getting report: internal server error")
            case _:
                raise HTTPError(f"Error while getting report: unexpected status {resp.status_code}")

        filename = "event_report.xlsx"
        cd = resp.headers.get("Content-Disposition")
        if cd and "filename=" in cd:
            try:
                raw = cd.split("filename=", 1)[1].strip().strip('"')
                filename = unquote(raw) or filename
            except Exception:
                pass

        return resp.content, filename

    def report_exists(  # legacy
        self,
        *,
        event_id: str,
        requester_messenger_id: int,
    ) -> bool:
        resp = self.client.get_report(
            event_id=event_id,
            requester_messenger_id=requester_messenger_id,
        )
        if resp.status_code == 200:
            return True
        if resp.status_code in (401, 403, 404):
            return False
        self._raise_http_error("Error while checking report existance", resp)
        return False

--- services/bot/core/services/requests.py ---
from core.clients.request import RequestClient
from core.schemas import RequestCardSchema, RequestCardsPageSchema
from core.services import BaseService
from core.services.registry import BaseServiceRegistry
from core.utils.logger import logger


class RequestService(BaseService):
    """Сервис для работы с заявками"""

    def __init__(self, registry: BaseServiceRegistry, client: RequestClient):
        super().__init__(registry)
        self.client = client

    def get_request_cards_page(
        self,
        *,
        requester_messenger_id: int,
        page: int = 1,
        limit: int = 10,
        status: str | None = None,
    ) -> RequestCardsPageSchema:
        """Получает страницу с заявками"""
        resp = self.client.get_request_cards(
            requester_messenger_id=requester_messenger_id,
            page=page,
            limit=limit,
            status=status,
        )

        if resp.status_code != 200:
            logger.error(f"[RequestService] get_request_cards_page failed: {resp.status_code}")
            return RequestCardsPageSchema(request_cards=[], current_page=1, total_pages=1)

        try:
            return RequestCardsPageSchema.from_response(resp.json())
        except Exception as e:
            logger.error(f"[RequestService] Failed to parse response: {e}")
            return RequestCardsPageSchema(request_cards=[], current_page=1, total_pages=1)

    def get_request_by_id(
        self, request_id: str, requester_messenger_id: int
    ) -> RequestCardSchema | None:
        """Получает заявку по ID"""
        resp = self.client.get_request(request_id, requester_messenger_id)

        if resp.status_code == 404:
            return None
        if resp.status_code != 200:
            logger.error(f"[RequestService] get_request_by_id failed: {resp.status_code}")
            return None

        try:
            return RequestCardSchema.from_dict(resp.json())
        except Exception as e:
            logger.error(f"[RequestService] Failed to parse request: {e}")
            return None

    def approve_request(self, request_id: str, requester_messenger_id: int) -> bool:
        """Одобряет заявку"""
        resp = self.client.approve_request(request_id, requester_messenger_id)
        return resp.status_code == 200

    def reject_request(self, request_id: str, requester_messenger_id: int, reason: str) -> bool:
        """Отклоняет заявку с причиной"""
        resp = self.client.reject_request(request_id, requester_messenger_id, reason)
        return resp.status_code == 200

--- services/bot/core/services/tag.py ---
from requests import HTTPError

from core.clients import TagClient
from core.schemas import TagSchema
from core.services import BaseService
from core.services.registry import BaseServiceRegistry
from core.utils.logger import logger


class TagService(BaseService):
    def __init__(self, registry: BaseServiceRegistry, client: TagClient):
        super().__init__(registry)
        self.client = client

    def get_all_tags(self) -> list[TagSchema]:
        resp = self.client.get_all_tags()

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while getting tags list: unauthorized")
            case 500:
                raise HTTPError("Error while getting tags list: internal server error")
            case _:
                raise HTTPError(
                    f"Error while getting tags list: unexpected status {resp.status_code}"
                )

        return [TagSchema(**item) for item in resp.json()]

    def sign_up(self, tag_id: str, messenger_id: int) -> bool:
        resp = self.client.sign_up(tag_id, messenger_id)
        if resp.status_code != 200:
            try:
                logger.error(
                    "[TagService]: sign_up failed tag_id=%s messenger_id=%s status=%s body=%s",
                    tag_id,
                    messenger_id,
                    resp.status_code,
                    resp.text,
                )
            except Exception as e:
                logger.error("[TagService]: sign up failed")
                logger.error("[TagService]: logger failed while logging an error: %s", e)
        return resp.status_code == 200

    def sign_out(self, tag_id: str, messenger_id: int) -> bool:
        resp = self.client.sign_out(tag_id, messenger_id)
        if resp.status_code != 200:
            try:
                logger.error(
                    "[TagService]: sign_out failed tag_id=%s messenger_id=%s status=%s body=%s",
                    tag_id,
                    messenger_id,
                    resp.status_code,
                    resp.text,
                )
            except Exception as e:
                logger.error("[TagService]: sign up failed")
                logger.error("[TagService]: logger failed while logging an error: %s", e)
        return resp.status_code == 200

--- services/bot/core/services/terbank.py ---
from requests import HTTPError

from core.clients import TerbankClient
from core.schemas import GosbSchema, TerbankSchema
from core.services import BaseService
from core.services.registry import BaseServiceRegistry


class TerbankService(BaseService):
    """
    сервис для заголовка "terbanks" бэкенда.
    здесь обрабатывается всё что связано со структурой, включая ГОСБы
    (и наверное потом модераторы)
    """

    def __init__(self, registry: BaseServiceRegistry, client: TerbankClient):
        super().__init__(registry)
        self.client = client

    def get_all_tbs(self, messenger_id: int) -> list[TerbankSchema]:
        resp = self.client.get_all_tbs(messenger_id)

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while getting TBs list: unauthorized")
            case 500:
                raise HTTPError("Error while getting TBs list: internal server error")
            case _:
                raise HTTPError(
                    f"Error while getting TBs list: unexpected status {resp.status_code}"
                )

        return [TerbankSchema(**item) for item in resp.json()]

    def get_gosbs_for_tb(self, tb_id: str, messenger_id: int) -> list[GosbSchema]:
        resp = self.client.get_gosbs_for_tb(tb_id, messenger_id)

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while getting GOSBs list: unauthorized")
            case 404:
                raise HTTPError("Error while getting GOSBs list: terbank not found")
            case 500:
                raise HTTPError("Error while getting GOSBs list: internal server error")
            case _:
                raise HTTPError(
                    f"Error while getting GOSBs list: unexpected status {resp.status_code}"
                )

        return [GosbSchema(**item) for item in resp.json()]

--- services/bot/core/services/user.py ---
from requests import HTTPError

from core.clients.user import UserClient
from core.schemas import TagSchema, UserSchema
from core.services import BaseService
from core.services.registry import BaseServiceRegistry
from core.utils.logger import logger


class UserService(BaseService):  # legacy
    def __init__(
        self, registry: BaseServiceRegistry, messenger_id: int, client: UserClient
    ):  # legacy
        super().__init__(registry)
        self.messenger_id = messenger_id
        self.client = client

    def get(self) -> UserSchema | None:  # legacy
        resp = self.client.get_by_messenger_id(messenger_id=self.messenger_id)
        if resp.status_code == 200:
            body = resp.json() or {}
            payload = {
                **(body.get("volunteer") or {}),
                "tags": body.get("tags") or [],
            }
            return self._map_user(payload)
        if resp.status_code == 404:
            return None

        raise Exception(f"Error while getting user: {resp.status_code}.\n{resp.text}")

    def ensure_user_exists(self, name: str, messenger_id) -> None:
        logger.debug("[UserService]: ensure_user_exists is called")
        self.client.ensure_user_exists(name, messenger_id)

    def get_user_by_user_data(self, messenger_id: int) -> UserSchema | None:
        logger.debug("[UserService]: get_user_by_user_data is called")
        resp = self.client.get_by_messenger_id(messenger_id)

        match resp.status_code:
            case 200:
                pass
            case 401:
                raise HTTPError("Error while getting user: unauthorized")
            case 404:
                return None
            case 500:
                raise HTTPError("Error while getting user: internal server error")
            case _:
                raise HTTPError(f"Error while getting user: unexpected status {resp.status_code}")

        resp_json = resp.json() or {}
        payload = {
            **(resp_json.get("volunteer") or {}),
            "tags": resp_json.get("tags") or [],
        }

        return UserSchema(**payload)

    def _map_user(self, body: dict) -> UserSchema:  # legacy
        tags_data = body.get("tags") or []
        tags = []
        for tag_data in tags_data:
            if isinstance(tag_data, dict):
                try:
                    tag = TagSchema(
                        id=str(tag_data.get("id", "")),
                        title=str(tag_data.get("title", "")),
                        description=str(tag_data.get("description", "")),
                        depricated=bool(tag_data.get("depricated", False)),
                        created_at=str(tag_data.get("created_at") or ""),
                        updated_at=str(tag_data.get("updated_at") or ""),
                    )
                    tags.append(tag)
                except Exception as e:
                    logger.error(f"[UserService] failed to map tag: {e}")

        return UserSchema(
            id=str(body.get("id") or ""),
            leader=bool(body.get("leader") or False),
            admin=bool(body.get("admin") or False),
            messenger_id=int(body.get("messenger_id") or 0),
            name=str(body.get("full_name") or body.get("name") or ""),
            hours=int(body.get("hours") or 0),
            created_at=str(body.get("created_at") or ""),
            updated_at=str(body.get("updated_at") or ""),
            tags=tags,
        )


def func_or_none(func, arg):  # legacy
    if arg is not None:
        return func(arg)
    return None

Editor is loading...
Leave a Comment