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, }) } }