[Feature] Support custom widget (refactor widget)
This commit is contained in:
parent
6cb73059b9
commit
3947c64ff8
11 changed files with 1006 additions and 105 deletions
|
|
@ -172,12 +172,6 @@ func Load(cfg config.Config) (err error) {
|
||||||
printErr(cfg.Mode, "Schedule", err)
|
printErr(cfg.Mode, "Schedule", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Custom Widget
|
|
||||||
err = widget.Load(cfg)
|
|
||||||
if err != nil {
|
|
||||||
printErr(cfg.Mode, "Widget", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load AIGC
|
// Load AIGC
|
||||||
err = aigc.Load(cfg)
|
err = aigc.Load(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -190,6 +184,18 @@ func Load(cfg config.Config) (err error) {
|
||||||
printErr(cfg.Mode, "AIGC", err)
|
printErr(cfg.Mode, "AIGC", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Custom Widget
|
||||||
|
err = widget.Load(cfg)
|
||||||
|
if err != nil {
|
||||||
|
printErr(cfg.Mode, "Widget", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Custom Widget Instances
|
||||||
|
err = widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
printErr(cfg.Mode, "Widget", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
177
widget/driver/connector.go
Normal file
177
widget/driver/connector.go
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/gou/connector"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/xun/capsule"
|
||||||
|
"github.com/yaoapp/xun/dbal/query"
|
||||||
|
"github.com/yaoapp/xun/dbal/schema"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector the store driver
|
||||||
|
type Connector struct {
|
||||||
|
Connector string
|
||||||
|
Table string
|
||||||
|
Reload bool
|
||||||
|
Widget string
|
||||||
|
query query.Query
|
||||||
|
schema schema.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnector create a new stroe driver
|
||||||
|
func NewConnector(widgetID string, connectorName string, tableName string, reload bool) (*Connector, error) {
|
||||||
|
if connectorName == "" {
|
||||||
|
connectorName = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if tableName == "" {
|
||||||
|
tableName = fmt.Sprintf("__yao_dsl_%s", widgetID)
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Connector{Widget: widgetID, Connector: connectorName, Reload: reload, Table: tableName}
|
||||||
|
if store.Connector == "default" {
|
||||||
|
store.query = capsule.Global.Query()
|
||||||
|
store.schema = capsule.Global.Schema()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
conn, err := connector.Select(connectorName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conn.Is(connector.DATABASE) {
|
||||||
|
return nil, fmt.Errorf("The connector %s is not a database connector", connectorName)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.query, err = conn.Query()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
store.schema, err = conn.Schema()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := store.init()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk load the widget instances
|
||||||
|
func (app *Connector) Walk(cb func(string, map[string]interface{})) error {
|
||||||
|
|
||||||
|
rows, err := app.query.
|
||||||
|
Table(app.Table).
|
||||||
|
Select("file", "source").
|
||||||
|
Limit(5000).
|
||||||
|
Get()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
for _, row := range rows {
|
||||||
|
|
||||||
|
source := map[string]interface{}{}
|
||||||
|
data := []byte(row["source"].(string))
|
||||||
|
file := row["file"].(string)
|
||||||
|
|
||||||
|
id := share.ID("", file)
|
||||||
|
err := application.Parse(row["file"].(string), data, &source)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cb(id, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save save the widget DSL
|
||||||
|
func (app *Connector) Save(file string, source map[string]interface{}) error {
|
||||||
|
|
||||||
|
bytes, err := jsoniter.Marshal(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(bytes)
|
||||||
|
|
||||||
|
has, err := app.query.Table(app.Table).Where("file", file).Exists()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
_, err = app.query.Table(app.Table).Where("file", file).Update(map[string]interface{}{"source": content})
|
||||||
|
} else {
|
||||||
|
err = app.query.Table(app.Table).Insert(map[string]interface{}{"file": file, "source": content})
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove remove the widget DSL
|
||||||
|
func (app *Connector) Remove(file string) error {
|
||||||
|
_, err := app.query.Table(app.Table).Where("file", file).Delete()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the widget store
|
||||||
|
func (app *Connector) init() error {
|
||||||
|
|
||||||
|
has, err := app.schema.HasTable(app.Table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the table
|
||||||
|
if !has {
|
||||||
|
err = app.schema.CreateTable(app.Table, func(table schema.Blueprint) {
|
||||||
|
table.ID("id") // The ID field
|
||||||
|
table.String("file", 255).Unique() // The file name
|
||||||
|
table.Text("source").Null()
|
||||||
|
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
||||||
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
|
table.TimestampTz("expired_at").Null().Index()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Trace("Create the conversation table: %s", app.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate the table
|
||||||
|
tab, err := app.schema.GetTable(app.Table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []string{"id", "file", "source", "created_at", "updated_at", "expired_at"}
|
||||||
|
for _, field := range fields {
|
||||||
|
if !tab.HasColumn(field) {
|
||||||
|
return fmt.Errorf("%s is required", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
82
widget/driver/source.go
Normal file
82
widget/driver/source.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Source the application source driver
|
||||||
|
type Source struct {
|
||||||
|
Path string
|
||||||
|
Extensions []string
|
||||||
|
Instances sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSource create a new local driver
|
||||||
|
func NewSource(path string, exts []string) *Source {
|
||||||
|
return &Source{
|
||||||
|
Path: path,
|
||||||
|
Extensions: exts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk load the widget instances
|
||||||
|
func (app *Source) Walk(cb func(string, map[string]interface{})) error {
|
||||||
|
|
||||||
|
if app.Path == "" {
|
||||||
|
return fmt.Errorf("The widget path is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.Extensions == nil || len(app.Extensions) == 0 {
|
||||||
|
app.Extensions = []string{"*.yao", "*.json", "*.jsonc"}
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
err := application.App.Walk(app.Path, func(root, file string, isdir bool) error {
|
||||||
|
if isdir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id := share.ID(root, file)
|
||||||
|
source := map[string]interface{}{}
|
||||||
|
|
||||||
|
data, err := application.App.Read(file)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = application.Parse(file, data, &source)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(id, source)
|
||||||
|
return nil
|
||||||
|
}, app.Extensions...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save save the widget DSL
|
||||||
|
func (app *Source) Save(file string, source map[string]interface{}) error {
|
||||||
|
return fmt.Errorf("The widget source driver is read-only, using Studio API instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove remove the widget DSL
|
||||||
|
func (app *Source) Remove(file string) error {
|
||||||
|
return fmt.Errorf("The widget source driver is read-only, using Studio API instead")
|
||||||
|
}
|
||||||
57
widget/instance.go
Normal file
57
widget/instance.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInstance create a new widget instance
|
||||||
|
func NewInstance(widgetID string, instanceID string, source map[string]interface{}, loader LoaderDSL) *Instance {
|
||||||
|
return &Instance{id: instanceID, source: source, widget: widgetID, loader: loader}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load load the widget instance
|
||||||
|
func (instance *Instance) Load() error {
|
||||||
|
if instance.loader.Load == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dsl, err := instance.exec(instance.loader.Load, instance.id, instance.source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.dsl = dsl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload reload the widget instance
|
||||||
|
func (instance *Instance) Reload() error {
|
||||||
|
if instance.loader.Reload == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dsl, err := instance.exec(instance.loader.Reload, instance.id, instance.source, instance.dsl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.dsl = dsl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload unload the widget instance
|
||||||
|
func (instance *Instance) Unload() error {
|
||||||
|
if instance.loader.Unload == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := instance.exec(instance.loader.Unload, instance.id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec exec the widget process
|
||||||
|
func (instance *Instance) exec(processName string, args ...interface{}) (interface{}, error) {
|
||||||
|
p, err := process.Of(processName, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.Exec()
|
||||||
|
}
|
||||||
108
widget/load.go
Normal file
108
widget/load.go
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
"github.com/yaoapp/yao/widget/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Widgets the loaded widgets
|
||||||
|
var Widgets = map[string]*DSL{}
|
||||||
|
|
||||||
|
// Load Widgets
|
||||||
|
func Load(cfg config.Config) error {
|
||||||
|
|
||||||
|
exts := []string{"*.wid.yao", "*.wid.json", "*.wid.jsonc"}
|
||||||
|
messages := []string{}
|
||||||
|
|
||||||
|
err := application.App.Walk("widgets", func(root, file string, isdir bool) error {
|
||||||
|
if isdir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id := share.ID(root, file)
|
||||||
|
_, err := LoadFile(file, id)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, exts...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadInstances load widget instances
|
||||||
|
func LoadInstances() error {
|
||||||
|
messages := []string{}
|
||||||
|
for _, widget := range Widgets {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile load widget by file
|
||||||
|
func LoadFile(file string, id string) (*DSL, error) {
|
||||||
|
|
||||||
|
data, err := application.App.Read(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadSource(data, file, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSource load widget by source
|
||||||
|
func LoadSource(data []byte, file, id string) (*DSL, error) {
|
||||||
|
|
||||||
|
widget := &DSL{ID: id, File: file, Instances: sync.Map{}}
|
||||||
|
err := application.Parse(file, data, &widget)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if widget.Remote != nil {
|
||||||
|
|
||||||
|
widget.FS, err = driver.NewConnector(widget.ID, widget.Remote.Connector, widget.Remote.Table, widget.Remote.Reload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
widget.FS = driver.NewSource(widget.Path, widget.Extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the widget process
|
||||||
|
err = widget.RegisterProcess()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the widget api
|
||||||
|
err = widget.RegisterAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
Widgets[id] = widget
|
||||||
|
return Widgets[id], nil
|
||||||
|
}
|
||||||
29
widget/load_test.go
Normal file
29
widget/load_test.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou/api"
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
Load(config.Conf)
|
||||||
|
check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(t *testing.T) {
|
||||||
|
assert.NotNil(t, Widgets["dyform"])
|
||||||
|
assert.NotNil(t, api.APIs["__yao.widget.dyform"])
|
||||||
|
assert.NotNil(t, process.Handlers["widgets.dyform.find"])
|
||||||
|
assert.NotNil(t, process.Handlers["widgets.dyform.delete"])
|
||||||
|
assert.NotNil(t, process.Handlers["widgets.dyform.cancel"])
|
||||||
|
assert.NotNil(t, process.Handlers["widgets.dyform.save"])
|
||||||
|
assert.NotNil(t, process.Handlers["widgets.dyform.setting"])
|
||||||
|
}
|
||||||
50
widget/process.go
Normal file
50
widget/process.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
process.RegisterGroup("widget", map[string]process.Handler{
|
||||||
|
"Save": ProcessSave,
|
||||||
|
"Remove": ProcessRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessSave process the widget save
|
||||||
|
func ProcessSave(process *process.Process) interface{} {
|
||||||
|
process.ValidateArgNums(3)
|
||||||
|
name := process.ArgsString(0)
|
||||||
|
file := process.ArgsString(1)
|
||||||
|
source := process.ArgsMap(2)
|
||||||
|
|
||||||
|
widget, ok := Widgets[name]
|
||||||
|
if !ok {
|
||||||
|
exception.New("The widget %s not found", 404, name).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := widget.Save(file, source)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500, name, err).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessRemove process the widget save
|
||||||
|
func ProcessRemove(process *process.Process) interface{} {
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
name := process.ArgsString(0)
|
||||||
|
file := process.ArgsString(1)
|
||||||
|
widget, ok := Widgets[name]
|
||||||
|
if !ok {
|
||||||
|
exception.New("The widget %s not found", 404, name).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := widget.Remove(file)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500, name).Throw()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
widget/process_test.go
Normal file
51
widget/process_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProcessSave(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
iform := preare(t)[1]
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
process.New("widget.Save", "dyform", "feedback/new.form.yao", map[string]interface{}{}).Run()
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
process.New("widget.Save", "iform", "feedback/new.form.yao", map[string]interface{}{"columns": []interface{}{}}).Run()
|
||||||
|
})
|
||||||
|
|
||||||
|
defer iform.Remove("feedback/new.form.yao")
|
||||||
|
|
||||||
|
instance, ok := iform.Instances.Load("feedback.new")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
assert.Equal(t, "feedback.new", instance.(*Instance).id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessRemove(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
iform := preare(t)[1]
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
process.New("widget.Remove", "dyform", "feedback/new.form.yao").Run()
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
process.New("widget.Remove", "iform", "feedback/new.form.yao").Run()
|
||||||
|
})
|
||||||
|
|
||||||
|
defer iform.Remove("feedback/new.form.yao")
|
||||||
|
|
||||||
|
_, ok := iform.Instances.Load("feedback.new")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
53
widget/types.go
Normal file
53
widget/types.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DSL is the widget DSL
|
||||||
|
type DSL struct {
|
||||||
|
ID string `json:"-"`
|
||||||
|
File string `json:"-"`
|
||||||
|
Instances sync.Map `json:"-"`
|
||||||
|
FS FS `json:"-"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
Extensions []string `json:"extensions,omitempty"`
|
||||||
|
Remote *RemoteDSL `json:"remote,omitempty"`
|
||||||
|
Loader LoaderDSL `json:"loader"`
|
||||||
|
Process map[string]string `json:"process,omitempty"`
|
||||||
|
API *api.HTTP `json:"api,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteDSL is the remote widget DSL
|
||||||
|
type RemoteDSL struct {
|
||||||
|
Connector string `json:"connector,omitempty"`
|
||||||
|
Table string `json:"table,omitempty"`
|
||||||
|
Reload bool `json:"reload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoaderDSL is the loader widget DSL
|
||||||
|
type LoaderDSL struct {
|
||||||
|
Load string `json:"load,omitempty"`
|
||||||
|
Reload string `json:"reload,omitempty"`
|
||||||
|
Unload string `json:"unload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance is the widget instance
|
||||||
|
type Instance struct {
|
||||||
|
source map[string]interface{}
|
||||||
|
dsl interface{}
|
||||||
|
loader LoaderDSL
|
||||||
|
id string
|
||||||
|
widget string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FS is the DSL File system
|
||||||
|
type FS interface {
|
||||||
|
Walk(cb func(id string, source map[string]interface{})) error
|
||||||
|
Save(file string, source map[string]interface{}) error
|
||||||
|
Remove(file string) error
|
||||||
|
}
|
||||||
255
widget/widget.go
255
widget/widget.go
|
|
@ -1,112 +1,189 @@
|
||||||
package widget
|
package widget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/yaoapp/gou/application"
|
"github.com/yaoapp/gou/api"
|
||||||
"github.com/yaoapp/gou/widget"
|
"github.com/yaoapp/gou/process"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load Widgets
|
// LoadInstances load the widget instances
|
||||||
func Load(cfg config.Config) error {
|
func (widget *DSL) LoadInstances() error {
|
||||||
|
|
||||||
register := moduleRegister()
|
messages := []string{}
|
||||||
return application.App.Walk("widgets", func(root, file string, isdir bool) error {
|
err := widget.FS.Walk(func(id string, source map[string]interface{}) {
|
||||||
if isdir {
|
instance := NewInstance(widget.ID, id, source, widget.Loader)
|
||||||
return nil
|
err := instance.Load()
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, fmt.Sprintf("%v %s", id, err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
path := filepath.Dir(file)
|
widget.Instances.Store(id, instance)
|
||||||
_, err := widget.Load(path, nil, register)
|
})
|
||||||
return err
|
|
||||||
|
|
||||||
}, "widget.yao", "widget.json", "widget.jsonc")
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("widgets.%s Load: %s", widget.ID, strings.Join(messages, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
// var root = filepath.Join(cfg.Root, "widgets")
|
return err
|
||||||
// return LoadFrom(root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// // LoadFrom widget
|
// ReloadInstances reload the widget instances
|
||||||
// func LoadFrom(dir string) error {
|
func (widget *DSL) ReloadInstances() error {
|
||||||
|
|
||||||
// register := moduleRegister()
|
messages := []string{}
|
||||||
|
|
||||||
// if share.DirNotExists(dir) {
|
// Reload the remote widget
|
||||||
// return fmt.Errorf("%s does not exists", dir)
|
widget.Instances.Range(func(key, value interface{}) bool {
|
||||||
// }
|
if instance, ok := value.(*Instance); ok {
|
||||||
|
err := instance.Reload()
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, fmt.Sprintf("%v %s", key, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
// paths, err := ioutil.ReadDir(dir)
|
if len(messages) > 0 {
|
||||||
// if err != nil {
|
return fmt.Errorf("widgets.%s Reload: %s", widget.ID, strings.Join(messages, ";"))
|
||||||
// return err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// for _, path := range paths {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// if !path.IsDir() {
|
// UnloadInstances unload the widget instances
|
||||||
// continue
|
func (widget *DSL) UnloadInstances() error {
|
||||||
// }
|
|
||||||
|
|
||||||
// name := path.Name()
|
messages := []string{}
|
||||||
// if _, err := os.Stat(filepath.Join(dir, name, "widget.json")); errors.Is(err, os.ErrNotExist) {
|
|
||||||
// // path/to/whatever does not exist
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// w, err := gou.LoadWidget(filepath.Join(dir, name), name, register)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Load instances
|
// Unload the remote widget
|
||||||
// err = w.Load()
|
widget.Instances.Range(func(key, value interface{}) bool {
|
||||||
// if err != nil {
|
if instance, ok := value.(*Instance); ok {
|
||||||
// return err
|
err := instance.Unload()
|
||||||
// }
|
if err != nil {
|
||||||
// }
|
messages = append(messages, fmt.Sprintf("%v %s", key, err.Error()))
|
||||||
|
}
|
||||||
|
widget.Instances.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
// return err
|
return true
|
||||||
// }
|
})
|
||||||
|
|
||||||
func moduleRegister() widget.ModuleRegister {
|
if len(messages) > 0 {
|
||||||
return widget.ModuleRegister{
|
return fmt.Errorf("widgets.%s Unload: %s", widget.ID, strings.Join(messages, ";"))
|
||||||
// "Apis": func(name string, source []byte) error {
|
}
|
||||||
// _, err := api.Load(string(source), name)
|
|
||||||
// log.Trace("[Widget] Register api %s", name)
|
return nil
|
||||||
// if err != nil {
|
}
|
||||||
// log.Error("[Widget] Register api %s %v", name, err)
|
|
||||||
// }
|
// RegisterProcess register the widget process
|
||||||
// return err
|
func (widget *DSL) RegisterProcess() error {
|
||||||
// },
|
if widget.Process == nil {
|
||||||
// "Models": func(name string, source []byte) error {
|
return nil
|
||||||
// _, err := model.Load(string(source), name)
|
}
|
||||||
// log.Trace("[Widget] Register model %s", name)
|
|
||||||
// if err != nil {
|
handlers := map[string]process.Handler{}
|
||||||
// log.Error("[Widget] Register model %s %v", name, err)
|
for name, processName := range widget.Process {
|
||||||
// }
|
|
||||||
// return err
|
if processName == "" {
|
||||||
// },
|
continue
|
||||||
// "Tables": func(name string, source []byte) error {
|
}
|
||||||
// log.Trace("[Widget] Register table %s", name)
|
handlers[name] = widget.handler(processName)
|
||||||
// _, err := table.LoadTable(string(source), name)
|
}
|
||||||
// if err != nil {
|
|
||||||
// log.Error("[Widget] Register table %s %v", name, err)
|
process.RegisterGroup(fmt.Sprintf("widgets.%s", widget.ID), handlers)
|
||||||
// }
|
return nil
|
||||||
// return nil
|
}
|
||||||
// },
|
|
||||||
// "Tasks": func(name string, source []byte) error {
|
// RegisterAPI register the widget API
|
||||||
// log.Trace("[Widget] Register task %s", name)
|
func (widget *DSL) RegisterAPI() error {
|
||||||
// _, err := gou.LoadTask(string(source), name)
|
|
||||||
// if err != nil {
|
if widget.API == nil {
|
||||||
// log.Error("[Widget] Register task %s %v", name, err)
|
return nil
|
||||||
// }
|
}
|
||||||
// return nil
|
|
||||||
// },
|
id := fmt.Sprintf("__yao.widget.%s", widget.ID)
|
||||||
// "Schedules": func(name string, source []byte) error {
|
widget.API.Group = fmt.Sprintf("/__yao/widget/%s", widget.ID)
|
||||||
// log.Trace("[Widget] Register schedule %s", name)
|
|
||||||
// _, err := gou.LoadSchedule(string(source), name)
|
// Register the widget API
|
||||||
// if err != nil {
|
api.APIs[id] = &api.API{
|
||||||
// log.Error("[Widget] Register schedule %s %v", name, err)
|
ID: fmt.Sprintf("__yao.widget.%s", widget.ID),
|
||||||
// }
|
File: widget.File,
|
||||||
// return nil
|
HTTP: *widget.API,
|
||||||
// },
|
Type: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the process handler
|
||||||
|
func (widget *DSL) handler(processName string) process.Handler {
|
||||||
|
|
||||||
|
return func(p *process.Process) interface{} {
|
||||||
|
|
||||||
|
p.ValidateArgNums(1)
|
||||||
|
instanceID := p.ArgsString(0)
|
||||||
|
instance, ok := widget.Instances.Load(instanceID)
|
||||||
|
if !ok {
|
||||||
|
exception.New("The widget %s instance %s not found", 404, widget.ID, instanceID).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []interface{}{}
|
||||||
|
args = append(args, p.Args...)
|
||||||
|
args = append(args, instance.(*Instance).dsl)
|
||||||
|
|
||||||
|
return process.New(processName, args...).Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the widget source to file
|
||||||
|
func (widget *DSL) Save(file string, source map[string]interface{}) error {
|
||||||
|
|
||||||
|
err := widget.FS.Save(file, source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := share.ID("", file)
|
||||||
|
instance := NewInstance(widget.ID, id, source, widget.Loader)
|
||||||
|
|
||||||
|
// new instance
|
||||||
|
old, ok := widget.Instances.Load(id)
|
||||||
|
if !ok {
|
||||||
|
err := instance.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.Instances.Store(id, instance)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the instance
|
||||||
|
if widget.Remote != nil && widget.Remote.Reload {
|
||||||
|
instance.dsl = old.(*Instance).dsl
|
||||||
|
err = instance.Reload()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.Instances.Store(id, instance)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the widget source file
|
||||||
|
func (widget *DSL) Remove(file string) error {
|
||||||
|
|
||||||
|
err := widget.FS.Remove(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := share.ID("", file)
|
||||||
|
widget.Instances.Delete(id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,237 @@
|
||||||
package widget
|
package widget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/yaoapp/gou/widget"
|
"github.com/yaoapp/gou/api"
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/xun/capsule"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/test"
|
"github.com/yaoapp/yao/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestWidgetLoadInstances(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
for _, widget := range preare(t) {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
Load(config.Conf)
|
instance, ok := widget.Instances.Load("feedback")
|
||||||
check(t)
|
if !ok {
|
||||||
}
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
func check(t *testing.T) {
|
assert.Equal(t, "feedback", instance.(*Instance).id)
|
||||||
ids := map[string]bool{}
|
assert.Equal(t, "feedback", instance.(*Instance).dsl.(map[string]interface{})["id"])
|
||||||
for id := range widget.Widgets {
|
|
||||||
ids[id] = true
|
|
||||||
}
|
}
|
||||||
assert.True(t, ids["dyform"])
|
}
|
||||||
|
|
||||||
|
func TestWidgetReLoadInstances(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
for _, widget := range preare(t) {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, ok := widget.Instances.Load("feedback")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = widget.ReloadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "feedback", instance.(*Instance).id)
|
||||||
|
assert.Equal(t, "feedback", instance.(*Instance).dsl.(map[string]interface{})["id"])
|
||||||
|
assert.Equal(t, true, instance.(*Instance).dsl.(map[string]interface{})["tests.reload"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWidgetUnLoadInstances(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
for _, widget := range preare(t) {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, ok := widget.Instances.Load("feedback")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "feedback", instance.(*Instance).id)
|
||||||
|
assert.Equal(t, "feedback", instance.(*Instance).dsl.(map[string]interface{})["id"])
|
||||||
|
|
||||||
|
err = widget.UnloadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = widget.Instances.Load("feedback")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWidgetRegisterProcess(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
for _, widget := range preare(t) {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("widgets.%s.Setting", widget.ID)
|
||||||
|
res := process.New(name, "feedback").Run()
|
||||||
|
assert.Equal(t, "feedback", res.(map[string]interface{})["id"])
|
||||||
|
assert.Equal(t, "feedback", res.(map[string]interface{})["tests.id"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWidgetRegisterAPI(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
for _, widget := range preare(t) {
|
||||||
|
err := widget.LoadInstances()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := testRouter(t)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
url := fmt.Sprintf("/api/__yao/widget/%s/feedback/setting", widget.ID)
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
router.ServeHTTP(response, req)
|
||||||
|
|
||||||
|
res := map[string]interface{}{}
|
||||||
|
err = jsoniter.Unmarshal(response.Body.Bytes(), &res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "feedback", res["id"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWidgetSaveCreate(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
dyform := preare(t)[0]
|
||||||
|
iform := preare(t)[1]
|
||||||
|
|
||||||
|
err := dyform.Save("feedback/new.form.yao", map[string]interface{}{})
|
||||||
|
assert.NotEmpty(t, err)
|
||||||
|
|
||||||
|
err = iform.Save("feedback/new.form.yao", map[string]interface{}{"columns": []interface{}{}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer iform.Remove("feedback/new.form.yao")
|
||||||
|
|
||||||
|
instance, ok := iform.Instances.Load("feedback.new")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
assert.Equal(t, "feedback.new", instance.(*Instance).id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWidgetSaveUpdate(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
iform := preare(t)[1]
|
||||||
|
|
||||||
|
err := iform.Save("feedback/new.form.yao", map[string]interface{}{"columns": []interface{}{}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer iform.Remove("feedback/new.form.yao")
|
||||||
|
|
||||||
|
err = iform.Save("feedback/new.form.yao", map[string]interface{}{"columns": []interface{}{}, "foo": "bar"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, ok := iform.Instances.Load("feedback.new")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("feedback instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "feedback.new", instance.(*Instance).id)
|
||||||
|
assert.Equal(t, "bar", instance.(*Instance).dsl.(map[string]interface{})["foo"])
|
||||||
|
assert.Equal(t, true, instance.(*Instance).dsl.(map[string]interface{})["tests.reload"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func preare(t *testing.T) []*DSL {
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
qb := capsule.Global.Query()
|
||||||
|
qb.Table("dsl_iform").Insert(map[string]interface{}{
|
||||||
|
"file": "feedback.iform.yao",
|
||||||
|
"source": `{
|
||||||
|
"columns": [
|
||||||
|
[
|
||||||
|
{ "type": "Title", "label": "Feedback Information" },
|
||||||
|
{ "type": "Input", "label": "Name" },
|
||||||
|
{ "type": "Input", "label": "Email" }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "type": "Title", "label": "Feedback Details" },
|
||||||
|
{ "type": "Textarea", "label": "Message" },
|
||||||
|
{ "type": "Checkbox", "label": "Anonymous" }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"actions": {
|
||||||
|
"left": [
|
||||||
|
{
|
||||||
|
"type": "api",
|
||||||
|
"text": "Submit Feedback",
|
||||||
|
"api": "/api/__yao/widget/dyform/save",
|
||||||
|
"isPrimary": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"right": [
|
||||||
|
{
|
||||||
|
"type": "info",
|
||||||
|
"text": "Help",
|
||||||
|
"info": "Need assistance? Click here."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "api",
|
||||||
|
"text": "Cancel",
|
||||||
|
"process": "widget.dyform.Cancel"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return []*DSL{Widgets["dyform"], Widgets["iform"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRouter(t *testing.T, middlewares ...gin.HandlerFunc) *gin.Engine {
|
||||||
|
router := gin.New()
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
router.Use(middlewares...)
|
||||||
|
api.SetGuards(map[string]gin.HandlerFunc{"bearer-jwt": func(ctx *gin.Context) {}})
|
||||||
|
api.SetRoutes(router, "/api")
|
||||||
|
return router
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue