Password hashing via pwdlib (argon2id), stateless JWT auth (2h expiry) delivered as an Authorization: Bearer token, with an OAuth2PasswordBearer dependency (app/dependencies.py) protecting future routes. Establishes app/routers/ as the convention for feature routes, explicitly registered in main.py (no auto-discovery, unlike app/models/). The first admin user is seeded via an Alembic data migration gated on SUPERUSER_EMAIL/SUPERUSER_PASSWORD env vars (read at migration-run time only, never persisted to .env/git) rather than a script or open endpoint, so a fresh `alembic upgrade head` still succeeds without them. The migration mirrors the users/organization tables locally instead of importing the live SQLModel classes, keeping it a stable schema snapshot; it does import app.core.security.hash_password, a pure utility with no table-shape dependency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
28 lines
723 B
Python
28 lines
723 B
Python
from typing import TYPE_CHECKING, Optional
|
|
|
|
from sqlmodel import Field, Relationship, SQLModel
|
|
|
|
if TYPE_CHECKING:
|
|
from app.models.organization import Organization
|
|
|
|
|
|
class UserBase(SQLModel):
|
|
email: str = Field(unique=True, index=True)
|
|
name: str
|
|
is_active: bool = Field(default=True)
|
|
is_superadmin: bool = Field(default=False)
|
|
|
|
|
|
class User(UserBase, table=True):
|
|
__tablename__ = "users"
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
hashed_password: str
|
|
organization_id: int = Field(foreign_key="organization.id", ondelete="RESTRICT")
|
|
|
|
organization: "Organization" = Relationship(back_populates="users")
|
|
|
|
|
|
class UserPublic(UserBase):
|
|
id: int
|
|
organization_id: int
|