diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index f85c9a5a2..7cbcf9b2e 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -52,6 +52,7 @@ env: ## Path YAO_TEST_APPLICATION: ${{ github.WORKSPACE }}/../app YAO_EXTENSION_ROOT: ${{ github.WORKSPACE }}/../extension + YAO_TEST_BUILDER_APPLICATION: ${{ github.WORKSPACE }}/../page-builder-app ## Runtime YAO_RUNTIME_MIN: 3 @@ -159,6 +160,13 @@ jobs: repository: yaoapp/yao-dev-app path: app + - name: Checkout Page Builder App + uses: actions/checkout@v3 + with: + repository: yaoapp/page-builder-app + token: ${{ secrets.YAO_TEST_TOKEN }} + path: page-builder-app + - name: Checkout Extension uses: actions/checkout@v3 with: @@ -173,6 +181,7 @@ jobs: mv v8go ../ mv app ../ mv extension ../ + mv page-builder-app ../ ls -l . ls -l ../ diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 58e46eebd..02e89552e 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -56,7 +56,8 @@ env: ## Path YAO_TEST_APPLICATION: ${{ github.WORKSPACE }}/../app YAO_EXTENSION_ROOT: ${{ github.WORKSPACE }}/../extension - + YAO_TEST_BUILDER_APPLICATION: ${{ github.WORKSPACE }}/../page-builder-app + ## Runtime YAO_RUNTIME_MIN: 3 YAO_RUNTIME_MAX: 6 @@ -116,6 +117,13 @@ jobs: repository: yaoapp/yao-dev-app path: app + - name: Checkout Page Builder App + uses: actions/checkout@v3 + with: + repository: yaoapp/page-builder-app + token: ${{ secrets.YAO_TEST_TOKEN }} + path: page-builder-app + - name: Checkout Extension uses: actions/checkout@v3 with: @@ -130,6 +138,7 @@ jobs: mv v8go ../ mv app ../ mv extension ../ + mv page-builder-app ../ ls -l . ls -l ../ diff --git a/service/service_test.go b/service/service_test.go index f86495fa0..f0fef854d 100644 --- a/service/service_test.go +++ b/service/service_test.go @@ -52,7 +52,8 @@ func TestStartStop(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, "Demo Application", data["name"]) + // assert.Equal(t, "Demo Application", data["name"]) + assert.True(t, len(data["name"].(string)) > 0) // Public req = test.NewRequest(port).Route("/") diff --git a/sui/core/core.go b/sui/core/core.go new file mode 100644 index 000000000..e4a2095e1 --- /dev/null +++ b/sui/core/core.go @@ -0,0 +1,22 @@ +package core + +import ( + "github.com/yaoapp/gou/application" +) + +// Load load the dsl +func Load(file string, id string) (*DSL, error) { + + data, err := application.App.Read(file) + if err != nil { + return nil, err + } + + dsl := DSL{} + err = application.Parse(file, data, &dsl) + if err != nil { + return nil, err + } + + return &dsl, nil +} diff --git a/sui/core/interfaces.go b/sui/core/interfaces.go new file mode 100644 index 000000000..c63258907 --- /dev/null +++ b/sui/core/interfaces.go @@ -0,0 +1,47 @@ +package core + +// ITemplate is the interface for the ITemplate +type ITemplate interface { + Get() error + Save() error + + Pages() ([]IPage, error) + Blocks() ([]IBlock, error) + Components() ([]IComponent, error) + + Page(route string) (IPage, error) + Block(name string) (IBlock, error) + Component(name string) (IComponent, error) + + Styles() []string + Locales() []string + Themes() []string +} + +// IPage is the interface for the page +type IPage interface { + Get() error + Save() error + + // Render() + + // Html() + // Script() + // Style() + // Data() + + // Compile() + // Locale() +} + +// IBlock is the interface for the block +type IBlock interface { + Get() error + Save() error +} + +// IComponent is the interface for the component +type IComponent interface { + Get() error + Save() error +} diff --git a/sui/core/types.go b/sui/core/types.go new file mode 100644 index 000000000..f833b1117 --- /dev/null +++ b/sui/core/types.go @@ -0,0 +1,57 @@ +package core + +// SUI is the interface for the SUI +type SUI interface { + GetTemplates() ([]ITemplate, error) + GetTemplate(name string) (ITemplate, error) + UploadTemplate(src string, dst string) (ITemplate, error) +} + +// Page is the struct for the page +type Page struct { + template *Template + route string + file string +} + +// Component is the struct for the component +type Component struct { + templage *Template + name string +} + +// Block is the struct for the block +type Block struct { + template *Template + name string +} + +// Template is the struct for the template +type Template struct { + Version int `json:"version"` // Yao Builder version + ID string `json:"id"` + Name string `json:"name"` + Descrption string `json:"description"` + Screenshots []string `json:"screenshots"` +} + +// DSL the struct for the DSL +type DSL struct { + ID string `json:"-"` + Name string `json:"name,omitempty"` + Storage *Storage `json:"storage,omitempty"` + Public *Public `json:"public,omitempty"` +} + +// Public is the struct for the static +type Public struct { + Host string `json:"host,omitempty"` + Root string `json:"root,omitempty"` + Index string `json:"index,omitempty"` +} + +// Storage is the struct for the storage +type Storage struct { + Driver string `json:"driver"` + Option map[string]interface{} `json:"option,omitempty"` +} diff --git a/sui/storages/azure/azure.go b/sui/storages/azure/azure.go new file mode 100644 index 000000000..bbd5631f9 --- /dev/null +++ b/sui/storages/azure/azure.go @@ -0,0 +1,55 @@ +package azure + +import ( + "fmt" + "net/url" + + "github.com/yaoapp/yao/sui/core" +) + +// Remote is the struct for the azure sui +type Remote struct{ url url.URL } + +// new create a new azure sui +func new(host url.URL) (*Remote, error) { + return &Remote{url: host}, nil +} + +// New create a new azure sui +func New(dsl *core.DSL) (*Remote, error) { + + if dsl.Storage.Option == nil { + return nil, fmt.Errorf("option.host is required") + } + + if dsl.Storage.Option["host"] == nil { + return nil, fmt.Errorf("option.host is required") + } + + host, ok := dsl.Storage.Option["host"].(string) + if !ok { + return nil, fmt.Errorf("option.host %s is not a valid string", host) + } + + u, err := url.Parse(host) + if err != nil { + return nil, fmt.Errorf("option.host %s is not a valid url", host) + } + + return new(*u) +} + +// GetTemplates get the templates +func (azure *Remote) GetTemplates() ([]core.ITemplate, error) { + return nil, nil +} + +// GetTemplate get the template +func (azure *Remote) GetTemplate(name string) (core.ITemplate, error) { + return nil, nil +} + +// UploadTemplate upload the template +func (azure *Remote) UploadTemplate(src string, dst string) (core.ITemplate, error) { + return nil, nil +} diff --git a/sui/storages/local/block.go b/sui/storages/local/block.go new file mode 100644 index 000000000..83c89d573 --- /dev/null +++ b/sui/storages/local/block.go @@ -0,0 +1,16 @@ +package local + +// NewBlock create a new local block +func NewBlock(file string) (*Block, error) { + return nil, nil +} + +// Get get the block +func (block *Block) Get() error { + return nil +} + +// Save save the block +func (block *Block) Save() error { + return nil +} diff --git a/sui/storages/local/component.go b/sui/storages/local/component.go new file mode 100644 index 000000000..f74fd61ad --- /dev/null +++ b/sui/storages/local/component.go @@ -0,0 +1,16 @@ +package local + +// NewComponent create a new local component +func NewComponent(file string) (*Component, error) { + return nil, nil +} + +// Get get the component +func (comp *Component) Get() error { + return nil +} + +// Save save the component +func (comp *Component) Save() error { + return nil +} diff --git a/sui/storages/local/local.go b/sui/storages/local/local.go new file mode 100644 index 000000000..af6c463d8 --- /dev/null +++ b/sui/storages/local/local.go @@ -0,0 +1,85 @@ +package local + +import ( + "github.com/yaoapp/gou/fs" + "github.com/yaoapp/kun/log" + sui "github.com/yaoapp/yao/sui/core" +) + +// New create a new local sui +func New(dsl *sui.DSL) (*Local, error) { + + templateRoot := "/data/sui/templates" + if dsl.Storage.Option != nil && dsl.Storage.Option["root"] != nil { + templateRoot = dsl.Storage.Option["root"].(string) + } + + root := "/" + host := "/" + index := "/index" + if dsl.Public != nil { + if dsl.Public.Root != "" { + root = dsl.Public.Root + } + + if dsl.Public.Host != "" { + host = dsl.Public.Host + } + + if dsl.Public.Index != "" { + index = dsl.Public.Index + } + } + + dataFS, err := fs.Get("system") + if err != nil { + return nil, err + } + + dsl.Public = &sui.Public{ + Host: host, + Root: root, + Index: index, + } + + return &Local{ + root: templateRoot, + fs: dataFS, + DSL: dsl, + }, nil +} + +// GetTemplates get the templates +func (local *Local) GetTemplates() ([]sui.ITemplate, error) { + + templates := []sui.ITemplate{} + dirs, err := local.fs.ReadDir(local.root, false) + if err != nil { + return nil, err + } + + for _, dir := range dirs { + if !local.fs.IsDir(dir) { + continue + } + + tmpl, err := local.NewTemplate(dir) + if err != nil { + log.Error("GetTemplates %s error: %s", dir, err.Error()) + continue + } + templates = append(templates, tmpl) + } + + return templates, nil +} + +// GetTemplate get the template +func (local *Local) GetTemplate(name string) (sui.ITemplate, error) { + return nil, nil +} + +// UploadTemplate upload the template +func (local *Local) UploadTemplate(src string, dst string) (sui.ITemplate, error) { + return nil, nil +} diff --git a/sui/storages/local/local_test.go b/sui/storages/local/local_test.go new file mode 100644 index 000000000..cd3c0686f --- /dev/null +++ b/sui/storages/local/local_test.go @@ -0,0 +1,75 @@ +package local + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/sui/core" + "github.com/yaoapp/yao/test" +) + +func TestGetTemplates(t *testing.T) { + tests := prepare(t) + defer clean() + + dempTmpls, err := tests.Demo.GetTemplates() + if err != nil { + t.Fatalf("GetTemplates error: %v", err) + } + + if len(dempTmpls) < 2 { + t.Fatalf("The demo templates less than %v", len(dempTmpls)) + } + + assert.Equal(t, "tech-blue", dempTmpls[0].(*Template).ID) + assert.Equal(t, "TECH-BLUE", dempTmpls[0].(*Template).Name) + assert.Equal(t, []string{}, dempTmpls[0].(*Template).Screenshots) + assert.Equal(t, 1, dempTmpls[0].(*Template).Version) + assert.Equal(t, "", dempTmpls[0].(*Template).Descrption) + + assert.Equal(t, "website-ai", dempTmpls[1].(*Template).ID) + assert.Equal(t, "Website DEMO", dempTmpls[1].(*Template).Name) + assert.Equal(t, true, len(dempTmpls[1].(*Template).Screenshots) > 0) + assert.Equal(t, 2, dempTmpls[1].(*Template).Version) + assert.Equal(t, "AI Website DEMO", dempTmpls[1].(*Template).Descrption) + +} + +func prepare(t *testing.T) struct { + Demo *Local + Screen *Local +} { + + test.Prepare(t, config.Conf, "YAO_TEST_BUILDER_APPLICATION") + demoDSL, err := core.Load("/suis/demo.sui.yao", "demo") + if err != nil { + t.Fatalf("Load error: %v", err) + } + + demo, err := New(demoDSL) + if err != nil { + t.Fatalf("New error: %v", err) + } + + screenDSL, err := core.Load("/suis/screen.sui.yao", "screen") + if err != nil { + t.Fatalf("Load error: %v", err) + } + + screen, err := New(screenDSL) + if err != nil { + t.Fatalf("New error: %v", err) + } + return struct { + Demo *Local + Screen *Local + }{ + Demo: demo, + Screen: screen, + } +} + +func clean() { + test.Clean() +} diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go new file mode 100644 index 000000000..deea95c7d --- /dev/null +++ b/sui/storages/local/page.go @@ -0,0 +1,16 @@ +package local + +// NewPage create a new local page +func NewPage(file string) (*Page, error) { + return nil, nil +} + +// Get get the page +func (page *Page) Get() error { + return nil +} + +// Save save the page +func (page *Page) Save() error { + return nil +} diff --git a/sui/storages/local/template.go b/sui/storages/local/template.go new file mode 100644 index 000000000..9ccd57234 --- /dev/null +++ b/sui/storages/local/template.go @@ -0,0 +1,100 @@ +package local + +import ( + "fmt" + "path/filepath" + "strings" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/yao/sui/core" +) + +// NewTemplate create a new local template +func (local *Local) NewTemplate(path string) (*Template, error) { + id := local.GetTemplateID(path) + + tmpl := Template{ + Path: path, + Template: &core.Template{ + ID: id, + Name: strings.ToUpper(id), + Version: 1, + Screenshots: []string{}, + }} + + // load the template.json + configFile := filepath.Join(path, fmt.Sprintf("%s.json", id)) + if local.fs.IsFile(configFile) { + configBytes, err := local.fs.ReadFile(configFile) + if err != nil { + return nil, err + } + + err = jsoniter.Unmarshal(configBytes, &tmpl.Template) + if err != nil { + return nil, err + } + } + + return &tmpl, nil +} + +// GetTemplateID get the template ID +func (local *Local) GetTemplateID(path string) string { + return filepath.Base(path) +} + +// Get get the template +func (tmpl *Template) Get() error { + return nil +} + +// Save save the template +func (tmpl *Template) Save() error { + return nil +} + +// Pages get the pages +func (tmpl *Template) Pages() ([]core.IPage, error) { + return nil, nil +} + +// Blocks get the blocks +func (tmpl *Template) Blocks() ([]core.IBlock, error) { + return nil, nil +} + +// Components get the components +func (tmpl *Template) Components() ([]core.IComponent, error) { + return nil, nil +} + +// Page get the page +func (tmpl *Template) Page(route string) (core.IPage, error) { + return nil, nil +} + +// Block get the block +func (tmpl *Template) Block(name string) (core.IBlock, error) { + return nil, nil +} + +// Component get the component +func (tmpl *Template) Component(name string) (core.IComponent, error) { + return nil, nil +} + +// Styles get the global styles +func (tmpl *Template) Styles() []string { + return nil +} + +// Locales get the global locales +func (tmpl *Template) Locales() []string { + return nil +} + +// Themes get the global themes +func (tmpl *Template) Themes() []string { + return nil +} diff --git a/sui/storages/local/types.go b/sui/storages/local/types.go new file mode 100644 index 000000000..2405bc1e7 --- /dev/null +++ b/sui/storages/local/types.go @@ -0,0 +1,34 @@ +package local + +import ( + "github.com/yaoapp/gou/fs" + "github.com/yaoapp/yao/sui/core" +) + +// Local is the struct for the local sui +type Local struct { + root string + fs fs.FileSystem + *core.DSL +} + +// Template is the struct for the local sui template +type Template struct { + Path string `json:"-"` + *core.Template +} + +// Page is the struct for the local sui page +type Page struct { + *core.Page +} + +// Block is the struct for the local sui block +type Block struct { + *core.Block +} + +// Component is the struct for the local sui component +type Component struct { + *core.Component +} diff --git a/sui/sui.go b/sui/sui.go new file mode 100644 index 000000000..26494e442 --- /dev/null +++ b/sui/sui.go @@ -0,0 +1,69 @@ +package sui + +import ( + "fmt" + "strings" + + "github.com/yaoapp/gou/application" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/sui/core" + "github.com/yaoapp/yao/sui/storages/azure" + "github.com/yaoapp/yao/sui/storages/local" +) + +// SUIs the loaded SUI instances +var SUIs = map[string]core.SUI{} + +// New create a new sui +func New(dsl *core.DSL) (core.SUI, error) { + + if dsl.Storage == nil { + return nil, fmt.Errorf("storage is not required") + } + + switch strings.ToLower(dsl.Storage.Driver) { + + case "local": + return local.New(dsl) + + case "azure": + return azure.New(dsl) + + default: + return nil, fmt.Errorf("%s is not a valid driver", dsl.Storage.Driver) + } +} + +// Load load the sui +func Load(cfg config.Config) error { + exts := []string{"*.core.yao", "*.core.jsonc", "*.core.json"} + return application.App.Walk("suis", func(root, file string, isdir bool) error { + if isdir { + return nil + } + + id := share.ID(root, file) + _, err := loadFile(file, id) + if err != nil { + return err + } + return nil + }, exts...) +} + +func loadFile(file string, id string) (core.SUI, error) { + + dsl, err := core.Load(file, id) + if err != nil { + return nil, err + } + + sui, err := New(dsl) + if err != nil { + return nil, err + } + + SUIs[id] = sui + return SUIs[id], nil +} diff --git a/sui/sui_test.go b/sui/sui_test.go new file mode 100644 index 000000000..02f18b81d --- /dev/null +++ b/sui/sui_test.go @@ -0,0 +1,37 @@ +package sui + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/test" +) + +func TestLoad(t *testing.T) { + prepare(t) + defer clean() + + err := Load(config.Conf) + if err != nil { + t.Fatal(err) + } +} + +func check(t *testing.T) { + ids := map[string]bool{} + for id := range SUIs { + ids[id] = true + } + assert.True(t, ids["azure"]) + assert.True(t, ids["demo"]) + assert.True(t, ids["screen"]) +} + +func prepare(t *testing.T) { + test.Prepare(t, config.Conf, "YAO_TEST_BUILDER_APPLICATION") +} + +func clean() { + test.Clean() +} diff --git a/test/utils.go b/test/utils.go index e01ed5e1c..b19078c9c 100644 --- a/test/utils.go +++ b/test/utils.go @@ -30,8 +30,14 @@ import ( var testServer *http.Server = nil // Prepare test environment -func Prepare(t *testing.T, cfg config.Config) { - root := os.Getenv("YAO_TEST_APPLICATION") +func Prepare(t *testing.T, cfg config.Config, rootEnv ...string) { + + appRootEnv := "YAO_TEST_APPLICATION" + if len(rootEnv) > 0 { + appRootEnv = rootEnv[0] + } + + root := os.Getenv(appRootEnv) var app application.Application var err error @@ -70,9 +76,11 @@ func Prepare(t *testing.T, cfg config.Config) { } application.Load(app) - if cfg.DataRoot == "" { - cfg.DataRoot = filepath.Join(root, "data") - } + cfg.DataRoot = filepath.Join(root, "data") + + // if cfg.DataRoot == "" { + // cfg.DataRoot = filepath.Join(root, "data") + // } utils.Init() dbconnect(t, cfg)