siftlode/backend/app/models.py
npeter83 3a5209e96c feat: M1 foundation — compose stack, FastAPI, Google OAuth, encrypted tokens
- docker-compose with Postgres 16 + slim Python API image
- FastAPI app with session middleware, health endpoint, static login page
- Google OAuth (Authlib) with email invite-list whitelist; admin role support
- User + OAuthToken models; refresh tokens encrypted at rest (Fernet)
- Alembic migrations, run automatically on container startup
- Postgres backup/restore scripts for portability between machines
2026-06-11 01:01:37 +02:00

43 lines
1.6 KiB
Python

from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
google_sub: Mapped[str] = mapped_column(String(64), unique=True, index=True)
email: Mapped[str] = mapped_column(String(320), unique=True, index=True)
display_name: Mapped[str | None] = mapped_column(String(255))
avatar_url: Mapped[str | None] = mapped_column(String(1024))
role: Mapped[str] = mapped_column(String(16), default="user", server_default="user")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
token: Mapped["OAuthToken | None"] = relationship(
back_populates="user", uselist=False, cascade="all, delete-orphan"
)
class OAuthToken(Base):
__tablename__ = "oauth_tokens"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), unique=True
)
# Refresh token encrypted at rest (Fernet). Access token is short-lived and low risk.
refresh_token_enc: Mapped[str | None] = mapped_column(String)
access_token: Mapped[str | None] = mapped_column(String)
expiry: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
scopes: Mapped[str | None] = mapped_column(String)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
user: Mapped["User"] = relationship(back_populates="token")