From f4b7daabd3b033203d2f29fd094cf747df4bef33 Mon Sep 17 00:00:00 2001 From: Alexis Fourmaux Date: Tue, 12 May 2026 21:59:24 +0200 Subject: [PATCH] feat: add API endpoint to get devices list --- server/backend/adapters/http/_devices_schemas.py | 10 ++++++++++ server/backend/adapters/http/devices.py | 12 ++++++++++++ server/backend/adapters/http/main.py | 2 ++ .../backend/adapters/postgres/device_repository.py | 13 +++++++++++++ server/backend/dependencies.py | 4 ++++ server/backend/domain/entities/__init__.py | 3 ++- server/backend/domain/entities/device.py | 6 ++++++ server/backend/ports/device_repository.py | 7 +++++++ server/backend/services/devices_service.py | 10 ++++++++++ 9 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 server/backend/adapters/http/_devices_schemas.py create mode 100644 server/backend/adapters/http/devices.py create mode 100644 server/backend/domain/entities/device.py create mode 100644 server/backend/services/devices_service.py diff --git a/server/backend/adapters/http/_devices_schemas.py b/server/backend/adapters/http/_devices_schemas.py new file mode 100644 index 0000000..9bda55d --- /dev/null +++ b/server/backend/adapters/http/_devices_schemas.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + +from domain.entities import Device + +class DeviceResponseSchema(BaseModel): + device_eui: str + + @classmethod + def from_domain(cls, device: Device) -> "DeviceResponseSchema": + return cls(device_eui=device.device_eui) diff --git a/server/backend/adapters/http/devices.py b/server/backend/adapters/http/devices.py new file mode 100644 index 0000000..d0a9b32 --- /dev/null +++ b/server/backend/adapters/http/devices.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter, Depends + +from services.devices_service import DeviceService +from dependencies import get_device_service +from ._devices_schemas import DeviceResponseSchema + +devices_router = APIRouter(prefix="/devices", tags=["devices"]) + + +@devices_router.get("", response_model=list[DeviceResponseSchema]) +def list_devices(service: DeviceService = Depends(get_device_service)): + return [DeviceResponseSchema.from_domain(d) for d in service.get_all_devices()] diff --git a/server/backend/adapters/http/main.py b/server/backend/adapters/http/main.py index f49aea0..1fa003a 100644 --- a/server/backend/adapters/http/main.py +++ b/server/backend/adapters/http/main.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .readings import readings_router +from .devices import devices_router app = FastAPI(title="SimuGazAPI", version="1.0.0") @@ -12,6 +13,7 @@ app.add_middleware( ) app.include_router(readings_router) +app.include_router(devices_router) @app.get("/health") def health(): diff --git a/server/backend/adapters/postgres/device_repository.py b/server/backend/adapters/postgres/device_repository.py index b48f10a..dcb4822 100644 --- a/server/backend/adapters/postgres/device_repository.py +++ b/server/backend/adapters/postgres/device_repository.py @@ -5,6 +5,7 @@ import psycopg2 from ports import DeviceRepository from domain.exceptions import DatabaseError from infrastructure.db import get_conn +from domain.entities import Device log = logging.getLogger(__name__) @@ -28,3 +29,15 @@ class PgDeviceRepository(DeviceRepository): return str(cur.fetchone()[0]) # type: ignore except psycopg2.DatabaseError as e: raise DatabaseError(f"Erreur de création du device {dev_eui}") from e + + def get_all(self) -> list[Device]: + query = "SELECT device_id, device_eui FROM device ORDER BY device_eui ASC" + try: + with get_conn() as conn: + with conn.cursor() as cur: + cur.execute(query) + rows = cur.fetchall() + except psycopg2.DatabaseError as e: + raise DatabaseError(f"Erreur d'accès aux devices : {e}") from e + + return [Device(device_id=r[0], device_eui=r[1]) for r in rows] \ No newline at end of file diff --git a/server/backend/dependencies.py b/server/backend/dependencies.py index 4667fd5..e4a9af0 100644 --- a/server/backend/dependencies.py +++ b/server/backend/dependencies.py @@ -4,6 +4,7 @@ from adapters.postgres import PgDeviceRepository, PgReadingRepository, PgReading from adapters.mqtt import PahoMqttBroker from services.uplink_service import UplinkService from services.consumption_service import ConsumptionService +from services.devices_service import DeviceService MQTT_HOST = os.getenv("MQTT_HOST", "mosquitto") MQTT_PORT = int(os.getenv("MQTT_PORT", 1883)) @@ -28,6 +29,9 @@ def get_uplink_service() -> UplinkService: def get_consumption_service() -> ConsumptionService: return ConsumptionService(get_query_repo()) +def get_device_service() -> DeviceService: + return DeviceService(get_device_repo()) + ## Adapters def get_mqtt_broker() -> PahoMqttBroker: diff --git a/server/backend/domain/entities/__init__.py b/server/backend/domain/entities/__init__.py index eb6f6cb..47d1e62 100644 --- a/server/backend/domain/entities/__init__.py +++ b/server/backend/domain/entities/__init__.py @@ -1,4 +1,5 @@ from .uplink_event import UplinkEvent from .consumption_point import ConsumptionPoint, ConsumptionResponse +from .device import Device -__all__ = ["UplinkEvent", "ConsumptionPoint", "ConsumptionResponse"] \ No newline at end of file +__all__ = ["UplinkEvent", "ConsumptionPoint", "ConsumptionResponse", "Device"] \ No newline at end of file diff --git a/server/backend/domain/entities/device.py b/server/backend/domain/entities/device.py new file mode 100644 index 0000000..f926577 --- /dev/null +++ b/server/backend/domain/entities/device.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + +@dataclass +class Device: + device_id: int + device_eui: str \ No newline at end of file diff --git a/server/backend/ports/device_repository.py b/server/backend/ports/device_repository.py index e5c2d34..36a6b33 100644 --- a/server/backend/ports/device_repository.py +++ b/server/backend/ports/device_repository.py @@ -1,7 +1,14 @@ from abc import ABC, abstractmethod +from domain.entities import Device + class DeviceRepository(ABC): @abstractmethod def get_or_create_device_id(self, dev_eui: str) -> str: """Retourne le device_id, crée le device s'il est inconnu""" + ... + + @abstractmethod + def get_all(self) -> list[Device]: + """Retourne l'ensemble des devices existants""" ... \ No newline at end of file diff --git a/server/backend/services/devices_service.py b/server/backend/services/devices_service.py new file mode 100644 index 0000000..e4e1b73 --- /dev/null +++ b/server/backend/services/devices_service.py @@ -0,0 +1,10 @@ +from domain.entities import Device +from adapters.postgres.device_repository import DeviceRepository + + +class DeviceService: + def __init__(self, device_repo: DeviceRepository) -> None: + self._device_repo = device_repo + + def get_all_devices(self) -> list[Device]: + return self._device_repo.get_all()