Tracker de vie auto-hébergé : suivi poids/calories/sport avec planning de pesées, sevrage tabac (vape) avec modèle de coût DIY et économies, et finances personnelles avec import de relevés bancaires. Architecture : FastAPI + SQLAlchemy 2.0 + PostgreSQL 16, React 18 + TS + Vite + Tailwind + ECharts, déploiement Docker Compose. Modules auto-découverts des deux côtés (pkgutil / import.meta.glob) et framework de connecteurs à deux voies (importeurs de fichiers + ingestion JSON) pour brancher de nouvelles sources sans toucher au noyau. Validé : 292 tests pytest, tsc + vite build, contrat API/web vérifié contre le schéma OpenAPI, et déploiement Docker réel sur PostgreSQL 16 (28 tables, SPA servie par nginx, wizard de premier démarrage). Documentation : README.md, docs/GUIDE.md, CONVENTIONS.md, et les documents de conception et de recherche dans docs/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import get_current_user
|
|
from app.core.security import create_access_token
|
|
from app.modules.auth import service
|
|
from app.modules.auth.models import User
|
|
from app.modules.auth.schemas import (
|
|
AuthStatus,
|
|
DeviceKeyCreate,
|
|
DeviceKeyCreated,
|
|
DeviceKeyRead,
|
|
LoginRequest,
|
|
SetupRequest,
|
|
TokenResponse,
|
|
UserRead,
|
|
UserUpdate,
|
|
)
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
DbDep = Annotated[Session, Depends(get_db)]
|
|
UserDep = Annotated[User, Depends(get_current_user)]
|
|
|
|
|
|
def _token_response(user: User) -> TokenResponse:
|
|
return TokenResponse(
|
|
access_token=create_access_token(user.id),
|
|
user=UserRead.model_validate(user),
|
|
)
|
|
|
|
|
|
@router.get("/status", response_model=AuthStatus)
|
|
def auth_status(db: DbDep) -> AuthStatus:
|
|
required = service.setup_required(db)
|
|
return AuthStatus(setup_required=required, needs_setup=required)
|
|
|
|
|
|
@router.post("/setup", response_model=TokenResponse, status_code=201)
|
|
def setup(payload: SetupRequest, db: DbDep) -> TokenResponse:
|
|
user = service.create_first_user(db, payload)
|
|
return _token_response(user)
|
|
|
|
|
|
@router.post("/login", response_model=TokenResponse)
|
|
def login(payload: LoginRequest, db: DbDep) -> TokenResponse:
|
|
user = service.authenticate(db, payload.email, payload.password)
|
|
return _token_response(user)
|
|
|
|
|
|
@router.get("/me", response_model=UserRead)
|
|
def me(user: UserDep) -> User:
|
|
return user
|
|
|
|
|
|
@router.patch("/me", response_model=UserRead)
|
|
def update_me(payload: UserUpdate, db: DbDep, user: UserDep) -> User:
|
|
return service.update_me(db, user, payload)
|
|
|
|
|
|
@router.get("/device-keys", response_model=list[DeviceKeyRead])
|
|
def list_device_keys(db: DbDep, user: UserDep) -> list[DeviceKeyRead]:
|
|
return service.list_device_keys(db, user.id)
|
|
|
|
|
|
@router.post("/device-keys", response_model=DeviceKeyCreated, status_code=201)
|
|
def create_device_key(
|
|
payload: DeviceKeyCreate, db: DbDep, user: UserDep
|
|
) -> DeviceKeyCreated:
|
|
key, plaintext = service.create_device_key(db, user.id, payload)
|
|
read = DeviceKeyRead.model_validate(key)
|
|
return DeviceKeyCreated(**read.model_dump(), key=plaintext)
|
|
|
|
|
|
@router.delete("/device-keys/{key_id}", status_code=204)
|
|
def revoke_device_key(key_id: int, db: DbDep, user: UserDep) -> None:
|
|
service.revoke_device_key(db, user.id, key_id)
|