213 lines
6.7 KiB
Go
213 lines
6.7 KiB
Go
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,
|
|
})
|
|
}
|
|
}
|