Files
memby/server/internal/api/notification_preferences.go
T
2026-08-18 08:41:48 +12:00

109 lines
3.3 KiB
Go

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
}