Files
spsays-bff/internal/repository/notification_prefs_repo.go
T

100 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"fmt"
)
// Notification delivery channels (notification_prefs.channel values).
const (
ChannelInApp = "in_app"
ChannelEmail = "email"
ChannelPush = "push"
)
// PrefKey is the lookup key for a (kind, channel) cell in a DisabledSet.
func PrefKey(kind, channel string) string { return kind + "/" + channel }
// NotificationPrefsRepo stores per-user notification opt-outs: one row per
// (kind, channel) the user has turned OFF. Absence of a row means enabled,
// so the table only ever holds overrides and new kinds/channels default on.
type NotificationPrefsRepo struct{ pg *Postgres }
func NewNotificationPrefsRepo(pg *Postgres) *NotificationPrefsRepo {
return &NotificationPrefsRepo{pg: pg}
}
// DisabledSet returns the user's opt-outs keyed by PrefKey(kind, channel).
func (r *NotificationPrefsRepo) DisabledSet(ctx context.Context, userID string) (map[string]struct{}, error) {
rows, err := r.pg.Pool.Query(ctx, `
SELECT kind, channel FROM notification_prefs
WHERE user_id = $1 AND enabled = FALSE`, userID)
if err != nil {
return nil, fmt.Errorf("list notification prefs: %w", err)
}
defer rows.Close()
out := map[string]struct{}{}
for rows.Next() {
var kind, channel string
if err := rows.Scan(&kind, &channel); err != nil {
return nil, fmt.Errorf("scan notification pref: %w", err)
}
out[PrefKey(kind, channel)] = struct{}{}
}
return out, rows.Err()
}
// Disable records opt-out rows for every kind × channel pair.
func (r *NotificationPrefsRepo) Disable(ctx context.Context, userID string, kinds, channels []string) error {
_, err := r.pg.Pool.Exec(ctx, `
INSERT INTO notification_prefs (user_id, kind, channel, enabled)
SELECT $1, k, c, FALSE
FROM unnest($2::text[]) AS k, unnest($3::text[]) AS c
ON CONFLICT (user_id, kind, channel)
DO UPDATE SET enabled = FALSE, updated_at = now()`,
userID, kinds, channels)
if err != nil {
return fmt.Errorf("disable notification prefs: %w", err)
}
return nil
}
// Enable removes opt-out rows (absence = enabled) for every kind × channel
// pair.
func (r *NotificationPrefsRepo) Enable(ctx context.Context, userID string, kinds, channels []string) error {
_, err := r.pg.Pool.Exec(ctx, `
DELETE FROM notification_prefs
WHERE user_id = $1 AND kind = ANY($2::text[]) AND channel = ANY($3::text[])`,
userID, kinds, channels)
if err != nil {
return fmt.Errorf("enable notification prefs: %w", err)
}
return nil
}
// SyncLegacyColumns recomputes users.like_emails_enabled and
// users.sadhna_reminders_enabled from the prefs table, so an old frontend
// (or a rollback) reading the legacy columns stays correct. Call after any
// prefs write; drop together with the columns once fully rolled out.
func (r *NotificationPrefsRepo) SyncLegacyColumns(ctx context.Context, userID string) error {
_, err := r.pg.Pool.Exec(ctx, `
UPDATE users SET
like_emails_enabled = NOT EXISTS (
SELECT 1 FROM notification_prefs np
WHERE np.user_id = users.id
AND np.kind = 'post_like' AND np.channel = 'email'
AND np.enabled = FALSE),
-- The legacy reminder toggle was all-or-nothing: it reads as OFF
-- only when every reminder kind × channel cell is opted out.
sadhna_reminders_enabled = (
SELECT count(*) FROM notification_prefs np
WHERE np.user_id = users.id
AND np.kind IN ('chant_reminder', 'quote_reminder', 'chant_incomplete_reminder')
AND np.enabled = FALSE) < 9
WHERE id = $1`, userID)
if err != nil {
return fmt.Errorf("sync legacy notification prefs: %w", err)
}
return nil
}