Files
workphone-sdk/sdk/app/routers/device_groups.py

123 lines
5.7 KiB
Python

"""总控平台设备分组 CRUD 与成员变更接口。"""
from __future__ import annotations
import uuid
from typing import Optional
from fastapi import APIRouter, Header
from fastapi.responses import JSONResponse
from models.workbench import DeviceGroupCreate, DeviceGroupMembers, DeviceGroupPatch
from services.device_groups import device_group_service
from services.workbench import ControlPlaneStorageUnavailable, IdempotencyConflict, error_body
router = APIRouter(prefix="/device-groups", tags=["设备分组"])
def _write_meta(key: Optional[str], actor: Optional[str]) -> tuple[Optional[str], str, str]:
return key.strip() if key else None, (actor or "system").strip() or "system", uuid.uuid4().hex
def _error(status: int, code: str, message: str, trace_id: Optional[str] = None):
return JSONResponse(status_code=status, content=error_body(code, message, trace_id))
def _storage_error(exc: Exception):
return _error(503, "control_plane_storage_unavailable", str(exc))
def _read_only_empty(exc: Exception):
"""仓库暂时没接上时,列表页返回可识别的空结果,不把整页打成 500。"""
return {
"code": 200,
"success": True,
"data": {
"items": [],
"data_state": "storage_unavailable",
"source": "control_plane_storage",
"message": str(exc),
},
}
@router.get("", response_model=dict)
async def list_device_groups():
try:
return {"code": 200, "success": True, "data": {"items": await device_group_service.list_groups()}}
except ControlPlaneStorageUnavailable as exc:
return _read_only_empty(exc)
@router.get("/{group_id}", response_model=dict)
async def get_device_group(group_id: str):
try:
item = await device_group_service.get_group(group_id)
except ControlPlaneStorageUnavailable as exc:
return _storage_error(exc)
if item is None:
return _error(404, "resource_not_found", "设备分组不存在")
return {"code": 200, "success": True, "data": item}
@router.post("", response_model=dict)
async def create_device_group(body: DeviceGroupCreate, idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"), actor: Optional[str] = Header(None, alias="X-Actor-Id")):
key, actor_id, trace_id = _write_meta(idempotency_key, actor)
if not key:
return _error(400, "validation_error", "所有写操作都需要 Idempotency-Key", trace_id)
try:
result = await device_group_service.create(name=body.name, device_ids=body.device_ids, description=body.description, actor=actor_id, key=key, trace_id=trace_id)
return {"code": 200, "success": True, "data": result}
except IdempotencyConflict:
return _error(409, "idempotency_conflict", "同一幂等键对应了不同请求", trace_id)
except ControlPlaneStorageUnavailable as exc:
return _storage_error(exc)
@router.patch("/{group_id}", response_model=dict)
async def patch_device_group(group_id: str, body: DeviceGroupPatch, idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"), actor: Optional[str] = Header(None, alias="X-Actor-Id")):
key, actor_id, trace_id = _write_meta(idempotency_key, actor)
if not key:
return _error(400, "validation_error", "所有写操作都需要 Idempotency-Key", trace_id)
try:
result = await device_group_service.patch(group_id=group_id, name=body.name, description=body.description, actor=actor_id, key=key, trace_id=trace_id)
if result is None:
return _error(404, "resource_not_found", "设备分组不存在", trace_id)
return {"code": 200, "success": True, "data": result}
except IdempotencyConflict:
return _error(409, "idempotency_conflict", "同一幂等键对应了不同请求", trace_id)
except ControlPlaneStorageUnavailable as exc:
return _storage_error(exc)
@router.post("/{group_id}/members", response_model=dict)
async def mutate_device_group_members(group_id: str, body: DeviceGroupMembers, idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"), actor: Optional[str] = Header(None, alias="X-Actor-Id")):
key, actor_id, trace_id = _write_meta(idempotency_key, actor)
if not key:
return _error(400, "validation_error", "所有写操作都需要 Idempotency-Key", trace_id)
try:
result = await device_group_service.mutate_members(group_id=group_id, device_ids=body.device_ids, mode=body.mode, actor=actor_id, key=key, trace_id=trace_id)
if result is None:
return _error(404, "resource_not_found", "设备分组不存在", trace_id)
return {"code": 200, "success": True, "data": result}
except IdempotencyConflict:
return _error(409, "idempotency_conflict", "同一幂等键对应了不同请求", trace_id)
except ControlPlaneStorageUnavailable as exc:
return _storage_error(exc)
@router.delete("/{group_id}", response_model=dict)
async def delete_device_group(group_id: str, idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"), actor: Optional[str] = Header(None, alias="X-Actor-Id")):
key, actor_id, trace_id = _write_meta(idempotency_key, actor)
if not key:
return _error(400, "validation_error", "所有写操作都需要 Idempotency-Key", trace_id)
try:
result = await device_group_service.delete(group_id=group_id, actor=actor_id, key=key, trace_id=trace_id)
if result is None:
return _error(404, "resource_not_found", "设备分组不存在", trace_id)
return {"code": 200, "success": True, "data": result}
except IdempotencyConflict:
return _error(409, "idempotency_conflict", "同一幂等键对应了不同请求", trace_id)
except ControlPlaneStorageUnavailable as exc:
return _storage_error(exc)