diff --git a/widgets/component/handlers.go b/widgets/component/handlers.go index 70d8a60d..333fb430 100644 --- a/widgets/component/handlers.go +++ b/widgets/component/handlers.go @@ -121,7 +121,13 @@ func Upload(args ...interface{}) (interface{}, error) { return nil, fmt.Errorf("Upload args[4] is not string") } - preifx := fmt.Sprintf("/api/__yao/table/%s/download/%s?name=", id, url.QueryEscape(path)) + widget := "table" + pinfo := strings.Split(path, ".") + if len(pinfo) >= 2 { + widget = pinfo[1] + } + + preifx := fmt.Sprintf("/api/__yao/%s/%s/download/%s?name=", widget, id, url.QueryEscape(path)) res := []string{} for _, file := range files { file = strings.TrimSpace(file) @@ -195,6 +201,12 @@ func Download(args ...interface{}) (interface{}, error) { return nil, fmt.Errorf("Download args[4] is not string") } + widget := "table" + pinfo := strings.Split(path, ".") + if len(pinfo) >= 2 { + widget = pinfo[1] + } + res := []string{} for _, file := range files { @@ -204,7 +216,7 @@ func Download(args ...interface{}) (interface{}, error) { continue } - file = fmt.Sprintf("/api/__yao/table/%s/download/%s?name=%s", id, url.QueryEscape(path), file) + file = fmt.Sprintf("/api/__yao/%s/%s/download/%s?name=%s", widget, id, url.QueryEscape(path), file) res = append(res, file) } diff --git a/widgets/form/action.go b/widgets/form/action.go index e529e7ff..afd7577d 100644 --- a/widgets/form/action.go +++ b/widgets/form/action.go @@ -27,6 +27,12 @@ var processActionDefaults = map[string]*action.Process{ Guard: "bearer-jwt", Default: []interface{}{nil, nil, nil}, }, + "Download": { + Name: "yao.form.Download", + Guard: "-", + Process: "fs.system.Download", + Default: []interface{}{nil}, + }, "Find": { Name: "yao.form.Find", Guard: "bearer-jwt", @@ -69,6 +75,10 @@ func (act *ActionDSL) SetDefaultProcess() { Merge(processActionDefaults["Upload"]). SetHandler(processHandler) + act.Download = action.ProcessOf(act.Download). + Merge(processActionDefaults["Download"]). + SetHandler(processHandler) + act.Find = action.ProcessOf(act.Find). WithBefore(act.BeforeFind).WithAfter(act.AfterFind). Merge(processActionDefaults["Find"]). diff --git a/widgets/form/api.go b/widgets/form/api.go index daade514..acc5b76e 100644 --- a/widgets/form/api.go +++ b/widgets/form/api.go @@ -53,6 +53,8 @@ func (form *DSL) getAction(path string) (*action.Process, error) { return form.Action.Component, nil case "/api/__yao/form/:id/upload/:xpath/:method": return form.Action.Upload, nil + case "/api/__yao/form/:id/download/:field": + return form.Action.Download, nil case "/api/__yao/form/:id/find/:primary": return form.Action.Find, nil case "/api/__yao/form/:id/save": @@ -128,6 +130,22 @@ func exportAPI() error { } http.Paths = append(http.Paths, path) + // GET /api/__yao/form/:id/download/:field -> Default process: yao.form.Download $param.id $param.xpath $param.field $query.name $query.token + path = gou.Path{ + Label: "Download", + Description: "Download", + Path: "/:id/download/:field", + Method: "GET", + Process: "yao.form.Download", + In: []string{"$param.id", "$param.field", "$query.name", "$query.token"}, + Out: gou.Out{ + Status: 200, + Body: "{{content}}", + Headers: map[string]string{"Content-Type": "{{type}}"}, + }, + } + http.Paths = append(http.Paths, path) + // POST /api/__yao/form/:id/save -> Default process: yao.form.Save $param.id :payload path = gou.Path{ Label: "Save", diff --git a/widgets/form/compute.go b/widgets/form/compute.go index ee33db8d..7618e6ae 100644 --- a/widgets/form/compute.go +++ b/widgets/form/compute.go @@ -11,9 +11,9 @@ func (dsl *DSL) getField() func(string) (*field.ColumnDSL, string, string, error return func(name string) (*field.ColumnDSL, string, string, error) { field, has := dsl.Fields.Form[name] if !has { - return nil, "fields.table", dsl.ID, fmt.Errorf("fields.table.%s does not exist", name) + return nil, "fields.form", dsl.ID, fmt.Errorf("fields.form.%s does not exist", name) } - return &field, "fields.table", dsl.ID, nil + return &field, "fields.form", dsl.ID, nil } } diff --git a/widgets/form/process.go b/widgets/form/process.go index 3efa0c90..beae5898 100644 --- a/widgets/form/process.go +++ b/widgets/form/process.go @@ -2,9 +2,13 @@ package form import ( "fmt" + "strings" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/fs" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/helper" ) // Export process @@ -13,6 +17,7 @@ func exportProcess() { gou.RegisterProcessHandler("yao.form.xgen", processXgen) gou.RegisterProcessHandler("yao.form.component", processComponent) gou.RegisterProcessHandler("yao.form.upload", processUpload) + gou.RegisterProcessHandler("yao.form.download", processDownload) gou.RegisterProcessHandler("yao.form.find", processFind) gou.RegisterProcessHandler("yao.form.save", processSave) gou.RegisterProcessHandler("yao.form.create", processCreate) @@ -60,6 +65,50 @@ func processComponent(process *gou.Process) interface{} { return res } +func processDownload(process *gou.Process) interface{} { + + process.ValidateArgNums(4) + form := MustGet(process) + field := process.ArgsString(1) + file := process.ArgsString(2) + tokenString := process.ArgsString(3) + + // checking + ext := fs.ExtName(file) + if _, has := fs.DownloadWhitelist[ext]; !has { + exception.New("%s.%s .%s file does not allow", 403, form.ID, field, ext).Throw() + } + + // Auth + tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer ")) + if tokenString == "" { + exception.New("%s.%s No permission", 403, form.ID, field).Throw() + } + claims := helper.JwtValidate(tokenString) + + // Get Process name + name := "fs.system.Download" + if form.Action.Download.Process != "" { + name = form.Action.Download.Process + } + + // Create process + p, err := gou.ProcessOf(name, file) + if err != nil { + log.Error("[downalod] %s.%s %s", form.ID, field, err.Error()) + exception.New("[downalod] %s.%s %s", 400, form.ID, field, err.Error()).Throw() + } + + // Excute process + res, err := p.WithGlobal(process.Global).WithSID(claims.SID).Exec() + if err != nil { + log.Error("[downalod] %s.%s %s", form.ID, field, err.Error()) + exception.New("[downalod] %s.%s %s", 500, form.ID, field, err.Error()).Throw() + } + + return res +} + func processUpload(process *gou.Process) interface{} { process.ValidateArgNums(4) diff --git a/widgets/form/process_test.go b/widgets/form/process_test.go index 6bc6290f..463fe53f 100644 --- a/widgets/form/process_test.go +++ b/widgets/form/process_test.go @@ -8,9 +8,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/fs" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/helper" q "github.com/yaoapp/yao/query" ) @@ -157,6 +159,20 @@ func TestProcessComponent(t *testing.T) { assert.Equal(t, "checked", pets[1]["status"]) } +func TestProcessComponentError(t *testing.T) { + load(t) + clear(t) + testData(t) + args := []interface{}{ + "pet", + "fields.filter.edit.props.状态.::not-exist", + "remote", + map[string]interface{}{"select": []string{"name", "status"}, "limit": 2}, + } + _, err := gou.NewProcess("yao.form.Component", args...).Exec() + assert.Contains(t, err.Error(), "fields.filter.edit.props.状态.::not-exist") +} + func TestProcessUpload(t *testing.T) { load(t) clear(t) @@ -178,18 +194,28 @@ func TestProcessUpload(t *testing.T) { assert.NotEmpty(t, file) } -func TestProcessComponentError(t *testing.T) { +func TestProcessDownload(t *testing.T) { load(t) clear(t) testData(t) - args := []interface{}{ - "pet", - "fields.filter.edit.props.状态.::not-exist", - "remote", - map[string]interface{}{"select": []string{"name", "status"}, "limit": 2}, + + jwt := helper.JwtMake(1, map[string]interface{}{"id": 1}, map[string]interface{}{"sid": 1}) + fs := fs.MustGet("system") + _, err := fs.WriteFile("/text.txt", []byte("Hello"), uint32(os.ModePerm)) + if err != nil { + t.Fatal(err) } - _, err := gou.NewProcess("yao.form.Component", args...).Exec() - assert.Contains(t, err.Error(), "fields.filter.edit.props.状态.::not-exist") + + args := []interface{}{"pet", "images", "/text.txt", jwt.Token} + res, err := gou.NewProcess("yao.form.Download", args...).Exec() + if err != nil { + t.Fatal(err) + } + + body, ok := res.(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, []byte("Hello"), body["content"]) + assert.Equal(t, "text/plain; charset=utf-8", body["type"]) } func TestProcessSetting(t *testing.T) { diff --git a/widgets/form/types.go b/widgets/form/types.go index 4ecd6130..6920eecf 100644 --- a/widgets/form/types.go +++ b/widgets/form/types.go @@ -27,6 +27,7 @@ type ActionDSL struct { Setting *action.Process `json:"setting,omitempty"` Component *action.Process `json:"component,omitempty"` Upload *action.Process `json:"upload,omitempty"` + Download *action.Process `json:"download,omitempty"` Find *action.Process `json:"find,omitempty"` Save *action.Process `json:"save,omitempty"` Update *action.Process `json:"update,omitempty"`