"""Hugin-Anbindung (→ Konzept 7.9): Host der Plattform, Zugang je Workspace, Auswahl und festgeschriebene Pakete je Workspace. Festgeschrieben wird wie in hugin.lock: Version und Prüfsumme aus Hugin, dazu das Paket im Quellformat als ZIP und dessen SHA-256. Beim Laden für einen Run wird die Kopie geprüft. """ from datetime import datetime from typing import Any from sqlalchemy import JSON, Boolean, ForeignKey, LargeBinary, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base, now class HuginHost(Base): """Die Hugin-Instanz der Plattform (genau eine Zeile, id 1); pflegt der Plattform-Admin.""" __tablename__ = "hugin_host" id: Mapped[int] = mapped_column(primary_key=True) url: Mapped[str] = mapped_column(String(500)) # new | ok | error, zuletzt geprüft über /readyz (ohne Key) status: Mapped[str] = mapped_column(String(16), default="new") status_message: Mapped[str] = mapped_column(Text, default="") checked: Mapped[datetime | None] = mapped_column(nullable=True) updated_by: Mapped[int] = mapped_column(ForeignKey("users.id")) updated: Mapped[datetime] = mapped_column(default=now) class WorkspaceHugin(Base): """Zugang des Workspace: Integrations-Key (hi_…) seines Tenants; pflegt der Workspace-Admin. Tenant und Key-Name meldet Hugin beim Testen (/status); Pflichtpakete kommen aus diesem Tenant.""" __tablename__ = "workspace_hugin" workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), primary_key=True) secret: Mapped[str] = mapped_column(Text) hint: Mapped[str] = mapped_column(String(8)) status: Mapped[str] = mapped_column(String(16), default="new") status_message: Mapped[str] = mapped_column(Text, default="") tenant: Mapped[str] = mapped_column(String(64), default="") key_name: Mapped[str] = mapped_column(String(200), default="") checked: Mapped[datetime | None] = mapped_column(nullable=True) # Letzter Abgleich (Hintergrund oder Admin) und was er fand: Updates, veraltete Pakete, neue oder # entfallene Pflichtpakete, neue Hauptversionen. Leer = alles aktuell. synced: Mapped[datetime | None] = mapped_column(nullable=True) findings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) updated_by: Mapped[int] = mapped_column(ForeignKey("users.id")) updated: Mapped[datetime] = mapped_column(default=now) class WorkspaceHuginPackage(Base): """Vom Workspace-Admin gewähltes Paket, wie eine Zeile in .hugin.yaml (z. B. pub-x@stable).""" __tablename__ = "workspace_hugin_packages" workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), primary_key=True) package_id: Mapped[str] = mapped_column(String(64), primary_key=True) spec: Mapped[str] = mapped_column(String(32), default="stable") added_by: Mapped[int] = mapped_column(ForeignKey("users.id")) added: Mapped[datetime] = mapped_column(default=now) class WorkspaceHuginLock(Base): """Festgeschriebene Version eines Pakets (gewählt oder Pflicht).""" __tablename__ = "workspace_hugin_locks" workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), primary_key=True) package_id: Mapped[str] = mapped_column(String(64), primary_key=True) kind: Mapped[str] = mapped_column(String(16)) version: Mapped[str] = mapped_column(String(32)) checksum: Mapped[str] = mapped_column(String(80)) # Prüfsumme der Version in Hugin risk: Mapped[str] = mapped_column(String(16)) mandatory: Mapped[bool] = mapped_column(Boolean, default=False) deprecated: Mapped[bool] = mapped_column(Boolean, default=False) description: Mapped[str] = mapped_column(Text, default="") arguments: Mapped[list[str]] = mapped_column(JSON, default=list) # Paket im Quellformat (hugin.yaml und Dateien) und dessen SHA-256, beim Laden geprüft archive: Mapped[bytes] = mapped_column(LargeBinary) archive_sha256: Mapped[str] = mapped_column(String(64)) # Leer = wird angeboten; sonst der Grund, warum nicht (z. B. Skripte, Art nicht unterstützt) unsupported: Mapped[str] = mapped_column(Text, default="") locked_by: Mapped[int] = mapped_column(ForeignKey("users.id")) locked: Mapped[datetime] = mapped_column(default=now)