299 lines
13 KiB
Python
299 lines
13 KiB
Python
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))
|