import logging import uuid from datetime import timedelta from sqlalchemy.orm import Session from shared.enums.notification import NotificationSeverity, NotificationType from shared.enums.sse import SSEChannel, SSEEventType from shared.models.notification import Notification, NotificationMetadata from shared.services.event_publisher import SyncEventPublisher from shared.utils.datetime import utc_now logger = logging.getLogger(__name__) NOTIFICATION_EXPIRY_DAYS = 7 def single_project(rows) -> uuid.UUID | None: ids = {r.project_id for r in rows if getattr(r, "project_id", None)} return next(iter(ids)) if len(ids) == 1 else None class SyncNotificationPublisher: def __init__(self, redis_url: str) -> None: self._event_publisher = SyncEventPublisher(redis_url) def publish( self, session: Session, type: NotificationType, severity: NotificationSeverity, title: str, message: str, metadata: NotificationMetadata | dict | None = None, project_id: uuid.UUID | str | None = None, ) -> Notification: if isinstance(metadata, NotificationMetadata): metadata_dict = metadata.model_dump(exclude_none=True) elif isinstance(metadata, dict): validated = NotificationMetadata(**metadata) metadata_dict = validated.model_dump(exclude_none=True) else: metadata_dict = {} now = utc_now() notification = Notification( type=type, severity=severity, title=title[:200], message=message, notification_metadata=metadata_dict, project_id=uuid.UUID(str(project_id)) if project_id else None, created_at=now, expires_at=now + timedelta(days=NOTIFICATION_EXPIRY_DAYS), ) session.add(notification) session.commit() session.refresh(notification) self._event_publisher.publish( channel=SSEChannel.BROADCAST, event_type=SSEEventType.NOTIFICATION, data={ "id": notification.id, "project_id": str(notification.project_id) if notification.project_id else None, "type": notification.type.value, "severity": notification.severity.value, "title": notification.title, "message": notification.message, "notification_metadata": notification.notification_metadata, "is_read": notification.is_read, "created_at": notification.created_at.isoformat(), }, ) logger.info( "Published notification: %s/%s - %s", type.value, severity.value, title ) try: from shared.services.notifier import dispatch_sync # noqa: PLC0415 dispatch_sync(session, type, severity, title, message) except Exception as exc: logger.warning("External notification dispatch failed: %s", exc) return notification