Files
ForbiddenStarsApp/backend/app/services/notify.py
T
NotBigGhostandClaude Opus 5.5 ef0e75aef2 SSE: не рассылать ratings на правки без влияния на рейтинг; событие group при смене настроек группы
- notify.match_changed получил флаг ratings: фото (игрок и админ) его не шлют, правка
  партии — только если изменились входы рейтинга (match_service.rating_inputs до/после).
- PATCH /api/groups/{id} публикует group — переименование и правило 9 раундов видны
  остальным участникам сразу.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:52:44 +03:00

92 lines
5.1 KiB
Python

"""Публикация 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, 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 _ratings_changed(session: Session, notified: list[int]) -> None:
"""Рейтинг общий и считается по всей истории (#80): завершённая партия двигает топ,
главную, историю и профили всех, кто играл после неё, и страницы других групп.
Игрокам вне группы (notified уже знают) — событие без подробностей о партии."""
skip = set(notified)
ids = [
uid
for uid in session.exec(
select(User.id).where(User.role == "player", User.is_active.is_(True)) # type: ignore[union-attr]
).all()
if uid not in skip
]
hub.publish(ids, {"type": "ratings"})
def match_changed(session: Session, match: Match, *, ratings: bool = True) -> None:
"""Партия изменилась — уведомить всех участников её группы, а если она завершена —
и остальных игроков (_ratings_changed).
ratings=False — правка, которая рейтинг не двигает (фото, комментарий): тогда
остальным игрокам незачем перезапрашивать все рейтинговые витрины."""
members = _group_member_ids(session, match.group_id)
hub.publish(members, {"type": "match", "match_id": match.id, "group_id": match.group_id})
if ratings and match.status == "finished":
_ratings_changed(session, members)
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, *, finished: bool) -> None:
"""Партия удалена — уведомить участников группы (обновить списки), а если она была
завершена — и остальных игроков. Статус передаётся снаружи: партии уже нет."""
members = _group_member_ids(session, group_id)
hub.publish(members, {"type": "match", "match_id": match_id, "group_id": group_id})
if finished:
_ratings_changed(session, members)
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"})