snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
86
server/compose/attachment.cue
Normal file
86
server/compose/attachment.cue
Normal file
@@ -0,0 +1,86 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
attachment: {
|
||||
features: {
|
||||
labels: false
|
||||
}
|
||||
|
||||
model: {
|
||||
ident: "compose_attachment"
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
}
|
||||
owner_id: {
|
||||
sortable: true,
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_owner",
|
||||
ident: "ownerID"
|
||||
dal: { type: "Ref", refModelResType: "corteza::system:user" }
|
||||
}
|
||||
kind: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
url: {
|
||||
dal: {}
|
||||
}
|
||||
preview_url: {
|
||||
dal: {}
|
||||
}
|
||||
name: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
meta: {
|
||||
goType: "types.AttachmentMeta"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"namespace": { attribute: "namespace_id" },
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
kind: {}
|
||||
namespace_id: { goType: "uint64", ident: "namespaceID" }
|
||||
page_id: { goType: "uint64", ident: "pageID" }
|
||||
record_id: { goType: "uint64", ident: "recordID" }
|
||||
module_id: { goType: "uint64", ident: "moduleID" }
|
||||
field_name: { }
|
||||
}
|
||||
|
||||
byValue: ["kind", "namespace_id"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
omit: true
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composeAttachment"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{ fields: ["id"] },
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
498
server/compose/automation/attachment_handler.gen.go
generated
Normal file
498
server/compose/automation/attachment_handler.gen.go
generated
Normal file
@@ -0,0 +1,498 @@
|
||||
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 Attachment 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: "Attachment lookup",
|
||||
Description: "Find specific attachment by ID",
|
||||
},
|
||||
|
||||
Parameters: []*atypes.Param{
|
||||
{
|
||||
Name: "attachment",
|
||||
Types: []string{"ID"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Attachment",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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
|
||||
|
||||
hasFieldName bool
|
||||
FieldName string
|
||||
|
||||
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"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Name",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "resource",
|
||||
Types: []string{"ComposeRecord"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Resource",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "fieldName",
|
||||
Types: []string{"String"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Field name",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "content",
|
||||
Types: []string{"String", "Reader", "Bytes"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Content",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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"),
|
||||
hasFieldName: in.Has("fieldName"),
|
||||
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,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Attachment",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Attachment",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Attachment",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
227
server/compose/automation/attachment_handler.go
Normal file
227
server/compose/automation/attachment_handler.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"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, fieldName string) (att *types.Attachment, err error)
|
||||
DeleteByID(ctx context.Context, namespaceID uint64, attachmentID uint64) error
|
||||
OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error)
|
||||
OpenPreview(att *types.Attachment) (io.ReadSeekCloser, 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
|
||||
}
|
||||
|
||||
// @todo we need to call Close() when file is read (or at the end of the workflow)
|
||||
// some kind of workflow-cleanup facility is needed
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// @todo we need to call Close() when file is read (or at the end of the workflow)
|
||||
// some kind of workflow-cleanup facility is needed
|
||||
|
||||
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.contentBytes))
|
||||
fh = bytes.NewReader(args.contentBytes)
|
||||
|
||||
case args.contentStream != nil:
|
||||
if rs, is := args.contentStream.(io.ReadSeeker); is {
|
||||
_, err = rs.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size, err = getReaderSize(rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rs.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fh = rs
|
||||
} else {
|
||||
// In case we only got a reader...
|
||||
//
|
||||
// For future proofing, for handling larger attachment, we create a temp.
|
||||
// file which we then use as a reader
|
||||
|
||||
// Preparations
|
||||
tmpf, err := ioutil.TempFile("", "reader")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tmpf.Close()
|
||||
defer os.Remove(tmpf.Name())
|
||||
|
||||
// Writing content to file
|
||||
w := bufio.NewWriter(tmpf)
|
||||
r := args.contentStream
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
// read
|
||||
n, err := r.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// on-the-fly size calculation
|
||||
size += int64(n)
|
||||
|
||||
// write
|
||||
if _, err := w.Write(buf[:n]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = tmpf.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fh = tmpf
|
||||
}
|
||||
|
||||
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,
|
||||
args.FieldName,
|
||||
)
|
||||
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")
|
||||
}
|
||||
|
||||
func getReaderSize(r io.Reader) (size int64, err error) {
|
||||
buf := make([]byte, 1024)
|
||||
var n int
|
||||
for {
|
||||
// read a chunk
|
||||
n, err = r.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
size += int64(n)
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
99
server/compose/automation/attachment_handler.yaml
Normal file
99
server/compose/automation/attachment_handler.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
# 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: Attachment lookup
|
||||
description: Find specific attachment by ID
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
attachment:
|
||||
meta: { label: 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:
|
||||
meta: { label: Name }
|
||||
types:
|
||||
- { wf: String }
|
||||
|
||||
resource:
|
||||
meta: { label: Resource }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ComposeRecord }
|
||||
|
||||
fieldName:
|
||||
meta: { label: Field name }
|
||||
types:
|
||||
- { wf: String }
|
||||
|
||||
content:
|
||||
meta: { label: 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:
|
||||
meta: { label: Attachment }
|
||||
required: true
|
||||
types: [ { wf: ID } ]
|
||||
|
||||
openOriginal:
|
||||
meta:
|
||||
short: Open original attachment
|
||||
labels:
|
||||
<<: *labels
|
||||
original-attachment: "step"
|
||||
params:
|
||||
attachment:
|
||||
meta: { label: 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:
|
||||
meta: { label: Attachment }
|
||||
required: true
|
||||
types: [ { wf: ID }, { wf: Attachment } ]
|
||||
results:
|
||||
content: { wf: Reader }
|
||||
858
server/compose/automation/expr_types.gen.go
generated
Normal file
858
server/compose/automation/expr_types.gen.go
generated
Normal file
@@ -0,0 +1,858 @@
|
||||
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/expr_types.yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
. "github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"sync"
|
||||
)
|
||||
|
||||
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
|
||||
//
|
||||
// Value types are passed by pointer, otherwise assigned values are lost
|
||||
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
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// NewComposeModule creates new instance of ComposeModule expression type
|
||||
func NewComposeModule(val interface{}) (*ComposeModule, error) {
|
||||
if c, err := CastToComposeModule(val); err != nil {
|
||||
return nil, fmt.Errorf("unable to create ComposeModule: %w", err)
|
||||
} else {
|
||||
return &ComposeModule{value: c}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get return underlying value on ComposeModule
|
||||
func (t *ComposeModule) Get() interface{} {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// GetValue returns underlying value on ComposeModule
|
||||
func (t *ComposeModule) GetValue() *types.Module {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// Type return type name
|
||||
func (ComposeModule) Type() string { return "ComposeModule" }
|
||||
|
||||
// Cast converts value to *types.Module
|
||||
func (ComposeModule) Cast(val interface{}) (TypedValue, error) {
|
||||
return NewComposeModule(val)
|
||||
}
|
||||
|
||||
// Assign new value to ComposeModule
|
||||
//
|
||||
// value is first passed through CastToComposeModule
|
||||
func (t *ComposeModule) Assign(val interface{}) error {
|
||||
if c, err := CastToComposeModule(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.value = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ComposeModule) AssignFieldValue(key string, val TypedValue) error {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
return assignToComposeModule(t.value, key, val)
|
||||
}
|
||||
|
||||
// SelectGVal implements gval.Selector requirements
|
||||
//
|
||||
// It allows gval lib to access ComposeModule's underlying value (*types.Module)
|
||||
// and it's fields
|
||||
func (t *ComposeModule) SelectGVal(ctx context.Context, k string) (interface{}, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return composeModuleGValSelector(t.value, k)
|
||||
}
|
||||
|
||||
// Select is field accessor for *types.Module
|
||||
//
|
||||
// Similar to SelectGVal but returns typed values
|
||||
func (t *ComposeModule) Select(k string) (TypedValue, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return composeModuleTypedValueSelector(t.value, k)
|
||||
}
|
||||
|
||||
func (t *ComposeModule) Has(k string) bool {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return true
|
||||
case "namespaceID":
|
||||
return true
|
||||
case "name":
|
||||
return true
|
||||
case "handle":
|
||||
return true
|
||||
case "labels":
|
||||
return true
|
||||
case "createdAt":
|
||||
return true
|
||||
case "updatedAt":
|
||||
return true
|
||||
case "deletedAt":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// composeModuleGValSelector is field accessor for *types.Module
|
||||
func composeModuleGValSelector(res *types.Module, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return res.ID, nil
|
||||
case "namespaceID":
|
||||
return res.NamespaceID, nil
|
||||
case "name":
|
||||
return res.Name, nil
|
||||
case "handle":
|
||||
return res.Handle, nil
|
||||
case "labels":
|
||||
return res.Labels, 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)
|
||||
}
|
||||
|
||||
// composeModuleTypedValueSelector is field accessor for *types.Module
|
||||
func composeModuleTypedValueSelector(res *types.Module, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return NewID(res.ID)
|
||||
case "namespaceID":
|
||||
return NewID(res.NamespaceID)
|
||||
case "name":
|
||||
return NewString(res.Name)
|
||||
case "handle":
|
||||
return NewHandle(res.Handle)
|
||||
case "labels":
|
||||
return NewLabelValue(res.Labels)
|
||||
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)
|
||||
}
|
||||
|
||||
// assignToComposeModule is field value setter for *types.Module
|
||||
//
|
||||
// Value types are passed by pointer, otherwise assigned values are lost
|
||||
func assignToComposeModule(res *types.Module, k string, val interface{}) error {
|
||||
switch k {
|
||||
case "ID", "moduleID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "namespaceID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "name":
|
||||
aux, err := CastToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Name = aux
|
||||
return nil
|
||||
case "handle":
|
||||
aux, err := CastToHandle(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Handle = aux
|
||||
return nil
|
||||
case "labels":
|
||||
aux, err := CastToLabelValue(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Labels = 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)
|
||||
}
|
||||
|
||||
// ComposeNamespace is an expression type, wrapper for *types.Namespace type
|
||||
type ComposeNamespace struct {
|
||||
value *types.Namespace
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// NewComposeNamespace creates new instance of ComposeNamespace expression type
|
||||
func NewComposeNamespace(val interface{}) (*ComposeNamespace, error) {
|
||||
if c, err := CastToComposeNamespace(val); err != nil {
|
||||
return nil, fmt.Errorf("unable to create ComposeNamespace: %w", err)
|
||||
} else {
|
||||
return &ComposeNamespace{value: c}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get return underlying value on ComposeNamespace
|
||||
func (t *ComposeNamespace) Get() interface{} {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// GetValue returns underlying value on ComposeNamespace
|
||||
func (t *ComposeNamespace) GetValue() *types.Namespace {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// Type return type name
|
||||
func (ComposeNamespace) Type() string { return "ComposeNamespace" }
|
||||
|
||||
// Cast converts value to *types.Namespace
|
||||
func (ComposeNamespace) Cast(val interface{}) (TypedValue, error) {
|
||||
return NewComposeNamespace(val)
|
||||
}
|
||||
|
||||
// Assign new value to ComposeNamespace
|
||||
//
|
||||
// value is first passed through CastToComposeNamespace
|
||||
func (t *ComposeNamespace) Assign(val interface{}) error {
|
||||
if c, err := CastToComposeNamespace(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.value = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ComposeNamespace) AssignFieldValue(key string, val TypedValue) error {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
return assignToComposeNamespace(t.value, key, val)
|
||||
}
|
||||
|
||||
// SelectGVal implements gval.Selector requirements
|
||||
//
|
||||
// It allows gval lib to access ComposeNamespace's underlying value (*types.Namespace)
|
||||
// and it's fields
|
||||
func (t *ComposeNamespace) SelectGVal(ctx context.Context, k string) (interface{}, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return composeNamespaceGValSelector(t.value, k)
|
||||
}
|
||||
|
||||
// Select is field accessor for *types.Namespace
|
||||
//
|
||||
// Similar to SelectGVal but returns typed values
|
||||
func (t *ComposeNamespace) Select(k string) (TypedValue, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return composeNamespaceTypedValueSelector(t.value, k)
|
||||
}
|
||||
|
||||
func (t *ComposeNamespace) Has(k string) bool {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return true
|
||||
case "name":
|
||||
return true
|
||||
case "slug", "handle":
|
||||
return true
|
||||
case "labels":
|
||||
return true
|
||||
case "createdAt":
|
||||
return true
|
||||
case "updatedAt":
|
||||
return true
|
||||
case "deletedAt":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// composeNamespaceGValSelector is field accessor for *types.Namespace
|
||||
func composeNamespaceGValSelector(res *types.Namespace, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return res.ID, nil
|
||||
case "name":
|
||||
return res.Name, nil
|
||||
case "slug", "handle":
|
||||
return res.Slug, nil
|
||||
case "labels":
|
||||
return res.Labels, 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)
|
||||
}
|
||||
|
||||
// composeNamespaceTypedValueSelector is field accessor for *types.Namespace
|
||||
func composeNamespaceTypedValueSelector(res *types.Namespace, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return NewID(res.ID)
|
||||
case "name":
|
||||
return NewString(res.Name)
|
||||
case "slug", "handle":
|
||||
return NewHandle(res.Slug)
|
||||
case "labels":
|
||||
return NewLabelValue(res.Labels)
|
||||
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)
|
||||
}
|
||||
|
||||
// assignToComposeNamespace is field value setter for *types.Namespace
|
||||
//
|
||||
// Value types are passed by pointer, otherwise assigned values are lost
|
||||
func assignToComposeNamespace(res *types.Namespace, k string, val interface{}) error {
|
||||
switch k {
|
||||
case "ID", "namespaceID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "name":
|
||||
aux, err := CastToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Name = aux
|
||||
return nil
|
||||
case "slug", "handle":
|
||||
aux, err := CastToHandle(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Slug = aux
|
||||
return nil
|
||||
case "labels":
|
||||
aux, err := CastToLabelValue(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Labels = 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)
|
||||
}
|
||||
|
||||
// ComposeRecord is an expression type, wrapper for *types.Record type
|
||||
type ComposeRecord struct {
|
||||
value *types.Record
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// NewComposeRecord creates new instance of ComposeRecord expression type
|
||||
func NewComposeRecord(val interface{}) (*ComposeRecord, error) {
|
||||
if c, err := CastToComposeRecord(val); err != nil {
|
||||
return nil, fmt.Errorf("unable to create ComposeRecord: %w", err)
|
||||
} else {
|
||||
return &ComposeRecord{value: c}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get return underlying value on ComposeRecord
|
||||
func (t *ComposeRecord) Get() interface{} {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// GetValue returns underlying value on ComposeRecord
|
||||
func (t *ComposeRecord) GetValue() *types.Record {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// Type return type name
|
||||
func (ComposeRecord) Type() string { return "ComposeRecord" }
|
||||
|
||||
// Cast converts value to *types.Record
|
||||
func (ComposeRecord) Cast(val interface{}) (TypedValue, error) {
|
||||
return NewComposeRecord(val)
|
||||
}
|
||||
|
||||
// Assign new value to ComposeRecord
|
||||
//
|
||||
// value is first passed through CastToComposeRecord
|
||||
func (t *ComposeRecord) Assign(val interface{}) error {
|
||||
if c, err := CastToComposeRecord(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.value = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ComposeRecord) Has(k string) bool {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return true
|
||||
case "moduleID":
|
||||
return true
|
||||
case "namespaceID":
|
||||
return true
|
||||
case "values":
|
||||
return true
|
||||
case "meta":
|
||||
return true
|
||||
case "ownedBy":
|
||||
return true
|
||||
case "createdAt":
|
||||
return true
|
||||
case "createdBy":
|
||||
return true
|
||||
case "updatedAt":
|
||||
return true
|
||||
case "updatedBy":
|
||||
return true
|
||||
case "deletedAt":
|
||||
return true
|
||||
case "deletedBy":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// composeRecordGValSelector is field accessor for *types.Record
|
||||
func composeRecordGValSelector(res *types.Record, k string) (interface{}, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return res.ID, nil
|
||||
case "moduleID":
|
||||
return res.ModuleID, nil
|
||||
case "namespaceID":
|
||||
return res.NamespaceID, nil
|
||||
case "values":
|
||||
return res.Values, nil
|
||||
case "meta":
|
||||
return res.Meta, nil
|
||||
case "ownedBy":
|
||||
return res.OwnedBy, nil
|
||||
case "createdAt":
|
||||
return res.CreatedAt, nil
|
||||
case "createdBy":
|
||||
return res.CreatedBy, nil
|
||||
case "updatedAt":
|
||||
return res.UpdatedAt, nil
|
||||
case "updatedBy":
|
||||
return res.UpdatedBy, nil
|
||||
case "deletedAt":
|
||||
return res.DeletedAt, nil
|
||||
case "deletedBy":
|
||||
return res.DeletedBy, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown field '%s'", k)
|
||||
}
|
||||
|
||||
// composeRecordTypedValueSelector is field accessor for *types.Record
|
||||
func composeRecordTypedValueSelector(res *types.Record, k string) (TypedValue, error) {
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return NewID(res.ID)
|
||||
case "moduleID":
|
||||
return NewID(res.ModuleID)
|
||||
case "namespaceID":
|
||||
return NewID(res.NamespaceID)
|
||||
case "values":
|
||||
return NewComposeRecordValues(res.Values)
|
||||
case "meta":
|
||||
return NewMeta(res.Meta)
|
||||
case "ownedBy":
|
||||
return NewID(res.OwnedBy)
|
||||
case "createdAt":
|
||||
return NewDateTime(res.CreatedAt)
|
||||
case "createdBy":
|
||||
return NewID(res.CreatedBy)
|
||||
case "updatedAt":
|
||||
return NewDateTime(res.UpdatedAt)
|
||||
case "updatedBy":
|
||||
return NewID(res.UpdatedBy)
|
||||
case "deletedAt":
|
||||
return NewDateTime(res.DeletedAt)
|
||||
case "deletedBy":
|
||||
return NewID(res.DeletedBy)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown field '%s'", k)
|
||||
}
|
||||
|
||||
// assignToComposeRecord is field value setter for *types.Record
|
||||
//
|
||||
// Value types are passed by pointer, otherwise assigned values are lost
|
||||
func assignToComposeRecord(res *types.Record, k string, val interface{}) error {
|
||||
switch k {
|
||||
case "ID", "recordID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "moduleID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "namespaceID":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "values":
|
||||
aux, err := CastToComposeRecordValues(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Values = aux
|
||||
return nil
|
||||
case "meta":
|
||||
aux, err := CastToMeta(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Meta = aux
|
||||
return nil
|
||||
case "ownedBy":
|
||||
aux, err := CastToID(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.OwnedBy = aux
|
||||
return nil
|
||||
case "createdAt":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "createdBy":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "updatedAt":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "updatedBy":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "deletedAt":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
case "deletedBy":
|
||||
return fmt.Errorf("field '%s' is read-only", k)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown field '%s'", k)
|
||||
}
|
||||
|
||||
// ComposeRecordValueErrorSet is an expression type, wrapper for *types.RecordValueErrorSet type
|
||||
type ComposeRecordValueErrorSet struct {
|
||||
value *types.RecordValueErrorSet
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// NewComposeRecordValueErrorSet creates new instance of ComposeRecordValueErrorSet expression type
|
||||
func NewComposeRecordValueErrorSet(val interface{}) (*ComposeRecordValueErrorSet, error) {
|
||||
if c, err := CastToComposeRecordValueErrorSet(val); err != nil {
|
||||
return nil, fmt.Errorf("unable to create ComposeRecordValueErrorSet: %w", err)
|
||||
} else {
|
||||
return &ComposeRecordValueErrorSet{value: c}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get return underlying value on ComposeRecordValueErrorSet
|
||||
func (t *ComposeRecordValueErrorSet) Get() interface{} {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// GetValue returns underlying value on ComposeRecordValueErrorSet
|
||||
func (t *ComposeRecordValueErrorSet) GetValue() *types.RecordValueErrorSet {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
return t.value
|
||||
}
|
||||
|
||||
// Type return type name
|
||||
func (ComposeRecordValueErrorSet) Type() string { return "ComposeRecordValueErrorSet" }
|
||||
|
||||
// Cast converts value to *types.RecordValueErrorSet
|
||||
func (ComposeRecordValueErrorSet) Cast(val interface{}) (TypedValue, error) {
|
||||
return NewComposeRecordValueErrorSet(val)
|
||||
}
|
||||
|
||||
// Assign new value to ComposeRecordValueErrorSet
|
||||
//
|
||||
// value is first passed through CastToComposeRecordValueErrorSet
|
||||
func (t *ComposeRecordValueErrorSet) Assign(val interface{}) error {
|
||||
if c, err := CastToComposeRecordValueErrorSet(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.value = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
817
server/compose/automation/expr_types.go
Normal file
817
server/compose/automation/expr_types.go
Normal file
@@ -0,0 +1,817 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/PaesslerAG/gval"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/slice"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func CastToComposeNamespace(val interface{}) (out *types.Namespace, err error) {
|
||||
switch val := val.(type) {
|
||||
case expr.Iterator:
|
||||
out = &types.Namespace{}
|
||||
return out, val.Each(func(k string, v expr.TypedValue) error {
|
||||
return assignToComposeNamespace(out, k, v)
|
||||
})
|
||||
}
|
||||
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case *types.Namespace:
|
||||
return val, nil
|
||||
case map[string]interface{}:
|
||||
out = &types.Namespace{}
|
||||
m, _ := json.Marshal(val)
|
||||
_ = json.Unmarshal(m, out)
|
||||
|
||||
return
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
func CastToComposeModule(val interface{}) (out *types.Module, err error) {
|
||||
switch val := val.(type) {
|
||||
case expr.Iterator:
|
||||
out = &types.Module{}
|
||||
return out, val.Each(func(k string, v expr.TypedValue) error {
|
||||
return assignToComposeModule(out, k, v)
|
||||
})
|
||||
}
|
||||
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case *types.Module:
|
||||
return val, nil
|
||||
case map[string]interface{}:
|
||||
out = &types.Module{}
|
||||
m, _ := json.Marshal(val)
|
||||
_ = json.Unmarshal(m, out)
|
||||
return
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
func CastToComposeRecord(val interface{}) (out *types.Record, err error) {
|
||||
switch val := val.(type) {
|
||||
case expr.Iterator:
|
||||
out = &types.Record{}
|
||||
return out, val.Each(func(k string, v expr.TypedValue) error {
|
||||
return assignToComposeRecord(out, k, v)
|
||||
})
|
||||
}
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case *types.Record:
|
||||
if val == nil {
|
||||
val = &types.Record{}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
case map[string]interface{}:
|
||||
out = &types.Record{}
|
||||
m, _ := json.Marshal(val)
|
||||
_ = json.Unmarshal(m, out)
|
||||
|
||||
return
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
var _ expr.DeepFieldAssigner = &ComposeRecord{}
|
||||
|
||||
// AssignFieldValue implements expr.DeepFieldAssigner
|
||||
//
|
||||
// We need to reroute value assigning for record-value-sets because
|
||||
// we loose the reference to record-value slice
|
||||
func (t *ComposeRecord) AssignFieldValue(p expr.Pather, val expr.TypedValue) (err error) {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
switch p.Get() {
|
||||
case "values":
|
||||
err = p.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return assignToComposeRecordValues(t.value, p, val)
|
||||
// case "labels":
|
||||
// @todo deep setting labels
|
||||
default:
|
||||
return assignToComposeRecord(t.value, p.Get(), val)
|
||||
}
|
||||
}
|
||||
|
||||
var _ gval.Selector = &ComposeRecord{}
|
||||
|
||||
// SelectGVal implements gval.Selector requirements
|
||||
//
|
||||
// It allows gval lib to access Record's underlying value (*types.Record)
|
||||
// and it's fields
|
||||
func (t *ComposeRecord) SelectGVal(_ context.Context, k string) (interface{}, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value != nil && k == "values" {
|
||||
if t.value.Values == nil {
|
||||
t.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
return &ComposeRecordValues{t.value}, nil
|
||||
}
|
||||
|
||||
return composeRecordGValSelector(t.value, k)
|
||||
}
|
||||
|
||||
// Select is field accessor for *types.ComposeRecord
|
||||
//
|
||||
// Similar to SelectGVal but returns typed values
|
||||
func (t *ComposeRecord) Select(k string) (expr.TypedValue, error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
if t.value != nil && k == "values" {
|
||||
if t.value.Values == nil {
|
||||
t.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
return &ComposeRecordValues{value: t.value}, nil
|
||||
}
|
||||
|
||||
return composeRecordTypedValueSelector(t.value, k)
|
||||
}
|
||||
|
||||
type ComposeRecordValues struct{ value *types.Record }
|
||||
|
||||
func CastToComposeRecordValues(val interface{}) (out types.RecordValueSet, err error) {
|
||||
out = types.RecordValueSet{}
|
||||
|
||||
switch val := val.(type) {
|
||||
case expr.Iterator:
|
||||
return out, val.Each(func(k string, v expr.TypedValue) error {
|
||||
// try with slice of strings first:
|
||||
|
||||
if v.Type() == "String" {
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: v.Get().(string)})
|
||||
return nil
|
||||
}
|
||||
|
||||
if ss, err := cast.ToStringSliceE(expr.UntypedValue(v)); err == nil {
|
||||
for i, v := range ss {
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: v, Place: uint(i)})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if str, err := expr.CastToString(v); err != nil {
|
||||
return err
|
||||
} else {
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: str})
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case *types.Record:
|
||||
return val.Values, nil
|
||||
case *types.RecordValue:
|
||||
return types.RecordValueSet{val}, nil
|
||||
case types.RecordValueSet:
|
||||
return val, nil
|
||||
case map[string]string:
|
||||
for _, k := range slice.Keys(val) {
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: val[k]})
|
||||
}
|
||||
return
|
||||
case map[string][]string:
|
||||
for _, k := range slice.Keys(val) {
|
||||
for i, v := range val[k] {
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: v, Place: uint(i)})
|
||||
}
|
||||
}
|
||||
return
|
||||
case map[string]interface{}:
|
||||
for _, k := range slice.Keys(val) {
|
||||
if isNil(val[k]) {
|
||||
continue
|
||||
}
|
||||
|
||||
var vv []interface{}
|
||||
// covering a couple of typed slices
|
||||
ref := reflect.ValueOf(val[k])
|
||||
|
||||
if ref.Kind() == reflect.Slice {
|
||||
for i := 0; i < ref.Len(); i++ {
|
||||
vv = append(vv, ref.Index(i).Interface())
|
||||
}
|
||||
} else {
|
||||
vv = []interface{}{val[k]}
|
||||
}
|
||||
|
||||
i := 0
|
||||
for _, value := range vv {
|
||||
switch v := value.(type) {
|
||||
// a small exception for boolean fields, don't add false values and when its true cast to 1
|
||||
case bool:
|
||||
if !v {
|
||||
continue
|
||||
} else {
|
||||
value = "1"
|
||||
}
|
||||
}
|
||||
|
||||
out = out.Set(&types.RecordValue{Name: k, Value: cast.ToString(value), Place: uint(i)})
|
||||
|
||||
// explicitly counting because of how booleans are handled
|
||||
i++
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) AssignFieldValue(p expr.Pather, val expr.TypedValue) error {
|
||||
return assignToComposeRecordValues(t.value, p, val)
|
||||
}
|
||||
|
||||
// SelectGVal implements gval.Selector requirements
|
||||
//
|
||||
// It allows gval lib to access Record's underlying value (*types.RecordValues)
|
||||
// and it's fields
|
||||
func (t *ComposeRecordValues) SelectGVal(_ context.Context, k string) (interface{}, error) {
|
||||
return composeRecordValuesGValSelector(t.value, k)
|
||||
}
|
||||
|
||||
// IsEmpty implements pkg/expr.empty requirements to be able to determine if
|
||||
// the value is empty.
|
||||
//
|
||||
// This is needed cor cases when we are working with empty records, but are trying
|
||||
// to access their values.
|
||||
func (t *ComposeRecordValues) IsEmpty() bool {
|
||||
return t == nil || t.value == nil || len(t.value.Values) == 0
|
||||
}
|
||||
|
||||
// Select is field accessor for *types.Record
|
||||
//
|
||||
// Similar to SelectGVal but returns typed values
|
||||
func (t *ComposeRecordValues) Select(k string) (expr.TypedValue, error) {
|
||||
return composeRecordValuesTypedValueSelector(t.value, k)
|
||||
}
|
||||
|
||||
func (t ComposeRecordValues) Has(k string) bool {
|
||||
return t.value.Values.Get(k, 0) != nil
|
||||
}
|
||||
|
||||
// recordGValSelector is field accessor for *types.RecordValueSet
|
||||
func composeRecordValuesGValSelector(res *types.Record, k string) (interface{}, error) {
|
||||
var (
|
||||
vv = res.Values.FilterByName(k)
|
||||
|
||||
multiValueField bool
|
||||
field *types.ModuleField
|
||||
)
|
||||
|
||||
if mod := res.GetModule(); mod != nil {
|
||||
field = mod.Fields.FindByName(k)
|
||||
|
||||
if field == nil {
|
||||
return nil, fmt.Errorf("field '%s' does not exist on module %s", k, mod.Name)
|
||||
}
|
||||
|
||||
multiValueField = field.Multi
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(vv) == 0:
|
||||
if field != nil && field.IsBoolean() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
case len(vv) == 1 && !multiValueField:
|
||||
return vv[0].Cast(field)
|
||||
|
||||
default:
|
||||
out := make([]interface{}, 0, len(vv))
|
||||
return out, vv.Walk(func(v *types.RecordValue) error {
|
||||
i, err := v.Cast(field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out = append(out, i)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// recordValuesTypedValueSelector is field accessor for *types.RecordValueSet
|
||||
func composeRecordValuesTypedValueSelector(res *types.Record, k string) (expr.TypedValue, error) {
|
||||
var (
|
||||
vv = res.Values.FilterByName(k)
|
||||
|
||||
multiValueField bool
|
||||
field *types.ModuleField
|
||||
)
|
||||
|
||||
if mod := res.GetModule(); mod != nil {
|
||||
field = mod.Fields.FindByName(k)
|
||||
|
||||
if field == nil {
|
||||
return nil, fmt.Errorf("field '%s' does not exist on module %s", k, mod.Name)
|
||||
}
|
||||
|
||||
multiValueField = field.Multi
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(vv) == 0:
|
||||
if field != nil && field.IsBoolean() {
|
||||
return &expr.Boolean{}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
case len(vv) == 1 && !multiValueField:
|
||||
return recordValueToExprTypedValue(field, vv[0])
|
||||
default:
|
||||
return recordValueSetToExprArray(field, vv...)
|
||||
}
|
||||
}
|
||||
|
||||
// assignToRecordValuesSet is field value setter for *types.RecordValueSet
|
||||
//
|
||||
// We'll be using types.Record for the base (and not types.RecordValueSet)
|
||||
func assignToComposeRecordValues(res *types.Record, p expr.Pather, val interface{}) (err error) {
|
||||
if p == nil || !p.More() {
|
||||
switch val := val.(type) {
|
||||
case *ComposeRecordValues:
|
||||
res.Values = val.value.Values
|
||||
return
|
||||
case *ComposeRecord:
|
||||
*res = *val.value
|
||||
return
|
||||
}
|
||||
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case types.RecordValueSet:
|
||||
res.Values = val
|
||||
return
|
||||
case *types.Record:
|
||||
*res = *val
|
||||
return
|
||||
}
|
||||
|
||||
return fmt.Errorf("empty path used for assigning record values")
|
||||
}
|
||||
|
||||
var (
|
||||
k = p.Get()
|
||||
rv = &types.RecordValue{Name: k}
|
||||
|
||||
setSliceOfValues = func(vv []interface{}) error {
|
||||
// Handle situation where array of values is assigned to a single (multi-value) field
|
||||
// @todo this should use field context (when available) to determinate if we're actually
|
||||
// setting array to a multi-value field
|
||||
|
||||
if !p.IsLast() {
|
||||
// Tying to assign an array of values to a single value; that will not work
|
||||
return fmt.Errorf("can not assign array of values to a single value in a record value set")
|
||||
}
|
||||
|
||||
if ss, err := cast.ToStringSliceE(vv); err != nil {
|
||||
return err
|
||||
} else {
|
||||
res.Values = res.Values.Replace(k, ss...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
// @todo this needs to be implemented properly
|
||||
// we're just guessing here and putting out fires
|
||||
switch utval := expr.UntypedValue(val).(type) {
|
||||
case time.Time:
|
||||
rv.Value = utval.Format(time.RFC3339)
|
||||
case *time.Time:
|
||||
if utval == nil {
|
||||
rv.Value = ""
|
||||
break
|
||||
}
|
||||
rv.Value = utval.Format(time.RFC3339)
|
||||
case []string:
|
||||
aux := make([]interface{}, len(utval))
|
||||
for i := range utval {
|
||||
aux[i] = utval[i]
|
||||
}
|
||||
|
||||
return setSliceOfValues(aux)
|
||||
case []expr.TypedValue: // expr.Array
|
||||
aux := make([]interface{}, len(utval))
|
||||
for i := range utval {
|
||||
aux[i] = utval[i].Get()
|
||||
}
|
||||
|
||||
return setSliceOfValues(aux)
|
||||
case []interface{}: // expr.Any
|
||||
return setSliceOfValues(utval)
|
||||
default:
|
||||
rv.Value, err = cast.ToStringE(utval)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !p.IsLast() {
|
||||
err = p.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rv.Place, err = cast.ToUintE(expr.UntypedValue(p.Get())); err != nil {
|
||||
return fmt.Errorf("failed to decode record value place from '%s': %w", p.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
res.Values = res.Values.Set(rv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewComposeRecordValues creates new instance of ComposeRecordValues expression type
|
||||
func NewComposeRecordValues(val interface{}) (*ComposeRecordValues, error) {
|
||||
if c, err := CastToComposeRecordValues(val); err != nil {
|
||||
return nil, fmt.Errorf("unable to create ComposeRecordValues: %w", err)
|
||||
} else {
|
||||
return &ComposeRecordValues{value: &types.Record{Values: c}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Return underlying value on ComposeRecordValues
|
||||
func (t ComposeRecordValues) Get() interface{} { return t.value }
|
||||
|
||||
// Return underlying value on ComposeRecordValues
|
||||
func (t ComposeRecordValues) GetValue() types.RecordValueSet { return t.value.Values }
|
||||
|
||||
// Return type name
|
||||
func (ComposeRecordValues) Type() string { return "ComposeRecordValues" }
|
||||
|
||||
// Convert value to types.RecordValueSet
|
||||
func (ComposeRecordValues) Cast(val interface{}) (expr.TypedValue, error) {
|
||||
return NewComposeRecordValues(val)
|
||||
}
|
||||
|
||||
// Assign new value to ComposeRecordValues
|
||||
//
|
||||
// value is first passed through CastToComposeRecordValues
|
||||
func (t *ComposeRecordValues) Assign(val interface{}) error {
|
||||
if c, err := CastToComposeRecordValues(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.value.Values = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func CastToComposeRecordValueErrorSet(val interface{}) (out *types.RecordValueErrorSet, err error) {
|
||||
switch val := expr.UntypedValue(val).(type) {
|
||||
case *types.RecordValueErrorSet:
|
||||
return val, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
func recordValueToExprTypedValue(field *types.ModuleField, rv *types.RecordValue) (expr.TypedValue, error) {
|
||||
if v, err := rv.Cast(field); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return expr.Typify(v)
|
||||
}
|
||||
}
|
||||
|
||||
func recordValueSetToExprArray(field *types.ModuleField, vv ...*types.RecordValue) (arr *expr.Array, err error) {
|
||||
var (
|
||||
tv expr.TypedValue
|
||||
)
|
||||
|
||||
arr = &expr.Array{}
|
||||
|
||||
for _, v := range vv {
|
||||
tv, err = recordValueToExprTypedValue(field, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
arr.Push(tv)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func isNil(i interface{}) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
switch reflect.TypeOf(i).Kind() {
|
||||
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Map:
|
||||
return reflect.ValueOf(i).IsNil()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func EmptyComposeRecordValues() *ComposeRecordValues {
|
||||
return &ComposeRecordValues{value: &types.Record{Values: types.RecordValueSet{}}}
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) Each(fn func(k string, v expr.TypedValue) error) (err error) {
|
||||
if t.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// Helper to check if field is a multi value or not; defaulting to false
|
||||
isMulti := func(name string) (multi bool) {
|
||||
m := t.value.GetModule()
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if m.Fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
f := m.Fields.FindByName(name)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return f.Multi
|
||||
}
|
||||
|
||||
// RecordValues can have multi value fields which need to be presented as an Array
|
||||
lastKey := ""
|
||||
values := []expr.TypedValue{}
|
||||
|
||||
// Processes the current set of values keeping multi values in mind
|
||||
processChunk := func() (err error) {
|
||||
if isMulti(lastKey) {
|
||||
ar := &expr.Array{}
|
||||
for _, x := range values {
|
||||
ar.Push(x)
|
||||
}
|
||||
|
||||
if err = fn(lastKey, ar); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, v := range values {
|
||||
if err = fn(lastKey, v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, vv := range t.GetValue() {
|
||||
key := vv.Name
|
||||
if len(key) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Reset placement since it's not valid on non multi value fields
|
||||
if !isMulti(key) {
|
||||
vv.Place = 0
|
||||
}
|
||||
|
||||
// Mismatch, process prev values
|
||||
if key != lastKey {
|
||||
err = processChunk()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
values = values[0:0]
|
||||
lastKey = key
|
||||
}
|
||||
|
||||
var val expr.TypedValue
|
||||
val, err = expr.Typify(vv)
|
||||
values = append(values, val)
|
||||
}
|
||||
|
||||
err = processChunk()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Merge combines the given ComposeRecordValues into ComposeRecordValues
|
||||
// NOTE: It will return CLONE of the original ComposeRecordValues, if it's called without any parameters
|
||||
func (t *ComposeRecordValues) Merge(nn ...expr.Iterator) (out expr.TypedValue, err error) {
|
||||
rv := EmptyComposeRecordValues()
|
||||
if t.value == nil {
|
||||
t.value = &types.Record{}
|
||||
}
|
||||
|
||||
rv.value.ID = t.value.ID
|
||||
rv.value.ModuleID = t.value.ModuleID
|
||||
rv.value.SetModule(t.value.GetModule())
|
||||
rv.value.NamespaceID = t.value.NamespaceID
|
||||
|
||||
nn = append([]expr.Iterator{t}, nn...)
|
||||
|
||||
procValue := func(v expr.TypedValue) (err error) {
|
||||
var (
|
||||
rVal types.RecordValue
|
||||
bb []byte
|
||||
)
|
||||
|
||||
// @todo implement casting of RecordValue from TypedValue
|
||||
bb, err = json.Marshal(v.Get())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bb, &rVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr := rv.GetValue().Set(&rVal)
|
||||
err = rv.Assign(rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range nn {
|
||||
err = i.Each(func(k string, v expr.TypedValue) error {
|
||||
if v.Type() == "Array" {
|
||||
// This holds since it's an array
|
||||
for _, v := range (v.(*expr.Array)).Get().([]expr.TypedValue) {
|
||||
err = procValue(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = procValue(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) Filter(keys ...string) (out expr.TypedValue, err error) {
|
||||
if t.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// get cloned ComposeRecordValues
|
||||
out, err = t.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv := out.(*ComposeRecordValues)
|
||||
if !rv.IsEmpty() {
|
||||
rv.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
keyMap := make(map[string]string)
|
||||
for _, k := range keys {
|
||||
keyMap[k] = k
|
||||
}
|
||||
|
||||
// Push the only with values with matching Name
|
||||
for _, val := range t.GetValue() {
|
||||
_, ok := keyMap[val.Name]
|
||||
if ok {
|
||||
rr := rv.GetValue().Set(val.Clone())
|
||||
err = rv.Assign(rr)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) Delete(keys ...string) (out expr.TypedValue, err error) {
|
||||
if t.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// get cloned ComposeRecordValues
|
||||
out, err = t.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv := out.(*ComposeRecordValues)
|
||||
if !rv.IsEmpty() {
|
||||
rv.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
keyMap := make(map[string]string)
|
||||
for _, k := range keys {
|
||||
keyMap[k] = k
|
||||
}
|
||||
|
||||
// Push the only with values with non-matching Name
|
||||
for _, val := range t.GetValue() {
|
||||
_, ok := keyMap[val.Name]
|
||||
if !ok {
|
||||
rr := rv.GetValue().Set(val.Clone())
|
||||
err = rv.Assign(rr)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (v *Attachment) Clone() (expr.TypedValue, error) {
|
||||
att := *v.value
|
||||
aux, err := NewAttachment(&att)
|
||||
return aux, err
|
||||
}
|
||||
|
||||
func (v *ComposeModule) Clone() (expr.TypedValue, error) {
|
||||
aux, err := NewComposeModule(v.value.Clone())
|
||||
return aux, err
|
||||
}
|
||||
|
||||
func (v *ComposeNamespace) Clone() (expr.TypedValue, error) {
|
||||
aux, err := NewComposeNamespace(v.value.Clone())
|
||||
return aux, err
|
||||
}
|
||||
|
||||
func (v *ComposeRecord) Clone() (expr.TypedValue, error) {
|
||||
aux, err := NewComposeRecord(v.value.Clone())
|
||||
return aux, err
|
||||
}
|
||||
|
||||
func (v *ComposeRecordValues) Clone() (expr.TypedValue, error) {
|
||||
aux, err := NewComposeRecordValues(v.value.Clone())
|
||||
return aux, err
|
||||
}
|
||||
|
||||
func (v *ComposeRecordValueErrorSet) Clone() (expr.TypedValue, error) {
|
||||
if v.value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
errs := types.RecordValueErrorSet{
|
||||
Set: make([]types.RecordValueError, len(v.value.Set)),
|
||||
}
|
||||
|
||||
for i, v := range v.value.Set {
|
||||
errs.Set[i] = v
|
||||
}
|
||||
|
||||
aux, err := NewComposeRecordValueErrorSet(&errs)
|
||||
return aux, err
|
||||
}
|
||||
64
server/compose/automation/expr_types.yaml
Normal file
64
server/compose/automation/expr_types.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
package: automation
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
types:
|
||||
ComposeNamespace:
|
||||
as: '*types.Namespace'
|
||||
struct:
|
||||
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'namespaceID' }
|
||||
- { name: 'name', exprType: 'String', goType: 'string' }
|
||||
- { name: 'slug', exprType: 'Handle', goType: 'string', alias: 'handle'}
|
||||
- { name: 'labels', exprType: 'LabelValue', goType: 'map[string]labelTypes.LabelValue' }
|
||||
- { 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 }
|
||||
ComposeModule:
|
||||
as: '*types.Module'
|
||||
struct:
|
||||
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'moduleID' }
|
||||
- { name: 'namespaceID', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
- { name: 'name', exprType: 'String', goType: 'string' }
|
||||
- { name: 'handle', exprType: 'Handle', goType: 'string' }
|
||||
- { name: 'labels', exprType: 'LabelValue', goType: 'map[string]labelTypes.LabelValue' }
|
||||
- { 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 }
|
||||
|
||||
ComposeRecord:
|
||||
as: '*types.Record'
|
||||
struct:
|
||||
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'recordID' }
|
||||
- { name: 'moduleID', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
- { name: 'namespaceID', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
- { name: 'values', exprType: 'ComposeRecordValues', goType: 'map[string]interface{}' }
|
||||
- { name: 'meta', exprType: 'Meta', goType: 'map[string]any' }
|
||||
- { name: 'ownedBy', exprType: 'ID', goType: 'uint64' }
|
||||
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro }
|
||||
- { name: 'createdBy', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
|
||||
- { name: 'updatedBy', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
|
||||
- { name: 'deletedBy', exprType: 'ID', goType: 'uint64', mode: ro }
|
||||
customSelector: true
|
||||
customGValSelector: true
|
||||
customFieldAssigner: true
|
||||
|
||||
ComposeRecordValues:
|
||||
customType: true
|
||||
as: 'types.RecordValueSet'
|
||||
|
||||
ComposeRecordValueErrorSet:
|
||||
as: '*types.RecordValueErrorSet'
|
||||
|
||||
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 }
|
||||
749
server/compose/automation/expr_types_test.go
Normal file
749
server/compose/automation/expr_types_test.go
Normal file
@@ -0,0 +1,749 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
aTypes "github.com/cortezaproject/corteza/server/automation/types"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSetRecordValuesWithPath(t *testing.T) {
|
||||
t.Run("some basic aerobics", func(t *testing.T) {
|
||||
|
||||
var (
|
||||
r = require.New(t)
|
||||
rvs = &ComposeRecordValues{&types.Record{}}
|
||||
)
|
||||
|
||||
r.NoError(expr.Assign(rvs, "field1", expr.Must(expr.NewString("a"))))
|
||||
r.NoError(expr.Assign(rvs, "field1.1", expr.Must(expr.NewString("a"))))
|
||||
r.True(rvs.value.Values.Has("field1", 0))
|
||||
r.True(rvs.value.Values.Has("field1", 1))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecordFieldValuesAccess(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
v expr.TypedValue
|
||||
|
||||
mod = &types.Module{Fields: types.ModuleFieldSet{
|
||||
&types.ModuleField{Name: "s1", Multi: false, Kind: "String"},
|
||||
&types.ModuleField{Name: "m1", Multi: true, Kind: "String"},
|
||||
&types.ModuleField{Name: "m2", Multi: true, Kind: "String"},
|
||||
&types.ModuleField{Name: "s2", Multi: false, Kind: "String"},
|
||||
&types.ModuleField{Name: "b0", Multi: false, Kind: "Bool"},
|
||||
&types.ModuleField{Name: "b1", Multi: false, Kind: "Bool"},
|
||||
&types.ModuleField{Name: "n1", Multi: false, Kind: "Number"},
|
||||
&types.ModuleField{Name: "n2", Multi: false, Kind: "Number"},
|
||||
&types.ModuleField{Name: "n3", Multi: false, Kind: "Number"},
|
||||
&types.ModuleField{Name: "d1", Multi: false, Kind: "DateTime"},
|
||||
&types.ModuleField{Name: "d2", Multi: false, Kind: "DateTime"},
|
||||
&types.ModuleField{Name: "ref1", Multi: false, Kind: "Record"},
|
||||
&types.ModuleField{Name: "ref2", Multi: false, Kind: "Record"},
|
||||
}}
|
||||
|
||||
rawValues = types.RecordValueSet{
|
||||
&types.RecordValue{Name: "s1", Value: "sVal1"},
|
||||
&types.RecordValue{Name: "m1", Value: "mVal1.0", Place: 0},
|
||||
&types.RecordValue{Name: "m1", Value: "mVal1.1", Place: 1},
|
||||
&types.RecordValue{Name: "m1", Value: "mVal1.2", Place: 2},
|
||||
&types.RecordValue{Name: "m2", Value: "mVal2.0", Place: 0},
|
||||
&types.RecordValue{Name: "b1", Value: "1", Place: 0},
|
||||
&types.RecordValue{Name: "n2", Value: "0", Place: 0},
|
||||
&types.RecordValue{Name: "n3", Value: "2", Place: 0},
|
||||
&types.RecordValue{Name: "ref2", Value: "", Ref: 2, Place: 0},
|
||||
&types.RecordValue{Name: "d1", Value: "", Place: 0},
|
||||
&types.RecordValue{Name: "d2", Value: "1999-09-09 09:09:09.000000009 +0000 UTC", Place: 0},
|
||||
}
|
||||
raw = &types.Record{Values: rawValues}
|
||||
|
||||
scope, _ = expr.NewVars(map[string]interface{}{
|
||||
"rec": &ComposeRecord{value: raw},
|
||||
|
||||
// nis is only for testing, gval does not recognise nil (or null) as a keyword!
|
||||
"nil": nil,
|
||||
|
||||
// typed value but empty (this happens when you assign next value in expression but do not set a value to it
|
||||
"initRec": &ComposeRecord{},
|
||||
|
||||
// same as &ComposeRecord{value: &types.Record{}},
|
||||
"validRecZero": &ComposeRecord{value: &types.Record{ID: 0, Values: types.RecordValueSet{}}},
|
||||
"validRecValidID": &ComposeRecord{value: &types.Record{ID: 42, Values: types.RecordValueSet{}}},
|
||||
|
||||
// "record" (not really) set to nil
|
||||
"fooRec": nil,
|
||||
|
||||
// record with id and value set (this was) fixme
|
||||
"record": &ComposeRecord{value: &types.Record{ID: 99, Values: rawValues}},
|
||||
})
|
||||
)
|
||||
|
||||
// @todo see note above regarding back-ref to record
|
||||
raw.SetModule(mod)
|
||||
|
||||
t.Run("via typed value", func(t *testing.T) {
|
||||
tcc := []struct {
|
||||
expects interface{}
|
||||
path string
|
||||
}{
|
||||
{"sVal1", "rec.values.s1"},
|
||||
{"mVal1.0", "rec.values.m1.0"},
|
||||
{"mVal1.1", "rec.values.m1.1"},
|
||||
{"mVal2.0", "rec.values.m2.0"},
|
||||
// expecting valid value (false) even when boolean fields are not set
|
||||
{false, "rec.values.b0"},
|
||||
{true, "rec.values.b1"},
|
||||
{uint64(2), "rec.values.ref2"},
|
||||
{raw, "rec.values"},
|
||||
}
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.path, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
v, err = expr.Select(scope, tc.path)
|
||||
req.NoError(err)
|
||||
req.Equal(tc.expects, v.Get())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("copy record's values via gval selector", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
output *expr.Vars
|
||||
inputRec = &types.Record{ID: 99, Values: rawValues}
|
||||
cloneRec = &types.Record{}
|
||||
)
|
||||
|
||||
// IMPORTANT! set module to record; otherwise values will not be copied properly
|
||||
inputRec.SetModule(mod)
|
||||
cloneRec.SetModule(mod)
|
||||
|
||||
e, err := aTypes.NewExpr(
|
||||
"recordClone.values",
|
||||
ComposeRecordValues{}.Type(),
|
||||
`record.values`)
|
||||
req.NoError(err)
|
||||
|
||||
err = e.SetType(func(s string) (expr.Type, error) {
|
||||
return ComposeRecordValues{}, nil
|
||||
})
|
||||
req.NoError(err)
|
||||
|
||||
evaluable, err := expr.NewParser().Parse(e.GetExpr())
|
||||
req.NoError(err)
|
||||
e.SetEval(evaluable)
|
||||
|
||||
input, _ := expr.NewVars(map[string]expr.TypedValue{
|
||||
"record": &ComposeRecord{value: inputRec},
|
||||
"recordClone": &ComposeRecord{value: cloneRec},
|
||||
})
|
||||
|
||||
output, err = (aTypes.ExprSet{e}).Eval(context.Background(), input)
|
||||
req.NoError(err)
|
||||
rc, err := output.Select("recordClone")
|
||||
req.NoError(err)
|
||||
req.NotNil(rc.Get())
|
||||
req.Equal(rawValues, rc.Get().(*types.Record).Values)
|
||||
})
|
||||
|
||||
t.Run("via gval selector", func(t *testing.T) {
|
||||
tcc := []struct {
|
||||
test interface{}
|
||||
expr string
|
||||
}{
|
||||
{false, `nil`},
|
||||
{true, `rec`},
|
||||
{true, `rec.values`},
|
||||
|
||||
// not set, so false
|
||||
{fmt.Errorf("failed to select 'notSetRec' on *expr.Vars: no such key 'notSetRec'"), `notSetRec`},
|
||||
|
||||
// set but nil
|
||||
{false, `fooRec`},
|
||||
|
||||
// set, initialized
|
||||
{true, `initRec`},
|
||||
|
||||
// set, initialized, but recordID is empty
|
||||
{false, `initRec.recordID`},
|
||||
|
||||
// set, initialized, but recordID (ID is alias) is empty
|
||||
{false, `initRec.ID`},
|
||||
|
||||
// set, initialized, but values are empty
|
||||
{false, `initRec.values`},
|
||||
|
||||
{true, `validRecZero`},
|
||||
{false, `validRecZero.recordID`},
|
||||
{false, `validRecZero.values`},
|
||||
|
||||
{true, `validRecValidID`},
|
||||
{true, `validRecValidID.recordID`},
|
||||
{false, `validRecValidID.values`},
|
||||
|
||||
// but we can not access the values below...
|
||||
{fmt.Errorf("unknown parameter initRec.values.foo"), `initRec.values.foo`},
|
||||
|
||||
// interaction with set values
|
||||
{true, `rec.values.s1 == "sVal1"`},
|
||||
{false, `rec.values.s1 == "sVal2"`},
|
||||
{true, `rec.values.s1`},
|
||||
{true, `rec.values.s1 != "foo"`},
|
||||
|
||||
// interaction with unset (= nil) values
|
||||
{true, `rec.values.s2 != "foo"`},
|
||||
{false, `rec.values.s2 == "foo"`},
|
||||
{true, `!rec.values.s2`},
|
||||
{false, `rec.values.s2`},
|
||||
|
||||
// multival
|
||||
{true, `rec.values.m1[0] == "mVal1.0"`},
|
||||
{true, `rec.values.m1[1] == "mVal1.1"`},
|
||||
{true, `rec.values.m2[0] == "mVal2.0"`},
|
||||
|
||||
// booleans
|
||||
{true, `!rec.values.b0`},
|
||||
{false, `rec.values.b0`},
|
||||
{true, `rec.values.b1`},
|
||||
{false, `!rec.values.b1`},
|
||||
|
||||
// numbers
|
||||
{false, `rec.values.n1`},
|
||||
{false, `rec.values.n2`},
|
||||
{true, `rec.values.n3`},
|
||||
|
||||
{true, `rec.values.n1 == 0`},
|
||||
{true, `rec.values.n2 == 0`},
|
||||
{false, `rec.values.n3 == 0`},
|
||||
|
||||
{false, `rec.values.n1 == 2`},
|
||||
{false, `rec.values.n2 == 2`},
|
||||
{true, `rec.values.n3 == 2`},
|
||||
|
||||
// {true, `rec.values.n1 < 3`}, // invalid op <nil> < 3
|
||||
{true, `rec.values.n2 < 3`},
|
||||
{true, `rec.values.n3 < 3`},
|
||||
|
||||
// {false, `rec.values.n1 > 1`}, // invalid op <nil> > 3
|
||||
{false, `rec.values.n2 > 2`},
|
||||
{false, `rec.values.n3 > 2`},
|
||||
|
||||
{true, `rec.values.ref1 != 2`},
|
||||
{true, `rec.values.ref2 == 2`},
|
||||
{true, `rec.values.ref2 == "2"`},
|
||||
|
||||
{true, `isNil(nil)`},
|
||||
{true, `isNil(rec.values.d1)`},
|
||||
{true, `rec.values.n3 ? true : false`},
|
||||
{false, `rec.values.d1 ? true : false`},
|
||||
{true, `rec.values.d2 ? true : false`},
|
||||
{true, `rec.values.d1 ? true : false || true `},
|
||||
{false, `rec.values.d1 ? true : false && true `},
|
||||
}
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.expr, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
parser = expr.NewParser()
|
||||
eval, err = parser.Parse(tc.expr)
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
|
||||
test, err := eval.Test(context.Background(), scope)
|
||||
switch tct := tc.test.(type) {
|
||||
case error:
|
||||
req.EqualError(err, tct.Error())
|
||||
|
||||
case bool:
|
||||
req.NoError(err)
|
||||
req.Equal(tc.test, test)
|
||||
|
||||
default:
|
||||
panic("unexpected test case type")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssignToComposeRecordValues(t *testing.T) {
|
||||
t.Run("assign simple", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{}}
|
||||
)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, initPather(req, expr.Path("a")), "b"))
|
||||
req.Len(target.Values, 1)
|
||||
req.True(target.Values.Has("a", 0))
|
||||
req.NoError(assignToComposeRecordValues(target, initPather(req, expr.Path("a[1]")), "b"))
|
||||
req.Len(target.Values, 2)
|
||||
req.True(target.Values.Has("a", 0))
|
||||
req.True(target.Values.Has("a", 1))
|
||||
})
|
||||
|
||||
t.Run("assign rvs", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{}}
|
||||
)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, nil, types.RecordValueSet{{}}))
|
||||
req.Len(target.Values, 1)
|
||||
})
|
||||
|
||||
t.Run("assign record", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{}}
|
||||
)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, nil, &types.Record{Values: types.RecordValueSet{{}}}))
|
||||
req.Len(target.Values, 1)
|
||||
})
|
||||
|
||||
t.Run("overwrite rvs", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{{Name: "a"}}}
|
||||
)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, nil, types.RecordValueSet{{Name: "b"}}))
|
||||
req.Len(target.Values, 1)
|
||||
req.False(target.Values.Has("a", 0))
|
||||
req.True(target.Values.Has("b", 0))
|
||||
})
|
||||
|
||||
t.Run("assign multiple values", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{}}
|
||||
)
|
||||
|
||||
req.Error(assignToComposeRecordValues(target, initPather(req, expr.Path("a[2]")), expr.Must(expr.NewAny([]interface{}{"1", "2"}))))
|
||||
req.Len(target.Values, 0)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, initPather(req, expr.Path("a")), expr.Must(expr.NewAny([]interface{}{"1", "2"}))))
|
||||
req.Len(target.Values, 2)
|
||||
|
||||
req.NoError(assignToComposeRecordValues(target, initPather(req, expr.Path("a")), expr.Must(expr.NewAny([]string{"1", "2"}))))
|
||||
req.Len(target.Values, 2)
|
||||
})
|
||||
|
||||
t.Run("overwrite multiple values", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
target = &types.Record{Values: types.RecordValueSet{
|
||||
&types.RecordValue{Name: "mval", Value: "203221389712893", Place: 0},
|
||||
&types.RecordValue{Name: "mval", Value: "203221389712893", Place: 1},
|
||||
}}
|
||||
|
||||
scope, _ = expr.NewVars(map[string]interface{}{
|
||||
"rec": expr.Must(NewComposeRecord(target)),
|
||||
})
|
||||
|
||||
ee = aTypes.ExprSet{
|
||||
&aTypes.Expr{Target: "rec.values.mval", Type: "Array", Expr: "[1,2,3,4]"},
|
||||
}
|
||||
)
|
||||
|
||||
// convert all types
|
||||
req.NoError(ee[0].SetType(func(_ string) (expr.Type, error) { return expr.Array{}, nil }))
|
||||
|
||||
// init expression handler
|
||||
evaluable, err := expr.NewParser().Parse(ee[0].GetExpr())
|
||||
req.NoError(err)
|
||||
ee[0].SetEval(evaluable)
|
||||
|
||||
_, err = ee.Eval(context.Background(), scope)
|
||||
req.NoError(err)
|
||||
req.Len(target.Values, 4)
|
||||
req.Equal(target.Values.Get("mval", 0).Value, "1")
|
||||
req.Equal(target.Values.Get("mval", 1).Value, "2")
|
||||
req.Equal(target.Values.Get("mval", 2).Value, "3")
|
||||
req.Equal(target.Values.Get("mval", 3).Value, "4")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCastToComposeRecordValues(t *testing.T) {
|
||||
var (
|
||||
commonRVS = types.RecordValueSet{
|
||||
&types.RecordValue{Name: "bools", Value: "1"},
|
||||
&types.RecordValue{Name: "interfaces", Value: "val"},
|
||||
&types.RecordValue{Name: "interfaces", Value: "val2", Place: 1},
|
||||
&types.RecordValue{Name: "string", Value: "val"},
|
||||
&types.RecordValue{Name: "strings", Value: "val"},
|
||||
&types.RecordValue{Name: "strings", Value: "val2", Place: 1},
|
||||
// false booleans will not be set and that is ok!
|
||||
// &types.RecordValue{Name: "bools", Value: "false", Place: 1}
|
||||
}
|
||||
|
||||
tt = time.Date(1999, 9, 9, 9, 9, 9, 9, time.UTC)
|
||||
|
||||
nilSlice []int
|
||||
nilUntypedMap map[string]interface{}
|
||||
|
||||
cases = []struct {
|
||||
name string
|
||||
in interface{}
|
||||
out types.RecordValueSet
|
||||
err error
|
||||
}{
|
||||
{
|
||||
in: map[string]interface{}{
|
||||
"string": "val",
|
||||
"interfaces": []interface{}{"val", "val2"},
|
||||
"strings": []string{"val", "val2"},
|
||||
"bools": []bool{true, false},
|
||||
"nilSlice": nilSlice,
|
||||
"nilUntypedMap": nilUntypedMap,
|
||||
},
|
||||
// warning! false booleans will not be set and that is ok!
|
||||
out: commonRVS,
|
||||
},
|
||||
{
|
||||
in: map[string][]string{
|
||||
"strings": {"val", "val2"},
|
||||
},
|
||||
out: types.RecordValueSet{
|
||||
&types.RecordValue{Name: "strings", Value: "val"},
|
||||
&types.RecordValue{Name: "strings", Value: "val2", Place: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: map[string]string{
|
||||
"string": "val",
|
||||
},
|
||||
out: types.RecordValueSet{
|
||||
&types.RecordValue{Name: "string", Value: "val"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: &types.RecordValue{Name: "string", Value: "val"},
|
||||
out: types.RecordValueSet{
|
||||
&types.RecordValue{Name: "string", Value: "val"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: &types.RecordValue{Name: "datetime", Value: tt.String()},
|
||||
out: types.RecordValueSet{
|
||||
&types.RecordValue{Name: "datetime", Value: tt.String()},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: commonRVS,
|
||||
out: commonRVS,
|
||||
},
|
||||
{
|
||||
in: &types.Record{Values: commonRVS},
|
||||
out: commonRVS,
|
||||
},
|
||||
{
|
||||
in: 42,
|
||||
err: fmt.Errorf("unable to cast type int to types.RecordValueSet"),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
if c.name == "" && c.in != nil {
|
||||
c.name = fmt.Sprintf("%T", c.in)
|
||||
}
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
if c.in == nil {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
if out, err := CastToComposeRecordValues(c.in); c.err != nil {
|
||||
req.EqualError(err, c.err.Error())
|
||||
} else {
|
||||
req.EqualValues(c.out, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNil(t *testing.T) {
|
||||
var (
|
||||
nilSlice []int
|
||||
nilUntypedSlice []interface{}
|
||||
nilPtr *int
|
||||
nilStringMap map[string]string
|
||||
nilUntypedMap map[string]interface{}
|
||||
|
||||
cases = []struct {
|
||||
name string
|
||||
input interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"nilSlice",
|
||||
nilSlice,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nilUntypedSlice",
|
||||
nilUntypedSlice,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nilPtr",
|
||||
nilPtr,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nilStringMap",
|
||||
nilStringMap,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nilUntypedMap",
|
||||
nilUntypedMap,
|
||||
true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
req.Equal(c.expected, isNil(c.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGvalStringFunctionParamsIntCasting(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
|
||||
scope = &expr.Vars{}
|
||||
)
|
||||
|
||||
t.Run("substring", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`substring("foobar", 1, -1)`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal("oobar", res.(string))
|
||||
})
|
||||
|
||||
t.Run("replace", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`replace(" foo foo foo", "foo", "bar", 1)`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal(" bar foo foo", res.(string))
|
||||
})
|
||||
|
||||
t.Run("split", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`split("foo-bar", "-")`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal([]string{"foo", "bar"}, res.([]string))
|
||||
})
|
||||
|
||||
t.Run("has", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`has([1,2], 2)`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal(true, res)
|
||||
})
|
||||
|
||||
t.Run("repeat", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`repeat("! ", 3)`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal("! ! ! ", res.(string))
|
||||
})
|
||||
|
||||
t.Run("shorten", func(t *testing.T) {
|
||||
eval, err := expr.NewParser().Parse(`shorten("This is a whole sentence", "word", 4)`)
|
||||
req.NoError(err)
|
||||
|
||||
res, err := eval.Eval(ctx, scope)
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal("This is a whole …", res.(string))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestRecordValues_Merge(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
rv = &ComposeRecordValues{}
|
||||
foo = &ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
bar = &ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expected = &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
out, err := rv.Merge(foo, bar)
|
||||
req.NoError(err)
|
||||
req.Equal(expected, out.Get())
|
||||
}
|
||||
|
||||
func TestRecordValues_Omit(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
rv = ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expected = &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
out, err := rv.Delete("k1", "k3")
|
||||
req.NoError(err)
|
||||
req.Equal(expected, out.Get())
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/cortezaproject/corteza/server/compose/automation
|
||||
// BenchmarkAssignToComposeRecordValues-12 22131764 53.22 ns/op 96 B/op 1 allocs/op
|
||||
// PASS
|
||||
func BenchmarkAssignToComposeRecordValues(b *testing.B) {
|
||||
var (
|
||||
req = require.New(b)
|
||||
target = &types.Record{Values: types.RecordValueSet{}}
|
||||
p = initPather(req, expr.Path("a"))
|
||||
)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
assignToComposeRecordValues(target, p, "b")
|
||||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/cortezaproject/corteza/server/compose/automation
|
||||
// BenchmarkRecordFieldValuesAccess-12 1947148 593.6 ns/op 544 B/op 19 allocs/op
|
||||
// PASS
|
||||
func BenchmarkRecordFieldValuesAccess(b *testing.B) {
|
||||
var (
|
||||
mod = &types.Module{Fields: types.ModuleFieldSet{
|
||||
&types.ModuleField{Name: "s1", Multi: true, Kind: "String"},
|
||||
}}
|
||||
|
||||
rawValues = types.RecordValueSet{
|
||||
&types.RecordValue{Name: "s1", Value: "sVal1.0", Place: 0},
|
||||
&types.RecordValue{Name: "s1", Value: "sVal1.1", Place: 1},
|
||||
&types.RecordValue{Name: "s1", Value: "sVal1.2", Place: 2},
|
||||
}
|
||||
raw = &types.Record{Values: rawValues}
|
||||
|
||||
scope, _ = expr.NewVars(map[string]interface{}{
|
||||
"rec": &ComposeRecord{value: raw},
|
||||
})
|
||||
)
|
||||
|
||||
// @todo see note above regarding back-ref to record
|
||||
raw.SetModule(mod)
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
expr.Select(scope, "rec.values.s1[1]")
|
||||
}
|
||||
}
|
||||
|
||||
func initPather(req *require.Assertions, p expr.Pather) (out expr.Pather) {
|
||||
err := p.Next()
|
||||
req.NoError(err)
|
||||
|
||||
return p
|
||||
}
|
||||
165
server/compose/automation/modules_handler.gen.go
generated
Normal file
165
server/compose/automation/modules_handler.gen.go
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
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/modules_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"
|
||||
)
|
||||
|
||||
var _ wfexec.ExecResponse
|
||||
|
||||
type (
|
||||
modulesHandlerRegistry interface {
|
||||
AddFunctions(ff ...*atypes.Function)
|
||||
Type(ref string) expr.Type
|
||||
}
|
||||
)
|
||||
|
||||
func (h modulesHandler) register() {
|
||||
h.reg.AddFunctions(
|
||||
h.Lookup(),
|
||||
)
|
||||
}
|
||||
|
||||
type (
|
||||
modulesLookupArgs struct {
|
||||
hasModule bool
|
||||
Module interface{}
|
||||
moduleID uint64
|
||||
moduleHandle string
|
||||
moduleRes *types.Module
|
||||
|
||||
hasNamespace bool
|
||||
Namespace interface{}
|
||||
namespaceID uint64
|
||||
namespaceHandle string
|
||||
namespaceRes *types.Namespace
|
||||
}
|
||||
|
||||
modulesLookupResults struct {
|
||||
Module *types.Module
|
||||
}
|
||||
)
|
||||
|
||||
func (a modulesLookupArgs) GetModule() (bool, uint64, string, *types.Module) {
|
||||
return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes
|
||||
}
|
||||
|
||||
func (a modulesLookupArgs) GetNamespace() (bool, uint64, string, *types.Namespace) {
|
||||
return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes
|
||||
}
|
||||
|
||||
// Lookup function Compose module lookup
|
||||
//
|
||||
// expects implementation of lookup function:
|
||||
//
|
||||
// func (h modulesHandler) lookup(ctx context.Context, args *modulesLookupArgs) (results *modulesLookupResults, err error) {
|
||||
// return
|
||||
// }
|
||||
func (h modulesHandler) Lookup() *atypes.Function {
|
||||
return &atypes.Function{
|
||||
Ref: "composeModulesLookup",
|
||||
Kind: "function",
|
||||
Labels: map[string]string{"compose": "step,workflow", "lookup": "step", "module": "step,workflow"},
|
||||
Meta: &atypes.FunctionMeta{
|
||||
Short: "Compose module lookup",
|
||||
Description: "Find specific module by ID or handle",
|
||||
},
|
||||
|
||||
Parameters: []*atypes.Param{
|
||||
{
|
||||
Name: "module",
|
||||
Types: []string{"ID", "Handle", "ComposeModule"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Module",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "namespace",
|
||||
Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Results: []*atypes.Param{
|
||||
|
||||
{
|
||||
Name: "module",
|
||||
Types: []string{"ComposeModule"},
|
||||
},
|
||||
},
|
||||
|
||||
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
|
||||
var (
|
||||
args = &modulesLookupArgs{
|
||||
hasModule: in.Has("module"),
|
||||
hasNamespace: in.Has("namespace"),
|
||||
}
|
||||
)
|
||||
|
||||
if err = in.Decode(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Converting Module argument
|
||||
if args.hasModule {
|
||||
aux := expr.Must(expr.Select(in, "module"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.moduleID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.moduleHandle = aux.Get().(string)
|
||||
case h.reg.Type("ComposeModule").Type():
|
||||
args.moduleRes = aux.Get().(*types.Module)
|
||||
}
|
||||
}
|
||||
|
||||
// Converting Namespace argument
|
||||
if args.hasNamespace {
|
||||
aux := expr.Must(expr.Select(in, "namespace"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.namespaceID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.namespaceHandle = aux.Get().(string)
|
||||
case h.reg.Type("ComposeNamespace").Type():
|
||||
args.namespaceRes = aux.Get().(*types.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
var results *modulesLookupResults
|
||||
if results, err = h.lookup(ctx, args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = &expr.Vars{}
|
||||
|
||||
{
|
||||
// converting results.Module (*types.Module) to ComposeModule
|
||||
var (
|
||||
tval expr.TypedValue
|
||||
)
|
||||
|
||||
if tval, err = h.reg.Type("ComposeModule").Cast(results.Module); err != nil {
|
||||
return
|
||||
} else if err = expr.Assign(out, "module", tval); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
83
server/compose/automation/modules_handler.go
Normal file
83
server/compose/automation/modules_handler.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
)
|
||||
|
||||
type (
|
||||
moduleService interface {
|
||||
FindByID(ctx context.Context, namespaceID, moduleID uint64) (*types.Module, error)
|
||||
FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.Module, error)
|
||||
Find(ctx context.Context, filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
|
||||
|
||||
Create(ctx context.Context, module *types.Module) (*types.Module, error)
|
||||
Update(ctx context.Context, module *types.Module) (*types.Module, error)
|
||||
|
||||
DeleteByID(ctx context.Context, namespaceID uint64, moduleID uint64) error
|
||||
}
|
||||
|
||||
modulesHandler struct {
|
||||
reg modulesHandlerRegistry
|
||||
ns namespaceService
|
||||
mod moduleService
|
||||
}
|
||||
|
||||
moduleLookup interface {
|
||||
GetModule() (bool, uint64, string, *types.Module)
|
||||
}
|
||||
)
|
||||
|
||||
func ModulesHandler(reg modulesHandlerRegistry, ns namespaceService, mod moduleService) *modulesHandler {
|
||||
h := &modulesHandler{
|
||||
reg: reg,
|
||||
ns: ns,
|
||||
mod: mod,
|
||||
}
|
||||
|
||||
h.register()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h modulesHandler) lookup(ctx context.Context, args *modulesLookupArgs) (results *modulesLookupResults, err error) {
|
||||
results = &modulesLookupResults{}
|
||||
results.Module, err = lookupModule(ctx, h.ns, h.mod, args)
|
||||
return
|
||||
}
|
||||
|
||||
func getModuleID(ctx context.Context, nsSvc namespaceService, modSvc moduleService, args moduleLookup) (namespaceID uint64, moduleID uint64, err error) {
|
||||
namespaceID, err = getNamespaceID(ctx, nsSvc, args.(namespaceLookup))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var mod *types.Module
|
||||
if _, moduleID, _, _ = args.GetModule(); moduleID > 0 {
|
||||
return
|
||||
} else if mod, err = lookupModule(ctx, nsSvc, modSvc, args); err != nil {
|
||||
return
|
||||
} else {
|
||||
return namespaceID, mod.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
func lookupModule(ctx context.Context, nsSvc namespaceService, modSvc moduleService, args moduleLookup) (*types.Module, error) {
|
||||
namespaceID, err := getNamespaceID(ctx, nsSvc, args.(namespaceLookup))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load namespace: %w", err)
|
||||
}
|
||||
|
||||
_, ID, handle, module := args.GetModule()
|
||||
|
||||
switch {
|
||||
case module != nil:
|
||||
return module, nil
|
||||
case ID > 0:
|
||||
return modSvc.FindByID(ctx, namespaceID, ID)
|
||||
case len(handle) > 0:
|
||||
return modSvc.FindByHandle(ctx, namespaceID, handle)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("empty module lookup params")
|
||||
}
|
||||
39
server/compose/automation/modules_handler.yaml
Normal file
39
server/compose/automation/modules_handler.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
prefix: compose
|
||||
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
params:
|
||||
moduleLookup: &moduleLookup
|
||||
meta: { label: Module }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: Handle, }
|
||||
- { wf: ComposeModule, suffix: res }
|
||||
|
||||
namespaceLookup: &namespaceLookup
|
||||
meta: { label: Namespace }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: Handle, }
|
||||
- { wf: ComposeNamespace, suffix: res }
|
||||
|
||||
rvModule: &rvModule
|
||||
wf: ComposeModule
|
||||
|
||||
functions:
|
||||
lookup:
|
||||
meta:
|
||||
short: Compose module lookup
|
||||
description: Find specific module by ID or handle
|
||||
params:
|
||||
module: *moduleLookup
|
||||
namespace: *namespaceLookup
|
||||
labels:
|
||||
lookup: "step"
|
||||
module: "step,workflow"
|
||||
compose: "step,workflow"
|
||||
results:
|
||||
module: *rvModule
|
||||
134
server/compose/automation/namespaces_handler.gen.go
generated
Normal file
134
server/compose/automation/namespaces_handler.gen.go
generated
Normal file
@@ -0,0 +1,134 @@
|
||||
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/namespaces_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"
|
||||
)
|
||||
|
||||
var _ wfexec.ExecResponse
|
||||
|
||||
type (
|
||||
namespacesHandlerRegistry interface {
|
||||
AddFunctions(ff ...*atypes.Function)
|
||||
Type(ref string) expr.Type
|
||||
}
|
||||
)
|
||||
|
||||
func (h namespacesHandler) register() {
|
||||
h.reg.AddFunctions(
|
||||
h.Lookup(),
|
||||
)
|
||||
}
|
||||
|
||||
type (
|
||||
namespacesLookupArgs struct {
|
||||
hasNamespace bool
|
||||
Namespace interface{}
|
||||
namespaceID uint64
|
||||
namespaceHandle string
|
||||
namespaceRes *types.Namespace
|
||||
}
|
||||
|
||||
namespacesLookupResults struct {
|
||||
Namespace *types.Namespace
|
||||
}
|
||||
)
|
||||
|
||||
func (a namespacesLookupArgs) GetNamespace() (bool, uint64, string, *types.Namespace) {
|
||||
return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes
|
||||
}
|
||||
|
||||
// Lookup function Compose namespace lookup
|
||||
//
|
||||
// expects implementation of lookup function:
|
||||
//
|
||||
// func (h namespacesHandler) lookup(ctx context.Context, args *namespacesLookupArgs) (results *namespacesLookupResults, err error) {
|
||||
// return
|
||||
// }
|
||||
func (h namespacesHandler) Lookup() *atypes.Function {
|
||||
return &atypes.Function{
|
||||
Ref: "composeNamespacesLookup",
|
||||
Kind: "function",
|
||||
Labels: map[string]string{"compose": "step,workflow", "lookup": "step", "namespace": "step,workflow"},
|
||||
Meta: &atypes.FunctionMeta{
|
||||
Short: "Compose namespace lookup",
|
||||
Description: "Find specific namespace by ID or handle",
|
||||
},
|
||||
|
||||
Parameters: []*atypes.Param{
|
||||
{
|
||||
Name: "namespace",
|
||||
Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Results: []*atypes.Param{
|
||||
|
||||
{
|
||||
Name: "namespace",
|
||||
Types: []string{"ComposeNamespace"},
|
||||
},
|
||||
},
|
||||
|
||||
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
|
||||
var (
|
||||
args = &namespacesLookupArgs{
|
||||
hasNamespace: in.Has("namespace"),
|
||||
}
|
||||
)
|
||||
|
||||
if err = in.Decode(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Converting Namespace argument
|
||||
if args.hasNamespace {
|
||||
aux := expr.Must(expr.Select(in, "namespace"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.namespaceID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.namespaceHandle = aux.Get().(string)
|
||||
case h.reg.Type("ComposeNamespace").Type():
|
||||
args.namespaceRes = aux.Get().(*types.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
var results *namespacesLookupResults
|
||||
if results, err = h.lookup(ctx, args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = &expr.Vars{}
|
||||
|
||||
{
|
||||
// converting results.Namespace (*types.Namespace) to ComposeNamespace
|
||||
var (
|
||||
tval expr.TypedValue
|
||||
)
|
||||
|
||||
if tval, err = h.reg.Type("ComposeNamespace").Cast(results.Namespace); err != nil {
|
||||
return
|
||||
} else if err = expr.Assign(out, "namespace", tval); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
72
server/compose/automation/namespaces_handler.go
Normal file
72
server/compose/automation/namespaces_handler.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
)
|
||||
|
||||
type (
|
||||
namespaceService interface {
|
||||
FindByID(ctx context.Context, namespaceID uint64) (*types.Namespace, error)
|
||||
FindByHandle(ctx context.Context, handle string) (*types.Namespace, error)
|
||||
Find(ctx context.Context, filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error)
|
||||
|
||||
Create(ctx context.Context, namespace *types.Namespace) (*types.Namespace, error)
|
||||
Update(ctx context.Context, namespace *types.Namespace) (*types.Namespace, error)
|
||||
|
||||
DeleteByID(ctx context.Context, namespaceID uint64) error
|
||||
}
|
||||
|
||||
namespacesHandler struct {
|
||||
reg namespacesHandlerRegistry
|
||||
ns namespaceService
|
||||
}
|
||||
|
||||
namespaceLookup interface {
|
||||
GetNamespace() (bool, uint64, string, *types.Namespace)
|
||||
}
|
||||
)
|
||||
|
||||
func NamespacesHandler(reg namespacesHandlerRegistry, ns namespaceService) *namespacesHandler {
|
||||
h := &namespacesHandler{
|
||||
reg: reg,
|
||||
ns: ns,
|
||||
}
|
||||
|
||||
h.register()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h namespacesHandler) lookup(ctx context.Context, args *namespacesLookupArgs) (results *namespacesLookupResults, err error) {
|
||||
results = &namespacesLookupResults{}
|
||||
|
||||
results.Namespace, err = lookupNamespace(ctx, h.ns, args)
|
||||
return
|
||||
}
|
||||
|
||||
func getNamespaceID(ctx context.Context, svc namespaceService, args namespaceLookup) (uint64, error) {
|
||||
_, ID, _, _ := args.GetNamespace()
|
||||
if ID > 0 {
|
||||
return ID, nil
|
||||
} else if ns, err := lookupNamespace(ctx, svc, args); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return ns.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
func lookupNamespace(ctx context.Context, svc namespaceService, args namespaceLookup) (*types.Namespace, error) {
|
||||
_, ID, handle, namespace := args.GetNamespace()
|
||||
|
||||
switch {
|
||||
case namespace != nil:
|
||||
return namespace, nil
|
||||
case ID > 0:
|
||||
return svc.FindByID(ctx, ID)
|
||||
case len(handle) > 0:
|
||||
return svc.FindByHandle(ctx, handle)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("empty namespace lookup params")
|
||||
}
|
||||
30
server/compose/automation/namespaces_handler.yaml
Normal file
30
server/compose/automation/namespaces_handler.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
prefix: compose
|
||||
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
params:
|
||||
namespaceLookup: &namespaceLookup
|
||||
meta: { label: Namespace }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: Handle, }
|
||||
- { wf: ComposeNamespace, suffix: res }
|
||||
|
||||
rvNamespace: &rvNamespace
|
||||
wf: ComposeNamespace
|
||||
|
||||
functions:
|
||||
lookup:
|
||||
meta:
|
||||
short: Compose namespace lookup
|
||||
description: Find specific namespace by ID or handle
|
||||
labels:
|
||||
lookup: "step"
|
||||
namespace: "step,workflow"
|
||||
compose: "step,workflow"
|
||||
params:
|
||||
namespace: *namespaceLookup
|
||||
results:
|
||||
namespace: *rvNamespace
|
||||
214
server/compose/automation/notification_handler.gen.go
generated
Normal file
214
server/compose/automation/notification_handler.gen.go
generated
Normal file
@@ -0,0 +1,214 @@
|
||||
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/notification_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"
|
||||
)
|
||||
|
||||
var _ wfexec.ExecResponse
|
||||
|
||||
type (
|
||||
notificationHandlerRegistry interface {
|
||||
AddFunctions(ff ...*atypes.Function)
|
||||
Type(ref string) expr.Type
|
||||
}
|
||||
)
|
||||
|
||||
func (h notificationHandler) register() {
|
||||
h.reg.AddFunctions(
|
||||
h.SendRecord(),
|
||||
)
|
||||
}
|
||||
|
||||
type (
|
||||
notificationSendRecordArgs struct {
|
||||
hasRecipient bool
|
||||
Recipient interface{}
|
||||
recipientID uint64
|
||||
recipientHandle string
|
||||
recipientEmail string
|
||||
|
||||
hasTitle bool
|
||||
Title string
|
||||
|
||||
hasDescription bool
|
||||
Description string
|
||||
|
||||
hasModule bool
|
||||
Module interface{}
|
||||
moduleID uint64
|
||||
moduleHandle string
|
||||
|
||||
hasNamespace bool
|
||||
Namespace interface{}
|
||||
namespaceID uint64
|
||||
namespaceHandle string
|
||||
|
||||
hasRecord bool
|
||||
Record uint64
|
||||
|
||||
hasOpenMode bool
|
||||
OpenMode string
|
||||
|
||||
hasEdit bool
|
||||
Edit bool
|
||||
}
|
||||
)
|
||||
|
||||
func (a notificationSendRecordArgs) GetRecipient() (bool, uint64, string, string) {
|
||||
return a.hasRecipient, a.recipientID, a.recipientHandle, a.recipientEmail
|
||||
}
|
||||
|
||||
func (a notificationSendRecordArgs) GetModule() (bool, uint64, string) {
|
||||
return a.hasModule, a.moduleID, a.moduleHandle
|
||||
}
|
||||
|
||||
func (a notificationSendRecordArgs) GetNamespace() (bool, uint64, string) {
|
||||
return a.hasNamespace, a.namespaceID, a.namespaceHandle
|
||||
}
|
||||
|
||||
// SendRecord function Send record notification
|
||||
//
|
||||
// expects implementation of sendRecord function:
|
||||
//
|
||||
// func (h notificationHandler) sendRecord(ctx context.Context, args *notificationSendRecordArgs) (err error) {
|
||||
// return
|
||||
// }
|
||||
func (h notificationHandler) SendRecord() *atypes.Function {
|
||||
return &atypes.Function{
|
||||
Ref: "notificationSendRecord",
|
||||
Kind: "function",
|
||||
Labels: map[string]string(nil),
|
||||
Meta: &atypes.FunctionMeta{
|
||||
Short: "Send record notification",
|
||||
Description: "Sends a notification that links to a specific record",
|
||||
},
|
||||
|
||||
Parameters: []*atypes.Param{
|
||||
{
|
||||
Name: "recipient",
|
||||
Types: []string{"ID", "Handle", "String"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Recipient",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "title",
|
||||
Types: []string{"String"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Title",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "description",
|
||||
Types: []string{"String"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Description",
|
||||
Visual: map[string]interface{}{"input": map[string]interface{}{"type": "richtext"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "module",
|
||||
Types: []string{"ID", "Handle"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Module",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "namespace",
|
||||
Types: []string{"ID", "Handle"}, Required: true,
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "record",
|
||||
Types: []string{"ID"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Record",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "openMode",
|
||||
Types: []string{"String"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Open mode",
|
||||
Visual: map[string]interface{}{"input": map[string]interface{}{"default": "modal", "properties": map[string]interface{}{"options": []interface{}{map[string]interface{}{"text": "Open link in the same tab", "value": "sameTab"}, map[string]interface{}{"text": "Open link in a new tab", "value": "newTab"}, map[string]interface{}{"text": "Open in a modal", "value": "modal"}}}, "type": "select"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "edit",
|
||||
Types: []string{"Boolean"},
|
||||
Meta: &atypes.ParamMeta{
|
||||
Label: "Edit",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
|
||||
var (
|
||||
args = ¬ificationSendRecordArgs{
|
||||
hasRecipient: in.Has("recipient"),
|
||||
hasTitle: in.Has("title"),
|
||||
hasDescription: in.Has("description"),
|
||||
hasModule: in.Has("module"),
|
||||
hasNamespace: in.Has("namespace"),
|
||||
hasRecord: in.Has("record"),
|
||||
hasOpenMode: in.Has("openMode"),
|
||||
hasEdit: in.Has("edit"),
|
||||
}
|
||||
)
|
||||
|
||||
if err = in.Decode(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Converting Recipient argument
|
||||
if args.hasRecipient {
|
||||
aux := expr.Must(expr.Select(in, "recipient"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.recipientID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.recipientHandle = aux.Get().(string)
|
||||
case h.reg.Type("String").Type():
|
||||
args.recipientEmail = aux.Get().(string)
|
||||
}
|
||||
}
|
||||
|
||||
// Converting Module argument
|
||||
if args.hasModule {
|
||||
aux := expr.Must(expr.Select(in, "module"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.moduleID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.moduleHandle = aux.Get().(string)
|
||||
}
|
||||
}
|
||||
|
||||
// Converting Namespace argument
|
||||
if args.hasNamespace {
|
||||
aux := expr.Must(expr.Select(in, "namespace"))
|
||||
switch aux.Type() {
|
||||
case h.reg.Type("ID").Type():
|
||||
args.namespaceID = aux.Get().(uint64)
|
||||
case h.reg.Type("Handle").Type():
|
||||
args.namespaceHandle = aux.Get().(string)
|
||||
}
|
||||
}
|
||||
|
||||
return out, h.sendRecord(ctx, args)
|
||||
},
|
||||
}
|
||||
}
|
||||
131
server/compose/automation/notification_handler.go
Normal file
131
server/compose/automation/notification_handler.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
composeTypes "github.com/cortezaproject/corteza/server/compose/types"
|
||||
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
notificationHandler struct {
|
||||
reg notificationHandlerRegistry
|
||||
svc notificationService
|
||||
uSvc notificationUserService
|
||||
nSvc notificationNamespaceService
|
||||
mSvc notificationModuleService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
notificationService interface {
|
||||
Create(context.Context, *systemTypes.Notification) (*systemTypes.Notification, error)
|
||||
}
|
||||
|
||||
// Interface for user service that we need for recipient lookups
|
||||
notificationUserService interface {
|
||||
FindByID(ctx context.Context, userID uint64) (*systemTypes.User, error)
|
||||
FindByHandle(ctx context.Context, handle string) (*systemTypes.User, error)
|
||||
FindByEmail(ctx context.Context, email string) (*systemTypes.User, error)
|
||||
}
|
||||
|
||||
notificationNamespaceService interface {
|
||||
FindByHandle(ctx context.Context, handle string) (*composeTypes.Namespace, error)
|
||||
}
|
||||
|
||||
notificationModuleService interface {
|
||||
FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*composeTypes.Module, error)
|
||||
}
|
||||
)
|
||||
|
||||
// NotificationHandler initializes the notification handler
|
||||
func NotificationHandler(reg notificationHandlerRegistry, ntfSvc notificationService, uSvc notificationUserService, nSvc notificationNamespaceService, mSvc notificationModuleService, log *zap.Logger) *notificationHandler {
|
||||
h := ¬ificationHandler{
|
||||
reg: reg,
|
||||
svc: ntfSvc,
|
||||
uSvc: uSvc,
|
||||
nSvc: nSvc,
|
||||
mSvc: mSvc,
|
||||
logger: log.Named("notification"),
|
||||
}
|
||||
|
||||
h.register()
|
||||
return h
|
||||
}
|
||||
|
||||
// sendRecord creates and sends a record notification
|
||||
func (h notificationHandler) sendRecord(ctx context.Context, args *notificationSendRecordArgs) error {
|
||||
// Get the recipient user ID from the input parameter
|
||||
var recipientID uint64
|
||||
|
||||
// Check if we have a direct user object
|
||||
if args.recipientID > 0 {
|
||||
// Direct ID passed
|
||||
recipientID = args.recipientID
|
||||
} else {
|
||||
// Need to look up the user
|
||||
var user *systemTypes.User
|
||||
var err error
|
||||
|
||||
if args.recipientHandle != "" {
|
||||
user, err = h.uSvc.FindByHandle(ctx, args.recipientHandle)
|
||||
} else if args.recipientEmail != "" {
|
||||
user, err = h.uSvc.FindByEmail(ctx, args.recipientEmail)
|
||||
} else {
|
||||
return fmt.Errorf("invalid recipient: unable to determine user ID")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return fmt.Errorf("recipient not found")
|
||||
}
|
||||
|
||||
recipientID = user.ID
|
||||
}
|
||||
|
||||
if args.namespaceHandle != "" {
|
||||
namespace, err := h.nSvc.FindByHandle(ctx, args.namespaceHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args.namespaceID = namespace.ID
|
||||
}
|
||||
|
||||
if args.moduleHandle != "" {
|
||||
module, err := h.mSvc.FindByHandle(ctx, args.namespaceID, args.moduleHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args.moduleID = module.ID
|
||||
}
|
||||
|
||||
// Create notification config for a record notification
|
||||
config := systemTypes.NotificationConfig{
|
||||
Record: systemTypes.RecordNotificationConfig{
|
||||
Title: args.Title,
|
||||
Description: args.Description,
|
||||
ModuleID: args.moduleID,
|
||||
NamespaceID: args.namespaceID,
|
||||
RecordID: args.Record,
|
||||
OpenMode: systemTypes.OpenModeType(args.OpenMode),
|
||||
Edit: args.Edit,
|
||||
},
|
||||
}
|
||||
|
||||
// Create a record notification
|
||||
ntf := &systemTypes.Notification{
|
||||
Kind: systemTypes.NotificationKindRecord,
|
||||
Config: config,
|
||||
Recipient: recipientID,
|
||||
}
|
||||
|
||||
// Create the notification
|
||||
_, err := h.svc.Create(ctx, ntf)
|
||||
return err
|
||||
}
|
||||
72
server/compose/automation/notification_handler.yaml
Normal file
72
server/compose/automation/notification_handler.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
name: notification
|
||||
|
||||
snippets:
|
||||
lookupUser: &lookupUser
|
||||
meta: { label: Recipient }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID }
|
||||
- { wf: Handle }
|
||||
- { wf: String, suffix: email }
|
||||
|
||||
functions:
|
||||
sendRecord:
|
||||
meta:
|
||||
short: Send record notification
|
||||
description: Sends a notification that links to a specific record
|
||||
params:
|
||||
recipient: *lookupUser
|
||||
title:
|
||||
meta: { label: Title }
|
||||
types:
|
||||
- { wf: String }
|
||||
required: true
|
||||
description: The title of the record notification
|
||||
description:
|
||||
types:
|
||||
- { wf: String }
|
||||
required: false
|
||||
description: Optional description for the record notification
|
||||
meta: { label: Description, visual: { input: { type: richtext } } }
|
||||
module:
|
||||
meta: { label: Module }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID }
|
||||
- { wf: Handle }
|
||||
description: The module ID of the record
|
||||
namespace:
|
||||
meta: { label: Namespace }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID }
|
||||
- { wf: Handle }
|
||||
description: The namespace ID of the record
|
||||
record:
|
||||
meta: { label: Record }
|
||||
required: false
|
||||
types:
|
||||
- { wf: ID }
|
||||
description: The record ID to link to
|
||||
openMode:
|
||||
types:
|
||||
- { wf: String }
|
||||
required: false
|
||||
description: How to open the record - "modal", "newTab", or "sameTab" (default)
|
||||
meta:
|
||||
label: Open mode
|
||||
visual:
|
||||
input:
|
||||
type: select
|
||||
properties:
|
||||
options:
|
||||
- { value: 'sameTab', text: 'Open link in the same tab' }
|
||||
- { value: 'newTab', text: 'Open link in a new tab' }
|
||||
- { value: 'modal', text: 'Open in a modal' }
|
||||
default: 'modal'
|
||||
edit:
|
||||
meta: { label: Edit }
|
||||
types:
|
||||
- { wf: Boolean }
|
||||
required: false
|
||||
description: Whether to open the record in edit mode (default is false)
|
||||
1846
server/compose/automation/records_handler.gen.go
generated
Normal file
1846
server/compose/automation/records_handler.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
460
server/compose/automation/records_handler.go
Normal file
460
server/compose/automation/records_handler.go
Normal file
@@ -0,0 +1,460 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
. "github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/wfexec"
|
||||
)
|
||||
|
||||
type (
|
||||
recordService interface {
|
||||
FindByID(ctx context.Context, namespaceID, moduleID, recordID uint64) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Find(ctx context.Context, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error)
|
||||
|
||||
Create(ctx context.Context, record *types.Record) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Update(ctx context.Context, record *types.Record) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Bulk(ctx context.Context, skipFailed bool, oo ...*types.RecordBulkOperation) ([]types.RecordBulkOperationResult, error)
|
||||
Report(ctx context.Context, namespaceID, moduleID uint64, metrics, dimensions, filter string) (out any, err error)
|
||||
|
||||
Validate(ctx context.Context, rec *types.Record) error
|
||||
|
||||
DeleteByID(ctx context.Context, namespaceID, moduleID uint64, recordID ...uint64) error
|
||||
}
|
||||
|
||||
recordsHandler struct {
|
||||
reg recordsHandlerRegistry
|
||||
ns namespaceService
|
||||
mod moduleService
|
||||
rec recordService
|
||||
}
|
||||
|
||||
recordSetIterator struct {
|
||||
// Item buffer, current item pointer, and total items traversed
|
||||
ptr uint
|
||||
buffer types.RecordSet
|
||||
total uint
|
||||
|
||||
// When filter limit is set, this constraints it
|
||||
iterLimit uint
|
||||
useIterLimit bool
|
||||
|
||||
// Item loader for additional chunks
|
||||
filter types.RecordFilter
|
||||
loader func() error
|
||||
}
|
||||
|
||||
recordLookup interface {
|
||||
GetRecord() (bool, uint64, *types.Record)
|
||||
}
|
||||
)
|
||||
|
||||
func RecordsHandler(reg recordsHandlerRegistry, ns namespaceService, mod moduleService, rec recordService) *recordsHandler {
|
||||
h := &recordsHandler{
|
||||
reg: reg,
|
||||
ns: ns,
|
||||
mod: mod,
|
||||
rec: rec,
|
||||
}
|
||||
|
||||
h.register()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h recordsHandler) lookup(ctx context.Context, args *recordsLookupArgs) (results *recordsLookupResults, err error) {
|
||||
results = &recordsLookupResults{}
|
||||
results.Record, err = h.lookupRecord(ctx, args)
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) search(ctx context.Context, args *recordsSearchArgs) (results *recordsSearchResults, err error) {
|
||||
results = &recordsSearchResults{}
|
||||
|
||||
var (
|
||||
f = types.RecordFilter{
|
||||
Query: args.Query,
|
||||
Meta: args.Meta,
|
||||
Deleted: filter.State(args.Deleted),
|
||||
}
|
||||
)
|
||||
|
||||
if ns, mod, err := h.loadCombo(ctx, args); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
f.ModuleID = mod.ID
|
||||
f.NamespaceID = ns.ID
|
||||
}
|
||||
|
||||
if args.hasSort {
|
||||
if err = f.Sort.Set(args.Sort); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if args.hasPageCursor {
|
||||
if args.PageCursor != "" {
|
||||
f.PageCursor = &filter.PagingCursor{}
|
||||
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.IncTotal = args.IncTotal
|
||||
f.IncPageNavigation = args.IncPageNavigation
|
||||
|
||||
if args.hasMeta {
|
||||
f.Meta = args.Meta
|
||||
}
|
||||
|
||||
if args.hasLimit {
|
||||
f.Limit = uint(args.Limit)
|
||||
}
|
||||
|
||||
var auxf types.RecordFilter
|
||||
results.Records, auxf, err = h.rec.Find(ctx, f)
|
||||
results.Total = uint64(auxf.Total)
|
||||
if auxf.NextPage != nil {
|
||||
results.NextPage = auxf.NextPage.Encode()
|
||||
}
|
||||
if auxf.PrevPage != nil {
|
||||
results.PrevPage = auxf.PrevPage.Encode()
|
||||
}
|
||||
|
||||
// Always assure at least empty cursor when requesting page nav.
|
||||
if auxf.PageNavigation == nil && auxf.IncPageNavigation {
|
||||
results.PageNavigation = append(results.PageNavigation, nil)
|
||||
}
|
||||
|
||||
for _, pn := range auxf.PageNavigation {
|
||||
if pn == nil {
|
||||
continue
|
||||
}
|
||||
// first page is null -- no cursor
|
||||
if pn.Cursor == nil {
|
||||
results.PageNavigation = append(results.PageNavigation, nil)
|
||||
} else {
|
||||
results.PageNavigation = append(results.PageNavigation, Must(NewString(pn.Cursor.Encode())))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) first(ctx context.Context, args *recordsFirstArgs) (results *recordsFirstResults, err error) {
|
||||
r, err := h.fetchEdge(ctx, args, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &recordsFirstResults{
|
||||
Record: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) last(ctx context.Context, args *recordsLastArgs) (results *recordsLastResults, err error) {
|
||||
r, err := h.fetchEdge(ctx, args, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &recordsLastResults{
|
||||
Record: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) each(ctx context.Context, args *recordsEachArgs) (out wfexec.IteratorHandler, err error) {
|
||||
var (
|
||||
i = &recordSetIterator{}
|
||||
f = types.RecordFilter{
|
||||
Query: args.Query,
|
||||
Meta: args.Meta,
|
||||
Deleted: filter.State(args.Deleted),
|
||||
}
|
||||
)
|
||||
|
||||
if ns, mod, err := h.loadCombo(ctx, args); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
f.ModuleID = mod.ID
|
||||
f.NamespaceID = ns.ID
|
||||
}
|
||||
|
||||
if args.hasSort {
|
||||
if err = f.Sort.Set(args.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if args.hasPageCursor {
|
||||
if args.PageCursor != "" {
|
||||
f.NextPage = &filter.PagingCursor{}
|
||||
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.IncTotal = args.IncTotal
|
||||
f.IncPageNavigation = args.IncPageNavigation
|
||||
|
||||
if args.hasMeta {
|
||||
f.Meta = args.Meta
|
||||
}
|
||||
|
||||
if args.hasLimit {
|
||||
i.useIterLimit = true
|
||||
i.iterLimit = uint(args.Limit)
|
||||
|
||||
f.Limit = uint(args.Limit)
|
||||
|
||||
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
|
||||
f.Limit = wfexec.MaxIteratorBufferSize
|
||||
}
|
||||
i.iterLimit = uint(args.Limit)
|
||||
} else {
|
||||
f.Limit = wfexec.MaxIteratorBufferSize
|
||||
}
|
||||
|
||||
i.filter = f
|
||||
i.loader = func() (err error) {
|
||||
// Edgecase
|
||||
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.total += i.ptr
|
||||
i.ptr = 0
|
||||
|
||||
i.filter.PageCursor = i.filter.NextPage
|
||||
i.filter.NextPage = nil
|
||||
i.buffer, i.filter, err = h.rec.Find(ctx, i.filter)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Initial load
|
||||
return i, i.loader()
|
||||
}
|
||||
|
||||
func (h recordsHandler) validate(ctx context.Context, args *recordsValidateArgs) (*recordsValidateResults, error) {
|
||||
results := &recordsValidateResults{Valid: true}
|
||||
if err := h.rec.Validate(ctx, args.Record); err != nil {
|
||||
results.Valid = false
|
||||
|
||||
//if rves, is := err.(*types.RecordValueErrorSet); is {
|
||||
// results.Errors = rves
|
||||
//} else {
|
||||
// return nil, err
|
||||
//}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) new(ctx context.Context, args *recordsNewArgs) (*recordsNewResults, error) {
|
||||
results := &recordsNewResults{}
|
||||
namespace, module, err := h.loadCombo(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Record = &types.Record{
|
||||
ModuleID: module.ID,
|
||||
NamespaceID: namespace.ID,
|
||||
}
|
||||
|
||||
results.Record.SetModule(module)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) create(ctx context.Context, args *recordsCreateArgs) (results *recordsCreateResults, err error) {
|
||||
results = &recordsCreateResults{}
|
||||
results.Record, err = wrapRecordValueErrorSet(h.rec.Create(ctx, args.Record))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) update(ctx context.Context, args *recordsUpdateArgs) (results *recordsUpdateResults, err error) {
|
||||
results = &recordsUpdateResults{}
|
||||
results.Record, err = wrapRecordValueErrorSet(h.rec.Update(ctx, args.Record))
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) delete(ctx context.Context, args *recordsDeleteArgs) error {
|
||||
if rec, err := h.lookupRecord(ctx, args); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return h.rec.DeleteByID(ctx, rec.NamespaceID, rec.ModuleID, rec.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h recordsHandler) report(ctx context.Context, args *recordsReportArgs) (*recordsReportResults, error) {
|
||||
r := &recordsReportResults{}
|
||||
|
||||
ns, mod, err := h.loadCombo(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Report, err = h.rec.Report(ctx, ns.ID, mod.ID, args.Metrics, args.Dimensons, args.Filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) lookupRecord(ctx context.Context, args recordLookup) (record *types.Record, err error) {
|
||||
var (
|
||||
namespace *types.Namespace
|
||||
module *types.Module
|
||||
recordID uint64
|
||||
)
|
||||
|
||||
if _, recordID, record = args.GetRecord(); record != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if namespace, module, err = h.loadCombo(ctx, args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
record, _, err = h.rec.FindByID(ctx, namespace.ID, module.ID, recordID)
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) loadCombo(ctx context.Context, args interface{}) (namespace *types.Namespace, module *types.Module, err error) {
|
||||
if lkp, is := args.(namespaceLookup); is {
|
||||
if namespace, err = lookupNamespace(ctx, h.ns, lkp); err != nil {
|
||||
err = fmt.Errorf("could not load namespace: %w", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("could not extract namespace lookup arguments")
|
||||
return
|
||||
}
|
||||
|
||||
if lkp, is := args.(moduleLookup); is {
|
||||
if module, err = lookupModule(ctx, h.ns, h.mod, lkp); err != nil {
|
||||
err = fmt.Errorf("could not load module: %w", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("could not extract module lookup arguments")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) fetchEdge(ctx context.Context, args interface{}, first bool) (*types.Record, error) {
|
||||
f := types.RecordFilter{}
|
||||
|
||||
if first {
|
||||
f.Sort.Set("createdAt ASC")
|
||||
} else {
|
||||
f.Sort.Set("createdAt DESC")
|
||||
}
|
||||
|
||||
f.Limit = 1
|
||||
|
||||
if ns, mod, err := h.loadCombo(ctx, args); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
f.ModuleID = mod.ID
|
||||
f.NamespaceID = ns.ID
|
||||
}
|
||||
|
||||
rr, _, err := h.rec.Find(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rr) == 0 {
|
||||
return nil, fmt.Errorf("could not fetch records: no records found")
|
||||
}
|
||||
return rr[0], nil
|
||||
}
|
||||
|
||||
func (i *recordSetIterator) More(context.Context, *Vars) (bool, error) {
|
||||
return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil), nil
|
||||
}
|
||||
|
||||
func (i *recordSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
|
||||
|
||||
func (i *recordSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
|
||||
if len(i.buffer)-int(i.ptr) <= 0 {
|
||||
if err = i.loader(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
out = &Vars{}
|
||||
out.Set("record", Must(NewComposeRecord(i.buffer[i.ptr])))
|
||||
out.Set("index", Must(NewInteger(i.total+i.ptr)))
|
||||
out.Set("total", Must(NewInteger(i.filter.Total)))
|
||||
|
||||
i.ptr++
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func wrapRecordValueErrorSet(rr *types.Record, res *types.RecordValueErrorSet, ee error) (r *types.Record, err error) {
|
||||
var (
|
||||
ss string
|
||||
j []byte
|
||||
e error
|
||||
)
|
||||
|
||||
r = rr
|
||||
err = ee
|
||||
|
||||
if res = types.IsRecordValueErrorSet(err); res == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !res.Safe() {
|
||||
return rr, err
|
||||
}
|
||||
|
||||
for _, vv := range res.Set {
|
||||
if j, e = json.Marshal(vv.Meta); e != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ss = fmt.Sprintf("%s, %s (%s)", ss, strings.ToLower(vv.Message), j)
|
||||
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("^,\\s")
|
||||
ss = re.ReplaceAllString(ss, "")
|
||||
|
||||
return rr, errors.New(ss)
|
||||
}
|
||||
|
||||
func (h recordsHandler) clone(ctx context.Context, args *recordsCloneArgs) (*recordsCloneResults, error) {
|
||||
results := &recordsCloneResults{}
|
||||
|
||||
rec, err := h.lookupRecord(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Record = rec.Clone()
|
||||
|
||||
results.Record.ID = 0
|
||||
results.Record.CreatedAt = time.Now()
|
||||
results.Record.UpdatedAt = nil
|
||||
results.Record.DeletedAt = nil
|
||||
results.Record.CreatedBy = 0
|
||||
results.Record.UpdatedBy = 0
|
||||
results.Record.DeletedBy = 0
|
||||
|
||||
return results, nil
|
||||
}
|
||||
300
server/compose/automation/records_handler.yaml
Normal file
300
server/compose/automation/records_handler.yaml
Normal file
@@ -0,0 +1,300 @@
|
||||
prefix: compose
|
||||
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
params:
|
||||
moduleLookup: &moduleLookup
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: Handle, }
|
||||
- { wf: ComposeModule, suffix: res }
|
||||
meta:
|
||||
label: Module
|
||||
description: |-
|
||||
Even with unique record ID across all modules, module needs to be known
|
||||
before doing any record operations. Mainly because records of different
|
||||
modules can be located in different stores.
|
||||
|
||||
namespaceLookup: &namespaceLookup
|
||||
meta: { label: Namespace }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: Handle, }
|
||||
- { wf: ComposeNamespace, suffix: res }
|
||||
|
||||
recordLookup: &recordLookup
|
||||
meta: { label: Record }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ID, }
|
||||
- { wf: ComposeRecord, suffix: res }
|
||||
|
||||
record: &record
|
||||
meta: { label: Record }
|
||||
required: true
|
||||
types:
|
||||
- { wf: ComposeRecord, }
|
||||
|
||||
rvRecord: &rvRecord
|
||||
wf: ComposeRecord
|
||||
|
||||
rvTotal: &rvTotal
|
||||
wf: UnsignedInteger
|
||||
meta:
|
||||
label: Total records found
|
||||
description: |-
|
||||
Total items that satisfy given conditions.
|
||||
|
||||
Needs to be explicitly requested with incTotal argument
|
||||
|
||||
rvIndex: &rvIndex
|
||||
wf: UnsignedInteger
|
||||
meta:
|
||||
label: Iteration counter
|
||||
description: |-
|
||||
Zero-based number iteration counter
|
||||
|
||||
rvPageCursor: &rvPageCursor
|
||||
wf: String
|
||||
|
||||
rvPageNavigation: &rvPageNavigation
|
||||
wf: Array
|
||||
|
||||
filterParams: &filterParams
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
query:
|
||||
meta: { label: Query }
|
||||
types:
|
||||
- { wf: String }
|
||||
meta:
|
||||
meta: { label: Meta }
|
||||
types:
|
||||
- { wf: Meta }
|
||||
deleted:
|
||||
meta: { label: Deleted }
|
||||
types:
|
||||
- { wf: UnsignedInteger }
|
||||
sort:
|
||||
meta: { label: Sort }
|
||||
types:
|
||||
- { wf: String }
|
||||
limit:
|
||||
meta: { label: Limit }
|
||||
types:
|
||||
- { wf: UnsignedInteger }
|
||||
incTotal:
|
||||
meta: { label: Include total }
|
||||
types:
|
||||
- { wf: Boolean }
|
||||
incPageNavigation:
|
||||
meta: { label: Include page navigation }
|
||||
types:
|
||||
- { wf: Boolean }
|
||||
pageCursor:
|
||||
meta: { label: Page cursor }
|
||||
types:
|
||||
- { wf: String }
|
||||
|
||||
edgeParams: &edgeParams
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
|
||||
labels: &labels
|
||||
record: "step,workflow"
|
||||
compose: "step,workflow"
|
||||
|
||||
functions:
|
||||
lookup:
|
||||
meta:
|
||||
short: Compose record lookup
|
||||
description: Find specific record by ID
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
record: *recordLookup
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
search:
|
||||
meta:
|
||||
short: Compose records search
|
||||
labels:
|
||||
<<: *labels
|
||||
params: *filterParams
|
||||
results:
|
||||
records:
|
||||
<<: *rvRecord
|
||||
isArray: true
|
||||
total: *rvTotal
|
||||
nextPage: *rvPageCursor
|
||||
prevPage: *rvPageCursor
|
||||
pageNavigation: *rvPageNavigation
|
||||
|
||||
first:
|
||||
meta:
|
||||
short: Compose record lookup (oldest)
|
||||
labels:
|
||||
<<: *labels
|
||||
params: *edgeParams
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
last:
|
||||
meta:
|
||||
short: Compose record lookup (newest)
|
||||
labels:
|
||||
<<: *labels
|
||||
params: *edgeParams
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
each:
|
||||
kind: iterator
|
||||
meta:
|
||||
short: Compose records
|
||||
labels:
|
||||
<<: *labels
|
||||
params: *filterParams
|
||||
results:
|
||||
record: *rvRecord
|
||||
index: *rvIndex
|
||||
total: *rvTotal
|
||||
|
||||
new:
|
||||
meta:
|
||||
short: Compose record maker
|
||||
description: Creates new compose record instance without saving it
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
validate:
|
||||
meta:
|
||||
short: Compose record validator
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
record: *record
|
||||
results:
|
||||
valid:
|
||||
wf: Boolean
|
||||
meta:
|
||||
label: Set to true when record is valid
|
||||
|
||||
create:
|
||||
meta:
|
||||
short: Compose record create
|
||||
labels:
|
||||
<<: *labels
|
||||
create: "step"
|
||||
params:
|
||||
record:
|
||||
<<: *record
|
||||
meta: { label: Record }
|
||||
required: true
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
update:
|
||||
meta:
|
||||
short: Compose record update
|
||||
labels:
|
||||
<<: *labels
|
||||
update: "step"
|
||||
params:
|
||||
record:
|
||||
<<: *record
|
||||
meta: { label: Record }
|
||||
required: true
|
||||
results:
|
||||
record: *rvRecord
|
||||
|
||||
delete:
|
||||
meta:
|
||||
short: Compose record delete
|
||||
labels:
|
||||
<<: *labels
|
||||
delete: "step"
|
||||
params:
|
||||
module:
|
||||
<<: *moduleLookup
|
||||
required: false
|
||||
namespace:
|
||||
<<: *namespaceLookup
|
||||
meta: { label: Namespace }
|
||||
required: false
|
||||
record: *recordLookup
|
||||
|
||||
# undelete:
|
||||
# meta:
|
||||
# short: Recovers deleted record by ID
|
||||
# params:
|
||||
# record: *recordLookup
|
||||
|
||||
|
||||
|
||||
report:
|
||||
meta:
|
||||
short: Report
|
||||
description: Compose records report
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
metrics:
|
||||
required: true
|
||||
types: [ { wf: String } ]
|
||||
meta:
|
||||
label: Metrics for records report
|
||||
description: |-
|
||||
List of comma delimited expressions with aggregation functions (count, sum, min, avg)
|
||||
dimensons:
|
||||
required: true
|
||||
types: [ { wf: String } ]
|
||||
meta:
|
||||
label: Dimensons for records report
|
||||
description: |-
|
||||
List of comma delimited dimension (i.e.: group by) expressions or fields
|
||||
filter:
|
||||
required: true
|
||||
types: [ { wf: String } ]
|
||||
meta:
|
||||
label: Filter for records report
|
||||
description: |-
|
||||
Filter in CortezaQL format
|
||||
results:
|
||||
report:
|
||||
wf: Any
|
||||
meta:
|
||||
label: Complex structure holding complete records report
|
||||
description: |-
|
||||
Example of a result value
|
||||
[]map[string]interface{}{
|
||||
{"count": 3, "dimension_0": 1, "metric_0": 3},
|
||||
{"count": 2, "dimension_0": 2, "metric_0": 5},
|
||||
{"count": 1, "dimension_0": nil, "metric_0": nil},
|
||||
}
|
||||
|
||||
clone:
|
||||
meta:
|
||||
short: Compose record cloner
|
||||
description: Creates a copy of an existing record
|
||||
labels:
|
||||
<<: *labels
|
||||
params:
|
||||
namespace: *namespaceLookup
|
||||
module: *moduleLookup
|
||||
record: *recordLookup
|
||||
results:
|
||||
record: *rvRecord
|
||||
146
server/compose/chart.cue
Normal file
146
server/compose/chart.cue
Normal file
@@ -0,0 +1,146 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
chart: {
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
]
|
||||
|
||||
model: {
|
||||
defaultSetter: true
|
||||
|
||||
ident: "compose_chart"
|
||||
attributes: {
|
||||
id: schema.IdField & {
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyEncode: "chartID"
|
||||
}
|
||||
}
|
||||
}
|
||||
handle: schema.HandleField
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["namespace", "namespace_id", "ns"]
|
||||
}
|
||||
}
|
||||
}
|
||||
name: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
config: {
|
||||
goType: "types.ChartConfig"
|
||||
dal: {}
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
customDecoder: true
|
||||
customEncoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"namespace": { attribute: "namespace_id" },
|
||||
"unique_handle": {
|
||||
fields: [{ attribute: "handle", modifiers: ["LOWERCASE"] }, { attribute: "namespace_id" }]
|
||||
predicate: "handle != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
chart_id: { goType: "[]uint64", ident: "chartID", storeIdent: "id" }
|
||||
namespace_id: { goType: "uint64", ident: "namespaceID", storeIdent: "rel_namespace" }
|
||||
handle: { goType: "string" }
|
||||
name: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
query: ["handle", "name"]
|
||||
byValue: ["handle", "chart_id", "namespace_id"]
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Handle"
|
||||
identKeyAlias: ["charts", "chrt"]
|
||||
}
|
||||
store: {
|
||||
extendedRefDecoder: true
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
extended: true
|
||||
|
||||
keys: {
|
||||
reportsYaxisLabel: {
|
||||
path: ["yAxis", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
reportsMetricLabel: {
|
||||
path: ["metrics", {part: "metricID", var: true}, "label"]
|
||||
customHandler: true
|
||||
}
|
||||
reportsDimensionStepLabel: {
|
||||
path: ["dimensions", {part: "dimensionID", var: true}, "meta", "steps", {part: "stepID", var: true}, "label"]
|
||||
customHandler: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composeChart"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose chart by ID
|
||||
|
||||
It returns compose chart even if deleted
|
||||
"""
|
||||
}, {
|
||||
fields: ["namespace_id", "handle"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for compose chart by handle (case-insensitive)
|
||||
"""
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
25
server/compose/commands/commands.go
Normal file
25
server/compose/commands/commands.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type (
|
||||
serviceInitializer interface {
|
||||
InitServices(ctx context.Context) error
|
||||
}
|
||||
)
|
||||
|
||||
func Base(ctx context.Context, app serviceInitializer) (cmd *cobra.Command) {
|
||||
cmd = &cobra.Command{
|
||||
Use: "compose",
|
||||
Aliases: []string{"cmp"},
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
Records(ctx, app),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
128
server/compose/commands/records.go
Normal file
128
server/compose/commands/records.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func Records(ctx context.Context, app serviceInitializer) (cmd *cobra.Command) {
|
||||
cmd = &cobra.Command{
|
||||
Use: "records",
|
||||
Aliases: []string{"rec", "record"},
|
||||
}
|
||||
|
||||
cmd.AddCommand(RecordsSynthetic(ctx, app))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func RecordsSynthetic(ctx context.Context, app serviceInitializer) *cobra.Command {
|
||||
var (
|
||||
total uint
|
||||
faker = gofakeit.NewCrypto()
|
||||
|
||||
namespace string
|
||||
module string
|
||||
|
||||
synth = &cobra.Command{
|
||||
Use: "synthetic",
|
||||
Aliases: []string{"synth"},
|
||||
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
if err = app.InitServices(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = service.DefaultModule.ReloadDALModels(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
gen = &cobra.Command{
|
||||
Use: "generate",
|
||||
Aliases: []string{"gen"},
|
||||
Short: "Create synthetic records",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(namespace) == 0 || len(module) == 0 {
|
||||
cli.HandleError(fmt.Errorf("specifiy ID and handle for both, module and namespace"))
|
||||
}
|
||||
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser())
|
||||
ns, mod, err := resolveModule(ctx, service.DefaultNamespace, service.DefaultModule, namespace, module)
|
||||
cli.HandleError(err)
|
||||
|
||||
_ = faker
|
||||
_ = ns
|
||||
_ = mod
|
||||
_ = err
|
||||
cmd.Printf("Generating %d compose records (module: %s) ...", total, mod.Name)
|
||||
bm := time.Now()
|
||||
|
||||
cli.HandleError(service.DefaultRecord.CreateSynthetic(ctx, faker, mod, total))
|
||||
|
||||
cmd.Printf("done in %s", time.Since(bm).Round(time.Millisecond))
|
||||
cmd.Println()
|
||||
},
|
||||
}
|
||||
|
||||
rem = &cobra.Command{
|
||||
Use: "remove",
|
||||
Aliases: []string{"rm", "d", "delete", "del"},
|
||||
Short: "Remove synthetic records",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(namespace) == 0 || len(module) == 0 {
|
||||
cli.HandleError(fmt.Errorf("specifiy ID and handle for both, module and namespace"))
|
||||
}
|
||||
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser())
|
||||
ns, mod, err := resolveModule(ctx, service.DefaultNamespace, service.DefaultModule, namespace, module)
|
||||
cli.HandleError(err)
|
||||
|
||||
_ = ns
|
||||
_ = mod
|
||||
_ = err
|
||||
|
||||
cmd.Printf("Removing all synthetic compose records (module: %s) ...", mod.Name)
|
||||
bm := time.Now()
|
||||
|
||||
cli.HandleError(service.DefaultRecord.RemoveSynthetic(ctx, mod))
|
||||
|
||||
cmd.Printf("done in %s", time.Since(bm).Round(time.Millisecond))
|
||||
cmd.Println()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
for _, cmd := range []*cobra.Command{gen, rem} {
|
||||
cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "namespace IS or handle for recode generation")
|
||||
cmd.Flags().StringVarP(&module, "module", "m", "", "module IS or handle for recode generation")
|
||||
}
|
||||
|
||||
gen.Flags().UintVarP(&total, "total", "t", 1, "Number of synthetic records generated")
|
||||
|
||||
synth.AddCommand(gen, rem)
|
||||
return synth
|
||||
}
|
||||
|
||||
func resolveModule(ctx context.Context, nsSvc service.NamespaceService, modSvc service.ModuleService, nsIdent, modIdent string) (ns *types.Namespace, mod *types.Module, err error) {
|
||||
if ns, err = nsSvc.FindByAny(ctx, nsIdent); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod, err = modSvc.FindByAny(ctx, ns.ID, modIdent)
|
||||
return
|
||||
}
|
||||
30
server/compose/component.cue
Normal file
30
server/compose/component.cue
Normal file
@@ -0,0 +1,30 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
component: schema.#component & {
|
||||
handle: "compose"
|
||||
|
||||
resources: {
|
||||
"attachment": attachment
|
||||
"chart": chart
|
||||
"module": module
|
||||
"module-field": moduleField
|
||||
"namespace": namespace
|
||||
"page": page
|
||||
"page-layout": pageLayout
|
||||
"record": record
|
||||
"record-revision": record_revision
|
||||
}
|
||||
|
||||
rbac: operations: {
|
||||
"settings.read": description: "Read settings"
|
||||
"settings.manage": description: "Manage settings"
|
||||
"namespace.create": description: "Create namespace"
|
||||
"namespaces.search": description: "List, search or filter namespaces"
|
||||
"resource-translations.manage": description: "List, search, create, or update resource translations"
|
||||
}
|
||||
}
|
||||
|
||||
630
server/compose/dalutils/records.go
Normal file
630
server/compose/dalutils/records.go
Normal file
@@ -0,0 +1,630 @@
|
||||
package dalutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
creator interface {
|
||||
Create(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, vv ...dal.ValueGetter) error
|
||||
}
|
||||
|
||||
updater interface {
|
||||
Update(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, rr ...dal.ValueGetter) (err error)
|
||||
}
|
||||
|
||||
searcher interface {
|
||||
Search(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (dal.Iterator, error)
|
||||
}
|
||||
|
||||
lookuper interface {
|
||||
Lookup(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, lookup dal.ValueGetter, dst dal.ValueSetter) (err error)
|
||||
}
|
||||
|
||||
deleter interface {
|
||||
Delete(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, pkv ...dal.ValueGetter) (err error)
|
||||
}
|
||||
|
||||
counter interface {
|
||||
Count(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (uint, error)
|
||||
}
|
||||
)
|
||||
|
||||
// ComposeRecordsList iterates over results and collects all available records
|
||||
func ComposeRecordsList(ctx context.Context, s searcher, mod *types.Module, filter types.RecordFilter) (set types.RecordSet, outFilter types.RecordFilter, err error) {
|
||||
iter, err := prepIterator(ctx, s, mod, filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
set, _, outFilter, err = drainIterator(ctx, iter, mod, filter)
|
||||
return
|
||||
}
|
||||
|
||||
func ComposeRecordsListN(ctx context.Context, s searcher, mod *types.Module, filter types.RecordFilter) (set types.RecordSet, summaries map[string]types.RecordSummary, outFilter types.RecordFilter, err error) {
|
||||
iter, err := prepIterator(ctx, s, mod, filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
set, summaries, outFilter, err = drainIterator(ctx, iter, mod, filter)
|
||||
return
|
||||
}
|
||||
|
||||
func ComposeRecordsIterator(ctx context.Context, s searcher, mod *types.Module, filter types.RecordFilter) (iter dal.Iterator, outFilter types.RecordFilter, err error) {
|
||||
iter, err = prepIterator(ctx, s, mod, filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
outFilter = filter
|
||||
outFilter.Paging = *filter.Paging.Clone()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ComposeRecordsFind(ctx context.Context, l lookuper, mod *types.Module, recordID uint64) (out *types.Record, err error) {
|
||||
out = prepareRecordTarget(mod)
|
||||
|
||||
err = l.Lookup(ctx, mod.ModelRef(), recLookupOperations(mod), dal.PKValues{"id": recordID}, out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ComposeRecordsCount(ctx context.Context, c counter, mod *types.Module, filter types.RecordFilter) (cnt uint, err error) {
|
||||
constraints := map[string][]interface{}{
|
||||
"namespaceID": {mod.NamespaceID},
|
||||
}
|
||||
|
||||
if filter.ModuleID != 0 {
|
||||
constraints["moduleID"] = []interface{}{filter.ModuleID}
|
||||
}
|
||||
|
||||
dalFilter := filter.ToConstraintedFilter(constraints)
|
||||
|
||||
return c.Count(ctx, mod.ModelRef(), recLookupOperations(mod), dalFilter)
|
||||
}
|
||||
|
||||
func ComposeRecordCreate(ctx context.Context, c creator, mod *types.Module, records ...*types.Record) (err error) {
|
||||
return c.Create(ctx, mod.ModelRef(), recCreateOperations(mod), recToGetters(records...)...)
|
||||
}
|
||||
|
||||
func ComposeRecordUpdate(ctx context.Context, u updater, mod *types.Module, records ...*types.Record) (err error) {
|
||||
return u.Update(ctx, mod.ModelRef(), recUpdateOperations(mod), recToGetters(records...)...)
|
||||
}
|
||||
|
||||
func ComposeRecordSoftDelete(ctx context.Context, u updater, mod *types.Module, records ...*types.Record) (err error) {
|
||||
return u.Update(ctx, mod.ModelRef(), recUpdateOperations(mod), recToGetters(records...)...)
|
||||
}
|
||||
|
||||
func ComposeRecordUndelete(ctx context.Context, u updater, mod *types.Module, records ...*types.Record) (err error) {
|
||||
return u.Update(ctx, mod.ModelRef(), recUpdateOperations(mod), recToGetters(records...)...)
|
||||
}
|
||||
|
||||
func ComposeRecordDelete(ctx context.Context, d deleter, mod *types.Module, records ...*types.Record) (err error) {
|
||||
return d.Delete(ctx, mod.ModelRef(), recDeleteOperations(mod), recToGetters(records...)...)
|
||||
}
|
||||
|
||||
func WalkIterator(ctx context.Context, iter dal.Iterator, mod *types.Module, f func(r *types.Record) error) (err error) {
|
||||
for iter.Next(ctx) {
|
||||
r := prepareRecordTarget(mod)
|
||||
if err = iter.Scan(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = f(r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Utils
|
||||
|
||||
func prepFilter(filter types.RecordFilter, mod *types.Module) filter.Filter {
|
||||
return filter.ToConstraintedFilter(mod.Config.DAL.Constraints)
|
||||
}
|
||||
|
||||
func prepIterator(ctx context.Context, dal searcher, mod *types.Module, filter types.RecordFilter) (iter dal.Iterator, err error) {
|
||||
dalFilter := prepFilter(filter, mod)
|
||||
|
||||
iter, err = dal.Search(ctx, mod.ModelRef(), recSearchOperations(mod, filter), dalFilter)
|
||||
return
|
||||
}
|
||||
|
||||
// drains iterator and collects all records
|
||||
//
|
||||
// Collection of records is done with respect to check function and limit constraint on record filter
|
||||
// For any other filter constraint we assume that underlying DAL took care of it
|
||||
func drainIterator(ctx context.Context, iter dal.Iterator, mod *types.Module, f types.RecordFilter) (set types.RecordSet, summaries map[string]types.RecordSummary, outFilter types.RecordFilter, err error) {
|
||||
// close iterator after we've drained it
|
||||
defer iter.Close()
|
||||
|
||||
if f.PageCursor != nil {
|
||||
if f.IncPageNavigation || f.IncTotal {
|
||||
err = fmt.Errorf("not allowed to fetch page navigation or total item count with page cursor")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ok bool
|
||||
fetched uint
|
||||
filtered uint
|
||||
lastRecord *types.Record
|
||||
)
|
||||
|
||||
// Get the requested number of record
|
||||
if f.Limit > 0 {
|
||||
set = make(types.RecordSet, 0, f.Limit)
|
||||
} else {
|
||||
set = make(types.RecordSet, 0, 1000)
|
||||
}
|
||||
|
||||
for f.Limit == 0 || uint(len(set)) < f.Limit {
|
||||
var firstRecord *types.Record
|
||||
|
||||
// reset counters every drain
|
||||
fetched = 0
|
||||
filtered = 0
|
||||
|
||||
add := make(types.RecordSet, 0, 12)
|
||||
err = WalkIterator(ctx, iter, mod, func(r *types.Record) error {
|
||||
lastRecord = r
|
||||
if firstRecord == nil {
|
||||
firstRecord = r
|
||||
}
|
||||
|
||||
// check fetched record
|
||||
if f.Check != nil {
|
||||
if ok, err = f.Check(r); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
filtered++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fetched++
|
||||
add = append(add, r)
|
||||
return err
|
||||
})
|
||||
|
||||
// if an error occurred inside Next()/WalkIterator,
|
||||
// we need to stop draining
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If it's reverse, we need to add extra fetches to the start
|
||||
if f.PageCursor != nil && f.PageCursor.ROrder {
|
||||
set = append(add, set...)
|
||||
} else {
|
||||
set = append(set, add...)
|
||||
}
|
||||
|
||||
total := fetched + filtered
|
||||
if total == 0 || f.Limit == 0 {
|
||||
// do not re-fetch if:
|
||||
// 1) nothing was fetch in the previous run
|
||||
// 2) there was no limit (everything was fetched)
|
||||
// 3) there are less total (fetched and filtered) items then value of limit
|
||||
break
|
||||
}
|
||||
|
||||
// Fetch more records
|
||||
setLen := uint(len(set))
|
||||
if total > 0 && setLen < f.Limit {
|
||||
fetchMore := f.Limit - setLen
|
||||
var crsrRec *types.Record
|
||||
|
||||
// request more items
|
||||
if f.PageCursor == nil || !f.PageCursor.ROrder {
|
||||
crsrRec = lastRecord
|
||||
} else {
|
||||
crsrRec = firstRecord
|
||||
}
|
||||
|
||||
if err = iter.More(fetchMore, crsrRec); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the page nav/total/next-prev cursors
|
||||
nav, auxSm, err := generatePageNavigation(ctx, iter, mod, f, set)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
summaries = auxSm
|
||||
|
||||
// Make out filter
|
||||
outFilter = f
|
||||
outFilter.Paging = nav.Paging
|
||||
outFilter.Sorting = nav.Sorting
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// generatePageNavigation generates page navigation for a given record set using an iterator and filter limit.
|
||||
// If the limit is not defined, the page navigation will consist of only one page without a cursor.
|
||||
// If the limit is defined and is greater than the total number of records in the set,
|
||||
// the page navigation will consist of only one page without a cursor.
|
||||
// If the limit is defined and is less than the total number of records in the set,
|
||||
// the page navigation will have multiple pages with cursor(s) based on the total number of records and the provided limit.
|
||||
// @todo revisit and clean up this function properly
|
||||
func generatePageNavigation(ctx context.Context, iter dal.Iterator, mod *types.Module, p types.RecordFilter, set types.RecordSet) (out types.RecordFilter, summaries map[string]types.RecordSummary, err error) {
|
||||
const (
|
||||
howMuchMore = 1000
|
||||
)
|
||||
var (
|
||||
ok bool
|
||||
total = uint(len(set))
|
||||
setLen = len(set)
|
||||
counter = 0
|
||||
|
||||
first *types.Record
|
||||
last *types.Record
|
||||
page filter.Page
|
||||
|
||||
pageNavigation = []*filter.Page{
|
||||
{
|
||||
Page: 1,
|
||||
Count: 0,
|
||||
Cursor: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// generatePage generates pageNavigation for given record set
|
||||
generatePage = func(last *types.Record) (err error) {
|
||||
if !p.IncPageNavigation || p.Limit == 0 || len(pageNavigation) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
lastNavPageNo := len(pageNavigation) - 1
|
||||
nextPage, err := iter.ForwardCursor(last)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if total < p.Limit {
|
||||
pageNavigation[lastNavPageNo].Count = total
|
||||
}
|
||||
|
||||
// prepare page
|
||||
if total != 0 && (total%p.Limit) == 0 {
|
||||
pageNavigation[lastNavPageNo].Count = p.Limit
|
||||
page = filter.Page{
|
||||
Page: uint(len(pageNavigation) + 1),
|
||||
Count: p.Limit,
|
||||
Cursor: nextPage,
|
||||
}
|
||||
}
|
||||
|
||||
expectedItemCountUpToPage := uint(lastNavPageNo+1) * p.Limit
|
||||
if p.Limit == 1 {
|
||||
expectedItemCountUpToPage = uint(lastNavPageNo) * p.Limit
|
||||
}
|
||||
|
||||
if expectedItemCountUpToPage < total {
|
||||
// push page when limit is matched with the previous page item size
|
||||
if pageNavigation[lastNavPageNo].Count == p.Limit {
|
||||
pageNavigation = append(pageNavigation, &filter.Page{
|
||||
Page: page.Page,
|
||||
Count: total % p.Limit,
|
||||
Cursor: page.Cursor, // prev cursor
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
recordChecker = func(i dal.Iterator) (ok bool, err error) {
|
||||
if p.Check == nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
rc := prepareRecordTarget(mod)
|
||||
err = i.Scan(rc)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return p.Check(rc)
|
||||
}
|
||||
)
|
||||
|
||||
if len(p.Summaries) > 0 {
|
||||
summaries = make(map[string]types.RecordSummary, len(p.Summaries))
|
||||
}
|
||||
|
||||
if setLen == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
existing := make(map[any]struct{}, 24)
|
||||
looped := false
|
||||
|
||||
procSummary := func(r *types.Record) (err error) {
|
||||
for _, smDef := range p.Summaries {
|
||||
// Get record value
|
||||
var vv []any
|
||||
vv, err = r.GetValues(smDef.Field)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bit := summaries[fmt.Sprintf("%s %s", smDef.Name, smDef.Field)]
|
||||
|
||||
// This will be constant so we're good
|
||||
bit.Name = smDef.Name
|
||||
|
||||
// Skip empty
|
||||
if len(vv) == 0 {
|
||||
bit.EmptyCount++
|
||||
summaries[fmt.Sprintf("%s %s", smDef.Name, smDef.Field)] = bit
|
||||
continue
|
||||
}
|
||||
|
||||
bit.NotEmptyCount++
|
||||
|
||||
for _, v := range vv {
|
||||
bit.Count++
|
||||
|
||||
if _, ok := existing[v]; !ok {
|
||||
existing[v] = struct{}{}
|
||||
bit.UniqueCount++
|
||||
}
|
||||
}
|
||||
|
||||
switch smDef.Name {
|
||||
case "min":
|
||||
for _, v := range vv {
|
||||
if !looped {
|
||||
bit.Min = cast.ToFloat64(v)
|
||||
} else {
|
||||
bit.Min = math.Min(bit.Min, cast.ToFloat64(v))
|
||||
}
|
||||
}
|
||||
|
||||
case "max":
|
||||
for _, v := range vv {
|
||||
if !looped {
|
||||
bit.Max = cast.ToFloat64(v)
|
||||
} else {
|
||||
bit.Max = math.Max(bit.Max, cast.ToFloat64(v))
|
||||
}
|
||||
}
|
||||
|
||||
case "avg":
|
||||
for _, v := range vv {
|
||||
bit.Sum += cast.ToFloat64(v)
|
||||
}
|
||||
|
||||
case "sum":
|
||||
for _, v := range vv {
|
||||
bit.Sum += cast.ToFloat64(v)
|
||||
}
|
||||
|
||||
case "earliest":
|
||||
for _, v := range vv {
|
||||
if bit.Earliest.IsZero() {
|
||||
bit.Earliest = cast.ToTime(v)
|
||||
} else {
|
||||
aux := cast.ToTime(v)
|
||||
if !aux.IsZero() && aux.Before(bit.Earliest) {
|
||||
bit.Earliest = aux
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "latest":
|
||||
for _, v := range vv {
|
||||
if bit.Latest.IsZero() {
|
||||
bit.Latest = cast.ToTime(v)
|
||||
} else {
|
||||
aux := cast.ToTime(v)
|
||||
if !aux.IsZero() && aux.After(bit.Latest) {
|
||||
bit.Latest = aux
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
looped = true
|
||||
summaries[fmt.Sprintf("%s %s", smDef.Name, smDef.Field)] = bit
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Firstly sort out the current things
|
||||
if len(p.Summaries) > 0 {
|
||||
for _, r := range set {
|
||||
err = procSummary(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
first = set[0]
|
||||
last = set[setLen-1]
|
||||
|
||||
// Limit
|
||||
out.Limit = p.Limit
|
||||
|
||||
// Sorting
|
||||
out.Sort = dal.IteratorSorting(iter)
|
||||
|
||||
// No need to generate prev/next cursor
|
||||
// if limit is not defined and set is empty
|
||||
if p.Limit > 0 && len(set) > 0 {
|
||||
// PrevPage
|
||||
if p.PageCursor != nil {
|
||||
out.PrevPage, err = dal.PreLoadCursor(ctx, iter, 100, true, first, recordChecker)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NextPage
|
||||
out.NextPage, err = dal.PreLoadCursor(ctx, iter, 100, false, last, recordChecker)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.IncTotal || p.IncPageNavigation || len(p.Summaries) > 0 {
|
||||
// For the first page nav
|
||||
err = generatePage(last)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for counter == 0 || counter < howMuchMore {
|
||||
counter++
|
||||
|
||||
interLoop := 0
|
||||
|
||||
if err = iter.More(howMuchMore, last); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = WalkIterator(ctx, iter, mod, func(rec *types.Record) error {
|
||||
// check fetched record
|
||||
if p.Check != nil {
|
||||
if ok, err = p.Check(rec); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = procSummary(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interLoop++
|
||||
total++
|
||||
last = rec
|
||||
return generatePage(rec)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if interLoop < howMuchMore {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Total
|
||||
if p.IncTotal {
|
||||
out.Total = total
|
||||
}
|
||||
|
||||
// Page navigation
|
||||
if p.IncPageNavigation {
|
||||
// Ensure that the last page count is correct if it's not equal to the limit.
|
||||
lastPageCount := pageNavigation[len(pageNavigation)-1].Count
|
||||
if lastPageCount > 0 && lastPageCount != p.Limit && lastPageCount != total%p.Limit {
|
||||
pageNavigation[len(pageNavigation)-1].Count = total % p.Limit
|
||||
}
|
||||
|
||||
if p.Limit == 1 {
|
||||
pageNavigation = pageNavigation[:len(pageNavigation)-1]
|
||||
}
|
||||
|
||||
out.PageNavigation = pageNavigation
|
||||
}
|
||||
|
||||
// Do averages
|
||||
if len(p.Summaries) > 0 {
|
||||
for n, s := range summaries {
|
||||
if s.Name != "avg" {
|
||||
continue
|
||||
}
|
||||
|
||||
s.Avg = s.Sum / float64(s.Count)
|
||||
summaries[n] = s
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func prepareRecordTarget(module *types.Module) *types.Record {
|
||||
// so we can avoid some code later involving (non)partitioned modules :seenoevil:
|
||||
r := &types.Record{
|
||||
ModuleID: module.ID,
|
||||
NamespaceID: module.NamespaceID,
|
||||
Values: make(types.RecordValueSet, 0, len(module.Fields)),
|
||||
}
|
||||
r.SetModule(module)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func recToGetters(rr ...*types.Record) (out []dal.ValueGetter) {
|
||||
out = make([]dal.ValueGetter, len(rr))
|
||||
|
||||
for i := range rr {
|
||||
out[i] = rr[i]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func recCreateOperations(m *types.Module) (out dal.OperationSet) {
|
||||
return dal.CreateOperations()
|
||||
}
|
||||
|
||||
func recUpdateOperations(m *types.Module) (out dal.OperationSet) {
|
||||
return dal.UpdateOperations()
|
||||
}
|
||||
|
||||
func recDeleteOperations(m *types.Module) (out dal.OperationSet) {
|
||||
return dal.DeleteOperations()
|
||||
}
|
||||
|
||||
func recFilterOperations(f types.RecordFilter) (out dal.OperationSet) {
|
||||
if f.PageCursor != nil {
|
||||
out = append(out, dal.Paging)
|
||||
}
|
||||
|
||||
if f.IncPageNavigation {
|
||||
out = append(out, dal.Paging)
|
||||
}
|
||||
|
||||
if f.Sort != nil {
|
||||
out = append(out, dal.Sorting)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func recSearchOperations(m *types.Module, f types.RecordFilter) (out dal.OperationSet) {
|
||||
return dal.SearchOperations().
|
||||
Union(recFilterOperations(f))
|
||||
}
|
||||
|
||||
func recLookupOperations(m *types.Module) (out dal.OperationSet) {
|
||||
return dal.LookupOperations()
|
||||
}
|
||||
208
server/compose/envoy/csv_encode.go
Normal file
208
server/compose/envoy/csv_encode.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx/datasource"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
CsvEncoder struct{}
|
||||
)
|
||||
|
||||
func (e CsvEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (err error) {
|
||||
w, err := e.getWriter(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cw := csv.NewWriter(w)
|
||||
|
||||
switch rt {
|
||||
case ComposeRecordDatasourceAuxType:
|
||||
_, err = e.encodeRecordDatasources(ctx, cw, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cw.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (e CsvEncoder) encodeRecordDatasources(ctx context.Context, writer *csv.Writer, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out any, err error) {
|
||||
for _, n := range nodes {
|
||||
_, err = e.encodeRecordDatasource(ctx, writer, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e CsvEncoder) encodeRecordDatasource(ctx context.Context, writer *csv.Writer, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (_ any, err error) {
|
||||
rds := node.Datasource.(*RecordDatasource)
|
||||
resolved := map[string]string{}
|
||||
|
||||
var loc *time.Location
|
||||
loc, err = resolveTimezone(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := make([]string, 0, 4)
|
||||
|
||||
for _, m := range p.FieldMapping {
|
||||
if strings.ToLower(m.Field) != "id" {
|
||||
header = append(header, m.Field)
|
||||
}
|
||||
}
|
||||
header = append([]string{"ID"}, header...)
|
||||
|
||||
mvDelimiter := ";"
|
||||
wrapBrackets := false
|
||||
|
||||
if p.Params["multiValueDelimiter"] == nil {
|
||||
wrapBrackets = false
|
||||
mvDelimiter = ";"
|
||||
}
|
||||
|
||||
switch cast.ToString(p.Params["multiValueDelimiter"]) {
|
||||
case ",":
|
||||
mvDelimiter = ","
|
||||
wrapBrackets = false
|
||||
case ";":
|
||||
mvDelimiter = ";"
|
||||
wrapBrackets = false
|
||||
case "|":
|
||||
mvDelimiter = "|"
|
||||
wrapBrackets = false
|
||||
case "[,]":
|
||||
mvDelimiter = ","
|
||||
wrapBrackets = true
|
||||
case "[;]":
|
||||
mvDelimiter = ";"
|
||||
wrapBrackets = true
|
||||
case "[|]":
|
||||
mvDelimiter = "|"
|
||||
wrapBrackets = true
|
||||
}
|
||||
|
||||
getValue := func(cache datasource.RawRecord, h string) string {
|
||||
v := cache[h]
|
||||
if len(v.Values) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Encode as mf if the OG field is multi value OR we're on the resolved record column
|
||||
src, ok := resolved[h]
|
||||
isDt := rds.datetimeFields[h] || (ok && rds.datetimeFields[src])
|
||||
if !rds.multivalues[h] && !(ok && rds.multivalues[src]) {
|
||||
vv := v.Values[0]
|
||||
if isDt {
|
||||
vv = formatInTimezone(vv, loc)
|
||||
}
|
||||
|
||||
return vv
|
||||
}
|
||||
|
||||
auxv := make([]string, 0, len(v.Values))
|
||||
for _, vv := range v.Values {
|
||||
if isDt {
|
||||
vv = formatInTimezone(vv, loc)
|
||||
}
|
||||
|
||||
if strings.Contains(vv, mvDelimiter) {
|
||||
auxv = append(auxv, fmt.Sprintf("\"%s\"", vv))
|
||||
} else {
|
||||
auxv = append(auxv, vv)
|
||||
}
|
||||
}
|
||||
|
||||
out := strings.Join(auxv, mvDelimiter)
|
||||
if wrapBrackets {
|
||||
out = fmt.Sprintf("[%s]", out)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Pre-build resolved field set from datasource so header is correct even
|
||||
// when the first row has no value for a resolved field.
|
||||
resolvedFieldSet := make(map[string]bool)
|
||||
for _, f := range rds.ResolvedFields() {
|
||||
resolvedFieldSet[f] = true
|
||||
}
|
||||
|
||||
row := make([]string, 0, 4)
|
||||
var (
|
||||
more bool
|
||||
hWritten bool
|
||||
)
|
||||
for {
|
||||
cache := make(datasource.RawRecord)
|
||||
_, more, err = rds.Next(ctx, cache)
|
||||
if err != nil || !more {
|
||||
return
|
||||
}
|
||||
|
||||
if len(header) == 0 {
|
||||
for k := range cache {
|
||||
header = append(header, k)
|
||||
}
|
||||
}
|
||||
|
||||
if !hWritten {
|
||||
hWritten = true
|
||||
|
||||
// Splice in "X ID" columns after each resolved field
|
||||
newHeader := make([]string, 0, len(header)+len(resolvedFieldSet))
|
||||
for _, h := range header {
|
||||
newHeader = append(newHeader, h)
|
||||
if resolvedFieldSet[h] {
|
||||
idCol := fmt.Sprintf("%s ID", h)
|
||||
newHeader = append(newHeader, idCol)
|
||||
resolved[idCol] = h
|
||||
}
|
||||
}
|
||||
header = newHeader
|
||||
|
||||
err = writer.Write(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, h := range header {
|
||||
row = append(row, getValue(cache, h))
|
||||
}
|
||||
|
||||
err = writer.Write(row)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
row = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e CsvEncoder) getWriter(p envoyx.EncodeParams) (out io.Writer, err error) {
|
||||
aux, ok := p.Params[paramsKeyWriter]
|
||||
if ok {
|
||||
out, ok = aux.(io.Writer)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = fmt.Errorf("csv encoder expects a writer conforming to io.Writer interface")
|
||||
return
|
||||
}
|
||||
153
server/compose/envoy/jsonl_encode.go
Normal file
153
server/compose/envoy/jsonl_encode.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx/datasource"
|
||||
"github.com/cortezaproject/corteza/server/pkg/j7s"
|
||||
)
|
||||
|
||||
type (
|
||||
JsonlEncoder struct{}
|
||||
)
|
||||
|
||||
func (e JsonlEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (err error) {
|
||||
w, err := e.getWriter(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
jw := json.NewEncoder(w)
|
||||
|
||||
switch rt {
|
||||
case ComposeRecordDatasourceAuxType:
|
||||
_, err = e.encodeRecordDatasources(ctx, jw, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e JsonlEncoder) encodeRecordDatasources(ctx context.Context, writer *json.Encoder, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out any, err error) {
|
||||
for _, n := range nodes {
|
||||
_, err = e.encodeRecordDatasource(ctx, writer, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e JsonlEncoder) encodeRecordDatasource(ctx context.Context, writer *json.Encoder, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (_ any, err error) {
|
||||
rds := node.Datasource.(*RecordDatasource)
|
||||
|
||||
var loc *time.Location
|
||||
loc, err = resolveTimezone(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := make([]string, 0, 4)
|
||||
|
||||
hasID := false
|
||||
for _, m := range p.FieldMapping {
|
||||
header = append(header, m.Field)
|
||||
|
||||
hasID = hasID || strings.ToLower(m.Field) == "id"
|
||||
}
|
||||
|
||||
if !hasID {
|
||||
header = append([]string{"ID"}, header...)
|
||||
}
|
||||
|
||||
var more bool
|
||||
for {
|
||||
out := make(datasource.RawRecord)
|
||||
_, more, err = rds.Next(ctx, out)
|
||||
if err != nil || !more {
|
||||
break
|
||||
}
|
||||
|
||||
if len(header) == 0 {
|
||||
for k := range out {
|
||||
header = append(header, k)
|
||||
}
|
||||
}
|
||||
|
||||
row, _ := j7s.MakeMap()
|
||||
for _, h := range header {
|
||||
proc := func(h string) {
|
||||
v := out[h]
|
||||
if len(v.Values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
srcKey := strings.TrimSuffix(h, " value")
|
||||
isDt := rds.datetimeFields[h] || rds.datetimeFields[srcKey]
|
||||
|
||||
if !rds.multivalues[h] {
|
||||
vv := v.Values[0]
|
||||
if isDt {
|
||||
vv = formatInTimezone(vv, loc)
|
||||
}
|
||||
|
||||
row, err = j7s.AddMap(row, h, vv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
auxv, _ := j7s.MakeSeq()
|
||||
for _, vv := range v.Values {
|
||||
if isDt {
|
||||
vv = formatInTimezone(vv, loc)
|
||||
}
|
||||
|
||||
auxv, err = j7s.AddSeq(auxv, vv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
row, err = j7s.AddMap(row, h, auxv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc(h)
|
||||
if _, ok := out[fmt.Sprintf("%s value", h)]; ok {
|
||||
proc(fmt.Sprintf("%s value", h))
|
||||
}
|
||||
}
|
||||
|
||||
err = writer.Encode(row)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e JsonlEncoder) getWriter(p envoyx.EncodeParams) (out io.Writer, err error) {
|
||||
aux, ok := p.Params[paramsKeyWriter]
|
||||
if ok {
|
||||
out, ok = aux.(io.Writer)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = fmt.Errorf("jsonl encoder expects a writer conforming to io.Writer interface")
|
||||
return
|
||||
}
|
||||
653
server/compose/envoy/record_datasource.go
Normal file
653
server/compose/envoy/record_datasource.go
Normal file
@@ -0,0 +1,653 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx/datasource"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/modern-go/reflect2"
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
// RecordDatasource provides a mechanism for you to access large
|
||||
// record datasets optimally
|
||||
RecordDatasource struct {
|
||||
Mapping envoyx.DatasourceMapping
|
||||
Provider envoyx.Provider
|
||||
|
||||
multivalues map[string]bool
|
||||
datetimeFields map[string]bool
|
||||
|
||||
CheckExisting func(ctx context.Context, ref ...[]string) ([]uint64, error)
|
||||
|
||||
currentIndex int
|
||||
|
||||
// Index to map from ref to ID
|
||||
// @todo we might need to flush these to the disc in case a huge dataset is passed in
|
||||
refToID map[string]uint64
|
||||
// @todo might be worth putting both into one map; not sure how much space we'd save up
|
||||
existingIDs map[uint64]bool
|
||||
}
|
||||
|
||||
// iteratorProvider is a wrapper around the dal.Iterator to conform to the
|
||||
// envoy.Provider interface
|
||||
iteratorProvider struct {
|
||||
iter dal.Iterator
|
||||
|
||||
// Items related to ref resolution
|
||||
// @todo can be removed when reworked
|
||||
resolveRefs bool
|
||||
includeRefID bool
|
||||
relMods map[string]refModWrap
|
||||
dal dal.FullService
|
||||
|
||||
// Access control for field-level permissions
|
||||
ac recordValueAccessController
|
||||
mod *types.Module
|
||||
readableFields map[string]bool
|
||||
|
||||
rows []datasource.RawRecord
|
||||
buffIndex int
|
||||
done bool
|
||||
|
||||
// User ref resolution
|
||||
store store.Storer
|
||||
userFields map[string]bool
|
||||
}
|
||||
|
||||
refModWrap struct {
|
||||
modLvl1 *types.Module
|
||||
labelLvl1 string
|
||||
|
||||
modLvl2 *types.Module
|
||||
labelLvl2 string
|
||||
}
|
||||
|
||||
// recordValueAccessController interface for checking field-level read permissions
|
||||
recordValueAccessController interface {
|
||||
CanReadRecordValueOnModuleField(context.Context, *types.ModuleField) bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
bufferPullChunkSize = int(100)
|
||||
)
|
||||
|
||||
func mkIteratorProvider(ctx context.Context, ac recordValueAccessController, s store.Storer, dl dal.FullService, iter dal.Iterator, mod *types.Module, resolveRefs bool, includeRefID bool) (out *iteratorProvider, err error) {
|
||||
out = &iteratorProvider{
|
||||
iter: iter,
|
||||
dal: dl,
|
||||
resolveRefs: resolveRefs,
|
||||
includeRefID: includeRefID,
|
||||
|
||||
ac: ac,
|
||||
mod: mod,
|
||||
}
|
||||
|
||||
// Pre-compute readable fields based on access control permissions
|
||||
// This avoids checking permissions on every record iteration
|
||||
if !reflect2.IsNil(ac) {
|
||||
out.readableFields = make(map[string]bool)
|
||||
for _, f := range mod.Fields {
|
||||
// Ensure NamespaceID is set on the field (it may not be loaded from store)
|
||||
// RBAC checks require the full resource path including namespace
|
||||
if f.NamespaceID == 0 {
|
||||
f.NamespaceID = mod.NamespaceID
|
||||
}
|
||||
out.readableFields[f.Name] = ac.CanReadRecordValueOnModuleField(ctx, f)
|
||||
}
|
||||
}
|
||||
|
||||
refMods := make(map[string]refModWrap)
|
||||
userFields := make(map[string]bool)
|
||||
|
||||
for _, name := range []string{"createdBy", "updatedBy", "ownedBy", "deletedBy"} {
|
||||
userFields[name] = true
|
||||
}
|
||||
|
||||
for _, f := range mod.Fields {
|
||||
switch f.Kind {
|
||||
case "Record":
|
||||
refMods[f.Name], err = mkRecordRefWrap(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "User":
|
||||
userFields[f.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
out.relMods = refMods
|
||||
out.store = s
|
||||
out.userFields = userFields
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ResolvedFields returns field names that will have a companion "X ID" column when includeRefID is enabled.
|
||||
func (ip *iteratorProvider) ResolvedFields() []string {
|
||||
if !ip.resolveRefs || !ip.includeRefID {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(ip.userFields)+len(ip.relMods))
|
||||
for f := range ip.userFields {
|
||||
out = append(out, f)
|
||||
}
|
||||
|
||||
for f := range ip.relMods {
|
||||
out = append(out, f)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ResolvedFields returns field names that will have a companion "X value" column.
|
||||
func (rd *RecordDatasource) ResolvedFields() []string {
|
||||
if ip, ok := rd.Provider.(*iteratorProvider); ok {
|
||||
return ip.ResolvedFields()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) SetProvider(s envoyx.Provider) bool {
|
||||
if rd.Mapping.SourceIdent != s.Ident() {
|
||||
return false
|
||||
}
|
||||
|
||||
rd.Provider = s
|
||||
return true
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) Next(ctx context.Context, out datasource.RawRecord) (ident []string, more bool, err error) {
|
||||
rowCache := make(datasource.RawRecord)
|
||||
|
||||
more, err = rd.Provider.Next(ctx, rowCache)
|
||||
if err != nil || !more {
|
||||
return
|
||||
}
|
||||
|
||||
rd.applyMapping(rowCache, out)
|
||||
|
||||
if len(rd.Mapping.KeyField) == 0 {
|
||||
ident = append(ident, strconv.FormatInt(int64(rd.currentIndex), 10))
|
||||
} else {
|
||||
for _, k := range rd.Mapping.KeyField {
|
||||
ident = append(ident, strings.Join(rowCache[k].Values, ","))
|
||||
}
|
||||
}
|
||||
|
||||
rd.currentIndex++
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rd *iteratorProvider) SetConfigs(map[string]any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) Reset(ctx context.Context) (err error) {
|
||||
rd.currentIndex = 0
|
||||
return rd.Provider.Reset(ctx)
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) applyMapping(in, out datasource.RawRecord) {
|
||||
if len(rd.Mapping.Mapping.Map) == 0 {
|
||||
if !rd.Mapping.Defaultable {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range in {
|
||||
out[k] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if rd.Mapping.Defaultable {
|
||||
rd.applyMappingWithDefaults(in, out)
|
||||
} else {
|
||||
rd.applyMappingWoDefaults(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) applyMappingWithDefaults(in, out datasource.RawRecord) {
|
||||
maps := make(map[string]envoyx.MapEntry)
|
||||
for k, v := range rd.Mapping.Mapping.Map {
|
||||
maps[k] = v
|
||||
}
|
||||
|
||||
for k, v := range in {
|
||||
if m, ok := maps[k]; ok {
|
||||
if m.Skip {
|
||||
continue
|
||||
}
|
||||
out[m.Field] = v
|
||||
} else {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) applyMappingWoDefaults(in, out datasource.RawRecord) {
|
||||
for _, m := range rd.Mapping.Mapping.Map {
|
||||
if m.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
out[m.Field] = in[m.Column]
|
||||
}
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) ResolveRef(ref ...any) (out uint64, err error) {
|
||||
idents, err := cast.ToStringSliceE(ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i, ident := range idents {
|
||||
idents[i] = strings.Replace(ident, "-", "_", -1)
|
||||
}
|
||||
|
||||
out = rd.refToID[strings.Join(idents, "-")]
|
||||
return
|
||||
}
|
||||
|
||||
func (rd *RecordDatasource) ResolveRefS(ref ...string) (out uint64, err error) {
|
||||
aux := make([]any, len(ref))
|
||||
for i, r := range ref {
|
||||
aux[i] = r
|
||||
}
|
||||
|
||||
return rd.ResolveRef(aux...)
|
||||
}
|
||||
|
||||
// @todo this should be replaced by some smarter structure
|
||||
func (rd *RecordDatasource) AddRef(id uint64, idents ...string) {
|
||||
for i, ident := range idents {
|
||||
idents[i] = strings.Replace(ident, "-", "_", -1)
|
||||
}
|
||||
|
||||
rd.refToID[strings.Join(idents, "-")] = id
|
||||
}
|
||||
|
||||
func (ip *iteratorProvider) Next(ctx context.Context, out datasource.RawRecord) (more bool, err error) {
|
||||
if ip.resolveRefs {
|
||||
return ip.nextResolved(ctx, out)
|
||||
}
|
||||
|
||||
return ip.next(ctx, out)
|
||||
}
|
||||
|
||||
func (ip *iteratorProvider) next(ctx context.Context, out datasource.RawRecord) (more bool, err error) {
|
||||
rowCache := make(datasource.RawRecord)
|
||||
|
||||
if !ip.iter.Next(ctx) {
|
||||
return false, ip.iter.Err()
|
||||
}
|
||||
|
||||
err = ip.iter.Scan(rowCache)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply field-level access control filtering
|
||||
ip.filterUnreadableFields(rowCache)
|
||||
|
||||
for k, v := range rowCache {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ip *iteratorProvider) nextResolved(ctx context.Context, out datasource.RawRecord) (more bool, err error) {
|
||||
if ip.done && ip.buffIndex >= len(ip.rows) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if ip.buffIndex == len(ip.rows) {
|
||||
|
||||
// pull chunk
|
||||
ip.rows = make([]datasource.RawRecord, 0)
|
||||
ip.buffIndex = 0
|
||||
|
||||
for i := 0; i < bufferPullChunkSize; i++ {
|
||||
rowCache := make(datasource.RawRecord)
|
||||
if !ip.iter.Next(ctx) {
|
||||
ip.done = true
|
||||
|
||||
err := ip.iter.Err()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
err = ip.iter.Scan(rowCache)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ip.rows = append(ip.rows, rowCache)
|
||||
}
|
||||
|
||||
if len(ip.rows) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// resolve stuff
|
||||
err = ip.resolveReferences(ctx, ip.dal)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ip.resolveUsers(ctx, ip.store)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rowCache := ip.rows[ip.buffIndex]
|
||||
ip.buffIndex++
|
||||
|
||||
// Apply field-level access control filtering
|
||||
ip.filterUnreadableFields(rowCache)
|
||||
|
||||
for k, v := range rowCache {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ip *iteratorProvider) resolveReferences(ctx context.Context, ds dal.FullService) (err error) {
|
||||
// @todo I'll need to chunk these up for to reduce DB query count...
|
||||
|
||||
for i, cacheRecord := range ip.rows {
|
||||
|
||||
for refField, refWrap := range ip.relMods {
|
||||
value := cacheRecord[refField]
|
||||
if len(value.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
aux := []string{}
|
||||
for _, v := range value.Values {
|
||||
aux = append(aux, fmt.Sprintf("recordID=%s", v))
|
||||
}
|
||||
|
||||
var relRecords types.RecordSet
|
||||
|
||||
resLab := refWrap.labelLvl1
|
||||
qq := fmt.Sprintf("(%s)", strings.Join(aux, " OR "))
|
||||
relRecords, _, err = dalutils.ComposeRecordsList(ctx, ds, refWrap.modLvl1, types.RecordFilter{
|
||||
Query: qq,
|
||||
Paging: filter.Paging{
|
||||
Limit: uint(len(aux)),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lvl 2 nesting; current max lvl
|
||||
if refWrap.modLvl2 != nil {
|
||||
resLab = refWrap.labelLvl2
|
||||
|
||||
// Iterate related records and collect lvl 2 identifiers
|
||||
aux := []string{}
|
||||
for _, rec := range relRecords {
|
||||
for _, v := range rec.Values.FilterByName(refWrap.labelLvl1) {
|
||||
aux = append(aux, fmt.Sprintf("recordID=%s", v.Value))
|
||||
}
|
||||
}
|
||||
|
||||
qq := fmt.Sprintf("(%s)", strings.Join(aux, " OR "))
|
||||
relRecords, _, err = dalutils.ComposeRecordsList(ctx, ds, refWrap.modLvl2, types.RecordFilter{
|
||||
Query: qq,
|
||||
Paging: filter.Paging{
|
||||
Limit: uint(len(aux)),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Build a map from recordID to resolved label for ordered assignment
|
||||
labelByID := make(map[string]string, len(relRecords))
|
||||
for _, rec := range relRecords {
|
||||
if rec.Values == nil {
|
||||
continue
|
||||
}
|
||||
v := rec.Values.Get(resLab, 0)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
labelByID[strconv.FormatUint(rec.ID, 10)] = v.Value
|
||||
}
|
||||
|
||||
for j, origID := range value.Values {
|
||||
label, ok := labelByID[origID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ip.includeRefID {
|
||||
cacheRecord.SetValue(fmt.Sprintf("%s ID", refField), uint(j), origID)
|
||||
}
|
||||
cacheRecord.SetValue(refField, uint(j), label)
|
||||
}
|
||||
|
||||
ip.rows[i] = cacheRecord
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// @todo consider omitting these from the interface since they're not always needed
|
||||
func (ip *iteratorProvider) Reset(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo consider omitting these from the interface since they're not always needed
|
||||
func (ip *iteratorProvider) Ident() (out string) {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo consider omitting these from the interface since they're not always needed
|
||||
func (ip *iteratorProvider) SetIdent(string) {
|
||||
}
|
||||
|
||||
func mkRecordRefWrap(ctx context.Context, s store.Storer, f *types.ModuleField) (wrap refModWrap, err error) {
|
||||
relModID := f.Options.UInt64("moduleID")
|
||||
if relModID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var relMod *types.Module
|
||||
relMod, err = s.LookupComposeModuleByID(ctx, relModID)
|
||||
if err != nil {
|
||||
// Referenced module may have been deleted or made inaccessible since the
|
||||
// field was configured. Skip ref resolution for this field instead of
|
||||
// failing the entire export — the column is still emitted, just unresolved.
|
||||
if errors.IsNotFound(err) {
|
||||
logger.Default().Warn(
|
||||
"skipping record ref resolution: referenced module not found",
|
||||
zap.String("field", f.Name),
|
||||
zap.Uint64("moduleID", relModID),
|
||||
)
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
relMod.Fields, _, err = s.SearchComposeModuleFields(ctx, types.ModuleFieldFilter{
|
||||
ModuleID: []uint64{relMod.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wrap = refModWrap{
|
||||
modLvl1: relMod,
|
||||
labelLvl1: f.Options.String("labelField"),
|
||||
}
|
||||
|
||||
if f.Options.String("recordLabelField") != "" {
|
||||
// Two-level label resolution: the labelField on the related module must
|
||||
// itself be a Record field pointing to another module. If that chain is
|
||||
// broken (labelField renamed/removed, target module missing), skip the
|
||||
// nested resolution instead of aborting the export.
|
||||
labelFieldName := f.Options.String("labelField")
|
||||
nestedRef := wrap.modLvl1.Fields.FindByName(labelFieldName)
|
||||
if nestedRef == nil {
|
||||
logger.Default().Warn(
|
||||
"skipping nested record ref resolution: labelField not found on related module",
|
||||
zap.String("field", f.Name),
|
||||
zap.String("labelField", labelFieldName),
|
||||
zap.Uint64("relatedModuleID", relMod.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
nestedModID := nestedRef.Options.UInt64("moduleID")
|
||||
if nestedModID == 0 {
|
||||
logger.Default().Warn(
|
||||
"skipping nested record ref resolution: labelField has no moduleID option",
|
||||
zap.String("field", f.Name),
|
||||
zap.String("labelField", labelFieldName),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
wrap.modLvl2, err = s.LookupComposeModuleByID(ctx, nestedModID)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logger.Default().Warn(
|
||||
"skipping nested record ref resolution: nested module not found",
|
||||
zap.String("field", f.Name),
|
||||
zap.Uint64("nestedModuleID", nestedModID),
|
||||
)
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
wrap.labelLvl2 = f.Options.String("recordLabelField")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ip *iteratorProvider) resolveUsers(ctx context.Context, s store.Storer) (err error) {
|
||||
if len(ip.userFields) == 0 || s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect unique user IDs across the current chunk
|
||||
seen := make(map[string]bool)
|
||||
for _, row := range ip.rows {
|
||||
for fieldName := range ip.userFields {
|
||||
for _, val := range row[fieldName].Values {
|
||||
if val != "" && val != "0" {
|
||||
seen[val] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(seen) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(seen))
|
||||
for id := range seen {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
uu, _, err := store.SearchUsers(ctx, s, systemTypes.UserFilter{
|
||||
UserID: ids,
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
Suspended: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := make(map[string]string, len(uu))
|
||||
for _, u := range uu {
|
||||
labels[strconv.FormatUint(u.ID, 10)] = userLabel(u)
|
||||
}
|
||||
|
||||
for i, row := range ip.rows {
|
||||
for fieldName := range ip.userFields {
|
||||
v := row[fieldName]
|
||||
if len(v.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
for j, val := range v.Values {
|
||||
if val == "0" || val == "" {
|
||||
row.SetValue(fieldName, uint(j), "")
|
||||
continue
|
||||
}
|
||||
if label, ok := labels[val]; ok {
|
||||
if ip.includeRefID {
|
||||
row.SetValue(fmt.Sprintf("%s ID", fieldName), uint(j), val)
|
||||
}
|
||||
row.SetValue(fieldName, uint(j), label)
|
||||
}
|
||||
}
|
||||
}
|
||||
ip.rows[i] = row
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func userLabel(u *systemTypes.User) string {
|
||||
if u.Name != "" {
|
||||
return u.Name
|
||||
}
|
||||
if u.Email != "" {
|
||||
return u.Email
|
||||
}
|
||||
if u.Handle != "" {
|
||||
return u.Handle
|
||||
}
|
||||
return strconv.FormatUint(u.ID, 10)
|
||||
}
|
||||
|
||||
// filterUnreadableFields removes values the given user does not have access to
|
||||
func (ip *iteratorProvider) filterUnreadableFields(rowCache datasource.RawRecord) {
|
||||
if reflect2.IsNil(ip.ac) {
|
||||
return
|
||||
}
|
||||
|
||||
for fieldName := range rowCache {
|
||||
// Skip system fields (ID, createdAt, etc.) - these are always readable
|
||||
// System fields are lowercase and don't have associated module field definitions
|
||||
if _, hasField := ip.readableFields[fieldName]; !hasField {
|
||||
// Field not in module definition, likely a system field - keep it
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if user can read this field
|
||||
if !ip.readableFields[fieldName] {
|
||||
delete(rowCache, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
server/compose/envoy/record_getter.go
Normal file
117
server/compose/envoy/record_getter.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
// recordGetter is a utility struct to resolve record references from
|
||||
// different parts of the system such as the dep graph and the database
|
||||
recordGetter struct {
|
||||
// Bits to get data from the dep graph
|
||||
relDatasource *RecordDatasource
|
||||
|
||||
// Bits to get data from the database
|
||||
dalSvc dal.FullService
|
||||
relModule types.Module
|
||||
baseFilter types.RecordFilter
|
||||
|
||||
// resolved refs, including misses; imports resolve the same handful
|
||||
// of refs over and over so this saves a query per value
|
||||
cache map[string]uint64
|
||||
}
|
||||
)
|
||||
|
||||
func makeRecordGetter(dalSvc dal.FullService, tt envoyx.Traverser, n *envoyx.Node, modRef, dsRef envoyx.Ref) (g *recordGetter) {
|
||||
g = &recordGetter{
|
||||
dalSvc: dalSvc,
|
||||
cache: make(map[string]uint64),
|
||||
}
|
||||
|
||||
// Resolve from dep graph
|
||||
auxDs := tt.ParentForRef(n, dsRef)
|
||||
if auxDs != nil {
|
||||
g.relDatasource = auxDs.Datasource.(*RecordDatasource)
|
||||
}
|
||||
|
||||
// Resolve from the database
|
||||
mod := g.getRefMod(tt, n, modRef)
|
||||
if mod != nil {
|
||||
g.baseFilter = types.RecordFilter{
|
||||
ModuleID: mod.ID,
|
||||
NamespaceID: mod.NamespaceID,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// resolve resolves the provided reference into a record ID; 0 if can't be resolved
|
||||
func (g *recordGetter) resolve(ctx context.Context, ref any) (out uint64, err error) {
|
||||
key := cast.ToString(ref)
|
||||
if out, ok := g.cache[key]; ok {
|
||||
return out, nil
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
g.cache[key] = out
|
||||
}
|
||||
}()
|
||||
|
||||
// Try to get from datasource
|
||||
if g.relDatasource != nil {
|
||||
out, err = g.getDS(ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if out > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback to the store
|
||||
out, err = g.getDB(ctx, ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if out > 0 {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (g *recordGetter) getDS(ref any) (out uint64, err error) {
|
||||
return g.relDatasource.ResolveRef(ref)
|
||||
}
|
||||
|
||||
func (g *recordGetter) getDB(ctx context.Context, ref any) (out uint64, err error) {
|
||||
// @note the old version only resolved IDs so that's what we're doing here also
|
||||
// @todo consider expanding this
|
||||
id, err := cast.ToUint64E(ref)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
aux, err := dalutils.ComposeRecordsFind(ctx, g.dalSvc, &g.relModule, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = aux.ID
|
||||
return
|
||||
}
|
||||
|
||||
func (g *recordGetter) getRefMod(tt envoyx.Traverser, n *envoyx.Node, ref envoyx.Ref) (refMod *types.Module) {
|
||||
aux := tt.ParentForRef(n, ref)
|
||||
if aux == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return aux.Resource.(*types.Module)
|
||||
}
|
||||
907
server/compose/envoy/store_decode.gen.go
generated
Normal file
907
server/compose/envoy/store_decode.gen.go
generated
Normal file
@@ -0,0 +1,907 @@
|
||||
package envoy
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// StoreDecoder is responsible for generating Envoy nodes from already stored
|
||||
// resources which can then be managed by Envoy and imported via an encoder.
|
||||
StoreDecoder struct{}
|
||||
|
||||
filterWrap struct {
|
||||
rt string
|
||||
f envoyx.ResourceFilter
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
paramsKeyStorer = "storer"
|
||||
paramsKeyDAL = "dal"
|
||||
)
|
||||
|
||||
var (
|
||||
// @todo temporary fix to make unused pkg/id not throw errors
|
||||
_ = id.Next
|
||||
)
|
||||
|
||||
// Decode returns a set of envoy nodes based on the provided params
|
||||
//
|
||||
// StoreDecoder expects the DecodeParam of `storer` and `dal` which conform
|
||||
// to the store.Storer and dal.FullService interfaces.
|
||||
func (d StoreDecoder) Decode(ctx context.Context, p envoyx.DecodeParams) (out envoyx.NodeSet, err error) {
|
||||
// @todo we can optionally not require them based on what we're doing
|
||||
s, err := d.getStorer(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dl, err := d.getDal(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return d.decode(ctx, s, dl, p)
|
||||
}
|
||||
|
||||
func (d StoreDecoder) decode(ctx context.Context, s store.Storer, dl dal.FullService, p envoyx.DecodeParams) (out envoyx.NodeSet, err error) {
|
||||
// Preprocessing and basic filtering (to omit what this decoder can't handle)
|
||||
wrappedFilters := d.prepFilters(p.Filter)
|
||||
|
||||
// Get all scoped nodes
|
||||
scopedNodes, err := d.getScopeNodes(ctx, s, dl, wrappedFilters)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get all reference nodes
|
||||
refNodes, refRefs, err := d.getReferenceNodes(ctx, s, dl, wrappedFilters)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Process filters to get the envoy nodes
|
||||
err = func() (err error) {
|
||||
var aux envoyx.NodeSet
|
||||
for i, wf := range wrappedFilters {
|
||||
switch wf.rt {
|
||||
case types.ChartResourceType:
|
||||
aux, err = d.decodeChart(ctx, s, dl, d.makeChartFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
case types.ModuleResourceType:
|
||||
aux, err = d.decodeModule(ctx, s, dl, d.makeModuleFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
case types.ModuleFieldResourceType:
|
||||
aux, err = d.decodeModuleField(ctx, s, dl, d.makeModuleFieldFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
case types.NamespaceResourceType:
|
||||
aux, err = d.decodeNamespace(ctx, s, dl, d.makeNamespaceFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
case types.PageResourceType:
|
||||
aux, err = d.decodePage(ctx, s, dl, d.makePageFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
case types.PageLayoutResourceType:
|
||||
aux, err = d.decodePageLayout(ctx, s, dl, d.makePageLayoutFilter(scopedNodes[i], refNodes[i], wf.f))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
default:
|
||||
aux, err = d.extendDecoder(ctx, s, dl, p, wf.rt, refNodes[i], wf.f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, a := range aux {
|
||||
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
|
||||
a.References = envoyx.MergeRefs(a.References, refRefs[i])
|
||||
}
|
||||
out = append(out, aux...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to decode filters")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource chart
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodeChart(ctx context.Context, s store.Storer, dl dal.FullService, f types.ChartFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposeCharts(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = ChartToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ChartToEnvoyNode(r *types.Chart) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.Handle,
|
||||
r.ID,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
if r.NamespaceID > 0 {
|
||||
refs["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:namespace",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.NamespaceID),
|
||||
}
|
||||
}
|
||||
|
||||
refs = envoyx.MergeRefs(refs, decodeChartRefs(r))
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: refs["NamespaceID"].ResourceType,
|
||||
Identifiers: refs["NamespaceID"].Identifiers,
|
||||
}
|
||||
for k, ref := range refs {
|
||||
// @todo temporary solution to not needlessly scope resources.
|
||||
// Optimally, this would be selectively handled by codegen.
|
||||
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Scope = scope
|
||||
refs[k] = ref
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.ChartResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makeChartFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.ChartFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
out.ChartID = id.Strings(ids...)
|
||||
|
||||
if len(hh) > 0 {
|
||||
out.Handle = hh[0]
|
||||
}
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
ar, ok = refs["NamespaceID"]
|
||||
if ok {
|
||||
out.NamespaceID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource module
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodeModule(ctx context.Context, s store.Storer, dl dal.FullService, f types.ModuleFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposeModules(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = ModuleToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
aux, err := d.extendedModuleDecoder(ctx, s, dl, f, out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, aux...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ModuleToEnvoyNode(r *types.Module) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.Handle,
|
||||
r.ID,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
if r.NamespaceID > 0 {
|
||||
refs["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:namespace",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.NamespaceID),
|
||||
}
|
||||
}
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: refs["NamespaceID"].ResourceType,
|
||||
Identifiers: refs["NamespaceID"].Identifiers,
|
||||
}
|
||||
for k, ref := range refs {
|
||||
// @todo temporary solution to not needlessly scope resources.
|
||||
// Optimally, this would be selectively handled by codegen.
|
||||
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Scope = scope
|
||||
refs[k] = ref
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makeModuleFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.ModuleFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
out.ModuleID = id.Strings(ids...)
|
||||
|
||||
if len(hh) > 0 {
|
||||
out.Handle = hh[0]
|
||||
}
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
ar, ok = refs["NamespaceID"]
|
||||
if ok {
|
||||
out.NamespaceID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
out = d.extendModuleFilter(scope, refs, auxf, out)
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource moduleField
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodeModuleField(ctx context.Context, s store.Storer, dl dal.FullService, f types.ModuleFieldFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposeModuleFields(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = ModuleFieldToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ModuleFieldToEnvoyNode(r *types.ModuleField) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.ID,
|
||||
r.Name,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
if r.ModuleID > 0 {
|
||||
refs["ModuleID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:module",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.ModuleID),
|
||||
}
|
||||
}
|
||||
|
||||
refs = envoyx.MergeRefs(refs, decodeModuleFieldRefs(r))
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: refs["NamespaceID"].ResourceType,
|
||||
Identifiers: refs["NamespaceID"].Identifiers,
|
||||
}
|
||||
for k, ref := range refs {
|
||||
// @todo temporary solution to not needlessly scope resources.
|
||||
// Optimally, this would be selectively handled by codegen.
|
||||
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Scope = scope
|
||||
refs[k] = ref
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.ModuleFieldResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Resource should define a custom filter builder
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource namespace
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodeNamespace(ctx context.Context, s store.Storer, dl dal.FullService, f types.NamespaceFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposeNamespaces(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = NamespaceToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NamespaceToEnvoyNode(r *types.Namespace) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.ID,
|
||||
r.Slug,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: ii,
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makeNamespaceFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.NamespaceFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
out.NamespaceID = id.Strings(ids...)
|
||||
|
||||
if len(hh) > 0 {
|
||||
out.Slug = hh[0]
|
||||
}
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
out = d.extendNamespaceFilter(scope, refs, auxf, out)
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource page
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodePage(ctx context.Context, s store.Storer, dl dal.FullService, f types.PageFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposePages(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = PageToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PageToEnvoyNode(r *types.Page) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.Handle,
|
||||
r.ID,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
if r.ModuleID > 0 {
|
||||
refs["ModuleID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:module",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.ModuleID),
|
||||
}
|
||||
}
|
||||
if r.NamespaceID > 0 {
|
||||
refs["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:namespace",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.NamespaceID),
|
||||
}
|
||||
}
|
||||
if r.SelfID > 0 {
|
||||
refs["SelfID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:page",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.SelfID),
|
||||
}
|
||||
}
|
||||
|
||||
refs = envoyx.MergeRefs(refs, decodePageRefs(r))
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: refs["NamespaceID"].ResourceType,
|
||||
Identifiers: refs["NamespaceID"].Identifiers,
|
||||
}
|
||||
for k, ref := range refs {
|
||||
// @todo temporary solution to not needlessly scope resources.
|
||||
// Optimally, this would be selectively handled by codegen.
|
||||
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Scope = scope
|
||||
refs[k] = ref
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.PageResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makePageFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.PageFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
out.PageID = id.Strings(ids...)
|
||||
|
||||
if len(hh) > 0 {
|
||||
out.Handle = hh[0]
|
||||
}
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
ar, ok = refs["ModuleID"]
|
||||
if ok {
|
||||
out.ModuleID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
ar, ok = refs["NamespaceID"]
|
||||
if ok {
|
||||
out.NamespaceID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
ar, ok = refs["SelfID"]
|
||||
if ok {
|
||||
out.ParentID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
out = d.extendPageFilter(scope, refs, auxf, out)
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource pageLayout
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) decodePageLayout(ctx context.Context, s store.Storer, dl dal.FullService, f types.PageLayoutFilter) (out envoyx.NodeSet, err error) {
|
||||
// @todo this might need to be improved.
|
||||
// Currently, no resource is vast enough to pose a problem.
|
||||
rr, _, err := store.SearchComposePageLayouts(ctx, s, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var n *envoyx.Node
|
||||
n, err = PageLayoutToEnvoyNode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PageLayoutToEnvoyNode(r *types.PageLayout) (node *envoyx.Node, err error) {
|
||||
// Identifiers
|
||||
ii := envoyx.MakeIdentifiers(
|
||||
r.Handle,
|
||||
r.ID,
|
||||
)
|
||||
|
||||
// Handle references
|
||||
// Omit any non-defined values
|
||||
refs := map[string]envoyx.Ref{}
|
||||
if r.NamespaceID > 0 {
|
||||
refs["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:namespace",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.NamespaceID),
|
||||
}
|
||||
}
|
||||
if r.OwnedBy > 0 {
|
||||
refs["OwnedBy"] = envoyx.Ref{
|
||||
ResourceType: "corteza::system:user",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.OwnedBy),
|
||||
}
|
||||
}
|
||||
if r.PageID > 0 {
|
||||
refs["PageID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:page",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.PageID),
|
||||
}
|
||||
}
|
||||
if r.ParentID > 0 {
|
||||
refs["ParentID"] = envoyx.Ref{
|
||||
ResourceType: "corteza::compose:page-layout",
|
||||
Identifiers: envoyx.MakeIdentifiers(r.ParentID),
|
||||
}
|
||||
}
|
||||
|
||||
var scope envoyx.Scope
|
||||
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: refs["NamespaceID"].ResourceType,
|
||||
Identifiers: refs["NamespaceID"].Identifiers,
|
||||
}
|
||||
for k, ref := range refs {
|
||||
// @todo temporary solution to not needlessly scope resources.
|
||||
// Optimally, this would be selectively handled by codegen.
|
||||
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Scope = scope
|
||||
refs[k] = ref
|
||||
}
|
||||
|
||||
node = &envoyx.Node{
|
||||
Resource: r,
|
||||
|
||||
ResourceType: types.PageLayoutResourceType,
|
||||
Identifiers: ii,
|
||||
References: refs,
|
||||
Scope: scope,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makePageLayoutFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.PageLayoutFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
out.PageLayoutID = id.Strings(ids...)
|
||||
|
||||
if len(hh) > 0 {
|
||||
out.Handle = hh[0]
|
||||
}
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
ar, ok = refs["NamespaceID"]
|
||||
if ok {
|
||||
out.NamespaceID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
ar, ok = refs["PageID"]
|
||||
if ok {
|
||||
out.PageID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
ar, ok = refs["ParentID"]
|
||||
if ok {
|
||||
out.ParentID = ar.Resource.GetID()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Utilities
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (d StoreDecoder) getStorer(p envoyx.DecodeParams) (s store.Storer, err error) {
|
||||
aux, ok := p.Params[paramsKeyStorer]
|
||||
if ok {
|
||||
s, ok = aux.(store.Storer)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.Errorf("store decoder expects a storer conforming to store.Storer interface")
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) getDal(p envoyx.DecodeParams) (dl dal.FullService, err error) {
|
||||
aux, ok := p.Params[paramsKeyDAL]
|
||||
if ok {
|
||||
dl, ok = aux.(dal.FullService)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.Errorf("store decoder expects a DAL conforming to dal.FullService interface")
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) prepFilters(ff map[string]envoyx.ResourceFilter) (out []filterWrap) {
|
||||
out = make([]filterWrap, 0, len(ff))
|
||||
for rt, f := range ff {
|
||||
// Handle resources that don't belong to this decoder
|
||||
if !strings.HasPrefix(rt, "corteza::compose") {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, filterWrap{rt: rt, f: f})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) getScopeNodes(ctx context.Context, s store.Storer, dl dal.FullService, ff []filterWrap) (scopes envoyx.NodeSet, err error) {
|
||||
// Get all requested scopes
|
||||
scopes = make(envoyx.NodeSet, len(ff))
|
||||
|
||||
err = func() (err error) {
|
||||
for i, fw := range ff {
|
||||
if fw.f.Scope.ResourceType == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// For now the scope can only point to namespace so this will do
|
||||
var nn envoyx.NodeSet
|
||||
nn, err = d.decodeNamespace(ctx, s, dl, d.makeNamespaceFilter(nil, nil, envoyx.ResourceFilter{Identifiers: fw.f.Scope.Identifiers}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(nn) > 1 {
|
||||
err = fmt.Errorf("ambiguous scope %v: matches multiple resources", fw.f.Scope)
|
||||
return
|
||||
}
|
||||
if len(nn) == 0 {
|
||||
err = fmt.Errorf("invalid scope %v: resource not found", fw.f)
|
||||
return
|
||||
}
|
||||
|
||||
scopes[i] = nn[0]
|
||||
}
|
||||
return
|
||||
}()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to decode node scopes")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getReferenceNodes returns all of the nodes referenced by the nodes defined by the filters
|
||||
//
|
||||
// The nodes are provided as a slice (the same order as the filters) and as a map for easier lookups.
|
||||
func (d StoreDecoder) getReferenceNodes(ctx context.Context, s store.Storer, dl dal.FullService, ff []filterWrap) (nodes []map[string]*envoyx.Node, refs []map[string]envoyx.Ref, err error) {
|
||||
nodes = make([]map[string]*envoyx.Node, len(ff))
|
||||
refs = make([]map[string]envoyx.Ref, len(ff))
|
||||
err = func() (err error) {
|
||||
for i, a := range ff {
|
||||
if len(a.f.Refs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
auxr := make(map[string]*envoyx.Node, len(a.f.Refs))
|
||||
auxa := make(map[string]envoyx.Ref)
|
||||
for field, ref := range a.f.Refs {
|
||||
f := ref.ResourceFilter()
|
||||
aux, err := d.decode(ctx, s, dl, envoyx.DecodeParams{
|
||||
Type: envoyx.DecodeTypeStore,
|
||||
Filter: f,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// @todo consider changing this.
|
||||
// Currently it's required because the .decode may return some
|
||||
// nested nodes as well.
|
||||
// Consider a flag or a new function.
|
||||
aux = envoyx.NodesForResourceType(ref.ResourceType, aux...)
|
||||
if len(aux) == 0 {
|
||||
return fmt.Errorf("invalid reference %v", ref)
|
||||
}
|
||||
if len(aux) > 1 {
|
||||
return fmt.Errorf("ambiguous reference: too many resources returned %v", a.f)
|
||||
}
|
||||
|
||||
auxr[field] = aux[0]
|
||||
auxa[field] = aux[0].ToRef()
|
||||
}
|
||||
|
||||
nodes[i] = auxr
|
||||
refs[i] = auxa
|
||||
}
|
||||
return
|
||||
}()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to decode node references")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
301
server/compose/envoy/store_decode.go
Normal file
301
server/compose/envoy/store_decode.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func (d StoreDecoder) extendNamespaceFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter, base types.NamespaceFilter) (out types.NamespaceFilter) {
|
||||
out = base
|
||||
|
||||
if scope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if scope.ResourceType == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite it
|
||||
out.NamespaceID = id.Strings(scope.Resource.GetID())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) extendModuleFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter, base types.ModuleFilter) (out types.ModuleFilter) {
|
||||
out = base
|
||||
|
||||
if scope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if scope.ResourceType == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite it
|
||||
out.NamespaceID = scope.Resource.GetID()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) extendPageFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter, base types.PageFilter) (out types.PageFilter) {
|
||||
out = base
|
||||
|
||||
if scope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if scope.ResourceType == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite it
|
||||
out.NamespaceID = scope.Resource.GetID()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) makeModuleFieldFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.ModuleFieldFilter) {
|
||||
out.Limit = auxf.Limit
|
||||
|
||||
ids, hh := auxf.Identifiers.Idents()
|
||||
_ = ids
|
||||
_ = hh
|
||||
|
||||
// Refs
|
||||
var (
|
||||
ar *envoyx.Node
|
||||
ok bool
|
||||
)
|
||||
_ = ar
|
||||
_ = ok
|
||||
|
||||
// ar, ok = refs["ModuleID"]
|
||||
// if ok {
|
||||
// out.ModuleID = ar.Resource.GetID()
|
||||
// }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) extendedModuleDecoder(ctx context.Context, s store.Storer, dl dal.FullService, f types.ModuleFilter, base envoyx.NodeSet) (out envoyx.NodeSet, err error) {
|
||||
var ff envoyx.NodeSet
|
||||
|
||||
for _, b := range base {
|
||||
mod := b.Resource.(*types.Module)
|
||||
|
||||
// Get all of the related module fields, append them to the output and
|
||||
// the original module (so other code can have access to the related fields)
|
||||
ff, err = d.decodeModuleField(ctx, s, dl, types.ModuleFieldFilter{ModuleID: []uint64{mod.ID}})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range ff {
|
||||
f.Scope = b.Scope
|
||||
f.References = envoyx.MergeRefs(f.References, b.References, map[string]envoyx.Ref{
|
||||
"ModuleID": b.ToRef(),
|
||||
})
|
||||
for k, ref := range f.References {
|
||||
ref.Scope = b.Scope
|
||||
f.References[k] = ref
|
||||
}
|
||||
|
||||
mod.Fields = append(mod.Fields, f.Resource.(*types.ModuleField))
|
||||
}
|
||||
|
||||
out = append(out, ff...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func decodeChartRefs(c *types.Chart) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref, len(c.Config.Reports))
|
||||
|
||||
for i, r := range c.Config.Reports {
|
||||
if r.ModuleID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Config.Reports.%d.ModuleID", i)] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(r.ModuleID),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func decodeModuleFieldRefs(c *types.ModuleField) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref, 1)
|
||||
|
||||
refs["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(c.NamespaceID),
|
||||
}
|
||||
|
||||
id := c.Options.UInt64("moduleID")
|
||||
if id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
refs["Options.ModuleID"] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func decodePageRefs(p *types.Page) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref, len(p.Blocks)/2)
|
||||
|
||||
for index, b := range p.Blocks {
|
||||
switch b.Kind {
|
||||
case "RecordList":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index))
|
||||
|
||||
case "Automation":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index))
|
||||
|
||||
case "RecordOrganizer":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index))
|
||||
|
||||
case "Chart":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index))
|
||||
|
||||
case "Calendar":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index))
|
||||
|
||||
case "Metric":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index))
|
||||
|
||||
case "Comment":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index))
|
||||
|
||||
case "Progress":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) extendDecoder(ctx context.Context, s store.Storer, dl dal.FullService, p envoyx.DecodeParams, rt string, refs map[string]*envoyx.Node, rf envoyx.ResourceFilter) (out envoyx.NodeSet, err error) {
|
||||
switch rt {
|
||||
// @todo consider hooking into the regular record resource type as well
|
||||
case ComposeRecordDatasourceAuxType:
|
||||
return d.decodeRecordDatasource(ctx, s, dl, p, refs, rf)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d StoreDecoder) decodeRecordDatasource(ctx context.Context, s store.Storer, dl dal.FullService, p envoyx.DecodeParams, refs map[string]*envoyx.Node, rf envoyx.ResourceFilter) (out envoyx.NodeSet, err error) {
|
||||
var (
|
||||
ok bool
|
||||
|
||||
module *types.Module
|
||||
moduleNode *envoyx.Node
|
||||
namespace *types.Namespace
|
||||
namespaceNode *envoyx.Node
|
||||
)
|
||||
|
||||
// Get the refs
|
||||
namespaceNode, ok = refs["NamespaceID"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("missing NamespaceID reference")
|
||||
return
|
||||
}
|
||||
namespace = namespaceNode.Resource.(*types.Namespace)
|
||||
|
||||
moduleNode, ok = refs["ModuleID"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("missing ModuleID reference")
|
||||
return
|
||||
}
|
||||
module = moduleNode.Resource.(*types.Module)
|
||||
|
||||
// Get the iterator
|
||||
iter, _, err := dalutils.ComposeRecordsIterator(ctx, dl, module, types.RecordFilter{
|
||||
Query: rf.Query,
|
||||
ModuleID: module.ID,
|
||||
NamespaceID: namespace.ID,
|
||||
Paging: filter.Paging{
|
||||
Limit: rf.Limit,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mv := make(map[string]bool)
|
||||
dt := make(map[string]bool)
|
||||
for _, f := range module.Fields {
|
||||
if f.Multi {
|
||||
mv[f.Name] = true
|
||||
}
|
||||
if f.Kind == "DateTime" {
|
||||
dt[f.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range []string{"createdAt", "updatedAt", "deletedAt"} {
|
||||
dt[n] = true
|
||||
}
|
||||
|
||||
rds := &RecordDatasource{
|
||||
Provider: &iteratorProvider{iter: iter},
|
||||
multivalues: mv,
|
||||
datetimeFields: dt,
|
||||
|
||||
refToID: make(map[string]uint64),
|
||||
existingIDs: make(map[uint64]bool),
|
||||
// @todo consider providing defaults from the outside
|
||||
Mapping: envoyx.DatasourceMapping{
|
||||
KeyField: []string{"id"},
|
||||
Defaultable: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Get access controller to enforce field-level read permissions
|
||||
ac := service.AccessControl(s)
|
||||
if rbac.Global() == nil {
|
||||
ac = nil
|
||||
}
|
||||
|
||||
rds.Provider, err = mkIteratorProvider(ctx, ac, s, dl, iter, module, cast.ToBool(p.Params["resolveRefs"]), cast.ToBool(p.Params["includeRefID"]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rr := map[string]envoyx.Ref{
|
||||
"NamespaceID": namespaceNode.ToRef(),
|
||||
"ModuleID": moduleNode.ToRef(),
|
||||
}
|
||||
|
||||
out = append(out, &envoyx.Node{
|
||||
Datasource: rds,
|
||||
ResourceType: ComposeRecordDatasourceAuxType,
|
||||
|
||||
Identifiers: envoyx.MakeIdentifiers(module.ID, module.Handle),
|
||||
References: rr,
|
||||
Scope: moduleNode.Scope,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
1552
server/compose/envoy/store_encode.gen.go
generated
Normal file
1552
server/compose/envoy/store_encode.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
233
server/compose/envoy/store_encode.go
Normal file
233
server/compose/envoy/store_encode.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (e StoreEncoder) encode(ctx context.Context, p envoyx.EncodeParams, s store.Storer, rt string, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
|
||||
dl, err := e.grabDal(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch rt {
|
||||
case ComposeRecordDatasourceAuxType:
|
||||
err = e.encodeRecordDatasources(ctx, p, s, dl, nn, tree)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setChartDefaults(res *types.Chart) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validateChart(*types.Chart) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setModuleDefaults(res *types.Module) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validateModule(*types.Module) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setModuleFieldDefaults(res *types.ModuleField) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
if res.Kind == "" {
|
||||
res.Kind = "String"
|
||||
}
|
||||
|
||||
// Update validator ID
|
||||
maxValidatorID := uint64(0)
|
||||
for _, v := range res.Expressions.Validators {
|
||||
if v.ValidatorID > maxValidatorID {
|
||||
maxValidatorID = v.ValidatorID
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range res.Expressions.Validators {
|
||||
if v.ValidatorID == 0 {
|
||||
v.ValidatorID = maxValidatorID + 1
|
||||
maxValidatorID++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) sanitizeModuleFieldBeforeSave(f *types.ModuleField) (err error) {
|
||||
delete(f.Options, "module")
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validateModuleField(*types.ModuleField) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setNamespaceDefaults(res *types.Namespace) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validateNamespace(*types.Namespace) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setPageDefaults(res *types.Page) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
if res.Title == "" {
|
||||
res.Title = res.Handle
|
||||
}
|
||||
|
||||
// Update pageblock ID
|
||||
maxPageBlockID := uint64(0)
|
||||
for _, b := range res.Blocks {
|
||||
if b.BlockID > maxPageBlockID {
|
||||
maxPageBlockID = b.BlockID
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range res.Blocks {
|
||||
if b.BlockID == 0 {
|
||||
b.BlockID = maxPageBlockID + 1
|
||||
maxPageBlockID++
|
||||
res.Blocks[i] = b
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validatePage(*types.Page) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setPageLayoutDefaults(res *types.PageLayout) (err error) {
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
// @note these pageblocks reference the ones define on the page so nothing
|
||||
// to do here.
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validatePageLayout(*types.PageLayout) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) setRecordDefaults(*types.Record) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) validateRecord(*types.Record) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) prepare(ctx context.Context, p envoyx.EncodeParams, s store.Storer, rt string, nn envoyx.NodeSet) (err error) {
|
||||
dl, err := e.grabDal(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch rt {
|
||||
case ComposeRecordDatasourceAuxType:
|
||||
return e.prepareRecordDatasource(ctx, p, s, dl, nn)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) encodeModuleExtend(ctx context.Context, p envoyx.EncodeParams, s store.Storer, n *envoyx.Node, nested envoyx.NodeSet, tree envoyx.Traverser) (err error) {
|
||||
|
||||
// Push fields under mod
|
||||
mod := n.Resource.(*types.Module)
|
||||
for _, n := range nested {
|
||||
if n.ResourceType != types.ModuleFieldResourceType {
|
||||
continue
|
||||
}
|
||||
|
||||
mod.Fields = append(mod.Fields, n.Resource.(*types.ModuleField))
|
||||
}
|
||||
|
||||
// Register to DAL
|
||||
dl, err := e.grabDal(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nsNode := tree.ParentForRef(n, n.References["NamespaceID"])
|
||||
ns := nsNode.Resource.(*types.Namespace)
|
||||
|
||||
// @todo get connection and things from there
|
||||
models, err := service.ModulesToModelSet(dl, ns, mod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// @note there is only one model so this is ok
|
||||
_, err = dl.ReplaceModel(ctx, nil, models[0])
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) postModulesEncode(ctx context.Context, p envoyx.EncodeParams, s store.Storer, tree envoyx.Traverser, nn envoyx.NodeSet) (err error) {
|
||||
dl, err := e.grabDal(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range nn {
|
||||
cc := tree.ChildrenForResourceType(n, ComposeRecordDatasourceAuxType)
|
||||
err = e.encodeRecordDatasources(ctx, p, s, dl, cc, tree)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *StoreEncoder) grabDal(p envoyx.EncodeParams) (dl dal.FullService, err error) {
|
||||
auxs, ok := p.Params[paramsKeyDAL]
|
||||
if !ok {
|
||||
err = errors.Errorf("store encoder expects a dal conforming to dal.FullService interface")
|
||||
return
|
||||
}
|
||||
|
||||
dl, ok = auxs.(dal.FullService)
|
||||
if !ok {
|
||||
err = errors.Errorf("store encoder expects a dal conforming to dal.FullService interface")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
420
server/compose/envoy/store_encode_datasource.go
Normal file
420
server/compose/envoy/store_encode_datasource.go
Normal file
@@ -0,0 +1,420 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/values"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx/datasource"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemEnvoy "github.com/cortezaproject/corteza/server/system/envoy"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
const (
|
||||
recordBatchMaxChunk = 100
|
||||
)
|
||||
|
||||
var (
|
||||
rvSanitizer = values.Sanitizer()
|
||||
rvValidator = values.Validator()
|
||||
rvFormatter = values.Formatter()
|
||||
)
|
||||
|
||||
func (e StoreEncoder) prepareRecordDatasource(ctx context.Context, p envoyx.EncodeParams, s store.Storer, dl dal.FullService, nn envoyx.NodeSet) (err error) {
|
||||
// @todo match existing records; for now use just the ID like V1
|
||||
|
||||
for _, n := range nn {
|
||||
if n.Datasource == nil {
|
||||
panic("unexpected state: cannot call prepareRecordDatasource with nodes without a defined Datasource")
|
||||
}
|
||||
|
||||
ds, ok := n.Datasource.(*RecordDatasource)
|
||||
if !ok {
|
||||
panic("unexpected datasource type: node expecting type of RecordDatasource")
|
||||
}
|
||||
|
||||
err = e.prepareRecords(ctx, p, s, dl, ds, nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ds.Reset(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) prepareRecords(ctx context.Context, p envoyx.EncodeParams, s store.Storer, dl dal.FullService, ds *RecordDatasource, nn envoyx.NodeSet) (err error) {
|
||||
var (
|
||||
aux = make(datasource.RawRecord)
|
||||
more bool
|
||||
ident []string
|
||||
)
|
||||
|
||||
ds.refToID = make(map[string]uint64)
|
||||
ds.existingIDs = make(map[uint64]bool)
|
||||
|
||||
// Just so we don't need to do a branch later down the line
|
||||
cex := ds.CheckExisting
|
||||
if cex == nil || len(ds.Mapping.KeyField) == 0 {
|
||||
cex = func(ctx context.Context, ref ...[]string) ([]uint64, error) {
|
||||
return make([]uint64, len(ref)), nil
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
ident, more, err = ds.Next(ctx, aux)
|
||||
if err != nil || !more {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo we'll need to batch these up
|
||||
existing, err := cex(ctx, ident)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing[0] != 0 {
|
||||
ds.existingIDs[existing[0]] = true
|
||||
}
|
||||
|
||||
if existing[0] == 0 {
|
||||
existing[0] = id.Next()
|
||||
}
|
||||
|
||||
ds.AddRef(existing[0], ident...)
|
||||
|
||||
// Construct a simple record for basic validation/preprocessing
|
||||
rec := types.Record{}
|
||||
for k, vv := range aux {
|
||||
for i, v := range vv.Values {
|
||||
// Ignore errors at this point since some values can't yet be properly casted
|
||||
rec.SetValue(k, uint(i), v)
|
||||
}
|
||||
}
|
||||
|
||||
// @note defaults and validation will have to happen again when encoding
|
||||
// since we won't persist it.
|
||||
// Consider supporting updating the datasource's data.
|
||||
err = e.setRecordDefaults(&rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.validateRecord(&rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e StoreEncoder) encodeRecordDatasources(ctx context.Context, p envoyx.EncodeParams, s store.Storer, dl dal.FullService, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
|
||||
for _, n := range nn {
|
||||
err = e.encodeRecordDatasource(ctx, p, s, dl, n, tree)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) encodeRecordDatasource(ctx context.Context, p envoyx.EncodeParams, s store.Storer, dl dal.FullService, n *envoyx.Node, tree envoyx.Traverser) (err error) {
|
||||
var (
|
||||
auxRec = make(datasource.RawRecord)
|
||||
more bool
|
||||
ident []string
|
||||
|
||||
nsNode *envoyx.Node
|
||||
ns *types.Namespace
|
||||
modNode *envoyx.Node
|
||||
mod *types.Module
|
||||
|
||||
// This was already validated so we can blindly cast
|
||||
ds = n.Datasource.(*RecordDatasource)
|
||||
)
|
||||
|
||||
// Get the parent namespace
|
||||
nsNode = tree.ParentForRef(n, n.References["NamespaceID"])
|
||||
if nsNode == nil {
|
||||
err = fmt.Errorf("cannot encode record datasource: missing reference for NamespaceID")
|
||||
return
|
||||
}
|
||||
ns = nsNode.Resource.(*types.Namespace)
|
||||
|
||||
// Get the parent module
|
||||
modNode = tree.ParentForRef(n, n.References["ModuleID"])
|
||||
if modNode == nil {
|
||||
err = fmt.Errorf("cannot encode record datasource: missing reference for ModuleID")
|
||||
return
|
||||
}
|
||||
mod = modNode.Resource.(*types.Module)
|
||||
|
||||
// Prepare getters for related resources
|
||||
recordGetters := e.makeRecordGetters(dl, tree, n)
|
||||
userGetters, sysUserFields := e.makeUserGetters(s, dl, tree, mod, n)
|
||||
|
||||
maykr := e.recordMaker(ns, mod, recordGetters, userGetters, sysUserFields)
|
||||
|
||||
var (
|
||||
rec types.Record
|
||||
creates types.RecordSet
|
||||
updates types.RecordSet
|
||||
rve *types.RecordValueErrorSet
|
||||
)
|
||||
for {
|
||||
ident, more, err = ds.Next(ctx, auxRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
|
||||
err = func() (err error) {
|
||||
rec, err = maykr(ctx, auxRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Do these at the end so they can't be overwritten
|
||||
rec.NamespaceID = ns.ID
|
||||
rec.ModuleID = mod.ID
|
||||
rec.CreatedAt = time.Now()
|
||||
rec.OwnedBy = service.CalcRecordOwner(0, rec.OwnedBy, p.Encoder.DefaultUserID)
|
||||
rec.CreatedBy = e.getCreatedBy(ctx, rec)
|
||||
rec.ID, err = ds.ResolveRefS(ident...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Standard record processing
|
||||
rec.Values.SetUpdatedFlag(true)
|
||||
//
|
||||
rve = service.RecordValueUpdateOpCheck(ctx, nil, mod, rec.Values)
|
||||
if !rve.IsValid() {
|
||||
return rve
|
||||
}
|
||||
//
|
||||
rve = service.RecordPreparer(ctx, s, rvSanitizer, rvValidator, rvFormatter, mod, &rec)
|
||||
if !rve.IsValid() {
|
||||
return rve
|
||||
}
|
||||
|
||||
ax := rec
|
||||
|
||||
if ds.existingIDs[rec.ID] {
|
||||
updates = append(updates, &ax)
|
||||
} else {
|
||||
creates = append(creates, &ax)
|
||||
}
|
||||
|
||||
if len(creates) > recordBatchMaxChunk {
|
||||
err = dalutils.ComposeRecordCreate(ctx, dl, mod, creates...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
creates = make(types.RecordSet, 0, recordBatchMaxChunk/2)
|
||||
}
|
||||
|
||||
if len(updates) > recordBatchMaxChunk {
|
||||
err = dalutils.ComposeRecordUpdate(ctx, dl, mod, updates...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
updates = make(types.RecordSet, 0, recordBatchMaxChunk/2)
|
||||
}
|
||||
return
|
||||
}()
|
||||
|
||||
if p.Defer != nil {
|
||||
p.Defer()
|
||||
}
|
||||
if err != nil {
|
||||
if p.DeferNok != nil {
|
||||
err = p.DeferNok(err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if p.DeferOk != nil {
|
||||
p.DeferOk()
|
||||
}
|
||||
}
|
||||
|
||||
if len(creates) > 0 {
|
||||
err = dalutils.ComposeRecordCreate(ctx, dl, mod, creates...)
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
err = dalutils.ComposeRecordUpdate(ctx, dl, mod, updates...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) getCreatedBy(ctx context.Context, rec types.Record) uint64 {
|
||||
if rec.CreatedBy > 0 {
|
||||
return rec.CreatedBy
|
||||
}
|
||||
|
||||
idt := auth.GetIdentityFromContext(ctx)
|
||||
if idt == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return idt.Identity()
|
||||
}
|
||||
|
||||
// maceRecordGetters returns a map of getters where the key is the field name
|
||||
func (e StoreEncoder) makeRecordGetters(dl dal.FullService, tree envoyx.Traverser, n *envoyx.Node) (getters map[string]*recordGetter) {
|
||||
var (
|
||||
modIndex = make(map[string]envoyx.Ref)
|
||||
dsIndex = make(map[string]envoyx.Ref)
|
||||
)
|
||||
|
||||
// First pass collects all the references
|
||||
for k, v := range n.References {
|
||||
if k == "NamespaceID" || k == "ModuleID" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Encoded as <fieldName>.<refKind>
|
||||
pp := strings.Split(k, ".")
|
||||
f := pp[0]
|
||||
kind := pp[1]
|
||||
|
||||
switch kind {
|
||||
case "module":
|
||||
modIndex[f] = v
|
||||
case "datasource":
|
||||
dsIndex[f] = v
|
||||
}
|
||||
}
|
||||
|
||||
// - second pass makes the getters
|
||||
getters = make(map[string]*recordGetter)
|
||||
for k := range modIndex {
|
||||
getters[k] = makeRecordGetter(dl, tree, n, modIndex[k], dsIndex[k])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sysUserRefFields are the record system user fields in the spellings
|
||||
// types.Record.SetValue understands
|
||||
var sysUserRefFields = []string{
|
||||
"ownedBy", "OwnedBy", "owned_by",
|
||||
"createdBy", "CreatedBy", "created_by",
|
||||
"updatedBy", "UpdatedBy", "updated_by",
|
||||
"deletedBy", "DeletedBy", "deleted_by",
|
||||
}
|
||||
|
||||
func (e StoreEncoder) makeUserGetters(s store.Storer, dl dal.FullService, tree envoyx.Traverser, mod *types.Module, n *envoyx.Node) (getters map[string]*systemEnvoy.UserGetter, sysFields map[string]bool) {
|
||||
userGetter := systemEnvoy.MakeUserGetter(s, tree)
|
||||
|
||||
getters = make(map[string]*systemEnvoy.UserGetter, len(sysUserRefFields))
|
||||
sysFields = make(map[string]bool, len(sysUserRefFields))
|
||||
for _, name := range sysUserRefFields {
|
||||
getters[name] = userGetter
|
||||
sysFields[name] = true
|
||||
}
|
||||
|
||||
// Module fields take precedence over the system field spellings; only
|
||||
// User kind fields resolve through the getter
|
||||
for _, f := range mod.Fields {
|
||||
if f.Kind == "User" {
|
||||
getters[f.Name] = userGetter
|
||||
} else {
|
||||
delete(getters, f.Name)
|
||||
}
|
||||
delete(sysFields, f.Name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e StoreEncoder) recordMaker(ns *types.Namespace, mod *types.Module, recordGetters map[string]*recordGetter, userGetters map[string]*systemEnvoy.UserGetter, sysUserFields map[string]bool) func(ctx context.Context, auxRec datasource.RawRecord) (r types.Record, err error) {
|
||||
return func(ctx context.Context, auxRec datasource.RawRecord) (rec types.Record, err error) {
|
||||
// Iterate mapified record values and populate the provided values
|
||||
var auxvv types.RecordValueSet
|
||||
for k, vv := range auxRec {
|
||||
auxvv = nil
|
||||
|
||||
if rg := recordGetters[k]; rg != nil {
|
||||
auxvv, err = resolveRefs(ctx, rg.resolve, k, vv.Values)
|
||||
} else if ug := userGetters[k]; ug != nil {
|
||||
vals := vv.Values
|
||||
// system fields hold a single user; the first value wins
|
||||
if sysUserFields[k] && len(vals) > 1 {
|
||||
vals = vals[:1]
|
||||
}
|
||||
auxvv, err = resolveRefs(ctx, ug.Resolve, k, vals)
|
||||
} else {
|
||||
// Default is regular values
|
||||
for i, v := range vv.Values {
|
||||
err = rec.SetValue(k, uint(i), v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, auxv := range auxvv {
|
||||
err = rec.SetValue(k, auxv.Place, auxv.Ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// resolveRefs resolves reference values (user or record identifiers) into IDs
|
||||
func resolveRefs(ctx context.Context, resolve func(context.Context, any) (uint64, error), k string, vals []string) (out types.RecordValueSet, err error) {
|
||||
var (
|
||||
ref uint64
|
||||
place uint
|
||||
)
|
||||
|
||||
for _, v := range vals {
|
||||
// mini-csv decoding can produce empty entries; nothing to resolve
|
||||
if strings.TrimSpace(v) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ref, err = resolve(ctx, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = append(out, &types.RecordValue{
|
||||
Name: k,
|
||||
Place: place,
|
||||
Ref: ref,
|
||||
Value: cast.ToString(ref),
|
||||
})
|
||||
place++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
50
server/compose/envoy/timezone.go
Normal file
50
server/compose/envoy/timezone.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// resolveTimezone reads the "timezone" param and returns the loaded location.
|
||||
// Returns a nil location when no timezone is set. An unresolvable timezone is
|
||||
// reported as an error so the export fails loudly instead of silently skipping
|
||||
// the conversion.
|
||||
func resolveTimezone(p envoyx.EncodeParams) (*time.Location, error) {
|
||||
tz := cast.ToString(p.Params["timezone"])
|
||||
if tz == "" {
|
||||
return nil, nil
|
||||
}
|
||||
loc, err := time.LoadLocation(tz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid export timezone %q: %w", tz, err)
|
||||
}
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
// formatInTimezone reparses v as a datetime and reformats it in loc.
|
||||
// On parse failure returns v unchanged so values are never dropped.
|
||||
func formatInTimezone(v string, loc *time.Location) string {
|
||||
if loc == nil || v == "" {
|
||||
return v
|
||||
}
|
||||
|
||||
layouts := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05.999999999 -0700 MST", "2006-01-02 15:04:05 -0700 MST", "2006-01-02T15:04:05Z"}
|
||||
for _, ly := range layouts {
|
||||
if t, err := time.Parse(ly, v); err == nil {
|
||||
return t.In(loc).Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to zone-less layouts; assume values are stored in UTC.
|
||||
naiveLayouts := []string{"2006-01-02T15:04:05.999999999", "2006-01-02T15:04:05", "2006-01-02 15:04:05.999999999", "2006-01-02 15:04:05"}
|
||||
for _, ly := range naiveLayouts {
|
||||
if t, err := time.ParseInLocation(ly, v, time.UTC); err == nil {
|
||||
return t.In(loc).Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
2571
server/compose/envoy/yaml_decode.gen.go
generated
Normal file
2571
server/compose/envoy/yaml_decode.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
492
server/compose/envoy/yaml_decode.go
Normal file
492
server/compose/envoy/yaml_decode.go
Normal file
@@ -0,0 +1,492 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
automationTypes "github.com/cortezaproject/corteza/server/automation/types"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/y7s"
|
||||
"github.com/modern-go/reflect2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ComposeRecordDatasourceAuxType = "corteza::compose:record-datasource"
|
||||
)
|
||||
|
||||
func (d *auxYamlDoc) unmarshalYAML(k string, n *yaml.Node) (out envoyx.NodeSet, err error) { return }
|
||||
|
||||
func (d *auxYamlDoc) unmarshalChartConfigNode(r *types.Chart, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
err = y7s.EachMap(n, func(k, v *yaml.Node) error {
|
||||
if k.Value != "reports" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if y7s.IsSeq(v) {
|
||||
var (
|
||||
auxRefs = make(map[string]envoyx.Ref)
|
||||
auxIdents envoyx.Identifiers
|
||||
i = -1
|
||||
)
|
||||
err = y7s.EachSeq(v, func(c *yaml.Node) error {
|
||||
i++
|
||||
|
||||
auxRefs, auxIdents, err = d.unmarshalChartConfigReportNode(r, c, i)
|
||||
refs = envoyx.MergeRefs(refs, auxRefs)
|
||||
idents = idents.Merge(auxIdents)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
refs, idents, err = d.unmarshalChartConfigReportNode(r, v, 0)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalChartConfigReportNode(r *types.Chart, n *yaml.Node, index int) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
err = y7s.EachMap(n, func(k, v *yaml.Node) error {
|
||||
switch strings.ToLower(k.Value) {
|
||||
case "module", "mod", "moduleid", "module_id":
|
||||
var auxi any
|
||||
y7s.DecodeScalar(v, "moduleID", &auxi)
|
||||
refs = map[string]envoyx.Ref{
|
||||
fmt.Sprintf("Config.Reports.%d.ModuleID", index): {
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(auxi),
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalPageBlocksNode(r *types.Page, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
refs = map[string]envoyx.Ref{}
|
||||
|
||||
for index, b := range r.Blocks {
|
||||
switch b.Kind {
|
||||
case "RecordList":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index))
|
||||
|
||||
case "Automation":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index))
|
||||
|
||||
case "RecordOrganizer":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index))
|
||||
|
||||
case "Chart":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index))
|
||||
|
||||
case "Calendar":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index))
|
||||
|
||||
case "Metric":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index))
|
||||
|
||||
case "Comment":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index))
|
||||
|
||||
case "Progress":
|
||||
refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockRecordListRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
id := optString(b.Options, "module", "moduleID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockChartRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
id := optString(b.Options, "chart", "chartID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.ChartID", index)] = envoyx.Ref{
|
||||
ResourceType: types.ChartResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockCalendarRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
ff, _ := b.Options["feeds"].([]interface{})
|
||||
for j, f := range ff {
|
||||
feed, _ := f.(map[string]interface{})
|
||||
opt, _ := (feed["options"]).(map[string]interface{})
|
||||
|
||||
id := optString(opt, "module", "moduleID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, j)] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockMetricRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
mm, _ := b.Options["metrics"].([]interface{})
|
||||
for j, m := range mm {
|
||||
mops, _ := m.(map[string]interface{})
|
||||
|
||||
id := optString(mops, "module", "moduleID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, j)] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockCommentRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
// Same difference
|
||||
return getPageBlockRecordListRefs(b, index)
|
||||
}
|
||||
|
||||
func getPageBlockProgressRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
var aux *envoyx.Ref
|
||||
|
||||
aux = getPageBlockProgressValueRefs(b.Options["minValue"])
|
||||
if aux != nil {
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.minValue.ModuleID", index)] = *aux
|
||||
}
|
||||
|
||||
aux = getPageBlockProgressValueRefs(b.Options["maxValue"])
|
||||
if aux != nil {
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.maxValue.ModuleID", index)] = *aux
|
||||
}
|
||||
|
||||
aux = getPageBlockProgressValueRefs(b.Options["value"])
|
||||
if aux != nil {
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.value.ModuleID", index)] = *aux
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPageBlockProgressValueRefs(val any) (ref *envoyx.Ref) {
|
||||
if reflect2.IsNil(val) {
|
||||
return
|
||||
}
|
||||
|
||||
opt, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
id := optString(opt, "module", "moduleID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
return &envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
}
|
||||
|
||||
func getPageBlockRecordOrganizerRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
// Same difference
|
||||
return getPageBlockRecordListRefs(b, index)
|
||||
}
|
||||
|
||||
func getPageBlockAutomationRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
bb, _ := b.Options["buttons"].([]interface{})
|
||||
for buttonIx, b := range bb {
|
||||
button, _ := b.(map[string]interface{})
|
||||
id := optString(button, "workflow", "workflowID")
|
||||
if id == "" || id == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, buttonIx)] = envoyx.Ref{
|
||||
ResourceType: automationTypes.WorkflowResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(id),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func optString(opt map[string]interface{}, kk ...string) string {
|
||||
for _, k := range kk {
|
||||
if vr, has := opt[k]; has {
|
||||
v, _ := vr.(string)
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalModuleFieldOptionsNode(r *types.ModuleField, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
refs = make(map[string]envoyx.Ref)
|
||||
|
||||
err = y7s.EachMap(n, func(k, v *yaml.Node) error {
|
||||
switch strings.ToLower(k.Value) {
|
||||
case "module", "module_id", "moduleid":
|
||||
var aux any
|
||||
err = y7s.DecodeScalar(v, "moduleID", &aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refs["Options.ModuleID"] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(aux),
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalModuleFieldDefaultValueNode(r *types.ModuleField, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
var rvs = types.RecordValueSet{}
|
||||
|
||||
switch n.Kind {
|
||||
case yaml.ScalarNode:
|
||||
rvs = rvs.Set(&types.RecordValue{Value: n.Value})
|
||||
|
||||
case yaml.SequenceNode:
|
||||
_ = y7s.EachSeq(n, func(v *yaml.Node) error {
|
||||
rvs = rvs.Set(&types.RecordValue{Value: n.Value, Place: uint(len(rvs))})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
r.DefaultValue = rvs
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalModuleFieldExpressionsNode(r *types.ModuleField, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) {
|
||||
|
||||
err = y7s.EachMap(n, func(k *yaml.Node, v *yaml.Node) error {
|
||||
switch k.Value {
|
||||
case "sanitizer":
|
||||
var aux string
|
||||
err = y7s.DecodeScalar(v, "sanitizer", &aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Expressions.Sanitizers = append(r.Expressions.Sanitizers, aux)
|
||||
|
||||
case "sanitizers":
|
||||
return y7s.EachSeq(v, func(san *yaml.Node) error {
|
||||
r.Expressions.Sanitizers = append(r.Expressions.Sanitizers, san.Value)
|
||||
return nil
|
||||
})
|
||||
|
||||
case "validator":
|
||||
var aux types.ModuleFieldValidator
|
||||
err = v.Decode(&aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Expressions.Validators = append(r.Expressions.Validators, aux)
|
||||
|
||||
case "validators":
|
||||
return y7s.Each(v, func(k *yaml.Node, v *yaml.Node) error {
|
||||
var aux types.ModuleFieldValidator
|
||||
if y7s.IsKind(v, yaml.MappingNode) {
|
||||
err = v.Decode(&aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
aux.Test = k.Value
|
||||
aux.Error = v.Value
|
||||
}
|
||||
|
||||
r.Expressions.Validators = append(r.Expressions.Validators, aux)
|
||||
return nil
|
||||
})
|
||||
|
||||
case "formatter":
|
||||
r.Expressions.Formatters = append(r.Expressions.Formatters, v.Value)
|
||||
return nil
|
||||
|
||||
case "formatters":
|
||||
return y7s.EachSeq(v, func(san *yaml.Node) error {
|
||||
r.Expressions.Formatters = append(r.Expressions.Formatters, san.Value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) postProcessNestedModuleNodes(nn envoyx.NodeSet) (out envoyx.NodeSet, err error) {
|
||||
// Get all references from all module fields
|
||||
refs := make(map[string]envoyx.Ref)
|
||||
for _, n := range nn {
|
||||
if n.ResourceType != types.ModuleFieldResourceType {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(n.References) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
r, ok := n.References["Options.ModuleID"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
refs[fmt.Sprintf("%s.module", n.Identifiers.FriendlyIdentifier())] = r
|
||||
refs[fmt.Sprintf("%s.datasource", n.Identifiers.FriendlyIdentifier())] = envoyx.Ref{
|
||||
ResourceType: ComposeRecordDatasourceAuxType,
|
||||
Identifiers: r.Identifiers,
|
||||
Scope: r.Scope,
|
||||
Optional: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Update datasources with references
|
||||
|
||||
for _, n := range nn {
|
||||
if n.Datasource == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
n.References = envoyx.MergeRefs(n.References, refs)
|
||||
}
|
||||
out = nn
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalPagesExtendedNode(dctx documentContext, n *yaml.Node, meta ...*yaml.Node) (out envoyx.NodeSet, err error) {
|
||||
return d.unmarshalPageNode(dctx, n, meta...)
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) unmarshalSourceExtendedNode(dctx documentContext, n *yaml.Node, meta ...*yaml.Node) (out envoyx.NodeSet, err error) {
|
||||
var r envoyx.DatasourceMapping
|
||||
|
||||
// @todo we're omitting errors because there will be a bunch due to invalid
|
||||
// resource field types. This might be a bit unstable as other errors may
|
||||
// also get ignored.
|
||||
//
|
||||
// A potential fix would be to firstly unmarshal into an any, check errors
|
||||
// and then unmarshal into the resource while omitting errors.
|
||||
n.Decode(&r)
|
||||
|
||||
err = y7s.EachMap(n, func(k, n *yaml.Node) error {
|
||||
var auxNodeValue any
|
||||
_ = auxNodeValue
|
||||
|
||||
switch strings.ToLower(k.Value) {
|
||||
case "origin", "from":
|
||||
err = y7s.DecodeScalar(n, "origin", &r.SourceIdent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "key", "index", "pk":
|
||||
if !y7s.IsKind(n, yaml.SequenceNode) {
|
||||
r.KeyField = []string{n.Value}
|
||||
} else {
|
||||
r.KeyField = make([]string, 0, 3)
|
||||
y7s.EachSeq(n, func(n *yaml.Node) error {
|
||||
r.KeyField = append(r.KeyField, n.Value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
case "map":
|
||||
return n.Decode(&r.Mapping)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// @todo for now we only support record datasources; extend when needed
|
||||
auxN := &envoyx.Node{
|
||||
Datasource: &RecordDatasource{
|
||||
Mapping: r,
|
||||
},
|
||||
|
||||
ResourceType: ComposeRecordDatasourceAuxType,
|
||||
Identifiers: dctx.parentIdent,
|
||||
}
|
||||
auxN.References, auxN.Scope = d.procMappingRefs(r.References)
|
||||
out = append(out, auxN)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *auxYamlDoc) procMappingRefs(in map[string]string) (out map[string]envoyx.Ref, scope envoyx.Scope) {
|
||||
out = make(map[string]envoyx.Ref)
|
||||
|
||||
nsII := envoyx.MakeIdentifiers(in["namespace"])
|
||||
if len(nsII.Slice) > 0 {
|
||||
scope = envoyx.Scope{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: nsII,
|
||||
}
|
||||
}
|
||||
|
||||
if len(nsII.Slice) > 0 {
|
||||
out["NamespaceID"] = envoyx.Ref{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: nsII,
|
||||
Scope: scope,
|
||||
}
|
||||
}
|
||||
|
||||
modII := envoyx.MakeIdentifiers(in["module"])
|
||||
if len(modII.Slice) > 0 {
|
||||
out["ModuleID"] = envoyx.Ref{
|
||||
ResourceType: types.ModuleResourceType,
|
||||
Identifiers: envoyx.MakeIdentifiers(in["module"]),
|
||||
Scope: scope,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
721
server/compose/envoy/yaml_encode.gen.go
generated
Normal file
721
server/compose/envoy/yaml_encode.gen.go
generated
Normal file
@@ -0,0 +1,721 @@
|
||||
package envoy
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/y7s"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type (
|
||||
// YamlEncoder is responsible for encoding Corteza resources into
|
||||
// a YAML supported format
|
||||
YamlEncoder struct{}
|
||||
)
|
||||
|
||||
const (
|
||||
paramsKeyWriter = "writer"
|
||||
)
|
||||
|
||||
// Encode encodes the given Corteza resources into some YAML supported format
|
||||
//
|
||||
// Encoding should not do any additional processing apart from matching with
|
||||
// dependencies and runtime validation
|
||||
//
|
||||
// Preparation runs validation, default value initialization, matching with
|
||||
// already existing instances, ...
|
||||
//
|
||||
// The prepare function receives a set of nodes grouped by the resource type.
|
||||
// This enables some batching optimization and simplifications when it comes to
|
||||
// matching with existing resources.
|
||||
//
|
||||
// Prepare does not receive any placeholder nodes which are used solely
|
||||
// for dependency resolution.
|
||||
func (e YamlEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (err error) {
|
||||
var (
|
||||
out *yaml.Node
|
||||
aux *yaml.Node
|
||||
)
|
||||
_ = aux
|
||||
|
||||
w, err := e.getWriter(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch rt {
|
||||
case types.ChartResourceType:
|
||||
aux, err = e.encodeCharts(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "chart", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case types.ModuleResourceType:
|
||||
aux, err = e.encodeModules(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "module", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case types.ModuleFieldResourceType:
|
||||
aux, err = e.encodeModuleFields(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "moduleField", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case types.NamespaceResourceType:
|
||||
aux, err = e.encodeNamespaces(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "namespace", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case types.PageResourceType:
|
||||
aux, err = e.encodePages(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "page", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case types.PageLayoutResourceType:
|
||||
aux, err = e.encodePageLayouts(ctx, p, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Root level resources are always encoded as a map
|
||||
out, err = y7s.AddMap(out, "pageLayout", aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
out, err = e.encode(ctx, out, p, rt, nodes, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Don't output nil values since that will produce broken yaml docs
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return yaml.NewEncoder(w).Encode(out)
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource chart
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodeCharts(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodeChart(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodeChart focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodeChart(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.Chart)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
auxConfig, err := e.encodeChartConfigC(ctx, p, tt, node, res, res.Config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxNamespaceID, err := e.encodeRef(p, res.NamespaceID, "NamespaceID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"config", auxConfig,
|
||||
"createdAt", auxCreatedAt,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"handle", res.Handle,
|
||||
"chartID", res.ID,
|
||||
"name", res.Name,
|
||||
"namespaceID", auxNamespaceID,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource module
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodeModules(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodeModule(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodeModule focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodeModule(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.Module)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxNamespaceID, err := e.encodeRef(p, res.NamespaceID, "NamespaceID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"config", res.Config,
|
||||
"createdAt", auxCreatedAt,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"handle", res.Handle,
|
||||
"moduleID", res.ID,
|
||||
"meta", res.Meta,
|
||||
"name", res.Name,
|
||||
"namespaceID", auxNamespaceID,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
// When processing module fields, we need to filter out the ones that
|
||||
// don't belong to this module
|
||||
//
|
||||
// @todo offload this to dependency resolution; this is a hack
|
||||
children := tt.ChildrenForResourceType(node, types.ModuleFieldResourceType)
|
||||
|
||||
var proc envoyx.NodeSet
|
||||
selfRef := node.ToRef()
|
||||
for _, c := range children {
|
||||
if c.References["ModuleID"].Equals(selfRef) {
|
||||
proc = append(proc, c)
|
||||
}
|
||||
}
|
||||
|
||||
aux, err = e.encodeModuleFields(ctx, p, proc, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = y7s.AddMap(out,
|
||||
"moduleField", aux,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource moduleField
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodeModuleFields(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodeModuleField(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodeModuleField focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodeModuleField(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.ModuleField)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxModuleID, err := e.encodeRef(p, res.ModuleID, "ModuleID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxOptions, err := e.encodeModuleFieldOptionsC(ctx, p, tt, node, res, res.Options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"config", res.Config,
|
||||
"createdAt", auxCreatedAt,
|
||||
"defaultValue", res.DefaultValue,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"expressions", res.Expressions,
|
||||
"id", res.ID,
|
||||
"kind", res.Kind,
|
||||
"label", res.Label,
|
||||
"moduleID", auxModuleID,
|
||||
"multi", res.Multi,
|
||||
"name", res.Name,
|
||||
"options", auxOptions,
|
||||
"place", res.Place,
|
||||
"required", res.Required,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource namespace
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodeNamespaces(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodeNamespace(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodeNamespace focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodeNamespace(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.Namespace)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"createdAt", auxCreatedAt,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"enabled", res.Enabled,
|
||||
"id", res.ID,
|
||||
"meta", res.Meta,
|
||||
"name", res.Name,
|
||||
"slug", res.Slug,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
aux, err = e.encodeCharts(ctx, p, tt.ChildrenForResourceType(node, types.ChartResourceType), tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = y7s.AddMap(out,
|
||||
"chart", aux,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aux, err = e.encodeModules(ctx, p, tt.ChildrenForResourceType(node, types.ModuleResourceType), tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = y7s.AddMap(out,
|
||||
"module", aux,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aux, err = e.encodePages(ctx, p, tt.ChildrenForResourceType(node, types.PageResourceType), tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = y7s.AddMap(out,
|
||||
"page", aux,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource page
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodePages(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodePage(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodePage focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodePage(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.Page)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
auxBlocks, err := e.encodePageBlocksC(ctx, p, tt, node, res, res.Blocks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxModuleID, err := e.encodeRef(p, res.ModuleID, "ModuleID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxNamespaceID, err := e.encodeRef(p, res.NamespaceID, "NamespaceID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxSelfID, err := e.encodeRef(p, res.SelfID, "SelfID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"blocks", auxBlocks,
|
||||
"children", res.Children,
|
||||
"config", res.Config,
|
||||
"createdAt", auxCreatedAt,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"description", res.Description,
|
||||
"handle", res.Handle,
|
||||
"id", res.ID,
|
||||
"meta", res.Meta,
|
||||
"moduleID", auxModuleID,
|
||||
"namespaceID", auxNamespaceID,
|
||||
"selfID", auxSelfID,
|
||||
"title", res.Title,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
"visible", res.Visible,
|
||||
"weight", res.Weight,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
aux, err = e.encodePageLayouts(ctx, p, tt.ChildrenForResourceType(node, types.PageLayoutResourceType), tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = y7s.AddMap(out,
|
||||
"pageLayout", aux,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Functions for resource pageLayout
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodePageLayouts(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
var aux *yaml.Node
|
||||
for _, n := range nodes {
|
||||
aux, err = e.encodePageLayout(ctx, p, n, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encodePageLayout focuses on the specific resource invoked by the Encode method
|
||||
func (e YamlEncoder) encodePageLayout(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
res := node.Resource.(*types.PageLayout)
|
||||
|
||||
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
|
||||
|
||||
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auxNamespaceID, err := e.encodeRef(p, res.NamespaceID, "NamespaceID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxOwnedBy, err := e.encodeRef(p, res.OwnedBy, "OwnedBy", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxPageID, err := e.encodeRef(p, res.PageID, "PageID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxParentID, err := e.encodeRef(p, res.ParentID, "ParentID", node, tt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddMap(out,
|
||||
"blocks", res.Blocks,
|
||||
"config", res.Config,
|
||||
"createdAt", auxCreatedAt,
|
||||
"deletedAt", auxDeletedAt,
|
||||
"handle", res.Handle,
|
||||
"id", res.ID,
|
||||
"meta", res.Meta,
|
||||
"namespaceID", auxNamespaceID,
|
||||
"ownedBy", auxOwnedBy,
|
||||
"pageID", auxPageID,
|
||||
"parentID", auxParentID,
|
||||
"updatedAt", auxUpdatedAt,
|
||||
"weight", res.Weight,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nested resources
|
||||
var aux *yaml.Node
|
||||
_ = aux
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Encoding utils
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) encodeTimestamp(p envoyx.EncodeParams, t time.Time) (any, error) {
|
||||
if t.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tz := p.Encoder.PreferredTimezone
|
||||
if tz != "" {
|
||||
tzL, err := time.LoadLocation(tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t = t.In(tzL)
|
||||
}
|
||||
|
||||
ly := p.Encoder.PreferredTimeLayout
|
||||
if ly == "" {
|
||||
ly = time.RFC3339
|
||||
}
|
||||
|
||||
return t.Format(ly), nil
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeTimestampNil(p envoyx.EncodeParams, t *time.Time) (any, error) {
|
||||
if t == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return e.encodeTimestamp(p, *t)
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeRef(p envoyx.EncodeParams, id uint64, field string, node *envoyx.Node, tt envoyx.Traverser) (any, error) {
|
||||
parent := tt.ParentForRef(node, node.References[field])
|
||||
|
||||
// @todo should we panic instead?
|
||||
// for now gracefully fallback to the ID
|
||||
if parent == nil {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
return parent.Identifiers.FriendlyIdentifier(), nil
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Utility functions
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func (e YamlEncoder) getWriter(p envoyx.EncodeParams) (out io.Writer, err error) {
|
||||
aux, ok := p.Params[paramsKeyWriter]
|
||||
if ok {
|
||||
out, ok = aux.(io.Writer)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// @todo consider adding support for managing files from a location
|
||||
err = errors.Errorf("YAML encoder expects a writer conforming to io.Writer interface")
|
||||
return
|
||||
}
|
||||
|
||||
func safeParentIdentifier(tt envoyx.Traverser, n *envoyx.Node, ref envoyx.Ref) (out string) {
|
||||
aux := tt.ParentForRef(n, ref)
|
||||
if aux == nil {
|
||||
return ref.Identifiers.FriendlyIdentifier()
|
||||
}
|
||||
|
||||
return aux.Identifiers.FriendlyIdentifier()
|
||||
}
|
||||
238
server/compose/envoy/yaml_encode.go
Normal file
238
server/compose/envoy/yaml_encode.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/y7s"
|
||||
"github.com/modern-go/reflect2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (e YamlEncoder) encode(ctx context.Context, base *yaml.Node, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeChartConfigC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, chart *types.Chart, cfg types.ChartConfig) (_ any, err error) {
|
||||
|
||||
reports, _ := y7s.MakeSeq()
|
||||
|
||||
for i, r := range cfg.Reports {
|
||||
modRef, ok := n.References[fmt.Sprintf("Config.Reports.%d.ModuleID", i)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := y7s.MakeMap(
|
||||
"filter", r.Filter,
|
||||
"module", safeParentIdentifier(tt, n, modRef),
|
||||
"metrics", r.Metrics,
|
||||
"dimensions", r.Dimensions,
|
||||
"yAxis", r.YAxis,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reports, err = y7s.AddSeq(reports, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return y7s.MakeMap(
|
||||
"reports", reports,
|
||||
"colorScheme", cfg.ColorScheme,
|
||||
)
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeModuleFieldsC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, mod *types.Module, fields types.ModuleFieldSet) (_ any, err error) {
|
||||
|
||||
fn := tt.ChildrenForResourceType(n, types.ModuleFieldResourceType)
|
||||
|
||||
out, err := e.encodeModuleFields(ctx, p, fn, tt)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeModuleFieldOptionsC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, f *types.ModuleField, opt types.ModuleFieldOptions) (_ any, err error) {
|
||||
if opt == nil {
|
||||
opt = make(types.ModuleFieldOptions)
|
||||
}
|
||||
switch f.Kind {
|
||||
case "Record":
|
||||
modRef := n.References["Options.ModuleID"]
|
||||
opt["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(opt, "moduleID")
|
||||
|
||||
case "User":
|
||||
aux := make([]string, 0, 2)
|
||||
for i := range opt.Strings("roles") {
|
||||
roleRef := n.References[fmt.Sprintf("Options.RoleID.%d", i)]
|
||||
aux = append(aux, safeParentIdentifier(tt, n, roleRef))
|
||||
}
|
||||
opt["roles"] = aux
|
||||
delete(opt, "role")
|
||||
delete(opt, "roleID")
|
||||
}
|
||||
|
||||
nopt, _ := y7s.MakeMap()
|
||||
for k, v := range opt {
|
||||
nopt, err = y7s.AddMap(nopt, k, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nopt, nil
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodePageBlocksC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, bb types.PageBlocks) (_ any, err error) {
|
||||
out, _ := y7s.MakeSeq()
|
||||
|
||||
var aux any
|
||||
for i, b := range pg.Blocks {
|
||||
aux, err = e.encodePageBlockC(ctx, p, tt, n, pg, i, b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = y7s.AddSeq(out, aux)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodePageBlockC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, index int, b types.PageBlock) (_ any, err error) {
|
||||
|
||||
switch b.Kind {
|
||||
case "RecordList":
|
||||
b = e.cleanupPageblockRecordList(b)
|
||||
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)]
|
||||
b.Options["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(b.Options, "moduleID")
|
||||
break
|
||||
|
||||
case "RecordOrganizer":
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)]
|
||||
b.Options["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(b.Options, "moduleID")
|
||||
break
|
||||
|
||||
case "Chart":
|
||||
chrRef := n.References[fmt.Sprintf("Blocks.%d.Options.ChartID", index)]
|
||||
b.Options["chart"] = safeParentIdentifier(tt, n, chrRef)
|
||||
delete(b.Options, "chartID")
|
||||
break
|
||||
|
||||
case "Calendar":
|
||||
ff, _ := b.Options["feeds"].([]interface{})
|
||||
for i, f := range ff {
|
||||
feed, _ := f.(map[string]interface{})
|
||||
fOpts, _ := (feed["options"]).(map[string]interface{})
|
||||
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, i)]
|
||||
fOpts["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(fOpts, "moduleID")
|
||||
}
|
||||
break
|
||||
|
||||
case "Automation":
|
||||
bb, _ := b.Options["buttons"].([]interface{})
|
||||
for i, b := range bb {
|
||||
button, _ := b.(map[string]interface{})
|
||||
if _, has := button["workflowID"]; !has {
|
||||
continue
|
||||
}
|
||||
|
||||
wfRef := n.References[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, i)]
|
||||
button["workflow"] = safeParentIdentifier(tt, n, wfRef)
|
||||
delete(button, "workflowID")
|
||||
i++
|
||||
}
|
||||
break
|
||||
|
||||
case "Metric":
|
||||
mm, _ := b.Options["metrics"].([]interface{})
|
||||
for i, m := range mm {
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, i)]
|
||||
|
||||
mops, _ := m.(map[string]interface{})
|
||||
mops["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(mops, "moduleID")
|
||||
}
|
||||
break
|
||||
|
||||
case "Comment":
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)]
|
||||
b.Options["module"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(b.Options, "moduleID")
|
||||
break
|
||||
|
||||
case "Progress":
|
||||
err = e.encodeProgressPageblockVal("minValue", index, n, tt, &b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = e.encodeProgressPageblockVal("maxValue", index, n, tt, &b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = e.encodeProgressPageblockVal("value", index, n, tt, &b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e YamlEncoder) encodeProgressPageblockVal(k string, index int, n *envoyx.Node, tt envoyx.Traverser, b *types.PageBlock) (err error) {
|
||||
if reflect2.IsNil(b.Options[k]) {
|
||||
return
|
||||
}
|
||||
|
||||
modRef := n.References[fmt.Sprintf("Blocks.%d.Options.%s.ModuleID", index, k)]
|
||||
opt := b.Options[k].(map[string]any)
|
||||
opt["moduleID"] = safeParentIdentifier(tt, n, modRef)
|
||||
delete(opt, "moduleID")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e YamlEncoder) cleanupPageblockRecordList(b types.PageBlock) (out types.PageBlock) {
|
||||
out = b
|
||||
rawFF, has := out.Options["fields"]
|
||||
if !has {
|
||||
return
|
||||
}
|
||||
|
||||
ff, ok := rawFF.([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
retFF := make([]interface{}, 0, len(ff))
|
||||
for _, rawF := range ff {
|
||||
switch c := rawF.(type) {
|
||||
case string:
|
||||
retFF = append(retFF, map[string]interface{}{"name": c})
|
||||
case map[string]interface{}, map[string]string:
|
||||
retFF = append(retFF, c)
|
||||
default:
|
||||
retFF = append(retFF, rawF)
|
||||
}
|
||||
}
|
||||
|
||||
out.Options["fields"] = retFF
|
||||
|
||||
return b
|
||||
}
|
||||
26
server/compose/model/init.gen.go
generated
Normal file
26
server/compose/model/init.gen.go
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
package model
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
)
|
||||
|
||||
type (
|
||||
modelReplacer interface {
|
||||
ReplaceModel(ctx context.Context, model *dal.Model) (err error)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
models []*dal.Model
|
||||
)
|
||||
|
||||
func Models() dal.ModelSet {
|
||||
return models
|
||||
}
|
||||
1203
server/compose/model/models.gen.go
generated
Normal file
1203
server/compose/model/models.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
172
server/compose/module.cue
Normal file
172
server/compose/module.cue
Normal file
@@ -0,0 +1,172 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
module: {
|
||||
handle: "module"
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
]
|
||||
|
||||
model: {
|
||||
ident: "compose_module"
|
||||
attributes: {
|
||||
id: schema.IdField & {
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyEncode: "moduleID"
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["namespace", "namespace_id", "ns", "ns_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
handle: schema.HandleField
|
||||
name: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
meta: {
|
||||
goType: "rawJson"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
config: {
|
||||
goType: "types.ModuleConfig"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
fields: {
|
||||
goType: "types.ModuleFieldSet",
|
||||
store: false
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
omitEncoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"namespace": { attribute: "namespace_id" },
|
||||
"unique_handle": {
|
||||
fields: [{ attribute: "handle", modifiers: ["LOWERCASE"] }, { attribute: "namespace_id" }]
|
||||
predicate: "handle != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
module_id: { goType: "[]uint64", ident: "moduleID", storeIdent: "id" }
|
||||
namespace_id: { goType: "uint64", ident: "namespaceID", storeIdent: "rel_namespace" }
|
||||
handle: { goType: "string" }
|
||||
name: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
query: ["handle", "name"]
|
||||
byValue: ["handle", "module_id", "namespace_id"]
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Handle"
|
||||
identKeyAlias: ["modules", "mod"]
|
||||
|
||||
extendedResourcePostProcess: true
|
||||
extendedResourceDecoders: [{
|
||||
ident: "source"
|
||||
expIdent: "Source"
|
||||
// @deprecated records is what the old version used
|
||||
identKeys: ["source", "datasource", "records"]
|
||||
supportMappedInput: false
|
||||
}]
|
||||
}
|
||||
store: {
|
||||
postSetEncoder: true
|
||||
extendedEncoder: true
|
||||
extendedFilterBuilder: true
|
||||
extendedDecoder: true
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
"record.create": description: "Create record"
|
||||
"owned-record.create": description: "Create record with custom owner"
|
||||
"records.search": description: "List, search or filter records"
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composeModule"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["namespace_id", "handle"]
|
||||
constraintCheck: true
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for compose module by handle (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["namespace_id", "name"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for compose module by name (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose module by ID
|
||||
|
||||
It returns compose module even if deleted
|
||||
"""
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
extended: true
|
||||
|
||||
keys: {
|
||||
"name": {}
|
||||
}
|
||||
}
|
||||
|
||||
//locale:
|
||||
// resource:
|
||||
// references: [ namespace, ID ]
|
||||
//
|
||||
// extended: true
|
||||
// keys:
|
||||
// - name
|
||||
}
|
||||
212
server/compose/module_field.cue
Normal file
212
server/compose/module_field.cue
Normal file
@@ -0,0 +1,212 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
moduleField: {
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
{handle: "module"},
|
||||
]
|
||||
|
||||
model: {
|
||||
defaultSetter: true
|
||||
|
||||
ident: "compose_module_field"
|
||||
attributes: {
|
||||
id: schema.IdField & {
|
||||
envoy: {
|
||||
identifier: true
|
||||
}
|
||||
}
|
||||
module_id: {
|
||||
ident: "moduleID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_module"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:module" }
|
||||
}
|
||||
place: {
|
||||
sortable: true,
|
||||
goType: "int"
|
||||
dal: { type: "Number", meta: { "rdbms:type": "integer" } }
|
||||
}
|
||||
kind: {
|
||||
sortable: true,
|
||||
goType: "string"
|
||||
dal: {}
|
||||
}
|
||||
options: {
|
||||
goType: "types.ModuleFieldOptions"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
customDecoder: true
|
||||
customEncoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
name: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
} & {
|
||||
envoy: {
|
||||
identifier: true
|
||||
}
|
||||
}
|
||||
label: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
config: {
|
||||
goType: "types.ModuleFieldConfig"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
required: {
|
||||
goType: "bool",
|
||||
storeIdent: "is_required"
|
||||
dal: { type: "Boolean" }
|
||||
}
|
||||
multi: {
|
||||
goType: "bool",
|
||||
storeIdent: "is_multi"
|
||||
dal: { type: "Boolean" }
|
||||
}
|
||||
default_value: {
|
||||
goType: "types.RecordValueSet"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
customDecoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
expressions: {
|
||||
goType: "types.ModuleFieldExpr"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
customDecoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"module": { attribute: "module_id" },
|
||||
"unique_name": {
|
||||
fields: [{ attribute: "name", modifiers: ["LOWERCASE"] }, { attribute: "module_id" }]
|
||||
predicate: "name != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
module_id: { goType: "[]uint64", ident: "moduleID", storeIdent: "rel_module" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
byNilState: ["deleted"]
|
||||
byValue: ["module_id"]
|
||||
}
|
||||
|
||||
features: {
|
||||
labels: false
|
||||
paging: false
|
||||
sorting: false
|
||||
checkFn: false
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Name"
|
||||
identKeyAlias: ["module_fields", "modulefields", "fields"]
|
||||
}
|
||||
store: {
|
||||
handleField: ""
|
||||
customFilterBuilder: true
|
||||
extendedRefDecoder: true
|
||||
sanitizeBeforeSave: true
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"record.value.read": description: "Read field value on records"
|
||||
"record.value.update": description: "Update field value on records"
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
skipSvc: true
|
||||
|
||||
keys: {
|
||||
label: {}
|
||||
descriptionView: {
|
||||
path: ["meta", "description", "view"]
|
||||
customHandler: true
|
||||
}
|
||||
descriptionEdit: {
|
||||
path: ["meta", "description", "edit"]
|
||||
customHandler: true
|
||||
}
|
||||
hintView: {
|
||||
path: ["meta", "hint", "view"]
|
||||
customHandler: true
|
||||
}
|
||||
hintEdit: {
|
||||
path: ["meta", "hint", "edit"]
|
||||
customHandler: true
|
||||
}
|
||||
validatorError: {
|
||||
path: ["expression", "validator", {part: "validatorID", var: true}, "error"]
|
||||
customHandler: true
|
||||
}
|
||||
optionsOptionTexts: {
|
||||
path: ["meta", "options", {part: "value", var: true}, "text"]
|
||||
customHandler: true
|
||||
}
|
||||
optionsBoolLabels: {
|
||||
path: ["meta", "bool", {part: "value", var: true}, "label"]
|
||||
customHandler: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composeModuleField"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["module_id", "name"]
|
||||
constraintCheck: true
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for compose module field by name (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose module field by ID
|
||||
"""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
128
server/compose/namespace.cue
Normal file
128
server/compose/namespace.cue
Normal file
@@ -0,0 +1,128 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
namespace: {
|
||||
model: {
|
||||
ident: "compose_namespace"
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
slug: {
|
||||
sortable: true,
|
||||
goType: "string"
|
||||
dal: {}
|
||||
envoy: {
|
||||
identifier: true
|
||||
}
|
||||
}
|
||||
enabled: {
|
||||
goType: "bool"
|
||||
dal: { type: "Boolean" }
|
||||
}
|
||||
meta: {
|
||||
goType: "types.NamespaceMeta"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
name: {
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"unique_handle": {
|
||||
fields: [{ attribute: "slug", modifiers: ["LOWERCASE"] }]
|
||||
predicate: "slug != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
namespace_id: { goType: "[]uint64", ident: "namespaceID", storeIdent: "id" }
|
||||
slug: { goType: "string" }
|
||||
name: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
query: ["name", "slug"]
|
||||
byValue: ["namespace_id", "name", "slug"]
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Slug"
|
||||
identKeyAlias: ["namespaces", "ns"]
|
||||
}
|
||||
store: {
|
||||
handleField: "Slug"
|
||||
extendedFilterBuilder: true
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
"export": description: "Access to export the entire namespace"
|
||||
"manage": description: "Access to namespace admin panel"
|
||||
"module.create": description: "Create module on namespace"
|
||||
"modules.search": description: "List, search or filter module on namespace"
|
||||
"modules.export": description: "Export modules on namespace"
|
||||
"chart.create": description: "Create chart on namespace"
|
||||
"charts.search": description: "List, search or filter chart on namespace"
|
||||
"charts.export": description: "Export charts on namespace"
|
||||
"page.create": description: "Create page on namespace"
|
||||
"pages.search": description: "List, search or filter pages on namespace"
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
keys: {
|
||||
name: {}
|
||||
metaSubtitle: {
|
||||
path: ["meta", "subtitle"]
|
||||
}
|
||||
metaDescription: {
|
||||
path: ["meta", "description"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composeNamespace"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["slug"]
|
||||
constraintCheck: true
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for namespace by slug (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose namespace by ID
|
||||
|
||||
It returns compose namespace even if deleted
|
||||
"""
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
238
server/compose/page.cue
Normal file
238
server/compose/page.cue
Normal file
@@ -0,0 +1,238 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
page: {
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
]
|
||||
|
||||
model: {
|
||||
defaultSetter: true
|
||||
|
||||
ident: "compose_page"
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
title: {
|
||||
goType: "string",
|
||||
sortable: true
|
||||
dal: {}
|
||||
}
|
||||
handle: schema.HandleField
|
||||
self_id: {
|
||||
ident: "selfID",
|
||||
goType: "uint64",
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:page" }
|
||||
sortable: true
|
||||
envoy: {
|
||||
store: {
|
||||
filterRefField: "ParentID"
|
||||
}
|
||||
yaml: {
|
||||
identKeyAlias: ["parent"]
|
||||
}
|
||||
}
|
||||
}
|
||||
module_id: {
|
||||
ident: "moduleID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_module"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:module" }
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["module"]
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["namespace"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
meta: {
|
||||
goType: "types.PageMeta"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
config: {
|
||||
goType: "types.PageConfig"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
blocks: {
|
||||
goType: "types.PageBlocks"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
customDecoder: true
|
||||
customEncoder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
children: {
|
||||
goType: "types.PageSet", store: false
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
visible: {
|
||||
goType: "bool"
|
||||
dal: { type: "Boolean", default: true }
|
||||
}
|
||||
weight: {
|
||||
goType: "int", sortable: true
|
||||
dal: { type: "Number", default: 0, meta: { "rdbms:type": "integer" } }
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["order"]
|
||||
}
|
||||
}
|
||||
}
|
||||
description: {
|
||||
goType: "string"
|
||||
dal: {}
|
||||
}
|
||||
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"namespace": { attribute: "namespace_id" },
|
||||
"module": { attribute: "module_id" },
|
||||
"self_id": { attribute: "self_id" },
|
||||
"unique_handle": {
|
||||
fields: [{ attribute: "handle", modifiers: ["LOWERCASE"] }, { attribute: "namespace_id" }]
|
||||
predicate: "handle != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
page_id: { goType: "[]uint64", ident: "pageID", storeIdent: "id" }
|
||||
namespace_id: { goType: "uint64", ident: "namespaceID", storeIdent: "rel_namespace" }
|
||||
parent_id: { goType: "uint64", ident: "parentID" }
|
||||
module_id: { goType: "uint64", ident: "moduleID", storeIdent: "rel_module" }
|
||||
root: { goType: "bool" }
|
||||
handle: { goType: "string" }
|
||||
title: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
query: ["handle", "title", "description"]
|
||||
byValue: ["page_id", "handle", "namespace_id", "module_id"]
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Handle"
|
||||
identKeyAlias: ["pages", "pg"]
|
||||
|
||||
extendedResourceDecoders: [{
|
||||
ident: "pages"
|
||||
expIdent: "Pages"
|
||||
identKeys: ["children", "pages"]
|
||||
supportMappedInput: true
|
||||
mappedField: "Handle"
|
||||
}]
|
||||
extendedResourceRefIdent: "SelfID"
|
||||
}
|
||||
store: {
|
||||
extendedFilterBuilder: true
|
||||
extendedRefDecoder: true
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
"page-layout.create": description: "Create page layout on namespace"
|
||||
"page-layouts.search": description: "List, search or filter page layouts on namespace"
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
extended: true
|
||||
|
||||
keys: {
|
||||
title: {}
|
||||
description: {}
|
||||
blockTitle: {
|
||||
path: ["pageBlock", {part: "blockID", var: true}, "title"]
|
||||
customHandler: true
|
||||
}
|
||||
blockDescription: {
|
||||
path: ["pageBlock", {part: "blockID", var: true}, "description"]
|
||||
customHandler: true
|
||||
}
|
||||
blockAutomationButtonLabel: {
|
||||
path: ["pageBlock", {part: "blockID", var: true}, "button", {part: "buttonID", var: true}, "label"]
|
||||
customHandler: true
|
||||
}
|
||||
blockContentBody: {
|
||||
path: ["pageBlock", {part: "blockID", var: true}, "content", "body"]
|
||||
customHandler: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composePage"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["namespace_id", "handle"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for page by handle (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["namespace_id", "module_id"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for page by moduleID
|
||||
"""
|
||||
}, {
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose page by ID
|
||||
|
||||
It returns compose page even if deleted
|
||||
"""
|
||||
},
|
||||
]
|
||||
|
||||
functions: [
|
||||
{
|
||||
expIdent: "ReorderComposePages"
|
||||
args: [
|
||||
{ ident: "namespace_id", goType: "uint64" },
|
||||
{ ident: "parent_id", goType: "uint64" },
|
||||
{ ident: "page_ids", goType: "[]uint64" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
215
server/compose/page_layout.cue
Normal file
215
server/compose/page_layout.cue
Normal file
@@ -0,0 +1,215 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
pageLayout: {
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
{handle: "page"},
|
||||
]
|
||||
|
||||
model: {
|
||||
ident: "compose_page_layout"
|
||||
|
||||
defaultGetter: true
|
||||
defaultSetter: true
|
||||
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
handle: schema.HandleField
|
||||
page_id: {
|
||||
ident: "pageID",
|
||||
goType: "uint64",
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:page" }
|
||||
sortable: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["page"]
|
||||
}
|
||||
}
|
||||
}
|
||||
parent_id: {
|
||||
ident: "parentID",
|
||||
goType: "uint64",
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:page-layout" }
|
||||
sortable: true
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["parent"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
envoy: {
|
||||
yaml: {
|
||||
identKeyAlias: ["namespace"]
|
||||
}
|
||||
}
|
||||
}
|
||||
weight: {
|
||||
goType: "int", sortable: true
|
||||
dal: { type: "Number", default: 0, meta: { "rdbms:type": "integer" } }
|
||||
}
|
||||
|
||||
meta: {
|
||||
goType: "types.PageLayoutMeta"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
|
||||
config: {
|
||||
goType: "types.PageLayoutConfig"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
blocks: {
|
||||
goType: "types.PageLayoutBlocks"
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
|
||||
owned_by: schema.AttributeUserRef
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"namespace": { attribute: "namespace_id" },
|
||||
"page_id": { attribute: "page_id" },
|
||||
"parent_id": { attribute: "parent_id" },
|
||||
"unique_handle": {
|
||||
fields: [{ attribute: "handle", modifiers: ["LOWERCASE"] }, { attribute: "page_id" }, { attribute: "namespace_id" }]
|
||||
predicate: "handle != '' AND deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
page_layout_id: { goType: "[]uint64", ident: "pageLayoutID", storeIdent: "id" }
|
||||
namespace_id: { goType: "uint64", ident: "namespaceID", storeIdent: "rel_namespace" }
|
||||
page_id: { goType: "uint64", ident: "pageID", storeIdent: "page_id" }
|
||||
parent_id: { goType: "uint64", ident: "parentID", storeIdent: "parent_id" }
|
||||
default: { goType: "bool", ident: "default" }
|
||||
handle: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
query: ["handle"]
|
||||
byValue: ["handle", "parent_id", "namespace_id", "page_id", "page_layout_id"]
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
envoy: {
|
||||
scoped: true
|
||||
yaml: {
|
||||
supportMappedInput: true
|
||||
mappedField: "Handle"
|
||||
identKeyAlias: ["page_layouts", "pagelayouts", "layouts"]
|
||||
}
|
||||
store: {
|
||||
}
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
}
|
||||
}
|
||||
|
||||
locale: {
|
||||
extended: true
|
||||
|
||||
keys: {
|
||||
title: {
|
||||
path: ["meta", "title"]
|
||||
}
|
||||
description: {
|
||||
path: ["meta", "description"]
|
||||
}
|
||||
|
||||
recordToolbarButtonNewLabel: {
|
||||
path: ["config", "buttons", "new", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
recordToolbarButtonEditLabel: {
|
||||
path: ["config", "buttons", "edit", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
recordToolbarButtonSubmitLabel: {
|
||||
path: ["config", "buttons", "submit", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
recordToolbarButtonDeleteLabel: {
|
||||
path: ["config", "buttons", "delete", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
recordToolbarButtonCloneLabel: {
|
||||
path: ["config", "buttons", "clone", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
recordToolbarButtonBackLabel: {
|
||||
path: ["config", "buttons", "back", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
actionLabel: {
|
||||
path: ["config", "actions", {part: "actionID", var: true}, "meta", "label"]
|
||||
customHandler: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store: {
|
||||
ident: "composePageLayout"
|
||||
|
||||
api: {
|
||||
lookups: [
|
||||
{
|
||||
fields: ["namespace_id", "handle"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for page layour by handle (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["namespace_id", "page_id", "handle"]
|
||||
nullConstraint: ["deleted_at"]
|
||||
description: """
|
||||
searches for page layour by handle (case-insensitive)
|
||||
"""
|
||||
}, {
|
||||
fields: ["id"]
|
||||
description: """
|
||||
searches for compose page layour by ID
|
||||
|
||||
It returns compose page layour even if deleted
|
||||
"""
|
||||
},
|
||||
]
|
||||
|
||||
functions: [
|
||||
{
|
||||
expIdent: "ReorderComposePageLayouts"
|
||||
args: [
|
||||
{ ident: "namespace_id", goType: "uint64" },
|
||||
{ ident: "page_id", goType: "uint64" },
|
||||
{ ident: "page_layout_ids", goType: "[]uint64" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
106
server/compose/record.cue
Normal file
106
server/compose/record.cue
Normal file
@@ -0,0 +1,106 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
record: {
|
||||
parents: [
|
||||
{handle: "namespace"},
|
||||
{handle: "module"},
|
||||
]
|
||||
|
||||
model: {
|
||||
ident: "compose_record"
|
||||
|
||||
defaultSetter: true
|
||||
defaultGetter: true
|
||||
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
revision: {
|
||||
goType: "int"
|
||||
dal: { type: "Number", meta: { "rdbms:type": "integer" }, default: 0 }
|
||||
}
|
||||
module_id: {
|
||||
ident: "moduleID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_module"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:module" }
|
||||
}
|
||||
module: {
|
||||
goType: "*types.Module",
|
||||
store: false
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
values: {
|
||||
goType: "types.RecordValueSet",
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
omitSetter: true
|
||||
omitGetter: true
|
||||
}
|
||||
meta: {
|
||||
goType: "map[string]any",
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
}
|
||||
namespace_id: {
|
||||
ident: "namespaceID",
|
||||
goType: "uint64",
|
||||
storeIdent: "rel_namespace"
|
||||
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
|
||||
}
|
||||
|
||||
created_at: schema.SortableTimestampNowField
|
||||
updated_at: schema.SortableTimestampNilField
|
||||
deleted_at: schema.SortableTimestampNilField
|
||||
owned_by: schema.AttributeUserRef & {
|
||||
identAlias: ["ownedBy", "OwnedBy", "owned_by"]
|
||||
}
|
||||
created_by: schema.AttributeUserRef & {
|
||||
identAlias: ["createdBy", "CreatedBy", "created_by"]
|
||||
}
|
||||
updated_by: schema.AttributeUserRef & {
|
||||
identAlias: ["updatedBy", "UpdatedBy", "updated_by"]
|
||||
}
|
||||
deleted_by: schema.AttributeUserRef & {
|
||||
identAlias: ["deletedBy", "DeletedBy", "deleted_by"]
|
||||
}
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
"idx_compose_record_base": {
|
||||
attributes: ["module_id", "namespace_id"]
|
||||
predicate: "deleted_at IS NULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo tmp
|
||||
envoy: {
|
||||
omit: true
|
||||
}
|
||||
|
||||
filter: {
|
||||
struct: {
|
||||
module_id: { goType: "uint64" }
|
||||
namespace_id: { goType: "uint64" }
|
||||
query: { goType: "string" }
|
||||
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
|
||||
}
|
||||
|
||||
byNilState: ["deleted"]
|
||||
}
|
||||
|
||||
rbac: {
|
||||
operations: {
|
||||
"read": {}
|
||||
"update": {}
|
||||
"delete": {}
|
||||
"undelete": {}
|
||||
"owner.manage": {}
|
||||
"revisions.search": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
server/compose/record_revision.cue
Normal file
45
server/compose/record_revision.cue
Normal file
@@ -0,0 +1,45 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/codegen/schema"
|
||||
)
|
||||
|
||||
record_revision: {
|
||||
model: {
|
||||
ident: "compose_record_revisions"
|
||||
omitGetterSetter: true
|
||||
|
||||
attributes: {
|
||||
id: schema.IdField
|
||||
timestamp: schema.SortableTimestampField & { storeIdent: "ts" }
|
||||
rel_resource: {
|
||||
ident: "resourceID",
|
||||
goType: "uint64",
|
||||
dal: { type: "ID" }
|
||||
}
|
||||
revision: {
|
||||
goType: "uint"
|
||||
dal: { type: "Number", meta: { "rdbms:type": "integer" } }
|
||||
}
|
||||
operation: {
|
||||
dal: {}
|
||||
}
|
||||
rel_user: schema.AttributeUserRef
|
||||
delta: {
|
||||
goType: "types.RecordValueSet",
|
||||
dal: { type: "JSON", defaultEmptyObject: true }
|
||||
}
|
||||
comment: {
|
||||
dal: {}
|
||||
}
|
||||
}
|
||||
|
||||
indexes: {
|
||||
"primary": { attribute: "id" }
|
||||
}
|
||||
}
|
||||
|
||||
envoy: {
|
||||
omit: true
|
||||
}
|
||||
}
|
||||
1783
server/compose/rest.yaml
Normal file
1783
server/compose/rest.yaml
Normal file
File diff suppressed because it is too large
Load Diff
199
server/compose/rest/attachment.go
Normal file
199
server/compose/rest/attachment.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
)
|
||||
|
||||
type (
|
||||
attachmentPayload struct {
|
||||
*types.Attachment
|
||||
}
|
||||
|
||||
attachmentSetPayload struct {
|
||||
Filter types.AttachmentFilter `json:"filter"`
|
||||
Set []*attachmentPayload `json:"set"`
|
||||
}
|
||||
|
||||
Attachment struct {
|
||||
attachment service.AttachmentService
|
||||
}
|
||||
)
|
||||
|
||||
func (Attachment) New() *Attachment {
|
||||
return &Attachment{
|
||||
attachment: service.DefaultAttachment,
|
||||
}
|
||||
}
|
||||
|
||||
// Attachments returns list of all files attached to records
|
||||
func (ctrl Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) {
|
||||
if !auth.GetIdentityFromContext(ctx).Valid() {
|
||||
return nil, errors.Unauthorized("cannot list attachments")
|
||||
}
|
||||
|
||||
f := types.AttachmentFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
Kind: r.Kind,
|
||||
ModuleID: r.ModuleID,
|
||||
RecordID: r.RecordID,
|
||||
FieldName: r.FieldName,
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.attachment.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) Read(ctx context.Context, r *request.AttachmentRead) (interface{}, error) {
|
||||
if !auth.GetIdentityFromContext(ctx).Valid() {
|
||||
return nil, errors.Unauthorized("cannot read attachment")
|
||||
}
|
||||
|
||||
a, err := ctrl.attachment.FindByID(ctx, r.NamespaceID, r.AttachmentID)
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) Delete(ctx context.Context, r *request.AttachmentDelete) (interface{}, error) {
|
||||
if !auth.GetIdentityFromContext(ctx).Valid() {
|
||||
return nil, errors.Unauthorized("cannot delete attachment")
|
||||
}
|
||||
|
||||
_, err := ctrl.attachment.FindByID(ctx, r.NamespaceID, r.AttachmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.attachment.DeleteByID(ctx, r.NamespaceID, r.AttachmentID)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
|
||||
if err := ctrl.isAccessible(r.Kind, r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, false, r.Download)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) {
|
||||
if err := ctrl.isAccessible(r.Kind, r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, true, false)
|
||||
}
|
||||
|
||||
func (ctrl Attachment) isAccessible(kind string, namespaceID, attachmentID, userID uint64, signature string) error {
|
||||
if kind == types.PageAttachment || kind == types.IconAttachment || kind == types.NamespaceAttachment {
|
||||
// Public Attachments
|
||||
return nil
|
||||
}
|
||||
|
||||
if signature == "" {
|
||||
return errors.Unauthorized("missing signature")
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
return errors.InvalidData("missing or invalid user ID")
|
||||
}
|
||||
|
||||
if attachmentID == 0 {
|
||||
return errors.InvalidData("missing or invalid attachment ID")
|
||||
}
|
||||
|
||||
if !auth.DefaultSigner.Verify(signature, userID, namespaceID, attachmentID) {
|
||||
return errors.InvalidData("missing or invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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.FindByID(ctx, namespaceID, attachmentID)
|
||||
if err != nil {
|
||||
// Simplify error handling for now
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var fh io.ReadSeekCloser
|
||||
|
||||
if preview {
|
||||
fh, err = ctrl.attachment.OpenPreview(att)
|
||||
} else {
|
||||
fh, err = ctrl.attachment.OpenOriginal(att)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer fh.Close()
|
||||
|
||||
name := url.QueryEscape(att.Name)
|
||||
|
||||
if download {
|
||||
w.Header().Add("Content-Disposition", "attachment; filename="+name)
|
||||
} else {
|
||||
w.Header().Add("Content-Disposition", "inline; filename="+name)
|
||||
w.Header().Add("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
||||
}
|
||||
|
||||
http.ServeContent(w, req, name, att.CreatedAt, fh)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Attachment) makeFilterPayload(ctx context.Context, aa types.AttachmentSet, f types.AttachmentFilter, err error) (*attachmentSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
asp := &attachmentSetPayload{Filter: f, Set: make([]*attachmentPayload, len(aa))}
|
||||
|
||||
for i := range aa {
|
||||
asp.Set[i], _ = makeAttachmentPayload(ctx, aa[i], nil)
|
||||
}
|
||||
|
||||
return asp, nil
|
||||
}
|
||||
|
||||
func makeAttachmentPayload(ctx context.Context, a *types.Attachment, err error) (*attachmentPayload, error) {
|
||||
if err != nil || a == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
userID = auth.GetIdentityFromContext(ctx).Identity()
|
||||
signParams = fmt.Sprintf("?sign=%s&userID=%d", auth.DefaultSigner.Sign(userID, a.NamespaceID, a.ID), userID)
|
||||
|
||||
preview string
|
||||
baseURL = fmt.Sprintf("/namespace/%d/attachment/%s/%d/", a.NamespaceID, a.Kind, a.ID)
|
||||
)
|
||||
|
||||
if a.Meta.Preview != nil {
|
||||
var ext = a.Meta.Preview.Extension
|
||||
if ext == "" {
|
||||
ext = "jpg"
|
||||
}
|
||||
|
||||
preview = baseURL + fmt.Sprintf("preview.%s", ext) + signParams
|
||||
}
|
||||
|
||||
ap := &attachmentPayload{a}
|
||||
|
||||
ap.Url = baseURL + fmt.Sprintf("original/%s", url.PathEscape(a.Name)) + signParams
|
||||
ap.PreviewUrl = preview
|
||||
|
||||
return ap, nil
|
||||
}
|
||||
49
server/compose/rest/automation.go
Normal file
49
server/compose/rest/automation.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
"github.com/cortezaproject/corteza/server/pkg/corredor"
|
||||
)
|
||||
|
||||
type (
|
||||
Automation struct{}
|
||||
)
|
||||
|
||||
func (Automation) New() *Automation {
|
||||
return &Automation{}
|
||||
}
|
||||
|
||||
func (ctrl *Automation) List(ctx context.Context, r *request.AutomationList) (interface{}, error) {
|
||||
return corredor.GenericListHandler(
|
||||
ctx,
|
||||
corredor.Service(),
|
||||
corredor.Filter{
|
||||
ResourceTypePrefixes: r.ResourceTypePrefixes,
|
||||
ExcludeInvalid: r.ExcludeInvalid,
|
||||
ResourceTypes: r.ResourceTypes,
|
||||
EventTypes: r.EventTypes,
|
||||
ExcludeServerScripts: r.ExcludeServerScripts,
|
||||
ExcludeClientScripts: r.ExcludeClientScripts,
|
||||
},
|
||||
"compose",
|
||||
)
|
||||
}
|
||||
|
||||
func (ctrl *Automation) Bundle(ctx context.Context, r *request.AutomationBundle) (interface{}, error) {
|
||||
return corredor.GenericBundleHandler(
|
||||
ctx,
|
||||
corredor.Service(),
|
||||
r.Bundle,
|
||||
r.Type,
|
||||
r.Ext,
|
||||
)
|
||||
}
|
||||
|
||||
func (ctrl *Automation) TriggerScript(ctx context.Context, r *request.AutomationTriggerScript) (interface{}, error) {
|
||||
return api.OK(), corredor.Service().Exec(ctx, r.Script, corredor.ExtendScriptArgs(event.ComposeOnManual(), r.Args))
|
||||
}
|
||||
173
server/compose/rest/chart.go
Normal file
173
server/compose/rest/chart.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
chartPayload struct {
|
||||
*types.Chart
|
||||
|
||||
CanGrant bool `json:"canGrant"`
|
||||
CanUpdateChart bool `json:"canUpdateChart"`
|
||||
CanDeleteChart bool `json:"canDeleteChart"`
|
||||
}
|
||||
|
||||
chartSetPayload struct {
|
||||
Filter types.ChartFilter `json:"filter"`
|
||||
Set []*chartPayload `json:"set"`
|
||||
}
|
||||
|
||||
Chart struct {
|
||||
chart interface {
|
||||
FindByID(ctx context.Context, namespaceID, chartID uint64) (*types.Chart, error)
|
||||
FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.Chart, error)
|
||||
Find(ctx context.Context, filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
|
||||
|
||||
Create(ctx context.Context, chart *types.Chart) (*types.Chart, error)
|
||||
Update(ctx context.Context, chart *types.Chart) (*types.Chart, error)
|
||||
DeleteByID(ctx context.Context, namespaceID, chartID uint64) error
|
||||
}
|
||||
locale service.ResourceTranslationsManagerService
|
||||
ac chartAccessController
|
||||
}
|
||||
|
||||
chartAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
|
||||
CanUpdateChart(context.Context, *types.Chart) bool
|
||||
CanDeleteChart(context.Context, *types.Chart) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Chart) New() *Chart {
|
||||
return &Chart{
|
||||
chart: service.DefaultChart,
|
||||
locale: service.DefaultResourceTranslation,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.ChartFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.IncTotal = r.IncTotal
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.chart.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) {
|
||||
var err error
|
||||
mod := &types.Chart{
|
||||
NamespaceID: r.NamespaceID,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
if err = r.Config.Unmarshal(&mod.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
mod, err = ctrl.chart.Create(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) {
|
||||
mod, err := ctrl.chart.FindByID(ctx, r.NamespaceID, r.ChartID)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
|
||||
}
|
||||
|
||||
func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.Chart{
|
||||
ID: r.ChartID,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
NamespaceID: r.NamespaceID,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
if err = r.Config.Unmarshal(&mod.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
mod, err = ctrl.chart.Update(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) {
|
||||
_, err := ctrl.chart.FindByID(ctx, r.NamespaceID, r.ChartID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.chart.DeleteByID(ctx, r.NamespaceID, r.ChartID)
|
||||
}
|
||||
|
||||
func (ctrl Chart) ListTranslations(ctx context.Context, r *request.ChartListTranslations) (interface{}, error) {
|
||||
return ctrl.locale.Chart(ctx, r.NamespaceID, r.ChartID)
|
||||
}
|
||||
|
||||
func (ctrl Chart) UpdateTranslations(ctx context.Context, r *request.ChartUpdateTranslations) (interface{}, error) {
|
||||
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
|
||||
}
|
||||
|
||||
func (ctrl Chart) makePayload(ctx context.Context, c *types.Chart, err error) (*chartPayload, error) {
|
||||
if err != nil || c == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chartPayload{
|
||||
Chart: c,
|
||||
|
||||
CanGrant: ctrl.ac.CanGrant(ctx),
|
||||
|
||||
CanUpdateChart: ctrl.ac.CanUpdateChart(ctx, c),
|
||||
CanDeleteChart: ctrl.ac.CanDeleteChart(ctx, c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Chart) makeFilterPayload(ctx context.Context, nn types.ChartSet, f types.ChartFilter, err error) (*chartSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
|
||||
}
|
||||
|
||||
return modp, nil
|
||||
}
|
||||
137
server/compose/rest/data_privacy.go
Normal file
137
server/compose/rest/data_privacy.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
)
|
||||
|
||||
type (
|
||||
sensitiveRecordsSetPayload struct {
|
||||
Set []*sensitiveDataPayload `json:"set"`
|
||||
}
|
||||
|
||||
sensitiveDataPayload struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Namespace string `json:"namespace"`
|
||||
ModuleID uint64 `json:"moduleID,string"`
|
||||
Module string `json:"module"`
|
||||
|
||||
Records []sensitiveRecords `json:"records"`
|
||||
}
|
||||
|
||||
sensitiveRecords struct {
|
||||
RecordID uint64 `json:"recordID,string"`
|
||||
Values []map[string]any `json:"values"`
|
||||
}
|
||||
|
||||
privacyModuleSetPayload struct {
|
||||
Filter types.PrivacyModuleFilter `json:"filter"`
|
||||
Set []*types.PrivacyModule `json:"set"`
|
||||
}
|
||||
|
||||
privateDataFinder interface {
|
||||
SearchSensitive(ctx context.Context) (set []types.SensitiveRecordSet, err error)
|
||||
}
|
||||
|
||||
DataPrivacy struct {
|
||||
record privateDataFinder
|
||||
module service.ModuleService
|
||||
namespace service.NamespaceService
|
||||
privacy service.DataPrivacyService
|
||||
}
|
||||
)
|
||||
|
||||
func (DataPrivacy) New() *DataPrivacy {
|
||||
return &DataPrivacy{
|
||||
record: service.DefaultRecord,
|
||||
module: service.DefaultModule,
|
||||
namespace: service.DefaultNamespace,
|
||||
privacy: service.DefaultDataPrivacy,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *DataPrivacy) RecordList(ctx context.Context, r *request.DataPrivacyRecordList) (out interface{}, err error) {
|
||||
// If we're requesting only specific connections, prepare filter params here
|
||||
reqConns := make(map[uint64]bool)
|
||||
hasReqConns := len(r.ConnectionID) > 0
|
||||
for _, connectionID := range payload.ParseUint64s(r.ConnectionID) {
|
||||
reqConns[connectionID] = true
|
||||
}
|
||||
|
||||
// Collect sensitive records
|
||||
ss, err := ctrl.record.SearchSensitive(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
outSet := sensitiveRecordsSetPayload{
|
||||
Set: make([]*sensitiveDataPayload, 0, 10),
|
||||
}
|
||||
|
||||
for _, s := range ss {
|
||||
// Skip the ones we don't want
|
||||
if hasReqConns && !reqConns[s.Module.Config.DAL.ConnectionID] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the payload
|
||||
payload := &sensitiveDataPayload{
|
||||
NamespaceID: s.Namespace.ID,
|
||||
Namespace: s.Namespace.Name,
|
||||
|
||||
ModuleID: s.Module.ID,
|
||||
Module: s.Module.Name,
|
||||
|
||||
Records: make([]sensitiveRecords, 0, len(s.Records)),
|
||||
}
|
||||
for _, a := range s.Records {
|
||||
if len(a.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
payload.Records = append(payload.Records, sensitiveRecords{
|
||||
RecordID: a.RecordID,
|
||||
Values: a.Values,
|
||||
})
|
||||
}
|
||||
|
||||
outSet.Set = append(outSet.Set, payload)
|
||||
}
|
||||
|
||||
return outSet, nil
|
||||
}
|
||||
|
||||
func (ctrl *DataPrivacy) ModuleList(ctx context.Context, r *request.DataPrivacyModuleList) (out interface{}, err error) {
|
||||
var (
|
||||
f = types.PrivacyModuleFilter{
|
||||
ConnectionID: r.ConnectionID,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, f, err := ctrl.privacy.FindModules(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, f, err)
|
||||
}
|
||||
|
||||
func (ctrl DataPrivacy) makeFilterPayload(_ context.Context, mm types.PrivacyModuleSet, f types.PrivacyModuleFilter, err error) (*privacyModuleSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(mm) == 0 {
|
||||
mm = make([]*types.PrivacyModule, 0)
|
||||
}
|
||||
|
||||
return &privacyModuleSetPayload{Filter: f, Set: mm}, nil
|
||||
}
|
||||
133
server/compose/rest/handlers/attachment.go
Normal file
133
server/compose/rest/handlers/attachment.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
AttachmentAPI interface {
|
||||
List(context.Context, *request.AttachmentList) (interface{}, error)
|
||||
Read(context.Context, *request.AttachmentRead) (interface{}, error)
|
||||
Delete(context.Context, *request.AttachmentDelete) (interface{}, error)
|
||||
Original(context.Context, *request.AttachmentOriginal) (interface{}, error)
|
||||
Preview(context.Context, *request.AttachmentPreview) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Attachment struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Original func(http.ResponseWriter, *http.Request)
|
||||
Preview func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewAttachment(h AttachmentAPI) *Attachment {
|
||||
return &Attachment{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Original: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentOriginal()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Original(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Preview: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAttachmentPreview()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Preview(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Attachment) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/attachment/{kind}/", h.List)
|
||||
r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", h.Read)
|
||||
r.Delete("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", h.Delete)
|
||||
r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}", h.Original)
|
||||
r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}", h.Preview)
|
||||
})
|
||||
}
|
||||
95
server/compose/rest/handlers/automation.go
Normal file
95
server/compose/rest/handlers/automation.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
AutomationAPI interface {
|
||||
List(context.Context, *request.AutomationList) (interface{}, error)
|
||||
Bundle(context.Context, *request.AutomationBundle) (interface{}, error)
|
||||
TriggerScript(context.Context, *request.AutomationTriggerScript) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Automation struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Bundle func(http.ResponseWriter, *http.Request)
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewAutomation(h AutomationAPI) *Automation {
|
||||
return &Automation{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAutomationList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Bundle: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAutomationBundle()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Bundle(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScript: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewAutomationTriggerScript()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScript(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Automation) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/automation/", h.List)
|
||||
r.Get("/automation/{bundle}-{type}.{ext}", h.Bundle)
|
||||
r.Post("/automation/trigger", h.TriggerScript)
|
||||
})
|
||||
}
|
||||
171
server/compose/rest/handlers/chart.go
Normal file
171
server/compose/rest/handlers/chart.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
ChartAPI interface {
|
||||
List(context.Context, *request.ChartList) (interface{}, error)
|
||||
Create(context.Context, *request.ChartCreate) (interface{}, error)
|
||||
Read(context.Context, *request.ChartRead) (interface{}, error)
|
||||
Update(context.Context, *request.ChartUpdate) (interface{}, error)
|
||||
Delete(context.Context, *request.ChartDelete) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.ChartListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.ChartUpdateTranslations) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Chart struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewChart(h ChartAPI) *Chart {
|
||||
return &Chart{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ListTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartListTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewChartUpdateTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Chart) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/chart/", h.List)
|
||||
r.Post("/namespace/{namespaceID}/chart/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/chart/{chartID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}/chart/{chartID}", h.Update)
|
||||
r.Delete("/namespace/{namespaceID}/chart/{chartID}", h.Delete)
|
||||
r.Get("/namespace/{namespaceID}/chart/{chartID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/chart/{chartID}/translation", h.UpdateTranslations)
|
||||
})
|
||||
}
|
||||
76
server/compose/rest/handlers/dataPrivacy.go
Normal file
76
server/compose/rest/handlers/dataPrivacy.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
DataPrivacyAPI interface {
|
||||
RecordList(context.Context, *request.DataPrivacyRecordList) (interface{}, error)
|
||||
ModuleList(context.Context, *request.DataPrivacyModuleList) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
DataPrivacy struct {
|
||||
RecordList func(http.ResponseWriter, *http.Request)
|
||||
ModuleList func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewDataPrivacy(h DataPrivacyAPI) *DataPrivacy {
|
||||
return &DataPrivacy{
|
||||
RecordList: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewDataPrivacyRecordList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.RecordList(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ModuleList: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewDataPrivacyModuleList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ModuleList(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h DataPrivacy) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/data-privacy/record", h.RecordList)
|
||||
r.Get("/data-privacy/module", h.ModuleList)
|
||||
})
|
||||
}
|
||||
95
server/compose/rest/handlers/icon.go
Normal file
95
server/compose/rest/handlers/icon.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
IconAPI interface {
|
||||
List(context.Context, *request.IconList) (interface{}, error)
|
||||
Upload(context.Context, *request.IconUpload) (interface{}, error)
|
||||
Delete(context.Context, *request.IconDelete) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Icon struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewIcon(h IconAPI) *Icon {
|
||||
return &Icon{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconUpload()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Upload(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Icon) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/icon/", h.List)
|
||||
r.Post("/icon/", h.Upload)
|
||||
r.Delete("/icon/{iconID}", h.Delete)
|
||||
})
|
||||
}
|
||||
190
server/compose/rest/handlers/module.go
Normal file
190
server/compose/rest/handlers/module.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
ModuleAPI interface {
|
||||
List(context.Context, *request.ModuleList) (interface{}, error)
|
||||
Create(context.Context, *request.ModuleCreate) (interface{}, error)
|
||||
Read(context.Context, *request.ModuleRead) (interface{}, error)
|
||||
Update(context.Context, *request.ModuleUpdate) (interface{}, error)
|
||||
Delete(context.Context, *request.ModuleDelete) (interface{}, error)
|
||||
TriggerScript(context.Context, *request.ModuleTriggerScript) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.ModuleListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.ModuleUpdateTranslations) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Module struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewModule(h ModuleAPI) *Module {
|
||||
return &Module{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScript: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleTriggerScript()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScript(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ListTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleListTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewModuleUpdateTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Module) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/module/", h.List)
|
||||
r.Post("/namespace/{namespaceID}/module/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}", h.Update)
|
||||
r.Delete("/namespace/{namespaceID}/module/{moduleID}", h.Delete)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/trigger", h.TriggerScript)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/module/{moduleID}/translation", h.UpdateTranslations)
|
||||
})
|
||||
}
|
||||
285
server/compose/rest/handlers/namespace.go
Normal file
285
server/compose/rest/handlers/namespace.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
NamespaceAPI interface {
|
||||
List(context.Context, *request.NamespaceList) (interface{}, error)
|
||||
Create(context.Context, *request.NamespaceCreate) (interface{}, error)
|
||||
Read(context.Context, *request.NamespaceRead) (interface{}, error)
|
||||
Update(context.Context, *request.NamespaceUpdate) (interface{}, error)
|
||||
Delete(context.Context, *request.NamespaceDelete) (interface{}, error)
|
||||
Upload(context.Context, *request.NamespaceUpload) (interface{}, error)
|
||||
Clone(context.Context, *request.NamespaceClone) (interface{}, error)
|
||||
Export(context.Context, *request.NamespaceExport) (interface{}, error)
|
||||
ImportInit(context.Context, *request.NamespaceImportInit) (interface{}, error)
|
||||
ImportRun(context.Context, *request.NamespaceImportRun) (interface{}, error)
|
||||
TriggerScript(context.Context, *request.NamespaceTriggerScript) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.NamespaceListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.NamespaceUpdateTranslations) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Namespace struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
Clone func(http.ResponseWriter, *http.Request)
|
||||
Export func(http.ResponseWriter, *http.Request)
|
||||
ImportInit func(http.ResponseWriter, *http.Request)
|
||||
ImportRun func(http.ResponseWriter, *http.Request)
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewNamespace(h NamespaceAPI) *Namespace {
|
||||
return &Namespace{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceUpload()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Upload(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Clone: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceClone()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Clone(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Export: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceExport()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Export(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ImportInit: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceImportInit()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ImportInit(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ImportRun: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceImportRun()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ImportRun(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScript: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceTriggerScript()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScript(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ListTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceListTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNamespaceUpdateTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Namespace) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/", h.List)
|
||||
r.Post("/namespace/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}", h.Update)
|
||||
r.Delete("/namespace/{namespaceID}", h.Delete)
|
||||
r.Post("/namespace/upload", h.Upload)
|
||||
r.Post("/namespace/{namespaceID}/clone", h.Clone)
|
||||
r.Get("/namespace/{namespaceID}/export/{filename}.zip", h.Export)
|
||||
r.Post("/namespace/import", h.ImportInit)
|
||||
r.Post("/namespace/import/{sessionID}", h.ImportRun)
|
||||
r.Post("/namespace/{namespaceID}/trigger", h.TriggerScript)
|
||||
r.Get("/namespace/{namespaceID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/translation", h.UpdateTranslations)
|
||||
})
|
||||
}
|
||||
57
server/compose/rest/handlers/notification.go
Normal file
57
server/compose/rest/handlers/notification.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
NotificationAPI interface {
|
||||
EmailSend(context.Context, *request.NotificationEmailSend) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Notification struct {
|
||||
EmailSend func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewNotification(h NotificationAPI) *Notification {
|
||||
return &Notification{
|
||||
EmailSend: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNotificationEmailSend()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.EmailSend(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Notification) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Post("/notification/email", h.EmailSend)
|
||||
})
|
||||
}
|
||||
266
server/compose/rest/handlers/page.go
Normal file
266
server/compose/rest/handlers/page.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
PageAPI interface {
|
||||
List(context.Context, *request.PageList) (interface{}, error)
|
||||
Create(context.Context, *request.PageCreate) (interface{}, error)
|
||||
Read(context.Context, *request.PageRead) (interface{}, error)
|
||||
Tree(context.Context, *request.PageTree) (interface{}, error)
|
||||
Update(context.Context, *request.PageUpdate) (interface{}, error)
|
||||
Reorder(context.Context, *request.PageReorder) (interface{}, error)
|
||||
Delete(context.Context, *request.PageDelete) (interface{}, error)
|
||||
Upload(context.Context, *request.PageUpload) (interface{}, error)
|
||||
TriggerScript(context.Context, *request.PageTriggerScript) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.PageListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.PageUpdateTranslations) (interface{}, error)
|
||||
UpdateIcon(context.Context, *request.PageUpdateIcon) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Page struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Tree func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Reorder func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateIcon func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewPage(h PageAPI) *Page {
|
||||
return &Page{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Tree: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageTree()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Tree(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Reorder: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageReorder()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Reorder(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageUpload()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Upload(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScript: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageTriggerScript()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScript(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ListTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageListTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageUpdateTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateIcon: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageUpdateIcon()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateIcon(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/page/", h.List)
|
||||
r.Post("/namespace/{namespaceID}/page/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}", h.Read)
|
||||
r.Get("/namespace/{namespaceID}/page/tree", h.Tree)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}", h.Update)
|
||||
r.Post("/namespace/{namespaceID}/page/{selfID}/reorder", h.Reorder)
|
||||
r.Delete("/namespace/{namespaceID}/page/{pageID}", h.Delete)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/attachment", h.Upload)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/trigger", h.TriggerScript)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/page/{pageID}/translation", h.UpdateTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/page/{pageID}/icon", h.UpdateIcon)
|
||||
})
|
||||
}
|
||||
228
server/compose/rest/handlers/pageLayout.go
Normal file
228
server/compose/rest/handlers/pageLayout.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
PageLayoutAPI interface {
|
||||
ListNamespace(context.Context, *request.PageLayoutListNamespace) (interface{}, error)
|
||||
List(context.Context, *request.PageLayoutList) (interface{}, error)
|
||||
Create(context.Context, *request.PageLayoutCreate) (interface{}, error)
|
||||
Read(context.Context, *request.PageLayoutRead) (interface{}, error)
|
||||
Update(context.Context, *request.PageLayoutUpdate) (interface{}, error)
|
||||
Reorder(context.Context, *request.PageLayoutReorder) (interface{}, error)
|
||||
Delete(context.Context, *request.PageLayoutDelete) (interface{}, error)
|
||||
Undelete(context.Context, *request.PageLayoutUndelete) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.PageLayoutListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.PageLayoutUpdateTranslations) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
PageLayout struct {
|
||||
ListNamespace func(http.ResponseWriter, *http.Request)
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Reorder func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Undelete func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewPageLayout(h PageLayoutAPI) *PageLayout {
|
||||
return &PageLayout{
|
||||
ListNamespace: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutListNamespace()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListNamespace(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Reorder: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutReorder()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Reorder(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Undelete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutUndelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Undelete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ListTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutListTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ListTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateTranslations: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageLayoutUpdateTranslations()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateTranslations(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h PageLayout) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/page-layout", h.ListNamespace)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}/layout/", h.List)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/layout/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}", h.Update)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/layout/reorder", h.Reorder)
|
||||
r.Delete("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}", h.Delete)
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}/undelete", h.Undelete)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/page/{pageID}/layout/{pageLayoutID}/translation", h.UpdateTranslations)
|
||||
})
|
||||
}
|
||||
152
server/compose/rest/handlers/permissions.go
Normal file
152
server/compose/rest/handlers/permissions.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
PermissionsAPI interface {
|
||||
List(context.Context, *request.PermissionsList) (interface{}, error)
|
||||
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
|
||||
Trace(context.Context, *request.PermissionsTrace) (interface{}, error)
|
||||
Read(context.Context, *request.PermissionsRead) (interface{}, error)
|
||||
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
|
||||
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Permissions struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Effective func(http.ResponseWriter, *http.Request)
|
||||
Trace func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewPermissions(h PermissionsAPI) *Permissions {
|
||||
return &Permissions{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Effective: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsEffective()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Effective(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Trace: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsTrace()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Trace(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/permissions/", h.List)
|
||||
r.Get("/permissions/effective", h.Effective)
|
||||
r.Get("/permissions/trace", h.Trace)
|
||||
r.Get("/permissions/{roleID}/rules", h.Read)
|
||||
r.Delete("/permissions/{roleID}/rules", h.Delete)
|
||||
r.Patch("/permissions/{roleID}/rules", h.Update)
|
||||
})
|
||||
}
|
||||
437
server/compose/rest/handlers/record.go
Normal file
437
server/compose/rest/handlers/record.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package handlers
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
RecordAPI interface {
|
||||
Report(context.Context, *request.RecordReport) (interface{}, error)
|
||||
List(context.Context, *request.RecordList) (interface{}, error)
|
||||
ImportInit(context.Context, *request.RecordImportInit) (interface{}, error)
|
||||
ImportRun(context.Context, *request.RecordImportRun) (interface{}, error)
|
||||
ImportProgress(context.Context, *request.RecordImportProgress) (interface{}, error)
|
||||
ExportInit(context.Context, *request.RecordExportInit) (interface{}, error)
|
||||
ExportPull(context.Context, *request.RecordExportPull) (interface{}, error)
|
||||
Export(context.Context, *request.RecordExport) (interface{}, error)
|
||||
Exec(context.Context, *request.RecordExec) (interface{}, error)
|
||||
Create(context.Context, *request.RecordCreate) (interface{}, error)
|
||||
Read(context.Context, *request.RecordRead) (interface{}, error)
|
||||
Update(context.Context, *request.RecordUpdate) (interface{}, error)
|
||||
Patch(context.Context, *request.RecordPatch) (interface{}, error)
|
||||
BulkDelete(context.Context, *request.RecordBulkDelete) (interface{}, error)
|
||||
Delete(context.Context, *request.RecordDelete) (interface{}, error)
|
||||
Undelete(context.Context, *request.RecordUndelete) (interface{}, error)
|
||||
BulkUndelete(context.Context, *request.RecordBulkUndelete) (interface{}, error)
|
||||
Upload(context.Context, *request.RecordUpload) (interface{}, error)
|
||||
TriggerScript(context.Context, *request.RecordTriggerScript) (interface{}, error)
|
||||
TriggerScriptOnList(context.Context, *request.RecordTriggerScriptOnList) (interface{}, error)
|
||||
Revisions(context.Context, *request.RecordRevisions) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Record struct {
|
||||
Report func(http.ResponseWriter, *http.Request)
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
ImportInit func(http.ResponseWriter, *http.Request)
|
||||
ImportRun func(http.ResponseWriter, *http.Request)
|
||||
ImportProgress func(http.ResponseWriter, *http.Request)
|
||||
ExportInit func(http.ResponseWriter, *http.Request)
|
||||
ExportPull func(http.ResponseWriter, *http.Request)
|
||||
Export func(http.ResponseWriter, *http.Request)
|
||||
Exec func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Patch func(http.ResponseWriter, *http.Request)
|
||||
BulkDelete func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Undelete func(http.ResponseWriter, *http.Request)
|
||||
BulkUndelete func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
TriggerScriptOnList func(http.ResponseWriter, *http.Request)
|
||||
Revisions func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewRecord(h RecordAPI) *Record {
|
||||
return &Record{
|
||||
Report: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordReport()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Report(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ImportInit: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordImportInit()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ImportInit(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ImportRun: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordImportRun()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ImportRun(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ImportProgress: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordImportProgress()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ImportProgress(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ExportInit: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordExportInit()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ExportInit(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
ExportPull: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordExportPull()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.ExportPull(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Export: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordExport()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Export(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Exec: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordExec()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Exec(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordCreate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Create(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordRead()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Read(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordUpdate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Update(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Patch: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordPatch()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Patch(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
BulkDelete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordBulkDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.BulkDelete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Undelete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordUndelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Undelete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
BulkUndelete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordBulkUndelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.BulkUndelete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordUpload()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Upload(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScript: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordTriggerScript()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScript(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
TriggerScriptOnList: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordTriggerScriptOnList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.TriggerScriptOnList(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Revisions: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordRevisions()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Revisions(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Record) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/report", h.Report)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/", h.List)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/import", h.ImportInit)
|
||||
r.Patch("/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}", h.ImportRun)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}", h.ImportProgress)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/export", h.ExportInit)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/export/{sessionID}/{filename}.{ext}", h.ExportPull)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}", h.Export)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/exec/{procedure}", h.Exec)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Update)
|
||||
r.Patch("/namespace/{namespaceID}/module/{moduleID}/record/", h.Patch)
|
||||
r.Delete("/namespace/{namespaceID}/module/{moduleID}/record/", h.BulkDelete)
|
||||
r.Delete("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Delete)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/undelete", h.Undelete)
|
||||
r.Patch("/namespace/{namespaceID}/module/{moduleID}/record/undelete", h.BulkUndelete)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/attachment", h.Upload)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/trigger", h.TriggerScript)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/trigger", h.TriggerScriptOnList)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/revisions", h.Revisions)
|
||||
})
|
||||
}
|
||||
126
server/compose/rest/icon.go
Normal file
126
server/compose/rest/icon.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
iconPayload struct {
|
||||
*attachmentPayload
|
||||
}
|
||||
|
||||
iconSetPayload struct {
|
||||
Filter types.IconFilter `json:"filter"`
|
||||
Set []*iconPayload `json:"set"`
|
||||
}
|
||||
|
||||
Icon struct {
|
||||
locale service.ResourceTranslationsManagerService
|
||||
attachment service.AttachmentService
|
||||
ac iconAccessController
|
||||
}
|
||||
|
||||
iconAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Icon) New() *Icon {
|
||||
return &Icon{
|
||||
locale: service.DefaultResourceTranslation,
|
||||
attachment: service.DefaultAttachment,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Icon) List(ctx context.Context, r *request.IconList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.AttachmentFilter{
|
||||
Kind: types.IconAttachment,
|
||||
}
|
||||
set types.AttachmentSet
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get only the undeleted icons
|
||||
f.Deleted = filter.StateExcluded
|
||||
|
||||
set, f, err = ctrl.attachment.Find(ctx, f)
|
||||
return ctrl.makeIconFilterPayload(ctx, set, f, err)
|
||||
}
|
||||
|
||||
func (ctrl *Icon) Upload(ctx context.Context, r *request.IconUpload) (interface{}, error) {
|
||||
file, err := r.Icon.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(file multipart.File) {
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(file)
|
||||
|
||||
a, err := ctrl.attachment.CreateIconAttachment(
|
||||
ctx,
|
||||
r.Icon.Filename,
|
||||
r.Icon.Size,
|
||||
file,
|
||||
)
|
||||
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl *Icon) makeIconFilterPayload(ctx context.Context, nn types.AttachmentSet, f types.AttachmentFilter, err error) (*iconSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
a *attachmentPayload
|
||||
ff types.IconFilter
|
||||
)
|
||||
|
||||
ff.Paging = f.Paging
|
||||
ff.Sorting = f.Sorting
|
||||
|
||||
res := &iconSetPayload{Filter: ff, Set: make([]*iconPayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
a, _ = makeAttachmentPayload(ctx, nn[i], nil)
|
||||
res.Set[i] = &iconPayload{a}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ctrl *Icon) Delete(ctx context.Context, r *request.IconDelete) (interface{}, error) {
|
||||
if !auth.GetIdentityFromContext(ctx).Valid() {
|
||||
return nil, errors.Unauthorized("cannot delete icon")
|
||||
}
|
||||
|
||||
_, err := ctrl.attachment.FindByID(ctx, 0, r.IconID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.attachment.DeleteByID(ctx, 0, r.IconID)
|
||||
}
|
||||
227
server/compose/rest/module.go
Normal file
227
server/compose/rest/module.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/corredor"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
moduleSetPayload struct {
|
||||
Filter types.ModuleFilter `json:"filter"`
|
||||
Set []*modulePayload `json:"set"`
|
||||
}
|
||||
|
||||
modulePayload struct {
|
||||
*types.Module
|
||||
|
||||
Fields []*moduleFieldPayload `json:"fields"`
|
||||
|
||||
CanGrant bool `json:"canGrant"`
|
||||
CanUpdateModule bool `json:"canUpdateModule"`
|
||||
CanDeleteModule bool `json:"canDeleteModule"`
|
||||
CanCreateRecord bool `json:"canCreateRecord"`
|
||||
CanCreateOwnedRecord bool `json:"canCreateOwnedRecord"`
|
||||
}
|
||||
|
||||
moduleFieldPayload struct {
|
||||
*types.ModuleField
|
||||
|
||||
CanReadRecordValue bool `json:"canReadRecordValue"`
|
||||
CanUpdateRecordValue bool `json:"canUpdateRecordValue"`
|
||||
}
|
||||
|
||||
Module struct {
|
||||
module service.ModuleService
|
||||
locale service.ResourceTranslationsManagerService
|
||||
namespace service.NamespaceService
|
||||
ac moduleAccessController
|
||||
}
|
||||
|
||||
moduleAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
|
||||
CanUpdateModule(context.Context, *types.Module) bool
|
||||
CanDeleteModule(context.Context, *types.Module) bool
|
||||
CanCreateRecordOnModule(context.Context, *types.Module) bool
|
||||
CanCreateOwnedRecordOnModule(context.Context, *types.Module) bool
|
||||
CanReadRecord(context.Context, *types.Record) bool
|
||||
|
||||
CanReadRecordValueOnModuleField(context.Context, *types.ModuleField) bool
|
||||
CanUpdateRecordValueOnModuleField(context.Context, *types.ModuleField) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Module) New() *Module {
|
||||
return &Module{
|
||||
module: service.DefaultModule,
|
||||
namespace: service.DefaultNamespace,
|
||||
ac: service.DefaultAccessControl,
|
||||
locale: service.DefaultResourceTranslation,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.ModuleFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
Query: r.Query,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.IncTotal = r.IncTotal
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.module.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) {
|
||||
mod, err := ctrl.module.FindByID(ctx, r.NamespaceID, r.ModuleID)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Module) ListTranslations(ctx context.Context, r *request.ModuleListTranslations) (interface{}, error) {
|
||||
return ctrl.locale.Module(ctx, r.NamespaceID, r.ModuleID)
|
||||
}
|
||||
|
||||
func (ctrl *Module) UpdateTranslations(ctx context.Context, r *request.ModuleUpdateTranslations) (interface{}, error) {
|
||||
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
|
||||
}
|
||||
|
||||
func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.Module{
|
||||
NamespaceID: r.NamespaceID,
|
||||
Config: r.Config,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Fields: r.Fields,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
mod, err = ctrl.module.Create(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.Module{
|
||||
ID: r.ModuleID,
|
||||
NamespaceID: r.NamespaceID,
|
||||
Config: r.Config,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Fields: r.Fields,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
mod, err = ctrl.module.Update(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Module) Delete(ctx context.Context, r *request.ModuleDelete) (interface{}, error) {
|
||||
_, err := ctrl.module.FindByID(ctx, r.NamespaceID, r.ModuleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.module.DeleteByID(ctx, r.NamespaceID, r.ModuleID)
|
||||
}
|
||||
|
||||
func (ctrl *Module) TriggerScript(ctx context.Context, r *request.ModuleTriggerScript) (rsp interface{}, err error) {
|
||||
var (
|
||||
module *types.Module
|
||||
namespace *types.Namespace
|
||||
)
|
||||
|
||||
if module, err = ctrl.module.FindByID(ctx, r.NamespaceID, r.ModuleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if namespace, err = ctrl.namespace.FindByID(ctx, r.NamespaceID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo implement same behaviour as we have on record - module+oldModule
|
||||
err = corredor.Service().Exec(ctx, r.Script, corredor.ExtendScriptArgs(event.ModuleOnManual(module, module, namespace), r.Args))
|
||||
return ctrl.makePayload(ctx, module, err)
|
||||
}
|
||||
|
||||
func (ctrl Module) makePayload(ctx context.Context, m *types.Module, err error) (*modulePayload, error) {
|
||||
if err != nil || m == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mfp, err := ctrl.makeFieldsPayload(ctx, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &modulePayload{
|
||||
Module: m,
|
||||
|
||||
Fields: mfp,
|
||||
|
||||
CanGrant: ctrl.ac.CanGrant(ctx),
|
||||
|
||||
CanUpdateModule: ctrl.ac.CanUpdateModule(ctx, m),
|
||||
CanDeleteModule: ctrl.ac.CanDeleteModule(ctx, m),
|
||||
|
||||
CanCreateRecord: ctrl.ac.CanCreateRecordOnModule(ctx, m),
|
||||
CanCreateOwnedRecord: ctrl.ac.CanCreateOwnedRecordOnModule(ctx, m),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Module) makeFieldsPayload(ctx context.Context, m *types.Module) (out []*moduleFieldPayload, err error) {
|
||||
out = make([]*moduleFieldPayload, len(m.Fields))
|
||||
|
||||
for i, f := range m.Fields {
|
||||
out[i] = &moduleFieldPayload{
|
||||
ModuleField: f,
|
||||
|
||||
CanReadRecordValue: ctrl.ac.CanReadRecordValueOnModuleField(ctx, f),
|
||||
CanUpdateRecordValue: ctrl.ac.CanUpdateRecordValueOnModuleField(ctx, f),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl Module) makeFilterPayload(ctx context.Context, nn types.ModuleSet, f types.ModuleFilter, err error) (*moduleSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msp := &moduleSetPayload{Filter: f, Set: make([]*modulePayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
msp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
|
||||
}
|
||||
|
||||
return msp, nil
|
||||
}
|
||||
600
server/compose/rest/namespace.go
Normal file
600
server/compose/rest/namespace.go
Normal file
@@ -0,0 +1,600 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
automationTypes "github.com/cortezaproject/corteza/server/automation/types"
|
||||
composeEnvoy "github.com/cortezaproject/corteza/server/compose/envoy"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/corredor"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
systemEnvoy "github.com/cortezaproject/corteza/server/system/envoy"
|
||||
systemService "github.com/cortezaproject/corteza/server/system/service"
|
||||
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
namespacePayload struct {
|
||||
*types.Namespace
|
||||
|
||||
CanGrant bool `json:"canGrant"`
|
||||
CanExportNamespace bool `json:"canExportNamespace"`
|
||||
CanUpdateNamespace bool `json:"canUpdateNamespace"`
|
||||
CanDeleteNamespace bool `json:"canDeleteNamespace"`
|
||||
CanManageNamespace bool `json:"canManageNamespace"`
|
||||
CanCreateModule bool `json:"canCreateModule"`
|
||||
CanExportModules bool `json:"canExportModules"`
|
||||
CanCreateChart bool `json:"canCreateChart"`
|
||||
CanExportCharts bool `json:"canExportCharts"`
|
||||
CanCreatePage bool `json:"canCreatePage"`
|
||||
}
|
||||
|
||||
namespaceSetPayload struct {
|
||||
Filter types.NamespaceFilter `json:"filter"`
|
||||
Set []*namespacePayload `json:"set"`
|
||||
}
|
||||
|
||||
pageFinder interface {
|
||||
Find(ctx context.Context, filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
|
||||
}
|
||||
|
||||
pageLayoutFinder interface {
|
||||
Find(ctx context.Context, filter types.PageLayoutFilter) (set types.PageLayoutSet, f types.PageLayoutFilter, err error)
|
||||
}
|
||||
|
||||
chartFinder interface {
|
||||
Find(ctx context.Context, filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
|
||||
}
|
||||
|
||||
Namespace struct {
|
||||
namespace service.NamespaceService
|
||||
module service.ModuleService
|
||||
page pageFinder
|
||||
pageLayout pageLayoutFinder
|
||||
chart chartFinder
|
||||
locale service.ResourceTranslationsManagerService
|
||||
attachment service.AttachmentService
|
||||
role systemService.RoleService
|
||||
ac namespaceAccessController
|
||||
}
|
||||
|
||||
namespaceAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
|
||||
CanExportNamespace(context.Context, *types.Namespace) bool
|
||||
CanUpdateNamespace(context.Context, *types.Namespace) bool
|
||||
CanDeleteNamespace(context.Context, *types.Namespace) bool
|
||||
CanManageNamespace(context.Context, *types.Namespace) bool
|
||||
|
||||
CanCreateModuleOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanExportModulesOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanCreateChartOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanExportChartsOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanCreatePageOnNamespace(context.Context, *types.Namespace) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Namespace) New() *Namespace {
|
||||
return &Namespace{
|
||||
namespace: service.DefaultNamespace,
|
||||
module: service.DefaultModule,
|
||||
page: service.DefaultPage,
|
||||
pageLayout: service.DefaultPageLayout,
|
||||
chart: service.DefaultChart,
|
||||
locale: service.DefaultResourceTranslation,
|
||||
role: systemService.DefaultRole,
|
||||
attachment: service.DefaultAttachment,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.NamespaceFilter{
|
||||
Query: r.Query,
|
||||
Slug: r.Slug,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.IncTotal = r.IncTotal
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.namespace.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
ns = &types.Namespace{
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
Enabled: r.Enabled,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
if err = r.Meta.Unmarshal(&ns.Meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, err = ctrl.namespace.Create(ctx, ns)
|
||||
return ctrl.makePayload(ctx, ns, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Read(ctx context.Context, r *request.NamespaceRead) (interface{}, error) {
|
||||
ns, err := ctrl.namespace.FindByID(ctx, r.NamespaceID)
|
||||
return ctrl.makePayload(ctx, ns, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) ListTranslations(ctx context.Context, r *request.NamespaceListTranslations) (interface{}, error) {
|
||||
return ctrl.locale.Namespace(ctx, r.NamespaceID)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) UpdateTranslations(ctx context.Context, r *request.NamespaceUpdateTranslations) (interface{}, error) {
|
||||
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
ns = &types.Namespace{
|
||||
ID: r.NamespaceID,
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
Enabled: r.Enabled,
|
||||
Labels: r.Labels,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
if err = r.Meta.Unmarshal(&ns.Meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, err = ctrl.namespace.Update(ctx, ns)
|
||||
return ctrl.makePayload(ctx, ns, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Delete(ctx context.Context, r *request.NamespaceDelete) (interface{}, error) {
|
||||
_, err := ctrl.namespace.FindByID(ctx, r.NamespaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.namespace.DeleteByID(ctx, r.NamespaceID)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Upload(ctx context.Context, r *request.NamespaceUpload) (interface{}, error) {
|
||||
file, err := r.Upload.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
a, err := ctrl.attachment.CreateNamespaceAttachment(
|
||||
ctx,
|
||||
r.Upload.Filename,
|
||||
r.Upload.Size,
|
||||
file,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Clone(ctx context.Context, r *request.NamespaceClone) (interface{}, error) {
|
||||
dup := &types.Namespace{
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
}
|
||||
|
||||
// @todo temporary workaround cause Envoy requires some identifiable thing
|
||||
if dup.Slug == "" {
|
||||
dup.Slug = fmt.Sprintf("cl_%d", r.NamespaceID)
|
||||
}
|
||||
|
||||
nodes, err := ctrl.gatherNodes(ctx, r.NamespaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decoder := func() (envoyx.NodeSet, error) {
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
ns, err := ctrl.namespace.Clone(ctx, r.NamespaceID, dup, decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// @todo temporary workaround cause Envoy requires some identifiable thing
|
||||
if r.Slug == "" {
|
||||
ns.Slug = ""
|
||||
ns, err = ctrl.namespace.Update(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ctrl.makePayload(ctx, ns, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) Export(ctx context.Context, r *request.NamespaceExport) (out interface{}, err error) {
|
||||
nodes, err := ctrl.gatherNodes(ctx, r.NamespaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p := envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeIo,
|
||||
Params: map[string]any{},
|
||||
}
|
||||
|
||||
evsvc := envoyx.Global()
|
||||
gg, err := evsvc.Bake(ctx, p, nil, nodes...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Archive encoded resources
|
||||
buf := bytes.NewBuffer(nil)
|
||||
zw := zip.NewWriter(buf)
|
||||
|
||||
f, err := zw.Create(fmt.Sprintf("%s.yaml", r.Filename))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.Params["writer"] = f
|
||||
err = evsvc.Encode(ctx, p, gg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = zw.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return ctrl.serveExport(ctx, fmt.Sprintf("%s.zip", r.Filename), bytes.NewReader(buf.Bytes()), nil)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) ImportInit(ctx context.Context, r *request.NamespaceImportInit) (interface{}, error) {
|
||||
f, err := r.Upload.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ctrl.namespace.ImportInit(ctx, f, r.Upload.Size)
|
||||
// return ctrl.namespace.ImportInit(ctx, f, r.Upload.Header.Get("content-type"), r.Upload.Size)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) ImportRun(ctx context.Context, r *request.NamespaceImportRun) (interface{}, error) {
|
||||
var (
|
||||
dup = &types.Namespace{
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
}
|
||||
)
|
||||
|
||||
// @todo temporary workaround cause Envoy requires some identifiable thing
|
||||
if dup.Slug == "" {
|
||||
dup.Slug = fmt.Sprintf("cl_%d", r.SessionID)
|
||||
}
|
||||
|
||||
ns, err := ctrl.namespace.ImportRun(ctx, r.SessionID, dup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// @todo temporary workaround cause Envoy requires some identifiable thing
|
||||
if r.Slug == "" {
|
||||
ns.Slug = ""
|
||||
ns, err = ctrl.namespace.Update(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ctrl.makePayload(ctx, ns, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) serveExport(ctx context.Context, fn string, archive io.ReadSeeker, err error) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Add("Content-Disposition", "attachment; filename="+fn)
|
||||
|
||||
http.ServeContent(w, req, fn, time.Now(), archive)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl *Namespace) TriggerScript(ctx context.Context, r *request.NamespaceTriggerScript) (rsp interface{}, err error) {
|
||||
var (
|
||||
namespace *types.Namespace
|
||||
)
|
||||
|
||||
if namespace, err = ctrl.namespace.FindByID(ctx, r.NamespaceID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = corredor.Service().Exec(ctx, r.Script, corredor.ExtendScriptArgs(event.NamespaceOnManual(namespace, nil), r.Args))
|
||||
return ctrl.makePayload(ctx, namespace, err)
|
||||
}
|
||||
|
||||
func (ctrl Namespace) makePayload(ctx context.Context, ns *types.Namespace, err error) (*namespacePayload, error) {
|
||||
if err != nil || ns == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &namespacePayload{
|
||||
Namespace: ns,
|
||||
|
||||
CanGrant: ctrl.ac.CanGrant(ctx),
|
||||
CanExportNamespace: ctrl.ac.CanExportNamespace(ctx, ns),
|
||||
CanUpdateNamespace: ctrl.ac.CanUpdateNamespace(ctx, ns),
|
||||
CanDeleteNamespace: ctrl.ac.CanDeleteNamespace(ctx, ns),
|
||||
CanManageNamespace: ctrl.ac.CanManageNamespace(ctx, ns),
|
||||
|
||||
CanCreateModule: ctrl.ac.CanCreateModuleOnNamespace(ctx, ns),
|
||||
CanExportModules: ctrl.ac.CanExportModulesOnNamespace(ctx, ns),
|
||||
CanCreateChart: ctrl.ac.CanCreateChartOnNamespace(ctx, ns),
|
||||
CanExportCharts: ctrl.ac.CanExportChartsOnNamespace(ctx, ns),
|
||||
CanCreatePage: ctrl.ac.CanCreatePageOnNamespace(ctx, ns),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Namespace) makeFilterPayload(ctx context.Context, nn types.NamespaceSet, f types.NamespaceFilter, err error) (*namespaceSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsp := &namespaceSetPayload{Filter: f, Set: make([]*namespacePayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
|
||||
}
|
||||
|
||||
return nsp, nil
|
||||
}
|
||||
|
||||
func (ctrl Namespace) gatherNodes(ctx context.Context, namespaceID uint64) (resources envoyx.NodeSet, err error) {
|
||||
var (
|
||||
nsII envoyx.Identifiers
|
||||
aux envoyx.NodeSet
|
||||
)
|
||||
|
||||
// Prepare resources
|
||||
aux, nsII, err = ctrl.exportCompose(ctx, namespaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux...)
|
||||
|
||||
// Tweak exported resources
|
||||
resources = ctrl.tweakExport(ctx, resources, nsII)
|
||||
|
||||
// Role placeholders for RBAC
|
||||
aux, err = ctrl.preparePlaceholders(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux...)
|
||||
|
||||
// RBAC
|
||||
aux, err = ctrl.exportRBAC(ctx, resources)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux...)
|
||||
|
||||
// Translations
|
||||
aux, err = ctrl.exportResourceTranslations(ctx, resources)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl Namespace) exportCompose(ctx context.Context, namespaceID uint64) (resources envoyx.NodeSet, nsII envoyx.Identifiers, err error) {
|
||||
// - namespace
|
||||
n, err := ctrl.namespace.FindByID(ctx, namespaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo this isn't ok, will do for now
|
||||
if !ctrl.ac.CanExportNamespace(ctx, n) {
|
||||
err = fmt.Errorf("not allowed to export namespace %s", n.Name)
|
||||
return
|
||||
}
|
||||
|
||||
nsNode, err := composeEnvoy.NamespaceToEnvoyNode(n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nsII = nsNode.Identifiers
|
||||
resources = append(resources, nsNode)
|
||||
|
||||
// - modules
|
||||
mm, _, err := ctrl.module.Find(ctx, types.ModuleFilter{NamespaceID: n.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range mm {
|
||||
var aux *envoyx.Node
|
||||
aux, err = composeEnvoy.ModuleToEnvoyNode(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux)
|
||||
|
||||
for _, f := range m.Fields {
|
||||
aux, err = composeEnvoy.ModuleFieldToEnvoyNode(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux)
|
||||
}
|
||||
}
|
||||
|
||||
// - pages
|
||||
pp, _, err := ctrl.page.Find(ctx, types.PageFilter{NamespaceID: n.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range pp {
|
||||
var aux *envoyx.Node
|
||||
aux, err = composeEnvoy.PageToEnvoyNode(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux)
|
||||
}
|
||||
|
||||
// - page layouts
|
||||
ll, _, err := ctrl.pageLayout.Find(ctx, types.PageLayoutFilter{NamespaceID: n.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, l := range ll {
|
||||
var aux *envoyx.Node
|
||||
aux, err = composeEnvoy.PageLayoutToEnvoyNode(l)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux)
|
||||
}
|
||||
|
||||
// - charts
|
||||
cc, _, err := ctrl.chart.Find(ctx, types.ChartFilter{NamespaceID: n.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, c := range cc {
|
||||
var aux *envoyx.Node
|
||||
aux, err = composeEnvoy.ChartToEnvoyNode(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resources = append(resources, aux)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl Namespace) exportRBAC(ctx context.Context, base envoyx.NodeSet) (resources envoyx.NodeSet, err error) {
|
||||
// Prepare RBAC Rules
|
||||
rawRules := rbac.Global().Rules()
|
||||
|
||||
resources, err = envoyx.RBACRulesForNodes(rawRules, base...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl Namespace) exportResourceTranslations(ctx context.Context, base envoyx.NodeSet) (resources envoyx.NodeSet, err error) {
|
||||
var (
|
||||
lsvc = locale.Global()
|
||||
tags = lsvc.Tags()
|
||||
translations = make([]*locale.ResourceTranslation, 0, 128)
|
||||
|
||||
resKeyTrans map[string]map[string]*locale.ResourceTranslation
|
||||
)
|
||||
|
||||
for _, t := range tags {
|
||||
resKeyTrans, err = lsvc.LoadResourceTranslations(ctx, t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, keyTrans := range resKeyTrans {
|
||||
for _, trans := range keyTrans {
|
||||
translations = append(translations, trans)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resources, err = envoyx.ResourceTranslationsForNodes(systemTypes.FromLocale(translations), base...)
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl Namespace) tweakExport(ctx context.Context, nodes envoyx.NodeSet, nsII envoyx.Identifiers) envoyx.NodeSet {
|
||||
nsRef := envoyx.Ref{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: nsII,
|
||||
Scope: envoyx.Scope{
|
||||
ResourceType: types.NamespaceResourceType,
|
||||
Identifiers: nsII,
|
||||
},
|
||||
}
|
||||
nsNode := envoyx.NodeForRef(nsRef, nodes...)
|
||||
|
||||
// - remove logo and icon references as attachments are not exported by default
|
||||
// @todo code in attachment exporting, most likely when we do attachment handling rework
|
||||
ns := nsNode.Resource.(*types.Namespace)
|
||||
ns.Meta.Icon = ""
|
||||
ns.Meta.IconID = 0
|
||||
ns.Meta.Logo = ""
|
||||
ns.Meta.LogoID = 0
|
||||
ns.Meta.LogoEnabled = false
|
||||
|
||||
// - prune resources we won't preserve
|
||||
pref := envoyx.Ref{
|
||||
ResourceType: automationTypes.WorkflowResourceType,
|
||||
}
|
||||
for _, n := range nodes {
|
||||
n.Prune(pref)
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (ctrl Namespace) preparePlaceholders(ctx context.Context) (resources envoyx.NodeSet, err error) {
|
||||
rr, _, err := ctrl.role.Find(ctx, systemTypes.RoleFilter{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var aux *envoyx.Node
|
||||
for _, role := range rr {
|
||||
aux, err = systemEnvoy.RoleToEnvoyNode(role)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aux.Placeholder = true
|
||||
resources = append(resources, aux)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
62
server/compose/rest/notification.go
Normal file
62
server/compose/rest/notification.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Notification struct {
|
||||
svc notificationService
|
||||
}
|
||||
|
||||
contentPayload struct {
|
||||
Plain string `json:"plain"`
|
||||
HTML string `json:"html"`
|
||||
}
|
||||
|
||||
notificationService interface {
|
||||
SendEmail(context.Context, *types.EmailNotification) error
|
||||
}
|
||||
)
|
||||
|
||||
func (Notification) New() *Notification {
|
||||
return &Notification{
|
||||
svc: service.DefaultNotification,
|
||||
}
|
||||
}
|
||||
|
||||
// EmailSend assembles Email Message and pushes message to notification service
|
||||
func (ctrl *Notification) EmailSend(ctx context.Context, r *request.NotificationEmailSend) (interface{}, error) {
|
||||
ntf := &types.EmailNotification{
|
||||
To: r.To,
|
||||
Cc: r.Cc,
|
||||
ReplyTo: r.ReplyTo,
|
||||
Subject: r.Subject,
|
||||
RemoteAttachments: r.RemoteAttachments,
|
||||
}
|
||||
|
||||
var cp = contentPayload{}
|
||||
if err := r.Content.Unmarshal(&cp); err != nil {
|
||||
return false, fmt.Errorf("could not unmarshal content: %w", err)
|
||||
} else {
|
||||
if len(cp.HTML) > 0 {
|
||||
ntf.ContentHTML = cp.HTML
|
||||
|
||||
}
|
||||
|
||||
if len(cp.Plain) > 0 {
|
||||
ntf.ContentPlain = cp.Plain
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctrl.svc.SendEmail(ctx, ntf); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
338
server/compose/rest/page.go
Normal file
338
server/compose/rest/page.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/corredor"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
)
|
||||
|
||||
type (
|
||||
pagePayload struct {
|
||||
*types.Page
|
||||
|
||||
Children []*pagePayload `json:"children,omitempty"`
|
||||
|
||||
CanGrant bool `json:"canGrant"`
|
||||
CanUpdatePage bool `json:"canUpdatePage"`
|
||||
CanDeletePage bool `json:"canDeletePage"`
|
||||
}
|
||||
|
||||
pageSetPayload struct {
|
||||
Filter types.PageFilter `json:"filter"`
|
||||
Set []*pagePayload `json:"set"`
|
||||
}
|
||||
|
||||
pageIconPayload struct {
|
||||
*types.PageConfigIcon
|
||||
}
|
||||
|
||||
Page struct {
|
||||
page interface {
|
||||
FindByID(ctx context.Context, namespaceID, pageID uint64) (*types.Page, error)
|
||||
FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.Page, error)
|
||||
FindByPageID(ctx context.Context, namespaceID, pageID uint64) (*types.Page, error)
|
||||
FindBySelfID(ctx context.Context, namespaceID, selfID uint64) (pages types.PageSet, f types.PageFilter, err error)
|
||||
Find(ctx context.Context, filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
|
||||
Tree(ctx context.Context, namespaceID uint64) (pages types.PageSet, err error)
|
||||
|
||||
Create(ctx context.Context, page *types.Page) (*types.Page, error)
|
||||
Update(ctx context.Context, page *types.Page) (*types.Page, error)
|
||||
DeleteByID(ctx context.Context, namespaceID, pageID uint64, pds types.PageChildrenDeleteStrategy) error
|
||||
|
||||
UpdateIcon(ctx context.Context, namespaceID, pageID uint64, icon *types.PageConfigIcon) (out *types.PageConfigIcon, err error)
|
||||
|
||||
Reorder(ctx context.Context, namespaceID, selfID uint64, pageIDs []uint64) error
|
||||
}
|
||||
locale service.ResourceTranslationsManagerService
|
||||
namespace service.NamespaceService
|
||||
attachment service.AttachmentService
|
||||
ac pageAccessController
|
||||
}
|
||||
|
||||
pageAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
|
||||
CanUpdatePage(context.Context, *types.Page) bool
|
||||
CanDeletePage(context.Context, *types.Page) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Page) New() *Page {
|
||||
return &Page{
|
||||
page: service.DefaultPage,
|
||||
locale: service.DefaultResourceTranslation,
|
||||
namespace: service.DefaultNamespace,
|
||||
attachment: service.DefaultAttachment,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.PageFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
ParentID: r.SelfID,
|
||||
Labels: r.Labels,
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.page.Find(ctx, f)
|
||||
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Tree(ctx context.Context, r *request.PageTree) (interface{}, error) {
|
||||
tree, err := ctrl.page.Tree(ctx, r.NamespaceID)
|
||||
return ctrl.makeTreePayload(ctx, tree, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.Page{
|
||||
NamespaceID: r.NamespaceID,
|
||||
SelfID: r.SelfID,
|
||||
ModuleID: r.ModuleID,
|
||||
Title: r.Title,
|
||||
Handle: r.Handle,
|
||||
Description: r.Description,
|
||||
Visible: r.Visible,
|
||||
Weight: r.Weight,
|
||||
Labels: r.Labels,
|
||||
Meta: r.Meta,
|
||||
}
|
||||
)
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
if err = r.Config.Unmarshal(&mod.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Blocks) > 2 {
|
||||
if err = r.Blocks.Unmarshal(&mod.Blocks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mod, err = ctrl.page.Create(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) {
|
||||
mod, err := ctrl.page.FindByID(ctx, r.NamespaceID, r.PageID)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) ListTranslations(ctx context.Context, r *request.PageListTranslations) (interface{}, error) {
|
||||
return ctrl.locale.Page(ctx, r.NamespaceID, r.PageID)
|
||||
}
|
||||
|
||||
func (ctrl *Page) UpdateTranslations(ctx context.Context, r *request.PageUpdateTranslations) (interface{}, error) {
|
||||
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) {
|
||||
return api.OK(), ctrl.page.Reorder(ctx, r.NamespaceID, r.SelfID, payload.ParseUint64s(r.PageIDs))
|
||||
}
|
||||
|
||||
func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.Page{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ID: r.PageID,
|
||||
SelfID: r.SelfID,
|
||||
ModuleID: r.ModuleID,
|
||||
Title: r.Title,
|
||||
Handle: r.Handle,
|
||||
Description: r.Description,
|
||||
Visible: r.Visible,
|
||||
Weight: r.Weight,
|
||||
Labels: r.Labels,
|
||||
Meta: r.Meta,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
// Process config if it was included in the request
|
||||
// if not, do not assume that config has been removed!
|
||||
if err = r.Config.Unmarshal(&mod.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Blocks) > 2 {
|
||||
// Process blocks if they were included in the request
|
||||
// if not, do not assume that blocks were removed!
|
||||
if err = r.Blocks.Unmarshal(&mod.Blocks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mod, err = ctrl.page.Update(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) {
|
||||
var strategy types.PageChildrenDeleteStrategy
|
||||
|
||||
switch aux := types.PageChildrenDeleteStrategy(r.Strategy); aux {
|
||||
case types.PageChildrenOnDeleteForce,
|
||||
types.PageChildrenOnDeleteRebase,
|
||||
types.PageChildrenOnDeleteCascade:
|
||||
strategy = aux
|
||||
default:
|
||||
strategy = types.PageChildrenOnDeleteAbort
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.page.DeleteByID(ctx, r.NamespaceID, r.PageID, strategy)
|
||||
}
|
||||
|
||||
func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{}, error) {
|
||||
file, err := r.Upload.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
a, err := ctrl.attachment.CreatePageAttachment(
|
||||
ctx,
|
||||
r.NamespaceID,
|
||||
r.Upload.Filename,
|
||||
r.Upload.Size,
|
||||
file,
|
||||
r.PageID,
|
||||
)
|
||||
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) TriggerScript(ctx context.Context, r *request.PageTriggerScript) (rsp interface{}, err error) {
|
||||
var (
|
||||
page *types.Page
|
||||
namespace *types.Namespace
|
||||
)
|
||||
|
||||
if page, err = ctrl.page.FindByID(ctx, r.NamespaceID, r.PageID); err != nil {
|
||||
return
|
||||
}
|
||||
if namespace, err = ctrl.namespace.FindByID(ctx, r.NamespaceID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo implement same behaviour as we have on record - page+oldPage
|
||||
err = corredor.Service().Exec(ctx, r.Script, corredor.ExtendScriptArgs(event.PageOnManual(page, page, namespace, nil), r.Args))
|
||||
return ctrl.makePayload(ctx, page, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) UpdateIcon(ctx context.Context, r *request.PageUpdateIcon) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
icon = &types.PageConfigIcon{
|
||||
Type: r.Type,
|
||||
Src: r.Source,
|
||||
Style: func() map[string]string {
|
||||
style := make(map[string]string, len(r.Style))
|
||||
for k, v := range r.Style {
|
||||
if v.Val != "" {
|
||||
style[k] = v.Val
|
||||
} else if len(v.Values) > 0 {
|
||||
style[k] = strings.Join(v.Values, ",")
|
||||
}
|
||||
}
|
||||
return style
|
||||
}(),
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
icon, err = ctrl.page.UpdateIcon(ctx, r.NamespaceID, r.PageID, icon)
|
||||
return ctrl.makeIconPayload(ctx, icon, err)
|
||||
}
|
||||
|
||||
func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pagePayload, error) {
|
||||
if err != nil || c == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pagePayload{
|
||||
Page: c,
|
||||
|
||||
CanGrant: ctrl.ac.CanGrant(ctx),
|
||||
|
||||
CanUpdatePage: ctrl.ac.CanUpdatePage(ctx, c),
|
||||
CanDeletePage: ctrl.ac.CanDeletePage(ctx, c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Page) makeIconPayload(_ context.Context, i *types.PageConfigIcon, err error) (*pageIconPayload, error) {
|
||||
if err != nil || i == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pageIconPayload{
|
||||
PageConfigIcon: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Page) makeTreePayload(ctx context.Context, pp types.PageSet, err error) ([]*pagePayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set := make([]*pagePayload, len(pp))
|
||||
|
||||
for i := range pp {
|
||||
set[i], err = ctrl.makePayload(ctx, pp[i], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(pp[i].Children) > 0 {
|
||||
set[i].Children, err = ctrl.makeTreePayload(ctx, pp[i].Children, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func (ctrl Page) makeFilterPayload(ctx context.Context, nn types.PageSet, f types.PageFilter, err error) (*pageSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modp := &pageSetPayload{Filter: f, Set: make([]*pagePayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
|
||||
}
|
||||
|
||||
return modp, nil
|
||||
}
|
||||
224
server/compose/rest/page_layout.go
Normal file
224
server/compose/rest/page_layout.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
)
|
||||
|
||||
type (
|
||||
pageLayoutPayload struct {
|
||||
*types.PageLayout
|
||||
|
||||
// CanGrant bool `json:"canGrant"`
|
||||
// CanUpdatePageLayout bool `json:"canUpdatePageLayout"`
|
||||
// CanDeletePageLayout bool `json:"canDeletePageLayout"`
|
||||
}
|
||||
|
||||
pageLayoutSetPayload struct {
|
||||
Filter types.PageLayoutFilter `json:"filter"`
|
||||
Set []*pageLayoutPayload `json:"set"`
|
||||
}
|
||||
|
||||
PageLayout struct {
|
||||
pageLayout interface {
|
||||
FindByID(ctx context.Context, namespaceID, pageLayoutID uint64) (*types.PageLayout, error)
|
||||
FindByHandle(ctx context.Context, namespaceID uint64, handle string) (*types.PageLayout, error)
|
||||
FindByPageLayoutID(ctx context.Context, namespaceID, pageLayoutID uint64) (*types.PageLayout, error)
|
||||
Find(ctx context.Context, filter types.PageLayoutFilter) (set types.PageLayoutSet, f types.PageLayoutFilter, err error)
|
||||
|
||||
Create(ctx context.Context, pageLayout *types.PageLayout) (*types.PageLayout, error)
|
||||
Reorder(ctx context.Context, namespaceID uint64, pageID uint64, pageLayoutIDs []uint64) error
|
||||
Update(ctx context.Context, pageLayout *types.PageLayout) (*types.PageLayout, error)
|
||||
DeleteByID(ctx context.Context, namespaceID, pageID, pageLayoutID uint64) error
|
||||
UndeleteByID(ctx context.Context, namespaceID, pageID, pageLayoutID uint64) error
|
||||
}
|
||||
locale service.ResourceTranslationsManagerService
|
||||
namespace service.NamespaceService
|
||||
ac pageLayoutAccessController
|
||||
}
|
||||
|
||||
pageLayoutAccessController interface {
|
||||
// @todo
|
||||
}
|
||||
)
|
||||
|
||||
func (PageLayout) New() *PageLayout {
|
||||
return &PageLayout{
|
||||
pageLayout: service.DefaultPageLayout,
|
||||
locale: service.DefaultResourceTranslation,
|
||||
namespace: service.DefaultNamespace,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) List(ctx context.Context, r *request.PageLayoutList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.PageLayoutFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
PageID: r.PageID,
|
||||
Labels: r.Labels,
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.pageLayout.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) ListNamespace(ctx context.Context, r *request.PageLayoutListNamespace) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.PageLayoutFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
Labels: r.Labels,
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
}
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.pageLayout.Find(ctx, f)
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Create(ctx context.Context, r *request.PageLayoutCreate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
layout = &types.PageLayout{
|
||||
PageID: r.PageID,
|
||||
ParentID: r.ParentID,
|
||||
Weight: r.Weight,
|
||||
NamespaceID: r.NamespaceID,
|
||||
Handle: r.Handle,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
OwnedBy: r.OwnedBy,
|
||||
}
|
||||
)
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
if err = r.Config.Unmarshal(&layout.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Blocks) > 2 {
|
||||
if err = r.Blocks.Unmarshal(&layout.Blocks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
layout, err = ctrl.pageLayout.Create(ctx, layout)
|
||||
return ctrl.makePayload(ctx, layout, err)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Read(ctx context.Context, r *request.PageLayoutRead) (interface{}, error) {
|
||||
mod, err := ctrl.pageLayout.FindByID(ctx, r.NamespaceID, r.PageLayoutID)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) ListTranslations(ctx context.Context, r *request.PageLayoutListTranslations) (interface{}, error) {
|
||||
return ctrl.locale.PageLayout(ctx, r.NamespaceID, r.PageID, r.PageLayoutID)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) UpdateTranslations(ctx context.Context, r *request.PageLayoutUpdateTranslations) (interface{}, error) {
|
||||
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Reorder(ctx context.Context, r *request.PageLayoutReorder) (interface{}, error) {
|
||||
return api.OK(), ctrl.pageLayout.Reorder(ctx, r.NamespaceID, r.PageID, payload.ParseUint64s(r.PageIDs))
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Update(ctx context.Context, r *request.PageLayoutUpdate) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
mod = &types.PageLayout{
|
||||
ID: r.PageLayoutID,
|
||||
PageID: r.PageID,
|
||||
Weight: r.Weight,
|
||||
ParentID: r.ParentID,
|
||||
NamespaceID: r.NamespaceID,
|
||||
Handle: r.Handle,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
OwnedBy: r.OwnedBy,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
// Process config if it was included in the request
|
||||
// if not, do not assume that config has been removed!
|
||||
if err = r.Config.Unmarshal(&mod.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Blocks) > 2 {
|
||||
// Process blocks if they were included in the request
|
||||
// if not, do not assume that blocks were removed!
|
||||
if err = r.Blocks.Unmarshal(&mod.Blocks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mod, err = ctrl.pageLayout.Update(ctx, mod)
|
||||
return ctrl.makePayload(ctx, mod, err)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Delete(ctx context.Context, r *request.PageLayoutDelete) (interface{}, error) {
|
||||
return api.OK(), ctrl.pageLayout.DeleteByID(ctx, r.NamespaceID, r.PageID, r.PageLayoutID)
|
||||
}
|
||||
|
||||
func (ctrl *PageLayout) Undelete(ctx context.Context, r *request.PageLayoutUndelete) (interface{}, error) {
|
||||
return api.OK(), ctrl.pageLayout.UndeleteByID(ctx, r.NamespaceID, r.PageID, r.PageLayoutID)
|
||||
}
|
||||
|
||||
func (ctrl PageLayout) makePayload(ctx context.Context, c *types.PageLayout, err error) (*pageLayoutPayload, error) {
|
||||
if err != nil || c == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pageLayoutPayload{
|
||||
PageLayout: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl PageLayout) makeFilterPayload(ctx context.Context, nn types.PageLayoutSet, f types.PageLayoutFilter, err error) (*pageLayoutSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modp := &pageLayoutSetPayload{Filter: f, Set: make([]*pageLayoutPayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
|
||||
}
|
||||
|
||||
return modp, nil
|
||||
}
|
||||
69
server/compose/rest/permissions.go
Normal file
69
server/compose/rest/permissions.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
)
|
||||
|
||||
type (
|
||||
Permissions struct {
|
||||
ac permissionsAccessController
|
||||
}
|
||||
|
||||
permissionsAccessController interface {
|
||||
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
|
||||
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
|
||||
List() []map[string]string
|
||||
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
|
||||
FindRules(ctx context.Context, roleID uint64, rr ...string) (rbac.RuleSet, error)
|
||||
Grant(ctx context.Context, rr ...*rbac.Rule) error
|
||||
}
|
||||
)
|
||||
|
||||
func (Permissions) New() *Permissions {
|
||||
return &Permissions{
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
|
||||
return ctrl.ac.Effective(ctx, types.Component{}), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Trace(ctx context.Context, r *request.PermissionsTrace) (interface{}, error) {
|
||||
return ctrl.ac.Trace(ctx, r.UserID, r.RoleID, r.Resource...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
|
||||
return ctrl.ac.List(), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
|
||||
return ctrl.ac.FindRules(ctx, r.RoleID, r.Resource...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
|
||||
rr, err := ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
r.Access = rbac.Inherit
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.ac.Grant(ctx, rr...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Update(ctx context.Context, r *request.PermissionsUpdate) (interface{}, error) {
|
||||
for _, rule := range r.Rules {
|
||||
// Make sure everything is properly set
|
||||
rule.RoleID = r.RoleID
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.ac.Grant(ctx, r.Rules...)
|
||||
}
|
||||
1092
server/compose/rest/record.go
Normal file
1092
server/compose/rest/record.go
Normal file
File diff suppressed because it is too large
Load Diff
751
server/compose/rest/request/attachment.go
Normal file
751
server/compose/rest/request/attachment.go
Normal file
@@ -0,0 +1,751 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
AttachmentList struct {
|
||||
// Kind PATH parameter
|
||||
//
|
||||
// Attachment kind
|
||||
Kind string
|
||||
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// Sign GET parameter
|
||||
//
|
||||
// Signature
|
||||
Sign string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
// User ID
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// PageID GET parameter
|
||||
//
|
||||
// Filter attachments by page ID
|
||||
PageID uint64 `json:",string"`
|
||||
|
||||
// ModuleID GET parameter
|
||||
//
|
||||
// Filter attachments by module ID
|
||||
ModuleID uint64 `json:",string"`
|
||||
|
||||
// RecordID GET parameter
|
||||
//
|
||||
// Filter attachments by record ID
|
||||
RecordID uint64 `json:",string"`
|
||||
|
||||
// FieldName GET parameter
|
||||
//
|
||||
// Filter attachments by field name
|
||||
FieldName string
|
||||
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
}
|
||||
|
||||
AttachmentRead struct {
|
||||
// Kind PATH parameter
|
||||
//
|
||||
// Attachment kind
|
||||
Kind string
|
||||
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// AttachmentID PATH parameter
|
||||
//
|
||||
// Attachment ID
|
||||
AttachmentID uint64 `json:",string"`
|
||||
|
||||
// Sign GET parameter
|
||||
//
|
||||
// Signature
|
||||
Sign string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
// User ID
|
||||
UserID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
AttachmentDelete struct {
|
||||
// Kind PATH parameter
|
||||
//
|
||||
// Attachment kind
|
||||
Kind string
|
||||
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// AttachmentID PATH parameter
|
||||
//
|
||||
// Attachment ID
|
||||
AttachmentID uint64 `json:",string"`
|
||||
|
||||
// Sign GET parameter
|
||||
//
|
||||
// Signature
|
||||
Sign string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
// User ID
|
||||
UserID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
AttachmentOriginal struct {
|
||||
// Kind PATH parameter
|
||||
//
|
||||
// Attachment kind
|
||||
Kind string
|
||||
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// AttachmentID PATH parameter
|
||||
//
|
||||
// Attachment ID
|
||||
AttachmentID uint64 `json:",string"`
|
||||
|
||||
// Name PATH parameter
|
||||
//
|
||||
// File name
|
||||
Name string
|
||||
|
||||
// Sign GET parameter
|
||||
//
|
||||
// Signature
|
||||
Sign string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
// User ID
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// Download GET parameter
|
||||
//
|
||||
// Force file download
|
||||
Download bool
|
||||
}
|
||||
|
||||
AttachmentPreview struct {
|
||||
// Kind PATH parameter
|
||||
//
|
||||
// Attachment kind
|
||||
Kind string
|
||||
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// AttachmentID PATH parameter
|
||||
//
|
||||
// Attachment ID
|
||||
AttachmentID uint64 `json:",string"`
|
||||
|
||||
// Ext PATH parameter
|
||||
//
|
||||
// Preview extension/format
|
||||
Ext string
|
||||
|
||||
// Sign GET parameter
|
||||
//
|
||||
// Signature
|
||||
Sign string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
// User ID
|
||||
UserID uint64 `json:",string"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewAttachmentList request
|
||||
func NewAttachmentList() *AttachmentList {
|
||||
return &AttachmentList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"kind": r.Kind,
|
||||
"namespaceID": r.NamespaceID,
|
||||
"sign": r.Sign,
|
||||
"userID": r.UserID,
|
||||
"pageID": r.PageID,
|
||||
"moduleID": r.ModuleID,
|
||||
"recordID": r.RecordID,
|
||||
"fieldName": r.FieldName,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetKind() string {
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetSign() string {
|
||||
return r.Sign
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetPageID() uint64 {
|
||||
return r.PageID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetModuleID() uint64 {
|
||||
return r.ModuleID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetRecordID() uint64 {
|
||||
return r.RecordID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetFieldName() string {
|
||||
return r.FieldName
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AttachmentList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sign"]; ok && len(val) > 0 {
|
||||
r.Sign, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageID"]; ok && len(val) > 0 {
|
||||
r.PageID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["moduleID"]; ok && len(val) > 0 {
|
||||
r.ModuleID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["recordID"]; ok && len(val) > 0 {
|
||||
r.RecordID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["fieldName"]; ok && len(val) > 0 {
|
||||
r.FieldName, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "kind")
|
||||
r.Kind, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAttachmentRead request
|
||||
func NewAttachmentRead() *AttachmentRead {
|
||||
return &AttachmentRead{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"kind": r.Kind,
|
||||
"namespaceID": r.NamespaceID,
|
||||
"attachmentID": r.AttachmentID,
|
||||
"sign": r.Sign,
|
||||
"userID": r.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) GetKind() string {
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) GetAttachmentID() uint64 {
|
||||
return r.AttachmentID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) GetSign() string {
|
||||
return r.Sign
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentRead) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AttachmentRead) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sign"]; ok && len(val) > 0 {
|
||||
r.Sign, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "kind")
|
||||
r.Kind, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "attachmentID")
|
||||
r.AttachmentID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAttachmentDelete request
|
||||
func NewAttachmentDelete() *AttachmentDelete {
|
||||
return &AttachmentDelete{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"kind": r.Kind,
|
||||
"namespaceID": r.NamespaceID,
|
||||
"attachmentID": r.AttachmentID,
|
||||
"sign": r.Sign,
|
||||
"userID": r.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) GetKind() string {
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) GetAttachmentID() uint64 {
|
||||
return r.AttachmentID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) GetSign() string {
|
||||
return r.Sign
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentDelete) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AttachmentDelete) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sign"]; ok && len(val) > 0 {
|
||||
r.Sign, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "kind")
|
||||
r.Kind, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "attachmentID")
|
||||
r.AttachmentID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAttachmentOriginal request
|
||||
func NewAttachmentOriginal() *AttachmentOriginal {
|
||||
return &AttachmentOriginal{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"kind": r.Kind,
|
||||
"namespaceID": r.NamespaceID,
|
||||
"attachmentID": r.AttachmentID,
|
||||
"name": r.Name,
|
||||
"sign": r.Sign,
|
||||
"userID": r.UserID,
|
||||
"download": r.Download,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetKind() string {
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetAttachmentID() uint64 {
|
||||
return r.AttachmentID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetSign() string {
|
||||
return r.Sign
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentOriginal) GetDownload() bool {
|
||||
return r.Download
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AttachmentOriginal) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sign"]; ok && len(val) > 0 {
|
||||
r.Sign, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["download"]; ok && len(val) > 0 {
|
||||
r.Download, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "kind")
|
||||
r.Kind, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "attachmentID")
|
||||
r.AttachmentID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "name")
|
||||
r.Name, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAttachmentPreview request
|
||||
func NewAttachmentPreview() *AttachmentPreview {
|
||||
return &AttachmentPreview{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"kind": r.Kind,
|
||||
"namespaceID": r.NamespaceID,
|
||||
"attachmentID": r.AttachmentID,
|
||||
"ext": r.Ext,
|
||||
"sign": r.Sign,
|
||||
"userID": r.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetKind() string {
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetAttachmentID() uint64 {
|
||||
return r.AttachmentID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetExt() string {
|
||||
return r.Ext
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetSign() string {
|
||||
return r.Sign
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentPreview) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AttachmentPreview) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sign"]; ok && len(val) > 0 {
|
||||
r.Sign, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "kind")
|
||||
r.Kind, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "attachmentID")
|
||||
r.AttachmentID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "ext")
|
||||
r.Ext, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
360
server/compose/rest/request/automation.go
Normal file
360
server/compose/rest/request/automation.go
Normal file
@@ -0,0 +1,360 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
AutomationList struct {
|
||||
// ResourceTypePrefixes GET parameter
|
||||
//
|
||||
// Filter by resource prefix
|
||||
ResourceTypePrefixes []string
|
||||
|
||||
// ResourceTypes GET parameter
|
||||
//
|
||||
// Filter by resource type
|
||||
ResourceTypes []string
|
||||
|
||||
// EventTypes GET parameter
|
||||
//
|
||||
// Filter by event type
|
||||
EventTypes []string
|
||||
|
||||
// ExcludeInvalid GET parameter
|
||||
//
|
||||
// Exclude scripts that cannot be used (errors)
|
||||
ExcludeInvalid bool
|
||||
|
||||
// ExcludeClientScripts GET parameter
|
||||
//
|
||||
// Do not include client scripts
|
||||
ExcludeClientScripts bool
|
||||
|
||||
// ExcludeServerScripts GET parameter
|
||||
//
|
||||
// Do not include server scripts
|
||||
ExcludeServerScripts bool
|
||||
}
|
||||
|
||||
AutomationBundle struct {
|
||||
// Bundle PATH parameter
|
||||
//
|
||||
// Name of the bundle
|
||||
Bundle string
|
||||
|
||||
// Type PATH parameter
|
||||
//
|
||||
// Bundle type
|
||||
Type string
|
||||
|
||||
// Ext PATH parameter
|
||||
//
|
||||
// Bundle extension
|
||||
Ext string
|
||||
}
|
||||
|
||||
AutomationTriggerScript struct {
|
||||
// Script POST parameter
|
||||
//
|
||||
// Script to execute
|
||||
Script string
|
||||
|
||||
// Args POST parameter
|
||||
//
|
||||
// Arguments to pass to the script
|
||||
Args map[string]interface{}
|
||||
}
|
||||
)
|
||||
|
||||
// NewAutomationList request
|
||||
func NewAutomationList() *AutomationList {
|
||||
return &AutomationList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resourceTypePrefixes": r.ResourceTypePrefixes,
|
||||
"resourceTypes": r.ResourceTypes,
|
||||
"eventTypes": r.EventTypes,
|
||||
"excludeInvalid": r.ExcludeInvalid,
|
||||
"excludeClientScripts": r.ExcludeClientScripts,
|
||||
"excludeServerScripts": r.ExcludeServerScripts,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetResourceTypePrefixes() []string {
|
||||
return r.ResourceTypePrefixes
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetResourceTypes() []string {
|
||||
return r.ResourceTypes
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetEventTypes() []string {
|
||||
return r.EventTypes
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetExcludeInvalid() bool {
|
||||
return r.ExcludeInvalid
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetExcludeClientScripts() bool {
|
||||
return r.ExcludeClientScripts
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationList) GetExcludeServerScripts() bool {
|
||||
return r.ExcludeServerScripts
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AutomationList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resourceTypePrefixes[]"]; ok {
|
||||
r.ResourceTypePrefixes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resourceTypePrefixes"]; ok {
|
||||
r.ResourceTypePrefixes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["resourceTypes[]"]; ok {
|
||||
r.ResourceTypes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resourceTypes"]; ok {
|
||||
r.ResourceTypes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["eventTypes[]"]; ok {
|
||||
r.EventTypes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["eventTypes"]; ok {
|
||||
r.EventTypes, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["excludeInvalid"]; ok && len(val) > 0 {
|
||||
r.ExcludeInvalid, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["excludeClientScripts"]; ok && len(val) > 0 {
|
||||
r.ExcludeClientScripts, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["excludeServerScripts"]; ok && len(val) > 0 {
|
||||
r.ExcludeServerScripts, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAutomationBundle request
|
||||
func NewAutomationBundle() *AutomationBundle {
|
||||
return &AutomationBundle{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationBundle) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"bundle": r.Bundle,
|
||||
"type": r.Type,
|
||||
"ext": r.Ext,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationBundle) GetBundle() string {
|
||||
return r.Bundle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationBundle) GetType() string {
|
||||
return r.Type
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationBundle) GetExt() string {
|
||||
return r.Ext
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AutomationBundle) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "bundle")
|
||||
r.Bundle, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "type")
|
||||
r.Type, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "ext")
|
||||
r.Ext, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAutomationTriggerScript request
|
||||
func NewAutomationTriggerScript() *AutomationTriggerScript {
|
||||
return &AutomationTriggerScript{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationTriggerScript) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"script": r.Script,
|
||||
"args": r.Args,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationTriggerScript) GetScript() string {
|
||||
return r.Script
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AutomationTriggerScript) GetArgs() map[string]interface{} {
|
||||
return r.Args
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *AutomationTriggerScript) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["script"]; ok && len(val) > 0 {
|
||||
r.Script, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["args[]"]; ok {
|
||||
r.Args, err = parseMapStringInterface(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.MultipartForm.Value["args"]; ok {
|
||||
r.Args, err = parseMapStringInterface(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if val, ok := req.Form["script"]; ok && len(val) > 0 {
|
||||
r.Script, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["args[]"]; ok {
|
||||
r.Args, err = parseMapStringInterface(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["args"]; ok {
|
||||
r.Args, err = parseMapStringInterface(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
897
server/compose/rest/request/chart.go
Normal file
897
server/compose/rest/request/chart.go
Normal file
@@ -0,0 +1,897 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
ChartList struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// Query GET parameter
|
||||
//
|
||||
// Search query to match against charts
|
||||
Query string
|
||||
|
||||
// Handle GET parameter
|
||||
//
|
||||
// Search charts by handle
|
||||
Handle string
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]labelTypes.LabelValue
|
||||
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// IncTotal GET parameter
|
||||
//
|
||||
// Include total counter
|
||||
IncTotal bool
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
// Sort items
|
||||
Sort string
|
||||
}
|
||||
|
||||
ChartCreate struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// Config POST parameter
|
||||
//
|
||||
// Chart JSON
|
||||
Config sqlxTypes.JSONText
|
||||
|
||||
// Name POST parameter
|
||||
//
|
||||
// Chart name
|
||||
Name string
|
||||
|
||||
// Handle POST parameter
|
||||
//
|
||||
// Chart handle
|
||||
Handle string
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]labelTypes.LabelValue
|
||||
}
|
||||
|
||||
ChartRead struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// ChartID PATH parameter
|
||||
//
|
||||
// Chart ID
|
||||
ChartID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
ChartUpdate struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// ChartID PATH parameter
|
||||
//
|
||||
// Chart ID
|
||||
ChartID uint64 `json:",string"`
|
||||
|
||||
// Config POST parameter
|
||||
//
|
||||
// Chart JSON
|
||||
Config sqlxTypes.JSONText
|
||||
|
||||
// Name POST parameter
|
||||
//
|
||||
// Chart name
|
||||
Name string
|
||||
|
||||
// Handle POST parameter
|
||||
//
|
||||
// Chart handle
|
||||
Handle string
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]labelTypes.LabelValue
|
||||
|
||||
// UpdatedAt POST parameter
|
||||
//
|
||||
// Last update (or creation) date
|
||||
UpdatedAt *time.Time
|
||||
}
|
||||
|
||||
ChartDelete struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// ChartID PATH parameter
|
||||
//
|
||||
// Chart ID
|
||||
ChartID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
ChartListTranslations struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// ChartID PATH parameter
|
||||
//
|
||||
// ID
|
||||
ChartID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
ChartUpdateTranslations struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// ChartID PATH parameter
|
||||
//
|
||||
// ID
|
||||
ChartID uint64 `json:",string"`
|
||||
|
||||
// Translations POST parameter
|
||||
//
|
||||
// Resource translation to upsert
|
||||
Translations locale.ResourceTranslationSet
|
||||
}
|
||||
)
|
||||
|
||||
// NewChartList request
|
||||
func NewChartList() *ChartList {
|
||||
return &ChartList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"query": r.Query,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
"limit": r.Limit,
|
||||
"incTotal": r.IncTotal,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetQuery() string {
|
||||
return r.Query
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetLabels() map[string]labelTypes.LabelValue {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetIncTotal() bool {
|
||||
return r.IncTotal
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetSort() string {
|
||||
return r.Sort
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["query"]; ok && len(val) > 0 {
|
||||
r.Query, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["handle"]; ok && len(val) > 0 {
|
||||
r.Handle, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["incTotal"]; ok && len(val) > 0 {
|
||||
r.IncTotal, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["sort"]; ok && len(val) > 0 {
|
||||
r.Sort, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartCreate request
|
||||
func NewChartCreate() *ChartCreate {
|
||||
return &ChartCreate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"config": r.Config,
|
||||
"name": r.Name,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetConfig() sqlxTypes.JSONText {
|
||||
return r.Config
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetLabels() map[string]labelTypes.LabelValue {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartCreate) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["config"]; ok && len(val) > 0 {
|
||||
r.Config, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
|
||||
r.Name, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["handle"]; ok && len(val) > 0 {
|
||||
r.Handle, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.MultipartForm.Value["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if val, ok := req.Form["config"]; ok && len(val) > 0 {
|
||||
r.Config, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["name"]; ok && len(val) > 0 {
|
||||
r.Name, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
|
||||
r.Handle, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartRead request
|
||||
func NewChartRead() *ChartRead {
|
||||
return &ChartRead{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartRead) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"chartID": r.ChartID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartRead) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartRead) GetChartID() uint64 {
|
||||
return r.ChartID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartRead) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "chartID")
|
||||
r.ChartID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartUpdate request
|
||||
func NewChartUpdate() *ChartUpdate {
|
||||
return &ChartUpdate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"chartID": r.ChartID,
|
||||
"config": r.Config,
|
||||
"name": r.Name,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
"updatedAt": r.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetChartID() uint64 {
|
||||
return r.ChartID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetConfig() sqlxTypes.JSONText {
|
||||
return r.Config
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetLabels() map[string]labelTypes.LabelValue {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetUpdatedAt() *time.Time {
|
||||
return r.UpdatedAt
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartUpdate) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["config"]; ok && len(val) > 0 {
|
||||
r.Config, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
|
||||
r.Name, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["handle"]; ok && len(val) > 0 {
|
||||
r.Handle, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.MultipartForm.Value["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["updatedAt"]; ok && len(val) > 0 {
|
||||
r.UpdatedAt, err = payload.ParseISODatePtrWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if val, ok := req.Form["config"]; ok && len(val) > 0 {
|
||||
r.Config, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["name"]; ok && len(val) > 0 {
|
||||
r.Name, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
|
||||
r.Handle, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["updatedAt"]; ok && len(val) > 0 {
|
||||
r.UpdatedAt, err = payload.ParseISODatePtrWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "chartID")
|
||||
r.ChartID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartDelete request
|
||||
func NewChartDelete() *ChartDelete {
|
||||
return &ChartDelete{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartDelete) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"chartID": r.ChartID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartDelete) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartDelete) GetChartID() uint64 {
|
||||
return r.ChartID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartDelete) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "chartID")
|
||||
r.ChartID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartListTranslations request
|
||||
func NewChartListTranslations() *ChartListTranslations {
|
||||
return &ChartListTranslations{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartListTranslations) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"chartID": r.ChartID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartListTranslations) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartListTranslations) GetChartID() uint64 {
|
||||
return r.ChartID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartListTranslations) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "chartID")
|
||||
r.ChartID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewChartUpdateTranslations request
|
||||
func NewChartUpdateTranslations() *ChartUpdateTranslations {
|
||||
return &ChartUpdateTranslations{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdateTranslations) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"chartID": r.ChartID,
|
||||
"translations": r.Translations,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdateTranslations) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdateTranslations) GetChartID() uint64 {
|
||||
return r.ChartID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdateTranslations) GetTranslations() locale.ResourceTranslationSet {
|
||||
return r.Translations
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartUpdateTranslations) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
//if val, ok := req.Form["translations[]"]; ok && len(val) > 0 {
|
||||
// r.Translations, err = locale.ResourceTranslationSet(val), nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "chartID")
|
||||
r.ChartID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
197
server/compose/rest/request/dataPrivacy.go
Normal file
197
server/compose/rest/request/dataPrivacy.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
DataPrivacyRecordList struct {
|
||||
// SensitivityLevelID GET parameter
|
||||
//
|
||||
// Sensitivity Level ID
|
||||
SensitivityLevelID uint64 `json:",string"`
|
||||
|
||||
// ConnectionID GET parameter
|
||||
//
|
||||
// Filter by connection ID
|
||||
ConnectionID []string
|
||||
}
|
||||
|
||||
DataPrivacyModuleList struct {
|
||||
// ConnectionID GET parameter
|
||||
//
|
||||
// Filter by connection ID
|
||||
ConnectionID []string
|
||||
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
// Sort items
|
||||
Sort string
|
||||
}
|
||||
)
|
||||
|
||||
// NewDataPrivacyRecordList request
|
||||
func NewDataPrivacyRecordList() *DataPrivacyRecordList {
|
||||
return &DataPrivacyRecordList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyRecordList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"sensitivityLevelID": r.SensitivityLevelID,
|
||||
"connectionID": r.ConnectionID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyRecordList) GetSensitivityLevelID() uint64 {
|
||||
return r.SensitivityLevelID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyRecordList) GetConnectionID() []string {
|
||||
return r.ConnectionID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *DataPrivacyRecordList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["sensitivityLevelID"]; ok && len(val) > 0 {
|
||||
r.SensitivityLevelID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["connectionID[]"]; ok {
|
||||
r.ConnectionID, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["connectionID"]; ok {
|
||||
r.ConnectionID, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewDataPrivacyModuleList request
|
||||
func NewDataPrivacyModuleList() *DataPrivacyModuleList {
|
||||
return &DataPrivacyModuleList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyModuleList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"connectionID": r.ConnectionID,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyModuleList) GetConnectionID() []string {
|
||||
return r.ConnectionID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyModuleList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyModuleList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r DataPrivacyModuleList) GetSort() string {
|
||||
return r.Sort
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *DataPrivacyModuleList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["connectionID[]"]; ok {
|
||||
r.ConnectionID, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["connectionID"]; ok {
|
||||
r.ConnectionID, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["sort"]; ok && len(val) > 0 {
|
||||
r.Sort, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
235
server/compose/rest/request/icon.go
Normal file
235
server/compose/rest/request/icon.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
IconList struct {
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// IncTotal GET parameter
|
||||
//
|
||||
// Include total counter
|
||||
IncTotal bool
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
// Sort items
|
||||
Sort string
|
||||
}
|
||||
|
||||
IconUpload struct {
|
||||
// Icon POST parameter
|
||||
//
|
||||
// Icon to upload
|
||||
Icon *multipart.FileHeader
|
||||
}
|
||||
|
||||
IconDelete struct {
|
||||
// IconID PATH parameter
|
||||
//
|
||||
// Icon ID
|
||||
IconID uint64 `json:",string"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewIconList request
|
||||
func NewIconList() *IconList {
|
||||
return &IconList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"limit": r.Limit,
|
||||
"incTotal": r.IncTotal,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetIncTotal() bool {
|
||||
return r.IncTotal
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetSort() string {
|
||||
return r.Sort
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["incTotal"]; ok && len(val) > 0 {
|
||||
r.IncTotal, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["sort"]; ok && len(val) > 0 {
|
||||
r.Sort, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIconUpload request
|
||||
func NewIconUpload() *IconUpload {
|
||||
return &IconUpload{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconUpload) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"icon": r.Icon,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconUpload) GetIcon() *multipart.FileHeader {
|
||||
return r.Icon
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconUpload) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
// Ignoring icon as its handled in the POST params section
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if _, r.Icon, err = req.FormFile("icon"); err != nil {
|
||||
return fmt.Errorf("error processing uploaded file: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIconDelete request
|
||||
func NewIconDelete() *IconDelete {
|
||||
return &IconDelete{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconDelete) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iconID": r.IconID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconDelete) GetIconID() uint64 {
|
||||
return r.IconID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconDelete) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "iconID")
|
||||
r.IconID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
1168
server/compose/rest/request/module.go
Normal file
1168
server/compose/rest/request/module.go
Normal file
File diff suppressed because it is too large
Load Diff
1392
server/compose/rest/request/namespace.go
Normal file
1392
server/compose/rest/request/namespace.go
Normal file
File diff suppressed because it is too large
Load Diff
213
server/compose/rest/request/notification.go
Normal file
213
server/compose/rest/request/notification.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
NotificationEmailSend struct {
|
||||
// To POST parameter
|
||||
//
|
||||
// Email addresses
|
||||
To []string
|
||||
|
||||
// Cc POST parameter
|
||||
//
|
||||
// Email addresses
|
||||
Cc []string
|
||||
|
||||
// ReplyTo POST parameter
|
||||
//
|
||||
// Email address in reply-to field
|
||||
ReplyTo string
|
||||
|
||||
// Subject POST parameter
|
||||
//
|
||||
// Email subject
|
||||
Subject string
|
||||
|
||||
// Content POST parameter
|
||||
//
|
||||
// Message content
|
||||
Content sqlxTypes.JSONText
|
||||
|
||||
// RemoteAttachments POST parameter
|
||||
//
|
||||
// Remote files to attach to the email
|
||||
RemoteAttachments []string
|
||||
}
|
||||
)
|
||||
|
||||
// NewNotificationEmailSend request
|
||||
func NewNotificationEmailSend() *NotificationEmailSend {
|
||||
return &NotificationEmailSend{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"to": r.To,
|
||||
"cc": r.Cc,
|
||||
"replyTo": r.ReplyTo,
|
||||
"subject": r.Subject,
|
||||
"content": r.Content,
|
||||
"remoteAttachments": r.RemoteAttachments,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetTo() []string {
|
||||
return r.To
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetCc() []string {
|
||||
return r.Cc
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetReplyTo() string {
|
||||
return r.ReplyTo
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetSubject() string {
|
||||
return r.Subject
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetContent() sqlxTypes.JSONText {
|
||||
return r.Content
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NotificationEmailSend) GetRemoteAttachments() []string {
|
||||
return r.RemoteAttachments
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *NotificationEmailSend) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["replyTo"]; ok && len(val) > 0 {
|
||||
r.ReplyTo, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["subject"]; ok && len(val) > 0 {
|
||||
r.Subject, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["content"]; ok && len(val) > 0 {
|
||||
r.Content, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
//if val, ok := req.Form["to[]"]; ok && len(val) > 0 {
|
||||
// r.To, err = val, nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
|
||||
//if val, ok := req.Form["cc[]"]; ok && len(val) > 0 {
|
||||
// r.Cc, err = val, nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
|
||||
if val, ok := req.Form["replyTo"]; ok && len(val) > 0 {
|
||||
r.ReplyTo, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["subject"]; ok && len(val) > 0 {
|
||||
r.Subject, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["content"]; ok && len(val) > 0 {
|
||||
r.Content, err = payload.ParseJSONTextWithErr(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//if val, ok := req.Form["remoteAttachments[]"]; ok && len(val) > 0 {
|
||||
// r.RemoteAttachments, err = val, nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
1880
server/compose/rest/request/page.go
Normal file
1880
server/compose/rest/request/page.go
Normal file
File diff suppressed because it is too large
Load Diff
1715
server/compose/rest/request/pageLayout.go
Normal file
1715
server/compose/rest/request/pageLayout.go
Normal file
File diff suppressed because it is too large
Load Diff
384
server/compose/rest/request/permissions.go
Normal file
384
server/compose/rest/request/permissions.go
Normal file
@@ -0,0 +1,384 @@
|
||||
package request
|
||||
|
||||
// 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:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
PermissionsList struct {
|
||||
}
|
||||
|
||||
PermissionsEffective struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource string
|
||||
}
|
||||
|
||||
PermissionsTrace struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
//
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// RoleID GET parameter
|
||||
//
|
||||
//
|
||||
RoleID []uint64
|
||||
}
|
||||
|
||||
PermissionsRead struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
// Role ID
|
||||
RoleID uint64 `json:",string"`
|
||||
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
}
|
||||
|
||||
PermissionsDelete struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
// Role ID
|
||||
RoleID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
PermissionsUpdate struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
// Role ID
|
||||
RoleID uint64 `json:",string"`
|
||||
|
||||
// Rules POST parameter
|
||||
//
|
||||
// List of permission rules to set
|
||||
Rules rbac.RuleSet
|
||||
}
|
||||
)
|
||||
|
||||
// NewPermissionsList request
|
||||
func NewPermissionsList() *PermissionsList {
|
||||
return &PermissionsList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsList) Fill(req *http.Request) (err error) {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsEffective request
|
||||
func NewPermissionsEffective() *PermissionsEffective {
|
||||
return &PermissionsEffective{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEffective) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEffective) GetResource() string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource"]; ok && len(val) > 0 {
|
||||
r.Resource, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsTrace request
|
||||
func NewPermissionsTrace() *PermissionsTrace {
|
||||
return &PermissionsTrace{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsTrace) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
"userID": r.UserID,
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsTrace) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsTrace) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsTrace) GetRoleID() []uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsTrace) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["roleID[]"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["roleID"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsRead request
|
||||
func NewPermissionsRead() *PermissionsRead {
|
||||
return &PermissionsRead{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsRead) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"roleID": r.RoleID,
|
||||
"resource": r.Resource,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsRead) GetRoleID() uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsRead) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "roleID")
|
||||
r.RoleID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsDelete request
|
||||
func NewPermissionsDelete() *PermissionsDelete {
|
||||
return &PermissionsDelete{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsDelete) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsDelete) GetRoleID() uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsDelete) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "roleID")
|
||||
r.RoleID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsUpdate request
|
||||
func NewPermissionsUpdate() *PermissionsUpdate {
|
||||
return &PermissionsUpdate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsUpdate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"roleID": r.RoleID,
|
||||
"rules": r.Rules,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsUpdate) GetRoleID() uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsUpdate) GetRules() rbac.RuleSet {
|
||||
return r.Rules
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsUpdate) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
//if val, ok := req.Form["rules[]"]; ok && len(val) > 0 {
|
||||
// r.Rules, err = rbac.RuleSet(val), nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "roleID")
|
||||
r.RoleID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
3061
server/compose/rest/request/record.go
Normal file
3061
server/compose/rest/request/record.go
Normal file
File diff suppressed because it is too large
Load Diff
44
server/compose/rest/request/util.go
Normal file
44
server/compose/rest/request/util.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package request
|
||||
|
||||
//lint:file-ignore U1000 Ignore unused code, part of request pkg toolset
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
ProcedureArgs []ProcedureArg
|
||||
|
||||
ProcedureArg struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
)
|
||||
|
||||
func (args ProcedureArgs) GetUint64(name string) uint64 {
|
||||
u, _ := strconv.ParseUint(args.Get(name), 10, 64)
|
||||
return u
|
||||
}
|
||||
|
||||
func (args ProcedureArgs) Get(name string) string {
|
||||
name = strings.ToLower(name)
|
||||
for _, arg := range args {
|
||||
if strings.ToLower(arg.Name) == name {
|
||||
return arg.Value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseMapStringInterface(ss []string) (map[string]interface{}, error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out := make(map[string]interface{})
|
||||
|
||||
return out, json.Unmarshal([]byte(ss[0]), out)
|
||||
}
|
||||
49
server/compose/rest/router.go
Normal file
49
server/compose/rest/router.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/handlers"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
)
|
||||
|
||||
func MountRoutes() func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
var (
|
||||
namespace = Namespace{}.New()
|
||||
module = Module{}.New()
|
||||
record = Record{}.New()
|
||||
page = Page{}.New()
|
||||
pageIcon = Icon{}.New()
|
||||
pageLayout = PageLayout{}.New()
|
||||
chart = Chart{}.New()
|
||||
notification = Notification{}.New()
|
||||
attachment = Attachment{}.New()
|
||||
automation = Automation{}.New()
|
||||
dataPrivacy = DataPrivacy{}.New()
|
||||
)
|
||||
|
||||
// Initialize handlers & controllers.
|
||||
r.Group(func(r chi.Router) {
|
||||
// Use alternative handlers that support file serving
|
||||
handlers.NewAttachment(attachment).MountRoutes(r)
|
||||
})
|
||||
|
||||
// Protect all _private_ routes
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.HttpTokenValidator("api"))
|
||||
|
||||
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
|
||||
handlers.NewNamespace(namespace).MountRoutes(r)
|
||||
handlers.NewPage(page).MountRoutes(r)
|
||||
handlers.NewIcon(pageIcon).MountRoutes(r)
|
||||
handlers.NewPageLayout(pageLayout).MountRoutes(r)
|
||||
handlers.NewAutomation(automation).MountRoutes(r)
|
||||
handlers.NewModule(module).MountRoutes(r)
|
||||
handlers.NewRecord(record).MountRoutes(r)
|
||||
handlers.NewChart(chart).MountRoutes(r)
|
||||
handlers.NewNotification(notification).MountRoutes(r)
|
||||
handlers.NewDataPrivacy(dataPrivacy).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
1299
server/compose/service/access_control.gen.go
generated
Normal file
1299
server/compose/service/access_control.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
107
server/compose/service/access_control.gen_test.go
generated
Normal file
107
server/compose/service/access_control.gen_test.go
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAccessControl_ResourceLoader(t *testing.T) {
|
||||
svc := accessControl{}
|
||||
|
||||
// Has wildcard resources
|
||||
testCases := []struct {
|
||||
resource string
|
||||
expected rbac.Resource
|
||||
err error
|
||||
}{
|
||||
{
|
||||
resource: "corteza::compose:chart/1/*",
|
||||
expected: rbac.NewResource(types.ChartRbacResource(1, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:chart/*/*",
|
||||
expected: rbac.NewResource(types.ChartRbacResource(0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:module/3/*",
|
||||
expected: rbac.NewResource(types.ModuleRbacResource(3, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:module/*/*",
|
||||
expected: rbac.NewResource(types.ModuleRbacResource(0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:module-field/5/*/*",
|
||||
expected: rbac.NewResource(types.ModuleFieldRbacResource(5, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:module-field/*/*/*",
|
||||
expected: rbac.NewResource(types.ModuleFieldRbacResource(0, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:namespace/*",
|
||||
expected: rbac.NewResource(types.NamespaceRbacResource(0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:page/9/*",
|
||||
expected: rbac.NewResource(types.PageRbacResource(9, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:page/*/*",
|
||||
expected: rbac.NewResource(types.PageRbacResource(0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:page-layout/11/*/*",
|
||||
expected: rbac.NewResource(types.PageLayoutRbacResource(11, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:page-layout/*/*/*",
|
||||
expected: rbac.NewResource(types.PageLayoutRbacResource(0, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:record/14/*/*",
|
||||
expected: rbac.NewResource(types.RecordRbacResource(14, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose:record/*/*/*",
|
||||
expected: rbac.NewResource(types.RecordRbacResource(0, 0, 0)),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "corteza::compose",
|
||||
expected: &types.Component{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
resource: "unknown_resource_type:17",
|
||||
expected: nil,
|
||||
err: fmt.Errorf("unknown resource type %q", "unknown_resource_type:17"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.resource, func(t *testing.T) {
|
||||
res, err := svc.resourceLoader(context.Background(), tc.resource)
|
||||
|
||||
require.Equal(t, tc.expected, res)
|
||||
require.Equal(t, tc.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
284
server/compose/service/access_control_actions.gen.go
generated
Normal file
284
server/compose/service/access_control_actions.gen.go
generated
Normal file
@@ -0,0 +1,284 @@
|
||||
package service
|
||||
|
||||
// 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/service/access_control_actions.yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
accessControlActionProps struct {
|
||||
rule *rbac.Rule
|
||||
}
|
||||
|
||||
accessControlAction struct {
|
||||
timestamp time.Time
|
||||
resource string
|
||||
action string
|
||||
log string
|
||||
severity actionlog.Severity
|
||||
|
||||
// prefix for error when action fails
|
||||
errorMessage string
|
||||
|
||||
props *accessControlActionProps
|
||||
}
|
||||
|
||||
accessControlLogMetaKey struct{}
|
||||
accessControlPropsMetaKey struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
// just a placeholder to cover template cases w/o fmt package use
|
||||
_ = fmt.Println
|
||||
)
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Props methods
|
||||
// setRule updates accessControlActionProps's rule
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p *accessControlActionProps) setRule(rule *rbac.Rule) *accessControlActionProps {
|
||||
p.rule = rule
|
||||
return p
|
||||
}
|
||||
|
||||
// Serialize converts accessControlActionProps to actionlog.Meta
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p accessControlActionProps) Serialize() actionlog.Meta {
|
||||
var (
|
||||
m = make(actionlog.Meta)
|
||||
)
|
||||
|
||||
if p.rule != nil {
|
||||
m.Set("rule.operation", p.rule.Operation, true)
|
||||
m.Set("rule.roleID", p.rule.RoleID, true)
|
||||
m.Set("rule.access", p.rule.Access, true)
|
||||
m.Set("rule.resource", p.rule.Resource, true)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// tr translates string and replaces meta value placeholder with values
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p accessControlActionProps) Format(in string, err error) string {
|
||||
var (
|
||||
pairs = []string{"{{err}}"}
|
||||
// first non-empty string
|
||||
fns = func(ii ...interface{}) string {
|
||||
for _, i := range ii {
|
||||
if s := fmt.Sprintf("%v", i); len(s) > 0 {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
pairs = append(pairs, err.Error())
|
||||
} else {
|
||||
pairs = append(pairs, "nil")
|
||||
}
|
||||
|
||||
if p.rule != nil {
|
||||
// replacement for "{{rule}}" (in order how fields are defined)
|
||||
pairs = append(
|
||||
pairs,
|
||||
"{{rule}}",
|
||||
fns(
|
||||
p.rule.Operation,
|
||||
p.rule.RoleID,
|
||||
p.rule.Access,
|
||||
p.rule.Resource,
|
||||
),
|
||||
)
|
||||
pairs = append(pairs, "{{rule.operation}}", fns(p.rule.Operation))
|
||||
pairs = append(pairs, "{{rule.roleID}}", fns(p.rule.RoleID))
|
||||
pairs = append(pairs, "{{rule.access}}", fns(p.rule.Access))
|
||||
pairs = append(pairs, "{{rule.resource}}", fns(p.rule.Resource))
|
||||
}
|
||||
return strings.NewReplacer(pairs...).Replace(in)
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action methods
|
||||
|
||||
// String returns loggable description as string
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (a *accessControlAction) String() string {
|
||||
var props = &accessControlActionProps{}
|
||||
|
||||
if a.props != nil {
|
||||
props = a.props
|
||||
}
|
||||
|
||||
return props.Format(a.log, nil)
|
||||
}
|
||||
|
||||
func (e *accessControlAction) ToAction() *actionlog.Action {
|
||||
return &actionlog.Action{
|
||||
Resource: e.resource,
|
||||
Action: e.action,
|
||||
Severity: e.severity,
|
||||
Description: e.String(),
|
||||
Meta: e.props.Serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action constructors
|
||||
|
||||
// AccessControlActionGrant returns "compose:access_control.grant" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func AccessControlActionGrant(props ...*accessControlActionProps) *accessControlAction {
|
||||
a := &accessControlAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:access_control",
|
||||
action: "grant",
|
||||
log: "grant",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Error constructors
|
||||
|
||||
// AccessControlErrGeneric returns "compose:access_control.generic" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func AccessControlErrGeneric(mm ...*accessControlActionProps) *errors.Error {
|
||||
var p = &accessControlActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("failed to complete request due to internal error", nil),
|
||||
|
||||
errors.Meta("type", "generic"),
|
||||
errors.Meta("resource", "compose:access_control"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(accessControlLogMetaKey{}, "{err}"),
|
||||
errors.Meta(accessControlPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "access-control.errors.generic"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// AccessControlErrNotAllowedToSetPermissions returns "compose:access_control.notAllowedToSetPermissions" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func AccessControlErrNotAllowedToSetPermissions(mm ...*accessControlActionProps) *errors.Error {
|
||||
var p = &accessControlActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to set permissions", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToSetPermissions"),
|
||||
errors.Meta("resource", "compose:access_control"),
|
||||
|
||||
errors.Meta(accessControlPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "access-control.errors.notAllowedToSetPermissions"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
|
||||
// recordAction is a service helper function wraps function that can return error
|
||||
//
|
||||
// It will wrap unrecognized/internal errors with generic errors.
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (svc accessControl) recordAction(ctx context.Context, props *accessControlActionProps, actionFn func(...*accessControlActionProps) *accessControlAction, err error) error {
|
||||
if svc.actionlog == nil || actionFn == nil {
|
||||
// action log disabled or no action fn passed, return error as-is
|
||||
return err
|
||||
} else if err == nil {
|
||||
// action completed w/o error, record it
|
||||
svc.actionlog.Record(ctx, actionFn(props).ToAction())
|
||||
return nil
|
||||
}
|
||||
|
||||
a := actionFn(props).ToAction()
|
||||
|
||||
// Extracting error information and recording it as action
|
||||
a.Error = err.Error()
|
||||
|
||||
switch c := err.(type) {
|
||||
case *errors.Error:
|
||||
m := c.Meta()
|
||||
|
||||
a.Error = err.Error()
|
||||
a.Severity = actionlog.Severity(m.AsInt("severity"))
|
||||
a.Description = props.Format(m.AsString(accessControlLogMetaKey{}), err)
|
||||
|
||||
if p, has := m[accessControlPropsMetaKey{}]; has {
|
||||
a.Meta = p.(*accessControlActionProps).Serialize()
|
||||
}
|
||||
|
||||
svc.actionlog.Record(ctx, a)
|
||||
default:
|
||||
svc.actionlog.Record(ctx, a)
|
||||
}
|
||||
|
||||
// Original error is passed on
|
||||
return err
|
||||
}
|
||||
25
server/compose/service/access_control_actions.yaml
Normal file
25
server/compose/service/access_control_actions.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# List of security/audit events and errors that we need to log
|
||||
|
||||
resource: compose:access_control
|
||||
service: accessControl
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: notice
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza/server/pkg/rbac
|
||||
|
||||
props:
|
||||
- name: rule
|
||||
type: "*rbac.Rule"
|
||||
fields: [ operation, roleID, access, resource ]
|
||||
|
||||
actions:
|
||||
- action: grant
|
||||
|
||||
errors:
|
||||
- error: notAllowedToSetPermissions
|
||||
message: "not allowed to set permissions"
|
||||
717
server/compose/service/attachment.go
Normal file
717
server/compose/service/attachment.go
Normal file
@@ -0,0 +1,717 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"image"
|
||||
"image/gif"
|
||||
"io"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/objstore"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemService "github.com/cortezaproject/corteza/server/system/service"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/edwvee/exiffix"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/mat/besticon/ico"
|
||||
)
|
||||
|
||||
const (
|
||||
attachmentPreviewMaxWidth = 320
|
||||
attachmentPreviewMaxHeight = 180
|
||||
|
||||
// using base 10, it will be less confusing for the non-techie users
|
||||
megabyte = 1_000_000
|
||||
)
|
||||
|
||||
var (
|
||||
reMimeType = regexp.MustCompile(`\w+/[-.\w]+(?:\+[-.\w]+)?`)
|
||||
)
|
||||
|
||||
type (
|
||||
attachment struct {
|
||||
actionlog actionlog.Recorder
|
||||
objects objstore.Store
|
||||
ac attachmentAccessController
|
||||
store store.Storer
|
||||
dal dalDater
|
||||
}
|
||||
|
||||
attachmentAccessController interface {
|
||||
CanReadNamespace(context.Context, *types.Namespace) bool
|
||||
CanCreateNamespace(context.Context) bool
|
||||
CanReadModule(context.Context, *types.Module) bool
|
||||
CanReadPage(context.Context, *types.Page) bool
|
||||
CanUpdatePage(context.Context, *types.Page) bool
|
||||
CanReadRecord(context.Context, *types.Record) bool
|
||||
CanUpdateRecord(context.Context, *types.Record) bool
|
||||
CanCreateRecordOnModule(context.Context, *types.Module) bool
|
||||
}
|
||||
|
||||
AttachmentService interface {
|
||||
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)
|
||||
CreateIconAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (*types.Attachment, error)
|
||||
CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error)
|
||||
CreateNamespaceAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (*types.Attachment, error)
|
||||
OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error)
|
||||
OpenPreview(att *types.Attachment) (io.ReadSeekCloser, error)
|
||||
DeleteByID(ctx context.Context, namespaceID, attachmentID uint64) error
|
||||
}
|
||||
)
|
||||
|
||||
func Attachment(store objstore.Store, dal dalDater) *attachment {
|
||||
return &attachment{
|
||||
objects: store,
|
||||
ac: DefaultAccessControl,
|
||||
store: DefaultStore,
|
||||
dal: dal,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc attachment) Find(ctx context.Context, filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if filter.NamespaceID == 0 && filter.Kind != types.IconAttachment {
|
||||
return AttachmentErrInvalidNamespaceID()
|
||||
}
|
||||
|
||||
if filter.PageID > 0 {
|
||||
aProps.namespace, aProps.page, err = loadPageCombo(ctx, svc.store, filter.NamespaceID, filter.PageID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if svc.ac.CanReadPage(ctx, aProps.page) {
|
||||
return AttachmentErrNotAllowedToReadPage()
|
||||
}
|
||||
}
|
||||
|
||||
if filter.RecordID > 0 {
|
||||
aProps.namespace, aProps.module, aProps.record, err = loadRecordCombo(ctx, svc.store, svc.dal, filter.NamespaceID, filter.ModuleID, filter.RecordID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if svc.ac.CanReadRecord(ctx, aProps.record) {
|
||||
return AttachmentErrNotAllowedToReadRecord()
|
||||
}
|
||||
} else if filter.ModuleID > 0 {
|
||||
aProps.namespace, aProps.module, err = loadModuleCombo(ctx, svc.store, filter.NamespaceID, filter.ModuleID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if svc.ac.CanReadRecord(ctx, aProps.record) {
|
||||
return AttachmentErrNotAllowedToReadRecord()
|
||||
}
|
||||
}
|
||||
|
||||
set, f, err = store.SearchComposeAttachments(ctx, svc.store, filter)
|
||||
return err
|
||||
}()
|
||||
|
||||
return set, f, svc.recordAction(ctx, aProps, AttachmentActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc attachment) FindByID(ctx context.Context, namespaceID, attachmentID uint64) (att *types.Attachment, err error) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if attachmentID == 0 {
|
||||
return AttachmentErrInvalidID()
|
||||
}
|
||||
|
||||
if att, err = store.LookupComposeAttachmentByID(ctx, svc.store, attachmentID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setAttachment(att)
|
||||
return nil
|
||||
}()
|
||||
|
||||
return att, svc.recordAction(ctx, aProps, AttachmentActionLookup, err)
|
||||
}
|
||||
|
||||
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(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if attachmentID == 0 {
|
||||
return AttachmentErrInvalidID()
|
||||
}
|
||||
|
||||
if att, err = store.LookupComposeAttachmentByID(ctx, s, attachmentID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setAttachment(att)
|
||||
|
||||
att.DeletedAt = now()
|
||||
return store.UpdateComposeAttachment(ctx, s, att)
|
||||
})
|
||||
|
||||
return svc.recordAction(ctx, aProps, AttachmentActionDelete, err)
|
||||
}
|
||||
|
||||
// func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
|
||||
// if namespaceID == 0 {
|
||||
// return nil, AttachmentErrInvalidNamespaceID()
|
||||
// }
|
||||
//
|
||||
// ns, err = svc.namespaceRepo.FindByID(namespaceID)
|
||||
// if repository.ErrNamespaceNotFound.Eq(err) {
|
||||
// return nil, AttachmentErrNamespaceNotFound()
|
||||
// }
|
||||
//
|
||||
// if !svc.ac.CanReadNamespace(ctx, ns) {
|
||||
// return nil, AttachmentErrNotAllowedToReadNamespace()
|
||||
// }
|
||||
//
|
||||
// return ns, nil
|
||||
// }
|
||||
//
|
||||
// func (svc attachment) findPageByID(namespaceID, pageID uint64) (p *types.Page, err error) {
|
||||
// if pageID == 0 {
|
||||
// return nil, AttachmentErrInvalidPageID()
|
||||
// }
|
||||
//
|
||||
// p, err = svc.pageRepo.FindByID(namespaceID, pageID)
|
||||
// if repository.ErrPageNotFound.Eq(err) {
|
||||
// return nil, AttachmentErrPageNotFound()
|
||||
// }
|
||||
//
|
||||
// if !svc.ac.CanReadPage(ctx, p) {
|
||||
// return nil, AttachmentErrNotAllowedToReadPage()
|
||||
// }
|
||||
//
|
||||
// return p, nil
|
||||
// }
|
||||
//
|
||||
// func (svc attachment) findModuleByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
|
||||
// if moduleID == 0 {
|
||||
// return nil, AttachmentErrInvalidModuleID()
|
||||
// }
|
||||
//
|
||||
// m, err = svc.moduleRepo.FindByID(namespaceID, moduleID)
|
||||
// if repository.ErrModuleNotFound.Eq(err) {
|
||||
// return nil, AttachmentErrModuleNotFound()
|
||||
// }
|
||||
//
|
||||
// if !svc.ac.CanReadModule(ctx, m) {
|
||||
// return nil, AttachmentErrNotAllowedToReadModule()
|
||||
// }
|
||||
//
|
||||
// return m, nil
|
||||
// }
|
||||
//
|
||||
// func (svc attachment) findRecordByID(m *types.Module, recordID uint64) (r *types.Record, err error) {
|
||||
// if recordID == 0 {
|
||||
// return nil, AttachmentErrInvalidRecordID()
|
||||
// }
|
||||
//
|
||||
// r, err = svc.recordRepo.FindByID(m.NamespaceID, recordID)
|
||||
// if repository.ErrRecordNotFound.Eq(err) {
|
||||
// return nil, AttachmentErrRecordNotFound()
|
||||
// }
|
||||
//
|
||||
// if !svc.ac.CanReadRecord(ctx, m) {
|
||||
// return nil, AttachmentErrNotAllowedToReadRecord()
|
||||
// }
|
||||
//
|
||||
// return r, nil
|
||||
// }
|
||||
|
||||
func (svc attachment) OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error) {
|
||||
if len(att.Url) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return svc.objects.Open(att.Url)
|
||||
}
|
||||
|
||||
func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeekCloser, error) {
|
||||
if len(att.PreviewUrl) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return svc.objects.Open(att.PreviewUrl)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
aProps = &attachmentActionProps{
|
||||
namespace: &types.Namespace{ID: namespaceID},
|
||||
page: &types.Page{ID: pageID},
|
||||
}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
ns, p, err = loadPageCombo(ctx, s, namespaceID, pageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
aProps.setPage(p)
|
||||
|
||||
if !svc.ac.CanUpdatePage(ctx, p) {
|
||||
return AttachmentErrNotAllowedToUpdatePage()
|
||||
}
|
||||
|
||||
{
|
||||
// Verify size and type of the uploaded page attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Page.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Page.Attachments.Mimetypes
|
||||
)
|
||||
|
||||
if err = svc.verifySizeAndMimetype(fh, size, maxSize, allowedTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
att = &types.Attachment{
|
||||
NamespaceID: namespaceID,
|
||||
Name: strings.TrimSpace(name),
|
||||
Kind: types.PageAttachment,
|
||||
}
|
||||
|
||||
return svc.create(ctx, s, name, size, fh, att)
|
||||
})
|
||||
|
||||
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc attachment) CreateIconAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (att *types.Attachment, err error) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
{
|
||||
// Verify size and type of the uploaded page attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Icon.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Icon.Attachments.Mimetypes
|
||||
)
|
||||
|
||||
if err = svc.verifySizeAndMimetype(fh, size, maxSize, allowedTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
att = &types.Attachment{
|
||||
Name: strings.TrimSpace(name),
|
||||
Kind: types.IconAttachment,
|
||||
}
|
||||
|
||||
return svc.create(ctx, s, name, size, fh, att)
|
||||
})
|
||||
|
||||
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc attachment) verifySizeAndMimetype(fh io.ReadSeeker, size, maxSize int64, allowedTypes []string) (err error) {
|
||||
// Verify size and type of the uploaded page attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
mimeType *mimetype.MIME
|
||||
)
|
||||
|
||||
if maxSize > 0 && maxSize < size {
|
||||
return AttachmentErrTooLarge().Apply(
|
||||
errors.Meta("size", size),
|
||||
errors.Meta("maxSize", maxSize),
|
||||
)
|
||||
}
|
||||
|
||||
if mimeType, err = svc.extractMimetype(fh); err != nil {
|
||||
return err
|
||||
} else if !svc.checkMimeType(mimeType, allowedTypes...) {
|
||||
return AttachmentErrNotAllowedToUploadThisType()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc attachment) CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (att *types.Attachment, err error) {
|
||||
var (
|
||||
ns *types.Namespace
|
||||
m *types.Module
|
||||
r *types.Record
|
||||
|
||||
aProps = &attachmentActionProps{
|
||||
namespace: &types.Namespace{ID: namespaceID},
|
||||
module: &types.Module{ID: moduleID},
|
||||
record: &types.Record{ID: recordID},
|
||||
}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
ns, m, err = loadModuleCombo(ctx, s, namespaceID, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
aProps.setModule(m)
|
||||
|
||||
if recordID > 0 {
|
||||
// Uploading to existing record
|
||||
//
|
||||
// To allow upload (attachment creation) user must have permissions to
|
||||
// alter that record
|
||||
|
||||
r, err = dalutils.ComposeRecordsFind(ctx, svc.dal, m, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setRecord(r)
|
||||
|
||||
if !svc.ac.CanUpdateRecord(ctx, r) {
|
||||
return AttachmentErrNotAllowedToUpdateRecord()
|
||||
}
|
||||
} else {
|
||||
// Uploading to non-existing record
|
||||
//
|
||||
// To allow upload (attachment creation) user must have permissions to
|
||||
// create records
|
||||
if !svc.ac.CanCreateRecordOnModule(ctx, m) {
|
||||
return AttachmentErrNotAllowedToCreateRecords()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Verify size and type of the uploaded record attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Record.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Record.Attachments.Mimetypes
|
||||
mimeType *mimetype.MIME
|
||||
)
|
||||
|
||||
f := m.Fields.FindByName(fieldName)
|
||||
if f == nil || f.Kind != "File" {
|
||||
return AttachmentErrInvalidModuleField().Apply(
|
||||
errors.Meta("fieldName", fieldName),
|
||||
)
|
||||
}
|
||||
|
||||
if aux := f.Options.Int64("maxSize"); aux > 0 {
|
||||
maxSize = aux * megabyte
|
||||
}
|
||||
if aux := f.Options.String("mimetypes"); len(aux) > 0 {
|
||||
allowedTypes = strings.Split(aux, ",")
|
||||
}
|
||||
|
||||
if maxSize > 0 && maxSize < size {
|
||||
return AttachmentErrTooLarge().Apply(
|
||||
errors.Meta("size", size),
|
||||
errors.Meta("maxSize", maxSize),
|
||||
)
|
||||
}
|
||||
|
||||
if mimeType, err = svc.extractMimetype(fh); err != nil {
|
||||
return err
|
||||
} else if !svc.checkMimeType(mimeType, allowedTypes...) {
|
||||
return AttachmentErrNotAllowedToUploadThisType().Apply(errors.Meta("mimetype", mimeType))
|
||||
}
|
||||
}
|
||||
|
||||
att = &types.Attachment{
|
||||
NamespaceID: namespaceID,
|
||||
Name: strings.TrimSpace(name),
|
||||
Kind: types.RecordAttachment,
|
||||
}
|
||||
|
||||
return svc.create(ctx, s, name, size, fh, att)
|
||||
})
|
||||
|
||||
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) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
if !svc.ac.CanCreateNamespace(ctx) {
|
||||
return AttachmentErrNotAllowedToUpdateNamespace()
|
||||
}
|
||||
|
||||
{
|
||||
// Verify file size and mimetype
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Namespace.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Namespace.Attachments.Mimetypes
|
||||
)
|
||||
|
||||
if err = svc.verifySizeAndMimetype(fh, size, maxSize, allowedTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
att = &types.Attachment{
|
||||
Name: strings.TrimSpace(name),
|
||||
Kind: types.NamespaceAttachment,
|
||||
}
|
||||
|
||||
// @todo limit upload on image/* only!
|
||||
|
||||
return svc.create(ctx, s, name, size, fh, att)
|
||||
})
|
||||
|
||||
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc attachment) create(ctx context.Context, s store.ComposeAttachments, name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{}
|
||||
)
|
||||
|
||||
// preset attachment ID because we need ref for storage
|
||||
att.ID = nextID()
|
||||
att.CreatedAt = *now()
|
||||
|
||||
if att.OwnerID == 0 {
|
||||
att.OwnerID = auth.GetIdentityFromContext(ctx).Identity()
|
||||
}
|
||||
|
||||
if svc.objects == nil {
|
||||
return errors.Internal("cannot create attachment: store handler not set")
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
aProps.setName(name)
|
||||
aProps.setSize(size)
|
||||
|
||||
// Extract extension but make sure path.Ext is not confused by any leading/trailing dots
|
||||
att.Meta.Original.Extension = strings.Trim(path.Ext(strings.Trim(name, ".")), ".")
|
||||
|
||||
att.Meta.Original.Size = size
|
||||
if att.Meta.Original.Mimetype, err = svc.extractMimetypeS(fh); err != nil {
|
||||
return AttachmentErrFailedToExtractMimeType(aProps).Wrap(err)
|
||||
}
|
||||
|
||||
att.Url = svc.objects.Original(att.ID, att.Meta.Original.Extension)
|
||||
aProps.setUrl(att.Url)
|
||||
|
||||
if err = svc.objects.Save(att.Url, fh); err != nil {
|
||||
return AttachmentErrFailedToStoreFile(aProps).Wrap(err)
|
||||
}
|
||||
|
||||
// Process image: extract width, height, make preview
|
||||
err = svc.processImage(fh, att)
|
||||
if err != nil {
|
||||
return AttachmentErrFailedToProcessImage(aProps).Wrap(err)
|
||||
}
|
||||
|
||||
if err = store.CreateComposeAttachment(ctx, s, att); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc attachment) extractMimetype(file io.ReadSeeker) (mType *mimetype.MIME, err error) {
|
||||
if _, err = file.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we rewind when we're done
|
||||
defer func(file io.ReadSeeker, offset int64, whence int) {
|
||||
_, _ = file.Seek(offset, whence)
|
||||
}(file, 0, 0)
|
||||
|
||||
var mime *mimetype.MIME
|
||||
if mime, err = mimetype.DetectReader(file); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return mime, nil
|
||||
}
|
||||
|
||||
func (svc attachment) extractMimetypeS(file io.ReadSeeker) (mType string, err error) {
|
||||
aux, err := svc.extractMimetype(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return aux.String(), nil
|
||||
}
|
||||
|
||||
func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment) (err error) {
|
||||
if !strings.HasPrefix(att.Meta.Original.Mimetype, "image/") {
|
||||
// Only supporting previews from images (for now)
|
||||
return
|
||||
}
|
||||
|
||||
if att.Meta.Original.Mimetype == "image/svg+xml" {
|
||||
// SVG is a vector format, skip raster image processing/preview generation
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
iconMimetype = "image/x-icon"
|
||||
iconExtension = "ico"
|
||||
)
|
||||
|
||||
var (
|
||||
preview image.Image
|
||||
opts []imaging.EncodeOption
|
||||
format imaging.Format
|
||||
previewFormat imaging.Format
|
||||
animated bool
|
||||
f2m = map[imaging.Format]string{
|
||||
imaging.JPEG: "image/jpeg",
|
||||
imaging.GIF: "image/gif",
|
||||
}
|
||||
f2e = map[imaging.Format]string{
|
||||
imaging.JPEG: "jpg",
|
||||
imaging.GIF: "gif",
|
||||
}
|
||||
isIcon = att.Meta.Original.Mimetype == iconMimetype
|
||||
)
|
||||
|
||||
if _, err = original.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isIcon {
|
||||
preview, err = ico.Decode(original)
|
||||
if err != nil {
|
||||
return errors.Internal("Could not decode ico config").Wrap(err)
|
||||
}
|
||||
} else {
|
||||
if format, err = imaging.FormatFromExtension(att.Meta.Original.Extension); err != nil {
|
||||
return errors.Internal("could not get format from extension '%s'", att.Meta.Original.Extension).Wrap(err)
|
||||
}
|
||||
|
||||
previewFormat = format
|
||||
|
||||
if imaging.JPEG == format {
|
||||
// Rotate image if needed
|
||||
// if preview, _, err = exiffix.Decode(original); err != nil {
|
||||
// return fmt.Errorf("Could not decode EXIF from JPEG", err)
|
||||
// }
|
||||
preview, _, _ = exiffix.Decode(original)
|
||||
}
|
||||
|
||||
if imaging.GIF == format {
|
||||
// Decode all and check loops & delay to determine if GIF is animated or not
|
||||
if cfg, err := gif.DecodeAll(original); err == nil {
|
||||
animated = cfg.LoopCount > 0 || len(cfg.Delay) > 1
|
||||
|
||||
// Use first image for the preview
|
||||
preview = cfg.Image[0]
|
||||
} else {
|
||||
return errors.Internal("Could not decode gif config").Wrap(err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Use GIF preview for GIFs and JPEG for everything else!
|
||||
previewFormat = imaging.JPEG
|
||||
|
||||
// Store with a bit lower quality
|
||||
opts = append(opts, imaging.JPEGQuality(85))
|
||||
}
|
||||
}
|
||||
|
||||
// In case of JPEG we decode the image and rotate it beforehand
|
||||
// other cases are handled here
|
||||
if preview == nil {
|
||||
if preview, err = imaging.Decode(original); err != nil {
|
||||
return errors.Internal("Could not decode original image").Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
var width, height = preview.Bounds().Max.X, preview.Bounds().Max.Y
|
||||
att.SetOriginalImageMeta(width, height, animated)
|
||||
|
||||
if width > attachmentPreviewMaxWidth && width > height {
|
||||
// Landscape does not fit
|
||||
preview = imaging.Resize(preview, attachmentPreviewMaxWidth, 0, imaging.Lanczos)
|
||||
} else if height > attachmentPreviewMaxHeight {
|
||||
// Height does not fit
|
||||
preview = imaging.Resize(preview, 0, attachmentPreviewMaxHeight, imaging.Lanczos)
|
||||
}
|
||||
|
||||
// Get dimensions from the preview
|
||||
width, height = preview.Bounds().Max.X, preview.Bounds().Max.Y
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
if err = imaging.Encode(buf, preview, previewFormat, opts...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta := att.SetPreviewImageMeta(width, height, false)
|
||||
meta.Size = int64(buf.Len())
|
||||
|
||||
if isIcon {
|
||||
meta.Mimetype = iconMimetype
|
||||
meta.Extension = iconExtension
|
||||
} else {
|
||||
meta.Mimetype = f2m[previewFormat]
|
||||
meta.Extension = f2e[previewFormat]
|
||||
}
|
||||
|
||||
// Can and how we make a preview of this attachment?
|
||||
att.PreviewUrl = svc.objects.Preview(att.ID, meta.Extension)
|
||||
|
||||
return svc.objects.Save(att.PreviewUrl, buf)
|
||||
}
|
||||
|
||||
func (attachment) checkMimeType(test *mimetype.MIME, vv ...string) bool {
|
||||
if len(vv) == 0 {
|
||||
// return true if there are no type constraints to check against
|
||||
return true
|
||||
}
|
||||
|
||||
for _, v := range vv {
|
||||
if test.Is(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var _ AttachmentService = &attachment{}
|
||||
1439
server/compose/service/attachment_actions.gen.go
generated
Normal file
1439
server/compose/service/attachment_actions.gen.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
163
server/compose/service/attachment_actions.yaml
Normal file
163
server/compose/service/attachment_actions.yaml
Normal file
@@ -0,0 +1,163 @@
|
||||
# List of loggable service actions
|
||||
|
||||
resource: compose:attachment
|
||||
service: attachment
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: notice
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
props:
|
||||
- name: size
|
||||
type: int64
|
||||
- name: name
|
||||
- name: mimetype
|
||||
- name: url
|
||||
- name: attachment
|
||||
type: "*types.Attachment"
|
||||
fields: [ name, kind, url, previewUrl, meta, ownerID, ID, namespaceID ]
|
||||
- name: filter
|
||||
type: "*types.AttachmentFilter"
|
||||
fields: [ filter, kind, sort ]
|
||||
- name: namespace
|
||||
type: "*types.Namespace"
|
||||
fields: [ name, slug, ID ]
|
||||
- name: record
|
||||
type: "*types.Record"
|
||||
fields: [ ID, moduleID, namespaceID ]
|
||||
- name: page
|
||||
type: "*types.Page"
|
||||
fields: [ handle, title, ID ]
|
||||
- name: module
|
||||
type: "*types.Module"
|
||||
fields: [ handle, name, ID ]
|
||||
|
||||
actions:
|
||||
- action: search
|
||||
log: "searched for attachments"
|
||||
severity: info
|
||||
|
||||
- action: lookup
|
||||
log: "looked-up for a {{attachment}}"
|
||||
severity: info
|
||||
|
||||
- action: create
|
||||
log: "created {{attachment}}"
|
||||
|
||||
- action: delete
|
||||
log: "deleted {{attachment}}"
|
||||
|
||||
errors:
|
||||
- error: notFound
|
||||
message: "attachment not found"
|
||||
severity: warning
|
||||
|
||||
- error: namespaceNotFound
|
||||
message: "namespace not found"
|
||||
severity: warning
|
||||
|
||||
- error: moduleNotFound
|
||||
message: "module not found"
|
||||
severity: warning
|
||||
|
||||
- error: pageNotFound
|
||||
message: "page not found"
|
||||
severity: warning
|
||||
|
||||
- error: recordNotFound
|
||||
message: "record not found"
|
||||
severity: warning
|
||||
|
||||
- error: invalidID
|
||||
message: "invalid ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidNamespaceID
|
||||
message: "invalid namespace ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidModuleID
|
||||
message: "invalid module ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidModuleField
|
||||
message: "invalid module field"
|
||||
severity: warning
|
||||
|
||||
- error: invalidPageID
|
||||
message: "invalid page ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidRecordID
|
||||
message: "invalid record ID"
|
||||
severity: warning
|
||||
|
||||
- error: tooLarge
|
||||
message: "file too large"
|
||||
log: "could not upload this file, file size too large"
|
||||
|
||||
- error: notAllowedToUploadThisType
|
||||
message: "file type not allowed"
|
||||
log: "could not upload this file, file type not allowed"
|
||||
|
||||
- error: notAllowedToListAttachments
|
||||
message: "not allowed to list attachments"
|
||||
log: "could not list attachments; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreate
|
||||
message: "not allowed to create attachments"
|
||||
log: "could not create attachments; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreateEmptyAttachment
|
||||
message: "not allowed to create empty attachments"
|
||||
log: "failed to create attachment; empty file"
|
||||
|
||||
- error: failedToExtractMimeType
|
||||
message: "could not extract mime type"
|
||||
|
||||
- error: failedToStoreFile
|
||||
message: "could not extract store file"
|
||||
|
||||
- error: failedToProcessImage
|
||||
message: "could not process image"
|
||||
|
||||
- error: notAllowedToRead
|
||||
message: "not allowed to read this module"
|
||||
log: "could not delete {{module}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToSearch
|
||||
message: "not allowed to search or list modules"
|
||||
log: "could not search or list modules; insufficient permissions"
|
||||
|
||||
- error: notAllowedToReadNamespace
|
||||
message: "not allowed to read this namespace"
|
||||
log: "could not delete {{namespace}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToReadPage
|
||||
message: "not allowed to read this page"
|
||||
log: "could not read {{page}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToReadRecord
|
||||
message: "not allowed to read this record"
|
||||
log: "could not read {{record}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdatePage
|
||||
message: "not allowed to update this page"
|
||||
log: "could not update {{page}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreateRecords
|
||||
message: "not allowed to create records"
|
||||
log: "could not create records; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdateRecord
|
||||
message: "not allowed to update this record"
|
||||
log: "could not update {{record}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdateNamespace
|
||||
message: "not allowed to update this namespace"
|
||||
log: "could not update {{namespace}}; insufficient permissions"
|
||||
447
server/compose/service/chart.go
Normal file
447
server/compose/service/chart.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
)
|
||||
|
||||
type (
|
||||
chart struct {
|
||||
actionlog actionlog.Recorder
|
||||
ac chartAccessController
|
||||
store store.Storer
|
||||
locale ResourceTranslationsManagerService
|
||||
}
|
||||
|
||||
chartAccessController interface {
|
||||
CanManageResourceTranslations(ctx context.Context) bool
|
||||
CanSearchChartsOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanReadNamespace(context.Context, *types.Namespace) bool
|
||||
CanCreateChartOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanReadChart(context.Context, *types.Chart) bool
|
||||
CanUpdateChart(context.Context, *types.Chart) bool
|
||||
CanDeleteChart(context.Context, *types.Chart) bool
|
||||
}
|
||||
|
||||
chartUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error)
|
||||
|
||||
chartChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
chartUnchanged chartChanges = 0
|
||||
chartChanged chartChanges = 1
|
||||
chartLabelsChanged chartChanges = 2
|
||||
)
|
||||
|
||||
func Chart() *chart {
|
||||
return &chart{
|
||||
ac: DefaultAccessControl,
|
||||
actionlog: DefaultActionlog,
|
||||
store: DefaultStore,
|
||||
locale: DefaultResourceTranslation,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc chart) Find(ctx context.Context, filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
|
||||
var (
|
||||
aProps = &chartActionProps{filter: &filter}
|
||||
ns *types.Namespace
|
||||
)
|
||||
|
||||
// For each fetched item, store backend will check if it is valid or not
|
||||
filter.Check = func(res *types.Chart) (bool, error) {
|
||||
if !svc.ac.CanReadChart(ctx, res) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
ns, err = loadNamespace(ctx, svc.store, filter.NamespaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
if !svc.ac.CanSearchChartsOnNamespace(ctx, ns) {
|
||||
return ChartErrNotAllowedToSearch()
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
ctx,
|
||||
svc.store,
|
||||
types.Chart{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
id.Uints(filter.ChartID...)...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposeCharts(ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, toLabeledCharts(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.Walk(func(c *types.Chart) error {
|
||||
svc.proc(ctx, c)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return set, f, svc.recordAction(ctx, aProps, ChartActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc chart) FindByID(ctx context.Context, namespaceID, chartID uint64) (c *types.Chart, err error) {
|
||||
return svc.lookup(ctx, namespaceID, func(aProps *chartActionProps) (*types.Chart, error) {
|
||||
return loadChart(ctx, svc.store, namespaceID, chartID)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc chart) FindByHandle(ctx context.Context, namespaceID uint64, h string) (c *types.Chart, err error) {
|
||||
return svc.lookup(ctx, namespaceID, func(aProps *chartActionProps) (*types.Chart, error) {
|
||||
if !handle.IsValid(h) {
|
||||
return nil, ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
aProps.chart.Handle = h
|
||||
return store.LookupComposeChartByNamespaceIDHandle(ctx, svc.store, namespaceID, h)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc chart) proc(ctx context.Context, c *types.Chart) {
|
||||
if svc.locale == nil || svc.locale.Locale() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tag := locale.GetAcceptLanguageFromContext(ctx)
|
||||
c.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, c.ResourceTranslation()))
|
||||
}
|
||||
|
||||
func (svc chart) Create(ctx context.Context, new *types.Chart) (*types.Chart, error) {
|
||||
var (
|
||||
err error
|
||||
ns *types.Namespace
|
||||
aProps = &chartActionProps{chart: new}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
|
||||
if !svc.ac.CanCreateChartOnNamespace(ctx, ns) {
|
||||
return ChartErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
if err = svc.uniqueCheck(ctx, new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
new.ID = nextID()
|
||||
new.CreatedAt = *now()
|
||||
new.UpdatedAt = nil
|
||||
new.DeletedAt = nil
|
||||
|
||||
// generate config element IDs
|
||||
new.Config.GenerateIDs(nextID)
|
||||
|
||||
aProps.setChanged(new)
|
||||
|
||||
if err = store.CreateComposeChart(ctx, s, new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = updateTranslations(ctx, svc.ac, svc.locale, new.EncodeTranslations()...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return new, svc.recordAction(ctx, aProps, ChartActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc chart) Update(ctx context.Context, upd *types.Chart) (c *types.Chart, err error) {
|
||||
return svc.updater(ctx, upd.NamespaceID, upd.ID, ChartActionUpdate, svc.handleUpdate(ctx, upd))
|
||||
}
|
||||
|
||||
func (svc chart) DeleteByID(ctx context.Context, namespaceID, chartID uint64) error {
|
||||
return trim1st(svc.updater(ctx, namespaceID, chartID, ChartActionDelete, svc.handleDelete))
|
||||
}
|
||||
|
||||
func (svc chart) UndeleteByID(ctx context.Context, namespaceID, chartID uint64) error {
|
||||
return trim1st(svc.updater(ctx, namespaceID, chartID, ChartActionUndelete, svc.handleUndelete))
|
||||
}
|
||||
|
||||
// lookup fn() orchestrates chart lookup, namespace preload and check
|
||||
func (svc chart) lookup(ctx context.Context, namespaceID uint64, lookup func(*chartActionProps) (*types.Chart, error)) (c *types.Chart, err error) {
|
||||
var aProps = &chartActionProps{chart: &types.Chart{NamespaceID: namespaceID}}
|
||||
|
||||
err = func() error {
|
||||
if ns, err := loadNamespace(ctx, svc.store, namespaceID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
aProps.setNamespace(ns)
|
||||
}
|
||||
|
||||
if c, err = lookup(aProps); errors.IsNotFound(err) {
|
||||
return ChartErrNotFound()
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setChart(c)
|
||||
|
||||
if !svc.ac.CanReadChart(ctx, c) {
|
||||
return ChartErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
svc.proc(ctx, c)
|
||||
|
||||
if err = label.Load(ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return c, svc.recordAction(ctx, aProps, ChartActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc chart) updater(ctx context.Context, namespaceID, chartID uint64, action func(...*chartActionProps) *chartAction, fn chartUpdateHandler) (*types.Chart, error) {
|
||||
var (
|
||||
changes chartChanges
|
||||
ns *types.Namespace
|
||||
c *types.Chart
|
||||
aProps = &chartActionProps{chart: &types.Chart{ID: chartID, NamespaceID: namespaceID}}
|
||||
err error
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
ns, c, err = loadChartCombo(ctx, s, namespaceID, chartID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
aProps.setChanged(c)
|
||||
|
||||
if changes, err = fn(ctx, ns, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// generate config element IDs if missing
|
||||
c.Config.GenerateIDs(nextID)
|
||||
|
||||
if changes&chartChanged > 0 {
|
||||
if err = store.UpdateComposeChart(ctx, s, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = updateTranslations(ctx, svc.ac, svc.locale, c.EncodeTranslations()...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if changes&chartLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c, svc.recordAction(ctx, aProps, action, err)
|
||||
}
|
||||
|
||||
func (svc chart) uniqueCheck(ctx context.Context, c *types.Chart) (err error) {
|
||||
if c.Handle != "" {
|
||||
if e, _ := store.LookupComposeChartByNamespaceIDHandle(ctx, svc.store, c.NamespaceID, c.Handle); e != nil && e.ID != c.ID {
|
||||
return ChartErrHandleNotUnique()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc chart) handleUpdate(ctx context.Context, upd *types.Chart) chartUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace, res *types.Chart) (changes chartChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return chartUnchanged, ChartErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
||||
return chartUnchanged, ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err := svc.uniqueCheck(ctx, upd); err != nil {
|
||||
return chartUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateChart(ctx, res) {
|
||||
return chartUnchanged, ChartErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if res.Name != upd.Name {
|
||||
changes |= chartChanged
|
||||
res.Name = upd.Name
|
||||
}
|
||||
|
||||
if res.Handle != upd.Handle {
|
||||
changes |= chartChanged
|
||||
res.Handle = upd.Handle
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(upd.Config, res.Config) {
|
||||
changes |= chartChanged
|
||||
res.Config = upd.Config
|
||||
}
|
||||
|
||||
// Assure ReportIDs
|
||||
for i, r := range res.Config.Reports {
|
||||
if r.ReportID == 0 {
|
||||
r.ReportID = nextID()
|
||||
res.Config.Reports[i] = r
|
||||
|
||||
changes |= chartChanged
|
||||
}
|
||||
|
||||
// Ensure chart report metric IDs
|
||||
for j, m := range r.Metrics {
|
||||
if val, ok := m["metricID"]; !ok || val == 0 {
|
||||
m["metricID"] = strconv.FormatUint(nextID(), 10)
|
||||
res.Config.Reports[i].Metrics[j] = m
|
||||
|
||||
changes |= chartChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
if changes&chartChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= chartLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc chart) handleDelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return chartUnchanged, ChartErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt != nil {
|
||||
// chart already deleted
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = now()
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func (svc chart) handleUndelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return chartUnchanged, ChartErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt == nil {
|
||||
// chart not deleted
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = nil
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func loadChartCombo(ctx context.Context, s interface {
|
||||
store.ComposeCharts
|
||||
store.ComposeNamespaces
|
||||
}, namespaceID, chartID uint64) (ns *types.Namespace, c *types.Chart, err error) {
|
||||
ns, err = loadNamespace(ctx, s, namespaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, err = loadChart(ctx, s, namespaceID, chartID)
|
||||
return
|
||||
}
|
||||
|
||||
func loadChart(ctx context.Context, s store.ComposeCharts, namespaceID, chartID uint64) (res *types.Chart, err error) {
|
||||
if chartID == 0 || namespaceID == 0 {
|
||||
return nil, ChartErrInvalidID()
|
||||
}
|
||||
|
||||
if res, err = store.LookupComposeChartByID(ctx, s, chartID); errors.IsNotFound(err) {
|
||||
err = ChartErrNotFound()
|
||||
}
|
||||
|
||||
if err == nil && namespaceID != res.NamespaceID {
|
||||
// Make sure chart belongs to the right namespace
|
||||
return nil, ChartErrNotFound()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledCharts converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledCharts(set []*types.Chart) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
964
server/compose/service/chart_actions.gen.go
generated
Normal file
964
server/compose/service/chart_actions.gen.go
generated
Normal file
@@ -0,0 +1,964 @@
|
||||
package service
|
||||
|
||||
// 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/service/chart_actions.yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
chartActionProps struct {
|
||||
chart *types.Chart
|
||||
changed *types.Chart
|
||||
filter *types.ChartFilter
|
||||
namespace *types.Namespace
|
||||
}
|
||||
|
||||
chartAction struct {
|
||||
timestamp time.Time
|
||||
resource string
|
||||
action string
|
||||
log string
|
||||
severity actionlog.Severity
|
||||
|
||||
// prefix for error when action fails
|
||||
errorMessage string
|
||||
|
||||
props *chartActionProps
|
||||
}
|
||||
|
||||
chartLogMetaKey struct{}
|
||||
chartPropsMetaKey struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
// just a placeholder to cover template cases w/o fmt package use
|
||||
_ = fmt.Println
|
||||
)
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Props methods
|
||||
// setChart updates chartActionProps's chart
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p *chartActionProps) setChart(chart *types.Chart) *chartActionProps {
|
||||
p.chart = chart
|
||||
return p
|
||||
}
|
||||
|
||||
// setChanged updates chartActionProps's changed
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p *chartActionProps) setChanged(changed *types.Chart) *chartActionProps {
|
||||
p.changed = changed
|
||||
return p
|
||||
}
|
||||
|
||||
// setFilter updates chartActionProps's filter
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p *chartActionProps) setFilter(filter *types.ChartFilter) *chartActionProps {
|
||||
p.filter = filter
|
||||
return p
|
||||
}
|
||||
|
||||
// setNamespace updates chartActionProps's namespace
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p *chartActionProps) setNamespace(namespace *types.Namespace) *chartActionProps {
|
||||
p.namespace = namespace
|
||||
return p
|
||||
}
|
||||
|
||||
// Serialize converts chartActionProps to actionlog.Meta
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p chartActionProps) Serialize() actionlog.Meta {
|
||||
var (
|
||||
m = make(actionlog.Meta)
|
||||
)
|
||||
|
||||
if p.chart != nil {
|
||||
m.Set("chart.name", p.chart.Name, true)
|
||||
m.Set("chart.handle", p.chart.Handle, true)
|
||||
m.Set("chart.ID", p.chart.ID, true)
|
||||
m.Set("chart.namespaceID", p.chart.NamespaceID, true)
|
||||
}
|
||||
if p.changed != nil {
|
||||
m.Set("changed.name", p.changed.Name, true)
|
||||
m.Set("changed.handle", p.changed.Handle, true)
|
||||
m.Set("changed.ID", p.changed.ID, true)
|
||||
m.Set("changed.namespaceID", p.changed.NamespaceID, true)
|
||||
m.Set("changed.config", p.changed.Config, true)
|
||||
}
|
||||
if p.filter != nil {
|
||||
m.Set("filter.query", p.filter.Query, true)
|
||||
m.Set("filter.handle", p.filter.Handle, true)
|
||||
m.Set("filter.namespaceID", p.filter.NamespaceID, true)
|
||||
m.Set("filter.sort", p.filter.Sort, true)
|
||||
m.Set("filter.limit", p.filter.Limit, true)
|
||||
}
|
||||
if p.namespace != nil {
|
||||
m.Set("namespace.name", p.namespace.Name, true)
|
||||
m.Set("namespace.slug", p.namespace.Slug, true)
|
||||
m.Set("namespace.ID", p.namespace.ID, true)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// tr translates string and replaces meta value placeholder with values
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (p chartActionProps) Format(in string, err error) string {
|
||||
var (
|
||||
pairs = []string{"{{err}}"}
|
||||
// first non-empty string
|
||||
fns = func(ii ...interface{}) string {
|
||||
for _, i := range ii {
|
||||
if s := fmt.Sprintf("%v", i); len(s) > 0 {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
pairs = append(pairs, err.Error())
|
||||
} else {
|
||||
pairs = append(pairs, "nil")
|
||||
}
|
||||
|
||||
if p.chart != nil {
|
||||
// replacement for "{{chart}}" (in order how fields are defined)
|
||||
pairs = append(
|
||||
pairs,
|
||||
"{{chart}}",
|
||||
fns(
|
||||
p.chart.Name,
|
||||
p.chart.Handle,
|
||||
p.chart.ID,
|
||||
p.chart.NamespaceID,
|
||||
),
|
||||
)
|
||||
pairs = append(pairs, "{{chart.name}}", fns(p.chart.Name))
|
||||
pairs = append(pairs, "{{chart.handle}}", fns(p.chart.Handle))
|
||||
pairs = append(pairs, "{{chart.ID}}", fns(p.chart.ID))
|
||||
pairs = append(pairs, "{{chart.namespaceID}}", fns(p.chart.NamespaceID))
|
||||
}
|
||||
|
||||
if p.changed != nil {
|
||||
// replacement for "{{changed}}" (in order how fields are defined)
|
||||
pairs = append(
|
||||
pairs,
|
||||
"{{changed}}",
|
||||
fns(
|
||||
p.changed.Name,
|
||||
p.changed.Handle,
|
||||
p.changed.ID,
|
||||
p.changed.NamespaceID,
|
||||
p.changed.Config,
|
||||
),
|
||||
)
|
||||
pairs = append(pairs, "{{changed.name}}", fns(p.changed.Name))
|
||||
pairs = append(pairs, "{{changed.handle}}", fns(p.changed.Handle))
|
||||
pairs = append(pairs, "{{changed.ID}}", fns(p.changed.ID))
|
||||
pairs = append(pairs, "{{changed.namespaceID}}", fns(p.changed.NamespaceID))
|
||||
pairs = append(pairs, "{{changed.config}}", fns(p.changed.Config))
|
||||
}
|
||||
|
||||
if p.filter != nil {
|
||||
// replacement for "{{filter}}" (in order how fields are defined)
|
||||
pairs = append(
|
||||
pairs,
|
||||
"{{filter}}",
|
||||
fns(
|
||||
p.filter.Query,
|
||||
p.filter.Handle,
|
||||
p.filter.NamespaceID,
|
||||
p.filter.Sort,
|
||||
p.filter.Limit,
|
||||
),
|
||||
)
|
||||
pairs = append(pairs, "{{filter.query}}", fns(p.filter.Query))
|
||||
pairs = append(pairs, "{{filter.handle}}", fns(p.filter.Handle))
|
||||
pairs = append(pairs, "{{filter.namespaceID}}", fns(p.filter.NamespaceID))
|
||||
pairs = append(pairs, "{{filter.sort}}", fns(p.filter.Sort))
|
||||
pairs = append(pairs, "{{filter.limit}}", fns(p.filter.Limit))
|
||||
}
|
||||
|
||||
if p.namespace != nil {
|
||||
// replacement for "{{namespace}}" (in order how fields are defined)
|
||||
pairs = append(
|
||||
pairs,
|
||||
"{{namespace}}",
|
||||
fns(
|
||||
p.namespace.Name,
|
||||
p.namespace.Slug,
|
||||
p.namespace.ID,
|
||||
),
|
||||
)
|
||||
pairs = append(pairs, "{{namespace.name}}", fns(p.namespace.Name))
|
||||
pairs = append(pairs, "{{namespace.slug}}", fns(p.namespace.Slug))
|
||||
pairs = append(pairs, "{{namespace.ID}}", fns(p.namespace.ID))
|
||||
}
|
||||
return strings.NewReplacer(pairs...).Replace(in)
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action methods
|
||||
|
||||
// String returns loggable description as string
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (a *chartAction) String() string {
|
||||
var props = &chartActionProps{}
|
||||
|
||||
if a.props != nil {
|
||||
props = a.props
|
||||
}
|
||||
|
||||
return props.Format(a.log, nil)
|
||||
}
|
||||
|
||||
func (e *chartAction) ToAction() *actionlog.Action {
|
||||
return &actionlog.Action{
|
||||
Resource: e.resource,
|
||||
Action: e.action,
|
||||
Severity: e.severity,
|
||||
Description: e.String(),
|
||||
Meta: e.props.Serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action constructors
|
||||
|
||||
// ChartActionSearch returns "compose:chart.search" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionSearch(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "search",
|
||||
log: "searched for charts",
|
||||
severity: actionlog.Info,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionLookup returns "compose:chart.lookup" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionLookup(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "lookup",
|
||||
log: "looked-up for a {{chart}}",
|
||||
severity: actionlog.Info,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionCreate returns "compose:chart.create" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionCreate(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "create",
|
||||
log: "created {{chart}}",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionUpdate returns "compose:chart.update" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionUpdate(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "update",
|
||||
log: "updated {{chart}}",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionDelete returns "compose:chart.delete" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionDelete(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "delete",
|
||||
log: "deleted {{chart}}",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionUndelete returns "compose:chart.undelete" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionUndelete(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "undelete",
|
||||
log: "undeleted {{chart}}",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ChartActionReorder returns "compose:chart.reorder" action
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartActionReorder(props ...*chartActionProps) *chartAction {
|
||||
a := &chartAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "compose:chart",
|
||||
action: "reorder",
|
||||
log: "reordered {{chart}}",
|
||||
severity: actionlog.Notice,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Error constructors
|
||||
|
||||
// ChartErrGeneric returns "compose:chart.generic" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrGeneric(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("failed to complete request due to internal error", nil),
|
||||
|
||||
errors.Meta("type", "generic"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "{err}"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.generic"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotFound returns "compose:chart.notFound" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotFound(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("chart does not exist", nil),
|
||||
|
||||
errors.Meta("type", "notFound"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notFound"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNamespaceNotFound returns "compose:chart.namespaceNotFound" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNamespaceNotFound(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("namespace does not exist", nil),
|
||||
|
||||
errors.Meta("type", "namespaceNotFound"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.namespaceNotFound"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrModuleNotFound returns "compose:chart.moduleNotFound" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrModuleNotFound(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("module does not exist", nil),
|
||||
|
||||
errors.Meta("type", "moduleNotFound"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.moduleNotFound"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrInvalidID returns "compose:chart.invalidID" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrInvalidID(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("invalid ID", nil),
|
||||
|
||||
errors.Meta("type", "invalidID"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.invalidID"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrInvalidHandle returns "compose:chart.invalidHandle" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrInvalidHandle(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("invalid handle", nil),
|
||||
|
||||
errors.Meta("type", "invalidHandle"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.invalidHandle"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrHandleNotUnique returns "compose:chart.handleNotUnique" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrHandleNotUnique(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("handle not unique", nil),
|
||||
|
||||
errors.Meta("type", "handleNotUnique"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "used duplicate handle ({{chart.handle}}) for chart"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.handleNotUnique"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrStaleData returns "compose:chart.staleData" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrStaleData(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("chart was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version", nil),
|
||||
|
||||
errors.Meta("type", "staleData"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.staleData"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrInvalidNamespaceID returns "compose:chart.invalidNamespaceID" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrInvalidNamespaceID(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("invalid or missing namespace ID", nil),
|
||||
|
||||
errors.Meta("type", "invalidNamespaceID"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.invalidNamespaceID"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToRead returns "compose:chart.notAllowedToRead" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToRead(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to read this chart", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToRead"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not read {{chart}}; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToRead"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToSearch returns "compose:chart.notAllowedToSearch" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToSearch(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to search or list charts", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToSearch"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not search or list charts; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToSearch"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToReadNamespace returns "compose:chart.notAllowedToReadNamespace" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToReadNamespace(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to read this namespace", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToReadNamespace"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not read namespace {{namespace}}; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToReadNamespace"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToCreate returns "compose:chart.notAllowedToCreate" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToCreate(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to create charts", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToCreate"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not create charts; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToCreate"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToUpdate returns "compose:chart.notAllowedToUpdate" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToUpdate(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to update this chart", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToUpdate"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not update {{chart}}; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToUpdate"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToDelete returns "compose:chart.notAllowedToDelete" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToDelete(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to delete this chart", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToDelete"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not delete {{chart}}; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToDelete"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// ChartErrNotAllowedToUndelete returns "compose:chart.notAllowedToUndelete" as *errors.Error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func ChartErrNotAllowedToUndelete(mm ...*chartActionProps) *errors.Error {
|
||||
var p = &chartActionProps{}
|
||||
if len(mm) > 0 {
|
||||
p = mm[0]
|
||||
}
|
||||
|
||||
var e = errors.New(
|
||||
errors.KindInternal,
|
||||
|
||||
p.Format("not allowed to undelete this chart", nil),
|
||||
|
||||
errors.Meta("type", "notAllowedToUndelete"),
|
||||
errors.Meta("resource", "compose:chart"),
|
||||
|
||||
// action log entry; no formatting, it will be applied inside recordAction fn.
|
||||
errors.Meta(chartLogMetaKey{}, "could not undelete {{chart}}; insufficient permissions"),
|
||||
errors.Meta(chartPropsMetaKey{}, p),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "chart.errors.notAllowedToUndelete"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
)
|
||||
|
||||
if len(mm) > 0 {
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
|
||||
// recordAction is a service helper function wraps function that can return error
|
||||
//
|
||||
// It will wrap unrecognized/internal errors with generic errors.
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (svc chart) recordAction(ctx context.Context, props *chartActionProps, actionFn func(...*chartActionProps) *chartAction, err error) error {
|
||||
if svc.actionlog == nil || actionFn == nil {
|
||||
// action log disabled or no action fn passed, return error as-is
|
||||
return err
|
||||
} else if err == nil {
|
||||
// action completed w/o error, record it
|
||||
svc.actionlog.Record(ctx, actionFn(props).ToAction())
|
||||
return nil
|
||||
}
|
||||
|
||||
a := actionFn(props).ToAction()
|
||||
|
||||
// Extracting error information and recording it as action
|
||||
a.Error = err.Error()
|
||||
|
||||
switch c := err.(type) {
|
||||
case *errors.Error:
|
||||
m := c.Meta()
|
||||
|
||||
a.Error = err.Error()
|
||||
a.Severity = actionlog.Severity(m.AsInt("severity"))
|
||||
a.Description = props.Format(m.AsString(chartLogMetaKey{}), err)
|
||||
|
||||
if p, has := m[chartPropsMetaKey{}]; has {
|
||||
a.Meta = p.(*chartActionProps).Serialize()
|
||||
}
|
||||
|
||||
svc.actionlog.Record(ctx, a)
|
||||
default:
|
||||
svc.actionlog.Record(ctx, a)
|
||||
}
|
||||
|
||||
// Original error is passed on
|
||||
return err
|
||||
}
|
||||
113
server/compose/service/chart_actions.yaml
Normal file
113
server/compose/service/chart_actions.yaml
Normal file
@@ -0,0 +1,113 @@
|
||||
# List of loggable service actions
|
||||
|
||||
resource: compose:chart
|
||||
service: chart
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: notice
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: error
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
|
||||
props:
|
||||
- name: chart
|
||||
type: "*types.Chart"
|
||||
fields: [ name, handle, ID, namespaceID ]
|
||||
- name: changed
|
||||
type: "*types.Chart"
|
||||
fields: [ name, handle, ID, namespaceID, config ]
|
||||
- name: filter
|
||||
type: "*types.ChartFilter"
|
||||
fields: [ query, handle, namespaceID, sort, limit ]
|
||||
- name: namespace
|
||||
type: "*types.Namespace"
|
||||
fields: [ name, slug, ID ]
|
||||
|
||||
actions:
|
||||
- action: search
|
||||
log: "searched for charts"
|
||||
severity: info
|
||||
|
||||
- action: lookup
|
||||
log: "looked-up for a {{chart}}"
|
||||
severity: info
|
||||
|
||||
- action: create
|
||||
log: "created {{chart}}"
|
||||
|
||||
- action: update
|
||||
log: "updated {{chart}}"
|
||||
|
||||
- action: delete
|
||||
log: "deleted {{chart}}"
|
||||
|
||||
- action: undelete
|
||||
log: "undeleted {{chart}}"
|
||||
|
||||
- action: reorder
|
||||
log: "reordered {{chart}}"
|
||||
|
||||
errors:
|
||||
- error: notFound
|
||||
message: "chart does not exist"
|
||||
severity: warning
|
||||
|
||||
- error: namespaceNotFound
|
||||
message: "namespace does not exist"
|
||||
severity: warning
|
||||
|
||||
- error: moduleNotFound
|
||||
message: "module does not exist"
|
||||
severity: warning
|
||||
|
||||
- error: invalidID
|
||||
message: "invalid ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidHandle
|
||||
message: "invalid handle"
|
||||
severity: warning
|
||||
|
||||
- error: handleNotUnique
|
||||
message: "handle not unique"
|
||||
log: "used duplicate handle ({{chart.handle}}) for chart"
|
||||
severity: warning
|
||||
|
||||
- error: staleData
|
||||
message: "chart was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version"
|
||||
severity: warning
|
||||
|
||||
- error: invalidNamespaceID
|
||||
message: "invalid or missing namespace ID"
|
||||
severity: warning
|
||||
|
||||
- error: notAllowedToRead
|
||||
message: "not allowed to read this chart"
|
||||
log: "could not read {{chart}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToSearch
|
||||
message: "not allowed to search or list charts"
|
||||
log: "could not search or list charts; insufficient permissions"
|
||||
|
||||
- error: notAllowedToReadNamespace
|
||||
message: "not allowed to read this namespace"
|
||||
log: "could not read namespace {{namespace}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreate
|
||||
message: "not allowed to create charts"
|
||||
log: "could not create charts; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdate
|
||||
message: "not allowed to update this chart"
|
||||
log: "could not update {{chart}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToDelete
|
||||
message: "not allowed to delete this chart"
|
||||
log: "could not delete {{chart}}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUndelete
|
||||
message: "not allowed to undelete this chart"
|
||||
log: "could not undelete {{chart}}; insufficient permissions"
|
||||
100
server/compose/service/chart_test.go
Normal file
100
server/compose/service/chart_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestCharts(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
s, err = sqlite.ConnectInMemory(ctx)
|
||||
|
||||
namespaceID = nextID()
|
||||
ns *types.Namespace
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to init sqlite in-memory db: %v", err)
|
||||
}
|
||||
|
||||
if err = store.Upgrade(ctx, zap.NewNop(), s); err != nil {
|
||||
t.Fatalf("failed to upgrade store: %v", err)
|
||||
}
|
||||
|
||||
if err = s.TruncateComposeNamespaces(ctx); err != nil {
|
||||
t.Fatalf("failed to truncate compose namespaces: %v", err)
|
||||
}
|
||||
|
||||
if err = s.TruncateComposeCharts(ctx); err != nil {
|
||||
t.Fatalf("failed to truncate compose charts: %v", err)
|
||||
}
|
||||
|
||||
ns = &types.Namespace{Name: "testing", ID: namespaceID, CreatedAt: *now()}
|
||||
if err = store.CreateComposeNamespace(ctx, s, ns); err != nil {
|
||||
t.Fatalf("failed to seed namespaces: %v", err)
|
||||
}
|
||||
|
||||
t.Run("crud", func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
svc := &chart{
|
||||
store: s,
|
||||
ac: &accessControl{rbac: &rbac.ServiceAllowAll{}},
|
||||
}
|
||||
res, err := svc.Create(ctx, &types.Chart{Name: "My first chart", NamespaceID: namespaceID})
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
|
||||
res, err = svc.FindByID(ctx, namespaceID, res.ID)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
|
||||
res, err = svc.FindByHandle(ctx, namespaceID, res.Handle)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
|
||||
res.Name = "Changed"
|
||||
res, err = svc.Update(ctx, res)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
req.NotNil(res.UpdatedAt)
|
||||
req.Equal(res.Name, "Changed")
|
||||
|
||||
res, err = svc.FindByID(ctx, namespaceID, res.ID)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
req.Equal(res.Name, "Changed")
|
||||
|
||||
err = svc.DeleteByID(ctx, namespaceID, res.ID)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
|
||||
// this works because we're allowed to do everything
|
||||
res, err = svc.FindByID(ctx, namespaceID, res.ID)
|
||||
req.NoError(unwrapChartInternal(err))
|
||||
req.NotNil(res)
|
||||
req.NotNil(res.DeletedAt)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func unwrapChartInternal(err error) error {
|
||||
g := ChartErrGeneric()
|
||||
for {
|
||||
if errors.Is(err, g) {
|
||||
err = errors.Unwrap(err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
21
server/compose/service/dal_interfaces.go
Normal file
21
server/compose/service/dal_interfaces.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
dalDater interface {
|
||||
Create(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, vv ...dal.ValueGetter) error
|
||||
Update(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, rr ...dal.ValueGetter) (err error)
|
||||
Search(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (dal.Iterator, error)
|
||||
Count(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (uint, error)
|
||||
Run(ctx context.Context, pp dal.Pipeline) (dal.Iterator, error)
|
||||
Lookup(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, lookup dal.ValueGetter, dst dal.ValueSetter) (err error)
|
||||
Delete(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, pkv ...dal.ValueGetter) (err error)
|
||||
Truncate(ctx context.Context, m dal.ModelRef, operations dal.OperationSet) (err error)
|
||||
}
|
||||
)
|
||||
84
server/compose/service/data_privacy.go
Normal file
84
server/compose/service/data_privacy.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
sysService "github.com/cortezaproject/corteza/server/system/service"
|
||||
sysTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
dataPrivacy struct {
|
||||
ns NamespaceService
|
||||
m ModuleService
|
||||
dalConn dalConnectionService
|
||||
locale ResourceTranslationsManagerService
|
||||
}
|
||||
|
||||
dalConnectionService interface {
|
||||
FindByID(ctx context.Context, ID uint64) (q *sysTypes.DalConnection, err error)
|
||||
}
|
||||
|
||||
DataPrivacyService interface {
|
||||
FindModules(ctx context.Context, filter types.PrivacyModuleFilter) (types.PrivacyModuleSet, types.PrivacyModuleFilter, error)
|
||||
}
|
||||
)
|
||||
|
||||
func DataPrivacy() *dataPrivacy {
|
||||
return &dataPrivacy{
|
||||
ns: DefaultNamespace,
|
||||
m: DefaultModule,
|
||||
dalConn: sysService.DefaultDalConnection,
|
||||
locale: DefaultResourceTranslation,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc dataPrivacy) FindModules(ctx context.Context, filter types.PrivacyModuleFilter) (out types.PrivacyModuleSet, f types.PrivacyModuleFilter, err error) {
|
||||
var (
|
||||
modules []types.PrivacyModule
|
||||
cc = make(map[uint64]*sysTypes.DalConnection, 0)
|
||||
)
|
||||
|
||||
namespaces, _, err := svc.ns.Find(ctx, types.NamespaceFilter{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range namespaces {
|
||||
tag := locale.GetAcceptLanguageFromContext(ctx)
|
||||
n.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, n.ResourceTranslation()))
|
||||
|
||||
modules, f, err = svc.m.SearchSensitive(ctx, types.PrivacyModuleFilter{
|
||||
NamespaceID: n.ID,
|
||||
ConnectionID: filter.ConnectionID,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, m := range modules {
|
||||
connID := m.ConnectionID
|
||||
if val, ok := cc[connID]; ok {
|
||||
m.Connection = val
|
||||
} else {
|
||||
m.Connection, err = svc.dalConn.FindByID(ctx, connID)
|
||||
if err != nil {
|
||||
cc[connID] = m.Connection
|
||||
}
|
||||
}
|
||||
|
||||
m.Namespace = types.PrivacyNamespaceMeta{
|
||||
ID: n.ID,
|
||||
Slug: n.Slug,
|
||||
Name: n.Name,
|
||||
}
|
||||
out = append(out, &m)
|
||||
}
|
||||
}
|
||||
|
||||
return out, f, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user