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:
Niklas Ye
2026-10-12 21:00:40 +02:00
parent 6292ad6708
commit b114251f45
42 changed files with 2200 additions and 611 deletions
+18 -4
View File
@@ -8,10 +8,11 @@ type User struct {
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
// NtfyTopic is where this user's push notifications go. Nil means they get
// none of their own; incidents assigned to them fall back to the configured
// fallback topic instead.
NtfyTopic *string `json:"ntfy_topic,omitempty"`
// Channels are where this user's notifications go. Empty means they get none
// of their own; incidents assigned to them fall back to the configured
// fallback target instead. Only filled in for the user themselves and for an
// administrator, and the address only for the user themselves.
Channels []NotificationChannel `json:"channels,omitempty"`
// DisabledAt is when the account was taken out of use, or nil. A disabled
// user cannot authenticate by either credential, and keeps their name on
@@ -31,6 +32,19 @@ type User struct {
AdminSource string `json:"admin_source,omitempty"`
}
// NotificationChannel is one way of reaching a user: a provider type and an
// address that provider understands.
type NotificationChannel struct {
ID int64 `json:"id"`
Type string `json:"type"`
// Address is an ntfy topic, later an email address. Blank unless the caller is
// the owner: it can be a secret, and an administrator has no need of it.
Address string `json:"address,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
}
type APIKey struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`