"""Webhook model — outgoing webhook subscriptions for event-driven notifications.""" from __future__ import annotations import uuid from sqlalchemy import JSON, Boolean, Integer, String from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin from app.models.owned_mixin import OwnedMixin class Webhook(Base, TenantMixin, OwnedMixin): """Outgoing webhook subscription. Each webhook defines a target URL, a list of events to subscribe to, and delivery settings (retry count, timeout, HMAC secret). """ __tablename__ = "webhooks" id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) # ── Target ── url: Mapped[str] = mapped_column(String(500), nullable=False) events: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) secret: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) # ── Delivery Settings ── is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=3) timeout_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=30) # ── Audit ── created_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True ) updated_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True ) @property def has_secret(self) -> bool: """Return True if a webhook secret is set (never expose the secret itself).""" return self.secret is not None and len(self.secret) > 0