From 5d86b241d5888100cfb269ccf748e62a75ea4878 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Mon, 1 Feb 2021 20:18:53 +0100 Subject: [PATCH] Add basic automation functions for system --- system/automation/expr_types.gen.go | 395 +++++++++++ system/automation/expr_types.go | 41 ++ system/automation/expr_types.yaml | 30 + system/automation/expr_types_test.go | 39 ++ system/automation/roles_handler.gen.go | 904 +++++++++++++++++++++++++ system/automation/roles_handler.go | 225 ++++++ system/automation/roles_handler.yaml | 135 ++++ system/automation/users_handler.gen.go | 903 ++++++++++++++++++++++++ system/automation/users_handler.go | 226 +++++++ system/automation/users_handler.yaml | 132 ++++ system/service/service.go | 25 +- system/service/user.go | 4 +- 12 files changed, 3053 insertions(+), 6 deletions(-) create mode 100644 system/automation/expr_types.gen.go create mode 100644 system/automation/expr_types.go create mode 100644 system/automation/expr_types.yaml create mode 100644 system/automation/expr_types_test.go create mode 100644 system/automation/roles_handler.gen.go create mode 100644 system/automation/roles_handler.go create mode 100644 system/automation/roles_handler.yaml create mode 100644 system/automation/users_handler.gen.go create mode 100644 system/automation/users_handler.go create mode 100644 system/automation/users_handler.yaml diff --git a/system/automation/expr_types.gen.go b/system/automation/expr_types.gen.go new file mode 100644 index 000000000..7c16d35d0 --- /dev/null +++ b/system/automation/expr_types.gen.go @@ -0,0 +1,395 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// system/automation/expr_types.yaml + +import ( + "context" + "fmt" + . "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/system/types" +) + +var _ = context.Background +var _ = fmt.Errorf + +// Role is an expression type, wrapper for *types.Role type +type Role struct{ value *types.Role } + +// NewRole creates new instance of Role expression type +func NewRole(val interface{}) (*Role, error) { + if c, err := CastToRole(val); err != nil { + return nil, fmt.Errorf("unable to create Role: %w", err) + } else { + return &Role{value: c}, nil + } +} + +// Return underlying value on Role +func (t Role) Get() interface{} { return t.value } + +// Return type name +func (Role) Type() string { return "Role" } + +// Convert value to *types.Role +func (Role) Cast(val interface{}) (TypedValue, error) { + return NewRole(val) +} + +// Assign new value to Role +// +// value is first passed through CastToRole +func (t *Role) Assign(val interface{}) error { + if c, err := CastToRole(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *Role) AssignFieldValue(key string, val interface{}) error { + return assignToRole(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access Role's underlying value (*types.Role) +// and it's fields +// +func (t Role) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return roleGValSelector(t.value, k) +} + +// Select is field accessor for *types.Role +// +// Similar to SelectGVal but returns typed values +func (t Role) Select(k string) (TypedValue, error) { + return roleTypedValueSelector(t.value, k) +} + +func (t Role) Has(k string) bool { + switch k { + case "ID": + return true + case "name": + return true + case "handle": + return true + case "labels": + return true + case "createdAt": + return true + case "updatedAt": + return true + case "archivedAt": + return true + case "deletedAt": + return true + } + return false +} + +// roleGValSelector is field accessor for *types.Role +func roleGValSelector(res *types.Role, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, 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 "archivedAt": + return res.ArchivedAt, nil + case "deletedAt": + return res.DeletedAt, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// roleTypedValueSelector is field accessor for *types.Role +func roleTypedValueSelector(res *types.Role, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "name": + return NewString(res.Name) + case "handle": + return NewHandle(res.Handle) + case "labels": + return NewKV(res.Labels) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "archivedAt": + return NewDateTime(res.ArchivedAt) + case "deletedAt": + return NewDateTime(res.DeletedAt) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToRole is field value setter for *types.Role +func assignToRole(res *types.Role, k string, val interface{}) error { + switch k { + case "ID": + 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 := CastToKV(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 "archivedAt": + 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) +} + +// User is an expression type, wrapper for *types.User type +type User struct{ value *types.User } + +// NewUser creates new instance of User expression type +func NewUser(val interface{}) (*User, error) { + if c, err := CastToUser(val); err != nil { + return nil, fmt.Errorf("unable to create User: %w", err) + } else { + return &User{value: c}, nil + } +} + +// Return underlying value on User +func (t User) Get() interface{} { return t.value } + +// Return type name +func (User) Type() string { return "User" } + +// Convert value to *types.User +func (User) Cast(val interface{}) (TypedValue, error) { + return NewUser(val) +} + +// Assign new value to User +// +// value is first passed through CastToUser +func (t *User) Assign(val interface{}) error { + if c, err := CastToUser(val); err != nil { + return err + } else { + t.value = c + return nil + } +} + +func (t *User) AssignFieldValue(key string, val interface{}) error { + return assignToUser(t.value, key, val) +} + +// SelectGVal implements gval.Selector requirements +// +// It allows gval lib to access User's underlying value (*types.User) +// and it's fields +// +func (t User) SelectGVal(ctx context.Context, k string) (interface{}, error) { + return userGValSelector(t.value, k) +} + +// Select is field accessor for *types.User +// +// Similar to SelectGVal but returns typed values +func (t User) Select(k string) (TypedValue, error) { + return userTypedValueSelector(t.value, k) +} + +func (t User) Has(k string) bool { + switch k { + case "ID": + return true + case "username": + return true + case "email": + return true + case "name": + return true + case "handle": + return true + case "emailConfirmed": + return true + case "labels": + return true + case "createdAt": + return true + case "updatedAt": + return true + case "suspendedAt": + return true + case "deletedAt": + return true + } + return false +} + +// userGValSelector is field accessor for *types.User +func userGValSelector(res *types.User, k string) (interface{}, error) { + switch k { + case "ID": + return res.ID, nil + case "username": + return res.Username, nil + case "email": + return res.Email, nil + case "name": + return res.Name, nil + case "handle": + return res.Handle, nil + case "emailConfirmed": + return res.EmailConfirmed, nil + case "labels": + return res.Labels, nil + case "createdAt": + return res.CreatedAt, nil + case "updatedAt": + return res.UpdatedAt, nil + case "suspendedAt": + return res.SuspendedAt, nil + case "deletedAt": + return res.DeletedAt, nil + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// userTypedValueSelector is field accessor for *types.User +func userTypedValueSelector(res *types.User, k string) (TypedValue, error) { + switch k { + case "ID": + return NewID(res.ID) + case "username": + return NewString(res.Username) + case "email": + return NewString(res.Email) + case "name": + return NewString(res.Name) + case "handle": + return NewHandle(res.Handle) + case "emailConfirmed": + return NewBoolean(res.EmailConfirmed) + case "labels": + return NewKV(res.Labels) + case "createdAt": + return NewDateTime(res.CreatedAt) + case "updatedAt": + return NewDateTime(res.UpdatedAt) + case "suspendedAt": + return NewDateTime(res.SuspendedAt) + case "deletedAt": + return NewDateTime(res.DeletedAt) + } + + return nil, fmt.Errorf("unknown field '%s'", k) +} + +// assignToUser is field value setter for *types.User +func assignToUser(res *types.User, k string, val interface{}) error { + switch k { + case "ID": + return fmt.Errorf("field '%s' is read-only", k) + case "username": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Username = aux + return nil + case "email": + aux, err := CastToString(val) + if err != nil { + return err + } + + res.Email = aux + return nil + 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 "emailConfirmed": + aux, err := CastToBoolean(val) + if err != nil { + return err + } + + res.EmailConfirmed = aux + return nil + case "labels": + aux, err := CastToKV(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 "suspendedAt": + 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) +} diff --git a/system/automation/expr_types.go b/system/automation/expr_types.go new file mode 100644 index 000000000..392475d68 --- /dev/null +++ b/system/automation/expr_types.go @@ -0,0 +1,41 @@ +package automation + +import ( + "fmt" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/system/types" +) + +func CastToUser(val interface{}) (out *types.User, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.User{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToUser(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.User: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} + +func CastToRole(val interface{}) (out *types.Role, err error) { + switch val := val.(type) { + case expr.Iterator: + out = &types.Role{} + return out, val.Each(func(k string, v expr.TypedValue) error { + return assignToRole(out, k, v) + }) + } + + switch val := expr.UntypedValue(val).(type) { + case *types.Role: + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} diff --git a/system/automation/expr_types.yaml b/system/automation/expr_types.yaml new file mode 100644 index 000000000..31b8aa3f2 --- /dev/null +++ b/system/automation/expr_types.yaml @@ -0,0 +1,30 @@ +package: automation +imports: + - github.com/cortezaproject/corteza-server/system/types + +types: + User: + as: '*types.User' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'username', exprType: 'String', goType: 'string' } + - { name: 'email', exprType: 'String', goType: 'string' } + - { name: 'name', exprType: 'String', goType: 'string' } + - { name: 'handle', exprType: 'Handle', goType: 'string' } + - { name: 'emailConfirmed', exprType: 'Boolean', goType: 'bool' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]string' } + - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro} + - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'suspendedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + Role: + as: '*types.Role' + struct: + - { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro } + - { name: 'name', exprType: 'String', goType: 'string' } + - { name: 'handle', exprType: 'Handle', goType: 'string' } + - { name: 'labels', exprType: 'KV', goType: 'map[string]''string' } + - { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro} + - { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'archivedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } + - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } diff --git a/system/automation/expr_types_test.go b/system/automation/expr_types_test.go new file mode 100644 index 000000000..04013defe --- /dev/null +++ b/system/automation/expr_types_test.go @@ -0,0 +1,39 @@ +package automation + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/stretchr/testify/require" + "testing" +) + +func TestUser(t *testing.T) { + var ( + req = require.New(t) + u, err = NewUser(&types.User{Handle: "handle"}) + ) + + req.NoError(err) + req.Equal("handle", u.value.Handle) + req.Error(u.AssignFieldValue("some-unexisting-field", nil)) + req.NoError(u.AssignFieldValue("email", "dummy@domain.tpl")) + req.Equal("dummy@domain.tpl", u.value.Email) +} + +func TestUser_Expr(t *testing.T) { + var ( + req = require.New(t) + u, err = NewUser(&types.User{Handle: "hendl"}) + ) + + req.NoError(err) + + eval, err := expr.NewParser().Parse("user.handle") + req.NoError(err) + + res, err := eval.Eval(context.Background(), expr.RVars{"user": u}.Vars()) + req.NoError(err) + + req.Equal("hendl", res.(string)) +} diff --git a/system/automation/roles_handler.gen.go b/system/automation/roles_handler.gen.go new file mode 100644 index 000000000..f696b7fb2 --- /dev/null +++ b/system/automation/roles_handler.gen.go @@ -0,0 +1,904 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// system/automation/roles_handler.yaml + +import ( + "context" + atypes "github.com/cortezaproject/corteza-server/automation/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" +) + +var _ wfexec.ExecResponse + +type ( + rolesHandlerRegistry interface { + AddFunctions(ff ...*atypes.Function) + Type(ref string) expr.Type + } +) + +func (h rolesHandler) register() { + h.reg.AddFunctions( + h.Lookup(), + h.Search(), + h.Each(), + h.Create(), + h.Update(), + h.Delete(), + h.Recover(), + h.Archive(), + h.Unarchive(), + ) +} + +type ( + rolesLookupArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } + + rolesLookupResults struct { + Role *types.Role + } +) + +func (a rolesLookupArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Lookup function Looks-up for role by ID +// +// expects implementation of lookup function: +// func (h rolesHandler) lookup(ctx context.Context, args *rolesLookupArgs) (results *rolesLookupResults, err error) { +// return +// } +func (h rolesHandler) Lookup() *atypes.Function { + return &atypes.Function{ + Ref: "rolesLookup", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Looks-up for role by ID", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "role", + Types: []string{"Role"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesLookupArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + var results *rolesLookupResults + if results, err = h.lookup(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Role (*types.Role) to Role + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Role").Cast(results.Role); err != nil { + return + } else if err = expr.Assign(out, "role", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + rolesSearchArgs struct { + hasQuery bool + Query string + + hasMemberID bool + MemberID uint64 + + hasHandle bool + Handle string + + hasName bool + Name string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasArchived bool + Archived uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + rolesSearchResults struct { + Roles []*types.Role + Total uint64 + PageCursor string + } +) + +// Search function Searches for roles and returns them +// +// expects implementation of search function: +// func (h rolesHandler) search(ctx context.Context, args *rolesSearchArgs) (results *rolesSearchResults, err error) { +// return +// } +func (h rolesHandler) Search() *atypes.Function { + return &atypes.Function{ + Ref: "rolesSearch", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Searches for roles and returns them", + }, + + Parameters: []*atypes.Param{ + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "memberID", + Types: []string{"ID"}, + }, + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "name", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "archived", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "roles", + Types: []string{"Role"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesSearchArgs{ + hasQuery: in.Has("query"), + hasMemberID: in.Has("memberID"), + hasHandle: in.Has("handle"), + hasName: in.Has("name"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasArchived: in.Has("archived"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *rolesSearchResults + if results, err = h.search(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Roles (*types.Role) to Array (of Role) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Roles)) + ) + + for i := range results.Roles { + if tarr[i], err = h.reg.Type("Role").Cast(results.Roles[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "roles", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + { + // converting results.PageCursor (string) to String + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("String").Cast(results.PageCursor); err != nil { + return + } else if err = expr.Assign(out, "pageCursor", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + rolesEachArgs struct { + hasQuery bool + Query string + + hasMemberID bool + MemberID uint64 + + hasHandle bool + Handle string + + hasName bool + Name string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasArchived bool + Archived uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + rolesEachResults struct { + Role *types.Role + Total uint64 + } +) + +// Each function Searches for roles and iterates over results +// +// expects implementation of each function: +// func (h rolesHandler) each(ctx context.Context, args *rolesEachArgs) (results *rolesEachResults, err error) { +// return +// } +func (h rolesHandler) Each() *atypes.Function { + return &atypes.Function{ + Ref: "rolesEach", + Kind: "iterator", + Meta: &atypes.FunctionMeta{ + Short: "Searches for roles and iterates over results", + }, + + Parameters: []*atypes.Param{ + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "memberID", + Types: []string{"ID"}, + }, + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "name", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "archived", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "role", + Types: []string{"Role"}, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) { + var ( + args = &rolesEachArgs{ + hasQuery: in.Has("query"), + hasMemberID: in.Has("memberID"), + hasHandle: in.Has("handle"), + hasName: in.Has("name"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasArchived: in.Has("archived"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return h.each(ctx, args) + }, + } +} + +type ( + rolesCreateArgs struct { + hasRole bool + Role *types.Role + } + + rolesCreateResults struct { + Role *types.Role + } +) + +// Create function Creates new role +// +// expects implementation of create function: +// func (h rolesHandler) create(ctx context.Context, args *rolesCreateArgs) (results *rolesCreateResults, err error) { +// return +// } +func (h rolesHandler) Create() *atypes.Function { + return &atypes.Function{ + Ref: "rolesCreate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Creates new role", + }, + + Parameters: []*atypes.Param{ + { + Name: "role", + Types: []string{"Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "role", + Types: []string{"Role"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesCreateArgs{ + hasRole: in.Has("role"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *rolesCreateResults + if results, err = h.create(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Role (*types.Role) to Role + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Role").Cast(results.Role); err != nil { + return + } else if err = expr.Assign(out, "role", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + rolesUpdateArgs struct { + hasRole bool + Role *types.Role + } + + rolesUpdateResults struct { + Role *types.Role + } +) + +// Update function Updates exiting role +// +// expects implementation of update function: +// func (h rolesHandler) update(ctx context.Context, args *rolesUpdateArgs) (results *rolesUpdateResults, err error) { +// return +// } +func (h rolesHandler) Update() *atypes.Function { + return &atypes.Function{ + Ref: "rolesUpdate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Updates exiting role", + }, + + Parameters: []*atypes.Param{ + { + Name: "role", + Types: []string{"Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "role", + Types: []string{"Role"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesUpdateArgs{ + hasRole: in.Has("role"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *rolesUpdateResults + if results, err = h.update(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Role (*types.Role) to Role + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Role").Cast(results.Role); err != nil { + return + } else if err = expr.Assign(out, "role", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + rolesDeleteArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } +) + +func (a rolesDeleteArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Delete function Deletes the role +// +// expects implementation of delete function: +// func (h rolesHandler) delete(ctx context.Context, args *rolesDeleteArgs) (err error) { +// return +// } +func (h rolesHandler) Delete() *atypes.Function { + return &atypes.Function{ + Ref: "rolesDelete", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Deletes the role", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesDeleteArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + return out, h.delete(ctx, args) + }, + } +} + +type ( + rolesRecoverArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } +) + +func (a rolesRecoverArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Recover function Recovers deleted role +// +// expects implementation of recover function: +// func (h rolesHandler) recover(ctx context.Context, args *rolesRecoverArgs) (err error) { +// return +// } +func (h rolesHandler) Recover() *atypes.Function { + return &atypes.Function{ + Ref: "rolesRecover", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Recovers deleted role", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesRecoverArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + return out, h.recover(ctx, args) + }, + } +} + +type ( + rolesArchiveArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } +) + +func (a rolesArchiveArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Archive function Archives the role +// +// expects implementation of archive function: +// func (h rolesHandler) archive(ctx context.Context, args *rolesArchiveArgs) (err error) { +// return +// } +func (h rolesHandler) Archive() *atypes.Function { + return &atypes.Function{ + Ref: "rolesArchive", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Archives the role", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesArchiveArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + return out, h.archive(ctx, args) + }, + } +} + +type ( + rolesUnarchiveArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } +) + +func (a rolesUnarchiveArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// Unarchive function Unarchives the role +// +// expects implementation of unarchive function: +// func (h rolesHandler) unarchive(ctx context.Context, args *rolesUnarchiveArgs) (err error) { +// return +// } +func (h rolesHandler) Unarchive() *atypes.Function { + return &atypes.Function{ + Ref: "rolesUnarchive", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Unarchives the role", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesUnarchiveArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + return out, h.unarchive(ctx, args) + }, + } +} diff --git a/system/automation/roles_handler.go b/system/automation/roles_handler.go new file mode 100644 index 000000000..80eed877c --- /dev/null +++ b/system/automation/roles_handler.go @@ -0,0 +1,225 @@ +package automation + +import ( + "context" + "fmt" + . "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + roleService interface { + FindByID(ctx context.Context, roleID uint64) (*types.Role, error) + FindByHandle(ctx context.Context, handle string) (*types.Role, error) + Find(ctx context.Context, filter types.RoleFilter) (set types.RoleSet, f types.RoleFilter, err error) + + Create(ctx context.Context, role *types.Role) (*types.Role, error) + Update(ctx context.Context, role *types.Role) (*types.Role, error) + + Delete(ctx context.Context, id uint64) error + Archive(ctx context.Context, id uint64) error + Unarchive(ctx context.Context, id uint64) error + Undelete(ctx context.Context, id uint64) error + } + + rolesHandler struct { + reg rolesHandlerRegistry + rSvc roleService + } + + roleSetIterator struct { + ptr int + set types.RoleSet + filter types.RoleFilter + } + + roleLookup interface { + GetLookup() (bool, uint64, string, *types.Role) + } +) + +func RolesHandler(reg rolesHandlerRegistry, rSvc roleService) *rolesHandler { + h := &rolesHandler{ + reg: reg, + rSvc: rSvc, + } + + h.register() + return h +} + +func (h rolesHandler) lookup(ctx context.Context, args *rolesLookupArgs) (results *rolesLookupResults, err error) { + results = &rolesLookupResults{} + results.Role, err = lookupRole(ctx, h.rSvc, args) + return +} + +func (h rolesHandler) search(ctx context.Context, args *rolesSearchArgs) (results *rolesSearchResults, err error) { + results = &rolesSearchResults{} + + var ( + f = types.RoleFilter{ + Query: args.Query, + MemberID: args.MemberID, + Name: args.Name, + Handle: args.Handle, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + Archived: filter.State(args.Archived), + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + results.Roles, _, err = h.rSvc.Find(ctx, f) + return +} + +func (h rolesHandler) each(ctx context.Context, args *rolesEachArgs) (out wfexec.IteratorHandler, err error) { + var ( + i = &roleSetIterator{} + f = types.RoleFilter{ + Query: args.Query, + MemberID: args.MemberID, + Name: args.Name, + Handle: args.Handle, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + Archived: filter.State(args.Archived), + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + i.set, i.filter, err = h.rSvc.Find(ctx, f) + return i, err +} + +func (h rolesHandler) create(ctx context.Context, args *rolesCreateArgs) (results *rolesCreateResults, err error) { + results = &rolesCreateResults{} + results.Role, err = h.rSvc.Create(ctx, args.Role) + return +} + +func (h rolesHandler) update(ctx context.Context, args *rolesUpdateArgs) (results *rolesUpdateResults, err error) { + results = &rolesUpdateResults{} + results.Role, err = h.rSvc.Update(ctx, args.Role) + return +} + +func (h rolesHandler) delete(ctx context.Context, args *rolesDeleteArgs) error { + if id, err := getRoleID(ctx, h.rSvc, args); err != nil { + return err + } else { + return h.rSvc.Delete(ctx, id) + } +} + +func (h rolesHandler) recover(ctx context.Context, args *rolesRecoverArgs) error { + if id, err := getRoleID(ctx, h.rSvc, args); err != nil { + return err + } else { + return h.rSvc.Undelete(ctx, id) + } +} + +func (h rolesHandler) archive(ctx context.Context, args *rolesArchiveArgs) error { + if id, err := getRoleID(ctx, h.rSvc, args); err != nil { + return err + } else { + return h.rSvc.Archive(ctx, id) + } +} + +func (h rolesHandler) unarchive(ctx context.Context, args *rolesUnarchiveArgs) error { + if id, err := getRoleID(ctx, h.rSvc, args); err != nil { + return err + } else { + return h.rSvc.Unarchive(ctx, id) + } +} + +func getRoleID(ctx context.Context, svc roleService, args roleLookup) (uint64, error) { + _, ID, _, _ := args.GetLookup() + + if ID > 0 { + return ID, nil + } + + role, err := lookupRole(ctx, svc, args) + if err != nil { + return 0, err + } + + return role.ID, nil + +} + +func lookupRole(ctx context.Context, svc roleService, args roleLookup) (*types.Role, error) { + _, ID, handle, role := args.GetLookup() + + switch { + case role != nil: + return role, nil + case ID > 0: + return svc.FindByID(ctx, ID) + case len(handle) > 0: + return svc.FindByHandle(ctx, handle) + } + + return nil, fmt.Errorf("empty lookup params") +} + +func (i *roleSetIterator) More(context.Context, *Vars) (bool, error) { + return i.ptr < len(i.set), nil +} + +func (i *roleSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil } + +func (i *roleSetIterator) Next(context.Context, *Vars) (*Vars, error) { + out := RVars{ + "role": Must(NewRole(i.set[i.ptr])), + "total": Must(NewUnsignedInteger(i.filter.Total)), + } + + i.ptr++ + return out.Vars(), nil +} diff --git a/system/automation/roles_handler.yaml b/system/automation/roles_handler.yaml new file mode 100644 index 000000000..a297792f9 --- /dev/null +++ b/system/automation/roles_handler.yaml @@ -0,0 +1,135 @@ +imports: + - github.com/cortezaproject/corteza-server/system/types + +params: + lookup: &lookup + required: true + types: + - { wf: ID, } + - { wf: Handle, } + - { wf: Role, suffix: res } + + role: &role + types: + - { wf: Role } + + rvRole: &rvRole + wf: Role + + + rvTotal: &rvTotal + wf: UnsignedInteger + + rvPageCursor: &rvPageCursor + wf: String + + filterParams: &filterParams + query: + types: + - { wf: String } + memberID: + types: + - { wf: ID } + handle: + types: + - { wf: String } + name: + types: + - { wf: String } + labels: + types: + - { wf: KV } + deleted: + types: + - { wf: UnsignedInteger } + archived: + types: + - { wf: UnsignedInteger } + sort: + types: + - { wf: String } + limit: + types: + - { wf: UnsignedInteger } + incTotal: + types: + - { wf: Boolean } + incPageNavigation: + types: + - { wf: Boolean } + pageCursor: + types: + - { wf: String } + +functions: + lookup: + meta: + short: Looks-up for role by ID + params: + lookup: *lookup + results: + role: *rvRole + + search: + meta: + short: Searches for roles and returns them + params: *filterParams + results: + roles: + <<: *rvRole + isArray: true + total: *rvTotal + pageCursor: *rvPageCursor + + each: + kind: iterator + meta: + short: Searches for roles and iterates over results + params: *filterParams + results: + role: *rvRole + total: *rvTotal + + create: + meta: + short: Creates new role + params: + role: + <<: *role + required: true + results: + role: *rvRole + + update: + meta: + short: Updates exiting role + params: + role: + <<: *role + required: true + results: + role: *rvRole + + delete: + meta: + short: Deletes the role + params: + lookup: *lookup + + recover: + meta: + short: Recovers deleted role + params: + lookup: *lookup + + archive: + meta: + short: Archives the role + params: + lookup: *lookup + + unarchive: + meta: + short: Unarchives the role + params: + lookup: *lookup diff --git a/system/automation/users_handler.gen.go b/system/automation/users_handler.gen.go new file mode 100644 index 000000000..3c57c582e --- /dev/null +++ b/system/automation/users_handler.gen.go @@ -0,0 +1,903 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// system/automation/users_handler.yaml + +import ( + "context" + atypes "github.com/cortezaproject/corteza-server/automation/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" +) + +var _ wfexec.ExecResponse + +type ( + usersHandlerRegistry interface { + AddFunctions(ff ...*atypes.Function) + Type(ref string) expr.Type + } +) + +func (h usersHandler) register() { + h.reg.AddFunctions( + h.Lookup(), + h.Search(), + h.Each(), + h.Create(), + h.Update(), + h.Delete(), + h.Recover(), + h.Suspend(), + h.Unsuspend(), + ) +} + +type ( + usersLookupArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } + + usersLookupResults struct { + User *types.User + } +) + +func (a usersLookupArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// Lookup function Looks-up for user by ID +// +// expects implementation of lookup function: +// func (h usersHandler) lookup(ctx context.Context, args *usersLookupArgs) (results *usersLookupResults, err error) { +// return +// } +func (h usersHandler) Lookup() *atypes.Function { + return &atypes.Function{ + Ref: "usersLookup", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Looks-up for user by ID", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "user", + Types: []string{"User"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersLookupArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + var results *usersLookupResults + if results, err = h.lookup(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.User (*types.User) to User + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("User").Cast(results.User); err != nil { + return + } else if err = expr.Assign(out, "user", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + usersSearchArgs struct { + hasQuery bool + Query string + + hasEmail bool + Email string + + hasHandle bool + Handle string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasSuspended bool + Suspended uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + usersSearchResults struct { + Users []*types.User + Total uint64 + PageCursor string + } +) + +// Search function Searches for users and returns them +// +// expects implementation of search function: +// func (h usersHandler) search(ctx context.Context, args *usersSearchArgs) (results *usersSearchResults, err error) { +// return +// } +func (h usersHandler) Search() *atypes.Function { + return &atypes.Function{ + Ref: "usersSearch", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Searches for users and returns them", + }, + + Parameters: []*atypes.Param{ + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "email", + Types: []string{"String"}, + }, + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "suspended", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "users", + Types: []string{"User"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersSearchArgs{ + hasQuery: in.Has("query"), + hasEmail: in.Has("email"), + hasHandle: in.Has("handle"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasSuspended: in.Has("suspended"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *usersSearchResults + if results, err = h.search(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Users (*types.User) to Array (of User) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Users)) + ) + + for i := range results.Users { + if tarr[i], err = h.reg.Type("User").Cast(results.Users[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "users", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + { + // converting results.PageCursor (string) to String + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("String").Cast(results.PageCursor); err != nil { + return + } else if err = expr.Assign(out, "pageCursor", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + usersEachArgs struct { + hasQuery bool + Query string + + hasEmail bool + Email string + + hasHandle bool + Handle string + + hasLabels bool + Labels map[string]string + + hasDeleted bool + Deleted uint64 + + hasSuspended bool + Suspended uint64 + + hasSort bool + Sort string + + hasLimit bool + Limit uint64 + + hasIncTotal bool + IncTotal bool + + hasIncPageNavigation bool + IncPageNavigation bool + + hasPageCursor bool + PageCursor string + } + + usersEachResults struct { + User *types.User + Total uint64 + } +) + +// Each function Searches for users and iterates over results +// +// expects implementation of each function: +// func (h usersHandler) each(ctx context.Context, args *usersEachArgs) (results *usersEachResults, err error) { +// return +// } +func (h usersHandler) Each() *atypes.Function { + return &atypes.Function{ + Ref: "usersEach", + Kind: "iterator", + Meta: &atypes.FunctionMeta{ + Short: "Searches for users and iterates over results", + }, + + Parameters: []*atypes.Param{ + { + Name: "query", + Types: []string{"String"}, + }, + { + Name: "email", + Types: []string{"String"}, + }, + { + Name: "handle", + Types: []string{"String"}, + }, + { + Name: "labels", + Types: []string{"KV"}, + }, + { + Name: "deleted", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "suspended", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "sort", + Types: []string{"String"}, + }, + { + Name: "limit", + Types: []string{"UnsignedInteger"}, + }, + { + Name: "incTotal", + Types: []string{"Boolean"}, + }, + { + Name: "incPageNavigation", + Types: []string{"Boolean"}, + }, + { + Name: "pageCursor", + Types: []string{"String"}, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "user", + Types: []string{"User"}, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) { + var ( + args = &usersEachArgs{ + hasQuery: in.Has("query"), + hasEmail: in.Has("email"), + hasHandle: in.Has("handle"), + hasLabels: in.Has("labels"), + hasDeleted: in.Has("deleted"), + hasSuspended: in.Has("suspended"), + hasSort: in.Has("sort"), + hasLimit: in.Has("limit"), + hasIncTotal: in.Has("incTotal"), + hasIncPageNavigation: in.Has("incPageNavigation"), + hasPageCursor: in.Has("pageCursor"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return h.each(ctx, args) + }, + } +} + +type ( + usersCreateArgs struct { + hasUser bool + User *types.User + } + + usersCreateResults struct { + User *types.User + } +) + +// Create function Creates new user +// +// expects implementation of create function: +// func (h usersHandler) create(ctx context.Context, args *usersCreateArgs) (results *usersCreateResults, err error) { +// return +// } +func (h usersHandler) Create() *atypes.Function { + return &atypes.Function{ + Ref: "usersCreate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Creates new user", + }, + + Parameters: []*atypes.Param{ + { + Name: "user", + Types: []string{"User"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "user", + Types: []string{"User"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersCreateArgs{ + hasUser: in.Has("user"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *usersCreateResults + if results, err = h.create(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.User (*types.User) to User + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("User").Cast(results.User); err != nil { + return + } else if err = expr.Assign(out, "user", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + usersUpdateArgs struct { + hasUser bool + User *types.User + } + + usersUpdateResults struct { + User *types.User + } +) + +// Update function Updates exiting user +// +// expects implementation of update function: +// func (h usersHandler) update(ctx context.Context, args *usersUpdateArgs) (results *usersUpdateResults, err error) { +// return +// } +func (h usersHandler) Update() *atypes.Function { + return &atypes.Function{ + Ref: "usersUpdate", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Updates exiting user", + }, + + Parameters: []*atypes.Param{ + { + Name: "user", + Types: []string{"User"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "user", + Types: []string{"User"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersUpdateArgs{ + hasUser: in.Has("user"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + var results *usersUpdateResults + if results, err = h.update(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.User (*types.User) to User + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("User").Cast(results.User); err != nil { + return + } else if err = expr.Assign(out, "user", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + usersDeleteArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } +) + +func (a usersDeleteArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// Delete function Deletes user +// +// expects implementation of delete function: +// func (h usersHandler) delete(ctx context.Context, args *usersDeleteArgs) (err error) { +// return +// } +func (h usersHandler) Delete() *atypes.Function { + return &atypes.Function{ + Ref: "usersDelete", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Deletes user", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersDeleteArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + return out, h.delete(ctx, args) + }, + } +} + +type ( + usersRecoverArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } +) + +func (a usersRecoverArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// Recover function Recovers deleted user +// +// expects implementation of recover function: +// func (h usersHandler) recover(ctx context.Context, args *usersRecoverArgs) (err error) { +// return +// } +func (h usersHandler) Recover() *atypes.Function { + return &atypes.Function{ + Ref: "usersRecover", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Recovers deleted user", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersRecoverArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + return out, h.recover(ctx, args) + }, + } +} + +type ( + usersSuspendArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } +) + +func (a usersSuspendArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// Suspend function Suspends user +// +// expects implementation of suspend function: +// func (h usersHandler) suspend(ctx context.Context, args *usersSuspendArgs) (err error) { +// return +// } +func (h usersHandler) Suspend() *atypes.Function { + return &atypes.Function{ + Ref: "usersSuspend", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Suspends user", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersSuspendArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + return out, h.suspend(ctx, args) + }, + } +} + +type ( + usersUnsuspendArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } +) + +func (a usersUnsuspendArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// Unsuspend function Unsuspends user +// +// expects implementation of unsuspend function: +// func (h usersHandler) unsuspend(ctx context.Context, args *usersUnsuspendArgs) (err error) { +// return +// } +func (h usersHandler) Unsuspend() *atypes.Function { + return &atypes.Function{ + Ref: "usersUnsuspend", + Kind: "function", + Meta: &atypes.FunctionMeta{ + Short: "Unsuspends user", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersUnsuspendArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + return out, h.unsuspend(ctx, args) + }, + } +} diff --git a/system/automation/users_handler.go b/system/automation/users_handler.go new file mode 100644 index 000000000..ca84f161e --- /dev/null +++ b/system/automation/users_handler.go @@ -0,0 +1,226 @@ +package automation + +import ( + "context" + "fmt" + . "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + userService interface { + FindByID(ctx context.Context, userID uint64) (*types.User, error) + FindByHandle(ctx context.Context, handle string) (*types.User, error) + FindByEmail(ctx context.Context, email string) (*types.User, error) + Find(ctx context.Context, filter types.UserFilter) (set types.UserSet, f types.UserFilter, err error) + + Create(ctx context.Context, user *types.User) (*types.User, error) + Update(ctx context.Context, user *types.User) (*types.User, error) + + Delete(ctx context.Context, id uint64) error + Suspend(ctx context.Context, id uint64) error + Unsuspend(ctx context.Context, id uint64) error + Undelete(ctx context.Context, id uint64) error + } + + usersHandler struct { + reg usersHandlerRegistry + uSvc userService + } + + userSetIterator struct { + ptr int + set types.UserSet + filter types.UserFilter + } + + userLookup interface { + GetLookup() (bool, uint64, string, string, *types.User) + } +) + +func UsersHandler(reg usersHandlerRegistry, uSvc userService) *usersHandler { + h := &usersHandler{ + reg: reg, + uSvc: uSvc, + } + + h.register() + return h +} + +func (h usersHandler) lookup(ctx context.Context, args *usersLookupArgs) (results *usersLookupResults, err error) { + results = &usersLookupResults{} + results.User, err = lookupUser(ctx, h.uSvc, args) + return +} + +func (h usersHandler) search(ctx context.Context, args *usersSearchArgs) (results *usersSearchResults, err error) { + results = &usersSearchResults{} + + var ( + f = types.UserFilter{ + Query: args.Query, + Email: args.Email, + Handle: args.Handle, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + Suspended: filter.State(args.Suspended), + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + results.Users, _, err = h.uSvc.Find(ctx, f) + return +} + +func (h usersHandler) each(ctx context.Context, args *usersEachArgs) (out wfexec.IteratorHandler, err error) { + var ( + i = &userSetIterator{} + f = types.UserFilter{ + Query: args.Query, + Email: args.Email, + Handle: args.Handle, + Labels: args.Labels, + Deleted: filter.State(args.Deleted), + Suspended: filter.State(args.Suspended), + } + ) + + if args.hasSort { + if err = f.Sort.Set(args.Sort); err != nil { + return + } + } + + if args.hasPageCursor { + if err = f.PageCursor.Decode(args.PageCursor); err != nil { + return + } + } + + if args.hasLabels { + f.Labels = args.Labels + } + + if args.hasLimit { + f.Limit = uint(args.Limit) + } + + i.set, i.filter, err = h.uSvc.Find(ctx, f) + return i, err +} + +func (h usersHandler) create(ctx context.Context, args *usersCreateArgs) (results *usersCreateResults, err error) { + results = &usersCreateResults{} + results.User, err = h.uSvc.Create(ctx, args.User) + return +} + +func (h usersHandler) update(ctx context.Context, args *usersUpdateArgs) (results *usersUpdateResults, err error) { + results = &usersUpdateResults{} + results.User, err = h.uSvc.Update(ctx, args.User) + return +} + +func (h usersHandler) delete(ctx context.Context, args *usersDeleteArgs) error { + if id, err := getUserID(ctx, h.uSvc, args); err != nil { + return err + } else { + return h.uSvc.Delete(ctx, id) + } +} + +func (h usersHandler) recover(ctx context.Context, args *usersRecoverArgs) error { + if id, err := getUserID(ctx, h.uSvc, args); err != nil { + return err + } else { + return h.uSvc.Undelete(ctx, id) + } +} + +func (h usersHandler) suspend(ctx context.Context, args *usersSuspendArgs) error { + if id, err := getUserID(ctx, h.uSvc, args); err != nil { + return err + } else { + return h.uSvc.Suspend(ctx, id) + } +} + +func (h usersHandler) unsuspend(ctx context.Context, args *usersUnsuspendArgs) error { + if id, err := getUserID(ctx, h.uSvc, args); err != nil { + return err + } else { + return h.uSvc.Unsuspend(ctx, id) + } +} + +func getUserID(ctx context.Context, svc userService, args userLookup) (uint64, error) { + _, ID, _, _, _ := args.GetLookup() + + if ID > 0 { + return ID, nil + } + + user, err := lookupUser(ctx, svc, args) + if err != nil { + return 0, err + } + + return user.ID, nil + +} + +func lookupUser(ctx context.Context, svc userService, args userLookup) (*types.User, error) { + _, ID, handle, email, user := args.GetLookup() + + switch { + case user != nil: + return user, nil + case ID > 0: + return svc.FindByID(ctx, ID) + case len(email) > 0: + return svc.FindByEmail(ctx, email) + case len(handle) > 0: + return svc.FindByHandle(ctx, handle) + } + + return nil, fmt.Errorf("empty lookup params") +} + +func (i *userSetIterator) More(context.Context, *Vars) (bool, error) { + return i.ptr < len(i.set), nil +} + +func (i *userSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil } + +func (i *userSetIterator) Next(context.Context, *Vars) (*Vars, error) { + out := RVars{ + "user": Must(NewUser(i.set[i.ptr])), + "total": Must(NewUnsignedInteger(i.filter.Total)), + } + + i.ptr++ + return out.Vars(), nil +} diff --git a/system/automation/users_handler.yaml b/system/automation/users_handler.yaml new file mode 100644 index 000000000..a38147bb2 --- /dev/null +++ b/system/automation/users_handler.yaml @@ -0,0 +1,132 @@ +imports: + - github.com/cortezaproject/corteza-server/system/types + +snippets: + lookup: &lookup + required: true + types: + - { wf: ID } + - { wf: Handle } + - { wf: String, suffix: email } + - { wf: User, suffix: res } + + user: &user + types: + - { wf: User } + + rvUser: &rvUser + wf: User + + rvTotal: &rvTotal + wf: UnsignedInteger + + rvPageCursor: &rvPageCursor + wf: String + + filterParams: &filterParams + query: + types: + - { wf: String } + email: + types: + - { wf: String } + handle: + types: + - { wf: String } + labels: + types: + - { wf: KV } + deleted: + types: + - { wf: UnsignedInteger } + suspended: + types: + - { wf: UnsignedInteger } + sort: + types: + - { wf: String } + limit: + types: + - { wf: UnsignedInteger } + incTotal: + types: + - { wf: Boolean } + incPageNavigation: + types: + - { wf: Boolean } + pageCursor: + types: + - { wf: String } + +functions: + lookup: + meta: + short: Looks-up for user by ID + params: + lookup: *lookup + results: + user: *rvUser + + search: + meta: + short: Searches for users and returns them + params: *filterParams + results: + users: + <<: *rvUser + isArray: true + total: *rvTotal + pageCursor: *rvPageCursor + + each: + kind: iterator + meta: + short: Searches for users and iterates over results + params: *filterParams + results: + user: *rvUser + total: *rvTotal + + create: + meta: + short: Creates new user + params: + user: + <<: *user + required: true + results: + user: *rvUser + + update: + meta: + short: Updates exiting user + params: + user: + <<: *user + required: true + results: + user: *rvUser + + delete: + meta: + short: Deletes user + params: + lookup: *lookup + + recover: + meta: + short: Recovers deleted user + params: + lookup: *lookup + + suspend: + meta: + short: Suspends user + params: + lookup: *lookup + + unsuspend: + meta: + short: Unsuspends user + params: + lookup: *lookup diff --git a/system/service/service.go b/system/service/service.go index 76d5a51e4..190603637 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -3,23 +3,23 @@ package service import ( "context" "errors" - "time" - - "github.com/cortezaproject/corteza-server/pkg/logger" - + automationService "github.com/cortezaproject/corteza-server/automation/service" "github.com/cortezaproject/corteza-server/pkg/actionlog" intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/eventbus" "github.com/cortezaproject/corteza-server/pkg/healthcheck" "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/pkg/logger" "github.com/cortezaproject/corteza-server/pkg/objstore" "github.com/cortezaproject/corteza-server/pkg/objstore/minio" "github.com/cortezaproject/corteza-server/pkg/objstore/plain" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/pkg/rbac" "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/automation" "github.com/cortezaproject/corteza-server/system/types" "go.uber.org/zap" + "time" ) type ( @@ -181,6 +181,23 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) DefaultAttachment = Attachment(DefaultObjectStore) DefaultRenderer = Renderer(c.Template) + automationService.DefaultUser = DefaultUser + + automationService.Registry().AddTypes( + automation.User{}, + automation.Role{}, + ) + + automation.UsersHandler( + automationService.Registry(), + DefaultUser, + ) + + automation.RolesHandler( + automationService.Registry(), + DefaultRole, + ) + return } diff --git a/system/service/user.go b/system/service/user.go index 81444c851..96bb75d53 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -91,7 +91,7 @@ type ( ) func User(ctx context.Context) UserService { - return (&user{ + return &user{ eventbus: eventbus.Service(), ac: DefaultAccessControl, settings: CurrentSettings, @@ -102,7 +102,7 @@ func User(ctx context.Context) UserService { actionlog: DefaultActionlog, subscription: CurrentSubscription, - }) + } } func (svc user) FindByID(ctx context.Context, userID uint64) (u *types.User, err error) {