package notifications import ( "context" "errors" "fmt" "time" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) // EmailSettings are the digest's two knobs, in admin Settings (rule 25). type EmailSettings struct { // SummaryHour is the local hour (0-23, in each user's own timezone) the // daily new-music summary goes out. SummaryHour int32 // BatchWindowMinutes is how long a batch stays open after its first item. BatchWindowMinutes int32 } const ( minBatchWindowMinutes = 15 maxBatchWindowMinutes = 1440 ) // DefaultEmailSettings mirrors migration 0074's column defaults. var DefaultEmailSettings = EmailSettings{SummaryHour: 9, BatchWindowMinutes: 60} // ErrEmailSettingOutOfRange is a value migration 0074's CHECKs would reject, // so the API answers 400 naming the field rather than a constraint violation. var ErrEmailSettingOutOfRange = errors.New("notification email setting out of range") // BatchWindow is BatchWindowMinutes as a duration. func (s EmailSettings) BatchWindow() time.Duration { return time.Duration(s.BatchWindowMinutes) * time.Minute } // LoadEmailSettings reads the settings. A failed read returns the defaults // with the error, so the digest keeps its shipped cadence rather than stopping. func LoadEmailSettings(ctx context.Context, q *dbq.Queries) (EmailSettings, error) { row, err := q.GetNotificationEmailSettings(ctx) if err != nil { return DefaultEmailSettings, fmt.Errorf("notification email settings: load: %w", err) } return EmailSettings{SummaryHour: row.SummaryHour, BatchWindowMinutes: row.BatchWindowMinutes}, nil } // SaveEmailSettings validates and writes the settings, returning them as saved. // The digest reads them on its next tick; nothing is cached. func SaveEmailSettings(ctx context.Context, q *dbq.Queries, in EmailSettings) (EmailSettings, error) { switch { case in.SummaryHour < 0 || in.SummaryHour > 23: return EmailSettings{}, fmt.Errorf("%w: summary_hour must be 0-23", ErrEmailSettingOutOfRange) case in.BatchWindowMinutes < minBatchWindowMinutes || in.BatchWindowMinutes > maxBatchWindowMinutes: return EmailSettings{}, fmt.Errorf("%w: batch_window_minutes must be %d-%d", ErrEmailSettingOutOfRange, minBatchWindowMinutes, maxBatchWindowMinutes) } row, err := q.UpdateNotificationEmailSettings(ctx, dbq.UpdateNotificationEmailSettingsParams{ SummaryHour: in.SummaryHour, BatchWindowMinutes: in.BatchWindowMinutes, }) if err != nil { return EmailSettings{}, fmt.Errorf("notification email settings: save: %w", err) } return EmailSettings{SummaryHour: row.SummaryHour, BatchWindowMinutes: row.BatchWindowMinutes}, nil }