mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
Per-user notification_channels (ntfy + Gotify): every
insertUserNotification now fans out best-effort to enabled channels —
deployment/backup/security/agent alerts reach the user's phone via the
ntfy app with zero extra infrastructure.
- POST/GET/DELETE /notifications/channels + POST .../:id/test which
pushes a real probe so misconfiguration surfaces before alerts depend
on it (502 DELIVERY_FAILED on endpoint errors).
- ntfy: POST to endpoint (topic in path), Title/Tags headers, optional
Bearer token. Gotify: POST {endpoint}/message?token=...
- Delivery is best-effort by design — a dead relay never breaks the
producing flow; one shared 5s-timeout client.
- CLI `notifications channels list|add|test|remove`, MCP tools,
OpenAPI paths + NotificationChannel schema.
- Web settings "Push notifications" section and a Push channels card on
the mobile notifications tab (add/test/remove).
Verified live: channel CRUD, test-ping hit a recording endpoint with
correct headers/body, and a real deployment fanned out "Deployment
succeeded" through the channel end-to-end.
Generated with [Devin](https://devin.ai)
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
259 lines
6.7 KiB
Go
259 lines
6.7 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: notifications.sql
|
|
|
|
package sqlcdb
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const countUnreadNotificationsByUser = `-- name: CountUnreadNotificationsByUser :one
|
|
SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND read_at IS NULL
|
|
`
|
|
|
|
func (q *Queries) CountUnreadNotificationsByUser(ctx context.Context, userID uuid.UUID) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countUnreadNotificationsByUser, userID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createNotification = `-- name: CreateNotification :exec
|
|
INSERT INTO notifications (id, user_id, kind, title, body, resource_type, resource_id, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
`
|
|
|
|
type CreateNotificationParams struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Kind string `json:"kind"`
|
|
Title string `json:"title"`
|
|
Body sql.NullString `json:"body"`
|
|
ResourceType sql.NullString `json:"resource_type"`
|
|
ResourceID sql.NullString `json:"resource_id"`
|
|
CreatedAt sql.NullTime `json:"created_at"`
|
|
}
|
|
|
|
func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) error {
|
|
_, err := q.db.ExecContext(ctx, createNotification,
|
|
arg.ID,
|
|
arg.UserID,
|
|
arg.Kind,
|
|
arg.Title,
|
|
arg.Body,
|
|
arg.ResourceType,
|
|
arg.ResourceID,
|
|
arg.CreatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const deleteNotificationChannel = `-- name: DeleteNotificationChannel :execrows
|
|
DELETE FROM notification_channels WHERE id = $1 AND user_id = $2
|
|
`
|
|
|
|
type DeleteNotificationChannelParams struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteNotificationChannel(ctx context.Context, arg DeleteNotificationChannelParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, deleteNotificationChannel, arg.ID, arg.UserID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const insertNotificationChannel = `-- name: InsertNotificationChannel :one
|
|
INSERT INTO notification_channels (user_id, kind, endpoint, token)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, user_id, kind, endpoint, token, enabled, created_at, updated_at
|
|
`
|
|
|
|
type InsertNotificationChannelParams struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Kind string `json:"kind"`
|
|
Endpoint string `json:"endpoint"`
|
|
Token sql.NullString `json:"token"`
|
|
}
|
|
|
|
func (q *Queries) InsertNotificationChannel(ctx context.Context, arg InsertNotificationChannelParams) (NotificationChannel, error) {
|
|
row := q.db.QueryRowContext(ctx, insertNotificationChannel,
|
|
arg.UserID,
|
|
arg.Kind,
|
|
arg.Endpoint,
|
|
arg.Token,
|
|
)
|
|
var i NotificationChannel
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Kind,
|
|
&i.Endpoint,
|
|
&i.Token,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listAllNotificationChannelsByUser = `-- name: ListAllNotificationChannelsByUser :many
|
|
SELECT id, user_id, kind, endpoint, token, enabled, created_at, updated_at
|
|
FROM notification_channels
|
|
WHERE user_id = $1
|
|
ORDER BY created_at
|
|
`
|
|
|
|
func (q *Queries) ListAllNotificationChannelsByUser(ctx context.Context, userID uuid.UUID) ([]NotificationChannel, error) {
|
|
rows, err := q.db.QueryContext(ctx, listAllNotificationChannelsByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []NotificationChannel{}
|
|
for rows.Next() {
|
|
var i NotificationChannel
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Kind,
|
|
&i.Endpoint,
|
|
&i.Token,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listNotificationChannelsByUser = `-- name: ListNotificationChannelsByUser :many
|
|
SELECT id, user_id, kind, endpoint, token, enabled, created_at, updated_at
|
|
FROM notification_channels
|
|
WHERE user_id = $1 AND enabled
|
|
ORDER BY created_at
|
|
`
|
|
|
|
func (q *Queries) ListNotificationChannelsByUser(ctx context.Context, userID uuid.UUID) ([]NotificationChannel, error) {
|
|
rows, err := q.db.QueryContext(ctx, listNotificationChannelsByUser, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []NotificationChannel{}
|
|
for rows.Next() {
|
|
var i NotificationChannel
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Kind,
|
|
&i.Endpoint,
|
|
&i.Token,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listNotificationsByUser = `-- name: ListNotificationsByUser :many
|
|
SELECT id, user_id, kind, title, body, resource_type, resource_id, read_at, created_at
|
|
FROM notifications
|
|
WHERE user_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT $2
|
|
`
|
|
|
|
type ListNotificationsByUserParams struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Limit int32 `json:"limit"`
|
|
}
|
|
|
|
func (q *Queries) ListNotificationsByUser(ctx context.Context, arg ListNotificationsByUserParams) ([]Notification, error) {
|
|
rows, err := q.db.QueryContext(ctx, listNotificationsByUser, arg.UserID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Notification{}
|
|
for rows.Next() {
|
|
var i Notification
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.UserID,
|
|
&i.Kind,
|
|
&i.Title,
|
|
&i.Body,
|
|
&i.ResourceType,
|
|
&i.ResourceID,
|
|
&i.ReadAt,
|
|
&i.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markAllNotificationsReadByUser = `-- name: MarkAllNotificationsReadByUser :exec
|
|
UPDATE notifications SET read_at = $2 WHERE user_id = $1 AND read_at IS NULL
|
|
`
|
|
|
|
type MarkAllNotificationsReadByUserParams struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
ReadAt sql.NullTime `json:"read_at"`
|
|
}
|
|
|
|
func (q *Queries) MarkAllNotificationsReadByUser(ctx context.Context, arg MarkAllNotificationsReadByUserParams) error {
|
|
_, err := q.db.ExecContext(ctx, markAllNotificationsReadByUser, arg.UserID, arg.ReadAt)
|
|
return err
|
|
}
|
|
|
|
const markNotificationReadByIDAndUser = `-- name: MarkNotificationReadByIDAndUser :exec
|
|
UPDATE notifications SET read_at = $3 WHERE id = $1 AND user_id = $2 AND read_at IS NULL
|
|
`
|
|
|
|
type MarkNotificationReadByIDAndUserParams struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
ReadAt sql.NullTime `json:"read_at"`
|
|
}
|
|
|
|
func (q *Queries) MarkNotificationReadByIDAndUser(ctx context.Context, arg MarkNotificationReadByIDAndUserParams) error {
|
|
_, err := q.db.ExecContext(ctx, markNotificationReadByIDAndUser, arg.ID, arg.UserID, arg.ReadAt)
|
|
return err
|
|
}
|