package mail import ( "context" "encoding/json" "strings" "github.com/jackc/pgx/v5" ) func (s *Service) GetMailSettings(ctx context.Context, externalID string) (MailSettings, error) { defaults := defaultMailSettings() var density, themeMode, backgroundID, inboxSort, readingPane *string var conversationMode *bool var notificationsJSON []byte var updatedAt any err := s.db.QueryRow(ctx, ` SELECT s.preferences->'mail'->>'density', s.preferences->'mail'->>'theme_mode', s.preferences->'mail'->>'background_id', s.preferences->'mail'->>'inbox_sort', s.preferences->'mail'->>'reading_pane', (s.preferences->'mail'->>'conversation_mode')::boolean, s.preferences->'mail'->'notifications', s.updated_at FROM users u LEFT JOIN settings s ON s.user_id = u.id WHERE u.external_id = $1 `, externalID).Scan(&density, &themeMode, &backgroundID, &inboxSort, &readingPane, &conversationMode, ¬ificationsJSON, &updatedAt) if err != nil { if err == pgx.ErrNoRows { return defaults, nil } return MailSettings{}, err } if density != nil && *density != "" { defaults.Density = *density } if themeMode != nil && *themeMode != "" { defaults.ThemeMode = *themeMode } if backgroundID != nil && *backgroundID != "" { defaults.BackgroundID = *backgroundID } if inboxSort != nil && *inboxSort != "" { defaults.InboxSort = *inboxSort } if readingPane != nil && *readingPane != "" { defaults.ReadingPane = *readingPane } if conversationMode != nil { defaults.ConversationMode = *conversationMode } if len(notificationsJSON) > 0 && string(notificationsJSON) != "null" { var n MailNotificationSettings if err := json.Unmarshal(notificationsJSON, &n); err == nil { defaults.Notifications = n } } if updatedAt != nil { defaults.UpdatedAt = updatedAt } return defaults, nil } func (s *Service) UpdateMailSettings(ctx context.Context, externalID string, req *patchMailSettingsRequest) (MailSettings, error) { patch := map[string]any{} if req.Density != nil { patch["density"] = strings.ToLower(strings.TrimSpace(*req.Density)) } if req.ThemeMode != nil { patch["theme_mode"] = strings.ToLower(strings.TrimSpace(*req.ThemeMode)) } if req.BackgroundID != nil { patch["background_id"] = strings.ToLower(strings.TrimSpace(*req.BackgroundID)) } if req.InboxSort != nil { patch["inbox_sort"] = strings.ToLower(strings.TrimSpace(*req.InboxSort)) } if req.ReadingPane != nil { patch["reading_pane"] = strings.ToLower(strings.TrimSpace(*req.ReadingPane)) } if req.ConversationMode != nil { patch["conversation_mode"] = *req.ConversationMode } if req.Notifications != nil { current, err := s.GetMailSettings(ctx, externalID) if err != nil { return MailSettings{}, err } merged := current.Notifications if req.Notifications.DesktopNewMail != nil { merged.DesktopNewMail = *req.Notifications.DesktopNewMail } if req.Notifications.DesktopMentions != nil { merged.DesktopMentions = *req.Notifications.DesktopMentions } if req.Notifications.EmailDigest != nil { merged.EmailDigest = *req.Notifications.EmailDigest } if req.Notifications.SoundEnabled != nil { merged.SoundEnabled = *req.Notifications.SoundEnabled } patch["notifications"] = merged } patchJSON, err := json.Marshal(patch) if err != nil { return MailSettings{}, err } var updatedAt any err = s.db.QueryRow(ctx, ` INSERT INTO settings (user_id, preferences) VALUES ( (SELECT id FROM users WHERE external_id = $1), jsonb_build_object('mail', $2::jsonb) ) ON CONFLICT (user_id) DO UPDATE SET preferences = jsonb_set( COALESCE(settings.preferences, '{}'::jsonb), '{mail}', COALESCE(settings.preferences->'mail', '{}'::jsonb) || $2::jsonb ), updated_at = NOW() RETURNING updated_at `, externalID, patchJSON).Scan(&updatedAt) if err != nil { return MailSettings{}, err } result, err := s.GetMailSettings(ctx, externalID) if err != nil { return MailSettings{}, err } result.UpdatedAt = updatedAt return result, nil }