Add test plugin routes to API + refactor plugins controller

This commit is contained in:
nemunaire 2026-01-28 17:12:58 +08:00
commit 7fb3224796
5 changed files with 394 additions and 105 deletions

View file

@ -26,17 +26,19 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api/middleware"
apicontroller "git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
// TestPluginController handles admin-level plugin operations.
// All methods in this controller work with admin-scoped options (nil user/domain/service IDs).
type TestPluginController struct {
testPluginService happydns.TestPluginUsecase
*apicontroller.BaseTestPluginController
}
func NewTestPluginController(testPluginService happydns.TestPluginUsecase) *TestPluginController {
return &TestPluginController{
testPluginService,
BaseTestPluginController: apicontroller.NewBaseTestPluginController(testPluginService),
}
}
@ -44,7 +46,7 @@ func NewTestPluginController(testPluginService happydns.TestPluginUsecase) *Test
func (uc *TestPluginController) TestPluginHandler(c *gin.Context) {
pname := c.Param("pname")
plugin, err := uc.testPluginService.GetTestPlugin(pname)
plugin, err := uc.BaseTestPluginController.GetTestPluginService().GetTestPlugin(pname)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, happydns.ErrorResponse{Message: "Plugin not found"})
return
@ -55,12 +57,13 @@ func (uc *TestPluginController) TestPluginHandler(c *gin.Context) {
c.Next()
}
// TestPluginOptionHandler is a middleware that retrieves a specific plugin option and sets it in the context.
// TestPluginOptionHandler is a middleware that retrieves a specific admin-level plugin option and sets it in the context.
func (uc *TestPluginController) TestPluginOptionHandler(c *gin.Context) {
pname := c.Param("pname")
optname := c.Param("optname")
opts, err := uc.testPluginService.GetTestPluginOptions(pname, nil, nil, nil)
// Get admin-level options (nil user/domain/service IDs)
opts, err := uc.BaseTestPluginController.GetTestPluginService().GetTestPluginOptions(pname, nil, nil, nil)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
return
@ -71,60 +74,11 @@ func (uc *TestPluginController) TestPluginOptionHandler(c *gin.Context) {
c.Next()
}
// ListTestPlugins retrieves all available test plugins.
// GetTestPluginOptions retrieves all admin-level options for a test plugin.
//
// @Summary List all test plugins
// @Summary Get test plugin options (admin)
// @Schemes
// @Description Returns a list of all available test plugins with their version information.
// @Tags plugins
// @Accept json
// @Produce json
// @Success 200 {object} map[string]happydns.PluginVersionInfo "Map of plugin names to version info"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests [get]
func (uc *TestPluginController) ListTestPlugins(c *gin.Context) {
plugins, err := uc.testPluginService.ListTestPlugins()
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
ret := map[string]happydns.PluginVersionInfo{}
for _, p := range plugins {
pnames := p.PluginEnvName()
ret[pnames[0]] = p.Version()
}
happydns.ApiResponse(c, ret, nil)
}
// GetTestPluginStatus retrieves the status and available options for a test plugin.
//
// @Summary Get test plugin status
// @Schemes
// @Description Retrieves the status information and available options for a specific test plugin.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Success 200 {object} happydns.PluginStatus "Plugin status with version info and available options"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Router /plugins/tests/{pname} [get]
func (uc *TestPluginController) GetTestPluginStatus(c *gin.Context) {
plugin := c.MustGet("plugin").(happydns.TestPlugin)
c.JSON(http.StatusOK, happydns.PluginStatus{
PluginVersionInfo: plugin.Version(),
Opts: plugin.AvailableOptions(),
})
}
// GetTestPluginOptions retrieves all options for a test plugin.
//
// @Summary Get test plugin options
// @Schemes
// @Description Retrieves all configuration options for a specific test plugin.
// @Description Retrieves all admin-level configuration options for a specific test plugin.
// @Tags plugins
// @Accept json
// @Produce json
@ -136,15 +90,15 @@ func (uc *TestPluginController) GetTestPluginStatus(c *gin.Context) {
func (uc *TestPluginController) GetTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
opts, err := uc.testPluginService.GetTestPluginOptions(pname, nil, nil, nil)
happydns.ApiResponse(c, opts, err)
// Get admin-level options (nil user/domain/service IDs)
uc.GetTestPluginOptionsWithScope(c, pname, nil, nil, nil)
}
// AddTestPluginOptions adds or overwrites specific options for a test plugin.
// AddTestPluginOptions adds or overwrites specific admin-level options for a test plugin.
//
// @Summary Add test plugin options
// @Summary Add test plugin options (admin)
// @Schemes
// @Description Adds or overwrites specific configuration options for a test plugin without affecting other options.
// @Description Adds or overwrites specific admin-level configuration options for a test plugin without affecting other options.
// @Tags plugins
// @Accept json
// @Produce json
@ -158,22 +112,15 @@ func (uc *TestPluginController) GetTestPluginOptions(c *gin.Context) {
func (uc *TestPluginController) AddTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = uc.testPluginService.OverwriteSomeTestPluginOptions(pname, nil, nil, nil, req.Options)
happydns.ApiResponse(c, true, err)
// Add admin-level options (nil user/domain/service IDs)
uc.AddTestPluginOptionsWithScope(c, pname, nil, nil, nil)
}
// ChangeTestPluginOptions replaces all options for a test plugin.
// ChangeTestPluginOptions replaces all admin-level options for a test plugin.
//
// @Summary Replace test plugin options
// @Summary Replace test plugin options (admin)
// @Schemes
// @Description Replaces all configuration options for a test plugin with the provided options.
// @Description Replaces all admin-level configuration options for a test plugin with the provided options.
// @Tags plugins
// @Accept json
// @Produce json
@ -187,22 +134,15 @@ func (uc *TestPluginController) AddTestPluginOptions(c *gin.Context) {
func (uc *TestPluginController) ChangeTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = uc.testPluginService.SetTestPluginOptions(pname, nil, nil, nil, req.Options)
happydns.ApiResponse(c, true, err)
// Replace admin-level options (nil user/domain/service IDs)
uc.ChangeTestPluginOptionsWithScope(c, pname, nil, nil, nil)
}
// GetTestPluginOption retrieves a specific option value for a test plugin.
// GetTestPluginOption retrieves a specific admin-level option value for a test plugin.
//
// @Summary Get test plugin option
// @Summary Get test plugin option (admin)
// @Schemes
// @Description Retrieves the value of a specific configuration option for a test plugin.
// @Description Retrieves the value of a specific admin-level configuration option for a test plugin.
// @Tags plugins
// @Accept json
// @Produce json
@ -213,16 +153,14 @@ func (uc *TestPluginController) ChangeTestPluginOptions(c *gin.Context) {
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options/{optname} [get]
func (uc *TestPluginController) GetTestPluginOption(c *gin.Context) {
opt := c.MustGet("option")
happydns.ApiResponse(c, opt, nil)
uc.GetTestPluginOptionValue(c)
}
// SetTestPluginOption sets or updates a specific option value for a test plugin.
// SetTestPluginOption sets or updates a specific admin-level option value for a test plugin.
//
// @Summary Set test plugin option
// @Summary Set test plugin option (admin)
// @Schemes
// @Description Sets or updates the value of a specific configuration option for a test plugin.
// @Description Sets or updates the value of a specific admin-level configuration option for a test plugin.
// @Tags plugins
// @Accept json
// @Produce json
@ -238,16 +176,6 @@ func (uc *TestPluginController) SetTestPluginOption(c *gin.Context) {
pname := c.Param("pname")
optname := c.Param("optname")
var req interface{}
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
po := happydns.PluginOptions{}
po[optname] = req
err = uc.testPluginService.OverwriteSomeTestPluginOptions(pname, nil, nil, nil, po)
happydns.ApiResponse(c, true, err)
// Set admin-level option (nil user/domain/service IDs)
uc.SetTestPluginOptionWithScope(c, pname, optname, nil, nil, nil)
}

View file

@ -0,0 +1,131 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 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"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
// BaseTestPluginController contains shared functionality for test plugin controllers.
// It provides common methods that can be used by both admin and user-scoped controllers.
type BaseTestPluginController struct {
testPluginService happydns.TestPluginUsecase
}
func NewBaseTestPluginController(testPluginService happydns.TestPluginUsecase) *BaseTestPluginController {
return &BaseTestPluginController{
testPluginService,
}
}
// GetTestPluginService returns the test plugin service for use by derived controllers.
func (bc *BaseTestPluginController) GetTestPluginService() happydns.TestPluginUsecase {
return bc.testPluginService
}
// ListTestPlugins retrieves all available test plugins.
func (bc *BaseTestPluginController) ListTestPlugins(c *gin.Context) {
plugins, err := bc.testPluginService.ListTestPlugins()
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
ret := map[string]happydns.PluginVersionInfo{}
for _, p := range plugins {
pnames := p.PluginEnvName()
ret[pnames[0]] = p.Version()
}
happydns.ApiResponse(c, ret, nil)
}
// GetTestPluginStatus retrieves the status and available options for a test plugin.
func (bc *BaseTestPluginController) GetTestPluginStatus(c *gin.Context) {
plugin := c.MustGet("plugin").(happydns.TestPlugin)
c.JSON(http.StatusOK, happydns.PluginStatus{
PluginVersionInfo: plugin.Version(),
Opts: plugin.AvailableOptions(),
})
}
// GetTestPluginOptionsWithScope retrieves all options for a test plugin with the given scope.
func (bc *BaseTestPluginController) GetTestPluginOptionsWithScope(c *gin.Context, pname string, userId *happydns.Identifier, domainId *happydns.Identifier, serviceId *happydns.Identifier) {
opts, err := bc.testPluginService.GetTestPluginOptions(pname, userId, domainId, serviceId)
happydns.ApiResponse(c, opts, err)
}
// AddTestPluginOptionsWithScope adds or overwrites specific options for a test plugin with the given scope.
func (bc *BaseTestPluginController) AddTestPluginOptionsWithScope(c *gin.Context, pname string, userId *happydns.Identifier, domainId *happydns.Identifier, serviceId *happydns.Identifier) {
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = bc.testPluginService.OverwriteSomeTestPluginOptions(pname, userId, domainId, serviceId, req.Options)
happydns.ApiResponse(c, true, err)
}
// ChangeTestPluginOptionsWithScope replaces all options for a test plugin with the given scope.
func (bc *BaseTestPluginController) ChangeTestPluginOptionsWithScope(c *gin.Context, pname string, userId *happydns.Identifier, domainId *happydns.Identifier, serviceId *happydns.Identifier) {
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = bc.testPluginService.SetTestPluginOptions(pname, userId, domainId, serviceId, req.Options)
happydns.ApiResponse(c, true, err)
}
// GetTestPluginOptionValue retrieves a specific option value from the context.
func (bc *BaseTestPluginController) GetTestPluginOptionValue(c *gin.Context) {
opt := c.MustGet("option")
happydns.ApiResponse(c, opt, nil)
}
// SetTestPluginOptionWithScope sets or updates a specific option value for a test plugin with the given scope.
func (bc *BaseTestPluginController) SetTestPluginOptionWithScope(c *gin.Context, pname string, optname string, userId *happydns.Identifier, domainId *happydns.Identifier, serviceId *happydns.Identifier) {
var req interface{}
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
po := happydns.PluginOptions{}
po[optname] = req
err = bc.testPluginService.OverwriteSomeTestPluginOptions(pname, userId, domainId, serviceId, po)
happydns.ApiResponse(c, true, err)
}

View file

@ -0,0 +1,180 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 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"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/model"
)
// TestPluginController handles user-scoped plugin operations for the main API.
// All methods work with options scoped to the authenticated user.
type TestPluginController struct {
*BaseTestPluginController
}
func NewTestPluginController(testPluginService happydns.TestPluginUsecase) *TestPluginController {
return &TestPluginController{
BaseTestPluginController: NewBaseTestPluginController(testPluginService),
}
}
// TestPluginHandler is a middleware that retrieves a test plugin by name and sets it in the context.
func (uc *TestPluginController) TestPluginHandler(c *gin.Context) {
pname := c.Param("pid")
plugin, err := uc.testPluginService.GetTestPlugin(pname)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, happydns.ErrorResponse{Message: "Plugin not found"})
return
}
c.Set("plugin", plugin)
c.Next()
}
// TestPluginOptionHandler is a middleware that retrieves a specific plugin option for the authenticated user and sets it in the context.
func (uc *TestPluginController) TestPluginOptionHandler(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
pname := c.Param("pid")
optname := c.Param("optname")
opts, err := uc.testPluginService.GetTestPluginOptions(pname, &user.Id, nil, nil)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
return
}
c.Set("option", (*opts)[optname])
c.Next()
}
// GetTestPluginOptions retrieves all options for a test plugin for the authenticated user.
//
// @Summary Get test plugin options
// @Schemes
// @Description Retrieves all configuration options for a specific test plugin for the authenticated user.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pid path string true "Plugin name"
// @Success 200 {object} happydns.PluginOptions "Plugin options as key-value pairs"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/{pid}/options [get]
func (uc *TestPluginController) GetTestPluginOptions(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
pname := c.Param("pid")
uc.GetTestPluginOptionsWithScope(c, pname, &user.Id, nil, nil)
}
// AddTestPluginOptions adds or overwrites specific options for a test plugin for the authenticated user.
//
// @Summary Add test plugin options
// @Schemes
// @Description Adds or overwrites specific configuration options for a test plugin for the authenticated user without affecting other options.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pid path string true "Plugin name"
// @Param body body happydns.SetPluginOptionsRequest true "Options to add or overwrite"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/{pid}/options [post]
func (uc *TestPluginController) AddTestPluginOptions(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
pname := c.Param("pid")
uc.AddTestPluginOptionsWithScope(c, pname, &user.Id, nil, nil)
}
// ChangeTestPluginOptions replaces all options for a test plugin for the authenticated user.
//
// @Summary Replace test plugin options
// @Schemes
// @Description Replaces all configuration options for a test plugin for the authenticated user with the provided options.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pid path string true "Plugin name"
// @Param body body happydns.SetPluginOptionsRequest true "New complete set of options"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/{pid}/options [put]
func (uc *TestPluginController) ChangeTestPluginOptions(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
pname := c.Param("pid")
uc.ChangeTestPluginOptionsWithScope(c, pname, &user.Id, nil, nil)
}
// GetTestPluginOption retrieves a specific option value for a test plugin for the authenticated user.
//
// @Summary Get test plugin option
// @Schemes
// @Description Retrieves the value of a specific configuration option for a test plugin for the authenticated user.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pid path string true "Plugin name"
// @Param optname path string true "Option name"
// @Success 200 {object} object "Option value (type varies)"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/{pid}/options/{optname} [get]
func (uc *TestPluginController) GetTestPluginOption(c *gin.Context) {
uc.GetTestPluginOptionValue(c)
}
// SetTestPluginOption sets or updates a specific option value for a test plugin for the authenticated user.
//
// @Summary Set test plugin option
// @Schemes
// @Description Sets or updates the value of a specific configuration option for a test plugin for the authenticated user.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pid path string true "Plugin name"
// @Param optname path string true "Option name"
// @Param body body object true "Option value (type varies by option)"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/{pid}/options/{optname} [put]
func (uc *TestPluginController) SetTestPluginOption(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
pname := c.Param("pid")
optname := c.Param("optname")
uc.SetTestPluginOptionWithScope(c, pname, optname, &user.Id, nil, nil)
}

View file

@ -0,0 +1,49 @@
// 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"
happydns "git.happydns.org/happyDomain/model"
)
func DeclarePluginsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
tpc := controller.NewTestPluginController(dependancies.TestPluginUsecase())
router.GET("/plugins", tpc.ListTestPlugins)
apiTestPluginRoutes := router.Group("/plugins/:pid")
apiTestPluginRoutes.Use(tpc.TestPluginHandler)
apiTestPluginRoutes.GET("", tpc.GetTestPluginStatus)
apiTestPluginRoutes.GET("/options", tpc.GetTestPluginOptions)
apiTestPluginRoutes.POST("/options", tpc.AddTestPluginOptions)
apiTestPluginRoutes.PUT("/options", tpc.ChangeTestPluginOptions)
apiTestPluginOptionsRoutes := apiTestPluginRoutes.Group("/options/:optname")
apiTestPluginOptionsRoutes.Use(tpc.TestPluginOptionHandler)
apiTestPluginOptionsRoutes.GET("", tpc.GetTestPluginOption)
apiTestPluginOptionsRoutes.PUT("", tpc.SetTestPluginOption)
}

View file

@ -77,6 +77,7 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, dependancies happy
DeclareAuthenticationCheckRoutes(apiAuthRoutes, dependancies, lc)
DeclareDomainRoutes(apiAuthRoutes, dependancies)
DeclarePluginsRoutes(apiAuthRoutes, dependancies)
DeclareProviderRoutes(apiAuthRoutes, dependancies)
DeclareProviderSettingsRoutes(apiAuthRoutes, dependancies)
DeclareRecordRoutes(apiAuthRoutes, dependancies)