feat(plugin): github connector

This commit is contained in:
LinkinStar 2022-12-30 10:21:41 +08:00
parent 0628e52da0
commit 154fa84bbb
19 changed files with 507 additions and 6 deletions

View File

@ -210,7 +210,9 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, dataData, siteInfoCommonService)
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter)
connectorController := controller.NewConnectorController(siteInfoCommonService)
pluginAPIRouter := router.NewPluginAPIRouter(connectorController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter)
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
return application, func() {

7
go.mod
View File

@ -12,10 +12,11 @@ require (
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.11.1
github.com/go-resty/resty/v2 v2.7.0
github.com/go-sql-driver/mysql v1.6.0
github.com/goccy/go-json v0.9.11
github.com/golang/mock v1.4.4
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
github.com/google/go-github/v48 v48.2.0
github.com/google/uuid v1.3.0
github.com/google/wire v0.5.0
github.com/gosimple/slug v1.13.1
@ -41,6 +42,7 @@ require (
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.1.0
golang.org/x/net v0.1.0
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
xorm.io/builder v0.3.12
@ -69,7 +71,9 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@ -114,6 +118,7 @@ require (
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

18
go.sum
View File

@ -202,6 +202,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@ -252,12 +254,12 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c h1:iyaGYbCmcYK0Ja9a3OUa2Fo+EaN0cbLu0eKpBwPFzc8=
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -271,7 +273,11 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE=
github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -830,7 +836,9 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
@ -844,6 +852,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1043,6 +1053,7 @@ google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -1113,6 +1124,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=

View File

@ -20,6 +20,7 @@ func NewHTTPServer(debug bool,
authUserMiddleware *middleware.AuthUserMiddleware,
avatarMiddleware *middleware.AvatarMiddleware,
templateRouter *router.TemplateRouter,
pluginAPIRouter *router.PluginAPIRouter,
) *gin.Engine {
if debug {
@ -62,5 +63,8 @@ func NewHTTPServer(debug bool,
answerRouter.RegisterAnswerAdminAPIRouter(adminauthV1)
templateRouter.RegisterTemplateRouter(rootGroup)
// plugin routes
pluginAPIRouter.RegisterConnector(r)
return r
}

View File

@ -0,0 +1,63 @@
package controller
import (
"fmt"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/plugin"
_ "github.com/answerdev/answer/internal/plugin/connector"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/siteinfo_common"
"github.com/gin-gonic/gin"
)
const (
oauthRedirectRouterPrefix = "/answer/api/v1/oauth/redirect/"
oauthLoginRouterPrefix = "/answer/api/v1/oauth/login/"
)
// ConnectorController comment controller
type ConnectorController struct {
siteInfoService *siteinfo_common.SiteInfoCommonService
}
// NewConnectorController new controller
func NewConnectorController(
siteInfoService *siteinfo_common.SiteInfoCommonService,
) *ConnectorController {
return &ConnectorController{siteInfoService: siteInfoService}
}
func (cc *ConnectorController) ConnectorRedirectRegisterRouters(r *gin.Engine) {
_ = plugin.CallConnector(func(fn plugin.Connector) error {
// user login url
r.GET(oauthLoginRouterPrefix+fn.ConnectorSlugName(), fn.ConnectorSender)
// oauth redirect url
r.GET(oauthRedirectRouterPrefix+fn.ConnectorSlugName(), fn.ConnectorReceiver)
return nil
})
r.GET("/answer/api/v1/oauth/info", cc.ConnectorsInfo)
}
func (cc *ConnectorController) ConnectorsInfo(ctx *gin.Context) {
general, err := cc.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
resp := make([]*schema.ConnectorInfoResp, 0)
err = plugin.CallConnector(func(fn plugin.Connector) error {
resp = append(resp, &schema.ConnectorInfoResp{
Name: fn.ConnectorSlugName(),
Icon: fn.ConnectorLogo(),
Link: fmt.Sprintf("%s%s%s", general.SiteUrl, oauthLoginRouterPrefix, fn.ConnectorSlugName()),
})
return nil
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
handler.HandleResponse(ctx, nil, resp)
}

View File

@ -24,4 +24,5 @@ var ProviderSetController = wire.NewSet(
NewUploadController,
NewActivityController,
NewTemplateController,
NewConnectorController,
)

22
internal/plugin/base.go Normal file
View File

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

View File

@ -0,0 +1,54 @@
package plugin
import "time"
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
// ConnectorName presents the name of the connector
// e.g. Facebook, Twitter, Instagram
ConnectorName() string
// ConnectorSlugName presents the slug name of the connector
// Please use lowercase and hyphen as the separator
// e.g. facebook, twitter, instagram
ConnectorSlugName() string
// ConnectorSender presents the sender of the connector
// It handles the start endpoint of the connector
ConnectorSender(ctx *GinContext)
ConnectorReceiver(ctx *GinContext)
//ConnectorLoginURL() (loginURL string)
//ConnectorLoginUserInfo(code string) (userInfo *UserExternalLogin)
}
type UserExternalLogin struct {
Provider string
ExternalID string
Email string
Name string
FirstName string
LastName string
NickName string
Description string
AvatarUrl string
Location string
AccessToken string
AccessTokenSecret string
RefreshToken string
ExpiresAt time.Time
}
var (
// CallConnector is a function that calls all registered connectors
CallConnector,
registerConnector = MakePlugin[Connector]()
)

View File

@ -0,0 +1,96 @@
package connector
import (
"context"
"fmt"
"net/http"
"os"
"github.com/answerdev/answer/internal/plugin"
"github.com/go-resty/resty/v2"
"github.com/google/go-github/v48/github"
"golang.org/x/oauth2"
oauth2GitHub "golang.org/x/oauth2/github"
)
type GitHub struct {
ClientID string
ClientSecret string
}
func init() {
plugin.Register(&GitHub{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
})
}
func (g *GitHub) Info() plugin.Info {
return plugin.Info{
Name: "github connector",
Description: "github connector plugin",
Version: "0.0.1",
}
}
func (g *GitHub) ConnectorLogo() []byte {
response, err := resty.New().R().Get("https://cdn-icons-png.flaticon.com/32/25/25231.png")
if err != nil {
return nil
}
return response.Body()
}
func (g *GitHub) ConnectorLogoContentType() string {
return "image/png"
}
func (g *GitHub) ConnectorName() string {
return "GitHubConnector"
}
func (g *GitHub) ConnectorSlugName() string {
return "github"
}
func (g *GitHub) ConnectorSender(ctx *plugin.GinContext) {
oauth2Config := &oauth2.Config{
ClientID: g.ClientID,
ClientSecret: g.ClientSecret,
Endpoint: oauth2GitHub.Endpoint,
RedirectURL: "http://127.0.0.1:8080/answer/api/v1/oauth/redirect/github",
Scopes: nil,
}
ctx.Redirect(http.StatusFound, oauth2Config.AuthCodeURL(""))
}
func (g *GitHub) ConnectorReceiver(ctx *plugin.GinContext) {
code := ctx.Query("code")
//state := ctx.Query("state")
oauth2Config := &oauth2.Config{
ClientID: g.ClientID,
ClientSecret: g.ClientSecret,
Endpoint: oauth2GitHub.Endpoint,
}
token, err := oauth2Config.Exchange(context.Background(), code+"1")
if err != nil {
ctx.Error(err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
cli := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token.AccessToken},
)))
userInfo, _, err := cli.Users.Get(context.Background(), "")
if err != nil {
ctx.Error(err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
fmt.Printf("user info is :%+v", userInfo)
ctx.Redirect(http.StatusFound, "/")
return
}

View File

@ -0,0 +1,47 @@
package connector
import (
"net/http"
"github.com/answerdev/answer/internal/plugin"
)
type Google struct {
}
func (g *Google) Info() plugin.Info {
return plugin.Info{
Name: "google connector",
Description: "google connector plugin",
Version: "0.0.1",
}
}
func (g *Google) ConnectorLogo() []byte {
return nil
}
func (g *Google) ConnectorLogoContentType() string {
return "image/png"
}
func (g *Google) ConnectorName() string {
return "google"
}
func (g *Google) ConnectorSlugName() string {
return "google"
}
func (g *Google) ConnectorSender(ctx *plugin.GinContext) {
//TODO implement me
panic("implement me")
}
func (g *Google) ConnectorReceiver(ctx *plugin.GinContext) {
ctx.String(http.StatusOK, "OK123")
}
func init() {
plugin.Register(&Google{})
}

12
internal/plugin/filter.go Normal file
View File

@ -0,0 +1,12 @@
package plugin
type Filter interface {
Base
FilterText(text string) (err error)
}
var (
// CallFilter is a function that calls all registered parsers
CallFilter,
registerFilter = MakePlugin[Filter]()
)

12
internal/plugin/parser.go Normal file
View File

@ -0,0 +1,12 @@
package plugin
type Parser interface {
Base
Parse(text string) (string, error)
}
var (
// CallParser is a function that calls all registered parsers
CallParser,
registerParser = MakePlugin[Parser]()
)

69
internal/plugin/plugin.go Normal file
View File

@ -0,0 +1,69 @@
package plugin
import "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
// 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)
}
}
// Dump returns all registered plugins infos
func Dump() []Info {
var infos []Info
CallBase(func(p Base) error {
infos = append(infos, p.Info())
return nil
})
return infos
}
type Stack[T Base] struct {
plugins []T
}
type RegisterFn[T Base] func(p T)
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
// 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]) {
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 {
continue
}
if err := fn(p); err != nil {
return err
}
}
return nil
}
register := func(p T) {
stack.plugins = append(stack.plugins, p)
}
return call, register
}

View File

@ -0,0 +1,30 @@
package sample
import (
"fmt"
"strings"
"github.com/answerdev/answer/internal/plugin"
)
type FilterSample struct {
}
func (s *FilterSample) Info() plugin.Info {
return plugin.Info{
Name: "filter sample",
Description: "filter sample plugin",
Version: "0.0.1",
}
}
func (s *FilterSample) FilterText(data string) (err error) {
if strings.Contains(data, "violent") {
return fmt.Errorf("bloody and violent words cannot appear in this website")
}
return nil
}
func init() {
plugin.Register(&FilterSample{})
}

View File

@ -0,0 +1,18 @@
package sample
import (
"fmt"
"testing"
"github.com/answerdev/answer/internal/plugin"
"github.com/stretchr/testify/assert"
)
func TestFilterSample_FilterText(t *testing.T) {
// try to call filter plugin for filter text that are not allowed
err := plugin.CallFilter(func(fn plugin.Filter) error {
return fn.FilterText("bloody and violent words")
})
assert.Error(t, err)
fmt.Println(err)
}

View File

@ -0,0 +1,18 @@
package sample
import "github.com/answerdev/answer/internal/plugin"
type Sample struct {
}
func (s *Sample) Info() plugin.Info {
return plugin.Info{
Name: "sample",
Description: "sample plugin",
Version: "0.0.1",
}
}
func init() {
plugin.Register(&Sample{})
}

View File

@ -0,0 +1,22 @@
package router
import (
"github.com/answerdev/answer/internal/controller"
"github.com/gin-gonic/gin"
)
type PluginAPIRouter struct {
connectorController *controller.ConnectorController
}
func NewPluginAPIRouter(
connectorController *controller.ConnectorController,
) *PluginAPIRouter {
return &PluginAPIRouter{
connectorController: connectorController,
}
}
func (pr *PluginAPIRouter) RegisterConnector(r *gin.Engine) {
pr.connectorController.ConnectorRedirectRegisterRouters(r)
}

View File

@ -3,4 +3,11 @@ package router
import "github.com/google/wire"
// ProviderSetRouter is providers.
var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewUIRouter, NewTemplateRouter)
var ProviderSetRouter = wire.NewSet(
NewAnswerAPIRouter,
NewSwaggerRouter,
NewStaticRouter,
NewUIRouter,
NewTemplateRouter,
NewPluginAPIRouter,
)

View File

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