Merge branch '2021.9.x-feature-wf-attachments' into 2021.9.x

This commit is contained in:
Denis Arh
2021-12-02 10:49:36 +01:00
18 changed files with 1079 additions and 73 deletions
+461
View File
@@ -0,0 +1,461 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// compose/automation/attachment_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/wfexec"
"io"
)
var _ wfexec.ExecResponse
type (
attachmentHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h attachmentHandler) register() {
h.reg.AddFunctions(
h.Lookup(),
h.Create(),
h.Delete(),
h.OpenOriginal(),
h.OpenPreview(),
)
}
type (
attachmentLookupArgs struct {
hasAttachment bool
Attachment uint64
}
attachmentLookupResults struct {
Attachment *types.Attachment
}
)
// Lookup function Compose record lookup
//
// expects implementation of lookup function:
// func (h attachmentHandler) lookup(ctx context.Context, args *attachmentLookupArgs) (results *attachmentLookupResults, err error) {
// return
// }
func (h attachmentHandler) Lookup() *atypes.Function {
return &atypes.Function{
Ref: "attachmentLookup",
Kind: "function",
Labels: map[string]string{"attachment": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Compose record lookup",
Description: "Find specific record by ID",
},
Parameters: []*atypes.Param{
{
Name: "attachment",
Types: []string{"ID"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "attachment",
Types: []string{"Attachment"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &attachmentLookupArgs{
hasAttachment: in.Has("attachment"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *attachmentLookupResults
if results, err = h.lookup(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Attachment (*types.Attachment) to Attachment
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Attachment").Cast(results.Attachment); err != nil {
return
} else if err = expr.Assign(out, "attachment", tval); err != nil {
return
}
}
return
},
}
}
type (
attachmentCreateArgs struct {
hasName bool
Name string
hasResource bool
Resource *types.Record
hasContent bool
Content interface{}
contentString string
contentStream io.Reader
contentBytes []byte
}
attachmentCreateResults struct {
Attachment *types.Attachment
}
)
func (a attachmentCreateArgs) GetContent() (bool, string, io.Reader, []byte) {
return a.hasContent, a.contentString, a.contentStream, a.contentBytes
}
// Create function Create file and attach it to a resource
//
// expects implementation of create function:
// func (h attachmentHandler) create(ctx context.Context, args *attachmentCreateArgs) (results *attachmentCreateResults, err error) {
// return
// }
func (h attachmentHandler) Create() *atypes.Function {
return &atypes.Function{
Ref: "attachmentCreate",
Kind: "function",
Labels: map[string]string{"attachment": "step,workflow", "create": "step"},
Meta: &atypes.FunctionMeta{
Short: "Create file and attach it to a resource",
},
Parameters: []*atypes.Param{
{
Name: "name",
Types: []string{"String"},
},
{
Name: "resource",
Types: []string{"ComposeRecord"}, Required: true,
},
{
Name: "content",
Types: []string{"String", "Reader", "Bytes"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "attachment",
Types: []string{"Attachment"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &attachmentCreateArgs{
hasName: in.Has("name"),
hasResource: in.Has("resource"),
hasContent: in.Has("content"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Content argument
if args.hasContent {
aux := expr.Must(expr.Select(in, "content"))
switch aux.Type() {
case h.reg.Type("String").Type():
args.contentString = aux.Get().(string)
case h.reg.Type("Reader").Type():
args.contentStream = aux.Get().(io.Reader)
case h.reg.Type("Bytes").Type():
args.contentBytes = aux.Get().([]byte)
}
}
var results *attachmentCreateResults
if results, err = h.create(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Attachment (*types.Attachment) to Attachment
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Attachment").Cast(results.Attachment); err != nil {
return
} else if err = expr.Assign(out, "attachment", tval); err != nil {
return
}
}
return
},
}
}
type (
attachmentDeleteArgs struct {
hasAttachment bool
Attachment uint64
}
)
// Delete function Delete attachment
//
// expects implementation of delete function:
// func (h attachmentHandler) delete(ctx context.Context, args *attachmentDeleteArgs) (err error) {
// return
// }
func (h attachmentHandler) Delete() *atypes.Function {
return &atypes.Function{
Ref: "attachmentDelete",
Kind: "function",
Labels: map[string]string{"attachment": "step,workflow", "delete": "step"},
Meta: &atypes.FunctionMeta{
Short: "Delete attachment",
},
Parameters: []*atypes.Param{
{
Name: "attachment",
Types: []string{"ID"}, Required: true,
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &attachmentDeleteArgs{
hasAttachment: in.Has("attachment"),
}
)
if err = in.Decode(args); err != nil {
return
}
return out, h.delete(ctx, args)
},
}
}
type (
attachmentOpenOriginalArgs struct {
hasAttachment bool
Attachment interface{}
attachmentID uint64
attachmentAttachment *types.Attachment
}
attachmentOpenOriginalResults struct {
Content io.Reader
}
)
func (a attachmentOpenOriginalArgs) GetAttachment() (bool, uint64, *types.Attachment) {
return a.hasAttachment, a.attachmentID, a.attachmentAttachment
}
// OpenOriginal function Open original attachment
//
// expects implementation of openOriginal function:
// func (h attachmentHandler) openOriginal(ctx context.Context, args *attachmentOpenOriginalArgs) (results *attachmentOpenOriginalResults, err error) {
// return
// }
func (h attachmentHandler) OpenOriginal() *atypes.Function {
return &atypes.Function{
Ref: "attachmentOpenOriginal",
Kind: "function",
Labels: map[string]string{"attachment": "step,workflow", "original-attachment": "step"},
Meta: &atypes.FunctionMeta{
Short: "Open original attachment",
},
Parameters: []*atypes.Param{
{
Name: "attachment",
Types: []string{"ID", "Attachment"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "content",
Types: []string{"Reader"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &attachmentOpenOriginalArgs{
hasAttachment: in.Has("attachment"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Attachment argument
if args.hasAttachment {
aux := expr.Must(expr.Select(in, "attachment"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.attachmentID = aux.Get().(uint64)
case h.reg.Type("Attachment").Type():
args.attachmentAttachment = aux.Get().(*types.Attachment)
}
}
var results *attachmentOpenOriginalResults
if results, err = h.openOriginal(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Content (io.Reader) to Reader
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Reader").Cast(results.Content); err != nil {
return
} else if err = expr.Assign(out, "content", tval); err != nil {
return
}
}
return
},
}
}
type (
attachmentOpenPreviewArgs struct {
hasAttachment bool
Attachment interface{}
attachmentID uint64
attachmentAttachment *types.Attachment
}
attachmentOpenPreviewResults struct {
Content io.Reader
}
)
func (a attachmentOpenPreviewArgs) GetAttachment() (bool, uint64, *types.Attachment) {
return a.hasAttachment, a.attachmentID, a.attachmentAttachment
}
// OpenPreview function Open attachment preview
//
// expects implementation of openPreview function:
// func (h attachmentHandler) openPreview(ctx context.Context, args *attachmentOpenPreviewArgs) (results *attachmentOpenPreviewResults, err error) {
// return
// }
func (h attachmentHandler) OpenPreview() *atypes.Function {
return &atypes.Function{
Ref: "attachmentOpenPreview",
Kind: "function",
Labels: map[string]string{"attachment": "step,workflow", "preview-attachment": "step"},
Meta: &atypes.FunctionMeta{
Short: "Open attachment preview",
},
Parameters: []*atypes.Param{
{
Name: "attachment",
Types: []string{"ID", "Attachment"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "content",
Types: []string{"Reader"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &attachmentOpenPreviewArgs{
hasAttachment: in.Has("attachment"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Attachment argument
if args.hasAttachment {
aux := expr.Must(expr.Select(in, "attachment"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.attachmentID = aux.Get().(uint64)
case h.reg.Type("Attachment").Type():
args.attachmentAttachment = aux.Get().(*types.Attachment)
}
}
var results *attachmentOpenPreviewResults
if results, err = h.openPreview(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Content (io.Reader) to Reader
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Reader").Cast(results.Content); err != nil {
return
} else if err = expr.Assign(out, "content", tval); err != nil {
return
}
}
return
},
}
}
+139
View File
@@ -0,0 +1,139 @@
package automation
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/cortezaproject/corteza-server/compose/types"
)
type (
attachmentService interface {
FindByID(ctx context.Context, namespaceID, attachmentID uint64) (*types.Attachment, error)
CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64) (att *types.Attachment, err error)
DeleteByID(ctx context.Context, namespaceID uint64, attachmentID uint64) error
OpenOriginal(att *types.Attachment) (io.ReadSeeker, error)
OpenPreview(att *types.Attachment) (io.ReadSeeker, error)
}
attachmentHandler struct {
reg modulesHandlerRegistry
svc attachmentService
}
attachmentLookup interface {
GetAttachment() (bool, uint64, *types.Attachment)
}
)
func AttachmentHandler(reg modulesHandlerRegistry, svc attachmentService) *attachmentHandler {
h := &attachmentHandler{
reg: reg,
svc: svc,
}
h.register()
return h
}
func (h attachmentHandler) lookup(ctx context.Context, args *attachmentLookupArgs) (results *attachmentLookupResults, err error) {
results = &attachmentLookupResults{}
results.Attachment, err = h.svc.FindByID(ctx, 0, args.Attachment)
return
}
func (h attachmentHandler) delete(ctx context.Context, args *attachmentDeleteArgs) error {
return h.svc.DeleteByID(ctx, 0, args.Attachment)
}
func (h attachmentHandler) openOriginal(ctx context.Context, args *attachmentOpenOriginalArgs) (*attachmentOpenOriginalResults, error) {
att, err := lookupAttachment(ctx, h.svc, args)
if err != nil {
return nil, err
}
r := &attachmentOpenOriginalResults{}
r.Content, err = h.svc.OpenOriginal(att)
if err != nil {
return nil, err
}
return r, nil
}
func (h attachmentHandler) openPreview(ctx context.Context, args *attachmentOpenPreviewArgs) (*attachmentOpenPreviewResults, error) {
att, err := lookupAttachment(ctx, h.svc, args)
if err != nil {
return nil, err
}
r := &attachmentOpenPreviewResults{}
r.Content, err = h.svc.OpenOriginal(att)
if err != nil {
return nil, err
}
return r, nil
}
func (h attachmentHandler) create(ctx context.Context, args *attachmentCreateArgs) (*attachmentCreateResults, error) {
var (
err error
att *types.Attachment
fh io.ReadSeeker
size int64
)
switch {
case len(args.contentBytes) > 0:
size = int64(len(args.contentString))
fh = bytes.NewReader(args.contentBytes)
case args.contentStream != nil:
if rs, is := args.contentStream.(io.ReadSeeker); is {
fh = rs
}
default:
fh = strings.NewReader(args.contentString)
size = int64(len(args.contentString))
}
switch {
case args.Resource != nil:
att, err = h.svc.CreateRecordAttachment(
ctx,
args.Resource.NamespaceID,
args.Name,
size,
fh,
args.Resource.ModuleID,
args.Resource.ID,
)
default:
return nil, fmt.Errorf("unknown resource")
}
if err != nil {
return nil, err
}
return &attachmentCreateResults{att}, nil
}
func lookupAttachment(ctx context.Context, svc attachmentService, args attachmentLookup) (*types.Attachment, error) {
_, ID, attachment := args.GetAttachment()
switch {
case attachment != nil:
return attachment, nil
case ID > 0:
return svc.FindByID(ctx, 0, ID)
}
return nil, fmt.Errorf("empty attachment lookup params")
}
@@ -0,0 +1,87 @@
# Note: we're intentionally not prefixing this with "compose"
# due to plans in the (near) future to refactor attachments
# from system & compose components into one single service
imports:
- github.com/cortezaproject/corteza-server/compose/types
- io
labels: &labels
attachment: "step,workflow"
functions:
lookup:
meta:
short: Compose record lookup
description: Find specific record by ID
labels:
<<: *labels
params:
attachment:
required: true
types: [ { wf: ID } ]
results:
attachment: { wf: Attachment }
create:
meta:
short: Create file and attach it to a resource
labels:
<<: *labels
create: "step"
params:
name:
types:
- { wf: String }
resource:
required: true
types:
- { wf: ComposeRecord }
content:
required: true
types:
- { wf: String, suffix: String }
- { wf: Reader, suffix: Stream }
- { wf: Bytes, suffix: Bytes }
results:
attachment: { wf: Attachment }
delete:
meta:
short: Delete attachment
labels:
<<: *labels
delete: "step"
params:
attachment:
required: true
types: [ { wf: ID } ]
openOriginal:
meta:
short: Open original attachment
labels:
<<: *labels
original-attachment: "step"
params:
attachment:
required: true
types: [ { wf: ID }, { wf: Attachment } ]
results:
content: { wf: Reader }
openPreview:
meta:
short: Open attachment preview
labels:
<<: *labels
preview-attachment: "step"
params:
attachment:
required: true
types: [ { wf: ID }, { wf: Attachment } ]
results:
content: { wf: Reader }
+201
View File
@@ -19,6 +19,207 @@ import (
var _ = context.Background
var _ = fmt.Errorf
// Attachment is an expression type, wrapper for *types.Attachment type
type Attachment struct {
value *types.Attachment
mux sync.RWMutex
}
// NewAttachment creates new instance of Attachment expression type
func NewAttachment(val interface{}) (*Attachment, error) {
if c, err := CastToAttachment(val); err != nil {
return nil, fmt.Errorf("unable to create Attachment: %w", err)
} else {
return &Attachment{value: c}, nil
}
}
// Get return underlying value on Attachment
func (t *Attachment) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on Attachment
func (t *Attachment) GetValue() *types.Attachment {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (Attachment) Type() string { return "Attachment" }
// Cast converts value to *types.Attachment
func (Attachment) Cast(val interface{}) (TypedValue, error) {
return NewAttachment(val)
}
// Assign new value to Attachment
//
// value is first passed through CastToAttachment
func (t *Attachment) Assign(val interface{}) error {
if c, err := CastToAttachment(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *Attachment) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToAttachment(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access Attachment's underlying value (*types.Attachment)
// and it's fields
//
func (t *Attachment) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return attachmentGValSelector(t.value, k)
}
// Select is field accessor for *types.Attachment
//
// Similar to SelectGVal but returns typed values
func (t *Attachment) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return attachmentTypedValueSelector(t.value, k)
}
func (t *Attachment) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "ID":
return true
case "kind":
return true
case "url":
return true
case "previewUrl":
return true
case "name":
return true
case "createdAt":
return true
case "updatedAt":
return true
case "deletedAt":
return true
}
return false
}
// attachmentGValSelector is field accessor for *types.Attachment
func attachmentGValSelector(res *types.Attachment, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "ID":
return res.ID, nil
case "kind":
return res.Kind, nil
case "url":
return res.Url, nil
case "previewUrl":
return res.PreviewUrl, nil
case "name":
return res.Name, nil
case "createdAt":
return res.CreatedAt, nil
case "updatedAt":
return res.UpdatedAt, nil
case "deletedAt":
return res.DeletedAt, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// attachmentTypedValueSelector is field accessor for *types.Attachment
func attachmentTypedValueSelector(res *types.Attachment, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "ID":
return NewID(res.ID)
case "kind":
return NewString(res.Kind)
case "url":
return NewHandle(res.Url)
case "previewUrl":
return NewHandle(res.PreviewUrl)
case "name":
return NewHandle(res.Name)
case "createdAt":
return NewDateTime(res.CreatedAt)
case "updatedAt":
return NewDateTime(res.UpdatedAt)
case "deletedAt":
return NewDateTime(res.DeletedAt)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToAttachment is field value setter for *types.Attachment
func assignToAttachment(res *types.Attachment, k string, val interface{}) error {
switch k {
case "ID":
return fmt.Errorf("field '%s' is read-only", k)
case "kind":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Kind = aux
return nil
case "url":
aux, err := CastToHandle(val)
if err != nil {
return err
}
res.Url = aux
return nil
case "previewUrl":
aux, err := CastToHandle(val)
if err != nil {
return err
}
res.PreviewUrl = aux
return nil
case "name":
aux, err := CastToHandle(val)
if err != nil {
return err
}
res.Name = aux
return nil
case "createdAt":
return fmt.Errorf("field '%s' is read-only", k)
case "updatedAt":
return fmt.Errorf("field '%s' is read-only", k)
case "deletedAt":
return fmt.Errorf("field '%s' is read-only", k)
}
return fmt.Errorf("unknown field '%s'", k)
}
// ComposeModule is an expression type, wrapper for *types.Module type
type ComposeModule struct {
value *types.Module
+9
View File
@@ -509,3 +509,12 @@ func isNil(i interface{}) bool {
return false
}
func CastToAttachment(val interface{}) (out *types.Attachment, err error) {
switch val := expr.UntypedValue(val).(type) {
case *types.Attachment:
return val, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
+11 -5
View File
@@ -51,8 +51,14 @@ types:
ComposeRecordValueErrorSet:
as: '*types.RecordValueErrorSet'
#
# Page:
# as: '*types.Page'
# Chart:
# as: '*types.Chart'
Attachment:
as: '*types.Attachment'
struct:
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro}
- { name: 'kind', exprType: 'String', goType: 'string' }
- { name: 'url', exprType: 'Handle', goType: 'string' }
- { name: 'previewUrl', exprType: 'Handle', goType: 'string' }
- { name: 'name', exprType: 'Handle', goType: 'string' }
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro }
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
+5 -5
View File
@@ -53,7 +53,7 @@ func (ctrl Attachment) List(ctx context.Context, r *request.AttachmentList) (int
FieldName: r.FieldName,
}
set, filter, err := ctrl.attachment.With(ctx).Find(f)
set, filter, err := ctrl.attachment.Find(ctx, f)
return ctrl.makeFilterPayload(ctx, set, filter, err)
}
@@ -62,7 +62,7 @@ func (ctrl Attachment) Read(ctx context.Context, r *request.AttachmentRead) (int
return nil, errors.New("Unauthorized")
}
a, err := ctrl.attachment.With(ctx).FindByID(r.NamespaceID, r.AttachmentID)
a, err := ctrl.attachment.FindByID(ctx, r.NamespaceID, r.AttachmentID)
return makeAttachmentPayload(ctx, a, err)
}
@@ -71,12 +71,12 @@ func (ctrl Attachment) Delete(ctx context.Context, r *request.AttachmentDelete)
return nil, errors.New("Unauthorized")
}
_, err := ctrl.attachment.With(ctx).FindByID(r.NamespaceID, r.AttachmentID)
_, err := ctrl.attachment.FindByID(ctx, r.NamespaceID, r.AttachmentID)
if err != nil {
return nil, err
}
return api.OK(), ctrl.attachment.With(ctx).DeleteByID(r.NamespaceID, r.AttachmentID)
return api.OK(), ctrl.attachment.DeleteByID(ctx, r.NamespaceID, r.AttachmentID)
}
func (ctrl Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
@@ -117,7 +117,7 @@ func (ctrl Attachment) isAccessible(namespaceID, attachmentID, userID uint64, si
func (ctrl Attachment) serve(ctx context.Context, namespaceID, attachmentID uint64, preview, download bool) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
att, err := ctrl.attachment.With(ctx).FindByID(namespaceID, attachmentID)
att, err := ctrl.attachment.FindByID(ctx, namespaceID, attachmentID)
if err != nil {
// Simplify error handling for now
w.WriteHeader(http.StatusNotFound)
+1 -1
View File
@@ -158,7 +158,7 @@ func (ctrl Namespace) Upload(ctx context.Context, r *request.NamespaceUpload) (i
}
defer file.Close()
a, err := ctrl.attachment.With(ctx).CreateNamespaceAttachment(
a, err := ctrl.attachment.CreateNamespaceAttachment(
ctx,
r.Upload.Filename,
r.Upload.Size,
+2 -1
View File
@@ -180,7 +180,8 @@ func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{
defer file.Close()
a, err := ctrl.attachment.With(ctx).CreatePageAttachment(
a, err := ctrl.attachment.CreatePageAttachment(
ctx,
r.NamespaceID,
r.Upload.Filename,
r.Upload.Size,
+2 -2
View File
@@ -266,14 +266,14 @@ func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interf
defer file.Close()
a, err := ctrl.attachment.With(ctx).CreateRecordAttachment(
a, err := ctrl.attachment.CreateRecordAttachment(
ctx,
r.NamespaceID,
r.Upload.Filename,
r.Upload.Size,
file,
r.ModuleID,
r.RecordID,
r.FieldName,
)
return makeAttachmentPayload(ctx, a, err)
+39 -52
View File
@@ -27,7 +27,6 @@ const (
type (
attachment struct {
ctx context.Context
actionlog actionlog.Recorder
objects objstore.Store
ac attachmentAccessController
@@ -46,38 +45,26 @@ type (
}
AttachmentService interface {
With(ctx context.Context) AttachmentService
FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error)
Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
CreatePageAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error)
CreateRecordAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error)
FindByID(ctx context.Context, namespaceID, attachmentID uint64) (*types.Attachment, error)
Find(ctx context.Context, filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
CreatePageAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error)
CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64) (*types.Attachment, error)
CreateNamespaceAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (*types.Attachment, error)
OpenOriginal(att *types.Attachment) (io.ReadSeeker, error)
OpenPreview(att *types.Attachment) (io.ReadSeeker, error)
DeleteByID(namespaceID, attachmentID uint64) error
DeleteByID(ctx context.Context, namespaceID, attachmentID uint64) error
}
)
func Attachment(store objstore.Store) AttachmentService {
return (&attachment{
func Attachment(store objstore.Store) *attachment {
return &attachment{
objects: store,
ac: DefaultAccessControl,
store: DefaultStore,
}).With(context.Background())
}
func (svc attachment) With(ctx context.Context) AttachmentService {
return &attachment{
ctx: ctx,
actionlog: DefaultActionlog,
ac: svc.ac,
objects: svc.objects,
store: svc.store,
}
}
func (svc attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
func (svc attachment) Find(ctx context.Context, filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
var (
aProps = &attachmentActionProps{filter: &filter}
)
@@ -88,38 +75,38 @@ func (svc attachment) Find(filter types.AttachmentFilter) (set types.AttachmentS
}
if filter.PageID > 0 {
aProps.namespace, aProps.page, err = loadPage(svc.ctx, svc.store, filter.NamespaceID, filter.PageID)
aProps.namespace, aProps.page, err = loadPage(ctx, svc.store, filter.NamespaceID, filter.PageID)
if err != nil {
return err
} else if svc.ac.CanReadPage(svc.ctx, aProps.page) {
} else if svc.ac.CanReadPage(ctx, aProps.page) {
return AttachmentErrNotAllowedToReadPage()
}
}
if filter.RecordID > 0 {
aProps.namespace, aProps.module, aProps.record, err = loadRecordCombo(svc.ctx, svc.store, filter.NamespaceID, filter.ModuleID, filter.RecordID)
aProps.namespace, aProps.module, aProps.record, err = loadRecordCombo(ctx, svc.store, filter.NamespaceID, filter.ModuleID, filter.RecordID)
if err != nil {
return err
} else if svc.ac.CanReadRecord(svc.ctx, aProps.record) {
} else if svc.ac.CanReadRecord(ctx, aProps.record) {
return AttachmentErrNotAllowedToReadRecord()
}
} else if filter.ModuleID > 0 {
aProps.namespace, aProps.module, err = loadModuleWithNamespace(svc.ctx, svc.store, filter.NamespaceID, filter.ModuleID)
aProps.namespace, aProps.module, err = loadModuleWithNamespace(ctx, svc.store, filter.NamespaceID, filter.ModuleID)
if err != nil {
return err
} else if svc.ac.CanReadRecord(svc.ctx, aProps.record) {
} else if svc.ac.CanReadRecord(ctx, aProps.record) {
return AttachmentErrNotAllowedToReadRecord()
}
}
set, f, err = store.SearchComposeAttachments(svc.ctx, svc.store, f)
set, f, err = store.SearchComposeAttachments(ctx, svc.store, f)
return err
}()
return set, f, svc.recordAction(svc.ctx, aProps, AttachmentActionSearch, err)
return set, f, svc.recordAction(ctx, aProps, AttachmentActionSearch, err)
}
func (svc attachment) FindByID(namespaceID, attachmentID uint64) (att *types.Attachment, err error) {
func (svc attachment) FindByID(ctx context.Context, namespaceID, attachmentID uint64) (att *types.Attachment, err error) {
var (
aProps = &attachmentActionProps{}
)
@@ -129,7 +116,7 @@ func (svc attachment) FindByID(namespaceID, attachmentID uint64) (att *types.Att
return AttachmentErrInvalidID()
}
if att, err = store.LookupComposeAttachmentByID(svc.ctx, svc.store, attachmentID); err != nil {
if att, err = store.LookupComposeAttachmentByID(ctx, svc.store, attachmentID); err != nil {
return err
}
@@ -137,16 +124,16 @@ func (svc attachment) FindByID(namespaceID, attachmentID uint64) (att *types.Att
return nil
}()
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionLookup, err)
return att, svc.recordAction(ctx, aProps, AttachmentActionLookup, err)
}
func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID uint64) (err error) {
var (
att *types.Attachment
aProps = &attachmentActionProps{attachment: &types.Attachment{ID: attachmentID}}
)
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
if attachmentID == 0 {
return AttachmentErrInvalidID()
}
@@ -161,7 +148,7 @@ func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
return store.UpdateComposeAttachment(ctx, s, att)
})
return svc.recordAction(svc.ctx, aProps, AttachmentActionDelete, err)
return svc.recordAction(ctx, aProps, AttachmentActionDelete, err)
}
//func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
@@ -174,7 +161,7 @@ func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
// return nil, AttachmentErrNamespaceNotFound()
// }
//
// if !svc.ac.CanReadNamespace(svc.ctx, ns) {
// if !svc.ac.CanReadNamespace(ctx, ns) {
// return nil, AttachmentErrNotAllowedToReadNamespace()
// }
//
@@ -191,7 +178,7 @@ func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
// return nil, AttachmentErrPageNotFound()
// }
//
// if !svc.ac.CanReadPage(svc.ctx, p) {
// if !svc.ac.CanReadPage(ctx, p) {
// return nil, AttachmentErrNotAllowedToReadPage()
// }
//
@@ -208,7 +195,7 @@ func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
// return nil, AttachmentErrModuleNotFound()
// }
//
// if !svc.ac.CanReadModule(svc.ctx, m) {
// if !svc.ac.CanReadModule(ctx, m) {
// return nil, AttachmentErrNotAllowedToReadModule()
// }
//
@@ -225,7 +212,7 @@ func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
// return nil, AttachmentErrRecordNotFound()
// }
//
// if !svc.ac.CanReadRecord(svc.ctx, m) {
// if !svc.ac.CanReadRecord(ctx, m) {
// return nil, AttachmentErrNotAllowedToReadRecord()
// }
//
@@ -248,7 +235,7 @@ func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error)
return svc.objects.Open(att.PreviewUrl)
}
func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (att *types.Attachment, err error) {
func (svc attachment) CreatePageAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (att *types.Attachment, err error) {
var (
ns *types.Namespace
p *types.Page
@@ -260,7 +247,7 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size
)
err = func() error {
ns, p, err = loadPage(svc.ctx, svc.store, namespaceID, pageID)
ns, p, err = loadPage(ctx, svc.store, namespaceID, pageID)
if err != nil {
return err
}
@@ -268,7 +255,7 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size
aProps.setNamespace(ns)
aProps.setPage(p)
if !svc.ac.CanUpdatePage(svc.ctx, p) {
if !svc.ac.CanUpdatePage(ctx, p) {
return AttachmentErrNotAllowedToUpdatePage()
}
@@ -278,14 +265,14 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size
Kind: types.PageAttachment,
}
return svc.create(name, size, fh, att)
return svc.create(ctx, name, size, fh, att)
}()
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionCreate, err)
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
}
func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (att *types.Attachment, err error) {
func (svc attachment) CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64) (att *types.Attachment, err error) {
var (
ns *types.Namespace
m *types.Module
@@ -298,7 +285,7 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
}
)
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
ns, m, err = loadModuleWithNamespace(ctx, s, namespaceID, moduleID)
if err != nil {
return err
@@ -339,10 +326,10 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
Kind: types.RecordAttachment,
}
return svc.create(name, size, fh, att)
return svc.create(ctx, name, size, fh, att)
})
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionCreate, err)
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
}
func (svc attachment) CreateNamespaceAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (att *types.Attachment, err error) {
@@ -361,13 +348,13 @@ func (svc attachment) CreateNamespaceAttachment(ctx context.Context, name string
Kind: types.NamespaceAttachment,
}
return svc.create(name, size, fh, att)
return svc.create(ctx, name, size, fh, att)
})
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
}
func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) {
func (svc attachment) create(ctx context.Context, name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) {
var (
aProps = &attachmentActionProps{}
)
@@ -377,7 +364,7 @@ func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *typ
att.CreatedAt = *now()
if att.OwnerID == 0 {
att.OwnerID = auth.GetIdentityFromContext(svc.ctx).Identity()
att.OwnerID = auth.GetIdentityFromContext(ctx).Identity()
}
if svc.objects == nil {
@@ -412,7 +399,7 @@ func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *typ
return AttachmentErrFailedToProcessImage(aProps).Wrap(err)
}
if err = store.CreateComposeAttachment(svc.ctx, svc.store, att); err != nil {
if err = store.CreateComposeAttachment(ctx, svc.store, att); err != nil {
return
}
+6
View File
@@ -157,6 +157,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
automation.ComposeModule{},
automation.ComposeRecord{},
automation.ComposeRecordValues{},
automation.Attachment{},
)
automation.RecordsHandler(
@@ -177,6 +178,11 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
DefaultNamespace,
)
automation.AttachmentHandler(
automationService.Registry(),
DefaultAttachment,
)
// Register reporters
// @todo additional datasource providers; generate?
systemService.DefaultReport.RegisterReporter("composeRecords", DefaultRecord)
+5 -2
View File
@@ -3,12 +3,13 @@ package codegen
import (
"encoding/json"
"fmt"
. "github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
"os"
"path"
"strings"
"text/template"
. "github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
type (
@@ -164,6 +165,7 @@ func expandAutomationFunctionTypes(ff []*aFuncDefs, tt []*exprTypesDef) {
for _, t := range p.Types {
if ti[t.WorkflowType] == nil {
fmt.Printf("%s/%s(): unknown type %q used for param %q\n", f.Prefix, fn.Name, t.WorkflowType, p.Name)
continue
}
if t.GoType == "" {
@@ -182,6 +184,7 @@ func expandAutomationFunctionTypes(ff []*aFuncDefs, tt []*exprTypesDef) {
for _, r := range fn.Results {
if ti[r.WorkflowType] == nil {
fmt.Printf("%s/%s(): unknown type %q used for result %q\n", f.Prefix, fn.Name, r.WorkflowType, r.Name)
continue
}
if r.GoType == "" {
+1
View File
@@ -1 +1,2 @@
.env
workflows/var
@@ -0,0 +1,43 @@
package workflows
import (
"context"
"io"
"io/ioutil"
"testing"
"github.com/cortezaproject/corteza-server/automation/types"
cmpTypes "github.com/cortezaproject/corteza-server/compose/types"
"github.com/stretchr/testify/require"
)
func Test_attachment_management(t *testing.T) {
var (
ctx = bypassRBAC(context.Background())
req = require.New(t)
)
req.NoError(defStore.TruncateAttachments(ctx))
loadNewScenario(ctx, t)
var (
aux = struct {
Base64blackGif string
LoadedAttBlackGif *cmpTypes.Attachment
StoredAttBlackGif *cmpTypes.Attachment
LoadedContent io.ReadSeeker
}{}
)
vars, _ := mustExecWorkflow(ctx, t, "attachments", types.WorkflowExecParams{})
req.NoError(vars.Decode(&aux))
req.Equal(int(aux.LoadedAttBlackGif.Meta.Original.Size), len(aux.Base64blackGif))
req.Equal(int(aux.StoredAttBlackGif.Meta.Original.Size), len(aux.Base64blackGif))
b, err := ioutil.ReadAll(aux.LoadedContent)
req.NoError(err)
req.Equal(aux.Base64blackGif, string(b))
}
-5
View File
@@ -16,7 +16,6 @@ import (
"github.com/cortezaproject/corteza-server/pkg/envoy/json"
envoyStore "github.com/cortezaproject/corteza-server/pkg/envoy/store"
"github.com/cortezaproject/corteza-server/pkg/envoy/yaml"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/id"
@@ -146,10 +145,6 @@ func mustExecWorkflow(ctx context.Context, t *testing.T, name string, p autTypes
}
}
if unw := errors.Unwrap(err); unw != nil {
err = unw
}
t.Fatalf("could not exec %q: %v", name, err)
}
@@ -0,0 +1,9 @@
namespaces:
ns1:
name: Namespace#1
modules:
mod1:
name: Module#1
fields:
f1: { label: 'Field1' }
@@ -0,0 +1,58 @@
workflows:
attachments:
enabled: true
trace: true
triggers:
- enabled: true
stepID: 10
steps:
- stepID: 10
kind: expressions
arguments:
- { target: base64blackGif, value: "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" }
- stepID: 11
kind: function
ref: composeRecordsNew
arguments:
- { target: module, type: Handle, value: "mod1" }
- { target: namespace, type: Handle, value: "ns1" }
results:
- { target: attachable, expr: "record" }
# Store attachment from given string
- stepID: 20
kind: function
ref: attachmentCreate
arguments:
- { target: content, type: String, expr: "base64blackGif" }
- { target: name, type: String, value: "black-from-base64-string.gif" }
- { target: resource, type: ComposeRecord, expr: "attachable" }
results:
- { target: storedAttBlackGif, expr: "attachment" }
# Load attachment
- stepID: 30
kind: function
ref: attachmentLookup
arguments:
- { target: attachment, type: ID, expr: "storedAttBlackGif.ID" }
results:
- { target: loadedAttBlackGif, expr: "attachment" }
# Open attachment
- stepID: 31
kind: function
ref: attachmentOpenOriginal
arguments:
- { target: attachment, type: "Attachment", expr: "storedAttBlackGif" }
results:
- { target: loadedContent, expr: "content" }
paths:
- { parentID: 10, childID: 11 }
- { parentID: 11, childID: 20 }
- { parentID: 20, childID: 30 }
- { parentID: 30, childID: 31 }