@@ -44,12 +44,17 @@ async def list_cameras(
4444 node_ids = {cam .node_id for cam in cameras if cam .node_id }
4545 if node_ids :
4646 existing_node_ids = {
47- n .id for n in db .query (CameraNode .id ).filter (CameraNode .id .in_ (node_ids )).all ()
47+ n .id
48+ for n in db .query (CameraNode .id )
49+ .filter (CameraNode .id .in_ (node_ids ))
50+ .all ()
4851 }
4952 for cam in cameras :
5053 if cam .node_id and cam .node_id not in existing_node_ids :
5154 logger .warning (
52- "Orphan camera %s (node_id=%s not found)" , cam .camera_id , cam .node_id
55+ "Orphan camera %s (node_id=%s not found)" ,
56+ cam .camera_id ,
57+ cam .node_id ,
5358 )
5459
5560 result = [c .to_dict () for c in cameras ]
@@ -96,7 +101,10 @@ async def take_snapshot(
96101
97102 try :
98103 result = await manager .send_command (
99- node .node_id , "take_snapshot" , {"camera_id" : camera_id }, timeout = 15.0 ,
104+ node .node_id ,
105+ "take_snapshot" ,
106+ {"camera_id" : camera_id },
107+ timeout = 15.0 ,
100108 )
101109 return result
102110 except TimeoutError :
@@ -136,7 +144,10 @@ async def toggle_recording(
136144 command = "start_recording" if recording else "stop_recording"
137145 try :
138146 result = await manager .send_command (
139- node .node_id , command , {"camera_id" : camera_id }, timeout = 10.0 ,
147+ node .node_id ,
148+ command ,
149+ {"camera_id" : camera_id },
150+ timeout = 10.0 ,
140151 )
141152 write_audit (
142153 db ,
@@ -240,13 +251,17 @@ async def get_all_settings(
240251 user : AuthUser = Depends (require_view ), db : Session = Depends (get_db )
241252):
242253 """Get all settings for the user's organization."""
243- vals = Setting .get_many (db , user .org_id , {
244- "scheduled_recording" : "false" ,
245- "scheduled_start" : "06:00" ,
246- "scheduled_end" : "17:00" ,
247- "continuous_24_7" : "false" ,
248- ** _NOTIFICATION_SETTING_DEFAULTS ,
249- })
254+ vals = Setting .get_many (
255+ db ,
256+ user .org_id ,
257+ {
258+ "scheduled_recording" : "false" ,
259+ "scheduled_start" : "06:00" ,
260+ "scheduled_end" : "17:00" ,
261+ "continuous_24_7" : "false" ,
262+ ** _NOTIFICATION_SETTING_DEFAULTS ,
263+ },
264+ )
250265 return {
251266 "recording" : {
252267 "scheduled_recording" : vals ["scheduled_recording" ] == "true" ,
@@ -256,8 +271,10 @@ async def get_all_settings(
256271 },
257272 "notifications" : {
258273 "motion_notifications" : vals ["motion_notifications" ] == "true" ,
259- "camera_transition_notifications" : vals ["camera_transition_notifications" ] == "true" ,
260- "node_transition_notifications" : vals ["node_transition_notifications" ] == "true" ,
274+ "camera_transition_notifications" : vals ["camera_transition_notifications" ]
275+ == "true" ,
276+ "node_transition_notifications" : vals ["node_transition_notifications" ]
277+ == "true" ,
261278 },
262279 }
263280
@@ -267,12 +284,16 @@ async def get_recording_settings(
267284 user : AuthUser = Depends (require_view ), db : Session = Depends (get_db )
268285):
269286 """Get recording settings."""
270- vals = Setting .get_many (db , user .org_id , {
271- "scheduled_recording" : "false" ,
272- "scheduled_start" : "06:00" ,
273- "scheduled_end" : "17:00" ,
274- "continuous_24_7" : "false" ,
275- })
287+ vals = Setting .get_many (
288+ db ,
289+ user .org_id ,
290+ {
291+ "scheduled_recording" : "false" ,
292+ "scheduled_start" : "06:00" ,
293+ "scheduled_end" : "17:00" ,
294+ "continuous_24_7" : "false" ,
295+ },
296+ )
276297 return {
277298 "scheduled_recording" : vals ["scheduled_recording" ] == "true" ,
278299 "scheduled_start" : vals ["scheduled_start" ],
@@ -317,6 +338,7 @@ async def update_recording_settings(
317338# GET is view-level (every member needs to know what's on), POST is
318339# admin-only (same audit-worthy gate as the other per-org toggles).
319340
341+
320342@router .get ("/settings/notifications" )
321343async def get_notification_settings (
322344 user : AuthUser = Depends (require_view ), db : Session = Depends (get_db )
@@ -330,8 +352,10 @@ async def get_notification_settings(
330352 vals = Setting .get_many (db , user .org_id , _NOTIFICATION_SETTING_DEFAULTS )
331353 return {
332354 "motion_notifications" : vals ["motion_notifications" ] == "true" ,
333- "camera_transition_notifications" : vals ["camera_transition_notifications" ] == "true" ,
334- "node_transition_notifications" : vals ["node_transition_notifications" ] == "true" ,
355+ "camera_transition_notifications" : vals ["camera_transition_notifications" ]
356+ == "true" ,
357+ "node_transition_notifications" : vals ["node_transition_notifications" ]
358+ == "true" ,
335359 }
336360
337361
@@ -349,13 +373,19 @@ async def update_notification_settings(
349373 key/value table can store it without a schema change — same
350374 convention the recording toggles use.
351375 """
352- Setting .set (db , user .org_id , "motion_notifications" , str (data .motion_notifications ).lower ())
353376 Setting .set (
354- db , user .org_id , "camera_transition_notifications" ,
377+ db , user .org_id , "motion_notifications" , str (data .motion_notifications ).lower ()
378+ )
379+ Setting .set (
380+ db ,
381+ user .org_id ,
382+ "camera_transition_notifications" ,
355383 str (data .camera_transition_notifications ).lower (),
356384 )
357385 Setting .set (
358- db , user .org_id , "node_transition_notifications" ,
386+ db ,
387+ user .org_id ,
388+ "node_transition_notifications" ,
359389 str (data .node_transition_notifications ).lower (),
360390 )
361391 write_audit (
@@ -366,7 +396,9 @@ async def update_notification_settings(
366396 username = audit_label (user ),
367397 details = {
368398 "motion_notifications" : bool (data .motion_notifications ),
369- "camera_transition_notifications" : bool (data .camera_transition_notifications ),
399+ "camera_transition_notifications" : bool (
400+ data .camera_transition_notifications
401+ ),
370402 "node_transition_notifications" : bool (data .node_transition_notifications ),
371403 },
372404 request = request ,
@@ -443,11 +475,13 @@ async def report_camera_codec(
443475 if not video_codec :
444476 raise HTTPException (status_code = 400 , detail = "video_codec is required" )
445477
446- # Codec strings are injected into HLS #EXT-X-CODECS headers —
478+ # Codec strings are stored for diagnostics and MCP tool reporting —
447479 # reject newlines or absurd lengths to prevent playlist corruption.
448- if len (video_codec ) > 64 or ' \n ' in video_codec or ' \r ' in video_codec :
480+ if len (video_codec ) > 64 or " \n " in video_codec or " \r " in video_codec :
449481 raise HTTPException (status_code = 400 , detail = "Invalid video_codec format" )
450- if audio_codec and (len (audio_codec ) > 64 or '\n ' in audio_codec or '\r ' in audio_codec ):
482+ if audio_codec and (
483+ len (audio_codec ) > 64 or "\n " in audio_codec or "\r " in audio_codec
484+ ):
451485 raise HTTPException (status_code = 400 , detail = "Invalid audio_codec format" )
452486
453487 # Defensive sanitization — older CloudNode builds shipped garbage
@@ -467,18 +501,27 @@ async def report_camera_codec(
467501 node .audio_codec = camera .audio_codec
468502 node .codec_detected_at = datetime .now (tz = timezone .utc ).replace (tzinfo = None )
469503 logger .info (
470- "Updated node %s codec: video=%s, audio=%s" , node .node_id , video_codec , camera .audio_codec
504+ "Updated node %s codec: video=%s, audio=%s" ,
505+ node .node_id ,
506+ video_codec ,
507+ camera .audio_codec ,
471508 )
472509
473510 db .commit ()
474511
475- logger .info ("Updated codec for camera %s: video=%s, audio=%s" , camera_id , video_codec , camera .audio_codec )
512+ logger .info (
513+ "Updated codec for camera %s: video=%s, audio=%s" ,
514+ camera_id ,
515+ video_codec ,
516+ camera .audio_codec ,
517+ )
476518
477519 return {"success" : True , "message" : "Codec updated" }
478520
479521
480522# ── Danger Zone ──────────────────────────────────────────────────────
481523
524+
482525@router .post ("/settings/danger/wipe-logs" )
483526@limiter .limit ("5/hour" )
484527async def wipe_stream_logs (
@@ -496,9 +539,12 @@ async def wipe_stream_logs(
496539
497540 count = db .query (StreamAccessLog ).filter_by (org_id = user .org_id ).delete ()
498541 from app .models .models import McpActivityLog
542+
499543 mcp_count = db .query (McpActivityLog ).filter_by (org_id = user .org_id ).delete ()
500544 db .commit ()
501- logger .warning ("Admin wiped %d stream + %d MCP logs (org redacted)" , count , mcp_count )
545+ logger .warning (
546+ "Admin wiped %d stream + %d MCP logs (org redacted)" , count , mcp_count
547+ )
502548 write_audit (
503549 db ,
504550 org_id = user .org_id ,
@@ -544,7 +590,10 @@ async def full_reset(
544590 # Tell CloudNode to wipe local data
545591 try :
546592 from app .api .ws import manager
547- result = await manager .send_command (node .node_id , "wipe_data" , {}, timeout = 10 )
593+
594+ result = await manager .send_command (
595+ node .node_id , "wipe_data" , {}, timeout = 10
596+ )
548597 if result and result .get ("status" ) == "success" :
549598 results ["nodes_wiped" ] += 1
550599 except Exception as e :
@@ -558,14 +607,21 @@ async def full_reset(
558607 results ["nodes_deleted" ] += 1
559608
560609 # 2. Wipe stream access logs
561- results ["logs_deleted" ] = db .query (StreamAccessLog ).filter_by (org_id = user .org_id ).delete ()
610+ results ["logs_deleted" ] = (
611+ db .query (StreamAccessLog ).filter_by (org_id = user .org_id ).delete ()
612+ )
562613
563614 # 3. Wipe MCP activity logs
564615 from app .models .models import McpActivityLog
565- results ["mcp_logs_deleted" ] = db .query (McpActivityLog ).filter_by (org_id = user .org_id ).delete ()
616+
617+ results ["mcp_logs_deleted" ] = (
618+ db .query (McpActivityLog ).filter_by (org_id = user .org_id ).delete ()
619+ )
566620
567621 # 4. Wipe settings
568- results ["settings_deleted" ] = db .query (Setting ).filter_by (org_id = user .org_id ).delete ()
622+ results ["settings_deleted" ] = (
623+ db .query (Setting ).filter_by (org_id = user .org_id ).delete ()
624+ )
569625
570626 # 5. Wipe audit logs
571627 db .query (AuditLog ).filter_by (org_id = user .org_id ).delete ()
0 commit comments