Add notification channels and a provider abstraction
Users get a notification_channels table (replacing users.ntfy_topic); pages fan
out to every enabled channel through a notify.Provider registry, with ntfy as
the first provider. Fallbacks are typed {type, address}. Schema edited in place:
existing databases must be recreated.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
+12
-9
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.ryuvia.com/niklas/terdut-server/internal/models"
|
||||
"git.ryuvia.com/niklas/terdut-server/internal/notify"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@@ -283,8 +284,9 @@ type memberStatus struct {
|
||||
// NextShift is the first day after today the rota has them (YYYY-MM-DD).
|
||||
NextShift *string `json:"next_shift,omitempty"`
|
||||
|
||||
// Pageable is whether they have an ntfy topic and an enabled account — the
|
||||
// conditions pageLevel and the notifier skip on. Never the topic itself.
|
||||
// Pageable is whether they have an enabled channel of a type this server can
|
||||
// deliver, and an enabled account — the conditions pageLevel and the notifier
|
||||
// skip on. Never the channel's address.
|
||||
Pageable bool `json:"pageable"`
|
||||
Problem string `json:"problem,omitempty"`
|
||||
|
||||
@@ -294,7 +296,7 @@ type memberStatus struct {
|
||||
|
||||
// handleListTeamMembers names everybody in a team, with their status. Visible to
|
||||
// any member: you can see who else is on the rota you are on.
|
||||
func handleListTeamMembers(db *sql.DB) http.HandlerFunc {
|
||||
func handleListTeamMembers(db *sql.DB, providers *notify.Registry) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
teamID, ok := teamParam(w, r)
|
||||
if !ok {
|
||||
@@ -306,7 +308,8 @@ func handleListTeamMembers(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
rows, err := db.QueryContext(r.Context(), `
|
||||
SELECT m.team_id, m.user_id, u.username, m.role, m.joined_at, m.source,
|
||||
u.ntfy_topic IS NOT NULL AND u.ntfy_topic <> '',
|
||||
EXISTS (SELECT 1 FROM notification_channels c
|
||||
WHERE c.user_id = u.id AND c.enabled AND c.type = ANY($3::text[])),
|
||||
u.disabled_at IS NOT NULL,
|
||||
GREATEST(
|
||||
COALESCE((SELECT MAX(last_seen_at) FROM sessions WHERE user_id = u.id), 0),
|
||||
@@ -318,7 +321,7 @@ func handleListTeamMembers(db *sql.DB) http.HandlerFunc {
|
||||
FROM team_members m
|
||||
JOIN users u ON u.id = m.user_id
|
||||
WHERE m.team_id = $1
|
||||
ORDER BY u.username`, teamID, todayUTC())
|
||||
ORDER BY u.username`, teamID, todayUTC(), providerTypes(providers))
|
||||
if err != nil {
|
||||
serverError(w, r, err)
|
||||
return
|
||||
@@ -329,9 +332,9 @@ func handleListTeamMembers(db *sql.DB) http.HandlerFunc {
|
||||
for rows.Next() {
|
||||
var m memberStatus
|
||||
var joined, lastActive int64
|
||||
var hasTopic, disabled bool
|
||||
var hasChannel, disabled bool
|
||||
if err := rows.Scan(&m.TeamID, &m.UserID, &m.Username, &m.Role, &joined, &m.Source,
|
||||
&hasTopic, &disabled, &lastActive, &m.OnCall, &m.NextShift); err != nil {
|
||||
&hasChannel, &disabled, &lastActive, &m.OnCall, &m.NextShift); err != nil {
|
||||
serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
@@ -343,8 +346,8 @@ func handleListTeamMembers(db *sql.DB) http.HandlerFunc {
|
||||
switch {
|
||||
case disabled:
|
||||
m.Problem = "account is disabled"
|
||||
case !hasTopic:
|
||||
m.Problem = "has no ntfy topic"
|
||||
case !hasChannel:
|
||||
m.Problem = "has no notification channel"
|
||||
}
|
||||
m.Pageable = m.Problem == ""
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user