diff --git a/script/script.go b/script/script.go index 06157f0e..f2547bd8 100644 --- a/script/script.go +++ b/script/script.go @@ -15,7 +15,13 @@ func Load(cfg config.Config) error { if share.BUILDIN { return LoadBuildIn("scripts") } - return LoadFrom(filepath.Join(cfg.Root, "scripts")) + + err := LoadFrom(filepath.Join(cfg.Root, "scripts"), "") + if err != nil { + return err + } + + return LoadFrom(filepath.Join(cfg.Root, "services"), "__yao_service.") } // LoadBuildIn 从制品中读取 @@ -24,16 +30,17 @@ func LoadBuildIn(dir string) error { } // LoadFrom 从特定目录加载共享库 -func LoadFrom(dir string) error { +func LoadFrom(dir string, prefix string) error { if share.DirNotExists(dir) { - return fmt.Errorf("%s does not exists", dir) + log.Error("%s does not exists", dir) + return nil } // 加载共享脚本 err := share.Walk(dir, ".js", func(root, filename string) { name := share.SpecName(root, filename) - err := gou.Yao.Load(filename, name) + err := gou.Yao.Load(filename, fmt.Sprintf("%s%s", prefix, name)) if err != nil { log.Error("加载脚本失败 %s", err.Error()) } diff --git a/script/script_test.go b/script/script_test.go index a08dd02d..8f78c4ca 100644 --- a/script/script_test.go +++ b/script/script_test.go @@ -11,7 +11,7 @@ import ( func init() { rootLib := path.Join(os.Getenv("YAO_DEV"), "/tests/scripts") - LoadFrom(rootLib) + LoadFrom(rootLib, "") } func TestScript(t *testing.T) { res, err := gou.Yao.New("time", "hello").Call("world") diff --git a/widgets/app/app.go b/widgets/app/app.go index d66b9319..895dda25 100644 --- a/widgets/app/app.go +++ b/widgets/app/app.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" + "strconv" "strings" "time" @@ -21,9 +23,12 @@ import ( // // API: -// GET /api/__yao/app/setting -> Default process: yao.app.Xgen -// POST /api/__yao/app/setting -> Default process: yao.app.Xgen {"sid":"xxx", "lang":"zh-hk", "time": "2022-10-10 22:00:10"} -// GET /api/__yao/app/menu -> Default process: yao.app.Menu +// GET /api/__yao/app/setting -> Default process: yao.app.Xgen +// POST /api/__yao/app/setting -> Default process: yao.app.Xgen {"sid":"xxx", "lang":"zh-hk", "time": "2022-10-10 22:00:10"} +// GET /api/__yao/app/menu -> Default process: yao.app.Menu +// POST /api/__yao/app/check -> Default process: yao.app.Check +// POST /api/__yao/app/setup -> Default process: yao.app.Setup {"sid":"xxxx", ...} +// POST /api/__yao/app/service/:name -> Default process: yao.app.Service {"method":"Bar", "args":["hello", "world"]} // // Process: // yao.app.Setting Return the App DSL @@ -33,6 +38,7 @@ import ( // Setting the application setting var Setting *DSL +var regExcp = regexp.MustCompile("^Exception\\|([0-9]+):(.+)$") // LoadAndExport load app func LoadAndExport(cfg config.Config) error { @@ -154,6 +160,42 @@ func exportAPI() error { } http.Paths = append(http.Paths, path) + path = gou.Path{ + Label: "Setup", + Description: "Setup", + Path: "/setup", + Guard: "-", + Method: "POST", + Process: "yao.app.Setup", + In: []string{":payload"}, + Out: gou.Out{Status: 200, Type: "application/json"}, + } + http.Paths = append(http.Paths, path) + + path = gou.Path{ + Label: "Check", + Description: "Check", + Path: "/check", + Guard: "-", + Method: "POST", + Process: "yao.app.Check", + In: []string{":payload"}, + Out: gou.Out{Status: 200, Type: "application/json"}, + } + http.Paths = append(http.Paths, path) + + path = gou.Path{ + Label: "Serivce", + Description: "Serivce", + Path: "/service/:name", + Guard: "bearer-jwt", + Method: "POST", + Process: "yao.app.Serivce", + In: []string{"$param.name", ":payload"}, + Out: gou.Out{Status: 200, Type: "application/json"}, + } + http.Paths = append(http.Paths, path) + // api source source, err := jsoniter.Marshal(http) if err != nil { @@ -176,6 +218,95 @@ func exportProcess() { gou.RegisterProcessHandler("yao.app.xgen", processXgen) gou.RegisterProcessHandler("yao.app.menu", processMenu) gou.RegisterProcessHandler("yao.app.icons", processIcons) + gou.RegisterProcessHandler("yao.app.setup", processSetup) + gou.RegisterProcessHandler("yao.app.check", processCheck) + gou.RegisterProcessHandler("yao.app.service", processService) +} + +func processService(process *gou.Process) interface{} { + process.ValidateArgNums(2) + service := fmt.Sprintf("__yao_service.%s", process.ArgsString(0)) + payload := process.ArgsMap(1) + if payload == nil || len(payload) == 0 { + exception.New("content is required", 400).Throw() + } + + method, ok := payload["method"].(string) + if !ok || service == "" { + exception.New("method is required", 400).Throw() + } + + args := []interface{}{} + if v, ok := payload["args"].([]interface{}); ok { + args = v + } + + req := gou.Yao.New(service, method) + if process.Sid != "" { + req.WithSid(process.Sid) + } + + res, err := req.RootCall(args...) + if err != nil { + // parse Exception + code := 500 + message := err.Error() + match := regExcp.FindStringSubmatch(message) + if len(match) > 0 { + code, err = strconv.Atoi(match[1]) + if err == nil { + message = strings.TrimSpace(match[2]) + } + } + exception.New(message, code).Throw() + } + + return res +} + +func processCheck(process *gou.Process) interface{} { + process.ValidateArgNums(1) + payload := process.ArgsMap(0) + time.Sleep(3 * time.Second) + + if _, has := payload["error"]; has { + exception.New("Something error", 500).Throw() + } + return nil +} + +func processSetup(process *gou.Process) interface{} { + process.ValidateArgNums(1) + payload := process.ArgsMap(0) + time.Sleep(3 * time.Second) + + if _, has := payload["error"]; has { + exception.New("Something error", 500).Throw() + } + + lang := process.Lang() + if sid, has := payload["sid"]; has { + lang, err := session.Global().ID(sid.(string)).Get("__yao_lang") + if err != nil { + lang = strings.ToLower(lang.(string)) + } + } + + root := "yao" + if Setting.Optional.AdminRoot != "" { + root = Setting.Optional.AdminRoot + } + + setting, err := i18n.Trans(lang, "app", "app", Setting) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + return map[string]interface{}{ + "home": fmt.Sprintf("http://127.0.0.1:%d", config.Conf.Port), + "admin": fmt.Sprintf("http://127.0.0.1:%d/%s/", config.Conf.Port, root), + "setting": setting, + } } func processIcons(process *gou.Process) interface{} { diff --git a/widgets/app/app_test.go b/widgets/app/app_test.go index 021e670f..348c9548 100644 --- a/widgets/app/app_test.go +++ b/widgets/app/app_test.go @@ -11,6 +11,7 @@ import ( "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/flow" "github.com/yaoapp/yao/i18n" + "github.com/yaoapp/yao/script" "github.com/yaoapp/yao/widgets/login" ) @@ -118,7 +119,7 @@ func TestExport(t *testing.T) { api, has := gou.APIs["widgets.app"] assert.True(t, has) - assert.Equal(t, 4, len(api.HTTP.Paths)) + assert.Equal(t, 7, len(api.HTTP.Paths)) _, has = gou.ThirdHandlers["yao.app.setting"] assert.True(t, has) @@ -128,6 +129,15 @@ func TestExport(t *testing.T) { _, has = gou.ThirdHandlers["yao.app.menu"] assert.True(t, has) + + _, has = gou.ThirdHandlers["yao.app.check"] + assert.True(t, has) + + _, has = gou.ThirdHandlers["yao.app.setup"] + assert.True(t, has) + + _, has = gou.ThirdHandlers["yao.app.service"] + assert.True(t, has) } func TestProcessSetting(t *testing.T) { @@ -218,9 +228,52 @@ func TestProcessIcons(t *testing.T) { assert.Greater(t, len(res.(string)), 10) } +func TestProcessCheck(t *testing.T) { + loadApp(t) + res, err := gou.NewProcess("yao.app.Check", map[string]interface{}{}).Exec() + if err != nil { + t.Fatal(err) + } + assert.Nil(t, res) + + _, err = gou.NewProcess("yao.app.Check", map[string]interface{}{"error": "1"}).Exec() + assert.NotNil(t, err) +} + +func TestProcessSetup(t *testing.T) { + loadApp(t) + res, err := gou.NewProcess("yao.app.Setup", map[string]interface{}{"sid": "hello"}).Exec() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "http://127.0.0.1:5099/admin/", res.(map[string]interface{})["admin"]) + _, err = gou.NewProcess("yao.app.Setup", map[string]interface{}{"error": "1"}).Exec() + assert.NotNil(t, err) +} + +func TestProcessService(t *testing.T) { + loadApp(t) + res, err := gou.NewProcess( + "yao.app.Service", + "foo", + map[string]interface{}{"method": "Bar", "args": []interface{}{"hello", "world"}}, + ).Exec() + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, []interface{}{"hello", "world"}, res) +} + func loadApp(t *testing.T) { - err := i18n.Load(config.Conf) + err := script.Load(config.Conf) + if err != nil { + t.Fatal(err) + } + + err = i18n.Load(config.Conf) if err != nil { t.Fatal(err) } diff --git a/widgets/app/types.go b/widgets/app/types.go index 4e4c7832..5555d0da 100644 --- a/widgets/app/types.go +++ b/widgets/app/types.go @@ -28,3 +28,9 @@ type OptionalDSL struct { AdminRoot string `json:"adminRoot,omitempty"` Setting string `json:"setting,omitempty"` // custom setting process } + +// CFUN cloud function +type CFUN struct { + Method string `json:"method"` + Args []interface{} `json:"args,omitempty"` +}