From bcd85459302516859438b6bd45355ea012009199 Mon Sep 17 00:00:00 2001 From: sunjuzhong <129019668+sunjuzhong@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:09:29 +0800 Subject: [PATCH] add rule and deparment user_id --- .vscode/launch.json | 55 +++++++++++++++ Makefile | 32 ++++++++- engine/load.go | 7 ++ helper/array.go | 80 ++++++++++++++-------- helper/jwt.go | 2 +- helper/str.go | 11 +++ rules/api.go | 35 ++++++++++ rules/load.go | 125 ++++++++++++++++++++++++++++++++++ rules/process.go | 32 +++++++++ rules/rule.go | 63 +++++++++++++++++ rules/type.go | 17 +++++ service/guards.go | 1 + widgets/chart/api.go | 2 + widgets/chart/chart.go | 4 ++ widgets/chart/types.go | 1 + widgets/component/handlers.go | 13 ++++ widgets/dashboard/api.go | 2 + widgets/dashboard/types.go | 1 + widgets/field/types.go | 3 + widgets/form/api.go | 22 ++++++ widgets/form/form.go | 4 ++ widgets/form/types.go | 2 + widgets/list/api.go | 2 + widgets/list/list.go | 3 + widgets/list/types.go | 1 + widgets/login/process.go | 19 ++++-- widgets/mapping/mapping.go | 1 + widgets/table/api.go | 27 ++++++++ widgets/table/layout.go | 2 +- widgets/table/mapping.go | 25 +++++++ widgets/table/process.go | 7 +- widgets/table/table.go | 4 ++ widgets/table/types.go | 8 +++ 33 files changed, 574 insertions(+), 39 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 helper/str.go create mode 100644 rules/api.go create mode 100644 rules/load.go create mode 100644 rules/process.go create mode 100644 rules/rule.go create mode 100644 rules/type.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..f817c94a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,55 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Execute Designated process", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "cwd": "/Users/juzhongsun/Works/yao-project", + "args": [ + "run", + "yao.app.menu", + ], + }, + { + "name": "Launch Yao Project", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "cwd": "/Users/juzhongsun/Works/yao-project", + "args": [ + "start" + ], + }, + { + "name": "Launch Fabric Project", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "cwd": "/Users/juzhongsun/Works/yao-fabric", + "args": [ + "start" + ], + }, + { + "name": "Launch Fabric Process", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "cwd": "/Users/juzhongsun/Works/yao-fabric", + "args": [ + "run", + "models.crm.person.Get", + "::{\"withs\": {\"company\": { \"query\": { \"select\": [\"name\"] }}}, \"wheres\": [{\"rel\": \"company\", \"column\":\"name\", \"value\": \"绍兴市金桥纺织有限公司\"}]}" + ], + }, + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 5fec9386..5d9adca9 100644 --- a/Makefile +++ b/Makefile @@ -283,7 +283,7 @@ release: clean # ** XGEN will be renamed to DUI in the feature. and move to the new repository. ** # ** new repository: https://github.com/YaoApp/dui.git ** export NODE_ENV=production - git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0 + git clone https://github.com/sjzsdu/xgen.git .tmp/xgen/v1.0 # cd .tmp/xgen/v1.0 && git checkout 5002c3fded585aaa69a4366135b415ea3234964e echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env cd .tmp/xgen/v1.0 && pnpm install --no-frozen-lockfile && pnpm run build @@ -347,7 +347,7 @@ linux-release: clean # ** XGEN will be renamed to DUI in the feature. and move to the new repository. ** # ** new repository: https://github.com/YaoApp/dui.git ** export NODE_ENV=production - git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0 + git clone https://github.com/sjzsdu/xgen.git .tmp/xgen/v1.0 rm -f .tmp/xgen/v1.0/pnpm-lock.yaml echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env cd .tmp/xgen/v1.0 && pnpm install --no-frozen-lockfile && pnpm run build @@ -387,6 +387,34 @@ linux-release: clean CGO_ENABLED=1 CGO_LDFLAGS="-static" go build -v -o dist/release/yao chmod +x dist/release/yao + +.PHONY: ubutu-release +ubutu-release: clean + mkdir -p dist/release + mkdir .tmp +# Checkout init + git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init + rm -rf .tmp/yao-init/.git + rm -rf .tmp/yao-init/.gitignore + rm -rf .tmp/yao-init/LICENSE + rm -rf .tmp/yao-init/README.md + +# Packing + mkdir -p .tmp/data/xgen + cp -r ./ui .tmp/data/ui + cp -r ./yao .tmp/data/yao + cp -r xgenDist .tmp/data/xgen/v1.0 + cp -r .tmp/yao-init .tmp/data/init + go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... + rm -rf .tmp/data + rm -rf .tmp/xgen + +# Making artifacts + mkdir -p dist + CGO_ENABLED=1 CGO_LDFLAGS="-static" go build -v -o dist/release/yao + chmod +x dist/release/yao + + # make clean .PHONY: clean clean: diff --git a/engine/load.go b/engine/load.go index 3f785289..33106891 100644 --- a/engine/load.go +++ b/engine/load.go @@ -27,6 +27,7 @@ import ( "github.com/yaoapp/yao/pipe" "github.com/yaoapp/yao/plugin" "github.com/yaoapp/yao/query" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/runtime" "github.com/yaoapp/yao/schedule" "github.com/yaoapp/yao/script" @@ -153,6 +154,12 @@ func Load(cfg config.Config, options LoadOption) (err error) { printErr(cfg.Mode, "Plugin", err) } + // Load Rules + err = rules.Load(cfg) + if err != nil { + printErr(cfg.Mode, "Rules", err) + } + // Load WASM Application (experimental) // Load build-in widgets (table / form / chart / ...) diff --git a/helper/array.go b/helper/array.go index 32f17a2d..27d7af66 100644 --- a/helper/array.go +++ b/helper/array.go @@ -1,6 +1,7 @@ package helper import ( + "errors" "fmt" jsoniter "github.com/json-iterator/go" @@ -191,59 +192,80 @@ func NewArrayTreeOption(option map[string]interface{}) ArrayTreeOption { // ArrayTree []map[string]interface{} 转树形结构 func ArrayTree(records []map[string]interface{}, setting map[string]interface{}) []map[string]interface{} { opt := NewArrayTreeOption(setting) - return opt.Tree(records) + res, err := opt.Tree(records) + if err == nil { + return res + } + return nil } -// Tree Array 转换为 Tree -func (opt ArrayTreeOption) Tree(records []map[string]interface{}) []map[string]interface{} { - +func (opt ArrayTreeOption) Tree(records []map[string]interface{}) ([]map[string]interface{}, error) { mapping := map[string]map[string]interface{}{} - for i := range records { - if key, has := records[i][opt.Key]; has { - primary := fmt.Sprintf("%v", key) - mapping[primary] = map[string]interface{}{} - mapping[primary][opt.Children] = []map[string]interface{}{} - for k, v := range records[i] { - mapping[primary][k] = v - } + recordOrder := []string{} + + for _, record := range records { + key, hasKey := record[opt.Key] + if !hasKey { + return nil, errors.New("missing key field") } + + primary := fmt.Sprintf("%v", key) + mapping[primary] = make(map[string]interface{}) + mapping[primary][opt.Children] = []map[string]interface{}{} + + for k, v := range record { + mapping[primary][k] = v + } + + recordOrder = append(recordOrder, primary) } // 向上归集 - for key, record := range mapping { + for _, key := range recordOrder { + record := mapping[key] + parent := fmt.Sprintf("%v", record[opt.Parent]) empty := fmt.Sprintf("%v", opt.Empty) if parent == empty { // 第一级 continue } + pKey := fmt.Sprintf("%v", parent) - if _, has := mapping[pKey]; !has { + parentRecord, hasParent := mapping[pKey] + if !hasParent { continue } - children, ok := mapping[pKey][opt.Children].([]map[string]interface{}) + + children, ok := parentRecord[opt.Children].([]map[string]interface{}) if !ok { children = []map[string]interface{}{} } - children = append(children, mapping[key]) - mapping[pKey][opt.Children] = children + + children = append(children, record) + parentRecord[opt.Children] = children + mapping[pKey] = parentRecord } res := []map[string]interface{}{} - for i := range records { - if key, has := records[i][opt.Key]; has { - record := mapping[fmt.Sprintf("%v", key)] - if pValue, has := record[opt.Parent]; has { - parent := fmt.Sprintf("%v", pValue) - empty := fmt.Sprintf("%v", opt.Empty) - if parent == empty { // 父类为空 - res = append(res, record) - } else if _, has := mapping[parent]; !has { // 或者父类为定义的 - res = append(res, record) - } + for _, key := range recordOrder { + record := mapping[key] + + // recordKey := fmt.Sprintf("%v", record[opt.Key]) + recordValue, hasParent := record[opt.Parent] + + if hasParent { + parent := fmt.Sprintf("%v", recordValue) + empty := fmt.Sprintf("%v", opt.Empty) + + if parent == empty || len(mapping[parent]) == 0 { + res = append(res, record) } + } else { + res = append(res, record) } } - return res + + return res, nil } // ArrayMapSet []map[string]interface{} 设定数值 diff --git a/helper/jwt.go b/helper/jwt.go index 60531a83..299cbcc1 100644 --- a/helper/jwt.go +++ b/helper/jwt.go @@ -64,7 +64,7 @@ func JwtMake(id int, data map[string]interface{}, option map[string]interface{}, now := time.Now().Unix() sid := "" - timeout := int64(3600) + timeout := int64(36000) uid := fmt.Sprintf("%d", id) subject := "User Token" audience := "Yao Process utils.jwt.Make" diff --git a/helper/str.go b/helper/str.go new file mode 100644 index 00000000..a4e80862 --- /dev/null +++ b/helper/str.go @@ -0,0 +1,11 @@ +package helper + +// 检查字符串数组中是否包含某个字符 +func ContainsString(arr []string, char string) bool { + for _, str := range arr { + if str == char { + return true + } + } + return false +} diff --git a/rules/api.go b/rules/api.go new file mode 100644 index 00000000..146ce4fd --- /dev/null +++ b/rules/api.go @@ -0,0 +1,35 @@ +package rules + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/gou/session" + "github.com/yaoapp/kun/any" + "github.com/yaoapp/yao/helper" +) + +// Guard table widget guard +func Guard(c *gin.Context, rule string) { + sid, exists := c.Get("__sid") + if !exists { + abort(c, 400, "session id is not found") + return + } + user, err := session.Global().ID(sid.(string)).Get("user") + if err != nil { + abort(c, 400, "user is not found") + return + } + ruleIds := any.Of(user).MapStr().Get("rule_ids") + ruleIds = any.Of(ruleIds).CStrings() + if !helper.ContainsString(ruleIds.([]string), rule) && !helper.ContainsString(ruleIds.([]string), "*") { + abort(c, 400, fmt.Sprintf("no permission for this action: %s", rule)) + return + } +} + +func abort(c *gin.Context, code int, message string) { + c.JSON(code, gin.H{"code": code, "message": message}) + c.Abort() +} diff --git a/rules/load.go b/rules/load.go new file mode 100644 index 00000000..1be0fb6e --- /dev/null +++ b/rules/load.go @@ -0,0 +1,125 @@ +package rules + +import ( + "fmt" + "strings" + + "github.com/yaoapp/gou/application" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +var RuleDSLS map[string]*DSL = map[string]*DSL{} + +func Load(cfg config.Config) error { + messages := []string{} + exts := []string{"*.rul.yao", "*.rul.json", "*.rul.jsonc"} + err := application.App.Walk("rules", func(root, file string, isdir bool) error { + if isdir { + return nil + } + if err := LoadFile(root, file); err != nil { + messages = append(messages, err.Error()) + } + + return nil + }, exts...) + + exportProcess() + + if len(messages) > 0 { + return fmt.Errorf(strings.Join(messages, ";\n")) + } + + return err +} + +func LoadFile(root string, file string) error { + + id := share.ID(root, file) + data, err := application.App.Read(file) + if err != nil { + return err + } + + _, err = load(data, id, file) + if err != nil { + return err + } + return nil +} + +// LoadSource load table dsl by source +func load(source []byte, id string, file string) (*DSL, error) { + dsl := &DSL{ + Rule: &Rule{ + ID: id, + Children: []Rule{}, + }, + file: file, + source: source, + } + err := dsl.parse() + if err != nil { + return nil, fmt.Errorf("[%s] %s", id, err.Error()) + } + + RuleDSLS[id] = dsl + return dsl, nil +} + +// parse method to parse and generate keys +func (dsl *DSL) parse() error { + err := application.Parse(dsl.file, dsl.source, dsl) + if err != nil { + return err + } + generateKeys(dsl.Rule, "") + return nil +} + +// generateKeys recursively generates keys for rules +func generateKeys(rule *Rule, parentKey string) { + if parentKey == "" { + rule.Key = rule.ID + } else { + rule.Key = fmt.Sprintf("%s_%s", parentKey, rule.ID) + } + + for i := range rule.Children { + generateKeys(&rule.Children[i], rule.Key) + } +} + +// GetMainKeys returns a slice of main keys in RuleDSLS +func GetMainKeys() []string { + keys := []string{} + for key := range RuleDSLS { + keys = append(keys, key) + } + return keys +} + +// GetAllKeys 返回Rule结构及其子结构中的所有Key字段 +func GetAllKeys() []string { + var keys []string + var collectKeys func(r Rule) + + // 定义递归函数来收集Key + collectKeys = func(r Rule) { + if r.Key != "" { + keys = append(keys, r.Key) + } + for _, child := range r.Children { + collectKeys(child) + } + } + + // 遍历RuleDSLS,收集所有Key + for _, dsl := range RuleDSLS { + if dsl.Rule != nil { + collectKeys(*dsl.Rule) + } + } + return keys +} diff --git a/rules/process.go b/rules/process.go new file mode 100644 index 00000000..34d88c2b --- /dev/null +++ b/rules/process.go @@ -0,0 +1,32 @@ +package rules + +import ( + gouProcess "github.com/yaoapp/gou/process" +) + +func exportProcess() { + gouProcess.Register("yao.rule.menus", processMenu) + gouProcess.Register("yao.rule.ruleKeys", processRuleKeys) +} + +func processMenu(process *gouProcess.Process) interface{} { + argsLen := process.NumOfArgs() + menuOnly := false + dsls := GetMainKeys() + menuKeys := []string{"*"} + if argsLen == 1 { + dsls = process.ArgsStrings(0) + } else if argsLen == 2 { + dsls = process.ArgsStrings(0) + menuOnly = process.ArgsBool(1) + } else if argsLen == 3 { + dsls = process.ArgsStrings(0) + menuOnly = process.ArgsBool(1) + menuKeys = process.ArgsStrings(2) + } + return GetDSLsMaps(dsls, menuOnly, menuKeys) +} + +func processRuleKeys(process *gouProcess.Process) interface{} { + return GetAllKeys() +} diff --git a/rules/rule.go b/rules/rule.go new file mode 100644 index 00000000..18c303a8 --- /dev/null +++ b/rules/rule.go @@ -0,0 +1,63 @@ +package rules + +import "github.com/yaoapp/yao/helper" + +// rulesToMaps converts a slice of Rule to a slice of maps +func rulesToMaps(rules []Rule, onlyMenu bool, keys []string) []map[string]interface{} { + maps := []map[string]interface{}{} + for _, rule := range rules { + if onlyMenu && rule.Path == "" && len(rule.Children) == 0 { + continue + } + if !helper.ContainsString(keys, rule.Key) && !helper.ContainsString(keys, "*") { + continue + } + maps = append(maps, rule.ToMap(onlyMenu, keys)) + } + return maps +} + +// ToMap converts a Rule to a map +func (rule *Rule) ToMap(onlyMenu bool, keys []string) map[string]interface{} { + return map[string]interface{}{ + "id": rule.ID, + "name": rule.Name, + "title": rule.Name, + "icon": rule.Icon, + "path": rule.Path, + "visible_menu": rule.Visible_menu, + "children": rulesToMaps(rule.Children, onlyMenu, keys), + "rule": rule.Key, + } +} + +func (dsl *DSL) HasChildren(onlyMenu bool, keys []string) bool { + ruls := []Rule{} + for _, rule := range dsl.Children { + if onlyMenu && rule.Path == "" && len(rule.Children) == 0 { + continue + } + if !helper.ContainsString(keys, rule.Key) && !helper.ContainsString(keys, "*") { + continue + } + ruls = append(ruls, rule) + } + return len(ruls) > 0 +} + +// GetDSLsMaps returns the maps of DSLs corresponding to the given IDs +func GetDSLsMaps(ids []string, onlyMenu bool, keys []string) []map[string]interface{} { + if keys == nil { + keys = []string{"*"} + } + maps := []map[string]interface{}{} + for _, id := range ids { + if dsl, ok := RuleDSLS[id]; ok { + rule := dsl.ToMap(onlyMenu, keys) + if dsl.HasChildren(onlyMenu, keys) { + maps = append(maps, rule) + } + } + } + return maps +} diff --git a/rules/type.go b/rules/type.go new file mode 100644 index 00000000..c7074565 --- /dev/null +++ b/rules/type.go @@ -0,0 +1,17 @@ +package rules + +type Rule struct { + ID string `json:"id,omitempty"` + Key string `json:"-"` + Name string `json:"name,omitempty"` + Icon string `json:"icon,omitempty"` + Path string `json:"path,omitempty"` + Visible_menu int `json:"visible_menu,omitempty"` + Children []Rule `json:"children,omitempty"` +} + +type DSL struct { + *Rule + file string `json:"-"` + source []byte `json:"-"` +} diff --git a/service/guards.go b/service/guards.go index d2df51d2..bcb4df8c 100644 --- a/service/guards.go +++ b/service/guards.go @@ -74,6 +74,7 @@ func guardBearerJWT(c *gin.Context) { claims := helper.JwtValidate(tokenString) c.Set("__sid", claims.SID) + return } // JWT Bearer JWT diff --git a/widgets/chart/api.go b/widgets/chart/api.go index d6aaa808..d3fa3697 100644 --- a/widgets/chart/api.go +++ b/widgets/chart/api.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/widgets/action" ) @@ -37,6 +38,7 @@ func Guard(c *gin.Context) { return } + rules.Guard(c, chart.Rule) } func abort(c *gin.Context, code int, message string) { diff --git a/widgets/chart/chart.go b/widgets/chart/chart.go index a047e9c0..835eee16 100644 --- a/widgets/chart/chart.go +++ b/widgets/chart/chart.go @@ -102,6 +102,10 @@ func LoadFile(root string, file string) error { // LoadData load via data func (dsl *DSL) parse(id string, root string) error { + if dsl.Rule == "" { + dsl.Rule = strings.ReplaceAll(id, ".", "_") + } + if dsl.Action == nil { dsl.Action = &ActionDSL{} } diff --git a/widgets/chart/types.go b/widgets/chart/types.go index 22389c14..ce1dd943 100644 --- a/widgets/chart/types.go +++ b/widgets/chart/types.go @@ -13,6 +13,7 @@ import ( type DSL struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` + Rule string `json:"rule,omitempty"` Action *ActionDSL `json:"action"` Layout *LayoutDSL `json:"layout"` Fields *FieldsDSL `json:"fields"` diff --git a/widgets/component/handlers.go b/widgets/component/handlers.go index 333fb430..0fe74567 100644 --- a/widgets/component/handlers.go +++ b/widgets/component/handlers.go @@ -11,6 +11,7 @@ var hanlders = map[string]ComputeHanlder{ "Trim": Trim, "Hide": Hide, "Concat": Concat, + "ValueOr": ValueOr, "Download": Download, "Upload": Upload, "QueryString": Trim, @@ -52,6 +53,18 @@ func Concat(args ...interface{}) (interface{}, error) { return res, nil } +// ValueOr +func ValueOr(args ...interface{}) (interface{}, error) { + res := "" + for _, arg := range args { + if arg != nil { + res = fmt.Sprintf("%v", arg) + break + } + } + return res, nil +} + // Get value func Get(args ...interface{}) (interface{}, error) { if len(args) == 0 { diff --git a/widgets/dashboard/api.go b/widgets/dashboard/api.go index ffc32898..129eff4a 100644 --- a/widgets/dashboard/api.go +++ b/widgets/dashboard/api.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/widgets/action" ) @@ -37,6 +38,7 @@ func Guard(c *gin.Context) { return } + rules.Guard(c, dashboard.Rule) } func abort(c *gin.Context, code int, message string) { diff --git a/widgets/dashboard/types.go b/widgets/dashboard/types.go index 74d52c1b..053d0026 100644 --- a/widgets/dashboard/types.go +++ b/widgets/dashboard/types.go @@ -13,6 +13,7 @@ import ( type DSL struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` + Rule string `json:"Rule,omitempty"` Action *ActionDSL `json:"action"` Layout *LayoutDSL `json:"layout"` Fields *FieldsDSL `json:"fields"` diff --git a/widgets/field/types.go b/widgets/field/types.go index 10f5b958..82ed271c 100644 --- a/widgets/field/types.go +++ b/widgets/field/types.go @@ -7,6 +7,9 @@ import ( // Filters the filters DSL type Filters map[string]FilterDSL +// Batch the batch DSL +type Batches map[string]FilterDSL + // Columns the columns DSL type Columns map[string]ColumnDSL diff --git a/widgets/form/api.go b/widgets/form/api.go index 7ab5f43f..4c9c3964 100644 --- a/widgets/form/api.go +++ b/widgets/form/api.go @@ -6,10 +6,23 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" + "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/widgets/action" ) +var editAPIS = []string{ + "/api/__yao/form/:id/upload/:xpath/:method", + "/api/__yao/form/:id/save", + "/api/__yao/form/:id/create", + "/api/__yao/form/:id/insert", +} + +var deleteAPIS = []string{ + "/api/__yao/form/:id/delete/:primary", +} + // Guard form widget guard func Guard(c *gin.Context) { @@ -37,6 +50,15 @@ func Guard(c *gin.Context) { return } + rules.Guard(c, form.Rule) + if helper.ContainsString(editAPIS, c.FullPath()) { + ruleKey := fmt.Sprintf("%s_edit", form.Rule) + rules.Guard(c, ruleKey) + } + if helper.ContainsString(deleteAPIS, c.FullPath()) { + ruleKey := fmt.Sprintf("%s_del", form.Rule) + rules.Guard(c, ruleKey) + } } func abort(c *gin.Context, code int, message string) { diff --git a/widgets/form/form.go b/widgets/form/form.go index d9f70ab1..ae2c115d 100644 --- a/widgets/form/form.go +++ b/widgets/form/form.go @@ -180,6 +180,10 @@ func load(source []byte, id string, file string) (*DSL, error) { // LoadData load via data func (dsl *DSL) parse(id string) error { + if dsl.Rule == "" { + dsl.Rule = strings.ReplaceAll(id, ".", "_") + } + if dsl.Action == nil { dsl.Action = &ActionDSL{} } diff --git a/widgets/form/types.go b/widgets/form/types.go index dd597db3..dd4dcdba 100644 --- a/widgets/form/types.go +++ b/widgets/form/types.go @@ -13,6 +13,7 @@ import ( type DSL struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` + Rule string `json:"rule,omitempty"` Action *ActionDSL `json:"action"` Layout *LayoutDSL `json:"layout"` Fields *FieldsDSL `json:"fields"` @@ -94,6 +95,7 @@ type SectionDSL struct { Icon interface{} `json:"icon,omitempty"` Color string `json:"color,omitempty"` Weight interface{} `json:"weight,omitempty"` + Rule string `json:"rule,omitempty"` Columns []Column `json:"columns,omitempty"` } diff --git a/widgets/list/api.go b/widgets/list/api.go index ce6a8661..97280b1d 100644 --- a/widgets/list/api.go +++ b/widgets/list/api.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/widgets/action" ) @@ -37,6 +38,7 @@ func Guard(c *gin.Context) { return } + rules.Guard(c, list.Rule) } func abort(c *gin.Context, code int, message string) { diff --git a/widgets/list/list.go b/widgets/list/list.go index 93dca942..2e012276 100644 --- a/widgets/list/list.go +++ b/widgets/list/list.go @@ -135,6 +135,9 @@ func LoadID(id string, root string) error { // LoadData load via data func (dsl *DSL) parse(id string, root string) error { + if dsl.Rule == "" { + dsl.Rule = strings.ReplaceAll(id, ".", "_") + } if dsl.Action == nil { dsl.Action = &ActionDSL{} } diff --git a/widgets/list/types.go b/widgets/list/types.go index 1dcd7044..c4dac30f 100644 --- a/widgets/list/types.go +++ b/widgets/list/types.go @@ -13,6 +13,7 @@ import ( type DSL struct { ID string `json:"id,omitempty"` Root string `json:"-"` + Rule string `json:"rule,omitempty"` Name string `json:"name,omitempty"` Action *ActionDSL `json:"action"` Layout *LayoutDSL `json:"layout"` diff --git a/widgets/login/process.go b/widgets/login/process.go index 8a2ef0ba..6dbc32fc 100644 --- a/widgets/login/process.go +++ b/widgets/login/process.go @@ -79,6 +79,7 @@ func auth(field string, value string, password string, sid string) maps.Map { Wheres: []model.QueryWhere{ {Column: column, Value: value}, {Column: "status", Value: "enabled"}, + {Column: "type", Value: "admin"}, }, }) @@ -91,6 +92,9 @@ func auth(field string, value string, password string, sid string) maps.Map { } row := rows[0] + row["rule_ids"] = []string{"*"} + row["role_ids"] = []int{0} + row["department_ids"] = []int{0} passwordHash := row.Get("password").(string) row.Del("password") @@ -99,7 +103,7 @@ func auth(field string, value string, password string, sid string) maps.Map { exception.New("Login password error (%v)", 403, value).Throw() } - expiresAt := time.Now().Unix() + 3600*8 + expiresAt := time.Now().Unix() + 36000 // token := MakeToken(row, expiresAt) id := any.Of(row.Get("id")).CInt() @@ -128,12 +132,19 @@ func auth(field string, value string, password string, sid string) maps.Map { } // Get user menus - menus := process.New("yao.app.menu").WithSID(sid).Run() + // menus := process.New("yao.app.menu").WithSID(sid).Run() + // 读取菜单 + setting := process.New("yao.rule.menus", []string{"sys"}, true).WithSID(sid).Run() + items := process.New("yao.rule.menus", []string{"pro", "crm"}, true).WithSID(sid).Run() + return maps.Map{ "expires_at": token.ExpiresAt, "token": token.Token, "user": row, - "menus": menus, - "studio": studio, + "menus": maps.Map{ + "setting": setting, + "items": items, + }, + "studio": studio, } } diff --git a/widgets/mapping/mapping.go b/widgets/mapping/mapping.go index 1e03d254..53a84a25 100644 --- a/widgets/mapping/mapping.go +++ b/widgets/mapping/mapping.go @@ -2,6 +2,7 @@ package mapping // Mapping common type Mapping struct { + Batches map[string]string Filters map[string]string Columns map[string]string Actions map[string]string diff --git a/widgets/table/api.go b/widgets/table/api.go index 33b4802f..2b7dfcbc 100644 --- a/widgets/table/api.go +++ b/widgets/table/api.go @@ -6,10 +6,28 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" + "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/rules" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/widgets/action" ) +var editAPIS = []string{ + "/api/__yao/table/:id/upload/:xpath/:method", + "/api/__yao/table/:id/save", + "/api/__yao/table/:id/create", + "/api/__yao/table/:id/insert", + "/api/__yao/table/:id/update/:primary", + "/api/__yao/table/:id/update/in", + "/api/__yao/table/:id/update/where", +} + +var deleteAPIS = []string{ + "/api/__yao/table/:id/delete/:primary", + "/api/__yao/table/:id/delete/in", + "/api/__yao/table/:id/delete/where", +} + // Guard table widget guard func Guard(c *gin.Context) { @@ -37,6 +55,15 @@ func Guard(c *gin.Context) { return } + rules.Guard(c, tab.Rule) + if helper.ContainsString(editAPIS, c.FullPath()) { + ruleKey := fmt.Sprintf("%s_edit", tab.Rule) + rules.Guard(c, ruleKey) + } + if helper.ContainsString(deleteAPIS, c.FullPath()) { + ruleKey := fmt.Sprintf("%s_del", tab.Rule) + rules.Guard(c, ruleKey) + } } func abort(c *gin.Context, code int, message string) { diff --git a/widgets/table/layout.go b/widgets/table/layout.go index a440ac48..aa8da828 100644 --- a/widgets/table/layout.go +++ b/widgets/table/layout.go @@ -192,7 +192,7 @@ func (layout *LayoutDSL) Xgen(data map[string]interface{}, excludes map[string]b if clone.Header.Preset.Batch != nil && clone.Header.Preset.Batch.Columns != nil { columns := []component.InstanceDSL{} for _, column := range clone.Header.Preset.Batch.Columns { - id, has := mapping.Filters[column.Name] + id, has := mapping.Batches[column.Name] if !has { continue } diff --git a/widgets/table/mapping.go b/widgets/table/mapping.go index 509f2330..6ce3998f 100644 --- a/widgets/table/mapping.go +++ b/widgets/table/mapping.go @@ -50,6 +50,10 @@ func (dsl *DSL) mapping() error { dsl.Mapping.Filters = map[string]string{} } + if dsl.Mapping.Batches == nil { + dsl.Mapping.Batches = map[string]string{} + } + if dsl.Mapping.Columns == nil { dsl.Mapping.Columns = map[string]string{} } @@ -84,6 +88,27 @@ func (dsl *DSL) mapping() error { } } + if dsl.Fields.Batch != nil && dsl.Layout.Header.Preset.Batch != nil && dsl.Layout.Header.Preset.Batch.Columns != nil { + for _, inst := range dsl.Layout.Header.Preset.Batch.Columns { + + if batch, has := dsl.Fields.Batch[inst.Name]; has { + + // Mapping ID + dsl.Mapping.Batches[batch.ID] = inst.Name + dsl.Mapping.Batches[inst.Name] = batch.ID + + // Mapping Compute + if batch.Edit != nil && batch.Edit.Compute != nil { + bind := batch.FilterBind() + if _, has := dsl.Computes.Filter[bind]; !has { + dsl.Computes.Filter[bind] = []compute.Unit{} + } + dsl.Computes.Filter[bind] = append(dsl.Computes.Filter[bind], compute.Unit{Name: inst.Name, Kind: compute.Filter}) + } + } + } + } + if dsl.Fields.Table != nil && dsl.Layout.Table != nil && dsl.Layout.Table.Columns != nil { for _, inst := range dsl.Layout.Table.Columns { if field, has := dsl.Fields.Table[inst.Name]; has { diff --git a/widgets/table/process.go b/widgets/table/process.go index ca8f609b..2132d95e 100644 --- a/widgets/table/process.go +++ b/widgets/table/process.go @@ -260,8 +260,11 @@ func processDeleteIn(process *gouProcess.Process) interface{} { func processExport(process *gouProcess.Process) interface{} { process.ValidateArgNums(1) tab := MustGet(process) // 0 + params := process.ArgsQueryParams(1, types.QueryParam{}) - pagesize := process.ArgsInt(2, 50) + page := process.ArgsInt(2, 1) + pagesize := process.ArgsInt(3, 50) + log.Trace("[table] export %s %v %d", tab.ID, params, pagesize) // Filename @@ -276,7 +279,7 @@ func processExport(process *gouProcess.Process) interface{} { } // Query - page := 1 + for page > 0 { process.Args = []interface{}{tab.ID, params, page, pagesize} data, err := tab.Action.Search.Exec(process) diff --git a/widgets/table/table.go b/widgets/table/table.go index 0502c0c7..e8f72d19 100644 --- a/widgets/table/table.go +++ b/widgets/table/table.go @@ -209,6 +209,10 @@ func load(source []byte, id string, file string) (*DSL, error) { // parse parse table dsl source func (dsl *DSL) parse(id string) error { + if dsl.Rule == "" { + dsl.Rule = strings.ReplaceAll(id, ".", "_") + } + if dsl.Action == nil { dsl.Action = &ActionDSL{} } diff --git a/widgets/table/types.go b/widgets/table/types.go index d7b74065..c3f60bdb 100644 --- a/widgets/table/types.go +++ b/widgets/table/types.go @@ -14,6 +14,7 @@ type DSL struct { // Root string `json:"-"` ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` + Rule string `json:"rule,omitempty"` Action *ActionDSL `json:"action"` Layout *LayoutDSL `json:"layout"` Fields *FieldsDSL `json:"fields"` @@ -99,6 +100,12 @@ type HeaderLayoutDSL struct { type PresetHeaderDSL struct { Batch *BatchPresetDSL `json:"batch,omitempty"` Import *ImportPresetDSL `json:"import,omitempty"` + Column *ColumnDSL `json:"column,omitempty"` + Filter *ColumnDSL `json:"filter,omitempty"` +} + +type ColumnDSL struct { + Musts []string `json:"musts,omitempty"` } // BatchPresetDSL layout.header.preset.batch @@ -137,6 +144,7 @@ type OperationTableDSL struct { // FieldsDSL the table fields DSL type FieldsDSL struct { Filter field.Filters `json:"filter,omitempty"` + Batch field.Batches `json:"batch,omitempty"` Table field.Columns `json:"table,omitempty"` filterMap map[string]field.FilterDSL tableMap map[string]field.ColumnDSL