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
This commit is contained in:
commit
3a5209e96c
27 changed files with 775 additions and 0 deletions
43
backend/app/models.py
Normal file
43
backend/app/models.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue