You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user