Merge remote-tracking branch 'origin/dev' into feat/endpoint-sites-current
# Conflicts: # apps/backend/app/repositories/reading.py # apps/backend/openapi.json # docs/architecture/20-backend.md
This commit is contained in:
@@ -31,7 +31,9 @@ from app.repositories.site import SiteRepository
|
||||
from app.repositories.user import UserRepository
|
||||
from app.services.alert import AlertService
|
||||
from app.services.auth import AuthService, LoginPolicy
|
||||
from app.services.reading import ReadingService
|
||||
from app.services.recommendation import RecommendationService
|
||||
from app.services.sensor import SensorService
|
||||
from app.services.site import SiteService
|
||||
from app.services.stats import StatsService
|
||||
from app.services.user import UserService
|
||||
@@ -167,6 +169,20 @@ def get_stats_service(session: SessionDep) -> StatsService:
|
||||
StatsServiceDep = Annotated[StatsService, Depends(get_stats_service)]
|
||||
|
||||
|
||||
def get_reading_service(session: SessionDep) -> ReadingService:
|
||||
return ReadingService(readings=ReadingRepository(session))
|
||||
|
||||
|
||||
ReadingServiceDep = Annotated[ReadingService, Depends(get_reading_service)]
|
||||
|
||||
|
||||
def get_sensor_service(session: SessionDep) -> SensorService:
|
||||
return SensorService(sites=SiteRepository(session), readings=ReadingRepository(session))
|
||||
|
||||
|
||||
SensorServiceDep = Annotated[SensorService, Depends(get_sensor_service)]
|
||||
|
||||
|
||||
async def get_current_principal(
|
||||
credentials: CredentialsDep,
|
||||
session: SessionDep,
|
||||
|
||||
@@ -71,6 +71,18 @@ TAGS: Final[list[dict[str, Any]]] = [
|
||||
"description": "Statistiques agrégées de consommation. Accessible à partir du rôle "
|
||||
"`lecteur`.",
|
||||
},
|
||||
{
|
||||
"name": "readings",
|
||||
"description": (
|
||||
"Historique des lectures de consommation. Fenêtre temporelle plafonnée à 90 jours, "
|
||||
"24 dernières heures par défaut si `start`/`end` sont omis. Accessible à partir du "
|
||||
"rôle `lecteur`."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "sensors",
|
||||
"description": "État de santé des capteurs par site. Réservé au rôle `admin`.",
|
||||
},
|
||||
]
|
||||
|
||||
cookie_de_rafraichissement = APIKeyCookie(
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, status
|
||||
|
||||
from app.api.deps import LecteurDep, ReadingServiceDep
|
||||
from app.api.openapi import REPONSE_VALIDATION, Reponses
|
||||
from app.schemas.errors import ErrorResponse
|
||||
from app.schemas.reading import ReadingResponse
|
||||
from app.services.reading import FenetreInverseeError, FenetreTropLargeError
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
REPONSES_FENETRE: Reponses = {
|
||||
**REPONSE_VALIDATION,
|
||||
400: {
|
||||
"model": ErrorResponse,
|
||||
"description": (
|
||||
"Fenêtre temporelle invalide : `start` postérieur ou égal à `end`, ou écart entre "
|
||||
"les deux supérieur à 90 jours."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[ReadingResponse],
|
||||
summary="Liste l'historique des lectures",
|
||||
responses=REPONSES_FENETRE,
|
||||
)
|
||||
async def list_readings(
|
||||
_: LecteurDep,
|
||||
service: ReadingServiceDep,
|
||||
site_id: str | None = None,
|
||||
start: datetime | None = None,
|
||||
end: datetime | None = None,
|
||||
limit: int = Query(500, ge=1, le=2000),
|
||||
offset: int = Query(0, ge=0),
|
||||
) -> list[ReadingResponse]:
|
||||
try:
|
||||
lectures = await service.list_history(
|
||||
site_id=site_id, start=start, end=end, limit=limit, offset=offset
|
||||
)
|
||||
except FenetreInverseeError as erreur:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="`start` doit être strictement antérieur à `end`",
|
||||
) from erreur
|
||||
except FenetreTropLargeError as erreur:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="L'écart entre `start` et `end` ne peut pas dépasser 90 jours",
|
||||
) from erreur
|
||||
return [ReadingResponse.model_validate(lecture) for lecture in lectures]
|
||||
@@ -0,0 +1,16 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.deps import AdminDep, SensorServiceDep
|
||||
from app.schemas.sensor import SensorStatusResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/status",
|
||||
response_model=SensorStatusResponse,
|
||||
summary="État de santé des capteurs par site",
|
||||
)
|
||||
async def get_status(_: AdminDep, service: SensorServiceDep) -> SensorStatusResponse:
|
||||
etat = await service.status()
|
||||
return SensorStatusResponse.model_validate(etat)
|
||||
@@ -1,7 +1,17 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.openapi import REPONSE_SERVEUR, REPONSES_ADMIN, REPONSES_LECTEUR
|
||||
from app.api.v1.endpoints import alerts, auth, health, recommendations, sites, stats, users
|
||||
from app.api.v1.endpoints import (
|
||||
alerts,
|
||||
auth,
|
||||
health,
|
||||
readings,
|
||||
recommendations,
|
||||
sensors,
|
||||
sites,
|
||||
stats,
|
||||
users,
|
||||
)
|
||||
|
||||
api_router = APIRouter(responses=REPONSE_SERVEUR)
|
||||
api_router.include_router(health.router, prefix="/health", tags=["health"])
|
||||
@@ -18,3 +28,9 @@ api_router.include_router(
|
||||
responses=REPONSES_LECTEUR,
|
||||
)
|
||||
api_router.include_router(stats.router, prefix="/stats", tags=["stats"], responses=REPONSES_LECTEUR)
|
||||
api_router.include_router(
|
||||
readings.router, prefix="/readings", tags=["readings"], responses=REPONSES_LECTEUR
|
||||
)
|
||||
api_router.include_router(
|
||||
sensors.router, prefix="/sensors", tags=["sensors"], responses=REPONSES_ADMIN
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user