feat(plugin): update plugin interface

This commit is contained in:
LinkinStar 2023-01-10 16:43:42 +08:00
parent 8575bbf8c8
commit 7331b49623
11 changed files with 169 additions and 32 deletions

View File

@ -106,7 +106,7 @@ func (cc *ConnectorController) ConnectorsInfo(ctx *gin.Context) {
err = plugin.CallConnector(func(fn plugin.Connector) error {
resp = append(resp, &schema.ConnectorInfoResp{
Name: fn.ConnectorSlugName(),
Icon: fn.ConnectorLogo(),
Icon: fn.ConnectorLogoSVG(),
Link: fmt.Sprintf("%s%s%s", general.SiteUrl, ConnectorLoginRouterPrefix, fn.ConnectorSlugName()),
})
return nil

View File

@ -0,0 +1,53 @@
package controller_admin
import (
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/plugin"
"github.com/answerdev/answer/internal/schema"
service "github.com/answerdev/answer/internal/service/role"
"github.com/gin-gonic/gin"
)
// PluginController role controller
type PluginController struct {
roleService *service.RoleService
}
// NewPluginController new controller
func NewPluginController(roleService *service.RoleService) *PluginController {
return &PluginController{roleService: roleService}
}
// GetPluginList get plugin list
func (pc *PluginController) GetPluginList(ctx *gin.Context) {
plugin.CallBase(func(base plugin.Base) error {
base.Info()
return nil
})
resp, err := pc.roleService.GetRoleList(ctx)
handler.HandleResponse(ctx, err, resp)
}
// UpdatePluginStatus update plugin status
func (pc *PluginController) UpdatePluginStatus(ctx *gin.Context) {
req := &schema.UpdatePluginStatusReq{}
if handler.BindAndCheck(ctx, req) {
return
}
plugin.StatusManager.Enable(req.PluginSlugName, req.Enabled)
handler.HandleResponse(ctx, nil, nil)
}
// GetPluginConfig get plugin config
func (pc *PluginController) GetPluginConfig(ctx *gin.Context) {
resp, err := pc.roleService.GetRoleList(ctx)
handler.HandleResponse(ctx, err, resp)
}
// UpdatePluginConfig get plugin config
func (pc *PluginController) UpdatePluginConfig(ctx *gin.Context) {
resp, err := pc.roleService.GetRoleList(ctx)
handler.HandleResponse(ctx, err, resp)
}

View File

@ -3,10 +3,10 @@ package plugin
// Info presents the plugin information
type Info struct {
Name string
SlugName string
Description string
Author string
Version string
Disabled bool
}
// Base is the base plugin
@ -18,5 +18,5 @@ type Base interface {
var (
// CallBase is a function that calls all registered base plugins
CallBase,
registerBase = MakePlugin[Base]()
registerBase = MakePlugin[Base](true)
)

45
internal/plugin/config.go Normal file
View File

@ -0,0 +1,45 @@
package plugin
type ConfigType int
const (
ConfigTypeInput ConfigType = iota
ConfigTypeTextarea
ConfigTypeSelect
ConfigTypeCheckbox
)
type ConfigField struct {
Name string
Description string
Required bool
Type ConfigType
Items []ConfigFieldItem
}
type ConfigFieldItem struct {
Name string
Label string
Value string
PlaceHolder string
Selected bool
}
type Config interface {
Base
// ConfigFields returns the list of config fields
ConfigFields() []ConfigField
// ConfigReceiver receives the config data, it calls when the config is saved or initialized.
// We recommend to unmarshal the data to a struct, and then use the struct to do something.
// The config is encoded in JSON format.
// It depends on the definition of ConfigFields.
ConfigReceiver(config []byte) error
}
var (
// CallConfig is a function that calls all registered config plugins
CallConfig,
registerConfig = MakePlugin[Config](true)
)

View File

@ -3,12 +3,8 @@ package plugin
type Connector interface {
Base
// ConnectorLogo presents the logo binary data of the connector
ConnectorLogo() []byte
// ConnectorLogoContentType presents the content type of the logo
// e.g. image/png, image/jpeg, image/gif
ConnectorLogoContentType() string
// ConnectorLogoSVG presents the logo in svg format
ConnectorLogoSVG() string
// ConnectorName presents the name of the connector
// e.g. Facebook, Twitter, Instagram
@ -44,5 +40,5 @@ type ExternalLoginUserInfo struct {
var (
// CallConnector is a function that calls all registered connectors
CallConnector,
registerConnector = MakePlugin[Connector]()
registerConnector = MakePlugin[Connector](false)
)

View File

@ -8,5 +8,5 @@ type Filter interface {
var (
// CallFilter is a function that calls all registered parsers
CallFilter,
registerFilter = MakePlugin[Filter]()
registerFilter = MakePlugin[Filter](false)
)

View File

@ -8,5 +8,5 @@ type Parser interface {
var (
// CallParser is a function that calls all registered parsers
CallParser,
registerParser = MakePlugin[Parser]()
registerParser = MakePlugin[Parser](false)
)

View File

@ -1,35 +1,43 @@
package plugin
import "github.com/gin-gonic/gin"
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
// GinContext is a wrapper of gin.Context
// We export it to make it easy to use in plugins
type GinContext = gin.Context
// StatusManager is a manager that manages the status of plugins
// Init Plugins:
// json.Unmarshal([]byte(`{"plugin1": true, "plugin2": false}`), &plugin.StatusManager)
// Dump Status:
// json.Marshal(plugin.StatusManager)
var StatusManager = statusManager{
status: make(map[string]bool),
}
// Register registers a plugin
func Register(p Base) {
registerBase(p)
switch pType := p.(type) {
case Connector:
registerConnector(pType)
case Parser:
registerParser(pType)
case Filter:
registerFilter(pType)
if _, ok := p.(Config); ok {
registerConfig(p.(Config))
}
}
// Dump returns all registered plugins infos
func Dump() []Info {
var infos []Info
if _, ok := p.(Connector); ok {
registerConnector(p.(Connector))
}
CallBase(func(p Base) error {
infos = append(infos, p.Info())
return nil
})
if _, ok := p.(Parser); ok {
registerParser(p.(Parser))
}
return infos
if _, ok := p.(Filter); ok {
registerFilter(p.(Filter))
}
}
type Stack[T Base] struct {
@ -41,16 +49,17 @@ type Caller[T Base] func(p T) error
type CallFn[T Base] func(fn Caller[T]) error
// MakePlugin creates a plugin caller and register stack manager
// The parameter super presents if the plugin can be disabled.
// It returns a register function and a caller function
// The register function is used to register a plugin, it will be called in the plugin's init function
// The caller function is used to call all registered plugins
func MakePlugin[T Base]() (CallFn[T], RegisterFn[T]) {
func MakePlugin[T Base](super bool) (CallFn[T], RegisterFn[T]) {
stack := Stack[T]{}
call := func(fn Caller[T]) error {
for _, p := range stack.plugins {
// If the plugin is disabled, skip it
if p.Info().Disabled {
if !super && !StatusManager.IsEnabled(p.Info().SlugName) {
continue
}
@ -67,3 +76,28 @@ func MakePlugin[T Base]() (CallFn[T], RegisterFn[T]) {
return call, register
}
type statusManager struct {
status map[string]bool
}
func (m *statusManager) Enable(name string, enabled bool) {
m.status[name] = enabled
}
func (m *statusManager) IsEnabled(name string) bool {
if status, ok := m.status[name]; ok {
return status
}
return true
}
// MarshalJSON implements the json.Marshaler interface.
func (m *statusManager) MarshalJSON() ([]byte, error) {
return json.Marshal(m.status)
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (m *statusManager) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &m.status)
}

View File

@ -2,6 +2,6 @@ package schema
type ConnectorInfoResp struct {
Name string `json:"name"`
Icon []byte `json:"icon"`
Icon string `json:"icon"`
Link string `json:"link"`
}

View File

@ -0,0 +1,6 @@
package schema
type UpdatePluginStatusReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
Enabled bool `json:"enabled"`
}

View File

@ -0,0 +1,3 @@
package plugin_common
type PluginConfig struct{}