feat: notification enhancement
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user