notification: add API endpoints for channels, preferences, history, and acknowledgement
New endpoints under /api/notifications: - CRUD for notification channels (email, webhook, UnifiedPush) - CRUD for notification preferences (global, per-domain, per-service) - Notification history listing - Test notification endpoint Acknowledgement endpoints added to scoped checker routes: - POST /api/domains/:domain/checkers/:checkerId/acknowledge - DELETE /api/domains/:domain/checkers/:checkerId/acknowledge Thread NotificationController through route declarations for scoped checker routes (domain and service level). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d9326c4689
commit
e41ea2fb98
8 changed files with 637 additions and 3 deletions
514
internal/api/controller/notification.go
Normal file
514
internal/api/controller/notification.go
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2026 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/api/middleware"
|
||||
notifUC "git.happydns.org/happyDomain/internal/usecase/notification"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
// NotificationController handles notification-related API endpoints.
|
||||
type NotificationController struct {
|
||||
dispatcher *notifUC.Dispatcher
|
||||
channelStore notifUC.NotificationChannelStorage
|
||||
prefStore notifUC.NotificationPreferenceStorage
|
||||
recordStore notifUC.NotificationRecordStorage
|
||||
}
|
||||
|
||||
// NewNotificationController creates a new NotificationController.
|
||||
func NewNotificationController(
|
||||
dispatcher *notifUC.Dispatcher,
|
||||
channelStore notifUC.NotificationChannelStorage,
|
||||
prefStore notifUC.NotificationPreferenceStorage,
|
||||
recordStore notifUC.NotificationRecordStorage,
|
||||
) *NotificationController {
|
||||
return &NotificationController{
|
||||
dispatcher: dispatcher,
|
||||
channelStore: channelStore,
|
||||
prefStore: prefStore,
|
||||
recordStore: recordStore,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Channel CRUD ---
|
||||
|
||||
// ListChannels returns all notification channels for the authenticated user.
|
||||
//
|
||||
// @Summary List notification channels
|
||||
// @Tags notifications
|
||||
// @Produce json
|
||||
// @Success 200 {array} happydns.NotificationChannel
|
||||
// @Router /notifications/channels [get]
|
||||
func (nc *NotificationController) ListChannels(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
channels, err := nc.channelStore.ListChannelsByUser(user.Id)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
if channels == nil {
|
||||
channels = []*happydns.NotificationChannel{}
|
||||
}
|
||||
c.JSON(http.StatusOK, channels)
|
||||
}
|
||||
|
||||
// CreateChannel creates a new notification channel.
|
||||
//
|
||||
// @Summary Create a notification channel
|
||||
// @Tags notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body happydns.NotificationChannel true "Channel configuration"
|
||||
// @Success 201 {object} happydns.NotificationChannel
|
||||
// @Router /notifications/channels [post]
|
||||
func (nc *NotificationController) CreateChannel(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
var ch happydns.NotificationChannel
|
||||
if err := c.ShouldBindJSON(&ch); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ch.UserId = user.Id
|
||||
|
||||
if err := nc.channelStore.CreateChannel(&ch); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, ch)
|
||||
}
|
||||
|
||||
// GetChannel returns a specific notification channel.
|
||||
//
|
||||
// @Summary Get a notification channel
|
||||
// @Tags notifications
|
||||
// @Produce json
|
||||
// @Param channelId path string true "Channel ID"
|
||||
// @Success 200 {object} happydns.NotificationChannel
|
||||
// @Router /notifications/channels/{channelId} [get]
|
||||
func (nc *NotificationController) GetChannel(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
channelId, err := happydns.NewIdentifierFromString(c.Param("channelId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid channel ID"})
|
||||
return
|
||||
}
|
||||
|
||||
ch, err := nc.channelStore.GetChannel(channelId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !ch.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ch)
|
||||
}
|
||||
|
||||
// UpdateChannel updates a notification channel.
|
||||
//
|
||||
// @Summary Update a notification channel
|
||||
// @Tags notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param channelId path string true "Channel ID"
|
||||
// @Param body body happydns.NotificationChannel true "Channel configuration"
|
||||
// @Success 200 {object} happydns.NotificationChannel
|
||||
// @Router /notifications/channels/{channelId} [put]
|
||||
func (nc *NotificationController) UpdateChannel(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
channelId, err := happydns.NewIdentifierFromString(c.Param("channelId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid channel ID"})
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := nc.channelStore.GetChannel(channelId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !existing.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
var ch happydns.NotificationChannel
|
||||
if err := c.ShouldBindJSON(&ch); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ch.Id = channelId
|
||||
ch.UserId = user.Id
|
||||
|
||||
if err := nc.channelStore.UpdateChannel(&ch); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ch)
|
||||
}
|
||||
|
||||
// DeleteChannel deletes a notification channel.
|
||||
//
|
||||
// @Summary Delete a notification channel
|
||||
// @Tags notifications
|
||||
// @Param channelId path string true "Channel ID"
|
||||
// @Success 204
|
||||
// @Router /notifications/channels/{channelId} [delete]
|
||||
func (nc *NotificationController) DeleteChannel(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
channelId, err := happydns.NewIdentifierFromString(c.Param("channelId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid channel ID"})
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := nc.channelStore.GetChannel(channelId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !existing.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := nc.channelStore.DeleteChannel(channelId); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// TestChannel sends a test notification through a channel.
|
||||
//
|
||||
// @Summary Send a test notification
|
||||
// @Tags notifications
|
||||
// @Param channelId path string true "Channel ID"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Router /notifications/channels/{channelId}/test [post]
|
||||
func (nc *NotificationController) TestChannel(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
channelId, err := happydns.NewIdentifierFromString(c.Param("channelId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid channel ID"})
|
||||
return
|
||||
}
|
||||
|
||||
ch, err := nc.channelStore.GetChannel(channelId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !ch.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := nc.dispatcher.SendTestNotification(ch, user); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Test notification sent"})
|
||||
}
|
||||
|
||||
// --- Preference CRUD ---
|
||||
|
||||
// ListPreferences returns all notification preferences for the authenticated user.
|
||||
//
|
||||
// @Summary List notification preferences
|
||||
// @Tags notifications
|
||||
// @Produce json
|
||||
// @Success 200 {array} happydns.NotificationPreference
|
||||
// @Router /notifications/preferences [get]
|
||||
func (nc *NotificationController) ListPreferences(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
prefs, err := nc.prefStore.ListPreferencesByUser(user.Id)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
if prefs == nil {
|
||||
prefs = []*happydns.NotificationPreference{}
|
||||
}
|
||||
c.JSON(http.StatusOK, prefs)
|
||||
}
|
||||
|
||||
// CreatePreference creates a new notification preference.
|
||||
//
|
||||
// @Summary Create a notification preference
|
||||
// @Tags notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body happydns.NotificationPreference true "Preference configuration"
|
||||
// @Success 201 {object} happydns.NotificationPreference
|
||||
// @Router /notifications/preferences [post]
|
||||
func (nc *NotificationController) CreatePreference(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
var pref happydns.NotificationPreference
|
||||
if err := c.ShouldBindJSON(&pref); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
pref.UserId = user.Id
|
||||
|
||||
if err := nc.prefStore.CreatePreference(&pref); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, pref)
|
||||
}
|
||||
|
||||
// GetPreference returns a specific notification preference.
|
||||
//
|
||||
// @Summary Get a notification preference
|
||||
// @Tags notifications
|
||||
// @Produce json
|
||||
// @Param prefId path string true "Preference ID"
|
||||
// @Success 200 {object} happydns.NotificationPreference
|
||||
// @Router /notifications/preferences/{prefId} [get]
|
||||
func (nc *NotificationController) GetPreference(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
prefId, err := happydns.NewIdentifierFromString(c.Param("prefId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid preference ID"})
|
||||
return
|
||||
}
|
||||
|
||||
pref, err := nc.prefStore.GetPreference(prefId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Preference not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !pref.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, pref)
|
||||
}
|
||||
|
||||
// UpdatePreference updates a notification preference.
|
||||
//
|
||||
// @Summary Update a notification preference
|
||||
// @Tags notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param prefId path string true "Preference ID"
|
||||
// @Param body body happydns.NotificationPreference true "Preference configuration"
|
||||
// @Success 200 {object} happydns.NotificationPreference
|
||||
// @Router /notifications/preferences/{prefId} [put]
|
||||
func (nc *NotificationController) UpdatePreference(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
prefId, err := happydns.NewIdentifierFromString(c.Param("prefId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid preference ID"})
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := nc.prefStore.GetPreference(prefId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Preference not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !existing.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
var pref happydns.NotificationPreference
|
||||
if err := c.ShouldBindJSON(&pref); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
pref.Id = prefId
|
||||
pref.UserId = user.Id
|
||||
|
||||
if err := nc.prefStore.UpdatePreference(&pref); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, pref)
|
||||
}
|
||||
|
||||
// DeletePreference deletes a notification preference.
|
||||
//
|
||||
// @Summary Delete a notification preference
|
||||
// @Tags notifications
|
||||
// @Param prefId path string true "Preference ID"
|
||||
// @Success 204
|
||||
// @Router /notifications/preferences/{prefId} [delete]
|
||||
func (nc *NotificationController) DeletePreference(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
prefId, err := happydns.NewIdentifierFromString(c.Param("prefId"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid preference ID"})
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := nc.prefStore.GetPreference(prefId)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Preference not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !existing.UserId.Equals(user.Id) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Access denied"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := nc.prefStore.DeletePreference(prefId); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// --- History ---
|
||||
|
||||
// ListHistory returns recent notification records for the authenticated user.
|
||||
//
|
||||
// @Summary List notification history
|
||||
// @Tags notifications
|
||||
// @Produce json
|
||||
// @Param limit query int false "Maximum number of records" default(50)
|
||||
// @Success 200 {array} happydns.NotificationRecord
|
||||
// @Router /notifications/history [get]
|
||||
func (nc *NotificationController) ListHistory(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
|
||||
limit := 50
|
||||
if l := c.Query("limit"); l != "" {
|
||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
records, err := nc.recordStore.ListRecordsByUser(user.Id, limit)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
if records == nil {
|
||||
records = []*happydns.NotificationRecord{}
|
||||
}
|
||||
c.JSON(http.StatusOK, records)
|
||||
}
|
||||
|
||||
// --- Acknowledgement ---
|
||||
|
||||
// AcknowledgeIssue marks a checker issue as acknowledged.
|
||||
//
|
||||
// @Summary Acknowledge a checker issue
|
||||
// @Tags checkers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param domain path string true "Domain identifier"
|
||||
// @Param checkerId path string true "Checker ID"
|
||||
// @Param body body happydns.AcknowledgeRequest true "Acknowledgement"
|
||||
// @Success 200 {object} happydns.NotificationState
|
||||
// @Router /domains/{domain}/checkers/{checkerId}/acknowledge [post]
|
||||
func (nc *NotificationController) AcknowledgeIssue(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
target := targetFromContext(c)
|
||||
checkerID := c.Param("checkerId")
|
||||
|
||||
var req happydns.AcknowledgeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// Body is optional for acknowledgement.
|
||||
req = happydns.AcknowledgeRequest{}
|
||||
}
|
||||
|
||||
if err := nc.dispatcher.AcknowledgeIssue(user.Id, checkerID, target, user.Email, req.Annotation); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
state, err := nc.dispatcher.GetState(user.Id, checkerID, target)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, state)
|
||||
}
|
||||
|
||||
// ClearAcknowledgement removes an acknowledgement from a checker issue.
|
||||
//
|
||||
// @Summary Clear acknowledgement
|
||||
// @Tags checkers
|
||||
// @Produce json
|
||||
// @Param domain path string true "Domain identifier"
|
||||
// @Param checkerId path string true "Checker ID"
|
||||
// @Success 200 {object} happydns.NotificationState
|
||||
// @Router /domains/{domain}/checkers/{checkerId}/acknowledge [delete]
|
||||
func (nc *NotificationController) ClearAcknowledgement(c *gin.Context) {
|
||||
user := middleware.MyUser(c)
|
||||
target := targetFromContext(c)
|
||||
checkerID := c.Param("checkerId")
|
||||
|
||||
if err := nc.dispatcher.ClearAcknowledgement(user.Id, checkerID, target); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
state, err := nc.dispatcher.GetState(user.Id, checkerID, target)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, state)
|
||||
}
|
||||
|
|
@ -61,7 +61,8 @@ func DeclareCheckerRoutes(
|
|||
|
||||
// DeclareScopedCheckerRoutes registers checker routes scoped to a domain or service.
|
||||
// Called for both /api/domains/:domain/checkers and .../services/:serviceid/checkers.
|
||||
func DeclareScopedCheckerRoutes(scopedRouter *gin.RouterGroup, cc *controller.CheckerController) {
|
||||
// nc may be nil if the notification system is not configured.
|
||||
func DeclareScopedCheckerRoutes(scopedRouter *gin.RouterGroup, cc *controller.CheckerController, nc *controller.NotificationController) {
|
||||
checkers := scopedRouter.Group("/checkers")
|
||||
checkers.GET("", cc.ListAvailableChecks)
|
||||
checkers.GET("/metrics", cc.GetDomainMetrics)
|
||||
|
|
@ -106,4 +107,10 @@ func DeclareScopedCheckerRoutes(scopedRouter *gin.RouterGroup, cc *controller.Ch
|
|||
// Results (under execution).
|
||||
executionID.GET("/results", cc.GetExecutionResults)
|
||||
executionID.GET("/results/:ruleName", cc.GetExecutionResult)
|
||||
|
||||
// Acknowledgement (requires notification system).
|
||||
if nc != nil {
|
||||
checkerID.POST("/acknowledge", nc.AcknowledgeIssue)
|
||||
checkerID.DELETE("/acknowledge", nc.ClearAcknowledgement)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ func DeclareDomainRoutes(
|
|||
serviceUC happydns.ServiceUsecase,
|
||||
cc *controller.CheckerController,
|
||||
checkStatusUC *checkerUC.CheckStatusUsecase,
|
||||
nc *controller.NotificationController,
|
||||
) {
|
||||
dc := controller.NewDomainController(
|
||||
domainUC,
|
||||
|
|
@ -67,7 +68,7 @@ func DeclareDomainRoutes(
|
|||
|
||||
// Mount domain-scoped checker routes.
|
||||
if cc != nil {
|
||||
DeclareScopedCheckerRoutes(apiDomainsRoutes, cc)
|
||||
DeclareScopedCheckerRoutes(apiDomainsRoutes, cc, nc)
|
||||
}
|
||||
|
||||
DeclareZoneRoutes(
|
||||
|
|
@ -78,5 +79,6 @@ func DeclareDomainRoutes(
|
|||
zoneServiceUC,
|
||||
serviceUC,
|
||||
cc,
|
||||
nc,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
69
internal/api/route/notification.go
Normal file
69
internal/api/route/notification.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2026 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/api/controller"
|
||||
notifUC "git.happydns.org/happyDomain/internal/usecase/notification"
|
||||
)
|
||||
|
||||
// DeclareNotificationRoutes registers notification routes under /api/notifications.
|
||||
func DeclareNotificationRoutes(
|
||||
apiAuthRoutes *gin.RouterGroup,
|
||||
dispatcher *notifUC.Dispatcher,
|
||||
channelStore notifUC.NotificationChannelStorage,
|
||||
prefStore notifUC.NotificationPreferenceStorage,
|
||||
recordStore notifUC.NotificationRecordStorage,
|
||||
) *controller.NotificationController {
|
||||
nc := controller.NewNotificationController(dispatcher, channelStore, prefStore, recordStore)
|
||||
|
||||
notif := apiAuthRoutes.Group("/notifications")
|
||||
|
||||
// Channels
|
||||
channels := notif.Group("/channels")
|
||||
channels.GET("", nc.ListChannels)
|
||||
channels.POST("", nc.CreateChannel)
|
||||
|
||||
channelID := channels.Group("/:channelId")
|
||||
channelID.GET("", nc.GetChannel)
|
||||
channelID.PUT("", nc.UpdateChannel)
|
||||
channelID.DELETE("", nc.DeleteChannel)
|
||||
channelID.POST("/test", nc.TestChannel)
|
||||
|
||||
// Preferences
|
||||
prefs := notif.Group("/preferences")
|
||||
prefs.GET("", nc.ListPreferences)
|
||||
prefs.POST("", nc.CreatePreference)
|
||||
|
||||
prefID := prefs.Group("/:prefId")
|
||||
prefID.GET("", nc.GetPreference)
|
||||
prefID.PUT("", nc.UpdatePreference)
|
||||
prefID.DELETE("", nc.DeletePreference)
|
||||
|
||||
// History
|
||||
notif.GET("/history", nc.ListHistory)
|
||||
|
||||
return nc
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"git.happydns.org/happyDomain/internal/api/controller"
|
||||
"git.happydns.org/happyDomain/internal/api/middleware"
|
||||
checkerUC "git.happydns.org/happyDomain/internal/usecase/checker"
|
||||
notifUC "git.happydns.org/happyDomain/internal/usecase/notification"
|
||||
happydns "git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
|
|
@ -58,6 +59,11 @@ type Dependencies struct {
|
|||
CheckPlanUC *checkerUC.CheckPlanUsecase
|
||||
CheckStatusUC *checkerUC.CheckStatusUsecase
|
||||
PlannedProvider checkerUC.PlannedJobProvider
|
||||
|
||||
NotificationDispatcher *notifUC.Dispatcher
|
||||
NotificationChannels notifUC.NotificationChannelStorage
|
||||
NotificationPrefs notifUC.NotificationPreferenceStorage
|
||||
NotificationRecords notifUC.NotificationRecordStorage
|
||||
}
|
||||
|
||||
// @title happyDomain API
|
||||
|
|
@ -126,6 +132,18 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.RouterGroup, dep Dependenc
|
|||
)
|
||||
}
|
||||
|
||||
// Initialize notification controller if dispatcher is available.
|
||||
var nc *controller.NotificationController
|
||||
if dep.NotificationDispatcher != nil {
|
||||
nc = DeclareNotificationRoutes(
|
||||
apiAuthRoutes,
|
||||
dep.NotificationDispatcher,
|
||||
dep.NotificationChannels,
|
||||
dep.NotificationPrefs,
|
||||
dep.NotificationRecords,
|
||||
)
|
||||
}
|
||||
|
||||
DeclareAuthenticationCheckRoutes(apiAuthRoutes, lc)
|
||||
DeclareDomainRoutes(
|
||||
apiAuthRoutes,
|
||||
|
|
@ -139,6 +157,7 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.RouterGroup, dep Dependenc
|
|||
dep.Service,
|
||||
cc,
|
||||
dep.CheckStatusUC,
|
||||
nc,
|
||||
)
|
||||
DeclareProviderRoutes(apiAuthRoutes, dep.Provider)
|
||||
DeclareProviderSettingsRoutes(apiAuthRoutes, dep.ProviderSettings)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func DeclareZoneServiceRoutes(
|
|||
serviceUC happydns.ServiceUsecase,
|
||||
zoneUC happydns.ZoneUsecase,
|
||||
cc *controller.CheckerController,
|
||||
nc *controller.NotificationController,
|
||||
) {
|
||||
sc := controller.NewServiceController(zoneServiceUC, serviceUC, zoneUC)
|
||||
|
||||
|
|
@ -51,6 +52,6 @@ func DeclareZoneServiceRoutes(
|
|||
|
||||
// Mount service-scoped checker routes.
|
||||
if cc != nil {
|
||||
DeclareScopedCheckerRoutes(apiZonesSubdomainServiceIDRoutes, cc)
|
||||
DeclareScopedCheckerRoutes(apiZonesSubdomainServiceIDRoutes, cc, nc)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func DeclareZoneRoutes(
|
|||
zoneServiceUC happydns.ZoneServiceUsecase,
|
||||
serviceUC happydns.ServiceUsecase,
|
||||
cc *controller.CheckerController,
|
||||
nc *controller.NotificationController,
|
||||
) {
|
||||
var checkStatusUC *checkerUC.CheckStatusUsecase
|
||||
if cc != nil {
|
||||
|
|
@ -74,6 +75,7 @@ func DeclareZoneRoutes(
|
|||
serviceUC,
|
||||
zoneUC,
|
||||
cc,
|
||||
nc,
|
||||
)
|
||||
|
||||
apiZonesRoutes.POST("/records", zc.AddRecords)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ package notification
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
|
|
@ -283,6 +284,25 @@ func (d *Dispatcher) sendAndRecord(ch *happydns.NotificationChannel, payload *no
|
|||
}
|
||||
}
|
||||
|
||||
// SendTestNotification sends a test notification through the given channel.
|
||||
func (d *Dispatcher) SendTestNotification(ch *happydns.NotificationChannel, user *happydns.User) error {
|
||||
sender, ok := d.senders[ch.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("no sender for channel type %q", ch.Type)
|
||||
}
|
||||
|
||||
payload := ¬ifPkg.NotificationPayload{
|
||||
User: user,
|
||||
CheckerID: "test",
|
||||
DomainName: "example.com",
|
||||
OldStatus: happydns.StatusOK,
|
||||
NewStatus: happydns.StatusWarn,
|
||||
BaseURL: d.baseURL,
|
||||
}
|
||||
|
||||
return sender.Send(ch, payload)
|
||||
}
|
||||
|
||||
func (d *Dispatcher) updateState(state *happydns.NotificationState, newStatus happydns.Status) {
|
||||
state.LastStatus = newStatus
|
||||
state.LastNotifiedAt = time.Now()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue