You've already forked agentic-coding-workflow
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
// Package notify 内的 prefs.go 实现 per-user 通知偏好(notification_prefs 表)。
|
|
//
|
|
// 偏好控制 email / im 两条外发通道的开关与最低投递级别(min_severity)。in-app
|
|
// inbox(持久化 + SSE)始终投递,不受偏好影响。im_webhook_url 以密文落库
|
|
// (im_webhook_url_enc BYTEA,经 crypto.Encryptor 加密),repo 层只搬运密文,
|
|
// 解密由 IMNotifier 在发送时按需进行——避免明文 webhook URL 进入领域内存层。
|
|
package notify
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
|
notifysqlc "github.com/yan1h/agent-coding-workflow/internal/infra/notify/sqlc"
|
|
)
|
|
|
|
// Prefs 是一个用户的通知偏好领域形态。ImWebhookURLEnc 保持密文(BYTEA 原样),
|
|
// 解密延迟到 IMNotifier.Send。MinSeverity 取值与 Severity 枚举一致。
|
|
type Prefs struct {
|
|
UserID uuid.UUID
|
|
EmailEnabled bool
|
|
IMEnabled bool
|
|
ImWebhookURLEnc []byte
|
|
MinSeverity Severity
|
|
}
|
|
|
|
// DefaultPrefs 是用户尚未显式设置偏好时的兜底:email / im 均关闭,最低级别
|
|
// warning(与 notification_prefs.min_severity 列默认值一致)。
|
|
func DefaultPrefs(userID uuid.UUID) Prefs {
|
|
return Prefs{
|
|
UserID: userID,
|
|
EmailEnabled: false,
|
|
IMEnabled: false,
|
|
MinSeverity: SeverityWarning,
|
|
}
|
|
}
|
|
|
|
// PrefsRepository 抽象 notification_prefs 的读写。Get 在无记录时返回
|
|
// DefaultPrefs(不报 not-found),让上层无需区分 "未设置" 与 "全关"。
|
|
type PrefsRepository interface {
|
|
Get(ctx context.Context, userID uuid.UUID) (Prefs, error)
|
|
Upsert(ctx context.Context, p Prefs) error
|
|
}
|
|
|
|
// pgPrefsRepo 是 PrefsRepository 的 Postgres 实现。
|
|
type pgPrefsRepo struct {
|
|
q *notifysqlc.Queries
|
|
}
|
|
|
|
// NewPostgresPrefsRepository 用现有 pgxpool 构造偏好仓库。
|
|
func NewPostgresPrefsRepository(pool *pgxpool.Pool) PrefsRepository {
|
|
return &pgPrefsRepo{q: notifysqlc.New(pool)}
|
|
}
|
|
|
|
// Get 读取用户偏好。无记录时返回 DefaultPrefs(userID) 与 nil error。
|
|
func (r *pgPrefsRepo) Get(ctx context.Context, userID uuid.UUID) (Prefs, error) {
|
|
row, err := r.q.GetNotificationPrefs(ctx, toPgUUID(userID))
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return DefaultPrefs(userID), nil
|
|
}
|
|
return Prefs{}, errs.Wrap(err, errs.CodeInternal, "get notification prefs")
|
|
}
|
|
return Prefs{
|
|
UserID: fromPgUUID(row.UserID),
|
|
EmailEnabled: row.EmailEnabled,
|
|
IMEnabled: row.ImEnabled,
|
|
ImWebhookURLEnc: row.ImWebhookUrlEnc,
|
|
MinSeverity: Severity(row.MinSeverity),
|
|
}, nil
|
|
}
|
|
|
|
// Upsert 写入(或更新)用户偏好。ImWebhookURLEnc 为 nil 时写 NULL。
|
|
func (r *pgPrefsRepo) Upsert(ctx context.Context, p Prefs) error {
|
|
if err := r.q.UpsertNotificationPrefs(ctx, notifysqlc.UpsertNotificationPrefsParams{
|
|
UserID: toPgUUID(p.UserID),
|
|
EmailEnabled: p.EmailEnabled,
|
|
ImEnabled: p.IMEnabled,
|
|
ImWebhookUrlEnc: p.ImWebhookURLEnc,
|
|
MinSeverity: string(p.MinSeverity),
|
|
}); err != nil {
|
|
return errs.Wrap(err, errs.CodeInternal, "upsert notification prefs")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// severityRank 把 Severity 映射为可比较的等级序数,用于 min_severity 过滤。
|
|
// 未知值按最低(info)处理,确保 "至少投递" 而非误丢。
|
|
func severityRank(s Severity) int {
|
|
switch s {
|
|
case SeverityError:
|
|
return 2
|
|
case SeverityWarning:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// meetsMinSeverity 报告消息级别是否达到(>=)用户设定的最低投递级别。
|
|
func meetsMinSeverity(msg Severity, min Severity) bool {
|
|
return severityRank(msg) >= severityRank(min)
|
|
}
|