package domain import ( "regexp" "time" ) // UserID is a strongly-typed identifier for users. type UserID string func (id UserID) String() string { return string(id) } func (id UserID) IsZero() bool { return id == "" } // Allowed theme values. var allowedThemes = map[string]bool{ "light": true, "dark": true, "system": true, } // Allowed digest values. var allowedDigests = map[string]bool{ "daily": true, "weekly": true, "never": true, } var languageRegex = regexp.MustCompile(`^[a-z]{2}$`) // NotificationSettings holds notification preferences. type NotificationSettings struct { Email bool Push bool Digest string } // Preferences holds all user preferences. type Preferences struct { UserID UserID Theme string Language string Notifications NotificationSettings UpdatedAt time.Time } // NewDefaultPreferences returns preferences with all defaults applied. func NewDefaultPreferences(userID UserID) *Preferences { return &Preferences{ UserID: userID, Theme: "system", Language: "en", Notifications: NotificationSettings{ Email: true, Push: true, Digest: "weekly", }, UpdatedAt: time.Now().UTC(), } } // Validate checks that all field values are within allowed sets. func (p *Preferences) Validate() error { if !allowedThemes[p.Theme] { return ErrInvalidTheme } if !languageRegex.MatchString(p.Language) { return ErrInvalidLanguage } if !allowedDigests[p.Notifications.Digest] { return ErrInvalidDigest } return nil } // NotificationSettingsUpdate uses pointer fields to distinguish provided vs absent. type NotificationSettingsUpdate struct { Email *bool Push *bool Digest *string } // PreferencesUpdate uses pointer fields to distinguish provided vs absent. type PreferencesUpdate struct { Theme *string Language *string Notifications *NotificationSettingsUpdate } // MergeFrom applies a shallow merge: only overwrites fields where the update pointer is non-nil. // For Notifications, individual sub-fields are merged when provided. func (p *Preferences) MergeFrom(incoming *PreferencesUpdate) { if incoming == nil { return } if incoming.Theme != nil { p.Theme = *incoming.Theme } if incoming.Language != nil { p.Language = *incoming.Language } if incoming.Notifications != nil { if incoming.Notifications.Email != nil { p.Notifications.Email = *incoming.Notifications.Email } if incoming.Notifications.Push != nil { p.Notifications.Push = *incoming.Notifications.Push } if incoming.Notifications.Digest != nil { p.Notifications.Digest = *incoming.Notifications.Digest } } }