devices.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. Superadmin endpoints for device management.
  3. """
  4. from datetime import datetime, timezone
  5. from typing import Annotated
  6. from fastapi import APIRouter, Depends, HTTPException, Query, status
  7. from sqlalchemy.ext.asyncio import AsyncSession
  8. from app.api.deps import get_current_superadmin
  9. from app.core.database import get_db
  10. from app.models.user import User
  11. from app.schemas.device import (
  12. DeviceCreate,
  13. DeviceListResponse,
  14. DeviceResponse,
  15. DeviceUpdate,
  16. )
  17. from app.services import device_service
  18. router = APIRouter()
  19. @router.get("", response_model=DeviceListResponse)
  20. async def list_devices(
  21. db: Annotated[AsyncSession, Depends(get_db)],
  22. current_user: Annotated[User, Depends(get_current_superadmin)],
  23. skip: int = Query(0, ge=0, description="Number of records to skip"),
  24. limit: int = Query(100, ge=1, le=1000, description="Max records to return"),
  25. organization_id: int | None = Query(
  26. None, description="Filter by organization"
  27. ),
  28. status: str | None = Query(None, description="Filter by status"),
  29. search: str | None = Query(
  30. None,
  31. min_length=2,
  32. description="Universal search: MAC, simple_id, organization name/email",
  33. ),
  34. ):
  35. """
  36. List all devices (superadmin only).
  37. Returns paginated list of devices with optional filters.
  38. Search parameter searches across:
  39. - MAC address
  40. - Simple ID (#1, #2, etc)
  41. - Organization name
  42. - Organization contact email
  43. """
  44. devices, total = await device_service.list_devices(
  45. db,
  46. skip=skip,
  47. limit=limit,
  48. organization_id=organization_id,
  49. status=status,
  50. search=search,
  51. )
  52. # Update status dynamically based on last_seen_at
  53. # Device is online if it fetched config within last 60 seconds
  54. now = datetime.now(timezone.utc)
  55. for device in devices:
  56. if device.last_seen_at:
  57. # Remove timezone info for comparison if needed
  58. last_seen = device.last_seen_at
  59. if last_seen.tzinfo is None:
  60. last_seen = last_seen.replace(tzinfo=timezone.utc)
  61. delta_seconds = (now - last_seen).total_seconds()
  62. device.status = "online" if delta_seconds < 60 else "offline"
  63. else:
  64. device.status = "offline"
  65. return DeviceListResponse(
  66. devices=devices,
  67. total=total,
  68. )
  69. @router.get("/{device_id}", response_model=DeviceResponse)
  70. async def get_device(
  71. device_id: int,
  72. db: Annotated[AsyncSession, Depends(get_db)],
  73. current_user: Annotated[User, Depends(get_current_superadmin)],
  74. ):
  75. """
  76. Get device by ID (superadmin only).
  77. """
  78. device = await device_service.get_device(db, device_id)
  79. if not device:
  80. raise HTTPException(
  81. status_code=status.HTTP_404_NOT_FOUND,
  82. detail="Device not found",
  83. )
  84. return device
  85. @router.post(
  86. "", response_model=DeviceResponse, status_code=status.HTTP_201_CREATED
  87. )
  88. async def create_device(
  89. data: DeviceCreate,
  90. db: Annotated[AsyncSession, Depends(get_db)],
  91. current_user: Annotated[User, Depends(get_current_superadmin)],
  92. ):
  93. """
  94. Register a new device (superadmin only).
  95. Devices are assigned a unique simple_id (Receiver #1, #2, etc).
  96. """
  97. try:
  98. device = await device_service.create_device(db, data)
  99. except ValueError as e:
  100. raise HTTPException(
  101. status_code=status.HTTP_400_BAD_REQUEST,
  102. detail=str(e),
  103. )
  104. return device
  105. @router.patch("/{device_id}", response_model=DeviceResponse)
  106. async def update_device(
  107. device_id: int,
  108. data: DeviceUpdate,
  109. db: Annotated[AsyncSession, Depends(get_db)],
  110. current_user: Annotated[User, Depends(get_current_superadmin)],
  111. ):
  112. """
  113. Update device (superadmin only).
  114. Can update device organization assignment, status, and configuration.
  115. """
  116. device = await device_service.update_device(db, device_id, data)
  117. if not device:
  118. raise HTTPException(
  119. status_code=status.HTTP_404_NOT_FOUND,
  120. detail="Device not found",
  121. )
  122. return device
  123. @router.delete("/{device_id}", status_code=status.HTTP_204_NO_CONTENT)
  124. async def delete_device(
  125. device_id: int,
  126. db: Annotated[AsyncSession, Depends(get_db)],
  127. current_user: Annotated[User, Depends(get_current_superadmin)],
  128. ):
  129. """
  130. Delete device (superadmin only).
  131. Warning: This permanently deletes the device.
  132. """
  133. deleted = await device_service.delete_device(db, device_id)
  134. if not deleted:
  135. raise HTTPException(
  136. status_code=status.HTTP_404_NOT_FOUND,
  137. detail="Device not found",
  138. )