"""Envoi des notifications Web Push.""" import asyncio import base64 import json from tempfile import NamedTemporaryFile from typing import Iterable from pywebpush import WebPushException, webpush from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import settings from app.models.push_subscription import PushSubscription def notifications_configured() -> bool: return bool(settings.VAPID_PUBLIC_KEY and settings.VAPID_PRIVATE_KEY_B64) def _private_key_pem() -> str: if not settings.VAPID_PRIVATE_KEY_B64: return "" return base64.b64decode(settings.VAPID_PRIVATE_KEY_B64).decode() def _send_sync(abonnement: dict, payload: dict) -> None: private_key = _private_key_pem() with NamedTemporaryFile("w", suffix=".pem") as key_file: key_file.write(private_key) key_file.flush() webpush( subscription_info=abonnement, data=json.dumps(payload, ensure_ascii=False), vapid_private_key=key_file.name, vapid_claims={"sub": settings.VAPID_SUBJECT}, ) async def send_to_subscriptions(db: AsyncSession, subscriptions: Iterable[PushSubscription], payload: dict) -> int: if not notifications_configured(): return 0 sent = 0 for sub in subscriptions: try: await asyncio.to_thread(_send_sync, sub.abonnement, payload) sent += 1 except WebPushException as exc: if getattr(exc.response, "status_code", None) in {404, 410}: await db.delete(sub) except Exception: continue return sent async def notify_group_change( db: AsyncSession, *, groupe_id, actor_id, title: str, body: str, url: str = "/", ) -> int: if not notifications_configured() or not groupe_id: return 0 result = await db.execute( select(PushSubscription).where( PushSubscription.groupe_id == groupe_id, PushSubscription.membre_id != actor_id, ) ) return await send_to_subscriptions(db, result.scalars().all(), { "title": title, "body": body, "url": url, })