751 lines
39 KiB
Python
751 lines
39 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Numeric, String, Text, UniqueConstraint, func
|
|
from sqlalchemy.dialects.postgresql import JSON, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .database import Base
|
|
|
|
|
|
class Organization(Base):
|
|
__tablename__ = "organizations"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
name: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
slug: Mapped[str] = mapped_column(String(100), unique=True, index=True)
|
|
home_region: Mapped[str] = mapped_column(String(32), default="ap-south-1")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
projects: Mapped[list["Project"]] = relationship(back_populates="organization")
|
|
|
|
|
|
class Project(Base):
|
|
__tablename__ = "projects"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
organization_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("organizations.id"))
|
|
name: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
environment: Mapped[str] = mapped_column(String(24), default="test")
|
|
status: Mapped[str] = mapped_column(String(24), default="active")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
organization: Mapped[Organization] = relationship(back_populates="projects")
|
|
|
|
|
|
class ActivityEvent(Base):
|
|
__tablename__ = "activity_events"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
project_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("projects.id"), index=True)
|
|
event_type: Mapped[str] = mapped_column(String(80), index=True)
|
|
title: Mapped[str] = mapped_column(String(200))
|
|
detail: Mapped[str] = mapped_column(String(500), default="")
|
|
status: Mapped[str] = mapped_column(String(24), default="success")
|
|
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class UserAccount(Base):
|
|
"""Canonical person record, independent of any login provider."""
|
|
|
|
__tablename__ = "user_accounts"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
email: Mapped[str] = mapped_column(String(320), unique=True, index=True)
|
|
display_name: Mapped[str] = mapped_column(String(160))
|
|
avatar_url: Mapped[str | None] = mapped_column(String(1000), nullable=True)
|
|
email_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
status: Mapped[str] = mapped_column(String(24), default="active")
|
|
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()
|
|
)
|
|
password_credential: Mapped["PasswordCredential | None"] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
oidc_identities: Mapped[list["OidcIdentity"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
kyc_application: Mapped["KycApplication | None"] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
dlt_application: Mapped["DltApplication | None"] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
sender_configs: Mapped[list["SenderConfig"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
template_configs: Mapped[list["TemplateConfig"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
api_keys: Mapped[list["ApiKey"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
urmiotp_projects: Mapped[list["UrmiOtpProject"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
campaigns: Mapped[list["Campaign"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
phone_numbers: Mapped[list["PhoneNumber"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
voice_calls: Mapped[list["VoiceCall"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
sms_message_logs: Mapped[list["SmsMessageLog"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
otp_message_logs: Mapped[list["OtpMessageLog"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
voice_socket_endpoints: Mapped[list["VoiceSocketEndpoint"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
sms_delivery_webhooks: Mapped[list["SmsDeliveryWebhook"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
agent_template_subscriptions: Mapped[list["AgentTemplateSubscription"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
agent_message_dispatches: Mapped[list["AgentMessageDispatchLog"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
urmiport_report_groups: Mapped[list["UrmiportReportGroup"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
urmiport_reports: Mapped[list["UrmiportReport"]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class PasswordCredential(Base):
|
|
"""First-party credential kept separate from the canonical user."""
|
|
|
|
__tablename__ = "password_credentials"
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), primary_key=True
|
|
)
|
|
password_hash: Mapped[str] = mapped_column(String(500))
|
|
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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="password_credential")
|
|
|
|
|
|
class OidcIdentity(Base):
|
|
"""Provider identity; no provider claims are stored on the user table."""
|
|
|
|
__tablename__ = "oidc_identities"
|
|
__table_args__ = (
|
|
UniqueConstraint("issuer", "subject", name="uq_oidc_issuer_subject"),
|
|
UniqueConstraint("user_id", "issuer", name="uq_oidc_user_issuer"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
issuer: Mapped[str] = mapped_column(String(500))
|
|
subject: Mapped[str] = mapped_column(String(500))
|
|
provider_email: Mapped[str] = mapped_column(String(320))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
last_login_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user: Mapped[UserAccount] = relationship(back_populates="oidc_identities")
|
|
|
|
|
|
class KycApplication(Base):
|
|
__tablename__ = "kyc_applications"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), unique=True, index=True
|
|
)
|
|
pan_number: Mapped[str | None] = mapped_column(String(10), nullable=True)
|
|
gst_number: Mapped[str | None] = mapped_column(String(15), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="not_started", index=True)
|
|
submitted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="kyc_application")
|
|
documents: Mapped[list["KycDocument"]] = relationship(
|
|
back_populates="application", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class KycDocument(Base):
|
|
__tablename__ = "kyc_documents"
|
|
__table_args__ = (UniqueConstraint("application_id", "document_type", name="uq_kyc_app_doc_type"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
application_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("kyc_applications.id", ondelete="CASCADE"), index=True
|
|
)
|
|
document_type: Mapped[str] = mapped_column(String(40))
|
|
original_filename: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
stored_path: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="missing")
|
|
review_note: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
uploaded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
application: Mapped[KycApplication] = relationship(back_populates="documents")
|
|
|
|
|
|
class DltApplication(Base):
|
|
__tablename__ = "dlt_applications"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), unique=True, index=True
|
|
)
|
|
status: Mapped[str] = mapped_column(String(24), default="not_started", index=True)
|
|
submitted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="dlt_application")
|
|
documents: Mapped[list["DltDocument"]] = relationship(
|
|
back_populates="application", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class DltDocument(Base):
|
|
__tablename__ = "dlt_documents"
|
|
__table_args__ = (UniqueConstraint("application_id", "document_type", name="uq_dlt_app_doc_type"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
application_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("dlt_applications.id", ondelete="CASCADE"), index=True
|
|
)
|
|
document_type: Mapped[str] = mapped_column(String(40))
|
|
original_filename: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
stored_path: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="missing")
|
|
review_note: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
uploaded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
application: Mapped[DltApplication] = relationship(back_populates="documents")
|
|
|
|
|
|
class SenderConfig(Base):
|
|
__tablename__ = "sender_configs"
|
|
__table_args__ = (UniqueConstraint("user_id", "sender_id", name="uq_sender_user_sender_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
sender_id: Mapped[str] = mapped_column(String(11), index=True)
|
|
sender_type: Mapped[str] = mapped_column(String(20))
|
|
purpose: Mapped[str] = mapped_column(String(500))
|
|
dlt_entity_id: Mapped[str] = mapped_column(String(50))
|
|
status: Mapped[str] = mapped_column(String(24), default="pending", index=True)
|
|
review_note: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="sender_configs")
|
|
templates: Mapped[list["TemplateConfig"]] = relationship(
|
|
back_populates="sender_config", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class TemplateConfig(Base):
|
|
__tablename__ = "template_configs"
|
|
__table_args__ = (UniqueConstraint("user_id", "template_name", name="uq_template_user_name"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
sender_config_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("sender_configs.id", ondelete="CASCADE"), index=True
|
|
)
|
|
template_name: Mapped[str] = mapped_column(String(40), index=True)
|
|
template_text: Mapped[str] = mapped_column(String(1000))
|
|
dlt_template_id: Mapped[str] = mapped_column(String(50))
|
|
status: Mapped[str] = mapped_column(String(24), default="pending", index=True)
|
|
review_note: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="template_configs")
|
|
sender_config: Mapped[SenderConfig] = relationship(back_populates="templates")
|
|
|
|
|
|
class ApiKey(Base):
|
|
__tablename__ = "api_keys"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(60))
|
|
key_prefix: Mapped[str] = mapped_column(String(24))
|
|
key_hash: Mapped[str] = mapped_column(String(500))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="api_keys")
|
|
|
|
|
|
class UrmiOtpProject(Base):
|
|
__tablename__ = "urmiotp_projects"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
project_name: Mapped[str] = mapped_column(String(30), unique=True, index=True)
|
|
message_label: Mapped[str] = mapped_column(String(20), server_default="URMI")
|
|
app_type: Mapped[str] = mapped_column(String(16))
|
|
app_url: Mapped[str] = mapped_column(String(1000))
|
|
purpose: Mapped[str] = mapped_column(String(1000))
|
|
terms_accepted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
|
status: Mapped[str] = mapped_column(String(24), default="pending", index=True)
|
|
review_note: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="urmiotp_projects")
|
|
|
|
|
|
class Campaign(Base):
|
|
__tablename__ = "campaigns"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(80))
|
|
sender_config_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("sender_configs.id", ondelete="CASCADE"), index=True
|
|
)
|
|
template_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("template_configs.id", ondelete="CASCADE"), index=True
|
|
)
|
|
number_column: Mapped[str] = mapped_column(String(80))
|
|
variable_columns: Mapped[list] = mapped_column(JSON)
|
|
run_mode: Mapped[str] = mapped_column(String(20), index=True)
|
|
scheduled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="active", index=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="campaigns")
|
|
sender_config: Mapped["SenderConfig"] = relationship()
|
|
template: Mapped["TemplateConfig"] = relationship()
|
|
csv_uploads: Mapped[list["CampaignCsvUpload"]] = relationship(
|
|
back_populates="campaign", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class CampaignCsvUpload(Base):
|
|
__tablename__ = "campaign_csv_uploads"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
campaign_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("campaigns.id", ondelete="CASCADE"), index=True
|
|
)
|
|
original_filename: Mapped[str] = mapped_column(String(255))
|
|
stored_path: Mapped[str] = mapped_column(String(500))
|
|
total_rows: Mapped[int] = mapped_column(default=0)
|
|
status: Mapped[str] = mapped_column(String(24), default="validated", index=True)
|
|
run_started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
run_completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
campaign: Mapped[Campaign] = relationship(back_populates="csv_uploads")
|
|
|
|
|
|
class PhoneNumber(Base):
|
|
__tablename__ = "phone_numbers"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
phone_number: Mapped[str] = mapped_column(String(20), unique=True, index=True)
|
|
user_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="SET NULL"), index=True, nullable=True
|
|
)
|
|
inbound_price: Mapped[float] = mapped_column(Numeric(10, 4))
|
|
outbound_price: Mapped[float] = mapped_column(Numeric(10, 4))
|
|
monthly_number_price: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0")
|
|
currency: Mapped[str] = mapped_column(String(8), default="INR")
|
|
status: Mapped[str] = mapped_column(String(20), default="available", index=True)
|
|
acquisition_type: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
|
assigned_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user: Mapped[UserAccount | None] = relationship(back_populates="phone_numbers")
|
|
voice_calls: Mapped[list["VoiceCall"]] = relationship(back_populates="phone_number")
|
|
voice_socket_endpoint: Mapped["VoiceSocketEndpoint | None"] = relationship(
|
|
back_populates="phone_number", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
sms_delivery_webhooks: Mapped[list["SmsDeliveryWebhook"]] = relationship(
|
|
back_populates="phone_number", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class VoiceSocketEndpoint(Base):
|
|
"""Customer WebSocket destination associated with one owned phone number."""
|
|
|
|
__tablename__ = "voice_socket_endpoints"
|
|
__table_args__ = (
|
|
UniqueConstraint("phone_number_id", name="uq_voice_socket_phone_number"),
|
|
UniqueConstraint("user_id", "name", name="uq_voice_socket_user_name"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
phone_number_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("phone_numbers.id", ondelete="CASCADE"), index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(120))
|
|
url: Mapped[str] = mapped_column(String(500))
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="voice_socket_endpoints")
|
|
phone_number: Mapped[PhoneNumber] = relationship(back_populates="voice_socket_endpoint")
|
|
|
|
|
|
class SmsDeliveryWebhook(Base):
|
|
"""Named SMS delivery-report destination associated with an owned number."""
|
|
|
|
__tablename__ = "sms_delivery_webhooks"
|
|
__table_args__ = (
|
|
UniqueConstraint("user_id", "name", name="uq_sms_webhook_user_name"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
phone_number_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("phone_numbers.id", ondelete="CASCADE"), index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(120))
|
|
url: Mapped[str] = mapped_column(String(500))
|
|
description: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="sms_delivery_webhooks")
|
|
phone_number: Mapped[PhoneNumber] = relationship(back_populates="sms_delivery_webhooks")
|
|
|
|
|
|
class VoiceCall(Base):
|
|
__tablename__ = "voice_calls"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
phone_number_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("phone_numbers.id", ondelete="CASCADE"), index=True
|
|
)
|
|
direction: Mapped[str] = mapped_column(String(16), index=True)
|
|
from_did: Mapped[str] = mapped_column(String(20))
|
|
to_number: Mapped[str] = mapped_column(String(20))
|
|
status: Mapped[str] = mapped_column(String(24), default="initiated", index=True)
|
|
provider_event_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
session_token: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
stream_id: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
call_id: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
bridge_mode: Mapped[str] = mapped_column(String(16), default="console")
|
|
upstream_ws: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
error_message: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
answered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user: Mapped[UserAccount] = relationship(back_populates="voice_calls")
|
|
phone_number: Mapped[PhoneNumber] = relationship(back_populates="voice_calls")
|
|
|
|
|
|
class SmsMessageLog(Base):
|
|
__tablename__ = "sms_message_logs"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
sender_id: Mapped[str] = mapped_column(String(11), index=True)
|
|
to_number: Mapped[str] = mapped_column(String(20), index=True)
|
|
template_name: Mapped[str] = mapped_column(String(40))
|
|
template_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="sent", index=True)
|
|
job_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
message_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
error_message: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="sms_message_logs")
|
|
|
|
|
|
class OtpMessageLog(Base):
|
|
__tablename__ = "otp_message_logs"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
project_name: Mapped[str] = mapped_column(String(30), index=True)
|
|
template_type: Mapped[str] = mapped_column(String(40), index=True)
|
|
to_number: Mapped[str] = mapped_column(String(20), index=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="sent", index=True)
|
|
job_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
message_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
error_message: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="otp_message_logs")
|
|
|
|
|
|
class WhatsAppWebhookEvent(Base):
|
|
"""Inbound webhook payloads from Meta WhatsApp Business Platform."""
|
|
|
|
__tablename__ = "whatsapp_webhook_events"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
waba_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
phone_number_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
field: Mapped[str] = mapped_column(String(80), index=True)
|
|
event_type: Mapped[str] = mapped_column(String(80), index=True)
|
|
from_wa_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
|
|
message_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
|
status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
payload: Mapped[dict] = mapped_column(JSON)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class WhatsAppMessage(Base):
|
|
"""Inbound and outbound WhatsApp messages for Urmi's business number."""
|
|
|
|
__tablename__ = "whatsapp_messages"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
direction: Mapped[str] = mapped_column(String(16), index=True)
|
|
wa_message_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
|
from_number: Mapped[str] = mapped_column(String(32), index=True)
|
|
to_number: Mapped[str] = mapped_column(String(32), index=True)
|
|
body: Mapped[str | None] = mapped_column(String(4096), nullable=True)
|
|
message_type: Mapped[str] = mapped_column(String(24), default="text")
|
|
status: Mapped[str] = mapped_column(String(24), default="received", index=True)
|
|
payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class WhatsAppMetaConnection(Base):
|
|
"""A customer's Meta authorization. Tokens stay server-side."""
|
|
|
|
__tablename__ = "whatsapp_meta_connections"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="pending", index=True)
|
|
meta_business_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
business_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
granted_scopes: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
access_token: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(String(500), nullable=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()
|
|
)
|
|
disconnected_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
accounts: Mapped[list["WhatsAppBusinessAccount"]] = relationship(back_populates="connection")
|
|
|
|
|
|
class WhatsAppBusinessAccount(Base):
|
|
"""One WhatsApp Business Account (WABA) owned by a single Urmi user."""
|
|
|
|
__tablename__ = "whatsapp_business_accounts"
|
|
__table_args__ = (UniqueConstraint("waba_id", name="uq_whatsapp_waba_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True)
|
|
connection_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("whatsapp_meta_connections.id", ondelete="CASCADE"), index=True
|
|
)
|
|
waba_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
name: Mapped[str] = mapped_column(String(200), default="WhatsApp Business")
|
|
status: Mapped[str] = mapped_column(String(24), default="provisioning", index=True)
|
|
messaging_status: Mapped[str] = mapped_column(String(24), default="unavailable")
|
|
management_status: Mapped[str] = mapped_column(String(24), default="unavailable")
|
|
templates_status: Mapped[str] = mapped_column(String(24), default="unavailable")
|
|
webhooks_status: Mapped[str] = mapped_column(String(24), default="unavailable")
|
|
template_count: Mapped[int] = mapped_column(default=0)
|
|
last_error: Mapped[str | None] = mapped_column(String(500), nullable=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()
|
|
)
|
|
connection: Mapped[WhatsAppMetaConnection] = relationship(back_populates="accounts")
|
|
phones: Mapped[list["WhatsAppPhoneAsset"]] = relationship(
|
|
back_populates="account", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class WhatsAppPhoneAsset(Base):
|
|
__tablename__ = "whatsapp_phone_assets"
|
|
__table_args__ = (UniqueConstraint("phone_number_id", name="uq_whatsapp_phone_number_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True)
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("whatsapp_business_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
phone_number_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
display_phone: Mapped[str] = mapped_column(String(32), default="")
|
|
verified_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
quality: Mapped[str | None] = mapped_column(String(24), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(32), default="pending")
|
|
code_verification_status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
account: Mapped[WhatsAppBusinessAccount] = relationship(back_populates="phones")
|
|
|
|
|
|
class WhatsAppConnectionAudit(Base):
|
|
__tablename__ = "whatsapp_connection_audits"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True)
|
|
connection_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True, index=True)
|
|
waba_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
event: Mapped[str] = mapped_column(String(40), index=True)
|
|
detail: Mapped[str] = mapped_column(String(500), default="")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class AgentMessageTemplate(Base):
|
|
"""Platform WhatsApp agent message templates available for user subscription."""
|
|
|
|
__tablename__ = "agent_message_templates"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
slug: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
name: Mapped[str] = mapped_column(String(120))
|
|
description: Mapped[str] = mapped_column(String(1000))
|
|
header_text: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
body_preview: Mapped[str] = mapped_column(String(2000))
|
|
footer_text: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
meta_template_name: Mapped[str] = mapped_column(String(80), index=True)
|
|
language: Mapped[str] = mapped_column(String(12), default="en")
|
|
category: Mapped[str] = mapped_column(String(32), default="utility")
|
|
parameters: Mapped[list] = mapped_column(JSON, default=list)
|
|
button_label: Mapped[str | None] = mapped_column(String(40), nullable=True)
|
|
button_url_base: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
subscriptions: Mapped[list["AgentTemplateSubscription"]] = relationship(
|
|
back_populates="template", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class AgentTemplateSubscription(Base):
|
|
__tablename__ = "agent_template_subscriptions"
|
|
__table_args__ = (UniqueConstraint("user_id", "template_id", name="uq_agent_template_subscription"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
template_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("agent_message_templates.id", ondelete="CASCADE"), index=True
|
|
)
|
|
subscribed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user: Mapped[UserAccount] = relationship(back_populates="agent_template_subscriptions")
|
|
template: Mapped[AgentMessageTemplate] = relationship(back_populates="subscriptions")
|
|
|
|
|
|
class AgentTemplateTestLog(Base):
|
|
__tablename__ = "agent_template_test_logs"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
template_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("agent_message_templates.id", ondelete="CASCADE"), index=True
|
|
)
|
|
recipient: Mapped[str] = mapped_column(String(20))
|
|
success: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
tested_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class AgentMessageDispatchLog(Base):
|
|
__tablename__ = "agent_message_dispatch_logs"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
template_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("agent_message_templates.id", ondelete="CASCADE"), index=True
|
|
)
|
|
recipient: Mapped[str] = mapped_column(String(20), index=True)
|
|
parameters: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
wa_message_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(24), default="sent", index=True)
|
|
source: Mapped[str] = mapped_column(String(16), default="api", index=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
user: Mapped[UserAccount] = relationship(back_populates="agent_message_dispatches")
|
|
template: Mapped[AgentMessageTemplate] = relationship()
|
|
|
|
|
|
class UrmiportReportGroup(Base):
|
|
__tablename__ = "urmiport_report_groups"
|
|
__table_args__ = (UniqueConstraint("user_id", "slug", name="uq_urmiport_group_user_slug"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
slug: Mapped[str] = mapped_column(String(50), index=True)
|
|
display_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
default_access: Mapped[str] = mapped_column(String(16), default="public", index=True)
|
|
password_hash: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
id_mode: Mapped[str] = mapped_column(String(16), default="auto")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user: Mapped[UserAccount] = relationship(back_populates="urmiport_report_groups")
|
|
reports: Mapped[list["UrmiportReport"]] = relationship(
|
|
back_populates="group", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class UrmiportReport(Base):
|
|
__tablename__ = "urmiport_reports"
|
|
__table_args__ = (UniqueConstraint("group_id", "report_id", name="uq_urmiport_group_report_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("user_accounts.id", ondelete="CASCADE"), index=True
|
|
)
|
|
group_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("urmiport_report_groups.id", ondelete="CASCADE"), index=True
|
|
)
|
|
report_id: Mapped[str] = mapped_column(String(120), index=True)
|
|
html_body: Mapped[str] = mapped_column(Text)
|
|
access_mode: Mapped[str] = mapped_column(String(16), default="public", index=True)
|
|
password_hash: Mapped[str | None] = mapped_column(String(500), nullable=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()
|
|
)
|
|
user: Mapped[UserAccount] = relationship(back_populates="urmiport_reports")
|
|
group: Mapped[UrmiportReportGroup] = relationship(back_populates="reports")
|
|
|
|
|
|
class PlatformSetting(Base):
|
|
"""Runtime overrides for platform credentials (admin-editable)."""
|
|
|
|
__tablename__ = "platform_settings"
|
|
|
|
key: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
value: Mapped[str] = mapped_column(Text, default="")
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
)
|