package api import ( "context" "encoding/json" "time" "github.com/ponzischeme89/memby/server/internal/cache" "github.com/ponzischeme89/memby/server/internal/store" ) const notificationPreferencesTTL = 5 * time.Minute func notificationPreferencesKey(userID string) string { return cache.UserKey(userID, "notification-preferences:v2") } // notificationPreferencesFor keeps the ten-second status poll off Postgres. Redis also // shares the answer if the gateway is ever run with more than one replica; writes replace // it immediately, while the short TTL still picks up an operator's direct database edit. func (s *Server) notificationPreferencesFor(ctx context.Context, userID string) (store.NotificationPreferences, error) { defaults := store.DefaultNotificationPreferences() if userID == "" || s.store == nil { return defaults, nil } if s.cache != nil { if raw, err := s.cache.Get(ctx, notificationPreferencesKey(userID)); err == nil { prefs := defaults if json.Unmarshal(raw, &prefs) == nil { return prefs, nil } } } prefs, err := s.store.NotificationPreferences(ctx, userID) if err != nil { return defaults, err } s.cacheNotificationPreferences(ctx, userID, prefs) return prefs, nil } func (s *Server) cacheNotificationPreferences(ctx context.Context, userID string, prefs store.NotificationPreferences) { if s.cache == nil || userID == "" { return } if raw, err := json.Marshal(prefs); err == nil { _ = s.cache.Set(ctx, notificationPreferencesKey(userID), raw, notificationPreferencesTTL) } } func (s *Server) saveNotificationPreferences(ctx context.Context, userID string, prefs store.NotificationPreferences) error { if err := s.store.SetNotificationPreferences(ctx, userID, prefs); err != nil { return err } s.cacheNotificationPreferences(ctx, userID, prefs) return nil } func filterClientAlerts(alerts []clientAlert, prefs store.NotificationPreferences) []clientAlert { if !prefs.Enabled { return []clientAlert{} } filtered := make([]clientAlert, 0, len(alerts)) for _, alert := range alerts { allowed := true switch alert.Kind { case alertKindSonarrAired: allowed = prefs.SonarrAlerts case alertKindRadarrImport: allowed = prefs.RadarrAlerts case alertKindLibrarySync: allowed = prefs.LibraryAlerts case alertKindServerDown, alertKindServerUp, alertKindDeploying: allowed = prefs.SystemAlerts } if allowed { filtered = append(filtered, alert) } } return filtered } func filterStoredNotifications(notifications []store.UserNotification, prefs store.NotificationPreferences) []store.UserNotification { if !prefs.Enabled { return []store.UserNotification{} } if prefs.SonarrAlerts && prefs.WatchTimeDigest { return notifications } filtered := make([]store.UserNotification, 0, len(notifications)) for _, notification := range notifications { switch notification.Kind { case "show-return", "show-added", "show-cancelled", "auto-follow": if !prefs.SonarrAlerts { continue } // A summary already sitting in somebody's list is withdrawn the moment they turn // these off, rather than waiting to be dismissed one at a time: switching a weekly // notice off is a statement about the ones already there as much as the next one. case watchTimeWeeklyKind, watchTimeMonthlyKind: if !prefs.WatchTimeDigest { continue } } filtered = append(filtered, notification) } return filtered }