113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
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
|
|
}
|
|
}
|
|
}
|