11import asyncio
2+ import json
23from datetime import datetime , timedelta , timezone
34from typing import Any , List
45
@@ -182,17 +183,17 @@ async def update_user_settings(
182183 settings_type = SettingsType .DISPLAY
183184 else :
184185 settings_type = SettingsType .PREFERENCES
185- # Flatten changes to string map for the generic event
186- changes : dict [str , str ] = {}
187- for k , v in updated .items ():
188- changes [ k ] = str ( v )
186+ # Stringify all values for Avro compatibility (nested dicts become JSON strings)
187+ updated_stringified : dict [str , str ] = {
188+ k : json . dumps ( v ) if isinstance ( v , dict ) else str ( v ) for k , v in updated .items ()
189+ }
189190 await self .event_service .publish_event (
190191 event_type = EventType .USER_SETTINGS_UPDATED ,
191192 aggregate_id = f"user_settings_{ user_id } " ,
192193 payload = {
193194 "user_id" : user_id ,
194195 "settings_type" : settings_type ,
195- "changes " : changes ,
196+ "updated " : updated_stringified ,
196197 "reason" : reason ,
197198 },
198199 metadata = None ,
@@ -297,7 +298,7 @@ async def restore_settings_to_point(self, user_id: str, timestamp: datetime) ->
297298 payload = {
298299 "user_id" : user_id ,
299300 "settings_type" : SettingsType .PREFERENCES ,
300- "changes " : {"restored_to" : timestamp .isoformat ()},
301+ "updated " : {"restored_to" : timestamp .isoformat ()},
301302 },
302303 metadata = None ,
303304 )
@@ -339,32 +340,22 @@ def _apply_event(self, settings: DomainUserSettings, event: DomainSettingsEvent)
339340 settings .theme = Theme (new_theme )
340341 return settings
341342
342- upd = event .payload .get ("updated" )
343- if not upd :
344- return settings
345-
346- # Top-level
347- if "theme" in upd :
348- settings .theme = Theme (upd ["theme" ])
349- if "timezone" in upd :
350- settings .timezone = upd ["timezone" ]
351- if "date_format" in upd :
352- settings .date_format = upd ["date_format" ]
353- if "time_format" in upd :
354- settings .time_format = upd ["time_format" ]
355- # Nested
356- if "notifications" in upd and isinstance (upd ["notifications" ], dict ):
357- n = upd ["notifications" ]
358- channels : list [NotificationChannel ] = [NotificationChannel (c ) for c in n .get ("channels" , [])]
343+ if event .event_type == EventType .USER_NOTIFICATION_SETTINGS_UPDATED :
344+ n = event .payload .get ("settings" , {})
345+ channels_raw = event .payload .get ("channels" , [])
346+ channels : list [NotificationChannel ] = [NotificationChannel (c ) for c in channels_raw ] if channels_raw else []
359347 settings .notifications = DomainNotificationSettings (
360348 execution_completed = n .get ("execution_completed" , settings .notifications .execution_completed ),
361349 execution_failed = n .get ("execution_failed" , settings .notifications .execution_failed ),
362350 system_updates = n .get ("system_updates" , settings .notifications .system_updates ),
363351 security_alerts = n .get ("security_alerts" , settings .notifications .security_alerts ),
364352 channels = channels or settings .notifications .channels ,
365353 )
366- if "editor" in upd and isinstance (upd ["editor" ], dict ):
367- e = upd ["editor" ]
354+ settings .updated_at = event .timestamp
355+ return settings
356+
357+ if event .event_type == EventType .USER_EDITOR_SETTINGS_UPDATED :
358+ e = event .payload .get ("settings" , {})
368359 settings .editor = DomainEditorSettings (
369360 theme = e .get ("theme" , settings .editor .theme ),
370361 font_size = e .get ("font_size" , settings .editor .font_size ),
@@ -373,8 +364,58 @@ def _apply_event(self, settings: DomainUserSettings, event: DomainSettingsEvent)
373364 word_wrap = e .get ("word_wrap" , settings .editor .word_wrap ),
374365 show_line_numbers = e .get ("show_line_numbers" , settings .editor .show_line_numbers ),
375366 )
376- if "custom_settings" in upd and isinstance (upd ["custom_settings" ], dict ):
377- settings .custom_settings = upd ["custom_settings" ]
367+ settings .updated_at = event .timestamp
368+ return settings
369+
370+ upd = event .payload .get ("updated" )
371+ if not upd :
372+ return settings
373+
374+ # Helper to parse JSON strings or return dict as-is
375+ def parse_value (val : object ) -> object :
376+ if isinstance (val , str ):
377+ try :
378+ return json .loads (val )
379+ except (json .JSONDecodeError , ValueError ):
380+ return val
381+ return val
382+
383+ # Top-level
384+ if "theme" in upd :
385+ settings .theme = Theme (str (upd ["theme" ]))
386+ if "timezone" in upd :
387+ settings .timezone = str (upd ["timezone" ])
388+ if "date_format" in upd :
389+ settings .date_format = str (upd ["date_format" ])
390+ if "time_format" in upd :
391+ settings .time_format = str (upd ["time_format" ])
392+ # Nested (may be JSON strings or dicts)
393+ if "notifications" in upd :
394+ n = parse_value (upd ["notifications" ])
395+ if isinstance (n , dict ):
396+ channels : list [NotificationChannel ] = [NotificationChannel (c ) for c in n .get ("channels" , [])]
397+ settings .notifications = DomainNotificationSettings (
398+ execution_completed = n .get ("execution_completed" , settings .notifications .execution_completed ),
399+ execution_failed = n .get ("execution_failed" , settings .notifications .execution_failed ),
400+ system_updates = n .get ("system_updates" , settings .notifications .system_updates ),
401+ security_alerts = n .get ("security_alerts" , settings .notifications .security_alerts ),
402+ channels = channels or settings .notifications .channels ,
403+ )
404+ if "editor" in upd :
405+ e = parse_value (upd ["editor" ])
406+ if isinstance (e , dict ):
407+ settings .editor = DomainEditorSettings (
408+ theme = e .get ("theme" , settings .editor .theme ),
409+ font_size = e .get ("font_size" , settings .editor .font_size ),
410+ tab_size = e .get ("tab_size" , settings .editor .tab_size ),
411+ use_tabs = e .get ("use_tabs" , settings .editor .use_tabs ),
412+ word_wrap = e .get ("word_wrap" , settings .editor .word_wrap ),
413+ show_line_numbers = e .get ("show_line_numbers" , settings .editor .show_line_numbers ),
414+ )
415+ if "custom_settings" in upd :
416+ cs = parse_value (upd ["custom_settings" ])
417+ if isinstance (cs , dict ):
418+ settings .custom_settings = cs
378419 settings .version = event .payload .get ("version" , settings .version )
379420 settings .updated_at = event .timestamp
380421 return settings
0 commit comments