diff --git a/widgets/component/props.go b/widgets/component/props.go index 9db963e2..65fd0fe9 100644 --- a/widgets/component/props.go +++ b/widgets/component/props.go @@ -11,8 +11,8 @@ import ( ) // CloudProps parse CloudProps -func (p PropsDSL) CloudProps(xpath string) (map[string]CloudPropsDSL, error) { - return p.parseCloudProps(xpath, p) +func (p PropsDSL) CloudProps(xpath, component string) (map[string]CloudPropsDSL, error) { + return p.parseCloudProps(xpath, component, p) } // Path api path @@ -20,6 +20,43 @@ func (cProp CloudPropsDSL) Path() string { return fmt.Sprintf("/component/%s/%s", url.QueryEscape(cProp.Xpath), url.QueryEscape(cProp.Name)) } +// UploadPath api UploadPath +func (cProp CloudPropsDSL) UploadPath() string { + return fmt.Sprintf("/upload/%s/%s", url.QueryEscape(cProp.Xpath), url.QueryEscape(cProp.Name)) +} + +// ExecUpload execute upload +func (cProp CloudPropsDSL) ExecUpload(process *gou.Process, upload gou.UploadFile) (interface{}, error) { + + if upload.TempFile == "" { + log.Error("[component] %s.$%s upload file is required", cProp.Xpath, cProp.Name) + return nil, fmt.Errorf("[component] %s.$%s upload file is required", cProp.Xpath, cProp.Name) + } + + // Process + name := cProp.Process + if name == "" { + log.Error("[component] %s.$%s process is required", cProp.Xpath, cProp.Name) + return nil, fmt.Errorf("[component] %s.$%s process is required", cProp.Xpath, cProp.Name) + } + + // Create process + p, err := gou.ProcessOf(name, upload) + 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() + 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()) + } + + return res, nil +} + // ExecQuery execute query func (cProp CloudPropsDSL) ExecQuery(process *gou.Process, query map[string]interface{}) (interface{}, error) { @@ -95,7 +132,7 @@ func (cProp CloudPropsDSL) replaceMap(data map[string]interface{}, root string, return nil } -func (p PropsDSL) parseCloudProps(xpath string, props map[string]interface{}) (map[string]CloudPropsDSL, error) { +func (p PropsDSL) parseCloudProps(xpath string, component string, props map[string]interface{}) (map[string]CloudPropsDSL, error) { res := map[string]CloudPropsDSL{} @@ -103,7 +140,7 @@ func (p PropsDSL) parseCloudProps(xpath string, props map[string]interface{}) (m fullname := fmt.Sprintf("%s.%s", xpath, name) if sub, ok := prop.(map[string]interface{}); ok { - cProps, err := p.parseCloudProps(fullname, sub) + cProps, err := p.parseCloudProps(fullname, component, sub) if err != nil { return nil, err } @@ -118,6 +155,7 @@ func (p PropsDSL) parseCloudProps(xpath string, props map[string]interface{}) (m cProp := &CloudPropsDSL{ Name: strings.TrimPrefix(name, "$"), + Type: component, Xpath: xpath, } diff --git a/widgets/component/types.go b/widgets/component/types.go index 5b5b5386..8f898100 100644 --- a/widgets/component/types.go +++ b/widgets/component/types.go @@ -66,6 +66,7 @@ type ComputeHanlder func(args ...interface{}) (interface{}, error) // CloudPropsDSL the cloud props type CloudPropsDSL struct { Xpath string `json:"xpath,omitempty"` + Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` Process string `json:"process,omitempty"` Query map[string]interface{} `json:"query,omitempty"` diff --git a/widgets/field/column.go b/widgets/field/column.go index 382ef076..dcf1017f 100644 --- a/widgets/field/column.go +++ b/widgets/field/column.go @@ -96,7 +96,7 @@ func (columns Columns) CPropsMerge(cloudProps map[string]component.CloudPropsDSL if column.Edit != nil && column.Edit.Props != nil { xpath := getXpath(name, "edit", column) - cProps, err := column.Edit.Props.CloudProps(xpath) + cProps, err := column.Edit.Props.CloudProps(xpath, column.Edit.Type) if err != nil { return err } @@ -105,7 +105,7 @@ func (columns Columns) CPropsMerge(cloudProps map[string]component.CloudPropsDSL if column.View != nil && column.View.Props != nil { xpath := getXpath(name, "view", column) - cProps, err := column.View.Props.CloudProps(xpath) + cProps, err := column.View.Props.CloudProps(xpath, column.View.Type) if err != nil { return err } diff --git a/widgets/field/filter.go b/widgets/field/filter.go index f4c62883..21736c06 100644 --- a/widgets/field/filter.go +++ b/widgets/field/filter.go @@ -67,7 +67,7 @@ func (filters Filters) CPropsMerge(cloudProps map[string]component.CloudPropsDSL for name, filter := range filters { if filter.Edit != nil && filter.Edit.Props != nil { xpath := getXpath(name, filter) - cProps, err := filter.Edit.Props.CloudProps(xpath) + cProps, err := filter.Edit.Props.CloudProps(xpath, filter.Edit.Type) if err != nil { return err } diff --git a/widgets/table/action.go b/widgets/table/action.go index 3063abfe..a26e34f8 100644 --- a/widgets/table/action.go +++ b/widgets/table/action.go @@ -21,6 +21,11 @@ var processActionDefaults = map[string]*action.Process{ Guard: "bearer-jwt", Default: []interface{}{nil, nil, nil}, }, + "Upload": { + Name: "yao.table.Upload", + Guard: "bearer-jwt", + Default: []interface{}{nil, nil, nil}, + }, "Search": { Name: "yao.table.Search", Guard: "bearer-jwt", @@ -94,6 +99,10 @@ func (act *ActionDSL) SetDefaultProcess() { Merge(processActionDefaults["Component"]). SetHandler(processHandler) + act.Upload = action.ProcessOf(act.Upload). + Merge(processActionDefaults["Upload"]). + SetHandler(processHandler) + act.Search = action.ProcessOf(act.Search). WithBefore(act.BeforeSearch).WithAfter(act.AfterSearch). Merge(processActionDefaults["Search"]). diff --git a/widgets/table/api.go b/widgets/table/api.go index d22f87c0..759304e4 100644 --- a/widgets/table/api.go +++ b/widgets/table/api.go @@ -51,6 +51,8 @@ func (table *DSL) getAction(path string) (*action.Process, error) { return table.Action.Setting, nil case "/api/__yao/table/:id/component/:xpath/:method": return table.Action.Component, nil + case "/api/__yao/table/:id/upload/:xpath/:method": + return table.Action.Upload, nil case "/api/__yao/table/:id/search": return table.Action.Search, nil case "/api/__yao/table/:id/get": @@ -152,6 +154,18 @@ func exportAPI() error { } http.Paths = append(http.Paths, path) + // POST /api/__yao/table/:id/upload/:xpath/:method -> Default process: yao.table.Component $param.id $param.xpath $param.method $file.file + path = gou.Path{ + Label: "Upload", + Description: "Component", + Path: "/:id/upload/:xpath/:method", + Method: "POST", + Process: "yao.table.Upload", + In: []string{"$param.id", "$param.xpath", "$param.method", "$file.file"}, + Out: gou.Out{Status: 200, Type: "application/json"}, + } + http.Paths = append(http.Paths, path) + // POST /api/__yao/table/:id/save -> Default process: yao.table.Save $param.id :payload path = gou.Path{ Label: "Save", diff --git a/widgets/table/process.go b/widgets/table/process.go index 8869bead..c952b72c 100644 --- a/widgets/table/process.go +++ b/widgets/table/process.go @@ -14,6 +14,7 @@ func exportProcess() { gou.RegisterProcessHandler("yao.table.setting", processSetting) gou.RegisterProcessHandler("yao.table.xgen", processXgen) gou.RegisterProcessHandler("yao.table.component", processComponent) + gou.RegisterProcessHandler("yao.table.upload", processUpload) gou.RegisterProcessHandler("yao.table.search", processSearch) gou.RegisterProcessHandler("yao.table.get", processGet) gou.RegisterProcessHandler("yao.table.find", processFind) @@ -39,6 +40,35 @@ func processXgen(process *gou.Process) interface{} { return setting } +func processUpload(process *gou.Process) interface{} { + + process.ValidateArgNums(4) + tab := MustGet(process) + xpath := process.ArgsString(1) + method := process.ArgsString(2) + key := fmt.Sprintf("%s.$%s", xpath, method) + + // get cloud props + cProp, has := tab.CProps[key] + if !has { + exception.New("%s does not exist", 400, key).Throw() + } + + // $file.file + tmpfile, ok := process.Args[3].(gou.UploadFile) + if !ok { + exception.New("parameters error: %v", 400, process.Args[3]).Throw() + } + + // execute upload + res, err := cProp.ExecUpload(process, tmpfile) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + return res +} + func processComponent(process *gou.Process) interface{} { process.ValidateArgNums(3) diff --git a/widgets/table/process_test.go b/widgets/table/process_test.go index f0d38b43..3a9f25fe 100644 --- a/widgets/table/process_test.go +++ b/widgets/table/process_test.go @@ -3,6 +3,7 @@ package table import ( "fmt" "net/url" + "os" "testing" "github.com/stretchr/testify/assert" @@ -331,6 +332,27 @@ func TestProcessComponent(t *testing.T) { assert.Equal(t, "checked", pets[1]["value"]) } +func TestProcessUpload(t *testing.T) { + load(t) + clear(t) + testData(t) + args := []interface{}{ + "pet", + "fields.table.相关图片.edit.props", + "api", + gou.UploadFile{TempFile: tempFile(t)}, + } + + res, err := gou.NewProcess("yao.table.Upload", args...).Exec() + if err != nil { + t.Fatal(err) + } + + file, ok := res.(string) + assert.True(t, ok) + assert.NotEmpty(t, file) +} + func TestProcessComponentError(t *testing.T) { load(t) clear(t) @@ -405,6 +427,21 @@ func testData(t *testing.T) { } } +func tempFile(t *testing.T) string { + file, err := os.CreateTemp("", "unit-test") + if err != nil { + t.Fatal(err) + } + defer file.Close() + + _, err = file.Write([]byte("HELLO")) + if err != nil { + t.Fatal(err) + } + + return file.Name() +} + func clear(t *testing.T) { for _, m := range gou.Models { err := m.DropTable() diff --git a/widgets/table/table.go b/widgets/table/table.go index 21f1508b..555643f2 100644 --- a/widgets/table/table.go +++ b/widgets/table/table.go @@ -271,6 +271,11 @@ func (dsl *DSL) Xgen() (map[string]interface{}, error) { setting["config"] = dsl.Config for _, cProp := range dsl.CProps { err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} { + + if cProp.Type == "Upload" { + return fmt.Sprintf("/api/__yao/table/%s%s", dsl.ID, cProp.UploadPath()) + } + return map[string]interface{}{ "api": fmt.Sprintf("/api/__yao/table/%s%s", dsl.ID, cProp.Path()), "params": cProp.Query, diff --git a/widgets/table/table_test.go b/widgets/table/table_test.go index bf093c5c..fb2e30cc 100644 --- a/widgets/table/table_test.go +++ b/widgets/table/table_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/flow" + "github.com/yaoapp/yao/fs" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/runtime" "github.com/yaoapp/yao/script" @@ -41,6 +42,12 @@ func prepare(t *testing.T, language ...string) { t.Fatal(err) } + // load fs + err = fs.Load(config.Conf) + if err != nil { + t.Fatal(err) + } + // load scripts err = script.Load(config.Conf) if err != nil { diff --git a/widgets/table/types.go b/widgets/table/types.go index 6608859e..3fe2a5a9 100644 --- a/widgets/table/types.go +++ b/widgets/table/types.go @@ -26,6 +26,7 @@ type ActionDSL struct { Bind *BindActionDSL `json:"bind,omitempty"` Setting *action.Process `json:"setting,omitempty"` Component *action.Process `json:"component,omitempty"` + Upload *action.Process `json:"upload,omitempty"` Search *action.Process `json:"search,omitempty"` Get *action.Process `json:"get,omitempty"` Find *action.Process `json:"find,omitempty"`