// Package service provides business logic / use cases for the application. // Services orchestrate domain operations using port interfaces. package service import ( "context" "time" "git.threesix.ai/jordan/slack5-1770603014/pkg/logging" "git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/domain" "git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/port" ) // PreferencesService handles preferences-related business logic. type PreferencesService struct { repo port.PreferencesRepository logger *logging.Logger } // NewPreferencesService creates a new preferences service. func NewPreferencesService(repo port.PreferencesRepository, logger *logging.Logger) *PreferencesService { return &PreferencesService{ repo: repo, logger: logger.WithService("PreferencesService"), } } // GetPreferences returns preferences for a user. Returns defaults if none are stored. func (s *PreferencesService) GetPreferences(ctx context.Context, userID domain.UserID) (*domain.UserPreferences, error) { prefs, err := s.repo.Get(ctx, userID) if err != nil { return nil, err } if prefs != nil { return prefs, nil } // Return defaults for unknown users return &domain.UserPreferences{ UserID: userID, Preferences: domain.DefaultPreferences(), UpdatedAt: time.Now().UTC(), }, nil } // UpdateInput contains the partial preference data for an update. // Pointer fields distinguish "not provided" from zero values. type UpdateInput struct { Theme *string Language *string Notifications *NotificationsInput } // NotificationsInput contains partial notification settings. type NotificationsInput struct { Email *bool Push *bool Digest *string } // UpdatePreferences merges partial input with existing preferences and persists. func (s *PreferencesService) UpdatePreferences(ctx context.Context, userID domain.UserID, input UpdateInput) (*domain.UserPreferences, error) { // Fetch existing or start from defaults existing, err := s.repo.Get(ctx, userID) if err != nil { return nil, err } var merged domain.Preferences if existing != nil { merged = existing.Preferences } else { merged = domain.DefaultPreferences() } // Deep merge incoming fields if input.Theme != nil { merged.Theme = *input.Theme } if input.Language != nil { merged.Language = *input.Language } if input.Notifications != nil { if input.Notifications.Email != nil { merged.Notifications.Email = *input.Notifications.Email } if input.Notifications.Push != nil { merged.Notifications.Push = *input.Notifications.Push } if input.Notifications.Digest != nil { merged.Notifications.Digest = *input.Notifications.Digest } } // Validate merged result if err := merged.Validate(); err != nil { return nil, err } now := time.Now().UTC() prefs := &domain.UserPreferences{ UserID: userID, Preferences: merged, UpdatedAt: now, } if err := s.repo.Upsert(ctx, prefs); err != nil { return nil, err } s.logger.Info("preferences updated", "user_id", userID) return prefs, nil }