First Production Push

This commit is contained in:
2026-02-25 21:29:56 +02:00
parent e62cffc10c
commit 8cb639c1bd
32 changed files with 3714 additions and 2719 deletions

View File

@@ -15,6 +15,33 @@ COLLECTION = "devices"
SN_CHARS = string.ascii_uppercase + string.digits
SN_SEGMENT_LEN = 4
# Clock/silence/backlight fields stored as Firestore Timestamps (written as datetime)
_TIMESTAMP_FIELD_NAMES = {
"daySilenceFrom", "daySilenceTo",
"nightSilenceFrom", "nightSilenceTo",
"backlightTurnOnTime", "backlightTurnOffTime",
}
def _restore_timestamps(d: dict) -> dict:
"""Recursively convert ISO 8601 strings for known timestamp fields to datetime objects.
Firestore stores Python datetime objects as native Timestamps, which Flutter
reads as DateTime. Plain strings would break the Flutter app.
"""
result = {}
for k, v in d.items():
if isinstance(v, dict):
result[k] = _restore_timestamps(v)
elif isinstance(v, str) and k in _TIMESTAMP_FIELD_NAMES:
try:
result[k] = datetime.fromisoformat(v.replace("Z", "+00:00"))
except ValueError:
result[k] = v
else:
result[k] = v
return result
def _generate_serial_number() -> str:
"""Generate a unique serial number in the format BS-XXXX-XXXX."""
@@ -139,6 +166,17 @@ def create_device(data: DeviceCreate) -> DeviceInDB:
return DeviceInDB(id=doc_ref.id, **doc_data)
def _deep_merge(base: dict, overrides: dict) -> dict:
"""Recursively merge overrides into base, preserving unmentioned nested keys."""
result = dict(base)
for k, v in overrides.items():
if isinstance(v, dict) and isinstance(result.get(k), dict):
result[k] = _deep_merge(result[k], v)
else:
result[k] = v
return result
def update_device(device_doc_id: str, data: DeviceUpdate) -> DeviceInDB:
"""Update an existing device document. Only provided fields are updated."""
db = get_db()
@@ -149,16 +187,16 @@ def update_device(device_doc_id: str, data: DeviceUpdate) -> DeviceInDB:
update_data = data.model_dump(exclude_none=True)
# For nested structs, merge with existing data rather than replacing
# Deep-merge nested structs so unmentioned sub-fields are preserved
existing = doc.to_dict()
nested_keys = (
"device_attributes", "device_subscription", "device_stats",
)
for key in nested_keys:
if key in update_data and key in existing:
merged = {**existing[key], **update_data[key]}
update_data[key] = merged
if key in update_data and isinstance(existing.get(key), dict):
update_data[key] = _deep_merge(existing[key], update_data[key])
update_data = _restore_timestamps(update_data)
doc_ref.update(update_data)
updated_doc = doc_ref.get()