import secrets import time import uuid from datetime import datetime from decimal import Decimal from typing import Any from sqlalchemy import ( ARRAY, BigInteger, Boolean, CheckConstraint, DateTime, ForeignKey, Index, Numeric, SmallInteger, String, Text, UniqueConstraint, func, ) from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from app.db import SCHEMA, Base, Common def uuid7() -> uuid.UUID: """Generate an RFC 9562 UUIDv7 without relying on Python 3.14.""" timestamp_ms = int(time.time_ns() // 1_000_000) & ((1 << 48) - 1) value = timestamp_ms << 80 value |= 0x7 << 76 value |= secrets.randbits(12) << 64 value |= 0b10 << 62 value |= secrets.randbits(62) return uuid.UUID(int=value) class NotificationCtaAction(Common, Base): __tablename__ = "notification_cta_actions" __table_args__ = (UniqueConstraint("code"), {"schema": SCHEMA}) code: Mapped[str] = mapped_column(String(32)) description: Mapped[str] = mapped_column(String(255)) requires_auth: Mapped[bool] = mapped_column(Boolean, default=False) required_instance_fields: Mapped[list[str]] = mapped_column( ARRAY(String(64)), default=list, server_default="{}" ) class NotificationButton(Common, Base): __tablename__ = "notification_buttons" __table_args__ = ( UniqueConstraint("code"), CheckConstraint("NOT applies_hidden_ttl OR sets_hidden"), CheckConstraint("NOT submits_documents OR close_reason IS NOT NULL"), {"schema": SCHEMA}, ) code: Mapped[str] = mapped_column(String(32)) label: Mapped[str] = mapped_column(String(64)) sets_hidden: Mapped[bool] = mapped_column(Boolean, default=False) applies_hidden_ttl: Mapped[bool] = mapped_column(Boolean, default=False) close_reason: Mapped[str | None] = mapped_column(String(32)) submits_documents: Mapped[bool] = mapped_column(Boolean, default=False) class NotificationColorToken(Common, Base): __tablename__ = "notification_color_tokens" __table_args__ = (UniqueConstraint("code"), {"schema": SCHEMA}) code: Mapped[str] = mapped_column(String(32)) description: Mapped[str] = mapped_column(String(255)) sort_order: Mapped[int] = mapped_column(SmallInteger) class NotificationType(Common, Base): __tablename__ = "notification_types" __table_args__ = ( UniqueConstraint("code"), CheckConstraint("contour IN ('G','P')"), CheckConstraint("contour <> 'G' OR countable = false"), CheckConstraint("contour <> 'G' OR cta_action <> 'open_detail'"), CheckConstraint( "cta_action <> 'open_detail' OR (cta_sets_hidden = false AND cta_close_reason IS NULL)" ), CheckConstraint( "cta_action = 'open_detail' OR " "(documents_allowed = false AND hide_on_document_download = false " "AND required_detail_blocks = '{}')" ), CheckConstraint("NOT hide_on_document_download OR documents_allowed"), CheckConstraint("cta_action <> 'open_detail' OR button_primary_code IS NOT NULL"), CheckConstraint( "cta_action = 'open_detail' OR " "(button_primary_code IS NULL AND button_secondary_code IS NULL)" ), CheckConstraint("button_secondary_code IS NULL OR button_primary_code IS NOT NULL"), CheckConstraint( "button_secondary_code IS NULL OR button_secondary_code <> button_primary_code" ), {"schema": SCHEMA}, ) code: Mapped[str] = mapped_column(String(32)) contour: Mapped[str] = mapped_column(String(1)) priority: Mapped[int] = mapped_column(SmallInteger) countable: Mapped[bool] = mapped_column(Boolean) label: Mapped[str] = mapped_column(String(64)) color_token: Mapped[str] = mapped_column( ForeignKey(f"{SCHEMA}.notification_color_tokens.code", ondelete="RESTRICT") ) icon_code: Mapped[str | None] = mapped_column(String(32)) cta_text: Mapped[str] = mapped_column(String(64)) cta_action: Mapped[str] = mapped_column( ForeignKey(f"{SCHEMA}.notification_cta_actions.code", ondelete="RESTRICT") ) cta_sets_hidden: Mapped[bool] = mapped_column(Boolean, default=False) cta_close_reason: Mapped[str | None] = mapped_column(String(32)) button_primary_code: Mapped[str | None] = mapped_column( ForeignKey(f"{SCHEMA}.notification_buttons.code", ondelete="RESTRICT") ) button_secondary_code: Mapped[str | None] = mapped_column( ForeignKey(f"{SCHEMA}.notification_buttons.code", ondelete="RESTRICT") ) hidden_ttl_days: Mapped[int | None] = mapped_column(SmallInteger) documents_allowed: Mapped[bool] = mapped_column(Boolean, default=False) hide_on_document_download: Mapped[bool] = mapped_column(Boolean, default=False) required_detail_blocks: Mapped[list[str]] = mapped_column( ARRAY(String(64)), default=list, server_default="{}" ) class NotificationSource(Common, Base): __tablename__ = "notification_sources" __table_args__ = (UniqueConstraint("code"), UniqueConstraint("token_hash"), {"schema": SCHEMA}) code: Mapped[str] = mapped_column(String(32)) description: Mapped[str | None] = mapped_column(Text) token_hash: Mapped[str] = mapped_column(String(128)) token_rotated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class Notification(Common, Base): __tablename__ = "notifications" __table_args__ = ( UniqueConstraint("source", "external_id", name="uq_notifications_source_key"), CheckConstraint("lifecycle_status IN ('active','closed')"), CheckConstraint("visibility IN ('visible','hidden')"), CheckConstraint( "close_reason IS NULL OR close_reason IN " "('user_done','docs_submitted','offer_accepted','paid','expired','cancelled')" ), CheckConstraint( "lifecycle_status <> 'closed' OR (close_reason IS NOT NULL AND closed_at IS NOT NULL)" ), CheckConstraint("old_price IS NULL OR price IS NOT NULL"), Index( "ix_notifications_user_active", "user_id", "lifecycle_status", "visibility", "notification_datetime", "id", ), Index("ix_notifications_expire", "date_expired"), {"schema": SCHEMA}, ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid7) user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey(f"{SCHEMA}.user_identities.id", ondelete="RESTRICT") ) notification_type: Mapped[str] = mapped_column( ForeignKey(f"{SCHEMA}.notification_types.code", ondelete="RESTRICT") ) source: Mapped[str] = mapped_column( ForeignKey(f"{SCHEMA}.notification_sources.code", ondelete="RESTRICT") ) external_id: Mapped[str] = mapped_column(String(128)) request_fingerprint: Mapped[str] = mapped_column(String(64)) notification_datetime: Mapped[datetime] = mapped_column(DateTime(timezone=True)) header: Mapped[str] = mapped_column(String(255)) text: Mapped[str | None] = mapped_column(String(1024)) priority_override: Mapped[int | None] = mapped_column(SmallInteger) date_expired: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) old_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) payment_url: Mapped[str | None] = mapped_column(Text) details: Mapped[dict[str, Any] | None] = mapped_column(JSONB) details_schema_version: Mapped[int] = mapped_column(SmallInteger, default=1) chat_message_text: Mapped[str | None] = mapped_column(String(1024)) lifecycle_status: Mapped[str] = mapped_column(String(16), default="active") visibility: Mapped[str] = mapped_column(String(16), default="visible") is_read: Mapped[bool] = mapped_column(Boolean, default=False) close_reason: Mapped[str | None] = mapped_column(String(32)) closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class GuestNotification(Common, Base): __tablename__ = "guest_notifications" __table_args__ = ( CheckConstraint("lifecycle_status IN ('active','closed')"), CheckConstraint("old_price IS NULL OR price IS NOT NULL"), CheckConstraint("instruction_url IS NULL OR instruction_url LIKE 'https://%'"), Index("ix_guest_notifications_active", "lifecycle_status", "notification_datetime", "id"), Index("ix_guest_notifications_expire", "date_expired"), {"schema": SCHEMA}, ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid7) notification_type: Mapped[str] = mapped_column( ForeignKey(f"{SCHEMA}.notification_types.code", ondelete="RESTRICT") ) notification_datetime: Mapped[datetime] = mapped_column(DateTime(timezone=True)) header: Mapped[str] = mapped_column(String(255)) text: Mapped[str | None] = mapped_column(String(1024)) priority_override: Mapped[int | None] = mapped_column(SmallInteger) date_expired: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) old_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) instruction_url: Mapped[str | None] = mapped_column(Text) chat_message_text: Mapped[str | None] = mapped_column(String(1024)) lifecycle_status: Mapped[str] = mapped_column(String(16), default="active") closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class NotificationDocument(Common, Base): __tablename__ = "notification_documents" __table_args__ = ( UniqueConstraint("notification_id", "document_id"), {"schema": SCHEMA}, ) notification_id: Mapped[uuid.UUID] = mapped_column( ForeignKey(f"{SCHEMA}.notifications.id", ondelete="RESTRICT") ) document_id: Mapped[uuid.UUID] = mapped_column( ForeignKey(f"{SCHEMA}.documents.id", ondelete="RESTRICT") ) sort_order: Mapped[int] = mapped_column(SmallInteger, default=0) download_url_issued_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class ClientUploadDraft(Base): __tablename__ = "client_upload_drafts" __table_args__ = ( CheckConstraint("context_type IN ('notification')"), CheckConstraint("size_bytes > 0"), CheckConstraint("scan_status IN ('pending','clean','infected','failed')"), CheckConstraint("state IN ('draft','submitted','discarded')"), Index("ix_client_upload_drafts_context", "user_id", "context_type", "context_id"), Index("ix_client_upload_drafts_scan", "scan_status", "updated_at"), Index("ix_client_upload_drafts_created", "created_at"), {"schema": SCHEMA}, ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid7) user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey(f"{SCHEMA}.user_identities.id", ondelete="RESTRICT") ) context_type: Mapped[str] = mapped_column(String(32)) context_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True)) original_file_name: Mapped[str] = mapped_column(String(255)) safe_file_name: Mapped[str] = mapped_column(String(255)) mime_type: Mapped[str] = mapped_column(String(128)) size_bytes: Mapped[int] = mapped_column(BigInteger) checksum_sha256: Mapped[str | None] = mapped_column(String(64)) scan_status: Mapped[str] = mapped_column(String(16), default="pending") storage_bucket: Mapped[str] = mapped_column(String(255)) object_key: Mapped[str] = mapped_column(String(1024)) quarantine_object_key: Mapped[str | None] = mapped_column(String(1024)) upload_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) state: Mapped[str] = mapped_column(String(16), default="draft") submission_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) class ClientDocument(Common, Base): __tablename__ = "client_documents" __table_args__ = ( UniqueConstraint("storage_bucket", "object_key"), UniqueConstraint("source_draft_id"), Index("ix_client_documents_context", "context_type", "context_id"), Index("ix_client_documents_user_submitted", "user_id", "submitted_at"), {"schema": SCHEMA}, ) user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey(f"{SCHEMA}.user_identities.id", ondelete="RESTRICT") ) context_type: Mapped[str] = mapped_column(String(32)) context_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True)) submission_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True)) source_draft_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True)) original_file_name: Mapped[str] = mapped_column(String(255)) safe_file_name: Mapped[str] = mapped_column(String(255)) mime_type: Mapped[str] = mapped_column(String(128)) size_bytes: Mapped[int] = mapped_column(BigInteger) checksum_sha256: Mapped[str] = mapped_column(String(64)) storage_bucket: Mapped[str] = mapped_column(String(255)) object_key: Mapped[str] = mapped_column(String(1024)) submitted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))