diff --git a/system/automation/expr_types.gen.go b/system/automation/expr_types.gen.go index abb8fb7f2..cbb51b948 100644 --- a/system/automation/expr_types.gen.go +++ b/system/automation/expr_types.gen.go @@ -18,6 +18,250 @@ import ( var _ = context.Background var _ = fmt.Errorf +// Document is an expression type, wrapper for *RenderedDocument type +type Document struct{ value *RenderedDocument } + +// NewDocument creates new instance of Document expression type +func NewDocument(val interface{}) (*Document, error) { + if c, err := CastToDocument(val); err != nil { + return nil, fmt.Errorf("unable to create Document: %w", err) + } else { + return &Document{value: c}, nil + } +} + +// Return underlying value on Document +func (t Document) Get() interface{} { return t.value } + +// Return underlying value on Document +func (t Document) GetValue() *RenderedDocument { return t.value } + +// Return type name +func (Document) Type() string { return "Document" } + +// Convert value to *RenderedDocument +func (Document) Cast(val interface{}) (TypedValue, error) { + return NewDocument(val) +} + +// Assign new value to Document +// +// value is first passed through CastToDocument +func (t *Document) Assign(val interface{}) error { + if c, err := CastToDocument(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *Document) AssignFieldValue(key string, val interface{}) error { + return assignToDocument(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access Document's underlying value (*RenderedDocument) +// and it's fields +// +func (t Document) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return documentGValSelector(t.value, k) +} + +// Select is field accessor for *RenderedDocument +// +// Similar to SelectGVal but returns typed values +func (t Document) Select(k string) (TypedValue, error) { + return documentTypedValueSelector(t.value, k) +} + +func (t Document) Has(k string) bool { + switch k { + case "document": + return true + case "name": + return true + case "type": + return true + } + return false +} + +// documentGValSelector is field accessor for *RenderedDocument +func documentGValSelector(res *RenderedDocument, k string) (interface{}, error) { + switch k { + case "document": + return res.Document, nil + case "name": + return res.Name, nil + case "type": + return res.Type, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// documentTypedValueSelector is field accessor for *RenderedDocument +func documentTypedValueSelector(res *RenderedDocument, k string) (TypedValue, error) { + switch k { + case "document": + return NewReader(res.Document) + case "name": + return NewString(res.Name) + case "type": + return NewString(res.Type) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToDocument is field value setter for *RenderedDocument +func assignToDocument(res *RenderedDocument, k string, val interface{}) error { + switch k { + case "document": + aux, err := CastToReader(val) + if err != nil { + return err + } + + res.Document = aux + return nil + case "name": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Name = aux + return nil + case "type": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Type = aux + return nil + } + + return fmt.Errorf("unknown field '%s'", k) +} + +// DocumentType is an expression type, wrapper for types.DocumentType type +type DocumentType struct{ value types.DocumentType } + +// NewDocumentType creates new instance of DocumentType expression type +func NewDocumentType(val interface{}) (*DocumentType, error) { + if c, err := CastToDocumentType(val); err != nil { + return nil, fmt.Errorf("unable to create DocumentType: %w", err) + } else { + return &DocumentType{value: c}, nil + } +} + +// Return underlying value on DocumentType +func (t DocumentType) Get() interface{} { return t.value } + +// Return underlying value on DocumentType +func (t DocumentType) GetValue() types.DocumentType { return t.value } + +// Return type name +func (DocumentType) Type() string { return "DocumentType" } + +// Convert value to types.DocumentType +func (DocumentType) Cast(val interface{}) (TypedValue, error) { + return NewDocumentType(val) +} + +// Assign new value to DocumentType +// +// value is first passed through CastToDocumentType +func (t *DocumentType) Assign(val interface{}) error { + if c, err := CastToDocumentType(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +// RenderOptions is an expression type, wrapper for map[string]string type +type RenderOptions struct{ value map[string]string } + +// NewRenderOptions creates new instance of RenderOptions expression type +func NewRenderOptions(val interface{}) (*RenderOptions, error) { + if c, err := CastToRenderOptions(val); err != nil { + return nil, fmt.Errorf("unable to create RenderOptions: %w", err) + } else { + return &RenderOptions{value: c}, nil + } +} + +// Return underlying value on RenderOptions +func (t RenderOptions) Get() interface{} { return t.value } + +// Return underlying value on RenderOptions +func (t RenderOptions) GetValue() map[string]string { return t.value } + +// Return type name +func (RenderOptions) Type() string { return "RenderOptions" } + +// Convert value to map[string]string +func (RenderOptions) Cast(val interface{}) (TypedValue, error) { + return NewRenderOptions(val) +} + +// Assign new value to RenderOptions +// +// value is first passed through CastToRenderOptions +func (t *RenderOptions) Assign(val interface{}) error { + if c, err := CastToRenderOptions(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +// RenderVariables is an expression type, wrapper for map[string]interface{} type +type RenderVariables struct{ value map[string]interface{} } + +// NewRenderVariables creates new instance of RenderVariables expression type +func NewRenderVariables(val interface{}) (*RenderVariables, error) { + if c, err := CastToRenderVariables(val); err != nil { + return nil, fmt.Errorf("unable to create RenderVariables: %w", err) + } else { + return &RenderVariables{value: c}, nil + } +} + +// Return underlying value on RenderVariables +func (t RenderVariables) Get() interface{} { return t.value } + +// Return underlying value on RenderVariables +func (t RenderVariables) GetValue() map[string]interface{} { return t.value } + +// Return type name +func (RenderVariables) Type() string { return "RenderVariables" } + +// Convert value to map[string]interface{} +func (RenderVariables) Cast(val interface{}) (TypedValue, error) { + return NewRenderVariables(val) +} + +// Assign new value to RenderVariables +// +// value is first passed through CastToRenderVariables +func (t *RenderVariables) Assign(val interface{}) error { + if c, err := CastToRenderVariables(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + // Role is an expression type, wrapper for *types.Role type type Role struct{ value *types.Role } @@ -188,6 +432,356 @@ func assignToRole(res *types.Role, k string, val interface{}) error { return fmt.Errorf("unknown field '%s'", k) } +// Template is an expression type, wrapper for *types.Template type +type Template struct{ value *types.Template } + +// NewTemplate creates new instance of Template expression type +func NewTemplate(val interface{}) (*Template, error) { + if c, err := CastToTemplate(val); err != nil { + return nil, fmt.Errorf("unable to create Template: %w", err) + } else { + return &Template{value: c}, nil + } +} + +// Return underlying value on Template +func (t Template) Get() interface{} { return t.value } + +// Return underlying value on Template +func (t Template) GetValue() *types.Template { return t.value } + +// Return type name +func (Template) Type() string { return "Template" } + +// Convert value to *types.Template +func (Template) Cast(val interface{}) (TypedValue, error) { + return NewTemplate(val) +} + +// Assign new value to Template +// +// value is first passed through CastToTemplate +func (t *Template) Assign(val interface{}) error { + if c, err := CastToTemplate(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *Template) AssignFieldValue(key string, val interface{}) error { + return assignToTemplate(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access Template's underlying value (*types.Template) +// and it's fields +// +func (t Template) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return templateGValSelector(t.value, k) +} + +// Select is field accessor for *types.Template +// +// Similar to SelectGVal but returns typed values +func (t Template) Select(k string) (TypedValue, error) { + return templateTypedValueSelector(t.value, k) +} + +func (t Template) Has(k string) bool { + switch k { + case "ID": + return true + case "handle": + return true + case "language": + return true + case "type": + return true + case "partial": + return true + case "meta": + return true + case "template": + return true + case "labels": + return true + case "ownerID": + return true + case "createdAt": + return true + case "updatedAt": + return true + case "deletedAt": + return true + case "lastUsedAt": + return true + } + return false +} + +// templateGValSelector is field accessor for *types.Template +func templateGValSelector(res *types.Template, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, nil + case "handle": + return res.Handle, nil + case "language": + return res.Language, nil + case "type": + return res.Type, nil + case "partial": + return res.Partial, nil + case "meta": + return res.Meta, nil + case "template": + return res.Template, nil + case "labels": + return res.Labels, nil + case "ownerID": + return res.OwnerID, nil + case "createdAt": + return res.CreatedAt, nil + case "updatedAt": + return res.UpdatedAt, nil + case "deletedAt": + return res.DeletedAt, nil + case "lastUsedAt": + return res.LastUsedAt, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// templateTypedValueSelector is field accessor for *types.Template +func templateTypedValueSelector(res *types.Template, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "handle": + return NewHandle(res.Handle) + case "language": + return NewString(res.Language) + case "type": + return NewDocumentType(res.Type) + case "partial": + return NewBoolean(res.Partial) + case "meta": + return NewTemplateMeta(res.Meta) + case "template": + return NewString(res.Template) + case "labels": + return NewKV(res.Labels) + case "ownerID": + return NewID(res.OwnerID) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "deletedAt": + return NewDateTime(res.DeletedAt) + case "lastUsedAt": + return NewDateTime(res.LastUsedAt) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToTemplate is field value setter for *types.Template +func assignToTemplate(res *types.Template, k string, val interface{}) error { + switch k { + case "ID": + return fmt.Errorf("field '%s' is read-only", k) + case "handle": + aux, err := CastToHandle(val) + if err != nil { + return err + } + + res.Handle = aux + return nil + case "language": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Language = aux + return nil + case "type": + aux, err := CastToDocumentType(val) + if err != nil { + return err + } + + res.Type = aux + return nil + case "partial": + aux, err := CastToBoolean(val) + if err != nil { + return err + } + + res.Partial = aux + return nil + case "meta": + aux, err := CastToTemplateMeta(val) + if err != nil { + return err + } + + res.Meta = aux + return nil + case "template": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Template = aux + return nil + case "labels": + aux, err := CastToKV(val) + if err != nil { + return err + } + + res.Labels = aux + return nil + case "ownerID": + return fmt.Errorf("field '%s' is read-only", k) + 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) + case "lastUsedAt": + return fmt.Errorf("field '%s' is read-only", k) + } + + return fmt.Errorf("unknown field '%s'", k) +} + +// TemplateMeta is an expression type, wrapper for types.TemplateMeta type +type TemplateMeta struct{ value types.TemplateMeta } + +// NewTemplateMeta creates new instance of TemplateMeta expression type +func NewTemplateMeta(val interface{}) (*TemplateMeta, error) { + if c, err := CastToTemplateMeta(val); err != nil { + return nil, fmt.Errorf("unable to create TemplateMeta: %w", err) + } else { + return &TemplateMeta{value: c}, nil + } +} + +// Return underlying value on TemplateMeta +func (t TemplateMeta) Get() interface{} { return t.value } + +// Return underlying value on TemplateMeta +func (t TemplateMeta) GetValue() types.TemplateMeta { return t.value } + +// Return type name +func (TemplateMeta) Type() string { return "TemplateMeta" } + +// Convert value to types.TemplateMeta +func (TemplateMeta) Cast(val interface{}) (TypedValue, error) { + return NewTemplateMeta(val) +} + +// Assign new value to TemplateMeta +// +// value is first passed through CastToTemplateMeta +func (t *TemplateMeta) Assign(val interface{}) error { + if c, err := CastToTemplateMeta(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *TemplateMeta) AssignFieldValue(key string, val interface{}) error { + return assignToTemplateMeta(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access TemplateMeta's underlying value (types.TemplateMeta) +// and it's fields +// +func (t TemplateMeta) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return templateMetaGValSelector(t.value, k) +} + +// Select is field accessor for types.TemplateMeta +// +// Similar to SelectGVal but returns typed values +func (t TemplateMeta) Select(k string) (TypedValue, error) { + return templateMetaTypedValueSelector(t.value, k) +} + +func (t TemplateMeta) Has(k string) bool { + switch k { + case "short": + return true + case "description": + return true + } + return false +} + +// templateMetaGValSelector is field accessor for types.TemplateMeta +func templateMetaGValSelector(res types.TemplateMeta, k string) (interface{}, error) { + switch k { + case "short": + return res.Short, nil + case "description": + return res.Description, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// templateMetaTypedValueSelector is field accessor for types.TemplateMeta +func templateMetaTypedValueSelector(res types.TemplateMeta, k string) (TypedValue, error) { + switch k { + case "short": + return NewString(res.Short) + case "description": + return NewString(res.Description) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToTemplateMeta is field value setter for types.TemplateMeta +func assignToTemplateMeta(res types.TemplateMeta, k string, val interface{}) error { + switch k { + case "short": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Short = aux + return nil + case "description": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Description = aux + return nil + } + + return fmt.Errorf("unknown field '%s'", k) +} + // User is an expression type, wrapper for *types.User type type User struct{ value *types.User } diff --git a/system/automation/expr_types.go b/system/automation/expr_types.go index 392475d68..95cbd0ef9 100644 --- a/system/automation/expr_types.go +++ b/system/automation/expr_types.go @@ -2,8 +2,19 @@ package automation import ( "fmt" + "io" + "github.com/cortezaproject/corteza-server/pkg/expr" "github.com/cortezaproject/corteza-server/system/types" + "github.com/spf13/cast" +) + +type ( + RenderedDocument struct { + Document io.Reader + Name string + Type string + } ) func CastToUser(val interface{}) (out *types.User, err error) { @@ -39,3 +50,97 @@ func CastToRole(val interface{}) (out *types.Role, err error) { return nil, fmt.Errorf("unable to cast type %T to %T", val, out) } } + +func CastToTemplate(val interface{}) (out *types.Template, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.Template{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToTemplate(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Template: + return val, nil + case nil: + return &types.Template{}, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToTemplateMeta(val interface{}) (out types.TemplateMeta, err error) { + switch val := val.(type) { + case expr.Iterator: + out = types.TemplateMeta{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToTemplateMeta(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case types.TemplateMeta: + return val, nil + default: + return types.TemplateMeta{}, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToDocument(val interface{}) (out *RenderedDocument, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &RenderedDocument{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToDocument(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *RenderedDocument: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToDocumentType(val interface{}) (out types.DocumentType, err error) { + switch val := val.(type) { + case string: + return types.DocumentType(val), nil + case *expr.String: + return types.DocumentType(val.GetValue()), nil + default: + return "", fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToRenderOptions(val interface{}) (out map[string]string, err error) { + switch val := expr.UntypedValue(val).(type) { + case map[string]string: + return val, nil + case nil: + return make(map[string]string), nil + default: + out, err = cast.ToStringMapStringE(val) + if err != nil { + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } + return out, nil + } +} + +func CastToRenderVariables(val interface{}) (out map[string]interface{}, err error) { + switch val := expr.UntypedValue(val).(type) { + case map[string]interface{}: + return val, nil + case nil: + return make(map[string]interface{}), nil + default: + out, err = cast.ToStringMapE(val) + if err != nil { + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } + return out, nil + } +} diff --git a/system/automation/expr_types.yaml b/system/automation/expr_types.yaml index 31b8aa3f2..ba02b60d3 100644 --- a/system/automation/expr_types.yaml +++ b/system/automation/expr_types.yaml @@ -3,6 +3,25 @@ imports: - github.com/cortezaproject/corteza-server/system/types types: + Template: + as: '*types.Template' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro} + - { name: 'handle', exprType: 'Handle', goType: 'string' } + - { name: 'language', exprType: 'String', goType: 'string' } + + - { name: 'type', exprType: 'DocumentType', goType: 'types.DocumentType' } + - { name: 'partial', exprType: 'Boolean', goType: 'bool' } + + - { name: 'meta', exprType: 'TemplateMeta', goType: 'types.TemplateMeta' } + - { name: 'template', exprType: 'String', goType: 'string' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } + + - { name: 'ownerID', exprType: 'ID', goType: 'uint64', mode: ro} + - { 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 } + - { name: 'lastUsedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } User: as: '*types.User' struct: @@ -23,8 +42,26 @@ types: - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } - { name: 'name', exprType: 'String', goType: 'string' } - { name: 'handle', exprType: 'Handle', goType: 'string' } - - { name: 'labels', exprType: 'KV', goType: 'map[string]''string' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro} - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } - { name: 'archivedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + + DocumentType: + as: 'types.DocumentType' + TemplateMeta: + as: 'types.TemplateMeta' + struct: + - { name: 'short', exprType: 'String', goType: 'string'} + - { name: 'description', exprType: 'String', goType: 'string'} + Document: + as: '*RenderedDocument' + struct: + - { name: 'document', exprType: 'Reader', goType: 'io.Reader' } + - { name: 'name', exprType: 'string', goType: 'string' } + - { name: 'type', exprType: 'string', goType: 'string' } + RenderVariables: + as: 'map[string]interface{}' + RenderOptions: + as: 'map[string]string' diff --git a/system/automation/templates_handler.gen.go b/system/automation/templates_handler.gen.go new file mode 100644 index 000000000..272ec408e --- /dev/null +++ b/system/automation/templates_handler.gen.go @@ -0,0 +1,879 @@ +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: +// system/automation/templates_handler.yaml + +import ( + "context" + atypes "github.com/cortezaproject/corteza-server/automation/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" +) + +var _ wfexec.ExecResponse + +type ( + templatesHandlerRegistry interface { + AddFunctions(ff ...*atypes.Function) + Type(ref string) expr.Type + } +) + +func (h templatesHandler) register() { + h.reg.AddFunctions( + h.Lookup(), + h.Search(), + h.Each(), + h.Create(), + h.Update(), + h.Delete(), + h.Recover(), + h.Render(), + ) +} + +type ( + templatesLookupArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Template + } + + templatesLookupResults struct { + Template *types.Template + } +) + +func (a templatesLookupArgs) GetLookup() (bool, uint64, string, *types.Template) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Lookup function Looks-up for template by ID +// +// expects implementation of lookup function: +// func (h templatesHandler) lookup(ctx context.Context, args *templatesLookupArgs) (results *templatesLookupResults, err error) { +// return +// } +func (h templatesHandler) Lookup() *atypes.Function { + return &atypes.Function{ + Ref: "templatesLookup", + Kind: "function", + Labels: map[string]string{"templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Looks-up for template by ID", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Template"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "template", + Types: []string{"Template"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesLookupArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Template").Type(): + args.lookupRes = aux.Get().(*types.Template) + } + } + + var results *templatesLookupResults + if results, err = h.lookup(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Template (*types.Template) to Template + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Template").Cast(results.Template); err != nil { + return + } else if err = expr.Assign(out, "template", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + templatesSearchArgs struct { + hasHandle bool + Handle string + + hasType bool + Type string + + hasOwnerID bool + OwnerID uint64 + + hasPartial bool + Partial bool + + hasLabels bool + Labels map[string]string + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + templatesSearchResults struct { + Templates []*types.Template + Total uint64 + PageCursor string + } +) + +// Search function Searches for templates and returns them +// +// expects implementation of search function: +// func (h templatesHandler) search(ctx context.Context, args *templatesSearchArgs) (results *templatesSearchResults, err error) { +// return +// } +func (h templatesHandler) Search() *atypes.Function { + return &atypes.Function{ + Ref: "templatesSearch", + Kind: "function", + Labels: map[string]string{"templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Searches for templates and returns them", + }, + + Parameters: []*atypes.Param{ + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "type", + Types: []string{"String"}, + }, + { + Name: "ownerID", + Types: []string{"ID"}, + }, + { + Name: "partial", + Types: []string{"Boolean"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "templates", + Types: []string{"Template"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesSearchArgs{ + hasHandle: in.Has("handle"), + hasType: in.Has("type"), + hasOwnerID: in.Has("ownerID"), + hasPartial: in.Has("partial"), + hasLabels: in.Has("labels"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *templatesSearchResults + if results, err = h.search(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Templates (*types.Template) to Array (of Template) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Templates)) + ) + + for i := range results.Templates { + if tarr[i], err = h.reg.Type("Template").Cast(results.Templates[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "templates", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + { + // converting results.PageCursor (string) to String + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("String").Cast(results.PageCursor); err != nil { + return + } else if err = expr.Assign(out, "pageCursor", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + templatesEachArgs struct { + hasHandle bool + Handle string + + hasType bool + Type string + + hasOwnerID bool + OwnerID uint64 + + hasPartial bool + Partial bool + + hasLabels bool + Labels map[string]string + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + templatesEachResults struct { + Template *types.Template + Total uint64 + } +) + +// Each function Searches for templates and iterates over results +// +// expects implementation of each function: +// func (h templatesHandler) each(ctx context.Context, args *templatesEachArgs) (results *templatesEachResults, err error) { +// return +// } +func (h templatesHandler) Each() *atypes.Function { + return &atypes.Function{ + Ref: "templatesEach", + Kind: "iterator", + Labels: map[string]string{"templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Searches for templates and iterates over results", + }, + + Parameters: []*atypes.Param{ + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "type", + Types: []string{"String"}, + }, + { + Name: "ownerID", + Types: []string{"ID"}, + }, + { + Name: "partial", + Types: []string{"Boolean"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "template", + Types: []string{"Template"}, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) { + var ( + args = &templatesEachArgs{ + hasHandle: in.Has("handle"), + hasType: in.Has("type"), + hasOwnerID: in.Has("ownerID"), + hasPartial: in.Has("partial"), + hasLabels: in.Has("labels"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return h.each(ctx, args) + }, + } +} + +type ( + templatesCreateArgs struct { + hasTemplate bool + Template *types.Template + } + + templatesCreateResults struct { + Template *types.Template + } +) + +// Create function Creates new template +// +// expects implementation of create function: +// func (h templatesHandler) create(ctx context.Context, args *templatesCreateArgs) (results *templatesCreateResults, err error) { +// return +// } +func (h templatesHandler) Create() *atypes.Function { + return &atypes.Function{ + Ref: "templatesCreate", + Kind: "function", + Labels: map[string]string{"templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Creates new template", + }, + + Parameters: []*atypes.Param{ + { + Name: "template", + Types: []string{"Template"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "template", + Types: []string{"Template"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesCreateArgs{ + hasTemplate: in.Has("template"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *templatesCreateResults + if results, err = h.create(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Template (*types.Template) to Template + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Template").Cast(results.Template); err != nil { + return + } else if err = expr.Assign(out, "template", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + templatesUpdateArgs struct { + hasTemplate bool + Template *types.Template + } + + templatesUpdateResults struct { + Template *types.Template + } +) + +// Update function Updates exiting template +// +// expects implementation of update function: +// func (h templatesHandler) update(ctx context.Context, args *templatesUpdateArgs) (results *templatesUpdateResults, err error) { +// return +// } +func (h templatesHandler) Update() *atypes.Function { + return &atypes.Function{ + Ref: "templatesUpdate", + Kind: "function", + Labels: map[string]string{"templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Updates exiting template", + }, + + Parameters: []*atypes.Param{ + { + Name: "template", + Types: []string{"Template"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "template", + Types: []string{"Template"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesUpdateArgs{ + hasTemplate: in.Has("template"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *templatesUpdateResults + if results, err = h.update(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Template (*types.Template) to Template + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Template").Cast(results.Template); err != nil { + return + } else if err = expr.Assign(out, "template", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + templatesDeleteArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Template + } +) + +func (a templatesDeleteArgs) GetLookup() (bool, uint64, string, *types.Template) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Delete function Deletes template +// +// expects implementation of delete function: +// func (h templatesHandler) delete(ctx context.Context, args *templatesDeleteArgs) (err error) { +// return +// } +func (h templatesHandler) Delete() *atypes.Function { + return &atypes.Function{ + Ref: "templatesDelete", + Kind: "function", + Labels: map[string]string{"delete": "step", "templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Deletes template", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Template"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesDeleteArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Template").Type(): + args.lookupRes = aux.Get().(*types.Template) + } + } + + return out, h.delete(ctx, args) + }, + } +} + +type ( + templatesRecoverArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Template + } +) + +func (a templatesRecoverArgs) GetLookup() (bool, uint64, string, *types.Template) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Recover function Recovers deleted template +// +// expects implementation of recover function: +// func (h templatesHandler) recover(ctx context.Context, args *templatesRecoverArgs) (err error) { +// return +// } +func (h templatesHandler) Recover() *atypes.Function { + return &atypes.Function{ + Ref: "templatesRecover", + Kind: "function", + Labels: map[string]string{"recover": "step", "templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Recovers deleted template", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Template"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesRecoverArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Template").Type(): + args.lookupRes = aux.Get().(*types.Template) + } + } + + return out, h.recover(ctx, args) + }, + } +} + +type ( + templatesRenderArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Template + + hasDocumentName bool + DocumentName string + + hasDocumentType bool + DocumentType string + + hasVariables bool + Variables map[string]interface{} + + hasOptions bool + Options map[string]string + } + + templatesRenderResults struct { + Document *RenderedDocument + } +) + +func (a templatesRenderArgs) GetLookup() (bool, uint64, string, *types.Template) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Render function Renders a document from template +// +// expects implementation of render function: +// func (h templatesHandler) render(ctx context.Context, args *templatesRenderArgs) (results *templatesRenderResults, err error) { +// return +// } +func (h templatesHandler) Render() *atypes.Function { + return &atypes.Function{ + Ref: "templatesRender", + Kind: "function", + Labels: map[string]string{"render": "step", "templates": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Renders a document from template", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Template"}, Required: true, + }, + { + Name: "documentName", + Types: []string{"String"}, + }, + { + Name: "documentType", + Types: []string{"String"}, + }, + { + Name: "variables", + Types: []string{"RenderVariables"}, + }, + { + Name: "options", + Types: []string{"RenderOptions"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "document", + Types: []string{"Document"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &templatesRenderArgs{ + hasLookup: in.Has("lookup"), + hasDocumentName: in.Has("documentName"), + hasDocumentType: in.Has("documentType"), + hasVariables: in.Has("variables"), + hasOptions: in.Has("options"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Template").Type(): + args.lookupRes = aux.Get().(*types.Template) + } + } + + var results *templatesRenderResults + if results, err = h.render(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Document (*RenderedDocument) to Document + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Document").Cast(results.Document); err != nil { + return + } else if err = expr.Assign(out, "document", tval); err != nil { + return + } + } + + return + }, + } +} diff --git a/system/automation/templates_handler.go b/system/automation/templates_handler.go new file mode 100644 index 000000000..b1c7f6bbf --- /dev/null +++ b/system/automation/templates_handler.go @@ -0,0 +1,247 @@ +package automation + +import ( + "context" + "fmt" + "io" + + . "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/spf13/cast" +) + +type ( + templateService interface { + FindByID(ctx context.Context, ID uint64) (*types.Template, error) + FindByHandle(ct context.Context, handle string) (*types.Template, error) + FindByAny(ctx context.Context, identifier interface{}) (*types.Template, error) + Search(context.Context, types.TemplateFilter) (types.TemplateSet, types.TemplateFilter, error) + + Create(ctx context.Context, tpl *types.Template) (*types.Template, error) + Update(ctx context.Context, tpl *types.Template) (*types.Template, error) + + DeleteByID(ctx context.Context, ID uint64) error + UndeleteByID(ctx context.Context, ID uint64) error + + Render(ctx context.Context, templateID uint64, dstType string, variables map[string]interface{}, options map[string]string) (io.ReadSeeker, error) + } + + templatesHandler struct { + reg templatesHandlerRegistry + tSvc templateService + } + + templateSetIterator struct { + ptr int + set types.TemplateSet + filter types.TemplateFilter + } + + templateLookup interface { + GetLookup() (bool, uint64, string, *types.Template) + } +) + +func TemplatesHandler(reg templatesHandlerRegistry, tSvc templateService) *templatesHandler { + h := &templatesHandler{ + reg: reg, + tSvc: tSvc, + } + + h.register() + return h +} + +func (h templatesHandler) lookup(ctx context.Context, args *templatesLookupArgs) (results *templatesLookupResults, err error) { + results = &templatesLookupResults{} + results.Template, err = lookupTemplate(ctx, h.tSvc, args) + return +} + +func (h templatesHandler) search(ctx context.Context, args *templatesSearchArgs) (results *templatesSearchResults, err error) { + results = &templatesSearchResults{} + + var ( + f = types.TemplateFilter{ + Handle: args.Handle, + Type: args.Type, + OwnerID: args.OwnerID, + Partial: args.Partial, + Labels: args.Labels, + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + results.Templates, _, err = h.tSvc.Search(ctx, f) + return +} + +func (h templatesHandler) each(ctx context.Context, args *templatesEachArgs) (out wfexec.IteratorHandler, err error) { + var ( + i = &templateSetIterator{} + f = types.TemplateFilter{ + Handle: args.Handle, + Type: args.Type, + OwnerID: args.OwnerID, + Partial: args.Partial, + Labels: args.Labels, + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + i.set, i.filter, err = h.tSvc.Search(ctx, f) + return i, err +} + +func (h templatesHandler) create(ctx context.Context, args *templatesCreateArgs) (results *templatesCreateResults, err error) { + results = &templatesCreateResults{} + results.Template, err = h.tSvc.Create(ctx, args.Template) + return +} + +func (h templatesHandler) update(ctx context.Context, args *templatesUpdateArgs) (results *templatesUpdateResults, err error) { + results = &templatesUpdateResults{} + results.Template, err = h.tSvc.Update(ctx, args.Template) + return +} + +func (h templatesHandler) delete(ctx context.Context, args *templatesDeleteArgs) error { + if id, err := getTemplateID(ctx, h.tSvc, args); err != nil { + return err + } else { + return h.tSvc.DeleteByID(ctx, id) + } +} + +func (h templatesHandler) recover(ctx context.Context, args *templatesRecoverArgs) error { + if id, err := getTemplateID(ctx, h.tSvc, args); err != nil { + return err + } else { + return h.tSvc.UndeleteByID(ctx, id) + } +} + +func (h templatesHandler) render(ctx context.Context, args *templatesRenderArgs) (*templatesRenderResults, error) { + var err error + + vars := make(map[string]interface{}) + if args.hasVariables { + vars, err = cast.ToStringMapE(args.Variables) + if err != nil { + return nil, err + } + } + + opts := make(map[string]string) + if args.hasOptions { + opts, err = cast.ToStringMapStringE(args.Options) + if err != nil { + return nil, err + } + } + + tplID, err := getTemplateID(ctx, h.tSvc, args) + if err != nil { + return nil, err + } + + doc, err := h.tSvc.Render(ctx, tplID, args.DocumentType, vars, opts) + if err != nil { + return nil, err + } + + rr := &templatesRenderResults{ + Document: &RenderedDocument{ + Document: doc, + Name: args.DocumentName, + Type: args.DocumentType, + }, + } + + return rr, nil +} + +func (i *templateSetIterator) More(context.Context, *Vars) (bool, error) { + return i.ptr < len(i.set), nil +} + +func (i *templateSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil } + +func (i *templateSetIterator) Next(context.Context, *Vars) (*Vars, error) { + out := RVars{ + "template": Must(NewTemplate(i.set[i.ptr])), + "total": Must(NewUnsignedInteger(i.filter.Total)), + } + + i.ptr++ + return out.Vars(), nil +} + +func lookupTemplate(ctx context.Context, svc templateService, args templateLookup) (*types.Template, error) { + _, ID, handle, template := args.GetLookup() + + switch { + case template != nil: + return template, nil + case ID > 0: + return svc.FindByID(ctx, ID) + case len(handle) > 0: + return svc.FindByHandle(ctx, handle) + } + + return nil, fmt.Errorf("empty lookup params") +} + +func getTemplateID(ctx context.Context, svc templateService, args templateLookup) (uint64, error) { + _, ID, _, _ := args.GetLookup() + + if ID > 0 { + return ID, nil + } + + tpl, err := lookupTemplate(ctx, svc, args) + if err != nil { + return 0, err + } + + return tpl.ID, nil +} diff --git a/system/automation/templates_handler.yaml b/system/automation/templates_handler.yaml new file mode 100644 index 000000000..c60bbe5bb --- /dev/null +++ b/system/automation/templates_handler.yaml @@ -0,0 +1,161 @@ +imports: + - github.com/cortezaproject/corteza-server/system/types + +snippets: + lookup: &lookup + required: true + types: + - { wf: ID } + - { wf: Handle } + - { wf: Template, suffix: res } + + template: &template + types: + - { wf: Template } + + rvTemplate: &rvTemplate + wf: Template + + rvDocument: &rvDocument + wf: Document + + rvTotal: &rvTotal + wf: UnsignedInteger + + rvPageCursor: &rvPageCursor + wf: String + + filterParams: &filterParams + handle: + types: + - { wf: String } + type: + types: + - { wf: String } + ownerID: + types: + - { wf: ID } + partial: + types: + - { wf: Boolean } + labels: + types: + - { wf: KV } + sort: + types: + - { wf: String } + limit: + types: + - { wf: UnsignedInteger } + incTotal: + types: + - { wf: Boolean } + incPageNavigation: + types: + - { wf: Boolean } + pageCursor: + types: + - { wf: String } + +labels: &labels + templates: "step,workflow" + +functions: + lookup: + meta: + short: Looks-up for template by ID + params: + lookup: *lookup + labels: + <<: *labels + results: + template: *rvTemplate + + search: + meta: + short: Searches for templates and returns them + params: *filterParams + labels: + <<: *labels + results: + templates: + <<: *rvTemplate + isArray: true + total: *rvTotal + pageCursor: *rvPageCursor + + each: + kind: iterator + meta: + short: Searches for templates and iterates over results + params: *filterParams + labels: + <<: *labels + results: + template: *rvTemplate + total: *rvTotal + + create: + meta: + short: Creates new template + labels: + <<: *labels + params: + template: + <<: *template + required: true + results: + template: *rvTemplate + + update: + meta: + short: Updates exiting template + labels: + <<: *labels + params: + template: + <<: *template + required: true + results: + template: *rvTemplate + + delete: + labels: + <<: *labels + delete: "step" + meta: + short: Deletes template + params: + lookup: *lookup + + recover: + meta: + short: Recovers deleted template + labels: + <<: *labels + recover: "step" + params: + lookup: *lookup + + render: + meta: + short: Renders a document from template + labels: + <<: *labels + render: "step" + params: + lookup: *lookup + documentName: + types: + - { wf: String } + documentType: + types: + - { wf: String } + variables: + types: + - { wf: RenderVariables } + options: + types: + - { wf: RenderOptions } + results: + document: *rvDocument diff --git a/system/service/service.go b/system/service/service.go index 8a7b128ae..6dfc891a8 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -172,6 +172,10 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) automationService.Registry().AddTypes( automation.User{}, automation.Role{}, + automation.Template{}, + automation.RenderVariables{}, + automation.RenderOptions{}, + automation.Document{}, ) automation.UsersHandler( @@ -179,6 +183,11 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) DefaultUser, ) + automation.TemplatesHandler( + automationService.Registry(), + DefaultRenderer, + ) + automation.RolesHandler( automationService.Registry(), DefaultRole,