Untitled

 avatar
4ae4d
plain_text
11 days ago
108 kB
4
Indexable
f=services/bot/core/services/event.py
f=services/bot/core/clients/event.py
f=services/bot/core/schemas/event.py
f=services/bot/core/utils/events_filter.py
f=services/bot/core/utils/events_filters.py
f=services/bot/core/utils/__init__.py
f=services/bot/core/handlers/events_filters.py
f=services/bot/core/markups/event.py
f=services/bot/core/markups/__init__.py
f=services/bot/core/handlers/moderation_events.py
f=services/bot/core/handlers/my_events.py
f=services/bot/core/handlers/points.py
f=services/bot/core/handlers/__init__.py
--- services/bot/core/services/event.py ---
from datetime import datetime, timezone
from typing import Any

from core.clients import EventClient
from core.schemas import EventCardSchema, ParticipationSchema
from core.services.base import BaseService
from core.services.registry import BaseServiceRegistry


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

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

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

    def get_event_cards_page(
        self,
        *,
        requester_messenger_id: str,
        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,
            )
        )

        self._check_response(resp, ctx="getting event card list")

        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):
                raise ValueError("Invalid event card contract: item must be dict")
            result.append(EventCardSchema.from_dict(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: str) -> str | None:
        """messenger_id = peer.id (как на бэкенде: user.get_by_messenger_id)"""
        resp = self.client.sign_up(event_id=event_id, messenger_id=messenger_id)
        if resp.status_code == 409:
            return resp.json()["error"]
        self._check_response(resp, ctx="signing up")
        return None

    def get_event_by_id(
        self,
        event_id: str,
        requester_messenger_id: str,
    ) -> EventCardSchema | None:
        resp = self.client.get_event_info(
            event_id=event_id,
            requester_messenger_id=requester_messenger_id,
        )
        if resp.status_code == 400:
            return None
        self._check_response(resp, ctx="getting event card")
        return EventCardSchema.from_dict(resp.json() or {})

    def sign_out_by_event(self, event_id: str, messenger_id: str) -> 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: str,
        code: str,
    ) -> ParticipationSchema | None:
        resp = self.client.confirm_participation(
            event_id=event_id,
            messenger_id=messenger_id,
            code=code,
        )
        if resp.status_code in {400, 404}:
            return None
        self._check_response(resp, ctx="confirming participation")

        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: str):
        resp = self.client.get_event_info(event_id, requester_messenger_id)
        card = EventCardSchema.from_dict(resp.json() or {})
        return card.participation

    def add_tag(self, event_id: str, tag_id: str, messenger_id: str) -> bool:
        resp = self.client.add_tag(event_id=event_id, tag_id=tag_id, messenger_id=messenger_id)
        return resp.status_code == 200

    def remove_tag(self, event_id: str, tag_id: str, messenger_id: str) -> bool:
        resp = self.client.remove_tag(event_id=event_id, tag_id=tag_id, messenger_id=messenger_id)
        return resp.status_code == 200

    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", "")
        gosb_id = data.get("gosb_id", "")
        project_id = data.get("project_id", "")
        hours = data.get("hours", "")
        participation_limit = data.get("event_participation_limit", "")

        verification_code = data.get("event_verification_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,
            gosb_id=gosb_id,
            project_id=project_id,
            verification_code=verification_code,
            hours=hours,
            participation_limit=participation_limit,
            requester_messenger_id=peer.id,
            tags=tags if tags else None,
        )
        self._check_response(resp, ctx="creating event")

        body = resp.json() or {}
        return {
            "TYPE": resp.json().get("create_type"),
            "event_id": body.get("event_id"),
            "request_id": body.get("request_id"),
        }

    def update_event(self, event_id: str, messenger_id: str, payload: dict[str, int | str]) -> bool:
        resp = self.client.update_event(
            event_id=event_id,
            messenger_id=messenger_id,
            payload=payload,
        )
        self._check_response(resp, ctx="patching event")
        return True

--- services/bot/core/clients/event.py ---
from core.clients.base import BaseApiClient


class EventClient(BaseApiClient):
    def get_event_cards_page(
        self,
        *,
        requester_messenger_id: str,
        page: int = 1,
        limit: int = 10,
        title_str: str | None = None,
        tag_ids: list[str] | None = None,
    ):
        params: dict = {
            "page": page,
            "limit": limit,
        }
        if title_str:
            params["title_str"] = title_str
        if tag_ids:
            params["tag_id"] = tag_ids

        return self.get(
            "/events/card/list",
            headers={"Authorization": requester_messenger_id},
            params=params,
        )

    def get_my_event_cards_page(
        self,
        *,
        requester_messenger_id: str,
        page: int = 1,
        limit: int = 10,
        title_str: str | None = None,
        tag_ids: list[str] | None = None,
    ):
        params: dict = {
            "page": page,
            "limit": limit,
        }
        if title_str:
            params["title_str"] = title_str
        if tag_ids:
            params["tag_id"] = tag_ids

        return self.get(
            "/events/card/list/mine",
            headers={"Authorization": requester_messenger_id},
            params=params,
        )

    def get_event_info(self, event_id: str, requester_messenger_id: str):
        """GET /events/{event_id}/card"""
        return self.get(
            f"/events/{event_id}/card",
            headers={"Authorization": requester_messenger_id},
        )

    def sign_up(self, *, event_id: str, messenger_id: str):
        """POST /events/{event_id}/participations/signup"""
        return self.post(
            f"/events/{event_id}/participations/signup",
            headers={"Authorization": messenger_id},
        )

    def sign_out(self, *, event_id: str, messenger_id: str):
        """DELETE /events/{event_id}/participations/cancel"""
        return self.delete(
            f"/events/{event_id}/participations/cancel",
            headers={"Authorization": messenger_id},
        )

    def confirm_participation(self, *, event_id: str, messenger_id: str, code: str):
        """POST /events/{event_id}/participations/confirm"""
        return self.post(
            f"/events/{event_id}/participations/confirm",
            headers={"Authorization": messenger_id},
            json={"code": code},
        )

    def add_tag(self, *, event_id: str, tag_id: str, messenger_id: str):
        """POST /events/{event_id}/tags"""
        return self.post(
            f"/events/{event_id}/tags",
            headers={"Authorization": messenger_id},
            json={"tag_id": tag_id},
        )

    def remove_tag(self, *, event_id: str, tag_id: str, messenger_id: str):
        """DELETE /events/{event_id}/tags"""
        return self.delete(
            f"/events/{event_id}/tags",
            headers={"Authorization": messenger_id},
            json={"tag_id": tag_id},
        )

    def create_event(
        self,
        *,
        title: str,
        description: str,
        date_time: str,
        gosb_id: str,
        project_id: str = "",
        verification_code: str,
        hours: int,
        participation_limit: int,
        requester_messenger_id: str,
        tags: list[str] | None = None,
    ):
        """POST /events"""
        payload = {
            "activation_code": verification_code,
            "hours": hours,
            "date_time": date_time,
            "description": description,
            "gosb_id": gosb_id,
            "project_id": project_id,
            "title": title,
            "participation_limit": participation_limit,
        }
        if tags:
            payload["selected_tags"] = tags
        return self.post(
            "/events",
            headers={"Authorization": requester_messenger_id},
            json=payload,
        )

    def update_event(self, event_id: str, messenger_id: str, payload: dict[str, int | str]):
        """PATCH /events/{event_id}"""
        return self.patch(
            f"/events/{event_id}",
            headers={"Authorization": messenger_id},
            json=payload,
        )

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

from pydantic import BaseModel, Field

from core.schemas.gosb import GosbSchema
from core.schemas.participation import ParticipationSchema
from core.schemas.tag import TagSchema
from core.schemas.terbank import TerbankSchema
from core.utils import logger, parse_datetime_value, require_datetime


class AllowedActionsSchema(BaseModel):
    """Схема доступных действий"""

    can_view_code: bool
    can_edit: bool
    can_sign_up: bool
    can_sign_out: bool

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "AllowedActionsSchema":
        return cls(
            can_view_code=bool(data.get("can_view_code", False)),
            can_edit=bool(data.get("can_edit", False)),
            can_sign_up=bool(data.get("can_signup", False)),
            can_sign_out=bool(data.get("can_signout", False)),
        )


class EventCardSchema(BaseModel):
    """Схема карточки мероприятия из списка"""

    event: "EventSchema"
    tags: list[TagSchema] = Field(default_factory=list)
    gosb: GosbSchema
    terbank: TerbankSchema
    participation: ParticipationSchema | None = None
    allowed_actions: AllowedActionsSchema | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "EventCardSchema":
        gosb_data = data.get("gosb")
        logger.info(f"DATA: {data}")
        if not isinstance(gosb_data, dict):
            logger.error(f"{gosb_data}, {type(gosb_data)}")
            raise ValueError("Invalid event card contract: gosb is required")
        gosb = GosbSchema.from_dict(gosb_data)

        terbank_data = data.get("terbank")
        if not isinstance(terbank_data, dict):
            raise ValueError("Invalid event card contract: terbank is required")
        terbank = TerbankSchema.from_dict(terbank_data)

        participation_data = data.get("participation")
        participation = None
        if participation_data is not None:
            if not isinstance(participation_data, dict):
                raise ValueError("Invalid event card contract: participation must be dict or None")
            participation = ParticipationSchema.from_dict(participation_data)

        tags_data = data.get("tags") or []
        tags = []
        for tag_data in tags_data:
            if not isinstance(tag_data, dict):
                raise ValueError("Invalid event card contract: item in 'tags_data' must be dict")
            tags.append(TagSchema.from_dict(tag_data))

        allowed_actions_data = data.get("allowed_actions")
        allowed_actions = None
        if allowed_actions_data is not None:
            if not isinstance(allowed_actions_data, dict):
                raise ValueError(
                    "Invalid event card contract: allowed_actions must be dict or None"
                )
            allowed_actions = AllowedActionsSchema.from_dict(allowed_actions_data)

        event_data = data.get("event")
        if not isinstance(event_data, dict):
            raise ValueError("Invalid event card contract: event is required")
        event = EventSchema(
            id=str(event_data.get("id", "")),
            title=str(event_data.get("title", "")),
            description=str(event_data.get("description", "")),
            date_time=require_datetime(event_data.get("date_time"), "event.date_time"),
            creator_id=str(event_data.get("creator_id", "")),
            gosb_id=str(event_data.get("gosb_id", "")),
            project_id=str(event_data.get("project_id", "")),
            archived_at=parse_datetime_value(event_data.get("archived_at"))
            if event_data.get("archived_at")
            else None,
            verification_code=str(event_data.get("verification_code"))
            if event_data.get("verification_code")
            else None,
            deleted=bool(event_data.get("deleted", False)),
            active=bool(event_data.get("active", True)),
            created_at=parse_datetime_value(event_data.get("created_at"))
            if event_data.get("created_at")
            else None,
            updated_at=parse_datetime_value(event_data.get("updated_at"))
            if event_data.get("updated_at")
            else None,
            hours=int(event_data.get("hours", 0)),
            participation_limit=int(event_data.get("participation_limit", 0)),
            participation_count=int(event_data.get("participation_count", 0)),
        )

        return cls(
            event=event,
            tags=tags,
            gosb=gosb,
            terbank=terbank,
            participation=participation,
            allowed_actions=allowed_actions,
        )


class EventSchema(BaseModel):
    """Полная схема мероприятия"""

    id: str
    title: str
    description: str
    date_time: datetime
    creator_id: str
    gosb_id: str
    project_id: str
    verification_code: str | None = None
    archived_at: datetime | None = None
    deleted: bool = False
    active: bool = True
    hours: int
    participation_limit: int
    participation_count: int
    created_at: datetime | None = None
    updated_at: datetime | None = None


class EventCardsPageSchema(BaseModel):
    """Схема страницы с карточками мероприятий"""

    event_cards: list[EventCardSchema]  # добавляем поле для карточек
    current_page: int
    total_pages: int

    @classmethod
    def from_response(cls, response_data: dict[str, Any]) -> "EventCardsPageSchema":
        """Создаёт схему из ответа бэкенда"""
        cards_data = response_data.get("event_cards") or []
        if not isinstance(cards_data, list):
            raise ValueError("Invalid event card contract: cards_data must be list")

        page_meta = response_data.get("page_meta") or {}
        if not isinstance(page_meta, dict):
            raise ValueError("Invalid event card contract: page_meta must be dict")

        event_cards: list[EventCardSchema] = []
        for card_data in cards_data:
            if not isinstance(card_data, dict):
                raise ValueError("Invalid event card contract: item must be dict")
            event_cards.append(EventCardSchema.from_dict(card_data))

        return cls(
            event_cards=event_cards,
            current_page=int(page_meta.get("current_page", 1)),
            total_pages=int(page_meta.get("total_pages", 1)),
        )

    @property
    def has_prev(self) -> bool:
        return self.current_page > 1

    @property
    def has_next(self) -> bool:
        return self.current_page < self.total_pages

--- services/bot/core/utils/events_filter.py ---
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal, cast

EventsMode = Literal["all", "participant", "organizer"]

FILTERS_CTX_KEY = "events_filters_v1"


@dataclass(slots=True)
class EventsFilter:  # legacy
    """
    фильтры списка карточек мероприятий на стороне бота.
    текущий бэкенд поддерживает только фильтр по тегам.
    """

    tag_ids: list[str] = field(default_factory=list)
    tag_names: list[str] = field(default_factory=list)
    mode: EventsMode = "all"

    # UI-only (пока не поддержано backend)
    date_from: str | None = None
    date_to: str | None = None

    @classmethod
    def from_dict(cls, raw: Any) -> EventsFilter:  # legacy
        if not isinstance(raw, dict):
            return cls()

        mode_raw = raw.get("mode") or "all"
        if mode_raw not in ("all", "participant", "organizer"):
            mode_raw = "all"
        mode: EventsMode = cast(EventsMode, mode_raw)

        tag_ids_raw = raw.get("tag_ids") or []
        if not isinstance(tag_ids_raw, list):
            tag_ids_raw = []
        tag_ids = [str(x) for x in tag_ids_raw if str(x).strip()]

        tag_names_raw = raw.get("tag_names") or []
        if not isinstance(tag_names_raw, list):
            tag_names_raw = []
        tag_names = [str(x) for x in tag_names_raw if str(x).strip()]

        date_from = raw.get("date_from")
        date_from = str(date_from) if date_from else None
        date_to = raw.get("date_to")
        date_to = str(date_to) if date_to else None

        return cls(
            tag_ids=tag_ids,
            tag_names=tag_names,
            mode=mode,
            date_from=date_from,
            date_to=date_to,
        )

    def to_dict(self) -> dict[str, Any]:  # legacy
        return {
            "tag_ids": list(self.tag_ids or []),
            "tag_names": list(self.tag_names or []),
            "mode": self.mode,
            "date_from": self.date_from,
            "date_to": self.date_to,
        }


@dataclass(frozen=True, slots=True)
class EventsSearchBase:  # legacy
    """
    базовые параметры конкретного списка (контекст):
    - например points всегда only_claimed=True
    - my_events (организатор) может фиксировать режим
    """

    requester_messenger_id: str | None

    only_claimed: bool = False
    only_organizer: bool = False
    only_participant: bool = False
    has_report: bool | None = None

    tag_ids: list[str] = field(default_factory=list)

    # если True — mode из EventsFilter игнорируется
    lock_mode: bool = False


def build_events_search_params(  # legacy
    *,
    base: EventsSearchBase,
    flt: EventsFilter | None,
    offset: int,
    limit: int,
) -> dict[str, Any]:
    """собираем kwargs для EventService.search_events_with_total(...)"""

    f = flt or EventsFilter()

    # tags: если в фильтре задано — переопределяет базу
    tag_ids = f.tag_ids if f.tag_ids else (base.tag_ids or [])

    only_claimed = bool(base.only_claimed)
    only_organizer = bool(base.only_organizer)
    only_participant = bool(base.only_participant)

    # mode: работаем только с organizer/participant
    if not base.lock_mode:
        if f.mode == "organizer":
            only_organizer = True
            only_participant = False
        elif f.mode == "participant":
            only_participant = True
            only_organizer = False
        else:
            # all
            # оставляем только то, что задано базой
            pass

    params: dict[str, Any] = {
        "offset": int(offset),
        "limit": int(limit),
        "only_claimed": bool(only_claimed),
        "only_organizer": bool(only_organizer),
        "only_participant": bool(only_participant),
    }
    if base.requester_messenger_id is not None:
        params["requester_messenger_id"] = int(base.requester_messenger_id)
    if base.has_report is not None:
        params["has_report"] = bool(base.has_report)
    if tag_ids:
        params["tag_ids"] = list(tag_ids)

    return params


def format_events_filter_summary(  # legacy
    flt: EventsFilter | None,
    *,
    locked_mode: EventsMode | None = None,
) -> str:
    f = flt or EventsFilter()
    parts: list[str] = []

    eff_mode: EventsMode = (
        locked_mode if locked_mode in ("all", "participant", "organizer") else f.mode
    )
    suffix = " (заблокирован)" if locked_mode in ("participant", "organizer") else ""

    if f.tag_names:
        names = [str(x).strip() for x in f.tag_names if str(x).strip()]
        parts.append("теги: " + ", ".join(names))
    elif f.tag_ids:
        parts.append(f"теги: {len(f.tag_ids)}")

    if eff_mode == "participant":
        parts.append("режим: участвую" + suffix)
    elif eff_mode == "organizer":
        parts.append("режим: организую" + suffix)

    if f.date_from or f.date_to:
        a = f.date_from or "..."
        b = f.date_to or "..."
        parts.append(f"даты: {a} - {b}")

    if not parts:
        return "Фильтр\nбез доп. условий"
    return "Фильтр\n" + "\n".join(parts)


def get_events_filter(context, *, ctx_name: str) -> EventsFilter:  # legacy
    data = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    if not isinstance(data, dict):
        return EventsFilter()
    return EventsFilter.from_dict(data.get(str(ctx_name)))


def set_events_filter(context, *, ctx_name: str, flt: EventsFilter) -> None:  # legacy
    raw = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    data = dict(raw) if isinstance(raw, dict) else {}
    data[str(ctx_name)] = flt.to_dict()
    context.update_data({FILTERS_CTX_KEY: data})


def reset_events_filter(context, *, ctx_name: str) -> None:  # legacy
    raw = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    data = dict(raw) if isinstance(raw, dict) else {}
    data.pop(str(ctx_name), None)
    context.update_data({FILTERS_CTX_KEY: data})


def clear_context_keep_events_filters(context) -> None:  # legacy
    """
    нужен для мест, где раньше делали context.clear(),
    но теперь фильтры должны переживать пагинацию
    """
    data = context.get_data() or {}
    keep = data.get(FILTERS_CTX_KEY)
    context.clear()
    if keep is not None:
        context.update_data({FILTERS_CTX_KEY: keep})

Error: 'services/bot/core/utils/events_filters.py' is not a valid file
--- services/bot/core/utils/__init__.py ---
from .date_time import format_datetime, parse_datetime_value, require_datetime
from .events_filter import (
    EventsFilter,
    EventsMode,
    clear_context_keep_events_filters,
    format_events_filter_summary,
    get_events_filter,
    reset_events_filter,
    set_events_filter,
)
from .logger import logger
from .messages import delete_prev_message, delete_prev_message_by_peer, patch_bot_messaging
from .text_wrap import wrap_text

__all__ = [
    "EventsFilter",
    "EventsMode",
    "clear_context_keep_events_filters",
    "delete_prev_message",
    "delete_prev_message_by_peer",
    "format_datetime",
    "format_events_filter_summary",
    "get_events_filter",
    "logger",
    "parse_datetime_value",
    "patch_bot_messaging",
    "require_datetime",
    "reset_events_filter",
    "set_events_filter",
    "wrap_text",
]

--- services/bot/core/handlers/events_filters.py ---
from dialog_bot_sdk.entities.messaging import UpdateInteractiveMediaEvent

from core.bot_kit.fsm import FSMContext, State, StatesGroup
from core.config import bot
from core.handlers.events_ui import send_event_cards_page, send_my_events_page, send_points_page
from core.markups import TagChoice, events_filters_menu_keyboard, events_filters_mode_keyboard
from core.services import EventService, TagService
from core.utils import (
    delete_prev_message_by_peer,
    format_events_filter_summary,
    get_events_filter,
    reset_events_filter,
    set_events_filter,
)
from core.utils.tags_toggle_ui import TagsToggleCfg, is_done, parse_toggle_key, send_tags_toggle_ui


class EventsFiltersTagsState(StatesGroup):  # legacy
    tags = State()


class EventsFiltersModeState(StatesGroup):  # legacy
    mode = State()


FILTERS_TAGS_CFG = TagsToggleCfg(
    media_id="events_filters_tags_toggle",
    toggle_prefix="toggle:",
    done_value="done",
)

_CTX_KEY = "events_filters_edit_ctx"
_OFFSET_KEY = "events_filters_edit_offset"
_TAG_IDS_KEY = "events_filters_edit_tags_selected"

_MODE_KEY = "events_filters_mode_selected"
_MODE_LOCKED_KEY = "events_filters_mode_locked"


def _locked_mode_for_ctx(ctx: str) -> str | None:  # legacy
    if ctx == "my_events":
        return "organizer"
    if ctx in ("part_events", "points"):
        return "participant"
    return None


def _split_ctx_offset(raw: object) -> tuple[str, int]:  # legacy
    s = str(raw or "").strip()
    if "|" not in s:
        return (s or "events", 0)
    a, b = s.split("|", 1)
    ctx = (a or "events").strip()
    try:
        off = int(str(b).strip())
        if off < 0:
            off = 0
    except Exception:
        off = 0
    return ctx, off


def _send_list(  # legacy
    *, peer, ctx_name: str, offset: int, context: FSMContext, event_service: EventService
):
    # apply/back используют эту маршрутизацию
    if ctx_name == "points":
        return send_points_page(peer, offset=offset, event_service=event_service, context=context)
    if ctx_name == "my_events":
        return send_my_events_page(
            peer,
            offset=offset,
            event_service=event_service,
            requester_messenger_id=peer.id,
            context=context,
        )
    # events / moderation
    return send_event_cards_page(
        peer,
        offset=offset,
        event_service=event_service,
        ctx_name=ctx_name,
        context=context,
    )


@bot.di
def events_filters_open_handler(  # legacy
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)

    locked_mode = _locked_mode_for_ctx(ctx_name)
    locked_note = ""
    if locked_mode == "participant":
        locked_note = "\n\n🔒 Режим зафиксирован: *участник*"
    elif locked_mode == "organizer":
        locked_note = "\n\n🔒 Режим зафиксирован: *организатор*"

    bot.messaging.send_message(
        peer=event.peer,
        text=f"🧰 *Фильтры*\n\n{summary}{locked_note}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


@bot.di
def events_filters_reset_handler(  # legacy
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    reset_events_filter(context, ctx_name=ctx_name)

    # остаёмся в меню, чтобы пользователь видел что “сбросилось”
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)

    locked_mode = _locked_mode_for_ctx(ctx_name)
    locked_note = ""
    if locked_mode == "participant":
        locked_note = "\n\n🔒 Режим зафиксирован: *участник*"
    elif locked_mode == "organizer":
        locked_note = "\n\n🔒 Режим зафиксирован: *организатор*"

    bot.messaging.send_message(
        peer=event.peer,
        text=f"🧰 *Фильтры*\n\n{summary}{locked_note}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


@bot.di
def events_filters_apply_handler(  # legacy
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, _offset = _split_ctx_offset(event.data.value)
    # безопаснее возвращать на первую страницу (фильтр мог сузить выдачу)
    _send_list(
        peer=event.peer, ctx_name=ctx_name, offset=0, context=context, event_service=event_service
    )


@bot.di
def events_filters_back_handler(  # legacy
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    _send_list(
        peer=event.peer,
        ctx_name=ctx_name,
        offset=offset,
        context=context,
        event_service=event_service,
    )


@bot.di
def events_filters_not_implemented_handler(
    event: UpdateInteractiveMediaEvent, context: FSMContext
):  # legacy
    # handlers для tags/date/mode до следующих коммитов
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)
    bot.messaging.send_message(
        peer=event.peer,
        text=f"⚠️ Пока не реализовано в этом коммите.\n\n{summary}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


def _tag_choices_from_backend(all_tags) -> list[TagChoice]:  # legacy
    out: list[TagChoice] = []
    for t in all_tags or []:
        tid = getattr(t, "id", None)
        name = getattr(t, "name", None)
        if tid and name:
            out.append(TagChoice(key=str(tid), label=str(name)))
    return out


@bot.di
def events_filters_tags_open_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    tag_service: TagService,
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    selected_ids = set(flt.tag_ids or [])

    all_tags = tag_service.get_all_tags()
    choices = _tag_choices_from_backend(all_tags)

    # сохраняем "куда вернуться"
    context.update_data(
        {
            _CTX_KEY: ctx_name,
            _OFFSET_KEY: int(offset),
            _TAG_IDS_KEY: list(selected_ids),
        }
    )
    context.set_state(EventsFiltersTagsState.tags)

    send_tags_toggle_ui(
        peer=event.peer,
        title="🏷️ *Фильтры: теги*\n\nВыбери теги, затем нажми «Готово»:",
        choices=choices,
        selected_keys=set(selected_ids),
        cfg=FILTERS_TAGS_CFG,
        done_label="✅ Готово",
    )


@bot.di
def events_filters_tags_toggle_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    tag_service: TagService,
):
    state = context.get_state()
    if not (state == EventsFiltersTagsState.tags or str(state) == str(EventsFiltersTagsState.tags)):
        return

    delete_prev_message_by_peer(bot, event.peer)

    raw = str(event.data.value or "")
    data = context.get_data() or {}

    ctx_name = str(data.get(_CTX_KEY) or "events")
    offset = int(data.get(_OFFSET_KEY) or 0)
    selected = set(data.get(_TAG_IDS_KEY) or [])

    # DONE -> сохранить filter.tag_ids и вернуться в меню фильтров
    if is_done(raw, FILTERS_TAGS_CFG):
        flt = get_events_filter(context, ctx_name=ctx_name)
        flt.tag_ids = sorted([str(x) for x in selected if str(x).strip()])

        all_tags = tag_service.get_all_tags()
        id_to_name = {
            str(getattr(t, "id", "")).strip(): str(getattr(t, "name", "")).strip()
            for t in (all_tags or [])
            if str(getattr(t, "id", "")).strip() and str(getattr(t, "name", "")).strip()
        }
        flt.tag_names = [id_to_name[i] for i in flt.tag_ids if i in id_to_name]
        set_events_filter(context, ctx_name=ctx_name, flt=flt)

        context.set_state(None)
        # чистим только "служебные" ключи экрана выбора тегов
        context.update_data({_CTX_KEY: None, _OFFSET_KEY: None, _TAG_IDS_KEY: None})

        summary = format_events_filter_summary(get_events_filter(context, ctx_name=ctx_name))
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
        )
        return

    # TOGGLE
    key = parse_toggle_key(raw, FILTERS_TAGS_CFG)
    if not key:
        return

    if key in selected:
        selected.remove(key)
    else:
        selected.add(key)

    context.update_data({_TAG_IDS_KEY: list(selected)})

    all_tags = tag_service.get_all_tags()
    choices = _tag_choices_from_backend(all_tags)

    send_tags_toggle_ui(
        peer=event.peer,
        title="🏷️ *Фильтры: теги*\n\nВыбери теги, затем нажми «Готово»:",
        choices=choices,
        selected_keys=set(selected),
        cfg=FILTERS_TAGS_CFG,
        done_label="✅ Готово",
    )


def _safe_int(value, default: int = 0) -> int:  # legacy
    try:
        if value is None:
            return default
        n = int(value)
        return n if n >= 0 else default
    except Exception:
        return default


def _send_mode_picker(
    *, peer, ctx_name: str, back_offset: int, context: FSMContext
) -> None:  # legacy
    data = context.get_data() or {}
    locked_mode = str(data.get(_MODE_LOCKED_KEY) or "").strip() or None
    if locked_mode not in ("all", "participant", "organizer"):
        locked_mode = None
    sel = str(data.get(_MODE_KEY) or "all").strip()
    if sel not in ("all", "participant", "organizer"):
        sel = "all"

    note = ""
    if locked_mode == "organizer":
        note = "\n\nℹ️ Для этой вкладки режим зафиксирован: *организатор*."
    if locked_mode == "participant":
        note = "\n\nℹ️ Для этой вкладки режим зафиксирован: *участник*."

    bot.messaging.send_message(
        peer=peer,
        text="🎛️ *Фильтры: режим*\n\nВыбери режим и нажми «Готово»:" + note,
        interactive_media_groups=events_filters_mode_keyboard(
            ctx_name=ctx_name,
            back_offset=back_offset,
            selected_mode=sel,  # type: ignore[arg-type]
            locked_mode=locked_mode,
        ),
    )


@bot.di
def events_filters_mode_open_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, back_offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)

    locked_mode = _locked_mode_for_ctx(ctx_name)
    context.update_data(
        {
            _CTX_KEY: ctx_name,
            _OFFSET_KEY: int(back_offset),
            _MODE_KEY: flt.mode,
            _MODE_LOCKED_KEY: locked_mode,
        }
    )
    context.set_state(EventsFiltersModeState.mode)
    _send_mode_picker(peer=event.peer, ctx_name=ctx_name, back_offset=back_offset, context=context)


@bot.di
def events_filters_mode_select_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
):
    state = context.get_state()
    if not (state == EventsFiltersModeState.mode or str(state) == str(EventsFiltersModeState.mode)):
        return

    delete_prev_message_by_peer(bot, event.peer)

    raw = str(event.data.value or "").strip()
    data = context.get_data() or {}

    ctx_name = str(data.get(_CTX_KEY) or "events")
    back_offset = _safe_int(data.get(_OFFSET_KEY), 0)
    locked_mode = str(data.get(_MODE_LOCKED_KEY) or "").strip() or None
    if locked_mode not in ("all", "participant", "organizer"):
        locked_mode = None

    if raw.startswith("back:"):
        _ctx, _off = _split_ctx_offset(raw[len("back:") :])
        summary = format_events_filter_summary(get_events_filter(context, ctx_name=_ctx))
        context.set_state(None)
        context.update_data(
            {_CTX_KEY: None, _OFFSET_KEY: None, _MODE_KEY: None, _MODE_LOCKED_KEY: None}
        )
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=_ctx, offset=_off),
        )
        return

    if raw.startswith("done:"):
        _ctx, _off = _split_ctx_offset(raw[len("done:") :])

        flt = get_events_filter(context, ctx_name=_ctx)
        if locked_mode in ("participant", "organizer"):
            flt.mode = locked_mode  # type: ignore[assignment]
        else:
            sel = str((context.get_data() or {}).get(_MODE_KEY) or "all").strip()
            if sel in ("all", "participant", "organizer"):
                flt.mode = sel  # type: ignore[assignment]
        set_events_filter(context, ctx_name=_ctx, flt=flt)

        summary = format_events_filter_summary(get_events_filter(context, ctx_name=_ctx))
        context.set_state(None)
        context.update_data(
            {_CTX_KEY: None, _OFFSET_KEY: None, _MODE_KEY: None, _MODE_LOCKED_KEY: None}
        )
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=_ctx, offset=_off),
        )
        return

    if raw.startswith("set:"):
        if locked_mode in ("participant", "organizer"):
            # показываем тот же экран с подсказкой, ничего не меняем
            _send_mode_picker(
                peer=event.peer, ctx_name=ctx_name, back_offset=back_offset, context=context
            )
            return

        sel = raw[len("set:") :].strip()
        if sel not in ("all", "participant", "organizer"):
            return
        context.update_data({_MODE_KEY: sel})
        _send_mode_picker(
            peer=event.peer, ctx_name=ctx_name, back_offset=back_offset, context=context
        )
        return

--- services/bot/core/markups/event.py ---
from dialog_bot_sdk.interactive_media import Button, InteractiveMediaGroup, MediaGroupBuilder

from core.markups.pagination import pagination_keyboard
from core.schemas import EventCardSchema
from core.utils import format_datetime, wrap_text


def format_event_details(
    card: EventCardSchema,
    *,
    show_code: bool,
    show_organizer: bool,
    show_report_status: bool = False,
    report_exists: bool | None = None,
    show_active: bool = False,
    make_frame: bool = False,
    wrap: bool = False,
) -> str:
    """формирует читаемое описание из EventCardSchema"""

    def make_bold(string: str) -> str:
        return f"*{string}*" if not make_frame else string

    def make_block(string: str) -> str:
        return f"`{string}`" if not make_frame else string

    def make_title(string: str) -> str:
        return f"{string}\n" if make_frame else f"{string}\n\n"

    lines: list[str] = [
        f"📣 {make_bold('Название')}: {card.event.title}",
        f"⏰ {make_bold('Когда')}: {format_datetime(card.event.date_time)}",
        f"🏷️ {make_bold('Теги')}: {_format_tags(card.tags)}",
        f"🏆 {make_bold('Часы')}: {card.event.hours}",
        f"🏛️ {make_bold('ГОСБ')}: {card.gosb.name}",
        f"👥 {make_bold('Участники')}: {card.event.participation_count}/{card.event.participation_limit}",
    ]

    if show_code:
        lines.append(f"🎟️ {make_bold('Код')}: {card.event.verification_code or '—'}")

    lines.extend(
        [
            f"📝 {make_bold('Описание')}: {wrap_text(card.event.description or '—')}"
            if wrap
            else f"📝 {make_bold('Описание')}: {card.event.description or '—'}",
        ]
    )

    if show_active:
        lines.append("")
        lines.append("✅ Согласовано" if card.event.active else "⏳ На согласовании")

    lines.extend(["", "🔧 Техническая информация:"])
    if show_report_status:
        status = "✅ Создан" if report_exists else "Ещё не создан"
        lines.append(f"Отчёт: {make_block(status)}")
    if show_organizer:
        lines.append(f"Creator id: {make_block(card.event.creator_id or '—')}")
    lines.append(f"Project ID: {make_block(card.event.project_id or '—')}")
    lines.append(f"ID: {make_block(card.event.id)}")

    result = "\n".join(lines)

    return make_title("📅 *Мероприятие*") + (
        "```plain\n" + result + "\n```" if make_frame else result
    )


def _format_tags(
    tags: list | None,
) -> str:  # TODO add type to 'tags' and simplify func depending on type
    if not tags:
        return "—"
    try:
        titles = [str(getattr(t, "title", "")).strip() for t in tags]
        titles = [t for t in titles if t]
        return ", ".join(titles) if titles else "—"
    except Exception:
        return str(tags)


def event_actions_keyboard(  # legacy
    event_id: str,
    *,
    is_participant: bool,
    can_edit: bool,
    back_value: str | None,
    back_label: str,
    enter_code_media_id: str,
    sign_up_media_id: str,
    sign_out_media_id: str,
    edit_media_id: str = "event_menu_edit",
    delete_media_id: str = "event_menu_delete",
    can_create_report: bool = False,
    can_view_report: bool = False,
    can_sign_up: bool = False,
    ctx: str | None = None,
) -> list[InteractiveMediaGroup]:
    actions = []
    v = f"{event_id}|{ctx}" if ctx else event_id

    actions.append(Button(media_id=enter_code_media_id, value=v, label="🏅 Ввести код"))

    if is_participant:
        actions.append(Button(media_id=sign_out_media_id, value=v, label="❌ Отписаться"))
    elif can_sign_up:
        actions.append(Button(media_id=sign_up_media_id, value=v, label="✅ Записаться"))
    else:
        actions.append(Button(media_id=sign_up_media_id, value=v, label="⚠️ Записаться"))

    if can_edit:
        actions.append(Button(media_id=edit_media_id, value=v, label="✏️ Редактировать"))
        # TODO: separate permissions
        actions.append(Button(media_id="event_participants_open", value=v, label="👥 Участники"))
        actions.append(Button(media_id=delete_media_id, value=v, label="🗑 Удалить"))

    if can_create_report:
        actions.append(Button(media_id="event_report_create", value=v, label="📄 Создать отчёт"))
    if can_view_report:
        actions.append(Button(media_id="event_report_view", value=v, label="📄 Посмотреть отчёт"))
    if back_value is not None:
        actions.append(Button(media_id=back_value, value="", label=back_label))

    return MediaGroupBuilder(actions).build()


def event_edit_fields_keyboard(  # legacy
    event_id: str,
    *,
    leave_media_id: str = "leave",
    leave_value: str = "moderation",
    leave_label: str = "⬅️ В мастерскую",
) -> list[InteractiveMediaGroup]:
    group_builder_1 = MediaGroupBuilder(
        [
            Button(
                media_id="event_menu_edit_name",
                value=f"{event_id}|title",
                label="📣 Название",
            ),
            Button(
                media_id="event_menu_edit_description",
                value=f"{event_id}|description",
                label="📝 Описание",
            ),
            Button(
                media_id="event_menu_edit_date",
                value=f"{event_id}|date",
                label="📅 Дата",
            ),
            Button(
                media_id="event_menu_edit_time",
                value=f"{event_id}|time",
                label="⏰ Время",
            ),
            Button(
                media_id="event_menu_edit_tags",
                value=f"{event_id}|tags",
                label="🏷️ Теги",
            ),
            Button(
                media_id="event_menu_edit_code",
                value=f"{event_id}|code",
                label="🎟️ Код",
            ),
        ]
    )
    group_builder_2 = MediaGroupBuilder(
        [
            Button(
                media_id="event_menu_edit_organizers",
                value=f"{event_id}|event_organizers",
                label="🤓 Организаторы",
            ),
            Button(
                media_id="event_menu_edit_hours",
                value=f"{event_id}|hours",
                label="🏆 Часы",
            ),
            Button(
                media_id="event_menu_edit_gosb",
                value=f"{event_id}|gosb_id",
                label="🏛️ ГОСБ",
            ),
            Button(
                media_id="event_menu_edit_participation_limit",
                value=f"{event_id}|participation_limit",
                label="👥 Кол-во волонтеров",
            ),
            Button(
                media_id=leave_media_id,
                value=leave_value,
                label=leave_label,
            ),
        ]
    )
    return group_builder_1.merge([group_builder_2])


def user_events_pagination_keyboard(  # legacy
    *, offset: int, limit: int, has_prev: bool, has_next: bool
) -> list[InteractiveMediaGroup]:
    return pagination_keyboard(
        offset=offset,
        limit=limit,
        has_prev=has_prev,
        has_next=has_next,
        prev_media_id="user_events_prev",
        next_media_id="user_events_next",
        leave_media_id="volunteer_home",
        leave_label="🏡 В дом волонтёра",
        keep_layout=True,
        add_view_by_uuid_button=True,
        view_by_uuid_value="events",
    )


def part_events_pagination_keyboard(  # legacy
    *, offset: int, limit: int, has_prev: bool, has_next: bool
) -> list[InteractiveMediaGroup]:
    return pagination_keyboard(
        offset=offset,
        limit=limit,
        has_prev=has_prev,
        has_next=has_next,
        prev_media_id="part_events_prev",
        next_media_id="part_events_next",
        leave_media_id="volunteer_home",
        leave_label="🏡 В дом волонтёра",
        keep_layout=True,
        add_view_by_uuid_button=True,
        view_by_uuid_value="part_events",
    )


def my_events_pagination_keyboard(  # legacy
    *, offset: int, limit: int, has_prev: bool, has_next: bool
) -> list[InteractiveMediaGroup]:
    return pagination_keyboard(
        offset=offset,
        limit=limit,
        has_prev=has_prev,
        has_next=has_next,
        prev_media_id="my_events_prev",
        next_media_id="my_events_next",
        leave_media_id="moderation",
        leave_label="🛡️ В мастерскую",
        keep_layout=True,
        add_view_by_uuid_button=True,
        view_by_uuid_value="my_events",
    )


def _event_button_label(event_card: EventCardSchema, ctx: str | None = None) -> str:
    """Формирует подпись для кнопки мероприятия"""
    ev = event_card.event
    participation = event_card.participation
    date_str = format_datetime(getattr(ev, "date_time", None))
    title = getattr(ev, "title", "—")

    if ctx == "my_events":
        status = "на согласовании ⏳"
        if event_card.event.active:
            status = "согласовано ✅"
        return f"{title} | {date_str} — {status}"

    participation_emoji = "⚪"
    if participation:
        state = getattr(participation, "state", "")
        if state == "signed_up":
            participation_emoji = "📝"  # записан
        elif state == "confirmed":
            participation_emoji = "✅"  # подтверждено
        elif state == "cancelled":
            participation_emoji = "❌"  # отменено (не пришел)

    return f"{title} | {date_str} {participation_emoji}"


def events_select_keyboard(
    events_cards: list[EventCardSchema],
    *,
    open_media_id: str,
    start_index: int = 1,
    per_row: int = 1,
    label_max: int = 120,
    ctx: str | None = None,
) -> list[InteractiveMediaGroup]:
    """
    строит список кнопок по мероприятиям
    нажатие вызывает handler по _id  = open_media_id, value = event.id
    """
    groups: list[InteractiveMediaGroup] = []
    row: list[Button] = []

    idx = start_index
    for event_card in events_cards:
        label = _event_button_label(event_card, ctx)
        row.append(
            Button(
                media_id=open_media_id, value=str(getattr(event_card.event, "id", "")), label=label
            )
        )
        idx += 1

        if len(row) >= per_row:
            groups.extend(MediaGroupBuilder(row).build())
            row = []

    if row:
        groups.extend(MediaGroupBuilder(row).build())

    return groups


def view_by_uuid_keyboard(*, media_id: str, label: str) -> list[InteractiveMediaGroup]:  # legacy
    return MediaGroupBuilder([Button(media_id=media_id, value="", label=label)]).build()


def all_events_pagination_keyboard(
    *, offset: int, limit: int, has_prev: bool, has_next: bool
):  # legacy
    return pagination_keyboard(
        offset=offset,
        limit=limit,
        has_prev=has_prev,
        has_next=has_next,
        prev_media_id="all_events_prev",
        next_media_id="all_events_next",
        leave_media_id="moderation",
        leave_label="🛡️ В мастерскую",
        keep_layout=True,
        add_view_by_uuid_button=True,
        view_by_uuid_value="moderation",
    )

--- services/bot/core/markups/__init__.py ---
from .admin import admin_menu_keyboard, back_to_admin_menu_keyboard
from .ai import build_event_buttons_keyboard
from .event import (
    all_events_pagination_keyboard,
    event_actions_keyboard,
    event_edit_fields_keyboard,
    events_select_keyboard,
    format_event_details,
    my_events_pagination_keyboard,
    part_events_pagination_keyboard,
    user_events_pagination_keyboard,
)
from .events_filters import (
    events_filters_button_keyboard,
    events_filters_menu_keyboard,
    events_filters_mode_keyboard,
)
from .gosb import gosb_select_keyboard
from .main import back_to_main_menu_keyboard, main_keyboard
from .moderation import (
    back_to_moderation_and_skip_keyboard,
    back_to_moderation_keyboard,
    moderation_menu_keyboard,
)
from .participation import event_participants_keyboard, event_participants_pagination_keyboard
from .points import (
    back_to_points_menu,
    leaderboard_keyboard,
    points_events_pagination_keyboard,
    points_menu_keyboard,
)
from .profile import profile_keyboard
from .project import (
    format_project_details,
    project_actions_keyboard,
    project_select_keyboard,
    user_projects_pagination_keyboard,
)
from .reports import back_to_reports_export_keyboard, reports_export_keyboard
from .request import format_request_details, requests_pagination_keyboard, requests_select_keyboard
from .tags import TagChoice, choices_from_backend_tags, tags_toggle_keyboard
from .terbank import tb_select_keyboard
from .volunteer_home import back_to_volunteer_home_keyboard, volunteer_home_keyboard

__all__ = [
    "TagChoice",
    "admin_menu_keyboard",
    "all_events_pagination_keyboard",
    "back_to_admin_menu_keyboard",
    "back_to_main_menu_keyboard",
    "back_to_moderation_and_skip_keyboard",
    "back_to_moderation_keyboard",
    "back_to_points_menu",
    "back_to_reports_export_keyboard",
    "back_to_volunteer_home_keyboard",
    "build_event_buttons_keyboard",
    "choices_from_backend_tags",
    "event_actions_keyboard",
    "event_edit_fields_keyboard",
    "event_participants_keyboard",
    "event_participants_pagination_keyboard",
    "events_filters_button_keyboard",
    "events_filters_menu_keyboard",
    "events_filters_mode_keyboard",
    "events_select_keyboard",
    "format_event_details",
    "format_project_details",
    "format_request_details",
    "gosb_select_keyboard",
    "leaderboard_keyboard",
    "main_keyboard",
    "moderation_menu_keyboard",
    "my_events_pagination_keyboard",
    "part_events_pagination_keyboard",
    "points_events_pagination_keyboard",
    "points_menu_keyboard",
    "profile_keyboard",
    "project_actions_keyboard",
    "project_select_keyboard",
    "reports_export_keyboard",
    "requests_pagination_keyboard",
    "requests_select_keyboard",
    "tags_toggle_keyboard",
    "tb_select_keyboard",
    "user_events_pagination_keyboard",
    "user_projects_pagination_keyboard",
    "volunteer_home_keyboard",
]

Error: 'services/bot/core/handlers/moderation_events.py' is not a valid file
Error: 'services/bot/core/handlers/my_events.py' is not a valid file
--- services/bot/core/handlers/points.py ---
from dialog_bot_sdk.entities.messaging import UpdateInteractiveMediaEvent

from core.bot_kit.fsm import FSMContext
from core.bot_kit.router import Router
from core.config import bot
from core.handlers.events_ui import send_event_card, send_points_page
from core.schemas import UserSchema
from core.services import EventService
from core.utils import clear_context_keep_events_filters, delete_prev_message_by_peer

points_rt = Router()


@bot.di
def points_menu_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
):
    delete_prev_message_by_peer(bot, event.peer)
    clear_context_keep_events_filters(context)
    send_points_page(
        event.peer,
        offset=0,
        event_service=event_service,
        context=context,
    )


@bot.di
def points_page_handler(  # legacy
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    try:
        offset = int(event.data.value)
        if offset < 0:
            offset = 0
    except Exception:
        offset = 0

    send_points_page(
        event.peer,
        offset=offset,
        event_service=event_service,
        context=context,
    )


@bot.di
def points_event_open_handler(  # legacy
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    user: UserSchema | None,
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    event_id = str(event.data.value or "").strip()
    send_event_card(
        event.peer,
        event_id=event_id,
        event_service=event_service,
        user=user,
        ctx_name="points",
    )

--- services/bot/core/handlers/__init__.py ---
from core.bot_kit.router import Router

from .admin import admin_menu_handler, admin_rt, del_moderator_handler, set_moderator_handler
from .ai import ai_assistant_handler, ai_rt, event_source_callback_handler
from .event import (
    event_user_enter_code_start_handler,
    event_user_sign_out_handler,
    event_user_sign_up_handler,
    events_menu_handler,
    part_events_menu_handler,
    part_events_page_handler,
    user_events_open_handler,
    user_events_page_handler,
    user_events_rt,
)
from .event_participations import (
    event_participants_open_handler,
    event_participants_page_handler,
    event_participation_hours_plus_handler,
)
from .events_filters import (
    events_filters_apply_handler,
    events_filters_back_handler,
    events_filters_mode_open_handler,
    events_filters_mode_select_handler,
    events_filters_not_implemented_handler,
    events_filters_open_handler,
    events_filters_reset_handler,
    events_filters_tags_open_handler,
    events_filters_tags_toggle_handler,
)
from .main import main_menu_handler, start_handler
from .moderation_create import (
    create_event_start_handler,
    create_rt,
    event_create_back_to_time_handler,
    project_input_dash_handler,
)
from .moderation_edit import (
    edit_rt,
    event_menu_delete_handler,
    event_menu_edit_field_handler,
    event_menu_edit_handler,
    event_menu_enter_code_start_handler,
    event_tags_toggle_handler,
    gosb_approved,
    gosb_update_selected,
    tb_approved,
    tb_reselect_handler,
    tb_update_selected,
)
from .moderation_menu import (
    all_events_handler,
    all_events_open_handler,
    all_events_page_handler,
    moderation_menu_handler,
    moderation_menu_rt,
    my_events_handler,
    my_events_open_handler,
    my_events_page_handler,
)
from .noop import noop_handler
from .points import points_event_open_handler, points_menu_handler, points_page_handler, points_rt
from .profile import my_profile_handler, profile_tags_toggle_handler, update_user_interests_start
from .profile import rt as update_user_rt
from .report import (
    report_create_start_handler,
    report_rt,
    report_view_handler,
    reports_export_handler,
)
from .request import (
    all_requests_handler,
    my_requests_handler,
    my_requests_page_handler,
    request_approve_handler,
    request_cancel_handler,
    request_open_handler,
    request_reject_handler,
    request_repeat_handler,
    requests_page_handler,
    requests_rt,
)
from .view_by_uuid import rt as view_by_uuid_rt
from .view_by_uuid import view_by_uuid_start_handler
from .volunteer_home import points_leaderboard_handler, volunteer_home_handler

events_rt = Router()
events_rt.register(moderation_menu_rt)
events_rt.register(create_rt)
events_rt.register(edit_rt)

__all__ = [
    "admin_menu_handler",
    "admin_rt",
    "ai_assistant_handler",
    "ai_rt",
    "all_events_handler",
    "all_events_open_handler",
    "all_events_page_handler",
    "all_requests_handler",
    "create_event_start_handler",
    "create_rt",
    "del_moderator_handler",
    "edit_rt",
    "event_create_back_to_time_handler",
    "event_menu_delete_handler",
    "event_menu_edit_field_handler",
    "event_menu_edit_handler",
    "event_menu_enter_code_start_handler",
    "event_participants_open_handler",
    "event_participants_page_handler",
    "event_participation_hours_plus_handler",
    "event_source_callback_handler",
    "event_tags_toggle_handler",
    "event_user_enter_code_start_handler",
    "event_user_sign_out_handler",
    "event_user_sign_up_handler",
    "events_filters_apply_handler",
    "events_filters_back_handler",
    "events_filters_mode_open_handler",
    "events_filters_mode_select_handler",
    "events_filters_not_implemented_handler",
    "events_filters_open_handler",
    "events_filters_reset_handler",
    "events_filters_tags_open_handler",
    "events_filters_tags_toggle_handler",
    "events_menu_handler",
    "events_rt",
    "gosb_approved",
    "gosb_update_selected",
    "main_menu_handler",
    "moderation_menu_handler",
    "moderation_menu_rt",
    "my_events_handler",
    "my_events_open_handler",
    "my_events_page_handler",
    "my_profile_handler",
    "my_requests_handler",
    "my_requests_page_handler",
    "noop_handler",
    "part_events_menu_handler",
    "part_events_page_handler",
    "points_event_open_handler",
    "points_leaderboard_handler",
    "points_menu_handler",
    "points_page_handler",
    "points_rt",
    "profile_tags_toggle_handler",
    "project_input_dash_handler",
    "report_create_start_handler",
    "report_rt",
    "report_view_handler",
    "reports_export_handler",
    "request_approve_handler",
    "request_cancel_handler",
    "request_open_handler",
    "request_reject_handler",
    "request_repeat_handler",
    "requests_page_handler",
    "requests_rt",
    "set_moderator_handler",
    "start_handler",
    "tb_approved",
    "tb_reselect_handler",
    "tb_update_selected",
    "update_user_interests_start",
    "update_user_rt",
    "user_events_open_handler",
    "user_events_page_handler",
    "user_events_rt",
    "view_by_uuid_rt",
    "view_by_uuid_start_handler",
    "volunteer_home_handler",
]

--- services/bot/main.py ---
from core.bot_kit.router import Router
from core.config import bot
from core.handlers import (
    admin_menu_handler,
    admin_rt,
    ai_assistant_handler,
    ai_rt,
    all_events_handler,
    all_events_open_handler,
    all_events_page_handler,
    all_requests_handler,
    create_event_start_handler,
    create_rt,
    del_moderator_handler,
    edit_rt,
    event_create_back_to_time_handler,
    event_menu_delete_handler,
    event_menu_edit_field_handler,
    event_menu_edit_handler,
    event_menu_enter_code_start_handler,
    event_participants_open_handler,
    event_participants_page_handler,
    event_participation_hours_plus_handler,
    event_source_callback_handler,
    event_tags_toggle_handler,
    event_user_enter_code_start_handler,
    event_user_sign_out_handler,
    event_user_sign_up_handler,
    events_filters_apply_handler,
    events_filters_back_handler,
    events_filters_mode_open_handler,
    events_filters_mode_select_handler,
    events_filters_not_implemented_handler,
    events_filters_open_handler,
    events_filters_reset_handler,
    events_filters_tags_open_handler,
    events_filters_tags_toggle_handler,
    events_menu_handler,
    events_rt,
    gosb_approved,
    gosb_update_selected,
    main_menu_handler,
    moderation_menu_handler,
    moderation_menu_rt,
    my_events_handler,
    my_events_open_handler,
    my_events_page_handler,
    my_profile_handler,
    my_requests_handler,
    my_requests_page_handler,
    noop_handler,
    part_events_menu_handler,
    part_events_page_handler,
    points_event_open_handler,
    points_leaderboard_handler,
    points_menu_handler,
    points_page_handler,
    points_rt,
    profile_tags_toggle_handler,
    project_input_dash_handler,
    report_create_start_handler,
    report_rt,
    report_view_handler,
    reports_export_handler,
    request_approve_handler,
    request_cancel_handler,
    request_open_handler,
    request_reject_handler,
    request_repeat_handler,
    requests_page_handler,
    requests_rt,
    set_moderator_handler,
    start_handler,
    tb_approved,
    tb_reselect_handler,
    tb_update_selected,
    update_user_interests_start,
    update_user_rt,
    user_events_open_handler,
    user_events_page_handler,
    user_events_rt,
    view_by_uuid_rt,
    view_by_uuid_start_handler,
    volunteer_home_handler,
)
from core.handlers.projects import (
    projects_menu_handler,
    user_projects_open_handler,
    user_projects_page_handler,
)
from dialog_bot_sdk.entities.messaging import CommandHandler, EventHandler


def handlers_setting() -> None:  # legacy
    router = Router()
    router.register(edit_rt)
    router.register(moderation_menu_rt)
    router.register(create_rt)
    router.register(update_user_rt)
    router.register(events_rt)
    router.register(user_events_rt)
    router.register(points_rt)
    router.register(view_by_uuid_rt)
    router.register(report_rt)
    router.register(ai_rt)
    router.register(requests_rt)
    router.register(admin_rt)

    router.subscribe(bot)
    bot.messaging.command_handler(
        [
            CommandHandler(
                function=start_handler,
                command="start",
                description="Расскажу о себе",
            ),
            CommandHandler(
                function=moderation_menu_handler,
                command="workship",
                description="Мастерская",
            ),
            CommandHandler(
                function=create_event_start_handler,
                command="create_event",
                description="Создать мероприятие",
            ),
            CommandHandler(
                function=all_events_handler,
                command="all_events",
                description="Показать все мероприятия",
            ),
            CommandHandler(
                function=reports_export_handler,
                command="reports_export",
                description="Выгрузка отчётов",
            ),
        ]
    )

    bot.messaging.event_handler(
        [
            EventHandler(function=volunteer_home_handler, _id="volunteer_home"),
            EventHandler(function=main_menu_handler, _id="main_menu"),
            EventHandler(function=ai_assistant_handler, _id="ai_assistant"),
            EventHandler(function=my_profile_handler, _id="my_profile"),
            EventHandler(function=update_user_interests_start, _id="update_user_interests"),
            EventHandler(function=profile_tags_toggle_handler, _id="profile_tags_toggle"),
            EventHandler(function=points_menu_handler, _id="points"),
            EventHandler(function=points_leaderboard_handler, _id="points_leaderboard"),
            EventHandler(function=moderation_menu_handler, _id="moderation"),
            EventHandler(function=reports_export_handler, _id="download_reports_list"),
            # PROJECTS
            EventHandler(function=projects_menu_handler, _id="projects"),
            EventHandler(function=user_projects_page_handler, _id="user_projects_next"),
            EventHandler(function=user_projects_page_handler, _id="user_projects_prev"),
            EventHandler(function=user_projects_open_handler, _id="user_projects_open"),
            # EVENTS
            EventHandler(function=all_events_handler, _id="all_events"),
            EventHandler(function=events_menu_handler, _id="events"),
            EventHandler(function=part_events_menu_handler, _id="part_events"),
            EventHandler(function=user_events_page_handler, _id="user_events_prev"),
            EventHandler(function=user_events_page_handler, _id="user_events_next"),
            EventHandler(function=part_events_page_handler, _id="part_events_prev"),
            EventHandler(function=part_events_page_handler, _id="part_events_next"),
            EventHandler(function=event_user_sign_up_handler, _id="event_user_sign_up"),
            EventHandler(function=event_user_sign_out_handler, _id="event_user_sign_out"),
            EventHandler(function=event_user_enter_code_start_handler, _id="event_user_enter_code"),
            EventHandler(function=event_tags_toggle_handler, _id="event_tags_toggle"),
            EventHandler(function=event_participants_open_handler, _id="event_participants_open"),
            EventHandler(function=event_participants_page_handler, _id="event_participants_prev"),
            EventHandler(function=event_participants_page_handler, _id="event_participants_next"),
            EventHandler(
                function=event_participation_hours_plus_handler,
                _id="event_participation_hours_plus",
            ),
            EventHandler(function=report_create_start_handler, _id="event_report_create"),
            EventHandler(function=report_view_handler, _id="event_report_view"),
            EventHandler(function=all_events_page_handler, _id="all_events_prev"),
            EventHandler(function=all_events_page_handler, _id="all_events_next"),
            EventHandler(function=my_events_handler, _id="my_events"),
            EventHandler(function=my_events_page_handler, _id="my_events_prev"),
            EventHandler(function=my_events_page_handler, _id="my_events_next"),
            EventHandler(function=my_events_open_handler, _id="my_events_open"),
            EventHandler(function=create_event_start_handler, _id="create_event"),
            EventHandler(function=admin_menu_handler, _id="administration"),
            EventHandler(function=event_menu_enter_code_start_handler, _id="event_menu_enter_code"),
            EventHandler(function=event_menu_delete_handler, _id="event_menu_delete"),
            EventHandler(function=event_menu_edit_handler, _id="event_menu_edit"),
            EventHandler(function=view_by_uuid_start_handler, _id="view_by_uuid"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_name"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_description"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_date"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_time"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_tags"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_organizers"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_hours"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_code"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_gosb"),
            EventHandler(
                function=event_menu_edit_field_handler, _id="event_menu_edit_participation_limit"
            ),
            EventHandler(function=user_events_open_handler, _id="user_events_open"),
            EventHandler(function=all_events_open_handler, _id="all_events_open"),
            EventHandler(function=points_page_handler, _id="points_prev"),
            EventHandler(function=points_page_handler, _id="points_next"),
            EventHandler(function=points_event_open_handler, _id="points_event_open"),
            EventHandler(function=points_event_open_handler, _id="points_events_open"),
            EventHandler(function=events_filters_open_handler, _id="events_filters_open"),
            EventHandler(function=events_filters_apply_handler, _id="events_filters_apply"),
            EventHandler(function=events_filters_reset_handler, _id="events_filters_reset"),
            EventHandler(function=events_filters_back_handler, _id="events_filters_back"),
            EventHandler(function=events_filters_tags_open_handler, _id="events_filters_tags"),
            EventHandler(
                function=events_filters_tags_toggle_handler, _id="events_filters_tags_toggle"
            ),
            EventHandler(
                function=events_filters_not_implemented_handler, _id="events_filters_date"
            ),
            EventHandler(function=events_filters_mode_open_handler, _id="events_filters_mode"),
            EventHandler(
                function=events_filters_mode_select_handler, _id="events_filters_mode_select"
            ),
            EventHandler(function=all_requests_handler, _id="requests"),
            EventHandler(function=my_requests_handler, _id="my_requests"),
            EventHandler(function=requests_page_handler, _id="requests_prev"),
            EventHandler(function=my_requests_page_handler, _id="my_requests_prev"),
            EventHandler(function=requests_page_handler, _id="requests_next"),
            EventHandler(function=my_requests_page_handler, _id="my_requests_next"),
            EventHandler(function=request_open_handler, _id="request_open"),
            EventHandler(function=request_open_handler, _id="my_request_open"),
            EventHandler(function=request_approve_handler, _id="request_approve"),
            EventHandler(function=request_reject_handler, _id="request_reject"),
            EventHandler(function=request_repeat_handler, _id="request_repeat"),
            EventHandler(function=request_cancel_handler, _id="request_cancel"),
            # TODO почистить
            EventHandler(function=project_input_dash_handler, _id="project_input_dash"),
            EventHandler(function=noop_handler, _id="noop_prev"),
            EventHandler(function=noop_handler, _id="noop_mid"),
            EventHandler(function=noop_handler, _id="noop_next"),
            EventHandler(function=noop_handler, _id="noop_uuid"),
            EventHandler(function=noop_handler, _id="noop"),
            EventHandler(function=tb_approved, _id="tb_approved"),
            EventHandler(function=gosb_approved, _id="gosb_approved"),
            EventHandler(function=tb_update_selected, _id="tb_select"),
            EventHandler(
                function=event_create_back_to_time_handler, _id="event_create_back_to_time"
            ),
            EventHandler(function=tb_reselect_handler, _id="tb_reselect"),
            EventHandler(function=gosb_update_selected, _id="gosb_select"),
            EventHandler(function=event_source_callback_handler, _id="ai_event_card"),
            EventHandler(function=set_moderator_handler, _id="add_moderator"),
            EventHandler(function=del_moderator_handler, _id="delete_moderator"),
        ]
    )


def main() -> None:  # legacy
    handlers_setting()
    bot.profile.edit_about_sync("Волонтёрский бот, который помогает сотрудникам Сбера нести добро!")
    bot.updates.on_updates(do_read_message=True, do_register_commands=True)


if __name__ == "__main__":
    main()




список файлов проекта:
.env.example
.gitignore
Makefile
docker-compose.yml
docs/TODO.md
docs/buisiness-model.md
docs/devops-instruction.md
docs/domain-model.md
docs/event-access-policy.md
docs/integration-notes.md
docs/specification.pdf
policy.yml
pyproject.toml
pyrightconfig.json
rag_seed.yml
seed.yml
services/ai/Dockerfile
services/ai/main.py
services/ai/rag_seed.yml
services/ai/requirements.txt
services/ai/russian_trusted_root_ca_pem.crt
services/ai/src/api/__init__.py
services/ai/src/api/controllers/__init__.py
services/ai/src/api/controllers/assistant_controller.py
services/ai/src/api/controllers/health_controller.py
services/ai/src/api/controllers/index_controller.py
services/ai/src/api/middlewares/auth.py
services/ai/src/api/models/__init__.py
services/ai/src/api/models/request.py
services/ai/src/api/models/response.py
services/ai/src/api/routes.py
services/ai/src/app.py
services/ai/src/clients/embedding_model.py
services/ai/src/clients/event_vector_store.py
services/ai/src/clients/intent_vector_store.py
services/ai/src/clients/language_model.py
services/ai/src/clients/question_vector_store.py
services/ai/src/clients/seed_loader.py
services/ai/src/config/env.py
services/ai/src/db/database.py
services/ai/src/repositories/__init__.py
services/ai/src/repositories/conversation.py
services/ai/src/repositories/seed_version.py
services/ai/src/run.py
services/ai/src/services/__init__.py
services/ai/src/services/assistant.py
services/ai/src/services/indexing.py
services/ai/src/services/intents.py
services/ai/src/services/rag_init.py
services/ai/src/utils/__init__.py
services/ai/src/utils/asterisks.py
services/ai/src/utils/logger.py
services/backend/.dockerignore
services/backend/Dockerfile
services/backend/bootstrap/modules.go
services/backend/cmd/root.go
services/backend/docs/docs.go
services/backend/docs/swagger.json
services/backend/docs/swagger.yaml
services/backend/go.mod
services/backend/go.sum
services/backend/internal/adapters/assistant/assistant_client.go
services/backend/internal/adapters/assistant/module.go
services/backend/internal/adapters/assistant/requests.go
services/backend/internal/adapters/policy/module.go
services/backend/internal/adapters/policy/policy_matrix.go
services/backend/internal/adapters/reports/collection_mapper.go
services/backend/internal/adapters/reports/collection_schema.go
services/backend/internal/adapters/reports/module.go
services/backend/internal/adapters/reports/report_query.go
services/backend/internal/adapters/reports/report_repo.go
services/backend/internal/adapters/reports/report_schema.go
services/backend/internal/adapters/volunteering/event_card/event_card_mappers.go
services/backend/internal/adapters/volunteering/event_card/event_card_query.go
services/backend/internal/adapters/volunteering/event_card/event_card_schema.go
services/backend/internal/adapters/volunteering/event_card/module.go
services/backend/internal/adapters/volunteering/event_organizer_card/module.go
services/backend/internal/adapters/volunteering/event_organizer_card/organizer_card_mappers.go
services/backend/internal/adapters/volunteering/event_organizer_card/organizer_card_query.go
services/backend/internal/adapters/volunteering/event_organizer_card/organizer_card_schema.go
services/backend/internal/adapters/volunteering/event_organizers/event_organizer_mappers.go
services/backend/internal/adapters/volunteering/event_organizers/event_organizer_repo.go
services/backend/internal/adapters/volunteering/event_organizers/event_organizer_schema.go
services/backend/internal/adapters/volunteering/event_organizers/module.go
services/backend/internal/adapters/volunteering/event_tags/event_tag_mappers.go
services/backend/internal/adapters/volunteering/event_tags/event_tag_repo.go
services/backend/internal/adapters/volunteering/event_tags/event_tag_schema.go
services/backend/internal/adapters/volunteering/event_tags/module.go
services/backend/internal/adapters/volunteering/events/event_config_provider.go
services/backend/internal/adapters/volunteering/events/event_mappers.go
services/backend/internal/adapters/volunteering/events/event_repository.go
services/backend/internal/adapters/volunteering/events/event_schema.go
services/backend/internal/adapters/volunteering/events/module.go
services/backend/internal/adapters/volunteering/gosbs/gosb_mappers.go
services/backend/internal/adapters/volunteering/gosbs/gosb_repo.go
services/backend/internal/adapters/volunteering/gosbs/gosb_schema.go
services/backend/internal/adapters/volunteering/gosbs/module.go
services/backend/internal/adapters/volunteering/module.go
services/backend/internal/adapters/volunteering/participation/module.go
services/backend/internal/adapters/volunteering/participation/participation_repo.go
services/backend/internal/adapters/volunteering/participation/participation_schema.go
services/backend/internal/adapters/volunteering/participation_card/module.go
services/backend/internal/adapters/volunteering/participation_card/participation_card_mapper.go
services/backend/internal/adapters/volunteering/participation_card/participation_card_query.go
services/backend/internal/adapters/volunteering/participation_card/participation_card_schema.go
services/backend/internal/adapters/volunteering/project/module.go
services/backend/internal/adapters/volunteering/project/project_mappers.go
services/backend/internal/adapters/volunteering/project/project_repository.go
services/backend/internal/adapters/volunteering/project/project_schema.go
services/backend/internal/adapters/volunteering/project_card/module.go
services/backend/internal/adapters/volunteering/project_card/project_card_mapers.go
services/backend/internal/adapters/volunteering/project_card/project_card_query.go
services/backend/internal/adapters/volunteering/project_card/project_card_schema.go
services/backend/internal/adapters/volunteering/request_card/module.go
services/backend/internal/adapters/volunteering/request_card/request_card_mappers.go
services/backend/internal/adapters/volunteering/request_card/request_card_query.go
services/backend/internal/adapters/volunteering/request_card/request_card_schema.go
services/backend/internal/adapters/volunteering/requests/module.go
services/backend/internal/adapters/volunteering/requests/request_mappers.go
services/backend/internal/adapters/volunteering/requests/request_repo.go
services/backend/internal/adapters/volunteering/requests/request_schema.go
services/backend/internal/adapters/volunteering/tags/module.go
services/backend/internal/adapters/volunteering/tags/tag_mapper.go
services/backend/internal/adapters/volunteering/tags/tag_schema.go
services/backend/internal/adapters/volunteering/tags/tags_repository.go
services/backend/internal/adapters/volunteering/terbank_moderators/module.go
services/backend/internal/adapters/volunteering/terbank_moderators/terbank_moderator_schema.go
services/backend/internal/adapters/volunteering/terbank_moderators/terbank_moderators_mappers.go
services/backend/internal/adapters/volunteering/terbank_moderators/terbank_moderators_repo.go
services/backend/internal/adapters/volunteering/terbanks/module.go
services/backend/internal/adapters/volunteering/terbanks/terbank_repo.go
services/backend/internal/adapters/volunteering/terbanks/terbank_schema.go
services/backend/internal/adapters/volunteering/terbanks/terbanks_mappers.go
services/backend/internal/adapters/volunteering/volunteer/module.go
services/backend/internal/adapters/volunteering/volunteer/volunteer_mapper.go
services/backend/internal/adapters/volunteering/volunteer/volunteer_repository.go
services/backend/internal/adapters/volunteering/volunteer/volunteer_schema.go
services/backend/internal/adapters/volunteering/volunteer_card/module.go
services/backend/internal/adapters/volunteering/volunteer_card/volunteer_card_mapper.go
services/backend/internal/adapters/volunteering/volunteer_card/volunteer_card_query.go
services/backend/internal/adapters/volunteering/volunteer_card/volunteer_card_schema.go
services/backend/internal/adapters/volunteering/volunteer_tags/module.go
services/backend/internal/adapters/volunteering/volunteer_tags/volunteer_tag_mappers.go
services/backend/internal/adapters/volunteering/volunteer_tags/volunteer_tag_repo.go
services/backend/internal/adapters/volunteering/volunteer_tags/volunteer_tag_schema.go
services/backend/internal/config/env.go
services/backend/internal/config/module.go
services/backend/internal/config/seed_config.go
services/backend/internal/domain/assistant/ask_response.go
services/backend/internal/domain/assistant/assistant_answer.go
services/backend/internal/domain/assistant/assistent_client.go
services/backend/internal/domain/assistant/assistent_errors.go
services/backend/internal/domain/assistant/assistent_service.go
services/backend/internal/domain/assistant/event_rules.go
services/backend/internal/domain/assistant/module.go
services/backend/internal/domain/assistant/rules.go
services/backend/internal/domain/event_bus.go
services/backend/internal/domain/policy/auth.go
services/backend/internal/domain/policy/context.go
services/backend/internal/domain/policy/module.go
services/backend/internal/domain/policy/permission_resolver.go
services/backend/internal/domain/policy/policy_matrix.go
services/backend/internal/domain/policy/role_resolver.go
services/backend/internal/domain/reports/module.go
services/backend/internal/domain/reports/report.go
services/backend/internal/domain/reports/report_collection.go
services/backend/internal/domain/reports/report_errors.go
services/backend/internal/domain/reports/report_query.go
services/backend/internal/domain/reports/report_repository.go
services/backend/internal/domain/reports/report_service.go
services/backend/internal/domain/volunteering/event_card/allowed_actions.go
services/backend/internal/domain/volunteering/event_card/event_card.go
services/backend/internal/domain/volunteering/event_card/event_card_errors.go
services/backend/internal/domain/volunteering/event_card/event_card_page.go
services/backend/internal/domain/volunteering/event_card/event_card_page_view.go
services/backend/internal/domain/volunteering/event_card/event_card_query.go
services/backend/internal/domain/volunteering/event_card/event_card_service.go
services/backend/internal/domain/volunteering/event_card/event_card_view.go
services/backend/internal/domain/volunteering/event_card/event_view.go
services/backend/internal/domain/volunteering/event_card/filter.go
services/backend/internal/domain/volunteering/event_card/module.go
services/backend/internal/domain/volunteering/event_card/page_meta.go
services/backend/internal/domain/volunteering/event_organizer_card/card_service.go
services/backend/internal/domain/volunteering/event_organizer_card/event_organizer_card.go
services/backend/internal/domain/volunteering/event_organizer_card/module.go
services/backend/internal/domain/volunteering/event_organizer_card/organizer_query.go
services/backend/internal/domain/volunteering/event_organizers/event_organizer.go
services/backend/internal/domain/volunteering/event_organizers/event_organizer_repository.go
services/backend/internal/domain/volunteering/event_organizers/event_organizer_service.go
services/backend/internal/domain/volunteering/event_organizers/event_roganizer_errors.go
services/backend/internal/domain/volunteering/event_organizers/module.go
services/backend/internal/domain/volunteering/event_organizers/organizer_request.go
services/backend/internal/domain/volunteering/event_tags/event_tag.go
services/backend/internal/domain/volunteering/event_tags/event_tag_errors.go
services/backend/internal/domain/volunteering/event_tags/event_tag_repository.go
services/backend/internal/domain/volunteering/event_tags/event_tag_service.go
services/backend/internal/domain/volunteering/event_tags/module.go
services/backend/internal/domain/volunteering/events/create_event_request.go
services/backend/internal/domain/volunteering/events/event.go
services/backend/internal/domain/volunteering/events/event_config.go
services/backend/internal/domain/volunteering/events/event_errors.go
services/backend/internal/domain/volunteering/events/event_events.go
services/backend/internal/domain/volunteering/events/event_repository.go
services/backend/internal/domain/volunteering/events/event_service.go
services/backend/internal/domain/volunteering/events/event_state.go
services/backend/internal/domain/volunteering/events/event_update.go
services/backend/internal/domain/volunteering/events/module.go
services/backend/internal/domain/volunteering/events/verificaion_result.go
services/backend/internal/domain/volunteering/gosbs/actual_gosb.go
services/backend/internal/domain/volunteering/gosbs/gosb.go
services/backend/internal/domain/volunteering/gosbs/gosb_errors.go
services/backend/internal/domain/volunteering/gosbs/gosb_repository.go
services/backend/internal/domain/volunteering/gosbs/gosb_service.go
services/backend/internal/domain/volunteering/gosbs/module.go
services/backend/internal/domain/volunteering/module.go
services/backend/internal/domain/volunteering/participation_card/page_meta.go
services/backend/internal/domain/volunteering/participation_card/participation_card.go
services/backend/internal/domain/volunteering/participation_card/participation_card_page.go
services/backend/internal/domain/volunteering/participation_card/participation_card_query.go
services/backend/internal/domain/volunteering/participations/module.go
services/backend/internal/domain/volunteering/participations/participation.go
services/backend/internal/domain/volunteering/participations/participation_errors.go
services/backend/internal/domain/volunteering/participations/participation_repository.go
services/backend/internal/domain/volunteering/participations/participation_service.go
services/backend/internal/domain/volunteering/project_card/allowed_actions.go
services/backend/internal/domain/volunteering/project_card/filter.go
services/backend/internal/domain/volunteering/project_card/module.go
services/backend/internal/domain/volunteering/project_card/page_meta.go
services/backend/internal/domain/volunteering/project_card/project_card.go
services/backend/internal/domain/volunteering/project_card/project_card_page.go
services/backend/internal/domain/volunteering/project_card/project_card_page_view.go
services/backend/internal/domain/volunteering/project_card/project_card_service.go
services/backend/internal/domain/volunteering/project_card/project_card_view.go
services/backend/internal/domain/volunteering/project_card/project_query.go
services/backend/internal/domain/volunteering/project_card/project_view.go
services/backend/internal/domain/volunteering/projects/create_project_request.go
services/backend/internal/domain/volunteering/projects/module.go
services/backend/internal/domain/volunteering/projects/project.go
services/backend/internal/domain/volunteering/projects/project_errors.go
services/backend/internal/domain/volunteering/projects/project_repository.go
services/backend/internal/domain/volunteering/projects/project_service.go
services/backend/internal/domain/volunteering/projects/project_state.go
services/backend/internal/domain/volunteering/projects/project_update.go
services/backend/internal/domain/volunteering/registry/create_result.go
services/backend/internal/domain/volunteering/registry/create_type.go
services/backend/internal/domain/volunteering/registry/module.go
services/backend/internal/domain/volunteering/registry/registry_service.go
services/backend/internal/domain/volunteering/registry/resource_activator.go
services/backend/internal/domain/volunteering/request_card/allowed_actions.go
services/backend/internal/domain/volunteering/request_card/filter.go
services/backend/internal/domain/volunteering/request_card/module.go
services/backend/internal/domain/volunteering/request_card/page_meta.go
services/backend/internal/domain/volunteering/request_card/request_card.go
services/backend/internal/domain/volunteering/request_card/request_card_errors.go
services/backend/internal/domain/volunteering/request_card/request_card_page.go
services/backend/internal/domain/volunteering/request_card/request_card_query.go
services/backend/internal/domain/volunteering/request_card/request_card_service.go
services/backend/internal/domain/volunteering/request_card/request_card_view.go
services/backend/internal/domain/volunteering/requests/module.go
services/backend/internal/domain/volunteering/requests/request.go
services/backend/internal/domain/volunteering/requests/request_errors.go
services/backend/internal/domain/volunteering/requests/request_repository.go
services/backend/internal/domain/volunteering/requests/request_service.go
services/backend/internal/domain/volunteering/requests/request_state.go
services/backend/internal/domain/volunteering/tags/actual_tag.go
services/backend/internal/domain/volunteering/tags/module.go
services/backend/internal/domain/volunteering/tags/tag.go
services/backend/internal/domain/volunteering/tags/tag_errors.go
services/backend/internal/domain/volunteering/tags/tag_repository.go
services/backend/internal/domain/volunteering/tags/tag_service.go
services/backend/internal/domain/volunteering/terbank_moderators/module.go
services/backend/internal/domain/volunteering/terbank_moderators/terbank_moderator.go
services/backend/internal/domain/volunteering/terbank_moderators/terbank_moderator_errors.go
services/backend/internal/domain/volunteering/terbank_moderators/terbank_moderator_repository.go
services/backend/internal/domain/volunteering/terbank_moderators/terbank_moderator_service.go
services/backend/internal/domain/volunteering/terbanks/actual_terbank.go
services/backend/internal/domain/volunteering/terbanks/module.go
services/backend/internal/domain/volunteering/terbanks/terbank.go
services/backend/internal/domain/volunteering/terbanks/terbank_errors.go
services/backend/internal/domain/volunteering/terbanks/terbank_repository.go
services/backend/internal/domain/volunteering/terbanks/terbank_service.go
services/backend/internal/domain/volunteering/volunteer_card/module.go
services/backend/internal/domain/volunteering/volunteer_card/volunteer_card.go
services/backend/internal/domain/volunteering/volunteer_card/volunteer_card_query.go
services/backend/internal/domain/volunteering/volunteer_card/volunteer_card_service.go
services/backend/internal/domain/volunteering/volunteer_tags/module.go
services/backend/internal/domain/volunteering/volunteer_tags/volunteer_tag.go
services/backend/internal/domain/volunteering/volunteer_tags/volunteer_tag_errors.go
services/backend/internal/domain/volunteering/volunteer_tags/volunteer_tag_repository.go
services/backend/internal/domain/volunteering/volunteer_tags/volunteer_tag_service.go
services/backend/internal/domain/volunteering/volunteers/module.go
services/backend/internal/domain/volunteering/volunteers/volunteer.go
services/backend/internal/domain/volunteering/volunteers/volunteer_errors.go
services/backend/internal/domain/volunteering/volunteers/volunteer_repository.go
services/backend/internal/domain/volunteering/volunteers/volunteer_service.go
services/backend/internal/drivers/http/middlewares/auth_middleware.go
services/backend/internal/drivers/http/middlewares/context_middleware.go
services/backend/internal/drivers/http/middlewares/module.go
services/backend/internal/drivers/http/middlewares/policy_middleware.go
services/backend/internal/drivers/http/middlewares/role_middleware.go
services/backend/internal/drivers/http/middlewares/test_auth_middleware.go
services/backend/internal/drivers/http/v1/assistant/assistant_controller.go
services/backend/internal/drivers/http/v1/assistant/assistant_requests.go
services/backend/internal/drivers/http/v1/assistant/assistant_routes.go
services/backend/internal/drivers/http/v1/health/health_controller.go
services/backend/internal/drivers/http/v1/health/health_routes.go
services/backend/internal/drivers/http/v1/reports/event_report_builder.go
services/backend/internal/drivers/http/v1/reports/report_controller.go
services/backend/internal/drivers/http/v1/reports/report_request.go
services/backend/internal/drivers/http/v1/reports/report_routes.go
services/backend/internal/drivers/http/v1/routes.go
services/backend/internal/drivers/http/v1/swagger/swagger_routes.go
services/backend/internal/drivers/http/v1/volunteering/event_card/event_card_controller.go
services/backend/internal/drivers/http/v1/volunteering/event_card/event_card_request.go
services/backend/internal/drivers/http/v1/volunteering/event_organizers/event_organizer_controller.go
services/backend/internal/drivers/http/v1/volunteering/event_organizers/event_organizer_requests.go
services/backend/internal/drivers/http/v1/volunteering/event_tags/event_tag_controller.go
services/backend/internal/drivers/http/v1/volunteering/event_tags/event_tag_request.go
services/backend/internal/drivers/http/v1/volunteering/events/event_controller.go
services/backend/internal/drivers/http/v1/volunteering/gosbs/gosb_controller.go
services/backend/internal/drivers/http/v1/volunteering/participation/participation_controller.go
services/backend/internal/drivers/http/v1/volunteering/participation/participation_requests.go
services/backend/internal/drivers/http/v1/volunteering/participation_card/participation_card_controller.go
services/backend/internal/drivers/http/v1/volunteering/participation_card/participation_card_request.go
services/backend/internal/drivers/http/v1/volunteering/project_card/project_card_controller.go
services/backend/internal/drivers/http/v1/volunteering/project_card/project_card_request.go
services/backend/internal/drivers/http/v1/volunteering/projects/project_controller.go
services/backend/internal/drivers/http/v1/volunteering/registry/registry_controller.go
services/backend/internal/drivers/http/v1/volunteering/request_card/request_card_controller.go
services/backend/internal/drivers/http/v1/volunteering/request_card/request_card_request.go
services/backend/internal/drivers/http/v1/volunteering/requests/request_contoller.go
services/backend/internal/drivers/http/v1/volunteering/requests/request_requests.go
services/backend/internal/drivers/http/v1/volunteering/tags/tags_controller.go
services/backend/internal/drivers/http/v1/volunteering/terbank_moderators/terbank_moderator_controller.go
services/backend/internal/drivers/http/v1/volunteering/terbank_moderators/terbank_moderator_requests.go
services/backend/internal/drivers/http/v1/volunteering/terbanks/terbank_controller.go
services/backend/internal/drivers/http/v1/volunteering/volunteer/volunteer_controller.go
services/backend/internal/drivers/http/v1/volunteering/volunteer/volunteer_requests.go
services/backend/internal/drivers/http/v1/volunteering/volunteer_card/volunteer_card_controller.go
services/backend/internal/drivers/http/v1/volunteering/volunteer_tag/volunteer_tag_controller.go
services/backend/internal/drivers/http/v1/volunteering/volunteer_tag/volunteer_tag_requests.go
services/backend/internal/drivers/http/v1/volunteering/volunteering_routes.go
services/backend/internal/drivers/seeders/admin_seeder.go
services/backend/internal/drivers/seeders/gosb_seeder.go
services/backend/internal/drivers/seeders/seeders.go
services/backend/internal/drivers/seeders/tag_seeder.go
services/backend/internal/drivers/seeders/terbank_seeder.go
services/backend/main.go
services/backend/migrations/000001_volunteer_table.down.sql
services/backend/migrations/000001_volunteer_table.up.sql
services/backend/migrations/000002_tag_table.down.sql
services/backend/migrations/000002_tag_table.up.sql
services/backend/migrations/000003_volunteer_tag_table.down.sql
services/backend/migrations/000003_volunteer_tag_table.up.sql
services/backend/migrations/000004_volunteer_role.down.sql
services/backend/migrations/000004_volunteer_role.up.sql
services/backend/migrations/000005_event_table.down.sql
services/backend/migrations/000005_event_table.up.sql
services/backend/migrations/000006_project_table.down.sql
services/backend/migrations/000006_project_table.up.sql
services/backend/migrations/000007_request_table.down.sql
services/backend/migrations/000007_request_table.up.sql
services/backend/migrations/000008_event_tag_table.down.sql
services/backend/migrations/000008_event_tag_table.up.sql
services/backend/migrations/000009_event_creator.down.sql
services/backend/migrations/000009_event_creator.up.sql
services/backend/migrations/000010_terbank_table.down.sql
services/backend/migrations/000010_terbank_table.up.sql
services/backend/migrations/000011_terbank_moderator_table.down.sql
services/backend/migrations/000011_terbank_moderator_table.up.sql
services/backend/migrations/000012_gosb_table.down.sql
services/backend/migrations/000012_gosb_table.up.sql
services/backend/migrations/000013_change_role.down.sql
services/backend/migrations/000013_change_role.up.sql
services/backend/migrations/000014_rename_terbank_id.down.sql
services/backend/migrations/000014_rename_terbank_id.up.sql
services/backend/migrations/000015_organizer_table.down.sql
services/backend/migrations/000015_organizer_table.up.sql
services/backend/migrations/000016_organizer_role.down.sql
services/backend/migrations/000016_organizer_role.up.sql
services/backend/migrations/000017_participation_table.down.sql
services/backend/migrations/000017_participation_table.up.sql
services/backend/migrations/000018_event_hours.down.sql
services/backend/migrations/000018_event_hours.up.sql
services/backend/migrations/000019_report_table.down.sql
services/backend/migrations/000019_report_table.up.sql
services/backend/migrations/000020_many_reports.down.sql
services/backend/migrations/000020_many_reports.up.sql
services/backend/migrations/000021_gosp_to_gosb.down.sql
services/backend/migrations/000021_gosp_to_gosb.up.sql
services/backend/migrations/000022_participation_limit.down.sql
services/backend/migrations/000022_participation_limit.up.sql
services/backend/migrations/000023_participation_count.down.sql
services/backend/migrations/000023_participation_count.up.sql
services/backend/migrations/000024_participation_hours.down.sql
services/backend/migrations/000024_participation_hours.up.sql
services/backend/migrations/000025_project_active.down.sql
services/backend/migrations/000025_project_active.up.sql
services/backend/migrations/000026_project_archive_at.down.sql
services/backend/migrations/000026_project_archive_at.up.sql
services/backend/pkg/bearer_parser.go
services/backend/pkg/excel_service.go
services/backend/pkg/gin_binding.go
services/backend/pkg/gorm.go
services/backend/pkg/gorm_logger.go
services/backend/pkg/gorm_paginate_simple.go
services/backend/pkg/http.go
services/backend/pkg/logger.go
services/backend/pkg/migrate_postgres.go
services/backend/pkg/module.go
services/backend/pkg/request_handler.go
services/bot/Dockerfile
services/bot/chain.pem
services/bot/core/__init__.py
services/bot/core/bot_kit/__init__.py
services/bot/core/bot_kit/fsm/__init__.py
services/bot/core/bot_kit/fsm/context.py
services/bot/core/bot_kit/fsm/state.py
services/bot/core/bot_kit/fsm/storage/__init__.py
services/bot/core/bot_kit/fsm/storage/base.py
services/bot/core/bot_kit/fsm/storage/in_memory.py
services/bot/core/bot_kit/router.py
services/bot/core/clients/__init__.py
services/bot/core/clients/admin.py
services/bot/core/clients/ai_assistant.py
services/bot/core/clients/base.py
services/bot/core/clients/event.py
services/bot/core/clients/participation.py
services/bot/core/clients/project.py
services/bot/core/clients/report.py
services/bot/core/clients/request.py
services/bot/core/clients/tag.py
services/bot/core/clients/terbank.py
services/bot/core/clients/user.py
services/bot/core/config/__init__.py
services/bot/core/config/bootstrap.py
services/bot/core/config/settings.py
services/bot/core/config/validation_config.py
services/bot/core/dependencies/__init__.py
services/bot/core/dependencies/admin.py
services/bot/core/dependencies/base.py
services/bot/core/dependencies/context.py
services/bot/core/dependencies/di.py
services/bot/core/dependencies/event.py
services/bot/core/dependencies/participation.py
services/bot/core/dependencies/project.py
services/bot/core/dependencies/registry.py
services/bot/core/dependencies/report.py
services/bot/core/dependencies/request.py
services/bot/core/dependencies/tag.py
services/bot/core/dependencies/terbank.py
services/bot/core/dependencies/user.py
services/bot/core/handlers/__init__.py
services/bot/core/handlers/admin.py
services/bot/core/handlers/ai.py
services/bot/core/handlers/event.py
services/bot/core/handlers/event_participations.py
services/bot/core/handlers/events_filters.py
services/bot/core/handlers/events_pages.py
services/bot/core/handlers/events_ui.py
services/bot/core/handlers/main.py
services/bot/core/handlers/moderation_common.py
services/bot/core/handlers/moderation_create.py
services/bot/core/handlers/moderation_edit.py
services/bot/core/handlers/moderation_menu.py
services/bot/core/handlers/noop.py
services/bot/core/handlers/participation_ui.py
services/bot/core/handlers/points.py
services/bot/core/handlers/profile.py
services/bot/core/handlers/project_pages.py
services/bot/core/handlers/projects.py
services/bot/core/handlers/projects_ui.py
services/bot/core/handlers/report.py
services/bot/core/handlers/request.py
services/bot/core/handlers/validators.py
services/bot/core/handlers/view_by_uuid.py
services/bot/core/handlers/volunteer_home.py
services/bot/core/markups/__init__.py
services/bot/core/markups/admin.py
services/bot/core/markups/ai.py
services/bot/core/markups/event.py
services/bot/core/markups/events_filters.py
services/bot/core/markups/gosb.py
services/bot/core/markups/main.py
services/bot/core/markups/moderation.py
services/bot/core/markups/pagination.py
services/bot/core/markups/participation.py
services/bot/core/markups/points.py
services/bot/core/markups/profile.py
services/bot/core/markups/project.py
services/bot/core/markups/reports.py
services/bot/core/markups/request.py
services/bot/core/markups/tags.py
services/bot/core/markups/terbank.py
services/bot/core/markups/volunteer_home.py
services/bot/core/schemas/__init__.py
services/bot/core/schemas/event.py
services/bot/core/schemas/gosb.py
services/bot/core/schemas/participation.py
services/bot/core/schemas/project.py
services/bot/core/schemas/request.py
services/bot/core/schemas/tag.py
services/bot/core/schemas/terbank.py
services/bot/core/schemas/user.py
services/bot/core/services/__init__.py
services/bot/core/services/admin.py
services/bot/core/services/base.py
services/bot/core/services/event.py
services/bot/core/services/file.py
services/bot/core/services/participation.py
services/bot/core/services/project.py
services/bot/core/services/registry/__init__.py
services/bot/core/services/registry/base.py
services/bot/core/services/registry/registry.py
services/bot/core/services/report.py
services/bot/core/services/requests.py
services/bot/core/services/tag.py
services/bot/core/services/terbank.py
services/bot/core/services/user.py
services/bot/core/states/__init__.py
services/bot/core/utils/__init__.py
services/bot/core/utils/date_time.py
services/bot/core/utils/events_filter.py
services/bot/core/utils/fsm/__init__.py
services/bot/core/utils/fsm/base.py
services/bot/core/utils/fsm/state.py
services/bot/core/utils/fsm/storage/__init__.py
services/bot/core/utils/fsm/storage/base.py
services/bot/core/utils/fsm/storage/memory.py
services/bot/core/utils/logger.py
services/bot/core/utils/messages.py
services/bot/core/utils/tags_toggle_ui.py
services/bot/core/utils/text_wrap.py
services/bot/main.py
services/bot/requirements.txt
templates.yml
utils/delete_all_events.zsh
utils/delete_all_users.zsh
utils/format
utils/prettify_logs.py
utils/restore_admin.zsh
utils/seed_events.json
utils/seed_test_data.zsh
utils/seed_users.tsv
validation.yml
Editor is loading...
Leave a Comment