85 lines
2.2 KiB
Python
85 lines
2.2 KiB
Python
"""Request and response bodies.
|
|
|
|
Declaring these as pydantic models is also a CSRF control: FastAPI then rejects
|
|
form-encoded content types with a 422, and an HTML form can only produce those.
|
|
A cross-site form therefore cannot reach any of these endpoints even before the
|
|
SameSite cookie attribute is considered.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, EmailStr, Field, field_validator
|
|
|
|
from app.config import get_settings
|
|
|
|
|
|
def _password_field() -> Field:
|
|
s = get_settings()
|
|
return Field(min_length=s.MIN_PASSWORD_LENGTH, max_length=s.MAX_PASSWORD_LENGTH)
|
|
|
|
|
|
class SignupIn(BaseModel):
|
|
email: EmailStr
|
|
password: str = _password_field()
|
|
name: str = Field(default="", max_length=120)
|
|
|
|
@field_validator("name")
|
|
@classmethod
|
|
def _tidy(cls, v: str) -> str:
|
|
return v.strip()
|
|
|
|
|
|
class LoginIn(BaseModel):
|
|
email: EmailStr
|
|
password: str = Field(min_length=1, max_length=256)
|
|
|
|
|
|
class EmailIn(BaseModel):
|
|
email: EmailStr
|
|
|
|
|
|
class TokenIn(BaseModel):
|
|
token: str = Field(min_length=8, max_length=256)
|
|
|
|
|
|
class ResetIn(TokenIn):
|
|
password: str = _password_field()
|
|
|
|
|
|
class SettingsIn(BaseModel):
|
|
"""The modelling assumptions the dashboard sends with /api/analyze.
|
|
|
|
`roas` is a string because the settings dialog sends "" for "use the
|
|
account average", and the pipeline already treats falsy as absent.
|
|
"""
|
|
|
|
roas: str | float | None = None
|
|
haircut: float = Field(default=0.7, ge=0, le=1)
|
|
cap: float = Field(default=3, gt=0, le=100)
|
|
merge_gap: int = Field(default=5, ge=0, le=720)
|
|
|
|
|
|
class UserOut(BaseModel):
|
|
id: int
|
|
email: str
|
|
name: str
|
|
is_admin: bool
|
|
is_active: bool
|
|
verified: bool
|
|
created_at: datetime | None = None
|
|
last_login_at: datetime | None = None
|
|
|
|
@classmethod
|
|
def of(cls, user) -> "UserOut":
|
|
return cls(id=user.id, email=user.email, name=user.name,
|
|
is_admin=user.is_admin, is_active=user.is_active,
|
|
verified=user.is_verified, created_at=user.created_at,
|
|
last_login_at=user.last_login_at)
|
|
|
|
|
|
class Ok(BaseModel):
|
|
ok: bool = True
|
|
message: str | None = None
|