"""Signal bot device, role, and dead letter ORM models.""" from __future__ import annotations from datetime import datetime from sqlalchemy import DateTime, Enum, ForeignKey, SmallInteger, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from python.orm.signal_bot.base import SignalBotTableBase, SignalBotTableBaseSmall from python.signal_bot.models import MessageStatus, TrustLevel class RoleRecord(SignalBotTableBaseSmall): """Lookup table for RBAC roles, keyed by smallint.""" __tablename__ = "role" name: Mapped[str] = mapped_column(String(50), unique=True) class DeviceRole(SignalBotTableBase): """Association between a device and a role.""" __tablename__ = "device_role" __table_args__ = ( UniqueConstraint("device_id", "role_id", name="uq_device_role_device_role"), {"schema": "main"}, ) device_id: Mapped[int] = mapped_column(ForeignKey("main.signal_device.id")) role_id: Mapped[int] = mapped_column(SmallInteger, ForeignKey("main.role.id")) class SignalDevice(SignalBotTableBase): """A Signal device tracked by phone number and safety number.""" __tablename__ = "signal_device" phone_number: Mapped[str] = mapped_column(String(50), unique=True) safety_number: Mapped[str | None] trust_level: Mapped[TrustLevel] = mapped_column( Enum(TrustLevel, name="trust_level", create_constraint=False, native_enum=False), default=TrustLevel.UNVERIFIED, ) last_seen: Mapped[datetime] = mapped_column(DateTime(timezone=True)) roles: Mapped[list[RoleRecord]] = relationship(secondary=DeviceRole.__table__) class DeadLetterMessage(SignalBotTableBase): """A Signal message that failed processing and was sent to the dead letter queue.""" __tablename__ = "dead_letter_message" source: Mapped[str] message: Mapped[str] = mapped_column(Text) received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) status: Mapped[MessageStatus] = mapped_column( Enum(MessageStatus, name="message_status", create_constraint=False, native_enum=False), default=MessageStatus.UNPROCESSED, )