Merge pull request #261 from trheyi/main
[add] table.Download api & process &compute
This commit is contained in:
commit
350c9fedc3
9 changed files with 272 additions and 9 deletions
|
|
@ -16,6 +16,7 @@ var defaults = []CArg{
|
|||
{IsExp: true, key: "props", value: nil},
|
||||
{IsExp: true, key: "type", value: nil},
|
||||
{IsExp: true, key: "id", value: nil},
|
||||
{IsExp: true, key: "path", value: nil},
|
||||
}
|
||||
|
||||
// NewExp create a new exp CArg
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package component
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -10,6 +11,8 @@ var hanlders = map[string]ComputeHanlder{
|
|||
"Trim": Trim,
|
||||
"Hide": Hide,
|
||||
"Concat": Concat,
|
||||
"Download": Download,
|
||||
"Upload": Upload,
|
||||
"QueryString": Trim,
|
||||
"ImagesView": Trim,
|
||||
"ImagesEdit": Trim,
|
||||
|
|
@ -61,3 +64,153 @@ func Get(args ...interface{}) (interface{}, error) {
|
|||
func Hide(args ...interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Upload return the file download path
|
||||
func Upload(args ...interface{}) (interface{}, error) {
|
||||
|
||||
if len(args) < 5 {
|
||||
return nil, fmt.Errorf("Upload args[0]~args[4] is required")
|
||||
}
|
||||
|
||||
if args[0] == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
files := []string{}
|
||||
switch values := args[0].(type) {
|
||||
case []interface{}:
|
||||
for i := range values {
|
||||
file := fmt.Sprintf("%v", values[i])
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case []string:
|
||||
for _, file := range values {
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case string:
|
||||
if values != "" {
|
||||
files = append(files, fmt.Sprintf("%v", values))
|
||||
}
|
||||
break
|
||||
|
||||
case map[string]interface{}:
|
||||
for name := range values {
|
||||
file := fmt.Sprintf("%v", values[name])
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
id, ok := args[3].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Upload args[3] is not string")
|
||||
}
|
||||
|
||||
path, ok := args[4].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Upload args[4] is not string")
|
||||
}
|
||||
|
||||
preifx := fmt.Sprintf("/api/__yao/table/%s/download/%s?name=", id, url.QueryEscape(path))
|
||||
res := []string{}
|
||||
for _, file := range files {
|
||||
file = strings.TrimSpace(file)
|
||||
if strings.HasPrefix(file, "http") {
|
||||
res = append(res, file)
|
||||
continue
|
||||
}
|
||||
res = append(res, strings.TrimPrefix(file, preifx))
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Download return the file download path
|
||||
func Download(args ...interface{}) (interface{}, error) {
|
||||
|
||||
if len(args) < 5 {
|
||||
return nil, fmt.Errorf("Download args[0]~args[4] is required")
|
||||
}
|
||||
|
||||
if args[0] == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
files := []string{}
|
||||
switch values := args[0].(type) {
|
||||
case []interface{}:
|
||||
for i := range values {
|
||||
file := fmt.Sprintf("%v", values[i])
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case []string:
|
||||
for _, file := range values {
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case string:
|
||||
if values != "" {
|
||||
files = append(files, fmt.Sprintf("%v", values))
|
||||
}
|
||||
break
|
||||
|
||||
case map[string]interface{}:
|
||||
for name := range values {
|
||||
file := fmt.Sprintf("%v", values[name])
|
||||
if file != "" {
|
||||
files = append(files, fmt.Sprintf("%v", file))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
id, ok := args[3].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Download args[3] is not string")
|
||||
}
|
||||
|
||||
path, ok := args[4].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Download args[4] is not string")
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
for _, file := range files {
|
||||
|
||||
file = strings.TrimSpace(file)
|
||||
if strings.HasPrefix(file, "http") {
|
||||
res = append(res, file)
|
||||
continue
|
||||
}
|
||||
|
||||
file = fmt.Sprintf("/api/__yao/table/%s/download/%s?name=%s", id, url.QueryEscape(path), file)
|
||||
res = append(res, file)
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ func (c *Computable) editRow(process *gou.Process, res map[string]interface{}, g
|
|||
|
||||
data.Set("id", id)
|
||||
data.Set("value", res[key])
|
||||
data.Set("path", fmt.Sprintf("%s.%s", path, unit.Name))
|
||||
data.Merge(any.MapOf(field.Edit.Map()).MapStrAny.Dot())
|
||||
new, err := field.Edit.Compute.Value(data, process.Sid, process.Global)
|
||||
if err != nil {
|
||||
|
|
@ -243,6 +244,7 @@ func (c *Computable) viewRow(name string, process *gou.Process, res map[string]i
|
|||
|
||||
data.Set("value", res[key])
|
||||
data.Set("id", id)
|
||||
data.Set("path", fmt.Sprintf("%s.%s", path, unit.Name))
|
||||
data.Merge(any.MapOf(field.View.Map()).MapStrAny.Dot())
|
||||
new, err := field.View.Compute.Value(data, process.Sid, process.Global)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ var processActionDefaults = map[string]*action.Process{
|
|||
Guard: "bearer-jwt",
|
||||
Default: []interface{}{nil, nil, nil},
|
||||
},
|
||||
"Download": {
|
||||
Name: "yao.table.Download",
|
||||
Guard: "-",
|
||||
Process: "fs.system.Download",
|
||||
Default: []interface{}{nil},
|
||||
},
|
||||
"Search": {
|
||||
Name: "yao.table.Search",
|
||||
Guard: "bearer-jwt",
|
||||
|
|
@ -103,6 +109,10 @@ func (act *ActionDSL) SetDefaultProcess() {
|
|||
Merge(processActionDefaults["Upload"]).
|
||||
SetHandler(processHandler)
|
||||
|
||||
act.Download = action.ProcessOf(act.Download).
|
||||
Merge(processActionDefaults["Download"]).
|
||||
SetHandler(processHandler)
|
||||
|
||||
act.Search = action.ProcessOf(act.Search).
|
||||
WithBefore(act.BeforeSearch).WithAfter(act.AfterSearch).
|
||||
Merge(processActionDefaults["Search"]).
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ func (table *DSL) getAction(path string) (*action.Process, error) {
|
|||
return table.Action.Component, nil
|
||||
case "/api/__yao/table/:id/upload/:xpath/:method":
|
||||
return table.Action.Upload, nil
|
||||
case "/api/__yao/table/:id/download/:field":
|
||||
return table.Action.Download, nil
|
||||
case "/api/__yao/table/:id/search":
|
||||
return table.Action.Search, nil
|
||||
case "/api/__yao/table/:id/get":
|
||||
|
|
@ -166,6 +168,22 @@ func exportAPI() error {
|
|||
}
|
||||
http.Paths = append(http.Paths, path)
|
||||
|
||||
// GET /api/__yao/table/:id/download/:field -> Default process: yao.table.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.table.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/table/:id/save -> Default process: yao.table.Save $param.id :payload
|
||||
path = gou.Path{
|
||||
Label: "Save",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import (
|
|||
"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
|
||||
|
|
@ -15,6 +18,7 @@ func exportProcess() {
|
|||
gou.RegisterProcessHandler("yao.table.xgen", processXgen)
|
||||
gou.RegisterProcessHandler("yao.table.component", processComponent)
|
||||
gou.RegisterProcessHandler("yao.table.upload", processUpload)
|
||||
gou.RegisterProcessHandler("yao.table.download", processDownload)
|
||||
gou.RegisterProcessHandler("yao.table.search", processSearch)
|
||||
gou.RegisterProcessHandler("yao.table.get", processGet)
|
||||
gou.RegisterProcessHandler("yao.table.find", processFind)
|
||||
|
|
@ -40,6 +44,50 @@ func processXgen(process *gou.Process) interface{} {
|
|||
return setting
|
||||
}
|
||||
|
||||
func processDownload(process *gou.Process) interface{} {
|
||||
|
||||
process.ValidateArgNums(4)
|
||||
tab := 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, tab.ID, field, ext).Throw()
|
||||
}
|
||||
|
||||
// Auth
|
||||
tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer "))
|
||||
if tokenString == "" {
|
||||
exception.New("%s.%s No permission", 403, tab.ID, field).Throw()
|
||||
}
|
||||
claims := helper.JwtValidate(tokenString)
|
||||
|
||||
// Get Process name
|
||||
name := "fs.system.Download"
|
||||
if tab.Action.Download.Process != "" {
|
||||
name = tab.Action.Download.Process
|
||||
}
|
||||
|
||||
// Create process
|
||||
p, err := gou.ProcessOf(name, file)
|
||||
if err != nil {
|
||||
log.Error("[downalod] %s.%s %s", tab.ID, field, err.Error())
|
||||
exception.New("[downalod] %s.%s %s", 400, tab.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", tab.ID, field, err.Error())
|
||||
exception.New("[downalod] %s.%s %s", 500, tab.ID, field, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func processUpload(process *gou.Process) interface{} {
|
||||
|
||||
process.ValidateArgNums(4)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/gou/fs"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/helper"
|
||||
q "github.com/yaoapp/yao/query"
|
||||
)
|
||||
|
||||
|
|
@ -332,6 +334,20 @@ func TestProcessComponent(t *testing.T) {
|
|||
assert.Equal(t, "checked", pets[1]["value"])
|
||||
}
|
||||
|
||||
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.table.Component", args...).Exec()
|
||||
assert.Contains(t, err.Error(), "fields.filter.edit.props.状态.::not-exist")
|
||||
}
|
||||
|
||||
func TestProcessUpload(t *testing.T) {
|
||||
load(t)
|
||||
clear(t)
|
||||
|
|
@ -353,18 +369,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.table.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.table.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) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
// GET /api/__yao/table/:id/get -> Default process: yao.table.Get $param.id :query
|
||||
// GET /api/__yao/table/:id/find/:primary -> Default process: yao.table.Find $param.id $param.primary :query
|
||||
// GET /api/__yao/table/:id/component/:xpath/:method -> Default process: yao.table.Component $param.id $param.xpath $param.method :query
|
||||
// GET /api/__yao/table/:id/upload/:xpath/:method -> Default process: yao.table.Upload $param.id $param.xpath $param.method $file.file
|
||||
// GET /api/__yao/table/:id/download/:field -> Default process: yao.table.Download $param.id $param.field $query.name $query.token
|
||||
// POST /api/__yao/table/:id/save -> Default process: yao.table.Save $param.id :payload
|
||||
// POST /api/__yao/table/:id/create -> Default process: yao.table.Create $param.id :payload
|
||||
// POST /api/__yao/table/:id/insert -> Default process: yao.table.Insert :payload
|
||||
|
|
@ -39,7 +41,9 @@ import (
|
|||
// yao.table.Search Return the records with pagination
|
||||
// yao.table.Get Return the records without pagination
|
||||
// yao.table.Find Return the record via the given primary key
|
||||
// yao.table.Component Return the result defined in props.xProps
|
||||
// yao.table.Component Return the result defined in props
|
||||
// yao.table.Upload Upload file defined in props
|
||||
// yao.table.Download Download file defined in props
|
||||
// yao.table.Save Save a record, if given a primary key update, else insert
|
||||
// yao.table.Create Create a record
|
||||
// yao.table.Insert Insert records
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
Search *action.Process `json:"search,omitempty"`
|
||||
Get *action.Process `json:"get,omitempty"`
|
||||
Find *action.Process `json:"find,omitempty"`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue