Files
ackify-ce/backend/internal/presentation/api/admin/webhooks_handler.go
Benjamin ab6cdbb383 refactor(go): rename module to github.com/btouchard/ackify-ce/backend
Enable importing backend packages in SaaS project by aligning module
path with its location in the repository.
2025-12-08 19:01:28 +01:00

150 lines
5.3 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-or-later
package admin
import (
"context"
"encoding/json"
"net/http"
"strconv"
"github.com/btouchard/ackify-ce/backend/internal/domain/models"
"github.com/btouchard/ackify-ce/backend/internal/presentation/api/shared"
"github.com/go-chi/chi/v5"
)
// webhookService defines webhook management operations
type webhookService interface {
CreateWebhook(ctx context.Context, input models.WebhookInput) (*models.Webhook, error)
UpdateWebhook(ctx context.Context, id int64, input models.WebhookInput) (*models.Webhook, error)
SetWebhookActive(ctx context.Context, id int64, active bool) error
DeleteWebhook(ctx context.Context, id int64) error
GetWebhookByID(ctx context.Context, id int64) (*models.Webhook, error)
ListWebhooks(ctx context.Context, limit, offset int) ([]*models.Webhook, error)
ListDeliveries(ctx context.Context, webhookID int64, limit, offset int) ([]*models.WebhookDelivery, error)
}
// WebhooksHandler groups operations on webhooks
type WebhooksHandler struct {
service webhookService
}
func NewWebhooksHandler(service webhookService) *WebhooksHandler {
return &WebhooksHandler{service: service}
}
type CreateWebhookRequest struct {
Title string `json:"title"`
TargetURL string `json:"targetUrl"`
Secret string `json:"secret"`
Active bool `json:"active"`
Events []string `json:"events"`
Headers map[string]string `json:"headers,omitempty"`
Description string `json:"description,omitempty"`
}
func (h *WebhooksHandler) HandleCreateWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req CreateWebhookRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
shared.WriteError(w, http.StatusBadRequest, shared.ErrCodeBadRequest, "Invalid request body", nil)
return
}
if req.Title == "" || req.TargetURL == "" || req.Secret == "" || len(req.Events) == 0 {
shared.WriteError(w, http.StatusBadRequest, shared.ErrCodeBadRequest, "title, targetUrl, secret and events are required", nil)
return
}
user, _ := shared.GetUserFromContext(ctx)
input := models.WebhookInput{Title: req.Title, TargetURL: req.TargetURL, Secret: req.Secret, Active: req.Active, Events: req.Events, Headers: req.Headers, Description: req.Description}
if user != nil {
input.CreatedBy = user.Email
}
wh, err := h.service.CreateWebhook(ctx, input)
if err != nil {
shared.WriteInternalError(w)
return
}
shared.WriteJSON(w, http.StatusCreated, wh)
}
func (h *WebhooksHandler) HandleListWebhooks(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
limit := 100
offset := 0
list, err := h.service.ListWebhooks(ctx, limit, offset)
if err != nil {
shared.WriteInternalError(w)
return
}
meta := map[string]interface{}{"total": len(list), "limit": limit, "offset": offset}
shared.WriteJSONWithMeta(w, http.StatusOK, list, meta)
}
func (h *WebhooksHandler) HandleGetWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
wh, err := h.service.GetWebhookByID(ctx, id)
if err != nil {
shared.WriteError(w, http.StatusNotFound, shared.ErrCodeNotFound, "Webhook not found", nil)
return
}
shared.WriteJSON(w, http.StatusOK, wh)
}
func (h *WebhooksHandler) HandleUpdateWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var req CreateWebhookRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
shared.WriteError(w, http.StatusBadRequest, shared.ErrCodeBadRequest, "Invalid request body", nil)
return
}
// For updates, allow empty secret to keep current value
if req.Title == "" || req.TargetURL == "" || len(req.Events) == 0 {
shared.WriteError(w, http.StatusBadRequest, shared.ErrCodeBadRequest, "title, targetUrl and events are required", nil)
return
}
input := models.WebhookInput{Title: req.Title, TargetURL: req.TargetURL, Secret: req.Secret, Active: req.Active, Events: req.Events, Headers: req.Headers, Description: req.Description}
wh, err := h.service.UpdateWebhook(ctx, id, input)
if err != nil {
shared.WriteInternalError(w)
return
}
shared.WriteJSON(w, http.StatusOK, wh)
}
func (h *WebhooksHandler) HandleToggleWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
enable := chi.URLParam(r, "action") == "enable"
if err := h.service.SetWebhookActive(ctx, id, enable); err != nil {
shared.WriteInternalError(w)
return
}
status := "disabled"
if enable {
status = "enabled"
}
shared.WriteJSON(w, http.StatusOK, map[string]string{"message": "Webhook " + status})
}
func (h *WebhooksHandler) HandleDeleteWebhook(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err := h.service.DeleteWebhook(ctx, id); err != nil {
shared.WriteInternalError(w)
return
}
shared.WriteJSON(w, http.StatusOK, map[string]string{"message": "Webhook deleted"})
}
func (h *WebhooksHandler) HandleListDeliveries(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
deliveries, err := h.service.ListDeliveries(ctx, id, 100, 0)
if err != nil {
shared.WriteInternalError(w)
return
}
shared.WriteJSON(w, http.StatusOK, deliveries)
}