[add] SUI & Page Builder API (10%)

This commit is contained in:
Max 2023-09-24 17:27:03 +08:00
parent d4695e76ae
commit 198fd88449
17 changed files with 663 additions and 7 deletions

View file

@ -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 ../

View file

@ -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 ../

View file

@ -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("/")

22
sui/core/core.go Normal file
View file

@ -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
}

47
sui/core/interfaces.go Normal file
View file

@ -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
}

57
sui/core/types.go Normal file
View file

@ -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"`
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

69
sui/sui.go Normal file
View file

@ -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
}

37
sui/sui_test.go Normal file
View file

@ -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()
}

View file

@ -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)