Files
minstrel/internal/notifications/email_settings.go
T
bvandeusenandClaude Opus 5.5 d191a28217 docs: server comments say what the code does, not how it came to be (#6277)
Comment blocks in cmd/minstrel and 30 smaller internal packages, rewritten
to rule 32's standard with each citation read in context: task, milestone
and rule ids go, and so do the stories they were attached to ("used to",
"Before #N", measurements from one install, quotes, pointers to Fabled
Librarian's records and to docs/superpowers files that are not in the
repo). Where a story wrapped a real constraint, the constraint stays as a
present-tense fact.

Comments that had gone stale are corrected:
- taste.Config: the tuned values are overlaid by internal/recsettings;
  it is no longer "a later follow-up".
- StorageConfig: the data dir holds art, caches and the stream secret,
  not only playlist covers.
- the Subsonic album list and getUser comments describe what they do
  today rather than a milestone that would change it.

Checked: the non-comment token stream of every changed file matches
HEAD, and gofmt -l is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:15:05 -04:00

67 lines
2.6 KiB
Go

package notifications
import (
"context"
"errors"
"fmt"
"time"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// EmailSettings are the digest's two knobs, edited in admin Settings.
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
}