"""API key ORM model for tenant-scoped programmatic access.""" import uuid from datetime import datetime from typing import Optional from sqlalchemy import DateTime, ForeignKey, Text, func from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class ApiKey(Base): """Tracks API keys for programmatic access to the portal. Keys are stored as SHA-256 hashes (never plaintext). Scoped permissions limit what each key can do. Revocation is soft-delete (sets revoked_at, row preserved for audit). """ __tablename__ = "api_keys" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=func.gen_random_uuid(), ) tenant_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) name: Mapped[str] = mapped_column(Text, nullable=False) key_prefix: Mapped[str] = mapped_column(Text, nullable=False) key_hash: Mapped[str] = mapped_column(Text, nullable=False, unique=True) scopes: Mapped[list] = mapped_column(JSONB, nullable=False, server_default="'[]'::jsonb") expires_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True ) last_used_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) revoked_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True ) def __repr__(self) -> str: return f""