from datetime import datetime from typing import Optional from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import ARRAY, JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from .base import Base, TimestampMixin DEVICE_CATEGORIES = ("raspberry", "mikrotik", "camera", "embedded", "server", "other") DEVICE_ROLES = ("entry", "exit", "server", "nvr", "switch", "other") DEVICE_STATUSES = ("online", "offline", "unknown") DEVICE_SOURCES = ("auto_sync", "manual", "csv_import", "sheets_sync") class Device(Base, TimestampMixin): __tablename__ = "devices" id: Mapped[int] = mapped_column(Integer, primary_key=True) object_id: Mapped[int] = mapped_column( Integer, ForeignKey("objects.id", ondelete="RESTRICT"), nullable=False, index=True, ) ip: Mapped[str] = mapped_column(String(45), nullable=False) hostname: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) mac: Mapped[Optional[str]] = mapped_column(String(17), nullable=True) # Classification category: Mapped[str] = mapped_column(String(20), nullable=False, default="other") role: Mapped[str] = mapped_column(String(20), nullable=False, default="other") model: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) firmware_version: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # Source tracking source: Mapped[str] = mapped_column(String(20), nullable=False, default="manual") external_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # SSH overrides (NULL = use object defaults) ssh_user_override: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) ssh_pass_override_enc: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) ssh_port_override: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # Device-specific metadata (Mikrotik API port, RTSP URL, etc.) device_meta: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) # Capabilities populated by drivers capabilities: Mapped[list[str]] = mapped_column( ARRAY(String), nullable=False, default=list ) # Monitoring monitor_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) last_seen: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) last_ping_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) status: Mapped[str] = mapped_column(String(20), nullable=False, default="unknown") notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) object: Mapped["Object"] = relationship("Object", back_populates="devices")