from datetime import datetime from sqlalchemy import DateTime, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.core.mixins import JSONB_V, TimestampMixin class User(TimestampMixin, Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True, index=True) password_hash: Mapped[str] = mapped_column(String(255)) display_name: Mapped[str] = mapped_column(String(100)) is_active: Mapped[bool] = mapped_column(default=True) class DeviceApiKey(TimestampMixin, Base): """Long-lived scoped token for device bridges (Android Health Connect…).""" __tablename__ = "device_api_keys" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) name: Mapped[str] = mapped_column( String(100) ) # ex: "Pixel 8 – pont Health Connect" key_prefix: Mapped[str] = mapped_column(String(12), unique=True, index=True) key_hash: Mapped[str] = mapped_column(String(64)) # sha256 hex of the full key scopes: Mapped[list[str]] = mapped_column( JSONB_V, default=list ) # ["ingest:health"] last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))