settings.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """
  2. Superadmin endpoints for system settings.
  3. """
  4. from datetime import datetime, timedelta, timezone
  5. from typing import Annotated
  6. from fastapi import APIRouter, Depends, HTTPException, status
  7. from pydantic import BaseModel
  8. from sqlalchemy import select
  9. from sqlalchemy.ext.asyncio import AsyncSession
  10. from app.api.deps import get_current_superadmin
  11. from app.core.database import get_db
  12. from app.models.settings import Settings
  13. from app.models.user import User
  14. router = APIRouter()
  15. class AutoRegistrationResponse(BaseModel):
  16. """Auto-registration status response."""
  17. enabled: bool
  18. last_device_at: str | None
  19. time_left: int # Seconds until auto-disable (0 if disabled)
  20. class AutoRegistrationToggleRequest(BaseModel):
  21. """Auto-registration toggle request."""
  22. enabled: bool
  23. @router.get("/auto-registration", response_model=AutoRegistrationResponse)
  24. async def get_auto_registration_status(
  25. db: Annotated[AsyncSession, Depends(get_db)],
  26. current_user: Annotated[User, Depends(get_current_superadmin)],
  27. ):
  28. """
  29. Get auto-registration status.
  30. Returns current state and last device registration timestamp.
  31. """
  32. result = await db.execute(
  33. select(Settings).where(Settings.key == "auto_registration")
  34. )
  35. setting = result.scalar_one_or_none()
  36. if not setting:
  37. # Create default setting if not exists
  38. setting = Settings(
  39. key="auto_registration",
  40. value={"enabled": False, "last_device_at": None},
  41. )
  42. db.add(setting)
  43. await db.commit()
  44. # Check if should auto-disable (1 hour since last device)
  45. enabled = setting.value.get("enabled", False)
  46. last_device_at_str = setting.value.get("last_device_at")
  47. time_left = 0
  48. if enabled and last_device_at_str:
  49. last_device_at = datetime.fromisoformat(last_device_at_str)
  50. time_since = datetime.now(timezone.utc) - last_device_at
  51. time_left = max(0, int(3600 - time_since.total_seconds())) # 1 hour = 3600 seconds
  52. if time_since > timedelta(hours=1):
  53. # Auto-disable
  54. setting.value["enabled"] = False
  55. await db.commit()
  56. enabled = False
  57. time_left = 0
  58. return AutoRegistrationResponse(
  59. enabled=enabled,
  60. last_device_at=last_device_at_str,
  61. time_left=time_left,
  62. )
  63. @router.post("/auto-registration", response_model=AutoRegistrationResponse)
  64. async def toggle_auto_registration(
  65. data: AutoRegistrationToggleRequest,
  66. db: Annotated[AsyncSession, Depends(get_db)],
  67. current_user: Annotated[User, Depends(get_current_superadmin)],
  68. ):
  69. """
  70. Enable or disable auto-registration of new devices.
  71. When enabled, devices that authenticate with unknown MAC addresses will be
  72. automatically registered in the system (unassigned to any organization).
  73. Auto-registration will automatically disable after 1 hour of the last registered device.
  74. """
  75. result = await db.execute(
  76. select(Settings).where(Settings.key == "auto_registration")
  77. )
  78. setting = result.scalar_one_or_none()
  79. if not setting:
  80. setting = Settings(
  81. key="auto_registration",
  82. value={"enabled": data.enabled, "last_device_at": None},
  83. )
  84. db.add(setting)
  85. else:
  86. setting.value["enabled"] = data.enabled
  87. if not data.enabled:
  88. # Reset last_device_at when disabling
  89. setting.value["last_device_at"] = None
  90. await db.commit()
  91. # Calculate time_left
  92. time_left = 0
  93. enabled = setting.value.get("enabled", False)
  94. last_device_at_str = setting.value.get("last_device_at")
  95. if enabled and last_device_at_str:
  96. last_device_at = datetime.fromisoformat(last_device_at_str)
  97. time_since = datetime.now(timezone.utc) - last_device_at
  98. time_left = max(0, int(3600 - time_since.total_seconds()))
  99. return AutoRegistrationResponse(
  100. enabled=enabled,
  101. last_device_at=last_device_at_str,
  102. time_left=time_left,
  103. )
  104. class SettingUpdate(BaseModel):
  105. """Generic setting update request."""
  106. value: dict
  107. class SettingResponse(BaseModel):
  108. """Generic setting response."""
  109. key: str
  110. value: dict
  111. updated_at: str | None
  112. @router.get("/setting/{key}", response_model=SettingResponse)
  113. async def get_setting(
  114. key: str,
  115. db: Annotated[AsyncSession, Depends(get_db)],
  116. current_user: Annotated[User, Depends(get_current_superadmin)],
  117. ):
  118. """Get a specific setting by key."""
  119. result = await db.execute(select(Settings).where(Settings.key == key))
  120. setting = result.scalar_one_or_none()
  121. if not setting:
  122. raise HTTPException(
  123. status_code=status.HTTP_404_NOT_FOUND,
  124. detail=f"setting_not_found:{key}",
  125. )
  126. return SettingResponse(
  127. key=setting.key,
  128. value=setting.value,
  129. updated_at=setting.updated_at.isoformat() if setting.updated_at else None,
  130. )
  131. @router.put("/setting/{key}", response_model=SettingResponse)
  132. async def update_setting(
  133. key: str,
  134. data: SettingUpdate,
  135. db: Annotated[AsyncSession, Depends(get_db)],
  136. current_user: Annotated[User, Depends(get_current_superadmin)],
  137. ):
  138. """Update a specific setting by key."""
  139. result = await db.execute(select(Settings).where(Settings.key == key))
  140. setting = result.scalar_one_or_none()
  141. if not setting:
  142. raise HTTPException(
  143. status_code=status.HTTP_404_NOT_FOUND,
  144. detail=f"setting_not_found:{key}",
  145. )
  146. setting.value = data.value
  147. setting.updated_at = datetime.now(timezone.utc)
  148. await db.commit()
  149. await db.refresh(setting)
  150. return SettingResponse(
  151. key=setting.key,
  152. value=setting.value,
  153. updated_at=setting.updated_at.isoformat(),
  154. )