package service import ( "context" "fmt" "git.threesix.ai/jordan/slack5-1770529463/pkg/logging" "git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/domain" "git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/port" ) // PreferencesResult is the result of a preference operation. type PreferencesResult struct { UserID string Preferences map[string]any } // PreferenceService handles preference business logic. type PreferenceService struct { repo port.PreferenceRepository logger *logging.Logger } // NewPreferenceService creates a new preference service. func NewPreferenceService(repo port.PreferenceRepository, logger *logging.Logger) *PreferenceService { return &PreferenceService{ repo: repo, logger: logger.WithService("PreferenceService"), } } // GetPreferences returns all preferences for a user, merged with defaults. func (s *PreferenceService) GetPreferences(ctx context.Context, userID string) (*PreferencesResult, error) { if err := domain.ValidateUserID(userID); err != nil { return nil, err } rows, err := s.repo.GetByUserID(ctx, userID) if err != nil { return nil, err } stored := make(map[domain.PreferenceKey]string, len(rows)) for _, row := range rows { stored[domain.PreferenceKey(row.Key)] = row.Value } merged := domain.MergeWithDefaults(stored) serialized := domain.SerializeForResponse(merged) return &PreferencesResult{ UserID: userID, Preferences: serialized, }, nil } // UpdatePreferences validates and upserts the provided preferences, then returns the full merged result. func (s *PreferenceService) UpdatePreferences(ctx context.Context, userID string, input map[string]any) (*PreferencesResult, error) { if err := domain.ValidateUserID(userID); err != nil { return nil, err } // Validate and serialize each input key-value pair toUpsert := make(map[domain.PreferenceKey]string, len(input)) for key, val := range input { if err := domain.ValidateKey(key); err != nil { return nil, err } // Serialize value to string strVal, err := serializeValue(val) if err != nil { return nil, fmt.Errorf("%w: %s must have a valid value", domain.ErrInvalidPreferenceValue, key) } pk := domain.PreferenceKey(key) if err := domain.ValidateValue(pk, strVal); err != nil { return nil, err } toUpsert[pk] = strVal } // Upsert each preference for key, value := range toUpsert { if err := s.repo.Upsert(ctx, userID, string(key), value); err != nil { return nil, err } } s.logger.Info("preferences updated", "user_id", userID, "keys_updated", len(toUpsert)) // Fetch and return full merged preferences return s.GetPreferences(ctx, userID) } // serializeValue converts an any value to a string representation. func serializeValue(val any) (string, error) { switch v := val.(type) { case string: return v, nil case bool: if v { return "true", nil } return "false", nil case float64: // JSON numbers are decoded as float64 if v == 1 { return "true", nil } if v == 0 { return "false", nil } return "", fmt.Errorf("unsupported numeric value") default: return "", fmt.Errorf("unsupported value type %T", val) } }