from __future__ import annotations import uuid from typing import TYPE_CHECKING from sqlalchemy import Boolean, Enum, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base, TimestampMixin, UUIDPrimaryKeyMixin from app.models.enums import NotificationType, SeverityLevel if TYPE_CHECKING: from app.models.company import Company class NotificationDestination(Base, UUIDPrimaryKeyMixin, TimestampMixin): __tablename__ = "notification_destinations" user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), index=True ) type: Mapped[NotificationType] = mapped_column( Enum(NotificationType, native_enum=False, length=20) ) # Email address, phone number, or a label for the console provider. # Not a secret, but still PII - see SECURITY.md. destination_value: Mapped[str] = mapped_column(String(320)) verified: Mapped[bool] = mapped_column(Boolean, default=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True) minimum_severity: Mapped[SeverityLevel] = mapped_column( Enum(SeverityLevel, native_enum=False, length=20), default=SeverityLevel.MEDIUM ) company_links: Mapped[list[NotificationDestinationCompany]] = relationship( back_populates="destination", cascade="all, delete-orphan" ) class NotificationDestinationCompany(Base, TimestampMixin): """Which companies a destination receives alerts for - a destination with zero links is orphaned and gets garbage-collected (see notification_destination_service.py) rather than left dangling.""" __tablename__ = "notification_destination_companies" destination_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("notification_destinations.id", ondelete="CASCADE"), primary_key=True ) company_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("companies.id", ondelete="CASCADE"), primary_key=True ) destination: Mapped[NotificationDestination] = relationship(back_populates="company_links") # Read-only path to the company's name for display; Company.notification_links # (used only for cascade-delete) writes the same FK from the other direction, # hence overlaps= to tell SQLAlchemy that's intentional, not a conflict. company: Mapped[Company] = relationship(overlaps="notification_links")