Sync from /srv/compose/unified-media-manager
This commit is contained in:
212
internal/api/notifications.go
Normal file
212
internal/api/notifications.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TopherMayor/unified-media-manager/internal/service"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func listNotificationChannels(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
channels, err := svc.ListChannels(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to list notification channels", "error", err)
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to list channels"})
|
||||
}
|
||||
|
||||
if channels == nil {
|
||||
channels = []service.NotificationChannel{}
|
||||
}
|
||||
return c.JSON(http.StatusOK, channels)
|
||||
}
|
||||
}
|
||||
|
||||
type createNotificationChannelRequest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Config json.RawMessage `json:"config"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
EventTypes []string `json:"event_types"`
|
||||
}
|
||||
|
||||
func createNotificationChannel(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var req createNotificationChannelRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if len(name) < 3 || len(name) > 50 {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "name must be 3-50 characters"})
|
||||
}
|
||||
|
||||
if req.Type != "webhook" && req.Type != "telegram" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "type must be webhook or telegram"})
|
||||
}
|
||||
|
||||
if req.Config == nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "config is required"})
|
||||
}
|
||||
|
||||
id, err := svc.CreateChannel(ctx, name, req.Type, req.Config)
|
||||
if err != nil {
|
||||
slog.Error("failed to create notification channel", "error", err, "name", name, "type", req.Type)
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if len(req.EventTypes) > 0 {
|
||||
if subErr := svc.UpdateSubscriptions(ctx, id, req.EventTypes); subErr != nil {
|
||||
slog.Error("failed to set subscriptions", "error", subErr, "channel_id", id)
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, map[string]int64{"id": id})
|
||||
}
|
||||
}
|
||||
|
||||
type updateNotificationChannelRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Config json.RawMessage `json:"config,omitempty"`
|
||||
EventTypes []string `json:"event_types,omitempty"`
|
||||
}
|
||||
|
||||
func updateNotificationChannel(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
|
||||
}
|
||||
|
||||
var req updateNotificationChannelRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
if err := svc.UpdateChannel(ctx, id, req.Name, req.Enabled, req.Config); err != nil {
|
||||
slog.Error("failed to update notification channel", "error", err, "id", id)
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.EventTypes != nil {
|
||||
if subErr := svc.UpdateSubscriptions(ctx, id, req.EventTypes); subErr != nil {
|
||||
slog.Error("failed to update subscriptions", "error", subErr, "id", id)
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update subscriptions"})
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "updated"})
|
||||
}
|
||||
}
|
||||
|
||||
func deleteNotificationChannel(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
|
||||
}
|
||||
|
||||
if err := svc.DeleteChannel(ctx, id); err != nil {
|
||||
slog.Error("failed to delete notification channel", "error", err, "id", id)
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "deleted"})
|
||||
}
|
||||
}
|
||||
|
||||
func testNotificationChannel(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
|
||||
}
|
||||
|
||||
ch, err := svc.GetChannelWithConfig(ctx, id)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "channel not found"})
|
||||
}
|
||||
|
||||
var configMap map[string]interface{}
|
||||
json.Unmarshal(ch.Config, &configMap)
|
||||
|
||||
var deliverErr error
|
||||
switch ch.Type {
|
||||
case "webhook":
|
||||
webhookURL, _ := configMap["url"].(string)
|
||||
deliverErr = svc.DeliverWebhook(ctx, webhookURL, map[string]interface{}{
|
||||
"test": true,
|
||||
"message": "Test notification from UMM",
|
||||
})
|
||||
|
||||
case "telegram":
|
||||
botToken, _ := configMap["bot_token"].(string)
|
||||
chatID, _ := configMap["chat_id"].(string)
|
||||
deliverErr = svc.DeliverTelegram(ctx, botToken, chatID, "🔔 Test notification from UMM")
|
||||
}
|
||||
|
||||
if deliverErr != nil {
|
||||
slog.Error("notification test failed", "channel", ch.Name, "type", ch.Type)
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"success": false,
|
||||
"error": "delivery failed",
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func listNotificationQueue(svc *service.NotificationService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
status := c.QueryParam("status")
|
||||
page, pageSize := service.ParsePagination(c.QueryParam("page"), c.QueryParam("page_size"))
|
||||
|
||||
entries, total, err := svc.ListQueue(ctx, status, page, pageSize)
|
||||
if err != nil {
|
||||
slog.Error("failed to list notification queue", "error", err)
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to list queue"})
|
||||
}
|
||||
|
||||
if entries == nil {
|
||||
entries = []service.QueueEntry{}
|
||||
}
|
||||
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
return c.JSON(http.StatusOK, paginatedResponse{
|
||||
Data: entries,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user