553 lines
13 KiB
Go
553 lines
13 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
"github.com/yaoapp/gou"
|
|
"github.com/yaoapp/gou/session"
|
|
"github.com/yaoapp/kun/exception"
|
|
"github.com/yaoapp/kun/log"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/data"
|
|
"github.com/yaoapp/yao/i18n"
|
|
"github.com/yaoapp/yao/share"
|
|
"github.com/yaoapp/yao/widgets/login"
|
|
)
|
|
|
|
//
|
|
// 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
|
|
// 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
|
|
// yao.app.Xgen Return the Xgen setting ( merge app & login )
|
|
// yao.app.Menu Return the menu list
|
|
//
|
|
|
|
// Setting the application setting
|
|
var Setting *DSL
|
|
var regExcp = regexp.MustCompile("^Exception\\|([0-9]+):(.+)$")
|
|
|
|
// LoadAndExport load app
|
|
func LoadAndExport(cfg config.Config) error {
|
|
err := Load(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return Export()
|
|
}
|
|
|
|
// Load the app DSL
|
|
func Load(cfg config.Config) error {
|
|
|
|
file := filepath.Join(cfg.Root, "app.json")
|
|
file, err := filepath.Abs(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dsl := &DSL{Optional: OptionalDSL{}, Lang: cfg.Lang}
|
|
err = jsoniter.Unmarshal(data, dsl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Replace Admin Root
|
|
err = dsl.replaceAdminRoot()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Load icons
|
|
dsl.icons(cfg)
|
|
|
|
Setting = dsl
|
|
return nil
|
|
}
|
|
|
|
// exportAPI export login api
|
|
func exportAPI() error {
|
|
|
|
if Setting == nil {
|
|
return fmt.Errorf("the app does not init")
|
|
}
|
|
|
|
http := gou.HTTP{
|
|
Name: "Widget App API",
|
|
Description: "Widget App API",
|
|
Version: share.VERSION,
|
|
Guard: "bearer-jwt",
|
|
Group: "__yao/app",
|
|
Paths: []gou.Path{},
|
|
}
|
|
|
|
process := "yao.app.Xgen"
|
|
if Setting.Setting != "" {
|
|
process = Setting.Setting
|
|
}
|
|
|
|
path := gou.Path{
|
|
Label: "App Setting",
|
|
Description: "App Setting",
|
|
Guard: "-",
|
|
Path: "/setting",
|
|
Method: "GET",
|
|
Process: process,
|
|
In: []string{},
|
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
|
}
|
|
http.Paths = append(http.Paths, path)
|
|
|
|
// POST
|
|
path = gou.Path{
|
|
Label: "App Setting",
|
|
Description: "App Setting",
|
|
Guard: "-",
|
|
Path: "/setting",
|
|
Method: "POST",
|
|
Process: process,
|
|
In: []string{":payload"},
|
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
|
}
|
|
http.Paths = append(http.Paths, path)
|
|
|
|
process = "yao.app.Menu"
|
|
args := []string{}
|
|
if Setting.Menu.Process != "" {
|
|
if Setting.Menu.Args != nil {
|
|
args = Setting.Menu.Args
|
|
}
|
|
}
|
|
|
|
path = gou.Path{
|
|
Label: "App Menu",
|
|
Description: "App Menu",
|
|
Path: "/menu",
|
|
Method: "GET",
|
|
Process: process,
|
|
In: args,
|
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
|
}
|
|
http.Paths = append(http.Paths, path)
|
|
|
|
process = "yao.app.Icons"
|
|
path = gou.Path{
|
|
Label: "App Icons",
|
|
Description: "App Icons",
|
|
Path: "/icons/:name",
|
|
Guard: "-",
|
|
Method: "GET",
|
|
Process: process,
|
|
In: []string{"$param.name"},
|
|
Out: gou.Out{Status: 200},
|
|
}
|
|
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.Service",
|
|
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 {
|
|
return err
|
|
}
|
|
|
|
// load apis
|
|
_, err = gou.LoadAPIReturn(string(source), "widgets.app")
|
|
return err
|
|
}
|
|
|
|
// Export export login api
|
|
func Export() error {
|
|
exportProcess()
|
|
return exportAPI()
|
|
}
|
|
|
|
func exportProcess() {
|
|
gou.RegisterProcessHandler("yao.app.setting", processSetting)
|
|
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.Call(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"].(string); has {
|
|
lang, err := session.Global().ID(sid).Get("__yao_lang")
|
|
if err != nil {
|
|
lang = strings.ToLower(lang.(string))
|
|
}
|
|
}
|
|
|
|
root := "yao"
|
|
if Setting.AdminRoot != "" {
|
|
root = Setting.AdminRoot
|
|
}
|
|
|
|
setting, err := i18n.Trans(lang, []string{"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{} {
|
|
process.ValidateArgNums(1)
|
|
name := process.ArgsString(0)
|
|
file, err := filepath.Abs(filepath.Join(config.Conf.Root, "icons", name))
|
|
if err != nil {
|
|
exception.New(err.Error(), 400).Throw()
|
|
}
|
|
content, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
exception.New(err.Error(), 400).Throw()
|
|
}
|
|
return string(content)
|
|
}
|
|
|
|
func processMenu(process *gou.Process) interface{} {
|
|
|
|
if Setting.Menu.Process != "" {
|
|
return gou.
|
|
NewProcess(Setting.Menu.Process, process.Args...).
|
|
WithGlobal(process.Global).
|
|
WithSID(process.Sid).
|
|
Run()
|
|
}
|
|
|
|
args := map[string]interface{}{
|
|
"select": []string{"id", "name", "icon", "parent", "path", "blocks", "visible_menu"},
|
|
"withs": map[string]interface{}{
|
|
"children": map[string]interface{}{
|
|
"query": map[string]interface{}{
|
|
"select": []string{"id", "name", "icon", "parent", "path", "blocks", "visible_menu"},
|
|
},
|
|
},
|
|
},
|
|
"wheres": []map[string]interface{}{
|
|
{"column": "status", "value": "enabled"},
|
|
{"column": "parent", "op": "null"},
|
|
},
|
|
"limit": 200,
|
|
"orders": []map[string]interface{}{{"column": "rank", "option": "asc"}},
|
|
}
|
|
return gou.
|
|
NewProcess("models.xiang.menu.get", args).
|
|
WithGlobal(process.Global).
|
|
WithSID(process.Sid).
|
|
Run()
|
|
}
|
|
|
|
func processSetting(process *gou.Process) interface{} {
|
|
if Setting == nil {
|
|
exception.New("the app does not init", 500).Throw()
|
|
return nil
|
|
}
|
|
|
|
sid := process.Sid
|
|
if sid == "" {
|
|
sid = session.ID()
|
|
}
|
|
|
|
// Set User ENV
|
|
if process.NumOfArgs() > 0 {
|
|
payload := process.ArgsMap(0, map[string]interface{}{
|
|
"now": time.Now().Unix(),
|
|
"lang": "en-us",
|
|
"sid": "",
|
|
})
|
|
|
|
if v, ok := payload["sid"].(string); ok && v != "" {
|
|
sid = v
|
|
}
|
|
|
|
lang := strings.ToLower(fmt.Sprintf("%v", payload["lang"]))
|
|
session.Global().ID(sid).Set("__yao_lang", lang)
|
|
}
|
|
|
|
setting, err := i18n.Trans(process.Lang(config.Conf.Lang), []string{"app.app"}, Setting)
|
|
if err != nil {
|
|
exception.New(err.Error(), 500).Throw()
|
|
}
|
|
|
|
setting.(*DSL).Sid = sid
|
|
return *setting.(*DSL)
|
|
}
|
|
|
|
func processXgen(process *gou.Process) interface{} {
|
|
|
|
if Setting == nil {
|
|
exception.New("the app does not init", 500).Throw()
|
|
}
|
|
|
|
sid := process.Sid
|
|
if sid == "" {
|
|
sid = session.ID()
|
|
}
|
|
|
|
// Set User ENV
|
|
if process.NumOfArgs() > 0 {
|
|
payload := process.ArgsMap(0, map[string]interface{}{
|
|
"now": time.Now().Unix(),
|
|
"lang": "en-us",
|
|
"sid": "",
|
|
})
|
|
|
|
if v, ok := payload["sid"].(string); ok && v != "" {
|
|
sid = v
|
|
}
|
|
|
|
lang := strings.ToLower(fmt.Sprintf("%v", payload["lang"]))
|
|
session.Global().ID(sid).Set("__yao_lang", lang)
|
|
}
|
|
|
|
mode := os.Getenv("YAO_ENV")
|
|
if mode == "" {
|
|
mode = "production"
|
|
}
|
|
|
|
xgenLogin := map[string]map[string]interface{}{
|
|
"entry": {"admin": "/x/Welcome"},
|
|
}
|
|
|
|
if admin, has := login.Logins["admin"]; has {
|
|
layout := map[string]interface{}{}
|
|
if admin.Layout.Site != "" {
|
|
layout["site"] = admin.Layout.Site
|
|
}
|
|
|
|
if admin.Layout.Slogan != "" {
|
|
layout["slogan"] = admin.Layout.Slogan
|
|
}
|
|
|
|
if admin.Layout.Cover != "" {
|
|
layout["cover"] = admin.Layout.Cover
|
|
}
|
|
|
|
// Translate
|
|
newLayout, err := i18n.Trans(process.Lang(config.Conf.Lang), []string{"login.admin"}, layout)
|
|
if err != nil {
|
|
log.Error("[Login] Xgen i18n.Trans login.admin %s", err.Error())
|
|
}
|
|
|
|
if new, ok := newLayout.(map[string]interface{}); ok {
|
|
layout = new
|
|
}
|
|
|
|
xgenLogin["entry"]["admin"] = admin.Layout.Entry
|
|
xgenLogin["admin"] = map[string]interface{}{
|
|
"captcha": "/api/__yao/login/admin/captcha?type=digit",
|
|
"login": "/api/__yao/login/admin",
|
|
"layout": layout,
|
|
}
|
|
}
|
|
|
|
if user, has := login.Logins["user"]; has {
|
|
layout := map[string]interface{}{}
|
|
if user.Layout.Site != "" {
|
|
layout["site"] = user.Layout.Site
|
|
}
|
|
|
|
if user.Layout.Slogan != "" {
|
|
layout["slogan"] = user.Layout.Slogan
|
|
}
|
|
|
|
if user.Layout.Cover != "" {
|
|
layout["cover"] = user.Layout.Cover
|
|
}
|
|
|
|
// Translate
|
|
newLayout, err := i18n.Trans(process.Lang(config.Conf.Lang), []string{"login.user"}, layout)
|
|
if err != nil {
|
|
log.Error("[Login] Xgen %s", err.Error())
|
|
}
|
|
|
|
if new, ok := newLayout.(map[string]interface{}); ok {
|
|
layout = new
|
|
}
|
|
xgenLogin["entry"]["user"] = user.Layout.Entry
|
|
xgenLogin["user"] = map[string]interface{}{
|
|
"captcha": "/api/__yao/login/user/captcha?type=digit",
|
|
"login": "/api/__yao/login/user",
|
|
"layout": layout,
|
|
}
|
|
}
|
|
|
|
xgenSetting := map[string]interface{}{
|
|
"name": Setting.Name,
|
|
"description": Setting.Description,
|
|
"theme": Setting.Theme,
|
|
"lang": Setting.Lang,
|
|
"mode": mode,
|
|
"apiPrefix": "__yao",
|
|
"token": "localStorage",
|
|
"optional": Setting.Optional,
|
|
"login": xgenLogin,
|
|
}
|
|
|
|
if Setting.Logo != "" {
|
|
xgenSetting["logo"] = Setting.Logo
|
|
}
|
|
|
|
if Setting.Favicon != "" {
|
|
xgenSetting["favicon"] = Setting.Favicon
|
|
}
|
|
|
|
setting, err := i18n.Trans(process.Lang(config.Conf.Lang), []string{"app.app"}, xgenSetting)
|
|
if err != nil {
|
|
exception.New(err.Error(), 500).Throw()
|
|
}
|
|
|
|
setting.(map[string]interface{})["sid"] = sid
|
|
return setting.(map[string]interface{})
|
|
}
|
|
|
|
// replaceAdminRoot
|
|
func (dsl *DSL) replaceAdminRoot() error {
|
|
|
|
if dsl.AdminRoot == "" {
|
|
dsl.AdminRoot = "yao"
|
|
}
|
|
|
|
root := strings.TrimPrefix(dsl.AdminRoot, "/")
|
|
root = strings.TrimSuffix(root, "/")
|
|
err := data.ReplaceXGen("/__yao_admin_root/", fmt.Sprintf("/%s/", root))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return data.ReplaceXGen("\"__yao_admin_root\"", fmt.Sprintf("\"%s\"", root))
|
|
}
|
|
|
|
// icons
|
|
func (dsl *DSL) icons(cfg config.Config) {
|
|
|
|
favicon := filepath.Join(cfg.Root, "icons", "app.ico")
|
|
if _, err := os.Stat(favicon); err == nil {
|
|
dsl.Favicon = fmt.Sprintf("/api/__yao/app/icons/app.ico")
|
|
}
|
|
|
|
logo := filepath.Join(cfg.Root, "icons", "app.png")
|
|
if _, err := os.Stat(logo); err == nil {
|
|
dsl.Logo = fmt.Sprintf("/api/__yao/app/icons/app.png")
|
|
}
|
|
}
|