- add multi-device portal device management, key rotation, and sync request APIs - add Pi settings UI for remote server URL, device name, and API key - add local test-connection and sync-now actions - make upload and config sync reuse dynamic shared credentials - add Phase 25.5c migration and docs updates Co-authored-by: Copilot <[email protected]>
92 lines
3.3 KiB
Python
92 lines
3.3 KiB
Python
"""Server-side device configuration management for Phase 25.5a sync.
|
|
|
|
Provides API endpoints for Pi to fetch device config and portal to update it.
|
|
"""
|
|
|
|
from . import db
|
|
|
|
|
|
def get_device_config(device_id):
|
|
"""Get device config for a specific device.
|
|
|
|
Returns dict with owner_name, car_model, device_name, avatar_path, config_version.
|
|
"""
|
|
dev = db.get_device_by_id(device_id)
|
|
if not dev:
|
|
return None
|
|
|
|
return {
|
|
"owner_name": dev.get("owner_name") or "",
|
|
"car_model": dev.get("car_model") or "",
|
|
"device_name": dev.get("device_name") or "",
|
|
"avatar_path": dev.get("avatar_path") or "img/yaris.png",
|
|
"config_version": dev.get("config_version") or 0,
|
|
}
|
|
|
|
|
|
def update_device_config(device_id, data):
|
|
"""Update device config. Returns (success, errors_dict).
|
|
|
|
Validates input and updates config_version on change.
|
|
"""
|
|
errors = {}
|
|
if db.get_device_by_id(device_id) is None:
|
|
return False, {"error": "Device not found."}
|
|
|
|
if not isinstance(data, dict):
|
|
return False, {"error": "Invalid request body"}
|
|
|
|
# Validate fields
|
|
owner_name = data.get("owner_name", "").strip() if "owner_name" in data else None
|
|
car_model = data.get("car_model", "").strip() if "car_model" in data else None
|
|
device_name = data.get("device_name", "").strip() if "device_name" in data else None
|
|
avatar_path = data.get("avatar_path", "").strip() if "avatar_path" in data else None
|
|
|
|
if owner_name is not None and len(owner_name) > 100:
|
|
errors["owner_name"] = "Owner name must be 1-100 characters."
|
|
|
|
if car_model is not None and len(car_model) > 100:
|
|
errors["car_model"] = "Car model must be 1-100 characters."
|
|
|
|
if device_name is not None and len(device_name) > 100:
|
|
errors["device_name"] = "Device name must be 1-100 characters."
|
|
|
|
if avatar_path is not None and len(avatar_path) > 255:
|
|
errors["avatar_path"] = "Avatar path must be 1-255 characters."
|
|
|
|
if errors:
|
|
return False, errors
|
|
|
|
# Build update dict (only include provided fields)
|
|
with db.get_cursor(commit=True) as cur:
|
|
# Increment config_version if any field changed
|
|
if owner_name is not None or car_model is not None or device_name is not None or avatar_path is not None:
|
|
cur.execute(
|
|
"UPDATE devices SET config_version = config_version + 1, config_updated_at = now() WHERE id = %s",
|
|
(device_id,)
|
|
)
|
|
|
|
# Update individual fields
|
|
if owner_name is not None:
|
|
cur.execute(
|
|
"UPDATE devices SET owner_name = %s WHERE id = %s",
|
|
(owner_name, device_id)
|
|
)
|
|
if car_model is not None:
|
|
cur.execute(
|
|
"UPDATE devices SET car_model = %s WHERE id = %s",
|
|
(car_model, device_id)
|
|
)
|
|
if device_name is not None:
|
|
cur.execute(
|
|
"UPDATE devices SET device_name = %s WHERE id = %s",
|
|
(device_name, device_id)
|
|
)
|
|
if avatar_path is not None:
|
|
cur.execute(
|
|
"UPDATE devices SET avatar_path = %s WHERE id = %s",
|
|
(avatar_path, device_id)
|
|
)
|
|
|
|
return True, {}
|