mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Change allow_existing default to True so duplicate fingerprint matches return the existing device (200) instead of 409 Conflict. Add model validator to force allow_existing=False when allow_duplicate is set.
180 lines
5.6 KiB
Python
180 lines
5.6 KiB
Python
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import HTTPException, Request, Response, status
|
|
from pydantic import BaseModel, model_validator
|
|
|
|
from decorators.auth import protected_route
|
|
from endpoints.responses.device import DeviceCreateResponse, DeviceSchema
|
|
from handler.auth.constants import Scope
|
|
from handler.database import db_device_handler, db_device_save_sync_handler
|
|
from logger.logger import log
|
|
from models.device import Device
|
|
from utils.router import APIRouter
|
|
|
|
router = APIRouter(
|
|
prefix="/devices",
|
|
tags=["devices"],
|
|
)
|
|
|
|
|
|
class DeviceCreatePayload(BaseModel):
|
|
name: str | None = None
|
|
platform: str | None = None
|
|
client: str | None = None
|
|
client_version: str | None = None
|
|
ip_address: str | None = None
|
|
mac_address: str | None = None
|
|
hostname: str | None = None
|
|
allow_existing: bool = True
|
|
allow_duplicate: bool = False
|
|
reset_syncs: bool = False
|
|
|
|
@model_validator(mode="after")
|
|
def _duplicate_disables_existing(self) -> "DeviceCreatePayload":
|
|
if self.allow_duplicate:
|
|
self.allow_existing = False
|
|
return self
|
|
|
|
|
|
class DeviceUpdatePayload(BaseModel):
|
|
name: str | None = None
|
|
platform: str | None = None
|
|
client: str | None = None
|
|
client_version: str | None = None
|
|
ip_address: str | None = None
|
|
mac_address: str | None = None
|
|
hostname: str | None = None
|
|
sync_enabled: bool | None = None
|
|
|
|
|
|
@protected_route(router.post, "", [Scope.DEVICES_WRITE])
|
|
def register_device(
|
|
request: Request,
|
|
response: Response,
|
|
payload: DeviceCreatePayload,
|
|
) -> DeviceCreateResponse:
|
|
existing_device = None
|
|
if not payload.allow_duplicate:
|
|
existing_device = db_device_handler.get_device_by_fingerprint(
|
|
user_id=request.user.id,
|
|
mac_address=payload.mac_address,
|
|
hostname=payload.hostname,
|
|
platform=payload.platform,
|
|
)
|
|
|
|
if existing_device:
|
|
if not payload.allow_existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail={
|
|
"error": "device_exists",
|
|
"message": "A device with this fingerprint already exists",
|
|
"device_id": existing_device.id,
|
|
},
|
|
)
|
|
|
|
if payload.reset_syncs:
|
|
db_device_save_sync_handler.delete_syncs_for_device(
|
|
device_id=existing_device.id
|
|
)
|
|
|
|
db_device_handler.update_last_seen(
|
|
device_id=existing_device.id, user_id=request.user.id
|
|
)
|
|
log.info(
|
|
f"Returned existing device {existing_device.id} for user {request.user.username}"
|
|
)
|
|
|
|
response.status_code = status.HTTP_200_OK
|
|
return DeviceCreateResponse(
|
|
device_id=existing_device.id,
|
|
name=existing_device.name,
|
|
created_at=existing_device.created_at,
|
|
)
|
|
|
|
response.status_code = status.HTTP_201_CREATED
|
|
device_id = str(uuid.uuid4())
|
|
now = datetime.now(timezone.utc)
|
|
|
|
device = Device(
|
|
id=device_id,
|
|
user_id=request.user.id,
|
|
name=payload.name,
|
|
platform=payload.platform,
|
|
client=payload.client,
|
|
client_version=payload.client_version,
|
|
ip_address=payload.ip_address,
|
|
mac_address=payload.mac_address,
|
|
hostname=payload.hostname,
|
|
last_seen=now,
|
|
)
|
|
|
|
db_device = db_device_handler.add_device(device)
|
|
log.info(f"Registered device {device_id} for user {request.user.username}")
|
|
|
|
return DeviceCreateResponse(
|
|
device_id=db_device.id,
|
|
name=db_device.name,
|
|
created_at=db_device.created_at,
|
|
)
|
|
|
|
|
|
@protected_route(router.get, "", [Scope.DEVICES_READ])
|
|
def get_devices(request: Request) -> list[DeviceSchema]:
|
|
devices = db_device_handler.get_devices(user_id=request.user.id)
|
|
return [DeviceSchema.model_validate(device) for device in devices]
|
|
|
|
|
|
@protected_route(router.get, "/{device_id}", [Scope.DEVICES_READ])
|
|
def get_device(request: Request, device_id: str) -> DeviceSchema:
|
|
device = db_device_handler.get_device(device_id=device_id, user_id=request.user.id)
|
|
if not device:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Device with ID {device_id} not found",
|
|
)
|
|
return DeviceSchema.model_validate(device)
|
|
|
|
|
|
@protected_route(router.put, "/{device_id}", [Scope.DEVICES_WRITE])
|
|
def update_device(
|
|
request: Request,
|
|
device_id: str,
|
|
payload: DeviceUpdatePayload,
|
|
) -> DeviceSchema:
|
|
device = db_device_handler.get_device(device_id=device_id, user_id=request.user.id)
|
|
if not device:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Device with ID {device_id} not found",
|
|
)
|
|
|
|
update_data = payload.model_dump(exclude_unset=True)
|
|
if update_data:
|
|
device = db_device_handler.update_device(
|
|
device_id=device_id,
|
|
user_id=request.user.id,
|
|
data=update_data,
|
|
)
|
|
|
|
return DeviceSchema.model_validate(device)
|
|
|
|
|
|
@protected_route(
|
|
router.delete,
|
|
"/{device_id}",
|
|
[Scope.DEVICES_WRITE],
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
)
|
|
def delete_device(request: Request, device_id: str) -> None:
|
|
device = db_device_handler.get_device(device_id=device_id, user_id=request.user.id)
|
|
if not device:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Device with ID {device_id} not found",
|
|
)
|
|
|
|
db_device_handler.delete_device(device_id=device_id, user_id=request.user.id)
|
|
log.info(f"Deleted device {device_id} for user {request.user.username}")
|