"""ORM model for ``auth_personal_access_tokens`` (migration 069).""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.sql import func from app.core.database import Base from app.core.sql_types import GUID as Uuid class PersonalAccessToken(Base): __tablename__ = "auth_personal_access_tokens" # Without eager_defaults, accessing the server-default ``created_at`` # immediately after a flush triggers a lazy refresh that, in an async # session, raises ``MissingGreenlet`` if the access happens outside # the original await chain. Same convention as asset_items, etc. __mapper_args__ = {"eager_defaults": True} id: Mapped[uuid.UUID] = mapped_column( Uuid, primary_key=True, default=uuid.uuid4, ) user_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("identity_persons.id"), nullable=False, ) name: Mapped[str] = mapped_column(String(120), nullable=False) # SHA-256(plaintext) — never reversible, never logged. token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) # First 12 chars of the plaintext (``helix_pat_`` + 2 random) — safe # to display so the user can identify their tokens at a glance. token_prefix: Mapped[str] = mapped_column(String(16), nullable=False) # ``None`` = never expires (user-selectable on creation). expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) revoked_by_user_id: Mapped[uuid.UUID | None] = mapped_column( Uuid, ForeignKey("identity_persons.id"), ) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_used_ip: Mapped[str | None] = mapped_column(String(64)) last_used_user_agent: Mapped[str | None] = mapped_column(String(512)) expiry_notified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), ) created_ip: Mapped[str | None] = mapped_column(String(64)) created_user_agent: Mapped[str | None] = mapped_column(String(512)) __all__ = ["PersonalAccessToken"]