diff --git a/utils/datetime_test.go b/utils/datetime_test.go index 7fd426f6..7db585f3 100644 --- a/utils/datetime_test.go +++ b/utils/datetime_test.go @@ -1,4 +1,4 @@ -package utils +package utils_test import ( "testing" diff --git a/utils/process.go b/utils/process.go index 16584c3d..c0007c87 100644 --- a/utils/process.go +++ b/utils/process.go @@ -6,6 +6,7 @@ import ( "github.com/yaoapp/yao/utils/fmt" "github.com/yaoapp/yao/utils/json" "github.com/yaoapp/yao/utils/str" + "github.com/yaoapp/yao/utils/throw" "github.com/yaoapp/yao/utils/tree" "github.com/yaoapp/yao/utils/url" ) @@ -15,6 +16,16 @@ func Init() { process.Alias("xiang.helper.Captcha", "yao.utils.Captcha") // deprecated process.Alias("xiang.helper.CaptchaValidate", "yao.utils.CaptchaValidate") // deprecated + // **************************************** + // * Processes Version 0.10.4+ + // **************************************** + process.Register("utils.throw.Forbidden", throw.Forbidden) + process.Register("utils.throw.Unauthorized", throw.Unauthorized) + process.Register("utils.throw.NotFound", throw.NotFound) + process.Register("utils.throw.BadRequest", throw.BadRequest) + process.Register("utils.throw.InternalError", throw.InternalError) + process.Register("utils.throw.Exception", throw.Exception) + // **************************************** // * Migrate Processes Version 0.10.2+ // **************************************** diff --git a/utils/str_test.go b/utils/str_test.go index 3a9d2e1f..38d203a8 100644 --- a/utils/str_test.go +++ b/utils/str_test.go @@ -1,4 +1,4 @@ -package utils +package utils_test import ( "fmt" diff --git a/utils/throw/throw.go b/utils/throw/throw.go new file mode 100644 index 00000000..216dfcbd --- /dev/null +++ b/utils/throw/throw.go @@ -0,0 +1,50 @@ +package throw + +import ( + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/exception" +) + +// Unauthorized throw a unauthorized exception +func Unauthorized(process *process.Process) interface{} { + message := process.ArgsString(0, "Authentication required") + exception.New(message, 401).Throw() + return nil +} + +// Forbidden throw a forbidden exception +func Forbidden(process *process.Process) interface{} { + message := process.ArgsString(0, "Access denied") + exception.New(message, 403).Throw() + return nil +} + +// NotFound throw a not found exception +func NotFound(process *process.Process) interface{} { + message := process.ArgsString(0, "Resource not found") + exception.New(message, 404).Throw() + return nil +} + +// BadRequest throw a bad request exception +func BadRequest(process *process.Process) interface{} { + message := process.ArgsString(0, "Bad Request") + exception.New(message, 400).Throw() + return nil +} + +// InternalError throw a internal error exception +func InternalError(process *process.Process) interface{} { + message := process.ArgsString(0, "Internal Error") + exception.New(message, 500).Throw() + return nil +} + +// Exception throw a exception +func Exception(process *process.Process) interface{} { + process.ValidateArgNums(2) + message := process.ArgsString(0) + code := process.ArgsInt(1) + exception.New(message, code).Throw() + return nil +} diff --git a/utils/throw_test.go b/utils/throw_test.go new file mode 100644 index 00000000..3defbe34 --- /dev/null +++ b/utils/throw_test.go @@ -0,0 +1,57 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/utils" +) + +func TestProcessUnauthorized(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.Unauthorized", "Authentication required") + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|401: Authentication required", err.Error()) +} + +func TestProcessForbidden(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.Forbidden", "Access denied") + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|403: Access denied", err.Error()) +} + +func TestProcessNotFound(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.NotFound", "Resource not found") + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|404: Resource not found", err.Error()) +} + +func TestProcessBadRequest(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.BadRequest", "Bad Request") + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|400: Bad Request", err.Error()) +} + +func TestProcessInternalError(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.InternalError", "Internal Error") + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|500: Internal Error", err.Error()) +} + +func TestProcessException(t *testing.T) { + utils.Init() + proc := process.New("utils.throw.Exception", "I'm a teapot", 418) + err := proc.Execute() + assert.NotNil(t, err) + assert.Equal(t, "Exception|418: I'm a teapot", err.Error()) +} diff --git a/utils/tree_test.go b/utils/tree_test.go index 942c9835..270a8595 100644 --- a/utils/tree_test.go +++ b/utils/tree_test.go @@ -1,4 +1,4 @@ -package utils +package utils_test import ( "testing" diff --git a/utils/url_test.go b/utils/url_test.go index 6c38a467..79cab312 100644 --- a/utils/url_test.go +++ b/utils/url_test.go @@ -1,4 +1,4 @@ -package utils +package utils_test import ( "net/url" @@ -7,10 +7,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou/process" "github.com/yaoapp/gou/types" + "github.com/yaoapp/yao/utils" ) func TestProcessParseQuery(t *testing.T) { - Init() + utils.Init() args := []interface{}{"a=1&b=2&c=3&c=4"} result, err := process.New("utils.url.ParseQuery", args...).Exec() if err != nil { @@ -23,7 +24,7 @@ func TestProcessParseQuery(t *testing.T) { } func TestProcessParseURL(t *testing.T) { - Init() + utils.Init() args := []interface{}{"http://www.google.com:8080/search?q=dotnet"} result, err := process.New("utils.url.ParseURL", args...).Exec() if err != nil { @@ -40,7 +41,7 @@ func TestProcessParseURL(t *testing.T) { } func TestProcessQueryParam(t *testing.T) { - Init() + utils.Init() args := []interface{}{ map[string]interface{}{ "where.name.eq": "yao", diff --git a/widgets/chart/mapping.go b/widgets/chart/mapping.go index 6eb2ce71..04f1c026 100644 --- a/widgets/chart/mapping.go +++ b/widgets/chart/mapping.go @@ -59,6 +59,8 @@ func (dsl *DSL) mapping() error { if dsl.Fields.Filter != nil && dsl.Layout.Filter != nil && dsl.Layout.Filter.Columns != nil { for _, inst := range dsl.Layout.Filter.Columns { if filter, has := dsl.Fields.Filter[inst.Name]; has { + // Add the default value, and parse the backend only props + filter.Parse() // Mapping ID dsl.Mapping.Filters[filter.ID] = inst.Name @@ -79,6 +81,9 @@ func (dsl *DSL) mapping() error { for _, inst := range dsl.Layout.Chart.Columns { if field, has := dsl.Fields.Chart[inst.Name]; has { + // Add the default value, and parse the backend only props + field.Parse() + // Mapping ID dsl.Mapping.Columns[field.ID] = inst.Name dsl.Mapping.Columns[inst.Name] = field.ID diff --git a/widgets/component/component.go b/widgets/component/component.go index db3b8f27..43142f77 100644 --- a/widgets/component/component.go +++ b/widgets/component/component.go @@ -1,6 +1,8 @@ package component import ( + "strings" + jsoniter "github.com/json-iterator/go" ) @@ -10,6 +12,35 @@ import ( // yao.component.ImageView // yao.component.UploadEdit +// BackendOnlyProps The component’s properties include visibility for backend only +var BackendOnlyProps = map[string]map[string]map[string]interface{}{ + "select": { + "query": { + "xProps": map[string]interface{}{ + "$remote": map[string]interface{}{"process": "yao.component.GetOptions"}, + }, + }, + }, + "autocomplete": {"query": { + "xProps": map[string]interface{}{ + "$remote": map[string]interface{}{"process": "yao.component.GetOptions"}, + }, + }}, +} + +// DefaultProps The default properties for the component +var DefaultProps = map[string]map[string]map[string]interface{}{ + "upload": {"api": {"$api": map[string]interface{}{"process": "fs.data.Upload"}}}, + "image": {"api": {"$api": map[string]interface{}{"process": "utils.throw.Forbidden"}}}, // Just generate an effective URL, no need to upload +} + +// UploadComponents the components that need to upload files +var UploadComponents = map[string]bool{ + "upload": true, + "wangeditor": true, + "image": true, +} + // Export processes func Export() error { exportProcess() @@ -25,12 +56,7 @@ func (dsl DSL) MarshalJSON() ([]byte, error) { func (dsl DSL) Map() map[string]interface{} { res := map[string]interface{}{ "type": dsl.Type, - "props": map[string]interface{}(dsl.Props), - } - - // Add Default Value for Upload api - if (dsl.Type == "Upload" || dsl.Type == "Image") && dsl.Props != nil && !dsl.Props.Has("api") { - res["props"].(map[string]interface{})["$api"] = map[string]interface{}{"process": "fs.data.Upload"} + "props": dsl.FontendProps(), } if dsl.HideLabel { @@ -43,6 +69,49 @@ func (dsl DSL) Map() map[string]interface{} { return res } +// FontendProps filter backend only properties +func (dsl DSL) FontendProps() map[string]interface{} { + if dsl.Props == nil { + return map[string]interface{}{} + } + + props := map[string]interface{}{} + t := strings.ToLower(dsl.Type) + for key, val := range dsl.Props { + if BackendOnlyProps[t] != nil && BackendOnlyProps[t][key] != nil { + continue + } + props[key] = val + } + return props +} + +// Parse the component properties +func (dsl *DSL) Parse() { + t := strings.ToLower(dsl.Type) + // Check if the component has default props + if dsl.Props != nil && DefaultProps[t] != nil { + for key, val := range DefaultProps[t] { + if !dsl.Props.Has(key) { + for k, v := range val { + dsl.Props[k] = v + } + } + } + } + + // Check if the component has backend only props + if dsl.Props != nil && BackendOnlyProps[t] != nil { + for key, val := range BackendOnlyProps[t] { + if dsl.Props.Has(key) { + for k, v := range val { + dsl.Props[k] = v + } + } + } + } +} + // Clone Component func (dsl *DSL) Clone() *DSL { new := DSL{ diff --git a/widgets/component/process.go b/widgets/component/process.go index 812387d9..7ad0b3e0 100644 --- a/widgets/component/process.go +++ b/widgets/component/process.go @@ -9,13 +9,24 @@ import ( "github.com/yaoapp/gou/process" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" ) // Export process func exportProcess() { + process.Register("yao.component.getoptions", processGetOptions) process.Register("yao.component.selectoptions", processSelectOptions) } +// processGetOptions get options +func processGetOptions(process *process.Process) interface{} { + utils.Dump(process.Args) + return []map[string]interface{}{ + {"label": "Option 1", "value": "1"}, + {"label": "Option 2", "value": "2"}, + } +} + func processSelectOptions(process *process.Process) interface{} { process.ValidateArgNums(1) query := process.ArgsMap(0, map[string]interface{}{}) diff --git a/widgets/component/props.go b/widgets/component/props.go index 50628413..4f6e2633 100644 --- a/widgets/component/props.go +++ b/widgets/component/props.go @@ -13,7 +13,12 @@ import ( // CloudProps parse CloudProps func (p PropsDSL) CloudProps(xpath, component string) (map[string]CloudPropsDSL, error) { - return p.parseCloudProps(xpath, component, p) + + if p == nil { + return nil, fmt.Errorf("props is required") + } + + return p.parseCloudProps(xpath, component, p, p) } // Path api path @@ -42,18 +47,20 @@ func (cProp CloudPropsDSL) ExecUpload(process *gouProcess.Process, upload types. } // Create process - p, err := gouProcess.Of(name, upload) + p, err := gouProcess.Of(name, upload, cProp.Props) if err != nil { log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) } // Excute process - res, err := p.WithGlobal(process.Global).WithSID(process.Sid).Exec() + err = p.WithGlobal(process.Global).WithSID(process.Sid).Execute() if err != nil { log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) } + defer p.Release() + res := p.Value() return res, nil } @@ -81,19 +88,21 @@ func (cProp CloudPropsDSL) ExecQuery(process *gouProcess.Process, query map[stri } // Create process - p, err := gouProcess.Of(name, query) + p, err := gouProcess.Of(name, query, cProp.Props) if err != nil { log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) } // Excute process - res, err := p.WithGlobal(process.Global).WithSID(process.Sid).Exec() + err = p.WithGlobal(process.Global).WithSID(process.Sid).Execute() if err != nil { log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error()) } + defer p.Release() + res := p.Value() return res, nil } @@ -140,7 +149,7 @@ func (cProp CloudPropsDSL) replaceMap(data map[string]interface{}, root string, return nil } -func (p PropsDSL) parseCloudProps(xpath string, component string, props map[string]interface{}) (map[string]CloudPropsDSL, error) { +func (p PropsDSL) parseCloudProps(xpath string, component string, props map[string]interface{}, root map[string]interface{}) (map[string]CloudPropsDSL, error) { res := map[string]CloudPropsDSL{} @@ -148,7 +157,7 @@ func (p PropsDSL) parseCloudProps(xpath string, component string, props map[stri fullname := fmt.Sprintf("%s.%s", xpath, name) if sub, ok := prop.(map[string]interface{}); ok { - cProps, err := p.parseCloudProps(fullname, component, sub) + cProps, err := p.parseCloudProps(fullname, component, sub, root) if err != nil { return nil, err } @@ -165,6 +174,7 @@ func (p PropsDSL) parseCloudProps(xpath string, component string, props map[stri Name: strings.TrimPrefix(name, "$"), Type: component, Xpath: xpath, + Props: root, } err := cProp.Parse(prop) diff --git a/widgets/component/types.go b/widgets/component/types.go index c2df429a..df952526 100644 --- a/widgets/component/types.go +++ b/widgets/component/types.go @@ -104,4 +104,5 @@ type CloudPropsDSL struct { Name string `json:"name,omitempty"` Process string `json:"process,omitempty"` Query map[string]interface{} `json:"query,omitempty"` + Props map[string]interface{} `json:"props,omitempty"` // The original props } diff --git a/widgets/dashboard/mapping.go b/widgets/dashboard/mapping.go index daa21b29..6e18a4fc 100644 --- a/widgets/dashboard/mapping.go +++ b/widgets/dashboard/mapping.go @@ -60,6 +60,9 @@ func (dsl *DSL) mapping() error { for _, inst := range dsl.Layout.Filter.Columns { if filter, has := dsl.Fields.Filter[inst.Name]; has { + // Add the default value, and parse the backend only props + filter.Parse() + // Mapping ID dsl.Mapping.Filters[filter.ID] = inst.Name dsl.Mapping.Filters[inst.Name] = filter.ID @@ -80,6 +83,9 @@ func (dsl *DSL) mapping() error { if field, has := dsl.Fields.Dashboard[inst.Name]; has { + // Add the default value, and parse the backend only props + field.Parse() + // Mapping ID dsl.Mapping.Columns[field.ID] = inst.Name dsl.Mapping.Columns[inst.Name] = field.ID diff --git a/widgets/field/column.go b/widgets/field/column.go index eef798c3..359e2945 100644 --- a/widgets/field/column.go +++ b/widgets/field/column.go @@ -87,6 +87,16 @@ func (column ColumnDSL) EditBind() string { return column.Bind } +// Parse the column dsl, add the default value, and parse the backend only props +func (column ColumnDSL) Parse() { + if column.View != nil { + column.View.Parse() + } + if column.Edit != nil { + column.Edit.Parse() + } +} + // Clone column func (column *ColumnDSL) Clone() *ColumnDSL { new := ColumnDSL{ diff --git a/widgets/field/filter.go b/widgets/field/filter.go index 6d12f1f8..254229f4 100644 --- a/widgets/field/filter.go +++ b/widgets/field/filter.go @@ -27,6 +27,13 @@ func (filter *FilterDSL) UnmarshalJSON(data []byte) error { return nil } +// Parse the column dsl, add the default value, and parse the backend only props +func (filter FilterDSL) Parse() { + if filter.Edit != nil { + filter.Edit.Parse() + } +} + // Hash hash value func (filter FilterDSL) Hash() (string, error) { h := md4.New() diff --git a/widgets/form/form.go b/widgets/form/form.go index 58ae07b4..d9f70ab1 100644 --- a/widgets/form/form.go +++ b/widgets/form/form.go @@ -303,7 +303,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool) (map for _, cProp := range dsl.CProps { err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} { - if cProp.Type == "Upload" || cProp.Type == "WangEditor" { + t := strings.ToLower(cProp.Type) + if component.UploadComponents[t] { return fmt.Sprintf("/api/__yao/form/%s%s", dsl.ID, cProp.UploadPath()) } diff --git a/widgets/form/mapping.go b/widgets/form/mapping.go index cb921929..2de32491 100644 --- a/widgets/form/mapping.go +++ b/widgets/form/mapping.go @@ -48,6 +48,10 @@ func (dsl *DSL) mapping() error { dsl.Layout.listColumns(func(path string, inst Column) { if field, has := dsl.Fields.Form[inst.Name]; has { + + // Add the default value, and parse the backend only props + field.Parse() + // Mapping ID dsl.Mapping.Columns[field.ID] = inst.Name dsl.Mapping.Columns[inst.Name] = field.ID diff --git a/widgets/list/list.go b/widgets/list/list.go index 7fd05916..93dca942 100644 --- a/widgets/list/list.go +++ b/widgets/list/list.go @@ -261,7 +261,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool, quer cProp.Query = newQuery.(map[string]interface{}) } - if cProp.Type == "Upload" || cProp.Type == "WangEditor" { + t := strings.ToLower(cProp.Type) + if component.UploadComponents[t] { return fmt.Sprintf("/api/__yao/list/%s%s", dsl.ID, cProp.UploadPath()) } diff --git a/widgets/list/mapping.go b/widgets/list/mapping.go index 1c77cfb8..07e86076 100644 --- a/widgets/list/mapping.go +++ b/widgets/list/mapping.go @@ -50,6 +50,9 @@ func (dsl *DSL) mapping() error { if field, has := dsl.Fields.List[inst.Name]; has { + // Add the default value, and parse the backend only props + field.Parse() + // Mapping ID dsl.Mapping.Columns[field.ID] = inst.Name dsl.Mapping.Columns[inst.Name] = field.ID diff --git a/widgets/table/mapping.go b/widgets/table/mapping.go index dc2b794e..509f2330 100644 --- a/widgets/table/mapping.go +++ b/widgets/table/mapping.go @@ -65,6 +65,9 @@ func (dsl *DSL) mapping() error { if filter, has := dsl.Fields.Filter[inst.Name]; has { + // Add the default value, and parse the backend only props + filter.Parse() + // Mapping ID dsl.Mapping.Filters[filter.ID] = inst.Name dsl.Mapping.Filters[inst.Name] = filter.ID @@ -85,6 +88,9 @@ func (dsl *DSL) mapping() error { for _, inst := range dsl.Layout.Table.Columns { if field, has := dsl.Fields.Table[inst.Name]; has { + // Add the default value, and parse the backend only props + field.Parse() + // Mapping ID dsl.Mapping.Columns[field.ID] = inst.Name dsl.Mapping.Columns[inst.Name] = field.ID diff --git a/widgets/table/table.go b/widgets/table/table.go index f2634d89..0502c0c7 100644 --- a/widgets/table/table.go +++ b/widgets/table/table.go @@ -344,7 +344,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool) (map for _, cProp := range dsl.CProps { err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} { - if cProp.Type == "Upload" || cProp.Type == "WangEditor" || cProp.Type == "Image" { + t := strings.ToLower(cProp.Type) + if component.UploadComponents[t] { return fmt.Sprintf("/api/__yao/table/%s%s", dsl.ID, cProp.UploadPath()) }