feat(phase-4a): backend skeleton, auth, health, tests

This commit is contained in:
CRM Bot
2026-06-03 20:52:01 +00:00
commit 955607f730
39 changed files with 2709 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Pydantic schemas for request/response validation."""
+61
View File
@@ -0,0 +1,61 @@
"""Pydantic schemas for authentication endpoints."""
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel, EmailStr, Field
from app.models.user import UserRole
class UserRegisterRequest(BaseModel):
"""Request body for POST /api/v1/auth/register (bootstrap)."""
email: EmailStr
password: str = Field(..., min_length=8, max_length=128)
name: str = Field(..., min_length=1, max_length=255)
role: UserRole = UserRole.sales_rep
class UserLoginRequest(BaseModel):
"""Request body for POST /api/v1/auth/login (form-data or JSON)."""
email: EmailStr
password: str = Field(..., min_length=1, max_length=128)
class TokenResponse(BaseModel):
"""Response body for successful auth (register/login/refresh)."""
access_token: str
token_type: str = "bearer"
expires_in: int # seconds
class LogoutResponse(BaseModel):
"""Response body for POST /api/v1/auth/logout.
The token is deleted client-side; this endpoint exists for consistency and
future server-side blacklisting.
"""
message: str = "logged out"
class RegisterResponse(BaseModel):
"""Response body for successful registration.
Returns the user info (without password) plus an access token.
"""
user: "UserOut"
access_token: str
token_type: str = "bearer"
expires_in: int
# Late import to avoid circular dependency
from app.schemas.user import UserOut # noqa: E402
RegisterResponse.model_rebuild()
+56
View File
@@ -0,0 +1,56 @@
"""Pydantic schemas for User endpoints."""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from app.models.user import UserRole
class UserOut(BaseModel):
"""Response schema for a user (never includes password_hash)."""
model_config = ConfigDict(from_attributes=True)
id: int
email: EmailStr
name: str
role: UserRole
org_id: int
avatar_url: Optional[str] = None
email_notifications: bool = True
created_at: datetime
class UserUpdate(BaseModel):
"""Request body for PATCH /api/v1/users/{id} (admin or self)."""
name: Optional[str] = Field(None, min_length=1, max_length=255)
avatar_url: Optional[str] = Field(None, max_length=1024)
email_notifications: Optional[bool] = None
role: Optional[UserRole] = None # admin-only
class UserCreateRequest(BaseModel):
"""Request body for POST /api/v1/users (admin-only)."""
email: EmailStr
password: str = Field(..., min_length=8, max_length=128)
name: str = Field(..., min_length=1, max_length=255)
role: UserRole = UserRole.sales_rep
class UserListResponse(BaseModel):
"""Response schema for paginated user list."""
items: list[UserOut]
total: int
page: int
page_size: int
# Re-export for late import in auth.py
__all__ = ["UserCreateRequest", "UserListResponse", "UserOut", "UserUpdate"]