import uuid import enum from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base if TYPE_CHECKING: from app.models.company import Company from app.models.user import User class KioskAuthMethod(str, enum.Enum): PIN = "pin" NFC = "nfc" QR = "qr" LIST = "list" # Mitarbeiter-Liste class KioskDeviceStatus(str, enum.Enum): PENDING = "pending" # Wartet auf Admin-Freigabe APPROVED = "approved" # Aktiv, darf stempeln REVOKED = "revoked" # Gesperrt / neu enrollen erforderlich class KioskDevice(Base): __tablename__ = "kiosk_devices" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) company_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("companies.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(255), nullable=False) location: Mapped[str | None] = mapped_column(String(255)) # ── Ed25519-Auth (löst token_hash + is_active ab) ───────────────────────── status: Mapped[KioskDeviceStatus] = mapped_column( Enum(KioskDeviceStatus, name="kioskdevicestatus", values_callable=lambda x: [e.value for e in x]), nullable=False, default=KioskDeviceStatus.REVOKED, ) public_key: Mapped[str | None] = mapped_column(Text) # Ed25519 PEM/OpenSSH key_algorithm: Mapped[str] = mapped_column(String(20), nullable=False, default="ed25519") # ── Enrollment (einmaliger Setup-Flow) ──────────────────────────────────── enrollment_token_hash: Mapped[str | None] = mapped_column(String(64)) enrollment_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) # ── Heartbeat & Liveness ────────────────────────────────────────────────── last_heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) client_version: Mapped[str | None] = mapped_column(String(50)) offline_queue_size: Mapped[int] = mapped_column(Integer, nullable=False, default=0) # ── DSGVO: aktuell eingestempelter User (per Firma deaktivierbar) ───────── current_user_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) # ── Sicherheit ──────────────────────────────────────────────────────────── ip_whitelist: Mapped[str | None] = mapped_column(Text) # CIDR-Liste, z.B. "10.0.0.0/24,192.168.1.0/24" created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) # ── Relationships ───────────────────────────────────────────────────────── company: Mapped["Company"] = relationship("Company", lazy="noload") current_user: Mapped["User | None"] = relationship( "User", foreign_keys=[current_user_id], lazy="noload" ) def __repr__(self) -> str: return f""