"""Публикация SSE-событий «данные изменились» нужным пользователям. Тонкая прослойка над core.events.hub — держит роутеры чистыми. Событие несёт лишь тип и id, клиент по нему точечно перезапрашивает данные. """ from __future__ import annotations from sqlmodel import Session, select from app.core.events import hub from app.models import GroupMember, Match, MatchParticipant, User def _group_member_ids(session: Session, group_id: int) -> list[int]: return list( session.exec(select(GroupMember.user_id).where(GroupMember.group_id == group_id)).all() ) def match_participant_ids(session: Session, match_id: int) -> list[int]: """Кто играл в партии. Нужен в событии, чтобы клиент понимал, чьи витрины (история игр, публичный профиль, личная статистика) реально протухли.""" return list( session.exec( select(MatchParticipant.user_id).where(MatchParticipant.match_id == match_id) ).all() ) def match_changed(session: Session, match: Match) -> None: """Партия изменилась — уведомить всех участников её группы. Адресат — вся группа: списки партий и статистика группы меняются у всех. А вот история и профили протухают только у игравших, поэтому их id едут в событии.""" hub.publish( _group_member_ids(session, match.group_id), { "type": "match", "match_id": match.id, "group_id": match.group_id, "participant_ids": match_participant_ids(session, match.id), # type: ignore[arg-type] }, ) def match_draft_changed(session: Session, match: Match, actor_id: int) -> None: """Черновик формы завершения изменился — остальным заполняющим из группы. Отдельный тип события: черновик меняется на каждое движение тайла, и гнать по нему полную инвалидацию (лидерборд, история, профили) было бы расточительно. Автору правки событие не шлём — у него уже актуальное состояние.""" ids = [uid for uid in _group_member_ids(session, match.group_id) if uid != actor_id] hub.publish(ids, {"type": "match_draft", "match_id": match.id, "group_id": match.group_id}) def match_removed( session: Session, match_id: int, group_id: int, participant_ids: list[int] | None = None ) -> None: """Партия удалена — уведомить участников группы (обновить списки). participant_ids передаются снаружи: к этому моменту партии уже нет, а её участники ушли каскадом, и собрать их из базы невозможно.""" hub.publish( _group_member_ids(session, group_id), { "type": "match", "match_id": match_id, "group_id": group_id, "participant_ids": participant_ids or [], }, ) def group_changed(session: Session, group_id: int, extra_user_ids: list[int] | None = None) -> None: """Состав/данные группы изменились — участникам (+ доп. адресатам, напр. удалённому).""" ids = _group_member_ids(session, group_id) if extra_user_ids: ids = ids + extra_user_ids hub.publish(ids, {"type": "group", "group_id": group_id}) def invitations_changed(user_id: int) -> None: """У пользователя изменился список приглашений.""" hub.publish([user_id], {"type": "invitations"}) def notifications_changed(user_id: int) -> None: """У пользователя появилось/изменилось уведомление — пусть подтянет список.""" hub.publish([user_id], {"type": "notifications"}) def announcements_changed(session: Session) -> None: """Админ создал, поправил, снял или удалил объявление — всем активным игрокам: открытые вкладки перезапросят, что показать. Объявление, чей период начнётся позже, клиент подхватит сам — периодическим перезапросом.""" ids = session.exec( select(User.id).where(User.role == "player", User.is_active.is_(True)) # type: ignore[union-attr] ).all() hub.publish(ids, {"type": "announcements"})