import time import requests from django.conf import settings from django.core.cache import cache from utils.clients.gooyal_notifications_client import AuthenticatedClient from utils.clients.gooyal_notifications_client.api.push import push_application_application_create ACCESS_TOKEN_CACHE_KEY = "chat_notifications_access_token" def login_as_client_credentials() -> dict: """ Client-credentials OAuth2 grant against the gooyal accounts service, reusing a cached access token until it's within 5s of expiry. """ cached = cache.get(ACCESS_TOKEN_CACHE_KEY) if cached: return cached data = { "grant_type": "client_credentials", "scope": settings.OAUTH2_PROVIDER_SCOPES, } auth = (settings.OAUTH2_PROVIDER_CLIENT_ID, settings.OAUTH2_PROVIDER_CLIENT_SECRET) response = requests.post( f"{settings.OAUTH2_PROVIDER_BASE_PUBLIC_URL}/token/", data=data, auth=auth ) response.raise_for_status() auth_data = response.json() if "access_token" in auth_data: auth_data["created_at"] = time.time() cache.set( ACCESS_TOKEN_CACHE_KEY, auth_data, timeout=auth_data.get("expires_in", 300) - 5 ) return auth_data def get_client() -> AuthenticatedClient: access_token = login_as_client_credentials()["access_token"] return AuthenticatedClient( base_url=settings.NOTIFICATIONS_BASE_PUBLIC_URL, token=access_token ) class _PushBody: """ Duck-typed request body: push_user is already in the URL and application is resolved server-side from the OAuth2 client, so only these fields are sent (mirrors the advertising service's client). """ def __init__(self, title: str, message: str, priority: int, extras): self._title = title self._message = message self._priority = priority self._extras = extras def to_dict(self) -> dict: return { "title": self._title, "message": self._message, "priority": self._priority, "extras": self._extras, } def push_user(user_uuid, title: str, message: str, priority: int = 5, extras=None): """ Send a push notification to a single user via the gooyal notifications service. Requires the `notifications.application.push:send` scope on this app's OAuth2 client credentials. Raises on failure — callers decide whether a down notifications service should be swallowed. """ client = get_client() body = _PushBody(title=title, message=message, priority=priority, extras=extras) return push_application_application_create.sync(user_uuid=str(user_uuid), client=client, body=body)