update: Add Global Search on Header, Add Global Audit log for all actions.
This commit is contained in:
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from auth.models import TokenPayload
|
||||
from auth.dependencies import require_permission
|
||||
from devices.models import (
|
||||
@@ -14,6 +15,8 @@ from devices import service
|
||||
import database as mqtt_db
|
||||
from mqtt.models import DeviceAlertEntry, DeviceAlertsResponse
|
||||
from shared.firebase import get_db as get_firestore
|
||||
from database.postgres import get_pg_session
|
||||
from shared.audit import log_action
|
||||
|
||||
router = APIRouter(prefix="/api/devices", tags=["devices"])
|
||||
|
||||
@@ -58,8 +61,12 @@ async def get_device_users(
|
||||
async def create_device(
|
||||
body: DeviceCreate,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "add")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
return service.create_device(body)
|
||||
device = service.create_device(body)
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "device",
|
||||
device.device_id, device.device_name or device.device_id)
|
||||
return device
|
||||
|
||||
|
||||
@router.put("/{device_id}", response_model=DeviceInDB)
|
||||
@@ -67,16 +74,32 @@ async def update_device(
|
||||
device_id: str,
|
||||
body: DeviceUpdate,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "edit")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
return service.update_device(device_id, body)
|
||||
old = service.get_device(device_id)
|
||||
device = service.update_device(device_id, body)
|
||||
_SKIP = {"updated_at", "device_id", "tags", "user_list"}
|
||||
changes = {
|
||||
k: {"old": getattr(old, k, None), "new": getattr(device, k, None)}
|
||||
for k in body.model_fields_set
|
||||
if k not in _SKIP and getattr(old, k, None) != getattr(device, k, None)
|
||||
}
|
||||
if "tags" in body.model_fields_set and (old.tags or []) != (device.tags or []):
|
||||
changes["tags"] = {"old": sorted(old.tags or []), "new": sorted(device.tags or [])}
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device",
|
||||
device_id, device.device_name or device_id, changes=changes or None)
|
||||
return device
|
||||
|
||||
|
||||
@router.delete("/{device_id}", status_code=204)
|
||||
async def delete_device(
|
||||
device_id: str,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "delete")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
service.delete_device(device_id)
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "DELETE", "device",
|
||||
device_id, device_id)
|
||||
|
||||
|
||||
@router.get("/{device_id}/alerts", response_model=DeviceAlertsResponse)
|
||||
@@ -100,16 +123,16 @@ async def list_device_notes(
|
||||
):
|
||||
"""List all notes for a device."""
|
||||
db = get_firestore()
|
||||
docs = db.collection(NOTES_COLLECTION).where("device_id", "==", device_id).order_by("created_at").stream()
|
||||
docs = db.collection(NOTES_COLLECTION).where("device_id", "==", device_id).stream()
|
||||
notes = []
|
||||
for doc in docs:
|
||||
note = doc.to_dict()
|
||||
note["id"] = doc.id
|
||||
# Convert Firestore Timestamps to ISO strings
|
||||
for f in ("created_at", "updated_at"):
|
||||
if hasattr(note.get(f), "isoformat"):
|
||||
note[f] = note[f].isoformat()
|
||||
notes.append(note)
|
||||
notes.sort(key=lambda n: n.get("created_at") or "", reverse=False)
|
||||
return {"notes": notes, "total": len(notes)}
|
||||
|
||||
|
||||
@@ -251,6 +274,7 @@ async def assign_device_to_customer(
|
||||
device_id: str,
|
||||
body: AssignCustomerBody,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "edit")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
"""Assign a device to a customer.
|
||||
|
||||
@@ -290,6 +314,9 @@ async def assign_device_to_customer(
|
||||
})
|
||||
customer_ref.update({"owned_items": owned_items})
|
||||
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device",
|
||||
device_id, device_id, meta={"action_detail": "assigned_to_customer",
|
||||
"customer_id": body.customer_id})
|
||||
return {"status": "assigned", "device_id": device_id, "customer_id": body.customer_id}
|
||||
|
||||
|
||||
@@ -298,6 +325,7 @@ async def unassign_device_from_customer(
|
||||
device_id: str,
|
||||
customer_id: str = Query(...),
|
||||
_user: TokenPayload = Depends(require_permission("devices", "edit")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
"""Remove device assignment from a customer."""
|
||||
db = get_firestore()
|
||||
@@ -317,6 +345,10 @@ async def unassign_device_from_customer(
|
||||
]
|
||||
customer_ref.update({"owned_items": owned_items})
|
||||
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device",
|
||||
device_id, device_id, meta={"action_detail": "unassigned_from_customer",
|
||||
"customer_id": customer_id})
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Customer detail (for Owner display in fleet)
|
||||
@@ -402,6 +434,7 @@ async def add_user_to_device(
|
||||
device_id: str,
|
||||
body: AddUserBody,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "edit")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
"""Add a user reference to the device's user_list field."""
|
||||
db = get_firestore()
|
||||
@@ -432,6 +465,9 @@ async def add_user_to_device(
|
||||
user_list.append(user_ref)
|
||||
device_ref.update({"user_list": user_list})
|
||||
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device",
|
||||
device_id, device_id, meta={"action_detail": "user_added",
|
||||
"user_id": body.user_id})
|
||||
return {"status": "added", "user_id": body.user_id}
|
||||
|
||||
|
||||
@@ -440,6 +476,7 @@ async def remove_user_from_device(
|
||||
device_id: str,
|
||||
user_id: str,
|
||||
_user: TokenPayload = Depends(require_permission("devices", "edit")),
|
||||
db: AsyncSession = Depends(get_pg_session),
|
||||
):
|
||||
"""Remove a user reference from the device's user_list field."""
|
||||
db = get_firestore()
|
||||
@@ -464,4 +501,7 @@ async def remove_user_from_device(
|
||||
new_list = [entry for entry in user_list if not resolves_to(entry, user_id)]
|
||||
device_ref.update({"user_list": new_list})
|
||||
|
||||
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device",
|
||||
device_id, device_id, meta={"action_detail": "user_removed",
|
||||
"user_id": user_id})
|
||||
return {"status": "removed", "user_id": user_id}
|
||||
|
||||
Reference in New Issue
Block a user