3
0

Added exposed module service, added details and delete endpoints, removed module service

This commit is contained in:
Peter Grlica
2020-09-17 15:26:45 +02:00
parent 2ff393edb7
commit 2cbc5605bf
23 changed files with 2251 additions and 91 deletions

View File

@@ -78,18 +78,37 @@ endpoints:
type: string
required: true
title: Auth token of the origin node
- title: Modules
description: Federation module definitions
entrypoint: module
path: "/structure/module"
- title: Federation sync structure
description: Federation structure sync
entrypoint: syncStructure
path: "/nodes/{nodeID}/modules/{moduleID}"
authentication: []
apis:
- name: read
- name: readExposed
method: GET
title: Read federated module
path: "/{moduleID}"
title: Exposed settings for module
path: "/exposed"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
- name: remove
method: DELETE
title: Remove from federation
path: "/exposed"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true

View File

@@ -58,6 +58,6 @@ func NewModule(h ModuleAPI) *Module {
func (h Module) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/structure/module/{moduleID}", h.Read)
r.Get("/exposed/modules/{moduleID}", h.Read)
})
}

View File

@@ -0,0 +1,86 @@
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/go-chi/chi"
"github.com/titpetric/factory/resputil"
"net/http"
"github.com/cortezaproject/corteza-server/federation/rest/request"
"github.com/cortezaproject/corteza-server/pkg/logger"
)
type (
// Internal API interface
SyncStructureAPI interface {
ReadExposed(context.Context, *request.SyncStructureReadExposed) (interface{}, error)
Remove(context.Context, *request.SyncStructureRemove) (interface{}, error)
}
// HTTP API interface
SyncStructure struct {
ReadExposed func(http.ResponseWriter, *http.Request)
Remove func(http.ResponseWriter, *http.Request)
}
)
func NewSyncStructure(h SyncStructureAPI) *SyncStructure {
return &SyncStructure{
ReadExposed: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncStructureReadExposed()
if err := params.Fill(r); err != nil {
logger.LogParamError("SyncStructure.ReadExposed", r, err)
resputil.JSON(w, err)
return
}
value, err := h.ReadExposed(r.Context(), params)
if err != nil {
logger.LogControllerError("SyncStructure.ReadExposed", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("SyncStructure.ReadExposed", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
Remove: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncStructureRemove()
if err := params.Fill(r); err != nil {
logger.LogParamError("SyncStructure.Remove", r, err)
resputil.JSON(w, err)
return
}
value, err := h.Remove(r.Context(), params)
if err != nil {
logger.LogControllerError("SyncStructure.Remove", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("SyncStructure.Remove", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
}
}
func (h SyncStructure) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/nodes/{nodeID}/modules/{moduleID}/exposed", h.ReadExposed)
r.Delete("/nodes/{nodeID}/modules/{moduleID}/exposed", h.Remove)
})
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/cortezaproject/corteza-server/federation/rest/request"
"github.com/cortezaproject/corteza-server/federation/service"
)
type (
@@ -17,5 +18,10 @@ func (Module) New() *Module {
func (ctrl Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) {
// use filtering and call structure sync service
return &struct{}{}, nil
s := service.ExposedModule()
// find the correct node (from request) and use it here
mod, err := s.FindByID(context.Background(), 0, r.GetModuleID())
return mod, err
}

View File

@@ -0,0 +1,169 @@
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"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
)
type (
// Internal API interface
SyncStructureReadExposed struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
}
SyncStructureRemove struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
}
)
// NewSyncStructureReadExposed request
func NewSyncStructureReadExposed() *SyncStructureReadExposed {
return &SyncStructureReadExposed{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposed) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposed) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposed) GetModuleID() uint64 {
return r.ModuleID
}
// Fill processes request and fills internal variables
func (r *SyncStructureReadExposed) Fill(req *http.Request) (err error) {
if 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)
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
val = chi.URLParam(req, "moduleID")
r.ModuleID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewSyncStructureRemove request
func NewSyncStructureRemove() *SyncStructureRemove {
return &SyncStructureRemove{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureRemove) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureRemove) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureRemove) GetModuleID() uint64 {
return r.ModuleID
}
// Fill processes request and fills internal variables
func (r *SyncStructureRemove) Fill(req *http.Request) (err error) {
if 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)
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
val = chi.URLParam(req, "moduleID")
r.ModuleID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -10,10 +10,11 @@ import (
func MountRoutes(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewPairRequest(NodePairRequest{}.New()).MountRoutes(r)
// temporary because of acl
handlers.NewModule((Module{}.New())).MountRoutes(r)
handlers.NewSyncStructure((SyncStructure{}.New())).MountRoutes(r)
})
var (
module = Module{}.New()
)
// Protect all _private_ routes
r.Group(func(r chi.Router) {
@@ -22,6 +23,5 @@ func MountRoutes(r chi.Router) {
handlers.NewIdentity(NodeIdentity{}.New()).MountRoutes(r)
handlers.NewPair(NodePair{}.New()).MountRoutes(r)
handlers.NewModule(module).MountRoutes(r)
})
}

View File

@@ -0,0 +1,24 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza-server/federation/rest/request"
"github.com/cortezaproject/corteza-server/federation/service"
)
type (
SyncStructure struct{}
)
func (SyncStructure) New() *SyncStructure {
return &SyncStructure{}
}
func (ctrl SyncStructure) Remove(ctx context.Context, r *request.SyncStructureRemove) (interface{}, error) {
return nil, (service.ExposedModule()).DeleteByID(ctx, r.NodeID, r.ModuleID)
}
func (ctrl SyncStructure) ReadExposed(ctx context.Context, r *request.SyncStructureReadExposed) (interface{}, error) {
return (service.ExposedModule()).FindByID(context.Background(), r.GetNodeID(), r.GetModuleID())
}

View File

@@ -0,0 +1,847 @@
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:
// federation/service/exposed_module_actions.yaml
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
)
type (
moduleActionProps struct {
module *types.ExposedModule
changed *types.ExposedModule
filter *types.ExposedModuleFilter
}
moduleAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *moduleActionProps
}
moduleError struct {
timestamp time.Time
error string
resource string
action string
message string
log string
severity actionlog.Severity
wrap error
props *moduleActionProps
}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setModule updates moduleActionProps's module
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *moduleActionProps) setModule(module *types.ExposedModule) *moduleActionProps {
p.module = module
return p
}
// setChanged updates moduleActionProps's changed
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *moduleActionProps) setChanged(changed *types.ExposedModule) *moduleActionProps {
p.changed = changed
return p
}
// setFilter updates moduleActionProps's filter
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *moduleActionProps) setFilter(filter *types.ExposedModuleFilter) *moduleActionProps {
p.filter = filter
return p
}
// serialize converts moduleActionProps to actionlog.Meta
//
// This function is auto-generated.
//
func (p moduleActionProps) serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.module != nil {
m.Set("module.ID", p.module.ID, true)
m.Set("module.ComposeModuleID", p.module.ComposeModuleID, true)
}
if p.changed != nil {
m.Set("changed.ID", p.changed.ID, true)
m.Set("changed.ComposeModuleID", p.changed.ComposeModuleID, true)
}
if p.filter != nil {
m.Set("filter.query", p.filter.Query, true)
m.Set("filter.sort", p.filter.Sort, true)
m.Set("filter.limit", p.filter.Limit, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
//
func (p moduleActionProps) tr(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 {
for {
// Unwrap errors
ue := errors.Unwrap(err)
if ue == nil {
break
}
err = ue
}
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.module != nil {
// replacement for "{module}" (in order how fields are defined)
pairs = append(
pairs,
"{module}",
fns(
p.module.ID,
p.module.ComposeModuleID,
),
)
pairs = append(pairs, "{module.ID}", fns(p.module.ID))
pairs = append(pairs, "{module.ComposeModuleID}", fns(p.module.ComposeModuleID))
}
if p.changed != nil {
// replacement for "{changed}" (in order how fields are defined)
pairs = append(
pairs,
"{changed}",
fns(
p.changed.ID,
p.changed.ComposeModuleID,
),
)
pairs = append(pairs, "{changed.ID}", fns(p.changed.ID))
pairs = append(pairs, "{changed.ComposeModuleID}", fns(p.changed.ComposeModuleID))
}
if p.filter != nil {
// replacement for "{filter}" (in order how fields are defined)
pairs = append(
pairs,
"{filter}",
fns(
p.filter.Query,
p.filter.Sort,
p.filter.Limit,
),
)
pairs = append(pairs, "{filter.query}", fns(p.filter.Query))
pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort))
pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
//
func (a *moduleAction) String() string {
var props = &moduleActionProps{}
if a.props != nil {
props = a.props
}
return props.tr(a.log, nil)
}
func (e *moduleAction) LoggableAction() *actionlog.Action {
return &actionlog.Action{
Timestamp: e.timestamp,
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error methods
// String returns loggable description as string
//
// It falls back to message if log is not set
//
// This function is auto-generated.
//
func (e *moduleError) String() string {
var props = &moduleActionProps{}
if e.props != nil {
props = e.props
}
if e.wrap != nil && !strings.Contains(e.log, "{err}") {
// Suffix error log with {err} to ensure
// we log the cause for this error
e.log += ": {err}"
}
return props.tr(e.log, e.wrap)
}
// Error satisfies
//
// This function is auto-generated.
//
func (e *moduleError) Error() string {
var props = &moduleActionProps{}
if e.props != nil {
props = e.props
}
return props.tr(e.message, e.wrap)
}
// Is fn for error equality check
//
// This function is auto-generated.
//
func (e *moduleError) Is(err error) bool {
t, ok := err.(*moduleError)
if !ok {
return false
}
return t.resource == e.resource && t.error == e.error
}
// Is fn for error equality check
//
// This function is auto-generated.
//
func (e *moduleError) IsGeneric() bool {
return e.error == "generic"
}
// Wrap wraps moduleError around another error
//
// This function is auto-generated.
//
func (e *moduleError) Wrap(err error) *moduleError {
e.wrap = err
return e
}
// Unwrap returns wrapped error
//
// This function is auto-generated.
//
func (e *moduleError) Unwrap() error {
return e.wrap
}
func (e *moduleError) LoggableAction() *actionlog.Action {
return &actionlog.Action{
Timestamp: e.timestamp,
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Error: e.Error(),
Meta: e.props.serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// ModuleActionSearch returns "federation:exposed_module.search" error
//
// This function is auto-generated.
//
func ModuleActionSearch(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "search",
log: "searched for modules",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleActionLookup returns "federation:exposed_module.lookup" error
//
// This function is auto-generated.
//
func ModuleActionLookup(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "lookup",
log: "looked-up for a {module}",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleActionCreate returns "federation:exposed_module.create" error
//
// This function is auto-generated.
//
func ModuleActionCreate(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "create",
log: "created {module}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleActionUpdate returns "federation:exposed_module.update" error
//
// This function is auto-generated.
//
func ModuleActionUpdate(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "update",
log: "updated {module}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleActionDelete returns "federation:exposed_module.delete" error
//
// This function is auto-generated.
//
func ModuleActionDelete(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "delete",
log: "deleted {module}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleActionUndelete returns "federation:exposed_module.undelete" error
//
// This function is auto-generated.
//
func ModuleActionUndelete(props ...*moduleActionProps) *moduleAction {
a := &moduleAction{
timestamp: time.Now(),
resource: "federation:exposed_module",
action: "undelete",
log: "undeleted {module}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// ModuleErrGeneric returns "federation:exposed_module.generic" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrGeneric(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "generic",
action: "error",
message: "failed to complete request due to internal error",
log: "{err}",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotFound returns "federation:exposed_module.notFound" audit event as actionlog.Warning
//
//
// This function is auto-generated.
//
func ModuleErrNotFound(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notFound",
action: "error",
message: "module does not exist",
log: "module does not exist",
severity: actionlog.Warning,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrInvalidID returns "federation:exposed_module.invalidID" audit event as actionlog.Warning
//
//
// This function is auto-generated.
//
func ModuleErrInvalidID(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "invalidID",
action: "error",
message: "invalid ID",
log: "invalid ID",
severity: actionlog.Warning,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrStaleData returns "federation:exposed_module.staleData" audit event as actionlog.Warning
//
//
// This function is auto-generated.
//
func ModuleErrStaleData(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "staleData",
action: "error",
message: "stale data",
log: "stale data",
severity: actionlog.Warning,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToRead returns "federation:exposed_module.notAllowedToRead" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToRead(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToRead",
action: "error",
message: "not allowed to read this module",
log: "could not read {module}; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToListModules returns "federation:exposed_module.notAllowedToListModules" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToListModules(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToListModules",
action: "error",
message: "not allowed to list modules",
log: "could not list modules; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToCreate returns "federation:exposed_module.notAllowedToCreate" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToCreate(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToCreate",
action: "error",
message: "not allowed to create modules",
log: "could not create modules; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToUpdate returns "federation:exposed_module.notAllowedToUpdate" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToUpdate(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToUpdate",
action: "error",
message: "not allowed to update this module",
log: "could not update {module}; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToDelete returns "federation:exposed_module.notAllowedToDelete" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToDelete(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToDelete",
action: "error",
message: "not allowed to delete this module",
log: "could not delete {module}; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// ModuleErrNotAllowedToUndelete returns "federation:exposed_module.notAllowedToUndelete" audit event as actionlog.Error
//
//
// This function is auto-generated.
//
func ModuleErrNotAllowedToUndelete(props ...*moduleActionProps) *moduleError {
var e = &moduleError{
timestamp: time.Now(),
resource: "federation:exposed_module",
error: "notAllowedToUndelete",
action: "error",
message: "not allowed to undelete this module",
log: "could not undelete {module}; insufficient permissions",
severity: actionlog.Error,
props: func() *moduleActionProps {
if len(props) > 0 {
return props[0]
}
return nil
}(),
}
if len(props) > 0 {
e.props = props[0]
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// context is used to enrich audit log entry with current user info, request ID, IP address...
// props are collected action/error properties
// action (optional) fn will be used to construct moduleAction struct from given props (and error)
// err is any error that occurred while action was happening
//
// Action has success and fail (error) state:
// - when recorded without an error (4th param), action is recorded as successful.
// - when an additional error is given (4th param), action is used to wrap
// the additional error
//
// This function is auto-generated.
//
func (svc module) recordAction(ctx context.Context, props *moduleActionProps, action func(...*moduleActionProps) *moduleAction, err error) error {
var (
ok bool
// Return error
retError *moduleError
// Recorder error
recError *moduleError
)
if err != nil {
if retError, ok = err.(*moduleError); !ok {
// got non-module error, wrap it with ModuleErrGeneric
retError = ModuleErrGeneric(props).Wrap(err)
if action != nil {
// copy action to returning and recording error
retError.action = action().action
}
// we'll use ModuleErrGeneric for recording too
// because it can hold more info
recError = retError
} else if retError != nil {
if action != nil {
// copy action to returning and recording error
retError.action = action().action
}
// start with copy of return error for recording
// this will be updated with tha root cause as we try and
// unwrap the error
recError = retError
// find the original recError for this error
// for the purpose of logging
var unwrappedError error = retError
for {
if unwrappedError = errors.Unwrap(unwrappedError); unwrappedError == nil {
// nothing wrapped
break
}
// update recError ONLY of wrapped error is of type moduleError
if unwrappedSinkError, ok := unwrappedError.(*moduleError); ok {
recError = unwrappedSinkError
}
}
if retError.props == nil {
// set props on returning error if empty
retError.props = props
}
if recError.props == nil {
// set props on recording error if empty
recError.props = props
}
}
}
if svc.actionlog != nil {
if retError != nil {
// failed action, log error
svc.actionlog.Record(ctx, recError)
} else if action != nil {
// successful
svc.actionlog.Record(ctx, action(props))
}
}
if err == nil {
// retError not an interface and that WILL (!!) cause issues
// with nil check (== nil) when it is not explicitly returned
return nil
}
return retError
}

View File

@@ -0,0 +1,82 @@
# List of loggable service actions
resource: federation:exposed_module
service: module
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: error
import:
- github.com/cortezaproject/corteza-server/federation/types
props:
- name: module
type: "*types.ExposedModule"
fields: [ ID, ComposeModuleID ]
- name: changed
type: "*types.ExposedModule"
fields: [ ID, ComposeModuleID ]
- name: filter
type: "*types.ExposedModuleFilter"
fields: [ query, sort, limit ]
actions:
- action: search
log: "searched for modules"
severity: info
- action: lookup
log: "looked-up for a {module}"
severity: info
- action: create
log: "created {module}"
- action: update
log: "updated {module}"
- action: delete
log: "deleted {module}"
- action: undelete
log: "undeleted {module}"
errors:
- error: notFound
message: "module does not exist"
severity: warning
- error: invalidID
message: "invalid ID"
severity: warning
- error: staleData
message: "stale data"
severity: warning
- error: notAllowedToRead
message: "not allowed to read this module"
log: "could not read {module}; insufficient permissions"
- error: notAllowedToListModules
message: "not allowed to list modules"
log: "could not list modules; insufficient permissions"
- error: notAllowedToCreate
message: "not allowed to create modules"
log: "could not create modules; insufficient permissions"
- error: notAllowedToUpdate
message: "not allowed to update this module"
log: "could not update {module}; insufficient permissions"
- error: notAllowedToDelete
message: "not allowed to delete this module"
log: "could not delete {module}; insufficient permissions"
- error: notAllowedToUndelete
message: "not allowed to undelete this module"
log: "could not undelete {module}; insufficient permissions"

View File

@@ -1,37 +0,0 @@
package service
import (
"context"
composeService "github.com/cortezaproject/corteza-server/compose/service"
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/store"
)
type (
module struct {
ctx context.Context
fetcher composeService.ModuleService
// actionlog actionlog.Recorder
// ac moduleAccessController
store store.Storer
}
ModuleService interface {
Find(ctx context.Context, filter types.ModuleFilter) (composeTypes.ModuleSet, error)
}
)
func Module() ModuleService {
return &module{
ctx: context.Background(),
// ac: DefaultAccessControl,
// eventbus: eventbus.Service(),
store: composeService.DefaultStore,
}
}
func (svc module) Find(ctx context.Context, filter types.ModuleFilter) (set composeTypes.ModuleSet, err error) {
return composeTypes.ModuleSet{}, nil
}

View File

@@ -1,41 +0,0 @@
package sync
import (
"context"
composeService "github.com/cortezaproject/corteza-server/compose/service"
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/federation/service"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/store"
)
type (
module struct {
ctx context.Context
compose composeService.ModuleService
federation service.ModuleService
store store.Storable
}
ModuleService interface {
FindForNode(ctx context.Context, filter types.ModuleFilter) (composeTypes.ModuleSet, error)
}
)
func Module() ModuleService {
return &module{
ctx: context.Background(),
store: composeService.DefaultNgStore,
compose: composeService.Module(),
federation: service.Module(),
}
}
func (svc module) FindForNode(ctx context.Context, filter types.ModuleFilter) (set composeTypes.ModuleSet, err error) {
// get all modules per-node
// feed the id's into the compose moduleservice
// get the data
// transform (but not here)
return composeTypes.ModuleSet{}, nil
}

View File

@@ -0,0 +1,143 @@
package service
import (
"context"
"strconv"
composeService "github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/store"
"github.com/davecgh/go-spew/spew"
)
type (
module struct {
ctx context.Context
compose composeService.ModuleService
store store.Storer
actionlog actionlog.Recorder
}
ExposedModuleService interface {
Find(ctx context.Context, filter types.ExposedModuleFilter) (types.ExposedModuleSet, error)
FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (*types.ExposedModule, error)
FindByAny(ctx context.Context, nodeID uint64, identifier interface{}) (*types.ExposedModule, error)
DeleteByID(ctx context.Context, nodeID, moduleID uint64) error
// Remove(ctx context.Context, filter types.ExposedModuleFilter) (err error)
}
moduleUpdateHandler func(ctx context.Context, ns *types.Node, c *types.ExposedModule) (bool, bool, error)
)
func ExposedModule() ExposedModuleService {
return &module{
ctx: context.Background(),
compose: composeService.Module(),
store: DefaultStore,
actionlog: DefaultActionlog,
}
}
// FindByAny tries to find module in a particular namespace by id, handle or name
func (svc module) FindByAny(ctx context.Context, nodeID uint64, identifier interface{}) (m *types.ExposedModule, err error) {
if ID, ok := identifier.(uint64); ok {
m, err = svc.FindByID(ctx, nodeID, ID)
} else if strIdentifier, ok := identifier.(string); ok {
if ID, _ := strconv.ParseUint(strIdentifier, 10, 64); ID > 0 {
m, err = svc.FindByID(ctx, nodeID, ID)
}
} else {
// force invalid ID error
// we do that to wrap error with lookup action context
_, err = svc.FindByID(ctx, nodeID, 0)
}
if err != nil {
return nil, err
}
return m, nil
}
func (svc module) FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (module *types.ExposedModule, err error) {
err = func() error {
if module, err = store.LookupFederationExposedModuleByID(svc.ctx, svc.store, moduleID); err != nil {
return err
}
return nil
}()
return module, err
}
func (svc module) DeleteByID(ctx context.Context, nodeID, moduleID uint64) error {
return trim1st(svc.updater(ctx, nodeID, moduleID, ModuleActionDelete, svc.handleDelete))
}
func (svc module) updater(ctx context.Context, nodeID, moduleID uint64, action func(...*moduleActionProps) *moduleAction, fn moduleUpdateHandler) (*types.ExposedModule, error) {
var (
moduleChanged, fieldsChanged bool
n *types.Node
m *types.ExposedModule
// m, old *types.ExposedModule
aProps = &moduleActionProps{module: &types.ExposedModule{ID: moduleID, NodeID: nodeID}}
err error
)
spew.Dump("before handle delete", fn, n, m)
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
if m, err = svc.store.LookupFederationExposedModuleByID(ctx, moduleID); err != nil {
return err
}
// TODO - handle node id also
if moduleChanged, fieldsChanged, err = fn(ctx, n, m); err != nil {
return err
}
return err
})
return m, svc.recordAction(ctx, aProps, action, err)
}
func (svc module) handleDelete(ctx context.Context, n *types.Node, m *types.ExposedModule) (bool, bool, error) {
if err := store.DeleteFederationExposedModuleByID(ctx, svc.store, m.ID); err != nil {
return false, false, err
}
return false, false, nil
}
func (svc module) Find(ctx context.Context, filter types.ExposedModuleFilter) (set types.ExposedModuleSet, err error) {
// get all modules per-node
// feed the id's into the compose moduleservice
// get the data
// transform (but not here)
// go to store and fetch the id's, first as module id in filter
// then without it
err = func() error {
if set, _, err = store.SearchFederationExposedModules(svc.ctx, svc.store, filter); err != nil {
return err
}
return nil
// return loadModuleFields(svc.ctx, svc.store, set...)
}()
spew.Dump("ERR", err)
return set, err
}
// trim1st removes 1st param and returns only error
func trim1st(_ interface{}, err error) error {
return err
}

View File

@@ -0,0 +1,30 @@
package types
import (
"time"
"github.com/cortezaproject/corteza-server/pkg/filter"
)
type (
ExposedModule struct {
ID uint64 `json:"moduleID,string"`
NodeID uint64 `json:"nodeID,string"`
ComposeModuleID uint64 `json:"ComposeModuleID,string"`
Fields ModuleFieldMappingList `json:"fields"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
ExposedModuleFilter struct {
NodeID uint64 `json:"node"`
Query string `json:"query"`
Check func(*ExposedModule) (bool, error) `json:"-"`
filter.Sorting
filter.Paging
}
)

View File

@@ -0,0 +1,36 @@
package types
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
)
type (
ModuleFieldMappingList []*ModuleFieldMapping
ModuleFieldMapping struct {
Kind string `json:"kind"`
Name string `json:"name"`
Label string `json:"label"`
IsMulti bool `json:"isMulti"`
}
)
func (list ModuleFieldMappingList) Value() (driver.Value, error) {
return json.Marshal(list)
}
func (list *ModuleFieldMappingList) Scan(value interface{}) error {
switch value.(type) {
case nil:
*list = ModuleFieldMappingList{}
case []uint8:
if err := json.Unmarshal(value.([]byte), list); err != nil {
return errors.New(fmt.Sprintf("Can not scan '%v' into RecordValueSet", value))
}
}
return nil
}

View File

@@ -10,12 +10,73 @@ package types
type (
// ExposedModuleSet slice of ExposedModule
//
// This type is auto-generated.
ExposedModuleSet []*ExposedModule
// NodeSet slice of Node
//
// This type is auto-generated.
NodeSet []*Node
)
// Walk iterates through every slice item and calls w(ExposedModule) err
//
// This function is auto-generated.
func (set ExposedModuleSet) Walk(w func(*ExposedModule) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(ExposedModule) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set ExposedModuleSet) Filter(f func(*ExposedModule) (bool, error)) (out ExposedModuleSet, err error) {
var ok bool
out = ExposedModuleSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
// FindByID finds items from slice by its ID property
//
// This function is auto-generated.
func (set ExposedModuleSet) FindByID(ID uint64) *ExposedModule {
for i := range set {
if set[i].ID == ID {
return set[i]
}
}
return nil
}
// IDs returns a slice of uint64s from all items in the set
//
// This function is auto-generated.
func (set ExposedModuleSet) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
IDs[i] = set[i].ID
}
return
}
// Walk iterates through every slice item and calls w(Node) err
//
// This function is auto-generated.

View File

@@ -14,6 +14,96 @@ import (
"testing"
)
func TestExposedModuleSetWalk(t *testing.T) {
var (
value = make(ExposedModuleSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*ExposedModule) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*ExposedModule) error { return fmt.Errorf("walk error") }))
}
func TestExposedModuleSetFilter(t *testing.T) {
var (
value = make(ExposedModuleSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*ExposedModule) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*ExposedModule) (bool, error) {
if !found {
found = true
return found, nil
}
return false, nil
})
req.NoError(err)
req.Len(set, 1)
}
// filter error
{
_, err := value.Filter(func(*ExposedModule) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestExposedModuleSetIDs(t *testing.T) {
var (
value = make(ExposedModuleSet, 3)
req = require.New(t)
)
// construct objects
value[0] = new(ExposedModule)
value[1] = new(ExposedModule)
value[2] = new(ExposedModule)
// set ids
value[0].ID = 1
value[1].ID = 2
value[2].ID = 3
// Find existing
{
val := value.FindByID(2)
req.Equal(uint64(2), val.ID)
}
// Find non-existing
{
val := value.FindByID(4)
req.Nil(val)
}
// List IDs from set
{
val := value.IDs()
req.Equal(len(val), len(value))
}
}
func TestNodeSetWalk(t *testing.T) {
var (
value = make(NodeSet, 3)

View File

@@ -1,2 +1,3 @@
types:
Node:
ExposedModule:

View File

@@ -0,0 +1,77 @@
package store
// This file is auto-generated.
//
// Template: pkg/codegen/assets/store_base.gen.go.tpl
// Definitions: store/federation_exposed_modules.yaml
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
import (
"context"
"github.com/cortezaproject/corteza-server/federation/types"
)
type (
FederationExposedModules interface {
SearchFederationExposedModules(ctx context.Context, f types.ExposedModuleFilter) (types.ExposedModuleSet, types.ExposedModuleFilter, error)
LookupFederationExposedModuleByID(ctx context.Context, id uint64) (*types.ExposedModule, error)
CreateFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) error
UpdateFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) error
UpsertFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) error
DeleteFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) error
DeleteFederationExposedModuleByID(ctx context.Context, ID uint64) error
TruncateFederationExposedModules(ctx context.Context) error
}
)
var _ *types.ExposedModule
var _ context.Context
// SearchFederationExposedModules returns all matching FederationExposedModules from store
func SearchFederationExposedModules(ctx context.Context, s FederationExposedModules, f types.ExposedModuleFilter) (types.ExposedModuleSet, types.ExposedModuleFilter, error) {
return s.SearchFederationExposedModules(ctx, f)
}
// LookupFederationExposedModuleByID searches for federation module by ID
//
// It returns federation module
func LookupFederationExposedModuleByID(ctx context.Context, s FederationExposedModules, id uint64) (*types.ExposedModule, error) {
return s.LookupFederationExposedModuleByID(ctx, id)
}
// CreateFederationExposedModule creates one or more FederationExposedModules in store
func CreateFederationExposedModule(ctx context.Context, s FederationExposedModules, rr ...*types.ExposedModule) error {
return s.CreateFederationExposedModule(ctx, rr...)
}
// UpdateFederationExposedModule updates one or more (existing) FederationExposedModules in store
func UpdateFederationExposedModule(ctx context.Context, s FederationExposedModules, rr ...*types.ExposedModule) error {
return s.UpdateFederationExposedModule(ctx, rr...)
}
// UpsertFederationExposedModule creates new or updates existing one or more FederationExposedModules in store
func UpsertFederationExposedModule(ctx context.Context, s FederationExposedModules, rr ...*types.ExposedModule) error {
return s.UpsertFederationExposedModule(ctx, rr...)
}
// DeleteFederationExposedModule Deletes one or more FederationExposedModules from store
func DeleteFederationExposedModule(ctx context.Context, s FederationExposedModules, rr ...*types.ExposedModule) error {
return s.DeleteFederationExposedModule(ctx, rr...)
}
// DeleteFederationExposedModuleByID Deletes FederationExposedModule from store
func DeleteFederationExposedModuleByID(ctx context.Context, s FederationExposedModules, ID uint64) error {
return s.DeleteFederationExposedModuleByID(ctx, ID)
}
// TruncateFederationExposedModules Deletes all FederationExposedModules from store
func TruncateFederationExposedModules(ctx context.Context, s FederationExposedModules) error {
return s.TruncateFederationExposedModules(ctx)
}

View File

@@ -0,0 +1,24 @@
import:
- github.com/cortezaproject/corteza-server/federation/types
types:
type: types.ExposedModule
fields:
- { field: ID }
- { field: NodeID }
- { field: ComposeModuleID }
- { field: Fields, type: "json.Text" }
lookups:
- fields: [ID]
description: |-
searches for federation module by ID
It returns federation module
rdbms:
alias: cmd
table: federation_module_exposed
customFilterConverter: true

View File

@@ -16,6 +16,7 @@ package store
// - store/compose_record_values.yaml
// - store/compose_records.yaml
// - store/credentials.yaml
// - store/federation_exposed_modules.yaml
// - store/labels.yaml
// - store/messaging_attachments.yaml
// - store/messaging_channel_members.yaml
@@ -51,6 +52,7 @@ type (
ComposeRecordValues
ComposeRecords
Credentials
FederationExposedModules
Labels
MessagingAttachments
MessagingChannelMembers

View File

@@ -0,0 +1,509 @@
package rdbms
// This file is an auto-generated file
//
// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl
// Definitions: store/federation_exposed_modules.yaml
//
// Changes to this file may cause incorrect behavior
// and will be lost if the code is regenerated.
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/store"
)
var _ = errors.Is
// SearchFederationExposedModules returns all matching rows
//
// This function calls convertFederationExposedModuleFilter with the given
// types.ExposedModuleFilter and expects to receive a working squirrel.SelectBuilder
func (s Store) SearchFederationExposedModules(ctx context.Context, f types.ExposedModuleFilter) (types.ExposedModuleSet, types.ExposedModuleFilter, error) {
var (
err error
set []*types.ExposedModule
q squirrel.SelectBuilder
)
q, err = s.convertFederationExposedModuleFilter(f)
if err != nil {
return nil, f, err
}
// Cleanup anything we've accidentally received...
f.PrevPage, f.NextPage = nil, nil
// When cursor for a previous page is used it's marked as reversed
// This tells us to flip the descending flag on all used sort keys
reversedCursor := f.PageCursor != nil && f.PageCursor.Reverse
// If paging with reverse cursor, change the sorting
// direction for all columns we're sorting by
curSort := f.Sort.Clone()
if reversedCursor {
curSort.Reverse()
}
return set, f, s.config.ErrorHandler(func() error {
set, err = s.fetchFullPageOfFederationExposedModules(ctx, q, curSort, f.PageCursor, f.Limit, f.Check)
if err != nil {
return err
}
if f.Limit > 0 && len(set) > 0 {
if f.PageCursor != nil && (!f.PageCursor.Reverse || uint(len(set)) == f.Limit) {
f.PrevPage = s.collectFederationExposedModuleCursorValues(set[0], curSort.Columns()...)
f.PrevPage.Reverse = true
}
// Less items fetched then requested by page-limit
// not very likely there's another page
f.NextPage = s.collectFederationExposedModuleCursorValues(set[len(set)-1], curSort.Columns()...)
}
f.PageCursor = nil
return nil
}())
}
// fetchFullPageOfFederationExposedModules collects all requested results.
//
// Function applies:
// - cursor conditions (where ...)
// - sorting rules (order by ...)
// - limit
//
// Main responsibility of this function is to perform additional sequential queries in case when not enough results
// are collected due to failed check on a specific row (by check fn). Function then moves cursor to the last item fetched
func (s Store) fetchFullPageOfFederationExposedModules(
ctx context.Context,
q squirrel.SelectBuilder,
sort filter.SortExprSet,
cursor *filter.PagingCursor,
limit uint,
check func(*types.ExposedModule) (bool, error),
) ([]*types.ExposedModule, error) {
var (
set = make([]*types.ExposedModule, 0, DefaultSliceCapacity)
aux []*types.ExposedModule
last *types.ExposedModule
// When cursor for a previous page is used it's marked as reversed
// This tells us to flip the descending flag on all used sort keys
reversedCursor = cursor != nil && cursor.Reverse
// copy of the select builder
tryQuery squirrel.SelectBuilder
fetched uint
err error
)
// Make sure we always end our sort by primary keys
if sort.Get("id") == nil {
sort = append(sort, &filter.SortExpr{Column: "id"})
}
// Apply sorting expr from filter to query
if q, err = setOrderBy(q, sort, s.sortableFederationExposedModuleColumns()...); err != nil {
return nil, err
}
for try := 0; try < MaxRefetches; try++ {
tryQuery = setCursorCond(q, cursor)
if limit > 0 {
tryQuery = tryQuery.Limit(uint64(limit))
}
if aux, fetched, last, err = s.QueryFederationExposedModules(ctx, tryQuery, check); err != nil {
return nil, err
}
if limit > 0 && uint(len(aux)) >= limit {
// we should use only as much as requested
set = append(set, aux[0:limit]...)
break
} else {
set = append(set, aux...)
}
// if limit is not set or we've already collected enough items
// we can break the loop right away
if limit == 0 || fetched == 0 || fetched < limit {
break
}
// In case limit is set very low and we've missed records in the first fetch,
// make sure next fetch limit is a bit higher
if limit < MinEnsureFetchLimit {
limit = MinEnsureFetchLimit
}
// @todo improve strategy for collecting next page with lower limit
// Point cursor to the last fetched element
if cursor = s.collectFederationExposedModuleCursorValues(last, sort.Columns()...); cursor == nil {
break
}
}
if reversedCursor {
// Cursor for previous page was used
// Fetched set needs to be reverseCursor because we've forced a descending order to
// get the previous page
for i, j := 0, len(set)-1; i < j; i, j = i+1, j-1 {
set[i], set[j] = set[j], set[i]
}
}
return set, nil
}
// QueryFederationExposedModules queries the database, converts and checks each row and
// returns collected set
//
// Fn also returns total number of fetched items and last fetched item so that the caller can construct cursor
// for next page of results
func (s Store) QueryFederationExposedModules(
ctx context.Context,
q squirrel.Sqlizer,
check func(*types.ExposedModule) (bool, error),
) ([]*types.ExposedModule, uint, *types.ExposedModule, error) {
var (
set = make([]*types.ExposedModule, 0, DefaultSliceCapacity)
res *types.ExposedModule
// Query rows with
rows, err = s.Query(ctx, q)
fetched uint
)
if err != nil {
return nil, 0, nil, err
}
defer rows.Close()
for rows.Next() {
fetched++
if err = rows.Err(); err == nil {
res, err = s.internalFederationExposedModuleRowScanner(rows)
}
if err != nil {
return nil, 0, nil, err
}
// If check function is set, call it and act accordingly
if check != nil {
if chk, err := check(res); err != nil {
return nil, 0, nil, err
} else if !chk {
// did not pass the check
// go with the next row
continue
}
}
set = append(set, res)
}
return set, fetched, res, rows.Err()
}
// LookupFederationExposedModuleByID searches for federation module by ID
//
// It returns federation module
func (s Store) LookupFederationExposedModuleByID(ctx context.Context, id uint64) (*types.ExposedModule, error) {
return s.execLookupFederationExposedModule(ctx, squirrel.Eq{
s.preprocessColumn("cmd.id", ""): s.preprocessValue(id, ""),
})
}
// CreateFederationExposedModule creates one or more rows in federation_module_exposed table
func (s Store) CreateFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) (err error) {
for _, res := range rr {
err = s.checkFederationExposedModuleConstraints(ctx, res)
if err != nil {
return err
}
err = s.execCreateFederationExposedModules(ctx, s.internalFederationExposedModuleEncoder(res))
if err != nil {
return err
}
}
return
}
// UpdateFederationExposedModule updates one or more existing rows in federation_module_exposed
func (s Store) UpdateFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) error {
return s.config.ErrorHandler(s.partialFederationExposedModuleUpdate(ctx, nil, rr...))
}
// partialFederationExposedModuleUpdate updates one or more existing rows in federation_module_exposed
func (s Store) partialFederationExposedModuleUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ExposedModule) (err error) {
for _, res := range rr {
err = s.checkFederationExposedModuleConstraints(ctx, res)
if err != nil {
return err
}
err = s.execUpdateFederationExposedModules(
ctx,
squirrel.Eq{
s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, ""),
},
s.internalFederationExposedModuleEncoder(res).Skip("id").Only(onlyColumns...))
if err != nil {
return s.config.ErrorHandler(err)
}
}
return
}
// UpsertFederationExposedModule updates one or more existing rows in federation_module_exposed
func (s Store) UpsertFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) (err error) {
for _, res := range rr {
err = s.checkFederationExposedModuleConstraints(ctx, res)
if err != nil {
return err
}
err = s.config.ErrorHandler(s.execUpsertFederationExposedModules(ctx, s.internalFederationExposedModuleEncoder(res)))
if err != nil {
return err
}
}
return nil
}
// DeleteFederationExposedModule Deletes one or more rows from federation_module_exposed table
func (s Store) DeleteFederationExposedModule(ctx context.Context, rr ...*types.ExposedModule) (err error) {
for _, res := range rr {
err = s.execDeleteFederationExposedModules(ctx, squirrel.Eq{
s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, ""),
})
if err != nil {
return s.config.ErrorHandler(err)
}
}
return nil
}
// DeleteFederationExposedModuleByID Deletes row from the federation_module_exposed table
func (s Store) DeleteFederationExposedModuleByID(ctx context.Context, ID uint64) error {
return s.execDeleteFederationExposedModules(ctx, squirrel.Eq{
s.preprocessColumn("cmd.id", ""): s.preprocessValue(ID, ""),
})
}
// TruncateFederationExposedModules Deletes all rows from the federation_module_exposed table
func (s Store) TruncateFederationExposedModules(ctx context.Context) error {
return s.config.ErrorHandler(s.Truncate(ctx, s.federationExposedModuleTable()))
}
// execLookupFederationExposedModule prepares FederationExposedModule query and executes it,
// returning types.ExposedModule (or error)
func (s Store) execLookupFederationExposedModule(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ExposedModule, err error) {
var (
row rowScanner
)
row, err = s.QueryRow(ctx, s.federationExposedModulesSelectBuilder().Where(cnd))
if err != nil {
return
}
res, err = s.internalFederationExposedModuleRowScanner(row)
if err != nil {
return
}
return res, nil
}
// execCreateFederationExposedModules updates all matched (by cnd) rows in federation_module_exposed with given data
func (s Store) execCreateFederationExposedModules(ctx context.Context, payload store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.federationExposedModuleTable()).SetMap(payload)))
}
// execUpdateFederationExposedModules updates all matched (by cnd) rows in federation_module_exposed with given data
func (s Store) execUpdateFederationExposedModules(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.federationExposedModuleTable("cmd")).Where(cnd).SetMap(set)))
}
// execUpsertFederationExposedModules inserts new or updates matching (by-primary-key) rows in federation_module_exposed with given data
func (s Store) execUpsertFederationExposedModules(ctx context.Context, set store.Payload) error {
upsert, err := s.config.UpsertBuilder(
s.config,
s.federationExposedModuleTable(),
set,
"id",
)
if err != nil {
return err
}
return s.config.ErrorHandler(s.Exec(ctx, upsert))
}
// execDeleteFederationExposedModules Deletes all matched (by cnd) rows in federation_module_exposed with given data
func (s Store) execDeleteFederationExposedModules(ctx context.Context, cnd squirrel.Sqlizer) error {
return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.federationExposedModuleTable("cmd")).Where(cnd)))
}
func (s Store) internalFederationExposedModuleRowScanner(row rowScanner) (res *types.ExposedModule, err error) {
res = &types.ExposedModule{}
if _, has := s.config.RowScanners["federationExposedModule"]; has {
scanner := s.config.RowScanners["federationExposedModule"].(func(_ rowScanner, _ *types.ExposedModule) error)
err = scanner(row, res)
} else {
err = row.Scan(
&res.ID,
&res.NodeID,
&res.ComposeModuleID,
&res.Fields,
)
}
if err == sql.ErrNoRows {
return nil, store.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("could not scan db row for FederationExposedModule: %w", err)
} else {
return res, nil
}
}
// QueryFederationExposedModules returns squirrel.SelectBuilder with set table and all columns
func (s Store) federationExposedModulesSelectBuilder() squirrel.SelectBuilder {
return s.SelectBuilder(s.federationExposedModuleTable("cmd"), s.federationExposedModuleColumns("cmd")...)
}
// federationExposedModuleTable name of the db table
func (Store) federationExposedModuleTable(aa ...string) string {
var alias string
if len(aa) > 0 {
alias = " AS " + aa[0]
}
return "federation_module_exposed" + alias
}
// FederationExposedModuleColumns returns all defined table columns
//
// With optional string arg, all columns are returned aliased
func (Store) federationExposedModuleColumns(aa ...string) []string {
var alias string
if len(aa) > 0 {
alias = aa[0] + "."
}
return []string{
alias + "id",
alias + "rel_node",
alias + "rel_compose_module",
alias + "fields",
}
}
// {true true true true true}
// sortableFederationExposedModuleColumns returns all FederationExposedModule columns flagged as sortable
//
// With optional string arg, all columns are returned aliased
func (Store) sortableFederationExposedModuleColumns() []string {
return []string{
"id",
}
}
// internalFederationExposedModuleEncoder encodes fields from types.ExposedModule to store.Payload (map)
//
// Encoding is done by using generic approach or by calling encodeFederationExposedModule
// func when rdbms.customEncoder=true
func (s Store) internalFederationExposedModuleEncoder(res *types.ExposedModule) store.Payload {
return store.Payload{
"id": res.ID,
"rel_node": res.NodeID,
"rel_compose_module": res.ComposeModuleID,
"fields": res.Fields,
}
}
// collectFederationExposedModuleCursorValues collects values from the given resource that and sets them to the cursor
// to be used for pagination
//
// Values that are collected must come from sortable, unique or primary columns/fields
// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end
//
// Known issue:
// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on
// undeleted items)
func (s Store) collectFederationExposedModuleCursorValues(res *types.ExposedModule, cc ...string) *filter.PagingCursor {
var (
cursor = &filter.PagingCursor{}
hasUnique bool
// All known primary key columns
pkId bool
collect = func(cc ...string) {
for _, c := range cc {
switch c {
case "id":
cursor.Set(c, res.ID, false)
pkId = true
}
}
}
)
collect(cc...)
if !hasUnique || !(pkId && true) {
collect("id")
}
return cursor
}
// checkFederationExposedModuleConstraints performs lookups (on valid) resource to check if any of the values on unique fields
// already exists in the store
//
// Using built-in constraint checking would be more performant but unfortunately we can not rely
// on the full support (MySQL does not support conditional indexes)
func (s *Store) checkFederationExposedModuleConstraints(ctx context.Context, res *types.ExposedModule) error {
// Consider resource valid when all fields in unique constraint check lookups
// have valid (non-empty) value
//
// Only string and uint64 are supported for now
// feel free to add additional types if needed
var valid = true
if !valid {
return nil
}
return nil
}

View File

@@ -0,0 +1,26 @@
package rdbms
import (
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/federation/types"
)
func (s Store) convertFederationExposedModuleFilter(f types.ExposedModuleFilter) (query squirrel.SelectBuilder, err error) {
query = s.federationExposedModulesSelectBuilder()
// query = filter.StateCondition(query, "cmd.deleted_at", f.Deleted)
if f.NodeID > 0 {
query = query.Where("cmd.rel_node = ?", f.NodeID)
}
// if f.Query != "" {
// q := "%" + strings.ToLower(f.Query) + "%"
// query = query.Where(squirrel.Or{
// squirrel.Like{"LOWER(cmd.name)": q},
// squirrel.Like{"LOWER(cmd.handle)": q},
// })
// }
return
}

View File

@@ -14,6 +14,7 @@ package tests
// - store/compose_namespaces.yaml
// - store/compose_pages.yaml
// - store/credentials.yaml
// - store/federation_exposed_modules.yaml
// - store/labels.yaml
// - store/messaging_attachments.yaml
// - store/messaging_channel_members.yaml
@@ -101,6 +102,11 @@ func testAllGenerated(t *testing.T, s store.Storer) {
testCredentials(t, s)
})
// Run generated tests for FederationExposedModules
t.Run("FederationExposedModules", func(t *testing.T) {
testFederationExposedModules(t, s)
})
// Run generated tests for Labels
t.Run("Labels", func(t *testing.T) {
testLabels(t, s)