from datetime import datetime from typing import Optional from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column, relationship from .base import Base class SSHKey(Base): """Registry of SSH private key files stored on the server filesystem.""" __tablename__ = "ssh_keys" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) path: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False ) class ObjectSSHCredential(Base): """Ordered list of SSH credentials (password or key) for an object. Tried in ascending priority order when connecting to hosts of this object. type='password': username + password_enc required. type='key': username + ssh_key_id required. """ __tablename__ = "object_ssh_credentials" __table_args__ = ( CheckConstraint("type IN ('password', 'key')", name="ck_obj_ssh_cred_type"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) object_id: Mapped[int] = mapped_column( Integer, ForeignKey("objects.id", ondelete="CASCADE"), nullable=False, index=True ) type: Mapped[str] = mapped_column(String(10), nullable=False) username: Mapped[str] = mapped_column(String(100), nullable=False) password_enc: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) ssh_key_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("ssh_keys.id", ondelete="SET NULL"), nullable=True ) priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0) object: Mapped["Object"] = relationship("Object", back_populates="ssh_credentials") ssh_key: Mapped[Optional["SSHKey"]] = relationship("SSHKey") class ObjectCategoryCredential(Base): """Per-category credentials on an object. protocol='ssh': SSH access to devices of this category. protocol='http': HTTP/API access (cameras, MikroTik, Proxmox, etc.). type='password': username + password_enc required. type='key': username + ssh_key_id required (ssh only). """ __tablename__ = "object_category_credentials" __table_args__ = ( CheckConstraint("protocol IN ('ssh', 'http')", name="ck_obj_cat_cred_protocol"), CheckConstraint("type IN ('password', 'key')", name="ck_obj_cat_cred_type"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) object_id: Mapped[int] = mapped_column( Integer, ForeignKey("objects.id", ondelete="CASCADE"), nullable=False, index=True ) category: Mapped[str] = mapped_column(String(20), nullable=False) protocol: Mapped[str] = mapped_column(String(10), nullable=False) type: Mapped[str] = mapped_column(String(10), nullable=False, default="password") username: Mapped[str] = mapped_column(String(100), nullable=False) password_enc: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) ssh_key_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("ssh_keys.id", ondelete="SET NULL"), nullable=True ) priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0) object: Mapped["Object"] = relationship("Object", back_populates="category_credentials") ssh_key: Mapped[Optional["SSHKey"]] = relationship("SSHKey") class GlobalSSHCredential(Base): """App-wide SSH credential used as last-resort fallback for all objects/devices. Tried after all object-level credentials are exhausted. type='password': username + password_enc required. type='key': username + ssh_key_id required. """ __tablename__ = "global_ssh_credentials" __table_args__ = ( CheckConstraint("type IN ('password', 'key')", name="ck_global_ssh_cred_type"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) type: Mapped[str] = mapped_column(String(10), nullable=False) username: Mapped[str] = mapped_column(String(100), nullable=False) password_enc: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) ssh_key_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("ssh_keys.id", ondelete="SET NULL"), nullable=True ) priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0) description: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) ssh_key: Mapped[Optional["SSHKey"]] = relationship("SSHKey")