snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
package commands
import (
"context"
"github.com/spf13/cobra"
)
type (
serviceInitializer interface {
InitServices(ctx context.Context) error
}
)
func Sync(ctx context.Context, app serviceInitializer) *cobra.Command {
// Sync commands.
cmd := &cobra.Command{
Use: "sync",
Short: "Sync commands",
}
// Sync structure.
syncStructureCmd := &cobra.Command{
Use: "structure",
Short: "Sync structure",
PreRunE: commandPreRunInitService(ctx, app),
Run: commandSyncStructure(ctx),
}
syncDataCmd := &cobra.Command{
Use: "data",
Short: "Sync data",
PreRunE: commandPreRunInitService(ctx, app),
Run: commandSyncData(ctx),
}
cmd.AddCommand(
syncStructureCmd,
syncDataCmd,
)
return cmd
}
func commandPreRunInitService(ctx context.Context, app serviceInitializer) func(*cobra.Command, []string) error {
return func(_ *cobra.Command, _ []string) error {
return app.InitServices(ctx)
}
}

View File

@@ -0,0 +1,25 @@
package commands
import (
"context"
cs "github.com/cortezaproject/corteza/server/compose/service"
"github.com/cortezaproject/corteza/server/federation/service"
ss "github.com/cortezaproject/corteza/server/system/service"
"github.com/spf13/cobra"
)
func commandSyncData(ctx context.Context) func(*cobra.Command, []string) {
return func(_ *cobra.Command, _ []string) {
syncService := service.NewSync(
&service.Syncer{},
&service.Mapper{},
service.DefaultSharedModule,
cs.DefaultRecord,
ss.DefaultUser,
ss.DefaultRole)
syncData := service.WorkerData(syncService, service.DefaultLogger)
syncData.Watch(ctx, service.DefaultOptions.DataMonitorInterval, service.DefaultOptions.DataPageSize)
}
}

View File

@@ -0,0 +1,25 @@
package commands
import (
"context"
cs "github.com/cortezaproject/corteza/server/compose/service"
"github.com/cortezaproject/corteza/server/federation/service"
ss "github.com/cortezaproject/corteza/server/system/service"
"github.com/spf13/cobra"
)
func commandSyncStructure(ctx context.Context) func(*cobra.Command, []string) {
return func(_ *cobra.Command, _ []string) {
syncService := service.NewSync(
&service.Syncer{},
&service.Mapper{},
service.DefaultSharedModule,
cs.DefaultRecord,
ss.DefaultUser,
ss.DefaultRole)
syncStructure := service.WorkerStructure(syncService, service.DefaultLogger)
syncStructure.Watch(ctx, service.DefaultOptions.StructureMonitorInterval, service.DefaultOptions.StructurePageSize)
}
}

View File

@@ -0,0 +1,30 @@
package federation
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
component: schema.#component & {
handle: "federation"
resources: {
"node": node
"node-sync": nodeSync
"exposed-module": exposedModule
"shared-module": sharedModule
"module-mapping": moduleMapping
}
envoy: {
omit: true
}
rbac: operations: {
"grant": description: "Manage federation permissions"
"pair": description: "Pair federation nodes"
"settings.read": description: "Read settings"
"settings.manage": description: "Manage settings"
"node.create": description: "Create new federation node"
"nodes.search": description: "List, search or filter federation nodes"
}
}

26
server/federation/model/init.gen.go generated Normal file
View File

@@ -0,0 +1,26 @@
package model
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"context"
"github.com/cortezaproject/corteza/server/pkg/dal"
)
type (
modelReplacer interface {
ReplaceModel(ctx context.Context, model *dal.Model) (err error)
}
)
var (
models []*dal.Model
)
func Models() dal.ModelSet {
return models
}

508
server/federation/model/models.gen.go generated Normal file
View File

@@ -0,0 +1,508 @@
package model
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/dal"
)
var ExposedModule = &dal.Model{
Ident: "federation_module_exposed",
ResourceType: types.ExposedModuleResourceType,
Attributes: dal.AttributeSet{
&dal.Attribute{
Ident: "ID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "id"},
},
&dal.Attribute{
Ident: "Handle",
Type: &dal.TypeText{Length: 64},
Store: &dal.CodecAlias{Ident: "handle"},
},
&dal.Attribute{
Ident: "Name", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "name"},
},
&dal.Attribute{
Ident: "NodeID", Sortable: true,
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::federation:node",
},
},
Store: &dal.CodecAlias{Ident: "rel_node"},
},
&dal.Attribute{
Ident: "ComposeModuleID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_compose_module"},
},
&dal.Attribute{
Ident: "ComposeNamespaceID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_compose_namespace"},
},
&dal.Attribute{
Ident: "Fields",
Type: &dal.TypeJSON{
DefaultValue: "{}",
},
Store: &dal.CodecAlias{Ident: "fields"},
},
&dal.Attribute{
Ident: "CreatedAt", Sortable: true,
Type: &dal.TypeTimestamp{
DefaultCurrentTimestamp: true, Timezone: true, Precision: -1,
},
Store: &dal.CodecAlias{Ident: "created_at"},
},
&dal.Attribute{
Ident: "UpdatedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "updated_at"},
},
&dal.Attribute{
Ident: "DeletedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "deleted_at"},
},
&dal.Attribute{
Ident: "CreatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "created_by"},
},
&dal.Attribute{
Ident: "UpdatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "updated_by"},
},
&dal.Attribute{
Ident: "DeletedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "deleted_by"},
},
},
Indexes: dal.IndexSet{
&dal.Index{
Ident: "PRIMARY",
Type: "BTREE",
Fields: []*dal.IndexField{
{
AttributeIdent: "ID",
},
},
},
},
}
var ModuleMapping = &dal.Model{
Ident: "federation_module_mapping",
ResourceType: types.ModuleMappingResourceType,
Attributes: dal.AttributeSet{
&dal.Attribute{
Ident: "NodeID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "node_id"},
},
&dal.Attribute{
Ident: "FederationModuleID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_federation_module"},
},
&dal.Attribute{
Ident: "ComposeModuleID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_compose_module"},
},
&dal.Attribute{
Ident: "ComposeNamespaceID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_compose_namespace"},
},
&dal.Attribute{
Ident: "FieldMapping",
Type: &dal.TypeJSON{
DefaultValue: "{}",
},
Store: &dal.CodecAlias{Ident: "field_mapping"},
},
},
Indexes: dal.IndexSet{
&dal.Index{
Ident: "federation_module_mapping_uniqueModuleComposeModule",
Type: "BTREE",
Unique: true,
Fields: []*dal.IndexField{
{
AttributeIdent: "FederationModuleID",
},
{
AttributeIdent: "ComposeModuleID",
},
{
AttributeIdent: "ComposeNamespaceID",
},
},
},
},
}
var Node = &dal.Model{
Ident: "federation_nodes",
ResourceType: types.NodeResourceType,
Attributes: dal.AttributeSet{
&dal.Attribute{
Ident: "ID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "id"},
},
&dal.Attribute{
Ident: "SharedNodeID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "shared_node_id"},
},
&dal.Attribute{
Ident: "Name", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "name"},
},
&dal.Attribute{
Ident: "BaseURL", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "base_url"},
},
&dal.Attribute{
Ident: "Status", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "status"},
},
&dal.Attribute{
Ident: "Contact", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "contact"},
},
&dal.Attribute{
Ident: "PairToken",
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "pair_token"},
},
&dal.Attribute{
Ident: "AuthToken",
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "auth_token"},
},
&dal.Attribute{
Ident: "CreatedAt", Sortable: true,
Type: &dal.TypeTimestamp{
DefaultCurrentTimestamp: true, Timezone: true, Precision: -1,
},
Store: &dal.CodecAlias{Ident: "created_at"},
},
&dal.Attribute{
Ident: "UpdatedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "updated_at"},
},
&dal.Attribute{
Ident: "DeletedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "deleted_at"},
},
&dal.Attribute{
Ident: "CreatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "created_by"},
},
&dal.Attribute{
Ident: "UpdatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "updated_by"},
},
&dal.Attribute{
Ident: "DeletedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "deleted_by"},
},
},
Indexes: dal.IndexSet{
&dal.Index{
Ident: "PRIMARY",
Type: "BTREE",
Fields: []*dal.IndexField{
{
AttributeIdent: "ID",
},
},
},
},
}
var NodeSync = &dal.Model{
Ident: "federation_nodes_sync",
ResourceType: types.NodeSyncResourceType,
Attributes: dal.AttributeSet{
&dal.Attribute{
Ident: "NodeID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_node"},
},
&dal.Attribute{
Ident: "ModuleID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_compose_module"},
},
&dal.Attribute{
Ident: "SyncType", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "sync_type"},
},
&dal.Attribute{
Ident: "SyncStatus", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "sync_status"},
},
&dal.Attribute{
Ident: "TimeOfAction", Sortable: true,
Type: &dal.TypeTimestamp{Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "time_of_action"},
},
},
Indexes: dal.IndexSet{
&dal.Index{
Ident: "federation_nodes_sync_idxRelNode",
Type: "BTREE",
Fields: []*dal.IndexField{
{
AttributeIdent: "NodeID",
},
},
},
},
}
var SharedModule = &dal.Model{
Ident: "federation_module_shared",
ResourceType: types.SharedModuleResourceType,
Attributes: dal.AttributeSet{
&dal.Attribute{
Ident: "ID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "id"},
},
&dal.Attribute{
Ident: "Handle",
Type: &dal.TypeText{Length: 64},
Store: &dal.CodecAlias{Ident: "handle"},
},
&dal.Attribute{
Ident: "NodeID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "rel_node"},
},
&dal.Attribute{
Ident: "Name", Sortable: true,
Type: &dal.TypeText{},
Store: &dal.CodecAlias{Ident: "name"},
},
&dal.Attribute{
Ident: "ExternalFederationModuleID", Sortable: true,
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "xref_module"},
},
&dal.Attribute{
Ident: "Fields",
Type: &dal.TypeJSON{
DefaultValue: "{}",
},
Store: &dal.CodecAlias{Ident: "fields"},
},
&dal.Attribute{
Ident: "CreatedAt", Sortable: true,
Type: &dal.TypeTimestamp{
DefaultCurrentTimestamp: true, Timezone: true, Precision: -1,
},
Store: &dal.CodecAlias{Ident: "created_at"},
},
&dal.Attribute{
Ident: "UpdatedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "updated_at"},
},
&dal.Attribute{
Ident: "DeletedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "deleted_at"},
},
&dal.Attribute{
Ident: "CreatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "created_by"},
},
&dal.Attribute{
Ident: "UpdatedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "updated_by"},
},
&dal.Attribute{
Ident: "DeletedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "deleted_by"},
},
},
Indexes: dal.IndexSet{
&dal.Index{
Ident: "PRIMARY",
Type: "BTREE",
Fields: []*dal.IndexField{
{
AttributeIdent: "ID",
},
},
},
},
}
func init() {
models = append(
models,
ExposedModule,
ModuleMapping,
Node,
NodeSync,
SharedModule,
)
}

View File

@@ -0,0 +1,100 @@
package federation
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
exposedModule: {
parents: [
{handle: "node"},
]
features: {
labels: false
}
model: {
ident: "federation_module_exposed"
attributes: {
id: schema.IdField
handle: schema.HandleField
name: {
sortable: true
dal: {}
}
node_id: {
sortable: true,
ident: "nodeID",
goType: "uint64",
storeIdent: "rel_node"
dal: { type: "Ref", refModelResType: "corteza::federation:node", default: 0 }
}
compose_module_id: {
ident: "composeModuleID",
goType: "uint64",
storeIdent: "rel_compose_module"
dal: { type: "ID" }
}
compose_namespace_id: {
ident: "composeNamespaceID",
goType: "uint64",
storeIdent: "rel_compose_namespace"
dal: { type: "ID" }
}
fields: {
goType: "types.ModuleFieldSet"
dal: { type: "JSON", defaultEmptyObject: true }
omitSetter: true
omitGetter: true
}
created_at: schema.SortableTimestampNowField
updated_at: schema.SortableTimestampNilField
deleted_at: schema.SortableTimestampNilField
created_by: schema.AttributeUserRef
updated_by: schema.AttributeUserRef
deleted_by: schema.AttributeUserRef
}
indexes: {
"primary": { attribute: "id" }
}
}
filter: {
struct: {
node_id: { goType: "uint64", ident: "nodeID", storeIdent: "rel_node" }
compose_module_id: { goType: "uint64", ident: "composeModuleID", storeIdent: "rel_compose_module" }
compose_namespace_id: { goType: "uint64", ident: "composeNamespaceID", storeIdent: "rel_compose_namespace" }
}
byValue: ["compose_module_id", "compose_namespace_id", "node_id"]
}
envoy: {
omit: true
}
rbac: {
operations: {
"manage": description: "Manage exposed module module"
}
}
store: {
ident: "federationExposedModule"
api: {
lookups: [
{
fields: ["id"]
description: """
searches for federation module by ID
It returns federation module
"""
}
]
}
}
}

View File

@@ -0,0 +1,94 @@
package federation
moduleMapping: {
parents: [
{handle: "node"},
]
features: {
labels: false
}
model: {
ident: "federation_module_mapping"
attributes: {
node_id: {
ident: "nodeID"
unique: true
goType: "uint64"
dal: { type: "ID" }
}
federation_module_id: {
sortable: true,
ident: "federationModuleID",
goType: "uint64"
storeIdent: "rel_federation_module"
dal: { type: "ID" }
}
compose_module_id: {
sortable: true,
ident: "composeModuleID",
goType: "uint64"
storeIdent: "rel_compose_module"
dal: { type: "ID" }
}
compose_namespace_id: {
sortable: true,
ident: "composeNamespaceID",
goType: "uint64"
storeIdent: "rel_compose_namespace"
dal: { type: "ID" }
}
field_mapping: {
goType: "types.ModuleFieldMappingSet"
dal: { type: "JSON", defaultEmptyObject: true }
omitSetter: true
omitGetter: true
}
}
indexes: {
"unique_module_compose_module": {
attributes: ["federation_module_id", "compose_module_id", "compose_namespace_id" ]
}
}
}
envoy: {
omit: true
}
filter: {
struct: {
compose_module_id: { goType: "uint64", ident: "composeModuleID", storeIdent: "rel_compose_module" }
compose_namespace_id: { goType: "uint64", ident: "composeNamespaceID", storeIdent: "rel_compose_namespace" }
federation_module_id: { goType: "uint64", ident: "federationModuleID", storeIdent: "rel_federation_module" }
}
byValue: ["compose_module_id", "compose_namespace_id", "federation_module_id"]
}
store: {
ident: "federationModuleMapping"
api: {
lookups: [
{
fields: ["federation_module_id", "compose_module_id", "compose_namespace_id"]
description: """
searches for module mapping by federation module id and compose module id
It returns module mapping
"""
}, {
fields: ["federation_module_id"]
description: """
searches for module mapping by federation module id
It returns module mapping
"""
}
]
}
}
}

109
server/federation/node.cue Normal file
View File

@@ -0,0 +1,109 @@
package federation
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
node: {
features: {
labels: false
paging: true
sorting: true
}
model: {
ident: "federation_nodes"
attributes: {
id: schema.IdField
shared_node_id: {
sortable: true,
ident: "sharedNodeID",
goType: "uint64"
dal: { type: "ID" }
}
name: {
sortable: true
dal: {}
}
base_url: {
sortable: true,
ident: "baseURL"
dal: {}
}
status: {
sortable: true,
dal: {}
}
contact: {
sortable: true,
dal: {}
}
pair_token: {
dal: {}
}
auth_token: {
dal: {}
}
created_at: schema.SortableTimestampNowField
updated_at: schema.SortableTimestampNilField
deleted_at: schema.SortableTimestampNilField
created_by: schema.AttributeUserRef
updated_by: schema.AttributeUserRef
deleted_by: schema.AttributeUserRef
}
indexes: {
"primary": { attribute: "id" }
}
}
envoy: {
omit: true
}
filter: {
struct: {
name: { goType: "string" }
base_url: { goType: "string", ident: "baseURL" }
status: { goType: "string" }
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
}
query: ["name", "base_url"]
byNilState: ["deleted"]
}
rbac: {
operations: {
"manage": description: "Manage federation node"
"module.create": description: "Create shared module"
}
}
store: {
ident: "federationNode"
api: {
lookups: [
{
fields: ["id"]
description: """
searches for federation node by ID
It returns federation node
"""
}, {
fields: ["base_url", "shared_node_id"]
description: """
searches for node by shared-node-id and base-url
"""
}, {
fields: ["shared_node_id"]
description: """
searches for node by shared-node-id
"""
}
]
}
}
}

View File

@@ -0,0 +1,85 @@
package federation
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
nodeSync: {
features: {
labels: false
}
model: {
ident: "federation_nodes_sync"
attributes: {
rel_node: {
sortable: true,
ident: "nodeID",
storeIdent: "rel_node",
goType: "uint64",
dal: { type: "ID" }
}
rel_module: {
sortable: true,
ident: "moduleID",
storeIdent: "rel_compose_module",
goType: "uint64"
dal: { type: "ID" }
}
sync_type: {
sortable: true,
goType: "string"
dal: {}
}
sync_status: {
sortable: true,
goType: "string"
dal: {}
}
time_of_action: schema.SortableTimestampField
}
indexes: {
"idx_rel_node": { attribute: "rel_node" }
}
}
filter: {
struct: {
rel_node: { goType: "uint64", storeIdent: "rel_node", ident: "nodeID" }
rel_module: { goType: "uint64", storeIdent: "rel_compose_module", ident: "moduleID" }
sync_status: { goType: "string", storeIdent: "sync_status" }
sync_type: { goType: "string", storeIdent: "sync_type" }
}
byValue: ["rel_node", "rel_module", "sync_status", "sync_type"]
}
envoy: {
omit: true
}
store: {
ident: "federationNodeSync"
api: {
lookups: [
{
fields: ["rel_node"]
description: """
searches for sync activity by node ID
It returns sync activity
"""
}, {
fields: ["rel_node", "rel_module", "sync_type", "sync_status"]
description: """
searches for activity by node, type and status
It returns sync activity
"""
}
]
}
}
}

590
server/federation/rest.yaml Normal file
View File

@@ -0,0 +1,590 @@
---
# This is direct conversion from old api/*/spec.json files
# using https://www.json2yaml.com/
#
# Next step: swagger.
endpoints:
- title: Federation node handshake
path: "/nodes/{nodeID}/handshake"
entrypoint: nodeHandshake
parameters:
path:
- name: nodeID
type: uint64
required: true
title: NodeID
apis:
- name: initialize
method: POST
title: Initialize the handshake step with node B
path: ""
parameters:
post:
- name: pairToken
type: string
required: true
title: Pairing token to authenticate handshake initialization request
- name: sharedNodeID
type: uint64
required: true
title: Remote (invoker's) node ID
- name: authToken
type: string
required: true
title: Authentication token so that remote
- title: Federation nodes
path: "/nodes"
entrypoint: node
apis:
- name: search
method: GET
title: Search federated nodes
path: "/"
parameters:
get:
- name: query
type: string
title: Filter nodes by name and host
- name: status
type: string
title: Filter by status
- type: uint
name: limit
title: Limit
- type: bool
name: incTotal
title: Include total counter
- type: string
name: pageCursor
title: Page cursor
- type: string
name: sort
title: Sort items
- name: create
method: POST
title: Create a new federation node
path: "/"
parameters:
post:
- name: baseURL
type: string
required: false
title: Federation API base URL
- name: name
type: string
required: false
title: Name for this node
- name: contact
type: string
required: false
title: Contact email
- name: pairingURI
type: string
required: false
title: Pairing URI
- name: read
method: GET
title: Read a federation node
path: "/{nodeID}"
parameters:
path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ]
- name: generateURI
method: POST
title: Creates new sharable federation URI
path: "/{nodeID}/uri"
parameters: { path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ] }
- name: update
method: POST
title: Updates existing node
path: "/{nodeID}"
parameters:
path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ]
post:
- name: name
type: string
required: false
title: Name for this node
- name: contact
type: string
required: false
title: Contact email
- name: baseURL
type: string
required: false
title: Federation API base URL
- name: delete
method: DELETE
title: Deletes node
path: "/{nodeID}"
parameters: { path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ] }
- name: undelete
method: POST
title: Undeletes a node
path: "/{nodeID}/undelete"
parameters: { path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ] }
- name: pair
method: POST
title: Initialize the pairing process between the two nodes
path: "/{nodeID}/pair"
parameters: { path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ] }
- name: handshake confirm
method: POST
title: Confirm the requested handshake
path: "/{nodeID}/handshake-confirm"
parameters: { path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ] }
- name: handshake complete
method: POST
title: Complete the handshake
path: "/{nodeID}/handshake-complete"
parameters:
path: [ { name: nodeID, type: uint64, required: true, title: NodeID } ]
post:
- name: authToken
type: string
required: true
title: Node A token
- title: Manage structure
description: Manage structure
entrypoint: manageStructure
path: "/nodes/{nodeID}/modules"
authentication: []
imports:
- github.com/cortezaproject/corteza/server/federation/types
apis:
- name: readExposed
method: GET
title: Exposed settings for module
path: "/{moduleID}/exposed"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
- name: createExposed
method: PUT
title: Add module to federation
path: "/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
post:
- type: uint64
name: composeModuleID
required: true
title: Compose module id
- type: uint64
name: composeNamespaceID
required: true
title: Compose namespace id
- type: string
name: name
required: true
title: Module name
- type: string
name: handle
required: true
title: Module handle
- type: types.ModuleFieldSet
name: fields
required: false
title: Exposed module fields
- name: updateExposed
method: POST
title: Update already exposed module
path: "/{moduleID}/exposed"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
post:
- type: uint64
name: composeModuleID
required: true
title: Compose module id
- type: uint64
name: composeNamespaceID
required: true
title: Compose namespace id
- type: string
name: name
required: true
title: Module name
- type: string
name: handle
required: true
title: Module handle
- type: types.ModuleFieldSet
name: fields
required: false
title: Exposed module fields
- name: removeExposed
method: DELETE
title: Remove from federation
path: "/{moduleID}/exposed"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
- name: readShared
method: GET
title: Shared settings for module
path: "/{moduleID}/shared"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
- name: createMappings
method: PUT
title: Add fields mappings to federated module
path: "/{moduleID}/mapped"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
post:
- type: uint64
name: composeModuleID
required: true
title: Compose module id
- type: uint64
name: composeNamespaceID
required: true
title: Compose namespace id
- type: types.ModuleFieldMappingSet
name: fields
required: false
title: Exposed module fields
- name: readMappings
method: GET
title: Fields mappings for module
path: "/{moduleID}/mapped"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
get:
- name: composeModuleID
type: uint64
required: false
title: Compose module id
- name: listAll
method: GET
title: List of shared/exposed/mapped modules
path: "/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
get:
- name: shared
type: bool
required: false
title: List shared modules
- name: exposed
type: bool
required: false
title: List exposed modules
- name: mapped
type: bool
required: false
title: List mapped modules
- title: Sync structure
description: Sync structure
entrypoint: syncStructure
path: "/nodes/{nodeID}/modules/exposed"
authentication: []
apis:
- name: readExposedInternal
method: GET
title: List all exposed modules changes
path: "/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
get:
- type: uint64
name: lastSync
required: false
title: Last sync timestamp
- type: string
name: query
required: false
title: Search query
- type: uint
name: limit
title: Limit
- type: string
name: pageCursor
title: Page cursor
- type: string
name: sort
title: Sort items
- name: readExposedSocial
method: GET
title: List all exposed modules changes in activity streams format
path: "/activity-stream"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
get:
- type: uint64
name: lastSync
required: false
title: Last sync timestamp
- type: string
name: query
required: false
title: Search query
- type: uint
name: limit
title: Limit
- type: string
name: pageCursor
title: Page cursor
- type: string
name: sort
title: Sort items
- title: Sync data
description: Sync data
entrypoint: syncData
path: "/nodes/{nodeID}/modules"
authentication: []
apis:
- name: readExposedAll
method: GET
title: List all record changes
path: "/exposed/records/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
get:
- type: uint64
name: lastSync
required: false
title: Last sync timestamp
- type: string
name: query
required: false
title: Search query
- type: uint
name: limit
title: Limit
- type: string
name: pageCursor
title: Page cursor
- type: string
name: sort
title: Sort items
- name: readExposedInternal
method: GET
title: List all records per module
path: "/{moduleID}/records/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
get:
- type: uint64
name: lastSync
required: false
title: Last sync timestamp
- type: string
name: query
required: false
title: Search query
- type: uint
name: limit
required: false
title: Limit
- type: string
name: pageCursor
required: false
title: Page cursor
- type: string
name: sort
required: false
title: Sort items
- name: readExposedSocial
method: GET
title: List all records per module in activitystreams format
path: "/{moduleID}/records/activity-stream/"
parameters:
path:
- type: uint64
name: nodeID
required: true
title: Node ID
- type: uint64
name: moduleID
required: true
title: Module ID
get:
- type: uint64
name: lastSync
required: false
title: Last sync timestamp
- type: string
name: query
required: false
title: Search query
- type: uint
name: limit
required: false
title: Limit
- type: string
name: pageCursor
required: false
title: Page cursor
- type: string
name: sort
required: false
title: Sort items
- title: Permissions
entrypoint: permissions
path: "/permissions"
authentication:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza/server/pkg/rbac
apis:
- name: list
path: "/"
method: GET
title: Retrieve defined permissions
- name: effective
path: "/effective"
method: GET
title: Effective rules for current user
parameters:
get:
- name: resource
type: string
required: false
title: Show only rules for a specific resource
- name: trace
path: "/trace"
method: GET
title: Evaluate rules for given user/role combo
parameters:
get:
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: userID
type: uint64
required: false
- name: roleID
type: "[]uint64"
required: false
- name: read
path: "/{roleID}/rules"
method: GET
title: Retrieve role permissions
parameters:
path:
- name: roleID
type: uint64
required: true
title: Role ID
get:
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: delete
path: "/{roleID}/rules"
method: DELETE
title: Remove all defined role permissions
parameters:
path:
- name: roleID
type: uint64
required: true
title: Role ID
- name: update
path: "/{roleID}/rules"
method: PATCH
title: Update permission settings
parameters:
path:
- name: roleID
type: uint64
required: true
title: Role ID
post:
- name: rules
type: rbac.RuleSet
required: true
title: List of permission rules to set

View File

@@ -0,0 +1,190 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
ManageStructureAPI interface {
ReadExposed(context.Context, *request.ManageStructureReadExposed) (interface{}, error)
CreateExposed(context.Context, *request.ManageStructureCreateExposed) (interface{}, error)
UpdateExposed(context.Context, *request.ManageStructureUpdateExposed) (interface{}, error)
RemoveExposed(context.Context, *request.ManageStructureRemoveExposed) (interface{}, error)
ReadShared(context.Context, *request.ManageStructureReadShared) (interface{}, error)
CreateMappings(context.Context, *request.ManageStructureCreateMappings) (interface{}, error)
ReadMappings(context.Context, *request.ManageStructureReadMappings) (interface{}, error)
ListAll(context.Context, *request.ManageStructureListAll) (interface{}, error)
}
// HTTP API interface
ManageStructure struct {
ReadExposed func(http.ResponseWriter, *http.Request)
CreateExposed func(http.ResponseWriter, *http.Request)
UpdateExposed func(http.ResponseWriter, *http.Request)
RemoveExposed func(http.ResponseWriter, *http.Request)
ReadShared func(http.ResponseWriter, *http.Request)
CreateMappings func(http.ResponseWriter, *http.Request)
ReadMappings func(http.ResponseWriter, *http.Request)
ListAll func(http.ResponseWriter, *http.Request)
}
)
func NewManageStructure(h ManageStructureAPI) *ManageStructure {
return &ManageStructure{
ReadExposed: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureReadExposed()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposed(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
CreateExposed: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureCreateExposed()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.CreateExposed(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
UpdateExposed: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureUpdateExposed()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.UpdateExposed(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
RemoveExposed: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureRemoveExposed()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.RemoveExposed(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ReadShared: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureReadShared()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadShared(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
CreateMappings: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureCreateMappings()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.CreateMappings(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ReadMappings: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureReadMappings()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadMappings(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ListAll: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewManageStructureListAll()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ListAll(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h ManageStructure) 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.Put("/nodes/{nodeID}/modules/", h.CreateExposed)
r.Post("/nodes/{nodeID}/modules/{moduleID}/exposed", h.UpdateExposed)
r.Delete("/nodes/{nodeID}/modules/{moduleID}/exposed", h.RemoveExposed)
r.Get("/nodes/{nodeID}/modules/{moduleID}/shared", h.ReadShared)
r.Put("/nodes/{nodeID}/modules/{moduleID}/mapped", h.CreateMappings)
r.Get("/nodes/{nodeID}/modules/{moduleID}/mapped", h.ReadMappings)
r.Get("/nodes/{nodeID}/modules/", h.ListAll)
})
}

View File

@@ -0,0 +1,228 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
NodeAPI interface {
Search(context.Context, *request.NodeSearch) (interface{}, error)
Create(context.Context, *request.NodeCreate) (interface{}, error)
Read(context.Context, *request.NodeRead) (interface{}, error)
GenerateURI(context.Context, *request.NodeGenerateURI) (interface{}, error)
Update(context.Context, *request.NodeUpdate) (interface{}, error)
Delete(context.Context, *request.NodeDelete) (interface{}, error)
Undelete(context.Context, *request.NodeUndelete) (interface{}, error)
Pair(context.Context, *request.NodePair) (interface{}, error)
HandshakeConfirm(context.Context, *request.NodeHandshakeConfirm) (interface{}, error)
HandshakeComplete(context.Context, *request.NodeHandshakeComplete) (interface{}, error)
}
// HTTP API interface
Node struct {
Search func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
GenerateURI func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Undelete func(http.ResponseWriter, *http.Request)
Pair func(http.ResponseWriter, *http.Request)
HandshakeConfirm func(http.ResponseWriter, *http.Request)
HandshakeComplete func(http.ResponseWriter, *http.Request)
}
)
func NewNode(h NodeAPI) *Node {
return &Node{
Search: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeSearch()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Search(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeCreate()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Create(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Read: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeRead()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Read(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
GenerateURI: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeGenerateURI()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.GenerateURI(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeUpdate()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Update(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeDelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Delete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Undelete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeUndelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Undelete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Pair: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodePair()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Pair(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
HandshakeConfirm: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeHandshakeConfirm()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.HandshakeConfirm(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
HandshakeComplete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeHandshakeComplete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.HandshakeComplete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h Node) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/nodes/", h.Search)
r.Post("/nodes/", h.Create)
r.Get("/nodes/{nodeID}", h.Read)
r.Post("/nodes/{nodeID}/uri", h.GenerateURI)
r.Post("/nodes/{nodeID}", h.Update)
r.Delete("/nodes/{nodeID}", h.Delete)
r.Post("/nodes/{nodeID}/undelete", h.Undelete)
r.Post("/nodes/{nodeID}/pair", h.Pair)
r.Post("/nodes/{nodeID}/handshake-confirm", h.HandshakeConfirm)
r.Post("/nodes/{nodeID}/handshake-complete", h.HandshakeComplete)
})
}

View File

@@ -0,0 +1,57 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
NodeHandshakeAPI interface {
Initialize(context.Context, *request.NodeHandshakeInitialize) (interface{}, error)
}
// HTTP API interface
NodeHandshake struct {
Initialize func(http.ResponseWriter, *http.Request)
}
)
func NewNodeHandshake(h NodeHandshakeAPI) *NodeHandshake {
return &NodeHandshake{
Initialize: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewNodeHandshakeInitialize()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Initialize(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h NodeHandshake) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Post("/nodes/{nodeID}/handshake", h.Initialize)
})
}

View File

@@ -0,0 +1,152 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
PermissionsAPI interface {
List(context.Context, *request.PermissionsList) (interface{}, error)
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
Trace(context.Context, *request.PermissionsTrace) (interface{}, error)
Read(context.Context, *request.PermissionsRead) (interface{}, error)
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
}
// HTTP API interface
Permissions struct {
List func(http.ResponseWriter, *http.Request)
Effective func(http.ResponseWriter, *http.Request)
Trace func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
}
)
func NewPermissions(h PermissionsAPI) *Permissions {
return &Permissions{
List: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsList()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.List(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Effective: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsEffective()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Effective(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Trace: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsTrace()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Trace(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Read: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsRead()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Read(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsDelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Delete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsUpdate()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Update(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/permissions/", h.List)
r.Get("/permissions/effective", h.Effective)
r.Get("/permissions/trace", h.Trace)
r.Get("/permissions/{roleID}/rules", h.Read)
r.Delete("/permissions/{roleID}/rules", h.Delete)
r.Patch("/permissions/{roleID}/rules", h.Update)
})
}

View File

@@ -0,0 +1,95 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
SyncDataAPI interface {
ReadExposedAll(context.Context, *request.SyncDataReadExposedAll) (interface{}, error)
ReadExposedInternal(context.Context, *request.SyncDataReadExposedInternal) (interface{}, error)
ReadExposedSocial(context.Context, *request.SyncDataReadExposedSocial) (interface{}, error)
}
// HTTP API interface
SyncData struct {
ReadExposedAll func(http.ResponseWriter, *http.Request)
ReadExposedInternal func(http.ResponseWriter, *http.Request)
ReadExposedSocial func(http.ResponseWriter, *http.Request)
}
)
func NewSyncData(h SyncDataAPI) *SyncData {
return &SyncData{
ReadExposedAll: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncDataReadExposedAll()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposedAll(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ReadExposedInternal: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncDataReadExposedInternal()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposedInternal(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ReadExposedSocial: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncDataReadExposedSocial()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposedSocial(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h SyncData) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/nodes/{nodeID}/modules/exposed/records/", h.ReadExposedAll)
r.Get("/nodes/{nodeID}/modules/{moduleID}/records/", h.ReadExposedInternal)
r.Get("/nodes/{nodeID}/modules/{moduleID}/records/activity-stream/", h.ReadExposedSocial)
})
}

View File

@@ -0,0 +1,76 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/go-chi/chi/v5"
"net/http"
)
type (
// Internal API interface
SyncStructureAPI interface {
ReadExposedInternal(context.Context, *request.SyncStructureReadExposedInternal) (interface{}, error)
ReadExposedSocial(context.Context, *request.SyncStructureReadExposedSocial) (interface{}, error)
}
// HTTP API interface
SyncStructure struct {
ReadExposedInternal func(http.ResponseWriter, *http.Request)
ReadExposedSocial func(http.ResponseWriter, *http.Request)
}
)
func NewSyncStructure(h SyncStructureAPI) *SyncStructure {
return &SyncStructure{
ReadExposedInternal: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncStructureReadExposedInternal()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposedInternal(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
ReadExposedSocial: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewSyncStructureReadExposedSocial()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.ReadExposedSocial(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, 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/exposed/", h.ReadExposedInternal)
r.Get("/nodes/{nodeID}/modules/exposed/activity-stream", h.ReadExposedSocial)
})
}

View File

@@ -0,0 +1,240 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/federation/types"
)
type (
ManageStructure struct{}
exposedModulePayload struct {
*types.ExposedModule
CanManageModule bool `json:"canManageModule"`
}
sharedModulePayload struct {
*types.SharedModule
CanMapModule bool `json:"canMapModule"`
}
moduleMappingPayload struct {
*types.ModuleMapping
}
)
func (ManageStructure) New() *ManageStructure {
return &ManageStructure{}
}
func (ctrl ManageStructure) CreateExposed(ctx context.Context, r *request.ManageStructureCreateExposed) (interface{}, error) {
var (
mod = &types.ExposedModule{
NodeID: r.NodeID,
ComposeModuleID: r.ComposeModuleID,
ComposeNamespaceID: r.ComposeNamespaceID,
Name: r.Name,
Handle: r.Handle,
Fields: r.Fields,
}
)
em, err := (service.DefaultExposedModule).Create(ctx, mod)
return ctrl.makePayload(ctx, em, err)
}
func (ctrl ManageStructure) ReadExposed(ctx context.Context, r *request.ManageStructureReadExposed) (interface{}, error) {
em, err := (service.DefaultExposedModule).FindByID(ctx, r.GetNodeID(), r.GetModuleID())
return ctrl.makePayload(ctx, em, err)
}
func (ctrl ManageStructure) UpdateExposed(ctx context.Context, r *request.ManageStructureUpdateExposed) (interface{}, error) {
var (
em = &types.ExposedModule{
ID: r.ModuleID,
NodeID: r.NodeID,
ComposeModuleID: r.ComposeModuleID,
ComposeNamespaceID: r.ComposeNamespaceID,
Name: r.Name,
Handle: r.Handle,
Fields: r.Fields,
}
)
em, err := (service.DefaultExposedModule).Update(ctx, em)
return ctrl.makePayload(ctx, em, err)
}
func (ctrl ManageStructure) RemoveExposed(ctx context.Context, r *request.ManageStructureRemoveExposed) (interface{}, error) {
em, err := (service.DefaultExposedModule).DeleteByID(ctx, r.NodeID, r.ModuleID)
return ctrl.makePayload(ctx, em, err)
}
func (ctrl ManageStructure) ReadShared(ctx context.Context, r *request.ManageStructureReadShared) (interface{}, error) {
list, err := (service.DefaultSharedModule).FindByID(ctx, r.GetNodeID(), r.GetModuleID())
return ctrl.makePayload(ctx, list, err)
}
func (ctrl ManageStructure) CreateMappings(ctx context.Context, r *request.ManageStructureCreateMappings) (interface{}, error) {
mm := &types.ModuleMapping{
NodeID: r.NodeID,
FederationModuleID: r.ModuleID,
ComposeModuleID: r.ComposeModuleID,
ComposeNamespaceID: r.ComposeNamespaceID,
FieldMapping: r.Fields,
}
// check if it exists, do an upsert
existing, _, err := service.DefaultModuleMapping.Find(ctx, types.ModuleMappingFilter{
NodeID: r.NodeID,
ComposeModuleID: r.ComposeModuleID,
ComposeNamespaceID: r.ComposeNamespaceID,
FederationModuleID: r.ModuleID,
})
if err != nil {
return nil, err
}
if len(existing) > 0 {
// do an update
return service.DefaultModuleMapping.Update(ctx, mm)
}
return service.DefaultModuleMapping.Create(ctx, mm)
}
// ReadMappings outputs an object with the module mappings per each federation module
func (ctrl ManageStructure) ReadMappings(ctx context.Context, r *request.ManageStructureReadMappings) (interface{}, error) {
f := types.ModuleMappingFilter{
NodeID: r.NodeID,
FederationModuleID: r.ModuleID,
}
if r.ComposeModuleID > 0 {
f.ComposeModuleID = r.ComposeModuleID
}
set, _, err := service.DefaultModuleMapping.Find(ctx, f)
if err != nil {
return nil, err
}
if len(set) != 1 {
return nil, service.ModuleMappingErrNotFound()
}
return set[0], nil
}
// ListAll show the list of exposed / shared / mapped modules
func (ctrl ManageStructure) ListAll(ctx context.Context, r *request.ManageStructureListAll) (interface{}, error) {
var (
list interface{}
err error
)
switch true {
case r.Exposed:
list, _, err = (service.DefaultExposedModule).Find(ctx, types.ExposedModuleFilter{
NodeID: r.NodeID,
})
break
case r.Shared:
list, _, err = (service.DefaultSharedModule).Find(ctx, types.SharedModuleFilter{
NodeID: r.NodeID,
})
break
case r.Mapped:
list, _, err = (service.DefaultModuleMapping).Find(ctx, types.ModuleMappingFilter{
NodeID: r.NodeID,
})
break
default:
return nil, service.ExposedModuleErrRequestParametersInvalid()
}
return ctrl.makePayload(ctx, list, err)
}
func (ctrl ManageStructure) makeSharedModulePayload(ctx context.Context, sm *types.SharedModule, err error) (*sharedModulePayload, error) {
if err != nil || sm == nil {
return nil, err
}
return &sharedModulePayload{
SharedModule: sm,
CanMapModule: service.DefaultAccessControl.CanMapSharedModule(ctx, sm),
}, nil
}
func (ctrl ManageStructure) makeExposedModulePayload(ctx context.Context, em *types.ExposedModule, err error) (*exposedModulePayload, error) {
if err != nil || em == nil {
return nil, err
}
return &exposedModulePayload{
ExposedModule: em,
CanManageModule: service.DefaultAccessControl.CanManageExposedModule(ctx, em),
}, nil
}
func (ctrl ManageStructure) makeModuleMappingPayload(ctx context.Context, em *types.ModuleMapping, err error) (*moduleMappingPayload, error) {
if err != nil || em == nil {
return nil, err
}
return &moduleMappingPayload{
ModuleMapping: em,
}, nil
}
func (ctrl ManageStructure) makePayload(ctx context.Context, payload interface{}, err error) (interface{}, error) {
if err != nil || payload == nil {
return nil, err
}
switch payload.(type) {
case types.SharedModuleSet:
set := make([]sharedModulePayload, len(payload.(types.SharedModuleSet)))
for i, d := range payload.(types.SharedModuleSet) {
mp, _ := ctrl.makeSharedModulePayload(ctx, d, err)
set[i] = *mp
}
return set, err
case types.ExposedModuleSet:
set := make([]exposedModulePayload, len(payload.(types.ExposedModuleSet)))
for i, d := range payload.(types.ExposedModuleSet) {
mp, _ := ctrl.makeExposedModulePayload(ctx, d, err)
set[i] = *mp
}
return set, err
case types.ModuleMappingSet:
set := make([]moduleMappingPayload, len(payload.(types.ModuleMappingSet)))
for i, d := range payload.(types.ModuleMappingSet) {
mp, _ := ctrl.makeModuleMappingPayload(ctx, d, err)
set[i] = *mp
}
return set, err
case *types.ExposedModule:
return ctrl.makeExposedModulePayload(ctx, payload.(*types.ExposedModule), err)
case *types.SharedModule:
return ctrl.makeSharedModulePayload(ctx, payload.(*types.SharedModule), err)
default:
return nil, nil
}
}

View File

@@ -0,0 +1,156 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/filter"
)
type (
nodeServicer interface {
Search(ctx context.Context, f types.NodeFilter) (types.NodeSet, types.NodeFilter, error)
Create(ctx context.Context, n *types.Node) (*types.Node, error)
CreateFromPairingURI(ctx context.Context, uri string) (*types.Node, error)
Read(ctx context.Context, ID uint64) (*types.Node, error)
Update(ctx context.Context, n *types.Node) (*types.Node, error)
DeleteByID(ctx context.Context, ID uint64) error
UndeleteByID(ctx context.Context, ID uint64) error
Pair(ctx context.Context, nodeID uint64) error
HandshakeConfirm(ctx context.Context, nodeID uint64) error
HandshakeComplete(ctx context.Context, nodeID uint64, token string) error
RegenerateNodeURI(ctx context.Context, nodeID uint64) (string, error)
}
Node struct {
svcNode nodeServicer
}
moduleSetPayload struct {
Filter types.NodeFilter `json:"filter"`
Set []*nodePayload `json:"set"`
}
nodePayload struct {
*types.Node
CanManageNode bool `json:"canManageNode"`
CanCreateModule bool `json:"canCreateModule"`
}
)
func (Node) New() *Node {
return &Node{
svcNode: service.DefaultNode,
}
}
func (ctrl Node) Search(ctx context.Context, r *request.NodeSearch) (interface{}, error) {
var (
err error
f = types.NodeFilter{
Query: r.Query,
Status: r.Status,
}
)
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
return nil, err
}
f.IncTotal = r.IncTotal
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
return nil, err
}
set, f, err := ctrl.svcNode.Search(ctx, f)
return ctrl.makeFilterPayload(ctx, set, f, err)
}
func (ctrl Node) Create(ctx context.Context, r *request.NodeCreate) (interface{}, error) {
if r.PairingURI != "" {
return ctrl.svcNode.CreateFromPairingURI(ctx, r.PairingURI)
} else {
n := &types.Node{
BaseURL: r.BaseURL,
Name: r.Name,
Contact: r.Contact,
}
n, err := ctrl.svcNode.Create(ctx, n)
return ctrl.makePayload(ctx, n, err)
}
}
func (ctrl Node) Read(ctx context.Context, r *request.NodeRead) (interface{}, error) {
n, err := ctrl.svcNode.Read(ctx, r.NodeID)
return ctrl.makePayload(ctx, n, err)
}
func (ctrl Node) Update(ctx context.Context, r *request.NodeUpdate) (interface{}, error) {
n, err := ctrl.svcNode.Update(ctx, &types.Node{
ID: r.NodeID,
Name: r.Name,
BaseURL: r.BaseURL,
Contact: r.Contact,
})
return ctrl.makePayload(ctx, n, err)
}
func (ctrl Node) Delete(ctx context.Context, r *request.NodeDelete) (interface{}, error) {
return api.OK(), ctrl.svcNode.DeleteByID(ctx, r.NodeID)
}
func (ctrl Node) Undelete(ctx context.Context, r *request.NodeUndelete) (interface{}, error) {
return api.OK(), ctrl.svcNode.UndeleteByID(ctx, r.NodeID)
}
func (ctrl Node) GenerateURI(ctx context.Context, r *request.NodeGenerateURI) (interface{}, error) {
return ctrl.svcNode.RegenerateNodeURI(ctx, r.NodeID)
}
func (ctrl Node) Pair(ctx context.Context, r *request.NodePair) (interface{}, error) {
return api.OK(), ctrl.svcNode.Pair(ctx, r.NodeID)
}
func (ctrl Node) HandshakeConfirm(ctx context.Context, r *request.NodeHandshakeConfirm) (interface{}, error) {
return api.OK(), ctrl.svcNode.HandshakeConfirm(ctx, r.NodeID)
}
func (ctrl Node) HandshakeComplete(ctx context.Context, r *request.NodeHandshakeComplete) (interface{}, error) {
return api.OK(), ctrl.svcNode.HandshakeComplete(ctx, r.NodeID, r.AuthToken)
}
func (ctrl Node) makePayload(ctx context.Context, m *types.Node, err error) (*nodePayload, error) {
if err != nil || m == nil {
return nil, err
}
return &nodePayload{
Node: m,
CanCreateModule: service.DefaultAccessControl.CanCreateModuleOnNode(ctx, m),
CanManageNode: service.DefaultAccessControl.CanManageNode(ctx, m),
}, nil
}
func (ctrl Node) makeFilterPayload(ctx context.Context, nn types.NodeSet, f types.NodeFilter, err error) (*moduleSetPayload, error) {
if err != nil {
return nil, err
}
msp := &moduleSetPayload{Filter: f, Set: make([]*nodePayload, len(nn))}
for i := range nn {
msp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
}
return msp, nil
}

View File

@@ -0,0 +1,28 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/pkg/api"
)
type (
handshakeInitializer interface {
HandshakeInit(context.Context, uint64, string, uint64, string) error
}
NodeHandshake struct {
svcNode handshakeInitializer
}
)
func (NodeHandshake) New() *NodeHandshake {
return &NodeHandshake{
svcNode: service.DefaultNode,
}
}
func (ctrl NodeHandshake) Initialize(ctx context.Context, r *request.NodeHandshakeInitialize) (interface{}, error) {
return api.OK(), ctrl.svcNode.HandshakeInit(ctx, r.NodeID, r.PairToken, r.SharedNodeID, r.AuthToken)
}

View File

@@ -0,0 +1,69 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/rbac"
)
type (
Permissions struct {
ac permissionsAccessController
}
permissionsAccessController interface {
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
List() []map[string]string
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
FindRules(ctx context.Context, roleID uint64, rr ...string) (rbac.RuleSet, error)
Grant(ctx context.Context, rr ...*rbac.Rule) error
}
)
func (Permissions) New() *Permissions {
return &Permissions{
ac: service.DefaultAccessControl,
}
}
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
return ctrl.ac.Effective(ctx, types.Component{}), nil
}
func (ctrl Permissions) Trace(ctx context.Context, r *request.PermissionsTrace) (interface{}, error) {
return ctrl.ac.Trace(ctx, r.UserID, r.RoleID, r.Resource...)
}
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
return ctrl.ac.List(), nil
}
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
return ctrl.ac.FindRules(ctx, r.RoleID, r.Resource...)
}
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
rr, err := ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
if err != nil {
return nil, err
}
for _, r := range rr {
r.Access = rbac.Inherit
}
return api.OK(), ctrl.ac.Grant(ctx, rr...)
}
func (ctrl Permissions) Update(ctx context.Context, r *request.PermissionsUpdate) (interface{}, error) {
for _, rule := range r.Rules {
// Make sure everything is properly set
rule.RoleID = r.RoleID
}
return api.OK(), ctrl.ac.Grant(ctx, r.Rules...)
}

View File

@@ -0,0 +1,945 @@
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/federation/types"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
ManageStructureReadExposed struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
}
ManageStructureCreateExposed struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ComposeModuleID POST parameter
//
// Compose module id
ComposeModuleID uint64 `json:",string"`
// ComposeNamespaceID POST parameter
//
// Compose namespace id
ComposeNamespaceID uint64 `json:",string"`
// Name POST parameter
//
// Module name
Name string
// Handle POST parameter
//
// Module handle
Handle string
// Fields POST parameter
//
// Exposed module fields
Fields types.ModuleFieldSet
}
ManageStructureUpdateExposed struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
// ComposeModuleID POST parameter
//
// Compose module id
ComposeModuleID uint64 `json:",string"`
// ComposeNamespaceID POST parameter
//
// Compose namespace id
ComposeNamespaceID uint64 `json:",string"`
// Name POST parameter
//
// Module name
Name string
// Handle POST parameter
//
// Module handle
Handle string
// Fields POST parameter
//
// Exposed module fields
Fields types.ModuleFieldSet
}
ManageStructureRemoveExposed struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
}
ManageStructureReadShared struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
}
ManageStructureCreateMappings struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
// ComposeModuleID POST parameter
//
// Compose module id
ComposeModuleID uint64 `json:",string"`
// ComposeNamespaceID POST parameter
//
// Compose namespace id
ComposeNamespaceID uint64 `json:",string"`
// Fields POST parameter
//
// Exposed module fields
Fields types.ModuleFieldMappingSet
}
ManageStructureReadMappings struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
// ComposeModuleID GET parameter
//
// Compose module id
ComposeModuleID uint64 `json:",string"`
}
ManageStructureListAll struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// Shared GET parameter
//
// List shared modules
Shared bool
// Exposed GET parameter
//
// List exposed modules
Exposed bool
// Mapped GET parameter
//
// List mapped modules
Mapped bool
}
)
// NewManageStructureReadExposed request
func NewManageStructureReadExposed() *ManageStructureReadExposed {
return &ManageStructureReadExposed{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadExposed) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadExposed) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadExposed) GetModuleID() uint64 {
return r.ModuleID
}
// Fill processes request and fills internal variables
func (r *ManageStructureReadExposed) Fill(req *http.Request) (err error) {
{
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
}
// NewManageStructureCreateExposed request
func NewManageStructureCreateExposed() *ManageStructureCreateExposed {
return &ManageStructureCreateExposed{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"composeModuleID": r.ComposeModuleID,
"composeNamespaceID": r.ComposeNamespaceID,
"name": r.Name,
"handle": r.Handle,
"fields": r.Fields,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetComposeModuleID() uint64 {
return r.ComposeModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetComposeNamespaceID() uint64 {
return r.ComposeNamespaceID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetName() string {
return r.Name
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetHandle() string {
return r.Handle
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateExposed) GetFields() types.ModuleFieldSet {
return r.Fields
}
// Fill processes request and fills internal variables
func (r *ManageStructureCreateExposed) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
//if val, ok := req.Form["fields[]"]; ok && len(val) > 0 {
// r.Fields, err = types.ModuleFieldSet(val), nil
// if err != nil {
// return err
// }
//}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewManageStructureUpdateExposed request
func NewManageStructureUpdateExposed() *ManageStructureUpdateExposed {
return &ManageStructureUpdateExposed{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
"composeModuleID": r.ComposeModuleID,
"composeNamespaceID": r.ComposeNamespaceID,
"name": r.Name,
"handle": r.Handle,
"fields": r.Fields,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetModuleID() uint64 {
return r.ModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetComposeModuleID() uint64 {
return r.ComposeModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetComposeNamespaceID() uint64 {
return r.ComposeNamespaceID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetName() string {
return r.Name
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetHandle() string {
return r.Handle
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureUpdateExposed) GetFields() types.ModuleFieldSet {
return r.Fields
}
// Fill processes request and fills internal variables
func (r *ManageStructureUpdateExposed) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
//if val, ok := req.Form["fields[]"]; ok && len(val) > 0 {
// r.Fields, err = types.ModuleFieldSet(val), nil
// if err != nil {
// return 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
}
// NewManageStructureRemoveExposed request
func NewManageStructureRemoveExposed() *ManageStructureRemoveExposed {
return &ManageStructureRemoveExposed{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureRemoveExposed) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureRemoveExposed) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureRemoveExposed) GetModuleID() uint64 {
return r.ModuleID
}
// Fill processes request and fills internal variables
func (r *ManageStructureRemoveExposed) Fill(req *http.Request) (err error) {
{
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
}
// NewManageStructureReadShared request
func NewManageStructureReadShared() *ManageStructureReadShared {
return &ManageStructureReadShared{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadShared) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadShared) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadShared) GetModuleID() uint64 {
return r.ModuleID
}
// Fill processes request and fills internal variables
func (r *ManageStructureReadShared) Fill(req *http.Request) (err error) {
{
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
}
// NewManageStructureCreateMappings request
func NewManageStructureCreateMappings() *ManageStructureCreateMappings {
return &ManageStructureCreateMappings{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
"composeModuleID": r.ComposeModuleID,
"composeNamespaceID": r.ComposeNamespaceID,
"fields": r.Fields,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) GetModuleID() uint64 {
return r.ModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) GetComposeModuleID() uint64 {
return r.ComposeModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) GetComposeNamespaceID() uint64 {
return r.ComposeNamespaceID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureCreateMappings) GetFields() types.ModuleFieldMappingSet {
return r.Fields
}
// Fill processes request and fills internal variables
func (r *ManageStructureCreateMappings) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["composeNamespaceID"]; ok && len(val) > 0 {
r.ComposeNamespaceID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
//if val, ok := req.Form["fields[]"]; ok && len(val) > 0 {
// r.Fields, err = types.ModuleFieldMappingSet(val), nil
// if err != nil {
// return 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
}
// NewManageStructureReadMappings request
func NewManageStructureReadMappings() *ManageStructureReadMappings {
return &ManageStructureReadMappings{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadMappings) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
"composeModuleID": r.ComposeModuleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadMappings) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadMappings) GetModuleID() uint64 {
return r.ModuleID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureReadMappings) GetComposeModuleID() uint64 {
return r.ComposeModuleID
}
// Fill processes request and fills internal variables
func (r *ManageStructureReadMappings) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["composeModuleID"]; ok && len(val) > 0 {
r.ComposeModuleID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return 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
}
// NewManageStructureListAll request
func NewManageStructureListAll() *ManageStructureListAll {
return &ManageStructureListAll{}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureListAll) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"shared": r.Shared,
"exposed": r.Exposed,
"mapped": r.Mapped,
}
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureListAll) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureListAll) GetShared() bool {
return r.Shared
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureListAll) GetExposed() bool {
return r.Exposed
}
// Auditable returns all auditable/loggable parameters
func (r ManageStructureListAll) GetMapped() bool {
return r.Mapped
}
// Fill processes request and fills internal variables
func (r *ManageStructureListAll) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["shared"]; ok && len(val) > 0 {
r.Shared, err = payload.ParseBool(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["exposed"]; ok && len(val) > 0 {
r.Exposed, err = payload.ParseBool(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["mapped"]; ok && len(val) > 0 {
r.Mapped, err = payload.ParseBool(val[0]), nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,801 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
NodeSearch struct {
// Query GET parameter
//
// Filter nodes by name and host
Query string
// Status GET parameter
//
// Filter by status
Status string
// Limit GET parameter
//
// Limit
Limit uint
// IncTotal GET parameter
//
// Include total counter
IncTotal bool
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
NodeCreate struct {
// BaseURL POST parameter
//
// Federation API base URL
BaseURL string
// Name POST parameter
//
// Name for this node
Name string
// Contact POST parameter
//
// Contact email
Contact string
// PairingURI POST parameter
//
// Pairing URI
PairingURI string
}
NodeRead struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodeGenerateURI struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodeUpdate struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
// Name POST parameter
//
// Name for this node
Name string
// Contact POST parameter
//
// Contact email
Contact string
// BaseURL POST parameter
//
// Federation API base URL
BaseURL string
}
NodeDelete struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodeUndelete struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodePair struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodeHandshakeConfirm struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
}
NodeHandshakeComplete struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
// AuthToken POST parameter
//
// Node A token
AuthToken string
}
)
// NewNodeSearch request
func NewNodeSearch() *NodeSearch {
return &NodeSearch{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) Auditable() map[string]interface{} {
return map[string]interface{}{
"query": r.Query,
"status": r.Status,
"limit": r.Limit,
"incTotal": r.IncTotal,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetStatus() string {
return r.Status
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetIncTotal() bool {
return r.IncTotal
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r NodeSearch) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *NodeSearch) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["status"]; ok && len(val) > 0 {
r.Status, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["incTotal"]; ok && len(val) > 0 {
r.IncTotal, err = payload.ParseBool(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
return err
}
// NewNodeCreate request
func NewNodeCreate() *NodeCreate {
return &NodeCreate{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeCreate) Auditable() map[string]interface{} {
return map[string]interface{}{
"baseURL": r.BaseURL,
"name": r.Name,
"contact": r.Contact,
"pairingURI": r.PairingURI,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeCreate) GetBaseURL() string {
return r.BaseURL
}
// Auditable returns all auditable/loggable parameters
func (r NodeCreate) GetName() string {
return r.Name
}
// Auditable returns all auditable/loggable parameters
func (r NodeCreate) GetContact() string {
return r.Contact
}
// Auditable returns all auditable/loggable parameters
func (r NodeCreate) GetPairingURI() string {
return r.PairingURI
}
// Fill processes request and fills internal variables
func (r *NodeCreate) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["baseURL"]; ok && len(val) > 0 {
r.BaseURL, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["contact"]; ok && len(val) > 0 {
r.Contact, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["pairingURI"]; ok && len(val) > 0 {
r.PairingURI, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["baseURL"]; ok && len(val) > 0 {
r.BaseURL, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["contact"]; ok && len(val) > 0 {
r.Contact, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["pairingURI"]; ok && len(val) > 0 {
r.PairingURI, err = val[0], nil
if err != nil {
return err
}
}
}
return err
}
// NewNodeRead request
func NewNodeRead() *NodeRead {
return &NodeRead{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeRead) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodeRead) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeGenerateURI request
func NewNodeGenerateURI() *NodeGenerateURI {
return &NodeGenerateURI{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeGenerateURI) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeGenerateURI) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodeGenerateURI) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeUpdate request
func NewNodeUpdate() *NodeUpdate {
return &NodeUpdate{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeUpdate) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"name": r.Name,
"contact": r.Contact,
"baseURL": r.BaseURL,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeUpdate) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r NodeUpdate) GetName() string {
return r.Name
}
// Auditable returns all auditable/loggable parameters
func (r NodeUpdate) GetContact() string {
return r.Contact
}
// Auditable returns all auditable/loggable parameters
func (r NodeUpdate) GetBaseURL() string {
return r.BaseURL
}
// Fill processes request and fills internal variables
func (r *NodeUpdate) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["contact"]; ok && len(val) > 0 {
r.Contact, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["baseURL"]; ok && len(val) > 0 {
r.BaseURL, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["name"]; ok && len(val) > 0 {
r.Name, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["contact"]; ok && len(val) > 0 {
r.Contact, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["baseURL"]; ok && len(val) > 0 {
r.BaseURL, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeDelete request
func NewNodeDelete() *NodeDelete {
return &NodeDelete{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeDelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeDelete) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodeDelete) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeUndelete request
func NewNodeUndelete() *NodeUndelete {
return &NodeUndelete{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeUndelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeUndelete) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodeUndelete) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodePair request
func NewNodePair() *NodePair {
return &NodePair{}
}
// Auditable returns all auditable/loggable parameters
func (r NodePair) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodePair) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodePair) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeHandshakeConfirm request
func NewNodeHandshakeConfirm() *NodeHandshakeConfirm {
return &NodeHandshakeConfirm{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeConfirm) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeConfirm) GetNodeID() uint64 {
return r.NodeID
}
// Fill processes request and fills internal variables
func (r *NodeHandshakeConfirm) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewNodeHandshakeComplete request
func NewNodeHandshakeComplete() *NodeHandshakeComplete {
return &NodeHandshakeComplete{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeComplete) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"authToken": r.AuthToken,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeComplete) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeComplete) GetAuthToken() string {
return r.AuthToken
}
// Fill processes request and fills internal variables
func (r *NodeHandshakeComplete) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["authToken"]; ok && len(val) > 0 {
r.AuthToken, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["authToken"]; ok && len(val) > 0 {
r.AuthToken, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,180 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
NodeHandshakeInitialize struct {
// NodeID PATH parameter
//
// NodeID
NodeID uint64 `json:",string"`
// PairToken POST parameter
//
// Pairing token to authenticate handshake initialization request
PairToken string
// SharedNodeID POST parameter
//
// Remote (invoker's) node ID
SharedNodeID uint64 `json:",string"`
// AuthToken POST parameter
//
// Authentication token so that remote
AuthToken string
}
)
// NewNodeHandshakeInitialize request
func NewNodeHandshakeInitialize() *NodeHandshakeInitialize {
return &NodeHandshakeInitialize{}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeInitialize) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"pairToken": r.PairToken,
"sharedNodeID": r.SharedNodeID,
"authToken": r.AuthToken,
}
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeInitialize) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeInitialize) GetPairToken() string {
return r.PairToken
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeInitialize) GetSharedNodeID() uint64 {
return r.SharedNodeID
}
// Auditable returns all auditable/loggable parameters
func (r NodeHandshakeInitialize) GetAuthToken() string {
return r.AuthToken
}
// Fill processes request and fills internal variables
func (r *NodeHandshakeInitialize) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
if val, ok := req.MultipartForm.Value["pairToken"]; ok && len(val) > 0 {
r.PairToken, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["sharedNodeID"]; ok && len(val) > 0 {
r.SharedNodeID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.MultipartForm.Value["authToken"]; ok && len(val) > 0 {
r.AuthToken, err = val[0], nil
if err != nil {
return err
}
}
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["pairToken"]; ok && len(val) > 0 {
r.PairToken, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["sharedNodeID"]; ok && len(val) > 0 {
r.SharedNodeID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := req.Form["authToken"]; ok && len(val) > 0 {
r.AuthToken, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,384 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
PermissionsList struct {
}
PermissionsEffective struct {
// Resource GET parameter
//
// Show only rules for a specific resource
Resource string
}
PermissionsTrace struct {
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
// UserID GET parameter
//
//
UserID uint64 `json:",string"`
// RoleID GET parameter
//
//
RoleID []uint64
}
PermissionsRead struct {
// RoleID PATH parameter
//
// Role ID
RoleID uint64 `json:",string"`
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
}
PermissionsDelete struct {
// RoleID PATH parameter
//
// Role ID
RoleID uint64 `json:",string"`
}
PermissionsUpdate struct {
// RoleID PATH parameter
//
// Role ID
RoleID uint64 `json:",string"`
// Rules POST parameter
//
// List of permission rules to set
Rules rbac.RuleSet
}
)
// NewPermissionsList request
func NewPermissionsList() *PermissionsList {
return &PermissionsList{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsList) Auditable() map[string]interface{} {
return map[string]interface{}{}
}
// Fill processes request and fills internal variables
func (r *PermissionsList) Fill(req *http.Request) (err error) {
return err
}
// NewPermissionsEffective request
func NewPermissionsEffective() *PermissionsEffective {
return &PermissionsEffective{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsEffective) Auditable() map[string]interface{} {
return map[string]interface{}{
"resource": r.Resource,
}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsEffective) GetResource() string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["resource"]; ok && len(val) > 0 {
r.Resource, err = val[0], nil
if err != nil {
return err
}
}
}
return err
}
// NewPermissionsTrace request
func NewPermissionsTrace() *PermissionsTrace {
return &PermissionsTrace{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsTrace) Auditable() map[string]interface{} {
return map[string]interface{}{
"resource": r.Resource,
"userID": r.UserID,
"roleID": r.RoleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsTrace) GetResource() []string {
return r.Resource
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsTrace) GetUserID() uint64 {
return r.UserID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsTrace) GetRoleID() []uint64 {
return r.RoleID
}
// Fill processes request and fills internal variables
func (r *PermissionsTrace) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
if val, ok := tmp["userID"]; ok && len(val) > 0 {
r.UserID, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["roleID[]"]; ok {
r.RoleID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
} else if val, ok := tmp["roleID"]; ok {
r.RoleID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
}
}
return err
}
// NewPermissionsRead request
func NewPermissionsRead() *PermissionsRead {
return &PermissionsRead{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"resource": r.Resource,
}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetResource() []string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "roleID")
r.RoleID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewPermissionsDelete request
func NewPermissionsDelete() *PermissionsDelete {
return &PermissionsDelete{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsDelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsDelete) GetRoleID() uint64 {
return r.RoleID
}
// Fill processes request and fills internal variables
func (r *PermissionsDelete) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "roleID")
r.RoleID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewPermissionsUpdate request
func NewPermissionsUpdate() *PermissionsUpdate {
return &PermissionsUpdate{}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsUpdate) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"rules": r.Rules,
}
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsUpdate) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsUpdate) GetRules() rbac.RuleSet {
return r.Rules
}
// Fill processes request and fills internal variables
func (r *PermissionsUpdate) Fill(req *http.Request) (err error) {
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
// Caching 32MB to memory, the rest to disk
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
return err
} else if err == nil {
// Multipart params
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
//if val, ok := req.Form["rules[]"]; ok && len(val) > 0 {
// r.Rules, err = rbac.RuleSet(val), nil
// if err != nil {
// return err
// }
//}
}
{
var val string
// path params
val = chi.URLParam(req, "roleID")
r.RoleID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,468 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
SyncDataReadExposedAll struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// LastSync GET parameter
//
// Last sync timestamp
LastSync uint64 `json:",string"`
// Query GET parameter
//
// Search query
Query string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
SyncDataReadExposedInternal struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
// LastSync GET parameter
//
// Last sync timestamp
LastSync uint64 `json:",string"`
// Query GET parameter
//
// Search query
Query string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
SyncDataReadExposedSocial struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// ModuleID PATH parameter
//
// Module ID
ModuleID uint64 `json:",string"`
// LastSync GET parameter
//
// Last sync timestamp
LastSync uint64 `json:",string"`
// Query GET parameter
//
// Search query
Query string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
)
// NewSyncDataReadExposedAll request
func NewSyncDataReadExposedAll() *SyncDataReadExposedAll {
return &SyncDataReadExposedAll{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"lastSync": r.LastSync,
"query": r.Query,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetLastSync() uint64 {
return r.LastSync
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedAll) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *SyncDataReadExposedAll) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["lastSync"]; ok && len(val) > 0 {
r.LastSync, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewSyncDataReadExposedInternal request
func NewSyncDataReadExposedInternal() *SyncDataReadExposedInternal {
return &SyncDataReadExposedInternal{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
"lastSync": r.LastSync,
"query": r.Query,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetModuleID() uint64 {
return r.ModuleID
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetLastSync() uint64 {
return r.LastSync
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedInternal) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *SyncDataReadExposedInternal) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["lastSync"]; ok && len(val) > 0 {
r.LastSync, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "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
}
// NewSyncDataReadExposedSocial request
func NewSyncDataReadExposedSocial() *SyncDataReadExposedSocial {
return &SyncDataReadExposedSocial{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"moduleID": r.ModuleID,
"lastSync": r.LastSync,
"query": r.Query,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetModuleID() uint64 {
return r.ModuleID
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetLastSync() uint64 {
return r.LastSync
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r SyncDataReadExposedSocial) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *SyncDataReadExposedSocial) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["lastSync"]; ok && len(val) > 0 {
r.LastSync, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "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

@@ -0,0 +1,301 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/payload"
"github.com/go-chi/chi/v5"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
SyncStructureReadExposedInternal struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// LastSync GET parameter
//
// Last sync timestamp
LastSync uint64 `json:",string"`
// Query GET parameter
//
// Search query
Query string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
SyncStructureReadExposedSocial struct {
// NodeID PATH parameter
//
// Node ID
NodeID uint64 `json:",string"`
// LastSync GET parameter
//
// Last sync timestamp
LastSync uint64 `json:",string"`
// Query GET parameter
//
// Search query
Query string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
)
// NewSyncStructureReadExposedInternal request
func NewSyncStructureReadExposedInternal() *SyncStructureReadExposedInternal {
return &SyncStructureReadExposedInternal{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"lastSync": r.LastSync,
"query": r.Query,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetLastSync() uint64 {
return r.LastSync
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedInternal) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *SyncStructureReadExposedInternal) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["lastSync"]; ok && len(val) > 0 {
r.LastSync, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewSyncStructureReadExposedSocial request
func NewSyncStructureReadExposedSocial() *SyncStructureReadExposedSocial {
return &SyncStructureReadExposedSocial{}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) Auditable() map[string]interface{} {
return map[string]interface{}{
"nodeID": r.NodeID,
"lastSync": r.LastSync,
"query": r.Query,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetNodeID() uint64 {
return r.NodeID
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetLastSync() uint64 {
return r.LastSync
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetQuery() string {
return r.Query
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r SyncStructureReadExposedSocial) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *SyncStructureReadExposedSocial) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["lastSync"]; ok && len(val) > 0 {
r.LastSync, err = payload.ParseUint64(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["query"]; ok && len(val) > 0 {
r.Query, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "nodeID")
r.NodeID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,30 @@
package rest
import (
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/go-chi/chi/v5"
"github.com/cortezaproject/corteza/server/federation/rest/handlers"
"github.com/cortezaproject/corteza/server/pkg/auth"
)
func MountRoutes(opts options.LimitOpt) func(r chi.Router) {
return func(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewNodeHandshake(NodeHandshake{}.New()).MountRoutes(r)
})
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(auth.HttpTokenValidator("api"))
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewNode(Node{}.New()).MountRoutes(r)
handlers.NewManageStructure((ManageStructure{}.New())).MountRoutes(r)
handlers.NewSyncData((SyncData{}.New(opts))).MountRoutes(r)
handlers.NewSyncStructure((SyncStructure{}.New())).MountRoutes(r)
})
}
}

View File

@@ -0,0 +1,301 @@
package rest
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/cortezaproject/corteza/server/pkg/options"
cs "github.com/cortezaproject/corteza/server/compose/service"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/federation"
"github.com/cortezaproject/corteza/server/pkg/filter"
ss "github.com/cortezaproject/corteza/server/system/service"
st "github.com/cortezaproject/corteza/server/system/types"
)
type (
SyncData struct {
opts options.LimitOpt
}
recordPayload struct {
*ct.Record
Records ct.RecordSet `json:"records,omitempty"`
CanUpdateRecord bool `json:"canUpdateRecord"`
CanDeleteRecord bool `json:"canDeleteRecord"`
}
listRecordResponse struct {
Filter *ct.RecordFilter `json:"filter,omitempty"`
Set *ct.RecordSet `json:"set"`
}
listResponse struct {
Set *responseSet `json:"set"`
}
responseSet []*moduleResponse
moduleResponse struct {
Type string `json:"type"`
Rel string `json:"rel"`
Href string `json:"href"`
}
)
func (SyncData) New(opts options.LimitOpt) *SyncData {
return &SyncData{
opts: opts,
}
}
func (ctrl SyncData) ReadExposedAll(ctx context.Context, r *request.SyncDataReadExposedAll) (interface{}, error) {
// todo - handle paging
var (
err error
node *types.Node
)
if node, err = service.DefaultNode.FindBySharedNodeID(ctx, r.NodeID); err != nil {
return nil, err
}
s, _, err := service.DefaultExposedModule.Find(ctx, types.ExposedModuleFilter{NodeID: node.ID})
if err != nil {
return nil, err
}
composeModuleList := make(map[uint64][]uint64, len(s))
err = s.Walk(func(em *types.ExposedModule) error {
composeModuleList[em.ComposeModuleID] = []uint64{em.ComposeNamespaceID, em.ID}
return nil
})
if err != nil || len(composeModuleList) == 0 {
return nil, err
}
recordQuery := buildLastSyncQuery(r.LastSync)
responseSet := responseSet{}
for composeModuleID, idList := range composeModuleList {
rf := ct.RecordFilter{
NamespaceID: idList[0],
ModuleID: composeModuleID,
Query: recordQuery,
Paging: filter.Paging{Limit: 1},
}
// todo - handle error properly
// @todo !!!
if list, _, err := (cs.Record(cs.RecordOptions{LimitRecords: ctrl.opts.RecordCountPerModule})).Find(ctx, rf); err != nil || len(list) == 0 {
continue
}
// generate url
moduleResponse := &moduleResponse{
Type: "GET",
Rel: "Federation Module",
Href: fmt.Sprintf("/nodes/%d/modules/%d/records/", node.ID, idList[1]),
}
responseSet = append(responseSet, moduleResponse)
}
return listResponse{&responseSet}, nil
}
// ReadExposedInternal fetches all the data - records (with paging)
// for an exposed module in an internal format
func (ctrl SyncData) ReadExposedInternal(ctx context.Context, r *request.SyncDataReadExposedInternal) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
payload, err := ctrl.readExposed(ctx, r)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
fEncoder := federation.NewEncoder(w, service.DefaultOptions)
err = fEncoder.Encode(payload, federation.CortezaInternalData)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
return
}, nil
}
// ReadExposedInternal fetches all the data - records (with paging)
// for an exposed module in activity streams format
func (ctrl SyncData) ReadExposedSocial(ctx context.Context, r *request.SyncDataReadExposedSocial) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
rr := request.SyncDataReadExposedInternal{
NodeID: r.NodeID,
ModuleID: r.ModuleID,
LastSync: r.LastSync,
Query: r.Query,
Limit: r.Limit,
PageCursor: r.PageCursor,
Sort: r.Sort,
}
payload, err := ctrl.readExposed(ctx, &rr)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
fEncoder := federation.NewEncoder(w, service.DefaultOptions)
err = fEncoder.Encode(payload, federation.ActivityStreamsData)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
return
}, nil
}
// readExposed fetches all the data - records (with paging) for an exposed module in an internal format
func (ctrl SyncData) readExposed(ctx context.Context, r *request.SyncDataReadExposedInternal) (interface{}, error) {
var (
err error
em *types.ExposedModule
users st.UserSet
node *types.Node
)
if node, err = service.DefaultNode.FindBySharedNodeID(ctx, r.NodeID); err != nil {
return nil, err
}
if em, err = service.DefaultExposedModule.FindByID(ctx, r.NodeID, r.ModuleID); err != nil {
return nil, err
}
if users, _, err = ss.DefaultUser.Find(ctx, st.UserFilter{}); err != nil {
return nil, err
}
users, _ = users.Filter(func(u *st.User) (bool, error) {
return strings.Contains(u.Handle, "federation_"), nil
})
users = append(users, auth.FederationUser())
query := buildLastSyncQuery(r.LastSync)
ignoredUsersQuery := buildIgnoredUsersQuery(users)
if ignoredUsersQuery != "" {
if query != "" {
query += fmt.Sprintf(" AND %s", ignoredUsersQuery)
} else {
query = ignoredUsersQuery
}
}
f := ct.RecordFilter{
NamespaceID: em.ComposeNamespaceID,
ModuleID: em.ComposeModuleID,
Query: query,
Deleted: filter.StateInclusive,
}
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
return nil, err
}
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
return nil, err
}
// @todo !!!
list, f, err := (cs.Record(cs.RecordOptions{LimitRecords: ctrl.opts.RecordCountPerModule})).Find(ctx, f)
if err != nil {
return nil, err
}
// do the actual field filtering
err = list.Walk(filterExposedFields(em))
if err != nil {
return nil, err
}
return federation.ListDataPayload{
NodeID: node.ID,
ModuleID: em.ID,
Filter: &f,
Set: &list,
}, nil
}
func buildLastSyncQuery(ts uint64) string {
if ts == 0 {
return ""
}
t := time.Unix(int64(ts), 0)
if t.IsZero() {
return ""
}
return fmt.Sprintf(
"(updated_at >= '%s' OR created_at >= '%s' OR deleted_at >= '%s')",
t.UTC().Format(time.RFC3339),
t.UTC().Format(time.RFC3339),
t.UTC().Format(time.RFC3339))
}
func buildIgnoredUsersQuery(users st.UserSet) string {
query := ""
if len(users) == 0 {
return query
}
for i, u := range users {
query += fmt.Sprintf("createdBy != %d", u.ID)
if i < len(users)-1 {
query += " AND "
}
}
return fmt.Sprintf("(%s)", query)
}
// filterExposedFields omits the fields that are not exposed as defined
// in the exposed module definition in store
func filterExposedFields(em *types.ExposedModule) func(r *ct.Record) error {
return func(r *ct.Record) error {
var err error
r.Values, err = r.Values.Filter(func(rv *ct.RecordValue) (bool, error) {
return em.Fields.HasField(rv.Name)
})
return err
}
}

View File

@@ -0,0 +1,129 @@
package rest
import (
"context"
"net/http"
"github.com/cortezaproject/corteza/server/federation/rest/request"
"github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/federation"
"github.com/cortezaproject/corteza/server/pkg/filter"
)
type (
SyncStructure struct{}
)
func (SyncStructure) New() *SyncStructure {
return &SyncStructure{}
}
// ReadExposedInternal gets the exposed module info and serves
// the internal Corteza format of the structure
func (ctrl SyncStructure) ReadExposedInternal(ctx context.Context, r *request.SyncStructureReadExposedInternal) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
var (
err error
ef federation.EncodingFormat = federation.CortezaInternalStructure
)
w.Header().Add("Content-Type", "application/json")
fEncoder := federation.NewEncoder(w, service.DefaultOptions)
payload, err := ctrl.readExposedAll(ctx, r)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
err = fEncoder.Encode(*payload, ef)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
return
}, nil
}
// ReadExposedSocial gets the exposed module info and serves
// the activity streams format of the structure
func (ctrl SyncStructure) ReadExposedSocial(ctx context.Context, r *request.SyncStructureReadExposedSocial) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
var (
err error
ef federation.EncodingFormat = federation.ActivityStreamsStructure
)
w.Header().Add("Content-Type", "application/json")
fEncoder := federation.NewEncoder(w, service.DefaultOptions)
rr := request.SyncStructureReadExposedInternal{
NodeID: r.NodeID,
LastSync: r.LastSync,
Query: r.Query,
Limit: r.Limit,
PageCursor: r.PageCursor,
Sort: r.Sort,
}
payload, err := ctrl.readExposedAll(ctx, &rr)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
err = fEncoder.Encode(*payload, ef)
if err != nil {
errors.ServeHTTP(w, req, err, false)
return
}
return
}, nil
}
// readExposedAll fetches the exposed modules for the specific node
func (ctrl SyncStructure) readExposedAll(ctx context.Context, r *request.SyncStructureReadExposedInternal) (*federation.ListStructurePayload, error) {
var (
err error
node *types.Node
)
if node, err = service.DefaultNode.FindBySharedNodeID(ctx, r.NodeID); err != nil {
return nil, err
}
f := types.ExposedModuleFilter{
NodeID: node.ID,
LastSync: r.LastSync,
}
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
return nil, err
}
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
return nil, err
}
list, f, err := (service.ExposedModule()).Find(ctx, f)
if err != nil {
return nil, err
}
return &federation.ListStructurePayload{
NodeID: node.ID,
Filter: &f,
Set: &list,
}, nil
}

View File

@@ -0,0 +1,622 @@
package service
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
internalAuth "github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/store"
systemTypes "github.com/cortezaproject/corteza/server/system/types"
"github.com/spf13/cast"
"strings"
)
type (
rbacService interface {
Can(rbac.Session, string, rbac.Resource) bool
Trace(rbac.Session, string, rbac.Resource) *rbac.Trace
Grant(context.Context, ...*rbac.Rule) error
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
}
accessControl struct {
actionlog actionlog.Recorder
store store.Storer
rbac rbacService
}
)
func AccessControl(s store.Storer) *accessControl {
return &accessControl{
store: s,
rbac: rbac.Global(),
actionlog: DefaultActionlog,
}
}
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
}
// Effective returns a list of effective permissions for all given resource
//
// This function is auto-generated
func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee rbac.EffectiveSet) {
for _, res := range rr {
r := res.RbacResource()
for op := range rbacResourceOperations(r) {
ee.Push(r, op, svc.can(ctx, op, res))
}
}
return
}
// Evaluate returns a list of permissions evaluated for the given user/roles combo
//
// This function is auto-generated
func (svc accessControl) Trace(ctx context.Context, userID uint64, roles []uint64, rr ...string) (ee []*rbac.Trace, err error) {
// Reusing the grant permission since this is who the feature is for
if !svc.CanGrant(ctx) {
// @todo should be altered to check grant permissions PER resource
return nil, AccessControlErrNotAllowedToSetPermissions()
}
var (
resource rbac.Resource
resources []rbac.Resource
members systemTypes.RoleMemberSet
)
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resource, err = svc.resourceLoader(ctx, r)
if err != nil {
return
}
resources = append(resources, resource)
}
} else {
resources = svc.Resources()
}
// User ID specified, load its roles
if userID != 0 {
if len(roles) > 0 {
// should be prevented on the client
return nil, fmt.Errorf("userID and roles are mutually exclusive")
}
members, _, err = store.SearchRoleMembers(ctx, svc.store, systemTypes.RoleMemberFilter{Resource: fmt.Sprintf("corteza::system:user/%d", userID)})
if err != nil {
return nil, err
}
for _, m := range members {
roles = append(roles, m.RoleID)
}
for _, r := range internalAuth.AuthenticatedRoles() {
roles = append(roles, r.ID)
}
}
if len(roles) == 0 {
// should be prevented on the client
return nil, fmt.Errorf("no roles specified")
}
session := rbac.ParamsToSession(ctx, userID, roles...)
for _, res := range resources {
r := res.RbacResource()
for op := range rbacResourceOperations(r) {
ee = append(ee, svc.rbac.Trace(session, op, res))
}
}
return
}
// Resources returns list of resources
//
// This function is auto-generated
func (svc accessControl) Resources() []rbac.Resource {
return []rbac.Resource{
rbac.NewResource(types.NodeRbacResource(0)),
rbac.NewResource(types.ExposedModuleRbacResource(0, 0)),
rbac.NewResource(types.SharedModuleRbacResource(0, 0)),
rbac.NewResource(types.ComponentRbacResource()),
}
}
// List returns list of operations on all resources
//
// This function is auto-generated
func (svc accessControl) List() (out []map[string]string) {
def := []map[string]string{
{
"type": types.NodeResourceType,
"any": types.NodeRbacResource(0),
"op": "manage",
},
{
"type": types.NodeResourceType,
"any": types.NodeRbacResource(0),
"op": "module.create",
},
{
"type": types.ExposedModuleResourceType,
"any": types.ExposedModuleRbacResource(0, 0),
"op": "manage",
},
{
"type": types.SharedModuleResourceType,
"any": types.SharedModuleRbacResource(0, 0),
"op": "map",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "grant",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "pair",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "settings.read",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "settings.manage",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "node.create",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "nodes.search",
},
}
func(svc interface{}) {
if svc, is := svc.(interface{}).(interface{ list() []map[string]string }); is {
def = append(def, svc.list()...)
}
}(svc)
return def
}
// Grant applies one or more RBAC rules
//
// This function is auto-generated
func (svc accessControl) Grant(ctx context.Context, rr ...*rbac.Rule) error {
if !svc.CanGrant(ctx) {
// @todo should be altered to check grant permissions PER resource
return AccessControlErrNotAllowedToSetPermissions()
}
for _, r := range rr {
err := rbacResourceValidator(r.Resource, r.Operation)
if err != nil {
return err
}
}
if err := svc.rbac.Grant(ctx, rr...); err != nil {
return AccessControlErrGeneric().Wrap(err)
}
svc.logGrants(ctx, rr)
return nil
}
// This function is auto-generated
func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
if svc.actionlog == nil {
return
}
for _, r := range rr {
g := AccessControlActionGrant(&accessControlActionProps{r})
g.log = r.String()
g.resource = r.Resource
svc.actionlog.Record(ctx, g.ToAction())
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
out, err = svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var resources []rbac.Resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
return out.FilterResource(resources...), nil
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) (rbac.RuleSet, error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
return svc.rbac.FindRulesByRoleID(roleID), nil
}
// CanManageNode checks if current user can manage federation node
//
// This function is auto-generated
func (svc accessControl) CanManageNode(ctx context.Context, r *types.Node) bool {
return svc.can(ctx, "manage", r)
}
// CanCreateModuleOnNode checks if current user can create shared module
//
// This function is auto-generated
func (svc accessControl) CanCreateModuleOnNode(ctx context.Context, r *types.Node) bool {
return svc.can(ctx, "module.create", r)
}
// CanManageExposedModule checks if current user can manage exposed module module
//
// This function is auto-generated
func (svc accessControl) CanManageExposedModule(ctx context.Context, r *types.ExposedModule) bool {
return svc.can(ctx, "manage", r)
}
// CanMapSharedModule checks if current user can map shared module
//
// This function is auto-generated
func (svc accessControl) CanMapSharedModule(ctx context.Context, r *types.SharedModule) bool {
return svc.can(ctx, "map", r)
}
// CanGrant checks if current user can manage federation permissions
//
// This function is auto-generated
func (svc accessControl) CanGrant(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "grant", r)
}
// CanPair checks if current user can pair federation nodes
//
// This function is auto-generated
func (svc accessControl) CanPair(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "pair", r)
}
// CanReadSettings checks if current user can read settings
//
// This function is auto-generated
func (svc accessControl) CanReadSettings(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "settings.read", r)
}
// CanManageSettings checks if current user can manage settings
//
// This function is auto-generated
func (svc accessControl) CanManageSettings(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "settings.manage", r)
}
// CanCreateNode checks if current user can create new federation node
//
// This function is auto-generated
func (svc accessControl) CanCreateNode(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "node.create", r)
}
// CanSearchNodes checks if current user can list, search or filter federation nodes
//
// This function is auto-generated
func (svc accessControl) CanSearchNodes(ctx context.Context) bool {
r := &types.Component{}
return svc.can(ctx, "nodes.search", r)
}
// rbacResourceValidator validates known component's resource by routing it to the appropriate validator
//
// This function is auto-generated
func rbacResourceValidator(r string, oo ...string) error {
switch rbac.ResourceType(r) {
case types.NodeResourceType:
return rbacNodeResourceValidator(r, oo...)
case types.ExposedModuleResourceType:
return rbacExposedModuleResourceValidator(r, oo...)
case types.SharedModuleResourceType:
return rbacSharedModuleResourceValidator(r, oo...)
case types.ComponentResourceType:
return rbacComponentResourceValidator(r, oo...)
}
return fmt.Errorf("unknown resource type %q", r)
}
// resourceLoader loads resource from store
//
// # Notes
// Function assumes existence of loader functions for all resource types
//
// This function is auto-generated
func (svc accessControl) resourceLoader(ctx context.Context, resource string) (rbac.Resource, error) {
var (
hasWildcard = false
resourceType, ids = rbac.ParseResourceID(resource)
)
for _, id := range ids {
if id == 0 {
hasWildcard = true
break
}
}
switch rbac.ResourceType(resourceType) {
case types.NodeResourceType:
if hasWildcard {
return rbac.NewResource(types.NodeRbacResource(ids[0])), nil
}
return loadNode(ctx, svc.store, ids[0])
case types.ExposedModuleResourceType:
if hasWildcard {
return rbac.NewResource(types.ExposedModuleRbacResource(ids[0], ids[1])), nil
}
return loadExposedModule(ctx, svc.store, ids[0], ids[1])
case types.SharedModuleResourceType:
if hasWildcard {
return rbac.NewResource(types.SharedModuleRbacResource(ids[0], ids[1])), nil
}
return loadSharedModule(ctx, svc.store, ids[0], ids[1])
case types.ComponentResourceType:
return &types.Component{}, nil
}
_ = ids
return nil, fmt.Errorf("unknown resource type %q", resourceType)
}
// rbacResourceOperations returns defined operations for a requested resource
//
// This function is auto-generated
func rbacResourceOperations(r string) map[string]bool {
switch rbac.ResourceType(r) {
case types.NodeResourceType:
return map[string]bool{
"manage": true,
"module.create": true,
}
case types.ExposedModuleResourceType:
return map[string]bool{
"manage": true,
}
case types.SharedModuleResourceType:
return map[string]bool{
"map": true,
}
case types.ComponentResourceType:
return map[string]bool{
"grant": true,
"pair": true,
"settings.read": true,
"settings.manage": true,
"node.create": true,
"nodes.search": true,
}
}
return nil
}
// rbacNodeResourceValidator checks validity of RBAC resource and operations
//
// # Notes
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbacNodeResourceValidator(r string, oo ...string) error {
if !strings.HasPrefix(r, types.NodeResourceType) {
// expecting resource to always include path
return fmt.Errorf("invalid resource type")
}
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for node resource", o)
}
}
const sep = "/"
var (
pp = strings.Split(strings.Trim(r[len(types.NodeResourceType):], sep), sep)
prc = []string{
"ID",
}
)
if len(pp) != len(prc) {
return fmt.Errorf("invalid resource path structure")
}
for i := 0; i < len(pp); i++ {
if pp[i] != "*" {
if i > 0 && pp[i-1] == "*" {
return fmt.Errorf("invalid path wildcard level (%d) for node resource", i)
}
if _, err := cast.ToUint64E(pp[i]); err != nil {
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i])
}
}
}
return nil
}
// rbacExposedModuleResourceValidator checks validity of RBAC resource and operations
//
// # Notes
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbacExposedModuleResourceValidator(r string, oo ...string) error {
if !strings.HasPrefix(r, types.ExposedModuleResourceType) {
// expecting resource to always include path
return fmt.Errorf("invalid resource type")
}
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for exposedModule resource", o)
}
}
const sep = "/"
var (
pp = strings.Split(strings.Trim(r[len(types.ExposedModuleResourceType):], sep), sep)
prc = []string{
"NodeID",
"ID",
}
)
if len(pp) != len(prc) {
return fmt.Errorf("invalid resource path structure")
}
for i := 0; i < len(pp); i++ {
if pp[i] != "*" {
if i > 0 && pp[i-1] == "*" {
return fmt.Errorf("invalid path wildcard level (%d) for exposedModule resource", i)
}
if _, err := cast.ToUint64E(pp[i]); err != nil {
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i])
}
}
}
return nil
}
// rbacSharedModuleResourceValidator checks validity of RBAC resource and operations
//
// # Notes
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbacSharedModuleResourceValidator(r string, oo ...string) error {
if !strings.HasPrefix(r, types.SharedModuleResourceType) {
// expecting resource to always include path
return fmt.Errorf("invalid resource type")
}
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for sharedModule resource", o)
}
}
const sep = "/"
var (
pp = strings.Split(strings.Trim(r[len(types.SharedModuleResourceType):], sep), sep)
prc = []string{
"NodeID",
"ID",
}
)
if len(pp) != len(prc) {
return fmt.Errorf("invalid resource path structure")
}
for i := 0; i < len(pp); i++ {
if pp[i] != "*" {
if i > 0 && pp[i-1] == "*" {
return fmt.Errorf("invalid path wildcard level (%d) for sharedModule resource", i)
}
if _, err := cast.ToUint64E(pp[i]); err != nil {
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i])
}
}
}
return nil
}
// rbacComponentResourceValidator checks validity of RBAC resource and operations
//
// # Notes
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbacComponentResourceValidator(r string, oo ...string) error {
if r != types.ComponentResourceType+"/" {
// expecting resource to always include path
return fmt.Errorf("invalid component resource, expecting " + types.ComponentResourceType + "/")
}
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for federation component resource", o)
}
}
return nil
}

View File

@@ -0,0 +1,284 @@
package service
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// federation/service/access_control_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"strings"
"time"
)
type (
accessControlActionProps struct {
rule *rbac.Rule
}
accessControlAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *accessControlActionProps
}
accessControlLogMetaKey struct{}
accessControlPropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setRule updates accessControlActionProps's rule
//
// This function is auto-generated.
func (p *accessControlActionProps) setRule(rule *rbac.Rule) *accessControlActionProps {
p.rule = rule
return p
}
// Serialize converts accessControlActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p accessControlActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.rule != nil {
m.Set("rule.operation", p.rule.Operation, true)
m.Set("rule.roleID", p.rule.RoleID, true)
m.Set("rule.access", p.rule.Access, true)
m.Set("rule.resource", p.rule.Resource, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
func (p accessControlActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.rule != nil {
// replacement for "{{rule}}" (in order how fields are defined)
pairs = append(
pairs,
"{{rule}}",
fns(
p.rule.Operation,
p.rule.RoleID,
p.rule.Access,
p.rule.Resource,
),
)
pairs = append(pairs, "{{rule.operation}}", fns(p.rule.Operation))
pairs = append(pairs, "{{rule.roleID}}", fns(p.rule.RoleID))
pairs = append(pairs, "{{rule.access}}", fns(p.rule.Access))
pairs = append(pairs, "{{rule.resource}}", fns(p.rule.Resource))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
func (a *accessControlAction) String() string {
var props = &accessControlActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *accessControlAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// AccessControlActionGrant returns "federation:access_control.grant" action
//
// This function is auto-generated.
func AccessControlActionGrant(props ...*accessControlActionProps) *accessControlAction {
a := &accessControlAction{
timestamp: time.Now(),
resource: "federation:access_control",
action: "grant",
log: "grant",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// AccessControlErrGeneric returns "federation:access_control.generic" as *errors.Error
//
// This function is auto-generated.
func AccessControlErrGeneric(mm ...*accessControlActionProps) *errors.Error {
var p = &accessControlActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:access_control"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(accessControlLogMetaKey{}, "{err}"),
errors.Meta(accessControlPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "access-control.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// AccessControlErrNotAllowedToSetPermissions returns "federation:access_control.notAllowedToSetPermissions" as *errors.Error
//
// This function is auto-generated.
func AccessControlErrNotAllowedToSetPermissions(mm ...*accessControlActionProps) *errors.Error {
var p = &accessControlActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to set permissions", nil),
errors.Meta("type", "notAllowedToSetPermissions"),
errors.Meta("resource", "federation:access_control"),
errors.Meta(accessControlPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "access-control.errors.notAllowedToSetPermissions"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc accessControl) recordAction(ctx context.Context, props *accessControlActionProps, actionFn func(...*accessControlActionProps) *accessControlAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(accessControlLogMetaKey{}), err)
if p, has := m[accessControlPropsMetaKey{}]; has {
a.Meta = p.(*accessControlActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,25 @@
# List of security/audit events and errors that we need to log
resource: federation:access_control
service: accessControl
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: alert
import:
- github.com/cortezaproject/corteza/server/pkg/rbac
props:
- name: rule
type: "*rbac.Rule"
fields: [ operation, roleID, access, resource ]
actions:
- action: grant
errors:
- error: notAllowedToSetPermissions
message: "not allowed to set permissions"

View File

@@ -0,0 +1,11 @@
package decoder
import (
"github.com/cortezaproject/corteza/server/compose/types"
)
type (
ComposeModule struct {
types.Module
}
)

View File

@@ -0,0 +1,16 @@
package decoder
import (
"github.com/cortezaproject/corteza/server/compose/types"
)
type (
ComposeRecord struct {
types.Record
}
ComposeRecordSet []*ComposeRecord
ComposeRecordFilter struct {
types.RecordFilter
}
)

View File

@@ -0,0 +1,105 @@
package decoder
import (
"encoding/json"
"time"
"github.com/cortezaproject/corteza/server/compose/types"
ftypes "github.com/cortezaproject/corteza/server/federation/types"
)
type (
ExposedRecord struct {
ID uint64 `json:"recordID,string"`
Values types.RecordValueSet `json:"values"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
// Bits for decoding structure sync
ModuleDocument struct {
Response ComposeModule
}
ExposedModuleDocument struct {
Response struct {
Set ftypes.ExposedModuleSet
}
}
ExposedRecordDocument struct {
Response struct {
Set []*ExposedRecord
}
}
ExposedModule struct {
ftypes.ExposedModule
}
// Bits for decoding data sync
ComposeRecordResponse struct {
Filter ComposeRecordFilter `json:",omitempty"`
Set ComposeRecordSet `json:",omitempty"`
}
ComposeRecordDocument struct {
Response ComposeRecordResponse
}
)
// DecodeModuleSync decodes the response from the structure sync request.
//
// It returns a set of modules.
func DecodeModuleSync(in []byte) (types.ModuleSet, error) {
out := &ModuleDocument{}
err := json.Unmarshal(in, out)
if err != nil {
return nil, err
}
return types.ModuleSet{&out.Response.Module}, nil
}
func DecodeFederationModuleSync(in []byte) (ftypes.ExposedModuleSet, error) {
out := &ExposedModuleDocument{}
err := json.Unmarshal(in, out)
if err != nil {
return nil, err
}
return out.Response.Set, nil
}
func DecodeFederationRecordSync(in []byte) ([]*ExposedRecord, error) {
out := &ExposedRecordDocument{}
err := json.Unmarshal(in, out)
if err != nil {
return nil, err
}
return out.Response.Set, nil
}
// DecodeRecordSync decodes the response from the data sync request.
//
// It returns a set of records along with the used filter.
func DecodeRecordSync(in []byte) (types.RecordSet, *types.RecordFilter, error) {
out := &ComposeRecordDocument{}
err := json.Unmarshal(in, out)
if err != nil {
return nil, nil, err
}
ss := make(types.RecordSet, 0, len(out.Response.Set))
for _, r := range out.Response.Set {
ss = append(ss, &r.Record)
}
return ss, &out.Response.Filter.RecordFilter, nil
}

View File

@@ -0,0 +1,341 @@
package service
import (
"context"
"strconv"
"github.com/cortezaproject/corteza/server/pkg/errors"
cs "github.com/cortezaproject/corteza/server/compose/service"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/store"
ss "github.com/cortezaproject/corteza/server/system/service"
st "github.com/cortezaproject/corteza/server/system/types"
)
type (
exposedModule struct {
node node
ac exposedModuleAccessController
module cs.ModuleService
namespace cs.NamespaceService
role ss.RoleService
store store.Storer
actionlog actionlog.Recorder
}
exposedModuleAccessController interface {
CanCreateModuleOnNode(ctx context.Context, r *types.Node) bool
CanManageExposedModule(ctx context.Context, r *types.ExposedModule) bool
}
ExposedModuleService interface {
Create(ctx context.Context, new *types.ExposedModule) (*types.ExposedModule, error)
Update(ctx context.Context, updated *types.ExposedModule) (*types.ExposedModule, error)
Find(ctx context.Context, filter types.ExposedModuleFilter) (types.ExposedModuleSet, types.ExposedModuleFilter, error)
FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (*types.ExposedModule, error)
DeleteByID(ctx context.Context, nodeID, moduleID uint64) (*types.ExposedModule, error)
}
moduleUpdateHandler func(ctx context.Context, ns *types.Node, c *types.ExposedModule) (bool, bool, error)
)
func ExposedModule() *exposedModule {
return &exposedModule{
ac: DefaultAccessControl,
node: *DefaultNode,
module: cs.DefaultModule,
role: ss.DefaultRole,
namespace: cs.DefaultNamespace,
store: DefaultStore,
actionlog: DefaultActionlog,
}
}
// FindByAny tries to find module in a particular namespace by id, handle or name
func (svc exposedModule) 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 exposedModule) FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (module *types.ExposedModule, err error) {
err = func() error {
if module, err = loadExposedModule(ctx, svc.store, nodeID, moduleID); err != nil {
return err
}
if !svc.ac.CanManageExposedModule(ctx, module) {
return ExposedModuleErrNotAllowedToManage()
}
return nil
}()
return module, err
}
func (svc exposedModule) Update(ctx context.Context, updated *types.ExposedModule) (*types.ExposedModule, error) {
var (
aProps = &exposedModuleActionProps{update: updated}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
m *ct.Module
node *types.Node
old *types.ExposedModule
)
if node, err = svc.node.FindByID(ctx, updated.NodeID); err != nil {
return ExposedModuleErrNodeNotFound()
}
if !svc.ac.CanManageExposedModule(ctx, updated) {
return ExposedModuleErrNotAllowedToManage()
}
if _, err := svc.namespace.FindByID(ctx, updated.ComposeNamespaceID); err != nil {
return ExposedModuleErrComposeNamespaceNotFound()
}
if m, err = svc.module.FindByID(ctx, updated.ComposeNamespaceID, updated.ComposeModuleID); err != nil {
return ExposedModuleErrComposeModuleNotFound()
}
if old, err = svc.FindByID(ctx, updated.NodeID, updated.ID); err != nil {
return ExposedModuleErrNotFound()
}
updated.UpdatedAt = now()
updated.CreatedAt = old.CreatedAt
updated.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
// set labels
AddFederationLabel(m, "federation", node.BaseURL)
if _, err := svc.module.Update(ctx, m); err != nil {
return err
}
aProps.setModule(updated)
if err = store.UpdateFederationExposedModule(ctx, s, updated); err != nil {
return err
}
return nil
})
return updated, svc.recordAction(ctx, aProps, ExposedModuleActionUpdate, err)
}
func (svc exposedModule) updater(ctx context.Context, nodeID, moduleID uint64, action func(...*exposedModuleActionProps) *exposedModuleAction, fn moduleUpdateHandler) (*types.ExposedModule, error) {
var (
moduleChanged, fieldsChanged bool
n *types.Node
m *types.ExposedModule
aProps = &exposedModuleActionProps{module: &types.ExposedModule{ID: moduleID, NodeID: nodeID}}
err error
)
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
if m, err = loadExposedModule(ctx, svc.store, nodeID, moduleID); err != nil {
return err
}
// TODO - handle node id also
if moduleChanged, fieldsChanged, err = fn(ctx, n, m); err != nil {
return err
}
_ = moduleChanged
_ = fieldsChanged
return err
})
return m, svc.recordAction(ctx, aProps, action, err)
}
func (svc exposedModule) DeleteByID(ctx context.Context, nodeID, moduleID uint64) (m *types.ExposedModule, err error) {
var (
aProps = &exposedModuleActionProps{}
)
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
m *types.ExposedModule
)
if _, err = svc.node.FindByID(ctx, nodeID); err != nil {
return ExposedModuleErrNodeNotFound()
}
if m, err = svc.FindByID(ctx, nodeID, moduleID); err != nil {
return err
}
if !svc.ac.CanManageExposedModule(ctx, m) {
return ExposedModuleErrNotAllowedToManage()
}
m.DeletedAt = now()
m.DeletedBy = auth.GetIdentityFromContext(ctx).Identity()
if err = store.UpdateFederationExposedModule(ctx, s, m); err != nil {
return err
}
aProps.delete = m
return nil
})
return m, svc.recordAction(ctx, aProps, ExposedModuleActionDelete, err)
}
func (svc exposedModule) Find(ctx context.Context, filter types.ExposedModuleFilter) (set types.ExposedModuleSet, f types.ExposedModuleFilter, err error) {
filter.Check = func(res *types.ExposedModule) (bool, error) {
if !svc.ac.CanManageExposedModule(ctx, res) {
return false, ExposedModuleErrNotAllowedToManage()
}
return true, nil
}
err = func() error {
if set, f, err = store.SearchFederationExposedModules(ctx, svc.store, filter); err != nil {
return err
}
return nil
}()
return set, f, err
}
func (svc exposedModule) Create(ctx context.Context, new *types.ExposedModule) (*types.ExposedModule, error) {
var (
aProps = &exposedModuleActionProps{create: new}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
m *ct.Module
node *types.Node
fedRole *st.Role
)
if node, err = svc.node.FindByID(ctx, new.NodeID); err != nil {
return ExposedModuleErrNodeNotFound()
}
if !svc.ac.CanCreateModuleOnNode(ctx, node) {
return ExposedModuleErrNotAllowedToCreate()
}
if _, err := svc.namespace.FindByID(ctx, new.ComposeNamespaceID); err != nil {
return ExposedModuleErrComposeNamespaceNotFound()
}
if m, err = svc.module.FindByID(ctx, new.ComposeNamespaceID, new.ComposeModuleID); err != nil {
return ExposedModuleErrComposeModuleNotFound()
}
if err = svc.uniqueCheck(ctx, new); err != nil {
return err
}
ctxIdentity := auth.GetIdentityFromContext(ctx)
new.ID = nextID()
new.CreatedAt = *now()
new.CreatedBy = ctxIdentity.Identity()
// find the federation role
if fedRole, err = svc.role.FindByHandle(ctx, "federation"); err != nil {
return nil
}
if fedRole != nil {
// get first ID from role and add it as allow rule
err = cs.DefaultAccessControl.Grant(ctx, rbac.AllowRule(fedRole.ID, ct.RecordRbacResource(m.NamespaceID, m.ID, 0), "read"))
}
// set labels
AddFederationLabel(m, "federation", node.BaseURL)
if _, err := svc.module.Update(ctx, m); err != nil {
return err
}
aProps.setModule(new)
if err = store.CreateFederationExposedModule(ctx, s, new); err != nil {
return err
}
return nil
})
return new, svc.recordAction(ctx, aProps, ExposedModuleActionCreate, err)
}
func (svc exposedModule) uniqueCheck(ctx context.Context, m *types.ExposedModule) (err error) {
f := types.ExposedModuleFilter{
NodeID: m.NodeID,
ComposeModuleID: m.ComposeModuleID,
ComposeNamespaceID: m.ComposeNamespaceID,
}
set, _, err := store.SearchFederationExposedModules(ctx, svc.store, f)
if len(set) > 0 && err == nil {
return ExposedModuleErrNotUnique()
} else if err != nil {
return err
}
return nil
}
func loadExposedModule(ctx context.Context, s store.FederationExposedModules, nodeID, ID uint64) (res *types.ExposedModule, err error) {
if ID == 0 || nodeID == 0 {
return nil, SharedModuleErrInvalidID()
}
if res, err = store.LookupFederationExposedModuleByID(ctx, s, ID); errors.IsNotFound(err) {
err = SharedModuleErrNotFound()
}
if err != nil {
return
}
if nodeID != res.NodeID {
// Make sure chart belongs to the right namespace
return nil, SharedModuleErrNotFound()
}
return
}

View File

@@ -0,0 +1,829 @@
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"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"strings"
"time"
)
type (
exposedModuleActionProps struct {
module *types.ExposedModule
update *types.ExposedModule
create *types.ExposedModule
delete *types.ExposedModule
filter *types.ExposedModuleFilter
node *types.Node
}
exposedModuleAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *exposedModuleActionProps
}
exposedModuleLogMetaKey struct{}
exposedModulePropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setModule updates exposedModuleActionProps's module
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setModule(module *types.ExposedModule) *exposedModuleActionProps {
p.module = module
return p
}
// setUpdate updates exposedModuleActionProps's update
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setUpdate(update *types.ExposedModule) *exposedModuleActionProps {
p.update = update
return p
}
// setCreate updates exposedModuleActionProps's create
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setCreate(create *types.ExposedModule) *exposedModuleActionProps {
p.create = create
return p
}
// setDelete updates exposedModuleActionProps's delete
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setDelete(delete *types.ExposedModule) *exposedModuleActionProps {
p.delete = delete
return p
}
// setFilter updates exposedModuleActionProps's filter
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setFilter(filter *types.ExposedModuleFilter) *exposedModuleActionProps {
p.filter = filter
return p
}
// setNode updates exposedModuleActionProps's node
//
// This function is auto-generated.
func (p *exposedModuleActionProps) setNode(node *types.Node) *exposedModuleActionProps {
p.node = node
return p
}
// Serialize converts exposedModuleActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p exposedModuleActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.module != nil {
m.Set("module.ID", p.module.ID, true)
m.Set("module.ComposeNamespaceID", p.module.ComposeNamespaceID, true)
m.Set("module.ComposeModuleID", p.module.ComposeModuleID, true)
m.Set("module.NodeID", p.module.NodeID, true)
}
if p.update != nil {
m.Set("update.ID", p.update.ID, true)
m.Set("update.ComposeNamespaceID", p.update.ComposeNamespaceID, true)
m.Set("update.ComposeModuleID", p.update.ComposeModuleID, true)
m.Set("update.NodeID", p.update.NodeID, true)
}
if p.create != nil {
m.Set("create.ID", p.create.ID, true)
m.Set("create.ComposeNamespaceID", p.create.ComposeNamespaceID, true)
m.Set("create.ComposeModuleID", p.create.ComposeModuleID, true)
m.Set("create.NodeID", p.create.NodeID, true)
}
if p.delete != nil {
m.Set("delete.ID", p.delete.ID, true)
m.Set("delete.ComposeNamespaceID", p.delete.ComposeNamespaceID, true)
m.Set("delete.ComposeModuleID", p.delete.ComposeModuleID, true)
m.Set("delete.NodeID", p.delete.NodeID, 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)
}
if p.node != nil {
m.Set("node.ID", p.node.ID, true)
m.Set("node.Name", p.node.Name, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
func (p exposedModuleActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.module != nil {
// replacement for "{{module}}" (in order how fields are defined)
pairs = append(
pairs,
"{{module}}",
fns(
p.module.ID,
p.module.ComposeNamespaceID,
p.module.ComposeModuleID,
p.module.NodeID,
),
)
pairs = append(pairs, "{{module.ID}}", fns(p.module.ID))
pairs = append(pairs, "{{module.ComposeNamespaceID}}", fns(p.module.ComposeNamespaceID))
pairs = append(pairs, "{{module.ComposeModuleID}}", fns(p.module.ComposeModuleID))
pairs = append(pairs, "{{module.NodeID}}", fns(p.module.NodeID))
}
if p.update != nil {
// replacement for "{{update}}" (in order how fields are defined)
pairs = append(
pairs,
"{{update}}",
fns(
p.update.ID,
p.update.ComposeNamespaceID,
p.update.ComposeModuleID,
p.update.NodeID,
),
)
pairs = append(pairs, "{{update.ID}}", fns(p.update.ID))
pairs = append(pairs, "{{update.ComposeNamespaceID}}", fns(p.update.ComposeNamespaceID))
pairs = append(pairs, "{{update.ComposeModuleID}}", fns(p.update.ComposeModuleID))
pairs = append(pairs, "{{update.NodeID}}", fns(p.update.NodeID))
}
if p.create != nil {
// replacement for "{{create}}" (in order how fields are defined)
pairs = append(
pairs,
"{{create}}",
fns(
p.create.ID,
p.create.ComposeNamespaceID,
p.create.ComposeModuleID,
p.create.NodeID,
),
)
pairs = append(pairs, "{{create.ID}}", fns(p.create.ID))
pairs = append(pairs, "{{create.ComposeNamespaceID}}", fns(p.create.ComposeNamespaceID))
pairs = append(pairs, "{{create.ComposeModuleID}}", fns(p.create.ComposeModuleID))
pairs = append(pairs, "{{create.NodeID}}", fns(p.create.NodeID))
}
if p.delete != nil {
// replacement for "{{delete}}" (in order how fields are defined)
pairs = append(
pairs,
"{{delete}}",
fns(
p.delete.ID,
p.delete.ComposeNamespaceID,
p.delete.ComposeModuleID,
p.delete.NodeID,
),
)
pairs = append(pairs, "{{delete.ID}}", fns(p.delete.ID))
pairs = append(pairs, "{{delete.ComposeNamespaceID}}", fns(p.delete.ComposeNamespaceID))
pairs = append(pairs, "{{delete.ComposeModuleID}}", fns(p.delete.ComposeModuleID))
pairs = append(pairs, "{{delete.NodeID}}", fns(p.delete.NodeID))
}
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))
}
if p.node != nil {
// replacement for "{{node}}" (in order how fields are defined)
pairs = append(
pairs,
"{{node}}",
fns(
p.node.ID,
p.node.Name,
),
)
pairs = append(pairs, "{{node.ID}}", fns(p.node.ID))
pairs = append(pairs, "{{node.Name}}", fns(p.node.Name))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
func (a *exposedModuleAction) String() string {
var props = &exposedModuleActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *exposedModuleAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// ExposedModuleActionSearch returns "federation:exposed_module.search" action
//
// This function is auto-generated.
func ExposedModuleActionSearch(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
}
// ExposedModuleActionLookup returns "federation:exposed_module.lookup" action
//
// This function is auto-generated.
func ExposedModuleActionLookup(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
}
// ExposedModuleActionCreate returns "federation:exposed_module.create" action
//
// This function is auto-generated.
func ExposedModuleActionCreate(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
}
// ExposedModuleActionUpdate returns "federation:exposed_module.update" action
//
// This function is auto-generated.
func ExposedModuleActionUpdate(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
}
// ExposedModuleActionDelete returns "federation:exposed_module.delete" action
//
// This function is auto-generated.
func ExposedModuleActionDelete(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
}
// ExposedModuleActionUndelete returns "federation:exposed_module.undelete" action
//
// This function is auto-generated.
func ExposedModuleActionUndelete(props ...*exposedModuleActionProps) *exposedModuleAction {
a := &exposedModuleAction{
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
// ExposedModuleErrGeneric returns "federation:exposed_module.generic" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrGeneric(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:exposed_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(exposedModuleLogMetaKey{}, "{err}"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrNotFound returns "federation:exposed_module.notFound" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrNotFound(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module does not exist", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.notFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrInvalidID returns "federation:exposed_module.invalidID" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrInvalidID(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("invalid ID", nil),
errors.Meta("type", "invalidID"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.invalidID"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrStaleData returns "federation:exposed_module.staleData" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrStaleData(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version", nil),
errors.Meta("type", "staleData"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.staleData"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrNotUnique returns "federation:exposed_module.notUnique" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrNotUnique(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node not unique", nil),
errors.Meta("type", "notUnique"),
errors.Meta("resource", "federation:exposed_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(exposedModuleLogMetaKey{}, "used duplicate node TODO - {{module.NodeID}} for this compose module TODO - module.rel_compose_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.notUnique"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrNodeNotFound returns "federation:exposed_module.nodeNotFound" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrNodeNotFound(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node does not exist", nil),
errors.Meta("type", "nodeNotFound"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.nodeNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrComposeModuleNotFound returns "federation:exposed_module.composeModuleNotFound" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrComposeModuleNotFound(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("compose module not found", nil),
errors.Meta("type", "composeModuleNotFound"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.composeModuleNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrComposeNamespaceNotFound returns "federation:exposed_module.composeNamespaceNotFound" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrComposeNamespaceNotFound(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("compose namespace not found", nil),
errors.Meta("type", "composeNamespaceNotFound"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.composeNamespaceNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrRequestParametersInvalid returns "federation:exposed_module.requestParametersInvalid" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrRequestParametersInvalid(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("request parameters invalid", nil),
errors.Meta("type", "requestParametersInvalid"),
errors.Meta("resource", "federation:exposed_module"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.requestParametersInvalid"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrNotAllowedToCreate returns "federation:exposed_module.notAllowedToCreate" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrNotAllowedToCreate(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to create modules", nil),
errors.Meta("type", "notAllowedToCreate"),
errors.Meta("resource", "federation:exposed_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(exposedModuleLogMetaKey{}, "could not create modules; insufficient permissions"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.notAllowedToCreate"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ExposedModuleErrNotAllowedToManage returns "federation:exposed_module.notAllowedToManage" as *errors.Error
//
// This function is auto-generated.
func ExposedModuleErrNotAllowedToManage(mm ...*exposedModuleActionProps) *errors.Error {
var p = &exposedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to manage this module", nil),
errors.Meta("type", "notAllowedToManage"),
errors.Meta("resource", "federation:exposed_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(exposedModuleLogMetaKey{}, "could not manage {{module}}; insufficient permissions"),
errors.Meta(exposedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "exposed-module.errors.notAllowedToManage"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc exposedModule) recordAction(ctx context.Context, props *exposedModuleActionProps, actionFn func(...*exposedModuleActionProps) *exposedModuleAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(exposedModuleLogMetaKey{}), err)
if p, has := m[exposedModulePropsMetaKey{}]; has {
a.Meta = p.(*exposedModuleActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,97 @@
# List of loggable service actions
resource: federation:exposed_module
service: exposedModule
# 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, ComposeNamespaceID, ComposeModuleID, NodeID ]
- name: update
type: "*types.ExposedModule"
fields: [ ID, ComposeNamespaceID, ComposeModuleID, NodeID ]
- name: create
type: "*types.ExposedModule"
fields: [ ID, ComposeNamespaceID, ComposeModuleID, NodeID ]
- name: delete
type: "*types.ExposedModule"
fields: [ ID, ComposeNamespaceID, ComposeModuleID, NodeID ]
- name: filter
type: "*types.ExposedModuleFilter"
fields: [ query, sort, limit ]
- name: node
type: "*types.Node"
fields: [ ID, Name ]
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: "module was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version"
severity: warning
- error: notUnique
message: "node not unique"
log: "used duplicate node TODO - {{module.NodeID}} for this compose module TODO - module.rel_compose_module"
severity: warning
- error: nodeNotFound
message: "node does not exist"
severity: warning
- error: composeModuleNotFound
message: "compose module not found"
severity: "warning"
- error: composeNamespaceNotFound
message: "compose namespace not found"
severity: "warning"
- error: requestParametersInvalid
message: "request parameters invalid"
severity: "warning"
- error: notAllowedToCreate
message: "not allowed to create modules"
log: "could not create modules; insufficient permissions"
- error: notAllowedToManage
message: "not allowed to manage this module"
log: "could not manage {{module}}; insufficient permissions"

View File

@@ -0,0 +1,54 @@
package service
import (
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
)
type (
Mapper struct{}
)
// Merge copies the values from originating structure
// do the destination
//
// mostly, there will be less mapped fields on the destination
// side, so start looping from here
func (m *Mapper) Merge(in *ct.RecordValueSet, out *ct.RecordValueSet, mappings *types.ModuleFieldMappingSet) {
var match *types.ModuleFieldMapping
for _, destVal := range *out {
// preset the value, since we're working with
// a pointer to an existing structure, could be some leftovers
destVal.Value = ""
// find origin field
if match, _ = mappings.FindByName(destVal.Name, types.ModuleFieldMappingSetFindTypeDestination); match == nil {
continue
}
// use the found origin, so the mapping is correct
for _, origVal := range *in {
if origVal.Name == match.Origin.Name {
destVal.Value = origVal.Value
break
}
}
}
return
}
// Prepare creates a set of Records to be used later
// when the fields will be mapped via Merge()
func (m *Mapper) Prepare(mappings types.ModuleFieldMappingSet) (out ct.RecordValueSet) {
for _, mm := range mappings {
rv := &ct.RecordValue{
Name: mm.Destination.Name,
Value: "",
}
out = append(out, rv)
}
return
}

View File

@@ -0,0 +1,97 @@
package service
import (
"encoding/json"
"testing"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/stretchr/testify/require"
)
func TestMapper_merge(t *testing.T) {
var (
tcc = []struct {
name string
m string
in ct.RecordValueSet
out ct.RecordValueSet
expect interface{}
}{
{
"merge_missing_destination_field",
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
ct.RecordValueSet{&ct.RecordValue{Name: "Facebook", Value: "https://fb.com/user_1"}, &ct.RecordValue{Name: "Phone", Value: "000 111 222"}, &ct.RecordValue{Name: "Twitter", Value: "https://twitter.com/@russian_bot_0"}},
ct.RecordValueSet{&ct.RecordValue{Name: "Name", Value: ""}, &ct.RecordValue{Name: "Fb", Value: ""}},
ct.RecordValueSet{&ct.RecordValue{Name: "Name", Value: ""}, &ct.RecordValue{Name: "Fb", Value: "https://fb.com/user_1"}},
},
{
"merge_empty_origin",
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
ct.RecordValueSet{},
ct.RecordValueSet{&ct.RecordValue{Name: "Name", Value: ""}, &ct.RecordValue{Name: "Fb", Value: ""}},
ct.RecordValueSet{&ct.RecordValue{Name: "Name", Value: ""}, &ct.RecordValue{Name: "Fb", Value: ""}},
},
{
"merge_empty_destination",
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
ct.RecordValueSet{&ct.RecordValue{Name: "Facebook", Value: "https://fb.com/user_1"}, &ct.RecordValue{Name: "Phone", Value: "000 111 222"}, &ct.RecordValue{Name: "Twitter", Value: "https://twitter.com/@russian_bot_0"}},
ct.RecordValueSet{},
ct.RecordValueSet{},
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
mapper = &Mapper{}
)
// dont catch any helper errors
mm := &types.ModuleFieldMappingSet{}
json.Unmarshal([]byte(tc.m), mm)
mapper.Merge(&tc.in, &tc.out, mm)
req.Equal(tc.out, tc.expect)
})
}
}
func TestMapper_prepare(t *testing.T) {
var (
tcc = []struct {
name string
m string
expect ct.RecordValueSet
}{
{
"prepare values",
`[
{"origin":{"kind":"String","name":"AccountName","label":"Account Name","isMulti":false},"destination":{"kind":"String","name":"AccountName","label":"Account Name","isMulti":false}},
{"origin":{"kind":"String","name":"Phone","label":"Phone","isMulti":false},"destination":{"kind":"String","name":"Phone","label":"Phone","isMulti":false}},
{"origin":{"kind":"Url","name":"Twitter","label":"Twitter","isMulti":false},"destination":{"kind":"Url","name":"Twitter","label":"Twitter","isMulti":false}}
]`,
ct.RecordValueSet{&ct.RecordValue{Name: "AccountName", Value: ""}, &ct.RecordValue{Name: "Phone", Value: ""}, &ct.RecordValue{Name: "Twitter", Value: ""}},
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
mapper = &Mapper{}
)
// dont catch any helper errors
mm := &types.ModuleFieldMappingSet{}
json.Unmarshal([]byte(tc.m), mm)
out := mapper.Prepare(*mm)
req.Equal(tc.expect, out)
})
}
}

View File

@@ -0,0 +1,219 @@
package service
import (
"context"
cs "github.com/cortezaproject/corteza/server/compose/service"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/store"
)
type (
moduleMapping struct {
store store.Storer
node node
ac moduleMappingAccessController
module cs.ModuleService
smodule SharedModuleService
namespace cs.NamespaceService
actionlog actionlog.Recorder
}
moduleMappingAccessController interface {
CanMapSharedModule(ctx context.Context, r *types.SharedModule) bool
}
ModuleMappingService interface {
Find(ctx context.Context, filter types.ModuleMappingFilter) (types.ModuleMappingSet, types.ModuleMappingFilter, error)
FindByID(ctx context.Context, federationModuleID uint64) (*types.ModuleMapping, error)
Create(ctx context.Context, new *types.ModuleMapping) (*types.ModuleMapping, error)
Update(ctx context.Context, updated *types.ModuleMapping) (*types.ModuleMapping, error)
}
moduleMappingUpdateHandler func(ctx context.Context, c *types.ModuleMapping) (bool, bool, error)
)
func ModuleMapping() *moduleMapping {
return &moduleMapping{
ac: DefaultAccessControl,
node: *DefaultNode,
store: DefaultStore,
actionlog: DefaultActionlog,
smodule: DefaultSharedModule,
module: cs.DefaultModule,
namespace: cs.DefaultNamespace,
}
}
func (svc moduleMapping) FindByID(ctx context.Context, federationModuleID uint64) (mm *types.ModuleMapping, err error) {
err = func() error {
var (
sm *types.SharedModule
)
if mm, err = store.LookupFederationModuleMappingByFederationModuleID(ctx, svc.store, federationModuleID); err != nil {
return err
}
// fetch shared module for access check
if sm, err = svc.smodule.FindByID(ctx, mm.NodeID, federationModuleID); err != nil {
return err
}
if !svc.ac.CanMapSharedModule(ctx, sm) {
return ModuleMappingErrNotAllowedToMap()
}
return nil
}()
return
}
func (svc moduleMapping) Find(ctx context.Context, filter types.ModuleMappingFilter) (set types.ModuleMappingSet, f types.ModuleMappingFilter, err error) {
// @todo - optimise this access check
filter.Check = func(res *types.ModuleMapping) (bool, error) {
// fetch shared module for this
sm, err := svc.smodule.FindByID(ctx, res.NodeID, res.FederationModuleID)
if err != nil {
return false, err
}
if !svc.ac.CanMapSharedModule(ctx, sm) {
return false, ModuleMappingErrNotAllowedToMap()
}
return true, nil
}
err = func() error {
if set, f, err = store.SearchFederationModuleMappings(ctx, svc.store, filter); err != nil {
return err
}
return nil
}()
set.Walk(func(mm *types.ModuleMapping) error {
mm.NodeID = f.NodeID
return nil
})
return
}
func (svc moduleMapping) Create(ctx context.Context, new *types.ModuleMapping) (*types.ModuleMapping, error) {
var (
aProps = &moduleMappingActionProps{created: new}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
m *ct.Module
sm *types.SharedModule
)
if _, err := svc.namespace.FindByID(ctx, new.ComposeNamespaceID); err != nil {
return ModuleMappingErrComposeNamespaceNotFound()
}
if _, err = svc.node.FindByID(ctx, new.NodeID); err != nil {
return ModuleMappingErrNodeNotFound()
}
// Check for federation module - compose.Module combo
if err = svc.uniqueCheck(ctx, new); err != nil {
return err
}
if m, err = svc.module.FindByID(ctx, new.ComposeNamespaceID, new.ComposeModuleID); err != nil {
return ModuleMappingErrComposeModuleNotFound()
}
if sm, err = svc.smodule.FindByID(ctx, new.NodeID, new.FederationModuleID); err != nil {
return err
}
if !svc.ac.CanMapSharedModule(ctx, sm) {
return ModuleMappingErrNotAllowedToMap()
}
if err = store.CreateFederationModuleMapping(ctx, s, new); err != nil {
return err
}
// set labels
AddFederationLabel(m, "federation", "")
if _, err := svc.module.Update(ctx, m); err != nil {
return err
}
return nil
})
return new, svc.recordAction(ctx, aProps, ModuleMappingActionCreate, err)
}
func (svc moduleMapping) Update(ctx context.Context, updated *types.ModuleMapping) (*types.ModuleMapping, error) {
var (
aProps = &moduleMappingActionProps{changed: updated}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
m *ct.Module
sm *types.SharedModule
)
if _, err := svc.namespace.FindByID(ctx, updated.ComposeNamespaceID); err != nil {
return ModuleMappingErrComposeNamespaceNotFound()
}
if m, err = svc.module.FindByID(ctx, updated.ComposeNamespaceID, updated.ComposeModuleID); err != nil {
return ModuleMappingErrComposeModuleNotFound()
}
if sm, err = svc.smodule.FindByID(ctx, updated.NodeID, updated.FederationModuleID); err != nil {
return err
}
if !svc.ac.CanMapSharedModule(ctx, sm) {
return ModuleMappingErrNotAllowedToMap()
}
if err = store.UpdateFederationModuleMapping(ctx, s, updated); err != nil {
return err
}
// set labels
AddFederationLabel(m, "federation", "")
if _, err := svc.module.Update(ctx, m); err != nil {
return err
}
return nil
})
return updated, svc.recordAction(ctx, aProps, ModuleMappingActionUpdate, err)
}
func (svc moduleMapping) uniqueCheck(ctx context.Context, m *types.ModuleMapping) (err error) {
f := types.ModuleMappingFilter{
FederationModuleID: m.FederationModuleID,
ComposeModuleID: m.ComposeModuleID,
ComposeNamespaceID: m.ComposeNamespaceID,
}
if set, _, err := store.SearchFederationModuleMappings(ctx, svc.store, f); len(set) > 0 && err == nil {
return ModuleMappingErrModuleMappingExists()
} else if err != nil {
return err
}
return err
}

View File

@@ -0,0 +1,632 @@
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/module_mapping_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"strings"
"time"
)
type (
moduleMappingActionProps struct {
created *types.ModuleMapping
mapping *types.ModuleMapping
changed *types.ModuleMapping
filter *types.ModuleMappingFilter
}
moduleMappingAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *moduleMappingActionProps
}
moduleMappingLogMetaKey struct{}
moduleMappingPropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setCreated updates moduleMappingActionProps's created
//
// This function is auto-generated.
func (p *moduleMappingActionProps) setCreated(created *types.ModuleMapping) *moduleMappingActionProps {
p.created = created
return p
}
// setMapping updates moduleMappingActionProps's mapping
//
// This function is auto-generated.
func (p *moduleMappingActionProps) setMapping(mapping *types.ModuleMapping) *moduleMappingActionProps {
p.mapping = mapping
return p
}
// setChanged updates moduleMappingActionProps's changed
//
// This function is auto-generated.
func (p *moduleMappingActionProps) setChanged(changed *types.ModuleMapping) *moduleMappingActionProps {
p.changed = changed
return p
}
// setFilter updates moduleMappingActionProps's filter
//
// This function is auto-generated.
func (p *moduleMappingActionProps) setFilter(filter *types.ModuleMappingFilter) *moduleMappingActionProps {
p.filter = filter
return p
}
// Serialize converts moduleMappingActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p moduleMappingActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.created != nil {
m.Set("created.FederationModuleID", p.created.FederationModuleID, true)
m.Set("created.ComposeModuleID", p.created.ComposeModuleID, true)
}
if p.mapping != nil {
m.Set("mapping.FederationModuleID", p.mapping.FederationModuleID, true)
m.Set("mapping.ComposeModuleID", p.mapping.ComposeModuleID, true)
}
if p.changed != nil {
m.Set("changed.FederationModuleID", p.changed.FederationModuleID, 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 moduleMappingActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.created != nil {
// replacement for "{{created}}" (in order how fields are defined)
pairs = append(
pairs,
"{{created}}",
fns(
p.created.FederationModuleID,
p.created.ComposeModuleID,
),
)
pairs = append(pairs, "{{created.FederationModuleID}}", fns(p.created.FederationModuleID))
pairs = append(pairs, "{{created.ComposeModuleID}}", fns(p.created.ComposeModuleID))
}
if p.mapping != nil {
// replacement for "{{mapping}}" (in order how fields are defined)
pairs = append(
pairs,
"{{mapping}}",
fns(
p.mapping.FederationModuleID,
p.mapping.ComposeModuleID,
),
)
pairs = append(pairs, "{{mapping.FederationModuleID}}", fns(p.mapping.FederationModuleID))
pairs = append(pairs, "{{mapping.ComposeModuleID}}", fns(p.mapping.ComposeModuleID))
}
if p.changed != nil {
// replacement for "{{changed}}" (in order how fields are defined)
pairs = append(
pairs,
"{{changed}}",
fns(
p.changed.FederationModuleID,
p.changed.ComposeModuleID,
),
)
pairs = append(pairs, "{{changed.FederationModuleID}}", fns(p.changed.FederationModuleID))
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 *moduleMappingAction) String() string {
var props = &moduleMappingActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *moduleMappingAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// ModuleMappingActionSearch returns "federation:module_mapping.search" action
//
// This function is auto-generated.
func ModuleMappingActionSearch(props ...*moduleMappingActionProps) *moduleMappingAction {
a := &moduleMappingAction{
timestamp: time.Now(),
resource: "federation:module_mapping",
action: "search",
log: "searched for modules",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleMappingActionLookup returns "federation:module_mapping.lookup" action
//
// This function is auto-generated.
func ModuleMappingActionLookup(props ...*moduleMappingActionProps) *moduleMappingAction {
a := &moduleMappingAction{
timestamp: time.Now(),
resource: "federation:module_mapping",
action: "lookup",
log: "looked-up for a module",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleMappingActionCreate returns "federation:module_mapping.create" action
//
// This function is auto-generated.
func ModuleMappingActionCreate(props ...*moduleMappingActionProps) *moduleMappingAction {
a := &moduleMappingAction{
timestamp: time.Now(),
resource: "federation:module_mapping",
action: "create",
log: "created module",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleMappingActionUpdate returns "federation:module_mapping.update" action
//
// This function is auto-generated.
func ModuleMappingActionUpdate(props ...*moduleMappingActionProps) *moduleMappingAction {
a := &moduleMappingAction{
timestamp: time.Now(),
resource: "federation:module_mapping",
action: "update",
log: "updated module",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ModuleMappingActionDelete returns "federation:module_mapping.delete" action
//
// This function is auto-generated.
func ModuleMappingActionDelete(props ...*moduleMappingActionProps) *moduleMappingAction {
a := &moduleMappingAction{
timestamp: time.Now(),
resource: "federation:module_mapping",
action: "delete",
log: "deleted module",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// ModuleMappingErrGeneric returns "federation:module_mapping.generic" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrGeneric(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:module_mapping"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(moduleMappingLogMetaKey{}, "{err}"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrNotFound returns "federation:module_mapping.notFound" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrNotFound(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module mapping does not exist", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.notFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrComposeModuleNotFound returns "federation:module_mapping.composeModuleNotFound" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrComposeModuleNotFound(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("compose module not found", nil),
errors.Meta("type", "composeModuleNotFound"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.composeModuleNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrComposeNamespaceNotFound returns "federation:module_mapping.composeNamespaceNotFound" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrComposeNamespaceNotFound(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("compose namespace not found", nil),
errors.Meta("type", "composeNamespaceNotFound"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.composeNamespaceNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrFederationModuleNotFound returns "federation:module_mapping.federationModuleNotFound" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrFederationModuleNotFound(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("federation module not found", nil),
errors.Meta("type", "federationModuleNotFound"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.federationModuleNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrNodeNotFound returns "federation:module_mapping.nodeNotFound" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrNodeNotFound(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node does not exist", nil),
errors.Meta("type", "nodeNotFound"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.nodeNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrModuleMappingExists returns "federation:module_mapping.moduleMappingExists" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrModuleMappingExists(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module mapping already exists", nil),
errors.Meta("type", "moduleMappingExists"),
errors.Meta("resource", "federation:module_mapping"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.moduleMappingExists"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ModuleMappingErrNotAllowedToMap returns "federation:module_mapping.notAllowedToMap" as *errors.Error
//
// This function is auto-generated.
func ModuleMappingErrNotAllowedToMap(mm ...*moduleMappingActionProps) *errors.Error {
var p = &moduleMappingActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to map this module", nil),
errors.Meta("type", "notAllowedToMap"),
errors.Meta("resource", "federation:module_mapping"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(moduleMappingLogMetaKey{}, "could not manage mapping; insufficient permissions"),
errors.Meta(moduleMappingPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "module-mapping.errors.notAllowedToMap"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc moduleMapping) recordAction(ctx context.Context, props *moduleMappingActionProps, actionFn func(...*moduleMappingActionProps) *moduleMappingAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(moduleMappingLogMetaKey{}), err)
if p, has := m[moduleMappingPropsMetaKey{}]; has {
a.Meta = p.(*moduleMappingActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,74 @@
# List of loggable service actions
resource: federation:module_mapping
service: moduleMapping
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: error
import:
- github.com/cortezaproject/corteza/server/federation/types
props:
- name: created
type: "*types.ModuleMapping"
fields: [ FederationModuleID, ComposeModuleID ]
- name: mapping
type: "*types.ModuleMapping"
fields: [ FederationModuleID, ComposeModuleID ]
- name: changed
type: "*types.ModuleMapping"
fields: [ FederationModuleID, ComposeModuleID ]
- name: filter
type: "*types.ModuleMappingFilter"
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"
errors:
- error: notFound
message: "module mapping does not exist"
severity: warning
- error: composeModuleNotFound
message: "compose module not found"
severity: "warning"
- error: composeNamespaceNotFound
message: "compose namespace not found"
severity: "warning"
- error: federationModuleNotFound
message: "federation module not found"
severity: "warning"
- error: nodeNotFound
message: "node does not exist"
severity: warning
- error: moduleMappingExists
message: "module mapping already exists"
# severity: "warning"
- error: notAllowedToMap
message: "not allowed to map this module"
log: "could not manage mapping; insufficient permissions"

View File

@@ -0,0 +1,602 @@
package service
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/rand"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
sysTypes "github.com/cortezaproject/corteza/server/system/types"
)
const (
TokenLength = 32
)
type (
tokenIssuer func(context.Context, auth.Identifiable) (token []byte, err error)
node struct {
store store.Storer
sysUser service.UserService
actionlog actionlog.Recorder
tokenIssuer tokenIssuer
name string
host string
baseURL string
handshaker nodeHandshaker
ac nodeAccessController
}
nodeAccessController interface {
CanPair(ctx context.Context) bool
CanSearchNodes(ctx context.Context) bool
CanCreateNode(ctx context.Context) bool
CanManageNode(ctx context.Context, r *types.Node) bool
}
nodeUpdateHandler func(ctx context.Context, n *types.Node) error
nodeHandshaker interface {
Init(ctx context.Context, n *types.Node, authToken string) error
Complete(ctx context.Context, n *types.Node, authToken string) error
}
)
func Node(s store.Storer, u service.UserService, al actionlog.Recorder, th tokenIssuer, options options.FederationOpt, sopt options.HttpServerOpt, ac nodeAccessController) *node {
return &node{
store: s,
sysUser: u,
actionlog: al,
tokenIssuer: th,
ac: ac,
name: options.Label,
host: options.Host,
baseURL: fmt.Sprintf("%s/federation", strings.TrimRight(sopt.ApiBaseUrl, "/")),
handshaker: HttpHandshake(http.DefaultClient),
}
}
func (svc *node) SetHandshaker(h nodeHandshaker) {
svc.handshaker = h
}
func (svc node) Search(ctx context.Context, filter types.NodeFilter) (set types.NodeSet, f types.NodeFilter, err error) {
var (
aProps = &nodeActionProps{filter: &filter}
)
filter.Check = func(res *types.Node) (bool, error) {
if !svc.ac.CanManageNode(ctx, res) {
return false, NodeErrNotAllowedToManage()
}
return true, nil
}
err = func() error {
if !svc.ac.CanSearchNodes(ctx) {
return NodeErrNotAllowedToSearch()
}
if set, f, err = store.SearchFederationNodes(ctx, svc.store, filter); err != nil {
return err
}
return nil
}()
return set, f, svc.recordAction(ctx, aProps, NodeActionSearch, err)
}
// Create is used on server A to create federation with server B
func (svc node) Create(ctx context.Context, new *types.Node) (*types.Node, error) {
var (
aProps = &nodeActionProps{}
n *types.Node
err error
)
// @todo verify new.baseURL; if it's set it must be valid URL that returns 200
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
n = &types.Node{
ID: nextID(),
Name: new.Name,
BaseURL: new.BaseURL,
Contact: new.Contact,
Status: types.NodeStatusPending,
CreatedAt: *now(),
}
if !svc.ac.CanCreateNode(ctx) {
return NodeErrNotAllowedToCreate()
}
return store.CreateFederationNode(ctx, s, n)
})
return n, svc.recordAction(ctx, aProps, NodeActionCreate, err)
}
// Read is used mainly in UI, when retrieving details about the node
func (svc node) Read(ctx context.Context, ID uint64) (*types.Node, error) {
var (
n, err = loadNode(ctx, svc.store, ID)
aProps = &nodeActionProps{node: n}
)
// permission takes precedence over any db error
if n != nil && !svc.ac.CanManageNode(ctx, n) {
err = NodeErrNotAllowedToManage()
}
return n, svc.recordAction(ctx, aProps, NodeActionCreate, err)
}
// CreateFromURI is used on server B to create federation with server A
func (svc node) CreateFromPairingURI(ctx context.Context, uri string) (n *types.Node, err error) {
var (
existing *types.Node
aProps = &nodeActionProps{pairingURI: uri}
)
err = func(ctx context.Context) error {
if !svc.ac.CanPair(ctx) {
return NodeErrNotAllowedToPair()
}
n, err = svc.decodePairingURI(uri)
return err
}(ctx)
if err != nil {
return nil, svc.recordAction(ctx, aProps, NodeActionCreateFromPairingURI, err)
}
existing, err = store.LookupFederationNodeByBaseURLSharedNodeID(ctx, svc.store, n.BaseURL, n.SharedNodeID)
aProps.setNode(existing)
if errors.Is(err, store.ErrNotFound) {
if !svc.ac.CanCreateNode(ctx) {
return n, svc.recordAction(ctx, aProps, NodeActionCreateFromPairingURI, NodeErrNotAllowedToCreate())
}
n.ID = nextID()
n.CreatedAt = *now()
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.Status = types.NodeStatusPending
err = store.CreateFederationNode(ctx, svc.store, n)
return n, svc.recordAction(ctx, aProps, NodeActionCreateFromPairingURI, err)
} else if err != nil {
return nil, svc.recordAction(ctx, aProps, NodeActionCreateFromPairingURI, err)
} else {
if !svc.ac.CanManageNode(ctx, n) {
return n, svc.recordAction(ctx, aProps, NodeActionCreateFromPairingURI, NodeErrNotAllowedToManage())
}
// Node with the same sharing ID and domain already exists
// so we'll reset status and tokens
existing.Status = types.NodeStatusPending
// Remove existing auth token
existing.AuthToken = ""
// Reset pairing token
existing.PairToken = n.PairToken
existing.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
existing.UpdatedAt = now()
err = store.UpdateFederationNode(ctx, svc.store, n)
return n, svc.recordAction(ctx, aProps, NodeActionRecreateFromPairingURI, err)
}
}
// RegenerateNodeURI loads and updates node with the new OTT and returns sharable link
func (svc node) RegenerateNodeURI(ctx context.Context, nodeID uint64) (string, error) {
var (
uri string
)
_, err := svc.updater(
ctx,
nodeID,
NodeActionOttRegenerated,
func(ctx context.Context, n *types.Node) error {
n.PairToken = string(rand.Bytes(TokenLength))
return nil
},
func(ctx context.Context, n *types.Node) error {
uri = svc.makePairingURI(n)
return nil
},
)
if err != nil {
return "", err
}
return uri, nil
}
func (svc node) Update(ctx context.Context, upd *types.Node) (*types.Node, error) {
return svc.updater(ctx, upd.ID, NodeActionUpdate, func(ctx context.Context, n *types.Node) error {
n.Name = upd.Name
n.BaseURL = upd.BaseURL
n.Contact = upd.Contact
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.UpdatedAt = now()
return nil
}, nil)
}
func (svc node) DeleteByID(ctx context.Context, ID uint64) error {
_, err := svc.updater(ctx, ID, NodeActionDelete, func(ctx context.Context, n *types.Node) error {
n.DeletedAt = now()
n.DeletedBy = auth.GetIdentityFromContext(ctx).Identity()
return nil
}, nil)
return err
}
func (svc node) UndeleteByID(ctx context.Context, ID uint64) error {
_, err := svc.updater(ctx, ID, NodeActionUndelete, func(ctx context.Context, n *types.Node) error {
n.DeletedAt = nil
n.DeletedBy = 0
return nil
}, nil)
return err
}
// Pair is used on server B to send request to server A
func (svc node) Pair(ctx context.Context, nodeID uint64) error {
if !svc.ac.CanPair(ctx) {
return NodeErrNotAllowedToPair()
}
// elevate permissions for user lookup & creation!
ctx = auth.SetIdentityToContext(ctx, auth.FederationUser())
_, err := svc.updater(
ctx,
nodeID,
NodeActionPair,
func(ctx context.Context, n *types.Node) error {
// Handle federated user
u, err := svc.fetchFederatedUser(ctx, n)
if err != nil {
return err
}
var accessToken []byte
// Generate JWT token for the federated user
accessToken, err = svc.tokenIssuer(ctx, u)
if err != nil {
return err
}
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.UpdatedAt = now()
// Start handshake initialization remote node
if err = svc.handshaker.Init(ctx, n, string(accessToken)); err != nil {
return err
}
n.Status = types.NodeStatusPairRequested
return nil
},
nil,
)
return err
}
// HandshakeInit is used on server A to handle pairing request (see Pair fn above) from server B
func (svc node) HandshakeInit(ctx context.Context, nodeID uint64, pairToken string, sharedNodeID uint64, authToken string) error {
// if !svc.ac.CanPair(ctx) {
// return NodeErrNotAllowedToPair()
// }
_, err := svc.updater(
ctx,
sharedNodeID,
NodeActionHandshakeInit,
func(ctx context.Context, n *types.Node) error {
// @todo need to check node status before we can proceed with initialization
if n.PairToken != pairToken {
return NodeErrPairingTokenInvalid()
}
n.SharedNodeID = sharedNodeID
n.AuthToken = authToken
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.UpdatedAt = now()
n.Status = types.NodeStatusPairRequested
return nil
},
func(ctx context.Context, n *types.Node) error {
// @todo notify the node administrator about the request
return nil
},
)
return err
}
// HandshakeConfirm is used by server A to manually confirm the handshake
func (svc node) HandshakeConfirm(ctx context.Context, nodeID uint64) error {
if !svc.ac.CanPair(ctx) {
return NodeErrNotAllowedToPair()
}
_, err := svc.updater(ctx, nodeID, NodeActionHandshakeConfirm, func(ctx context.Context, n *types.Node) error {
// Handle federated user
u, err := svc.fetchFederatedUser(ctx, n)
if err != nil {
return err
}
// Generate JWT token for the federated user
var accessToken []byte
if accessToken, err = svc.tokenIssuer(ctx, u); err != nil {
return fmt.Errorf("could not confirm handshake: %w", err)
}
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.UpdatedAt = now()
// Complete handshake on remote node
if err = svc.handshaker.Complete(ctx, n, string(accessToken)); err != nil {
return err
}
n.Status = types.NodeStatusPaired
return nil
}, nil)
return err
}
// HandshakeComplete is used by server B to handle handshake confirmation
func (svc node) HandshakeComplete(ctx context.Context, sharedNodeID uint64, token string) error {
var (
n *types.Node
err error
)
if !svc.ac.CanPair(ctx) {
return NodeErrNotAllowedToPair()
}
if n, err = store.LookupFederationNodeBySharedNodeID(ctx, svc.store, sharedNodeID); err != nil {
return err
}
_, err = svc.updater(ctx, n.ID, NodeActionHandshakeComplete, func(ctx context.Context, n *types.Node) error {
n.AuthToken = token
n.Status = types.NodeStatusPaired
n.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
n.UpdatedAt = now()
return nil
}, nil)
return err
}
func (svc node) updater(ctx context.Context, nodeID uint64, action func(...*nodeActionProps) *nodeAction, fn, afterFn nodeUpdateHandler) (*types.Node, error) {
var (
err error
n *types.Node
aProps = &nodeActionProps{node: &types.Node{ID: nodeID}}
)
err = func() error {
n, err = loadNode(ctx, svc.store, nodeID)
if err != nil {
return err
}
aProps.setNode(n)
if err = fn(ctx, n); err != nil {
n.Status = types.NodeStatusFailed
// persist the node status change
if updateErr := store.UpdateFederationNode(ctx, svc.store, n); updateErr != nil {
return updateErr
}
return err
}
// any other type of access check
// should be done in fn() before
// if !svc.ac.CanManageNode(ctx, n) {
// return NodeErrNotAllowedToManage()
// }
if err = store.UpdateFederationNode(ctx, svc.store, n); err != nil {
return err
}
if afterFn != nil {
if err = afterFn(ctx, n); err != nil {
return err
}
}
return nil
}()
return n, svc.recordAction(ctx, aProps, action, err)
}
func (svc node) FindBySharedNodeID(ctx context.Context, sharedNodeID uint64) (*types.Node, error) {
n, err := store.LookupFederationNodeBySharedNodeID(ctx, svc.store, sharedNodeID)
if n != nil && !svc.ac.CanManageNode(ctx, n) {
return nil, NodeErrNotAllowedToManage()
}
return n, err
}
func (svc node) FindByID(ctx context.Context, nodeID uint64) (n *types.Node, err error) {
if n, err = loadNode(ctx, svc.store, nodeID); err != nil {
return
}
if !svc.ac.CanManageNode(ctx, n) {
return nil, NodeErrNotAllowedToManage()
}
return
}
// Looks for existing user or crates a new one
func (svc node) fetchFederatedUser(ctx context.Context, n *types.Node) (*sysTypes.User, error) {
// Generate handle for user that se this node
uHandle := fmt.Sprintf("federation_%d", n.ID)
u, err := svc.sysUser.FindByHandle(ctx, uHandle)
if err == nil {
// Reuse existing user
return u, nil
}
if service.UserErrNotFound().Is(err) {
user := &sysTypes.User{
Email: strconv.FormatUint(n.ID, 10) + "@federation.corteza",
Handle: uHandle,
}
AddFederationLabel(user, "federation", n.BaseURL)
// Create a user to service this node
r, err := service.DefaultRole.FindByHandle(ctx, "federation")
if err != nil {
return nil, err
}
// context with fed. user credentials
ctxfed := auth.SetIdentityToContext(ctx, auth.FederationUser())
u, err = svc.sysUser.Create(ctxfed, user)
if err != nil {
return nil, err
}
if err = service.DefaultRole.MemberAdd(ctxfed, r.ID, u.ID); err != nil {
return nil, err
}
u.SetRoles(append(u.Roles(), r.ID)...)
return u, nil
}
return nil, err
}
// decodePairingURI decodes URI (string) to federation node
//
// Four parts are collected from the given URI:
// 1. node host from URI's host
// 2. shared node ID from URI's username
// 3. shared token from URI's password
// 4. name of the node from query string param "name" (optional)
func (node) decodePairingURI(uri string) (*types.Node, error) {
var (
n = &types.Node{}
)
return n, func() error {
parsedURI, err := url.Parse(uri)
if err != nil {
return NodeErrPairingURIInvalid().Wrap(err)
}
n.PairToken, _ = parsedURI.User.Password()
if len(n.PairToken) != TokenLength {
return NodeErrPairingURITokenInvalid()
}
n.SharedNodeID, err = strconv.ParseUint(parsedURI.User.Username(), 10, 64)
if err != nil || n.SharedNodeID == 0 {
return NodeErrPairingURISourceIDInvalid().Wrap(err)
}
n.Name = parsedURI.Query().Get("name")
n.BaseURL = fmt.Sprintf("%s://%s/%s", parsedURI.Scheme, parsedURI.Host, strings.Trim(parsedURI.Path, "/"))
return nil
}()
}
// makePairingURI encodes details about this deployment and pairing token into sharable URI
//
// This URI contains info about this server (name, host, fed. api base url) and node created here
// that is used to identify remote federation server ID with pairing token
//
// URI structure:
// corteza+federation://<node ID>:<pairing token>@<this-host-where-the-api-is></path-to-federation-api>?qs-meta-data
func (svc node) makePairingURI(n *types.Node) string {
uri := url.URL{
Scheme: "https",
User: url.UserPassword(strconv.FormatUint(n.ID, 10), n.PairToken),
Host: svc.host,
Path: svc.baseURL,
}
qs := url.Values{}
if len(svc.name) > 0 {
qs.Add("name", svc.name)
}
if len(qs) > 0 {
uri.RawQuery = qs.Encode()
}
return uri.String()
}
func loadNode(ctx context.Context, s store.FederationNodes, ID uint64) (res *types.Node, err error) {
if ID == 0 {
return nil, NodeErrInvalidID()
}
if res, err = store.LookupFederationNodeByID(ctx, s, ID); errors.IsNotFound(err) {
err = NodeErrNotFound()
}
return
}

View File

@@ -0,0 +1,846 @@
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/node_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"strings"
"time"
)
type (
nodeActionProps struct {
node *types.Node
pairingURI string
filter *types.NodeFilter
}
nodeAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *nodeActionProps
}
nodeLogMetaKey struct{}
nodePropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setNode updates nodeActionProps's node
//
// This function is auto-generated.
func (p *nodeActionProps) setNode(node *types.Node) *nodeActionProps {
p.node = node
return p
}
// setPairingURI updates nodeActionProps's pairingURI
//
// This function is auto-generated.
func (p *nodeActionProps) setPairingURI(pairingURI string) *nodeActionProps {
p.pairingURI = pairingURI
return p
}
// setFilter updates nodeActionProps's filter
//
// This function is auto-generated.
func (p *nodeActionProps) setFilter(filter *types.NodeFilter) *nodeActionProps {
p.filter = filter
return p
}
// Serialize converts nodeActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p nodeActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.node != nil {
m.Set("node.Name", p.node.Name, true)
m.Set("node.BaseURL", p.node.BaseURL, true)
m.Set("node.ID", p.node.ID, true)
m.Set("node.Status", p.node.Status, true)
}
m.Set("pairingURI", p.pairingURI, true)
if p.filter != nil {
m.Set("filter.query", p.filter.Query, true)
m.Set("filter.status", p.filter.Status, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
func (p nodeActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.node != nil {
// replacement for "{{node}}" (in order how fields are defined)
pairs = append(
pairs,
"{{node}}",
fns(
p.node.Name,
p.node.BaseURL,
p.node.ID,
p.node.Status,
),
)
pairs = append(pairs, "{{node.Name}}", fns(p.node.Name))
pairs = append(pairs, "{{node.BaseURL}}", fns(p.node.BaseURL))
pairs = append(pairs, "{{node.ID}}", fns(p.node.ID))
pairs = append(pairs, "{{node.Status}}", fns(p.node.Status))
}
pairs = append(pairs, "{{pairingURI}}", fns(p.pairingURI))
if p.filter != nil {
// replacement for "{{filter}}" (in order how fields are defined)
pairs = append(
pairs,
"{{filter}}",
fns(
p.filter.Query,
p.filter.Status,
),
)
pairs = append(pairs, "{{filter.query}}", fns(p.filter.Query))
pairs = append(pairs, "{{filter.status}}", fns(p.filter.Status))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
func (a *nodeAction) String() string {
var props = &nodeActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *nodeAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// NodeActionSearch returns "federation:node.search" action
//
// This function is auto-generated.
func NodeActionSearch(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "search",
log: "searched for nodes",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionLookup returns "federation:node.lookup" action
//
// This function is auto-generated.
func NodeActionLookup(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "lookup",
log: "looked-up for a {{node}}",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionCreate returns "federation:node.create" action
//
// This function is auto-generated.
func NodeActionCreate(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "create",
log: "created {{node}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionCreateFromPairingURI returns "federation:node.createFromPairingURI" action
//
// This function is auto-generated.
func NodeActionCreateFromPairingURI(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "createFromPairingURI",
log: "created {{node}} from pairing URI",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionRecreateFromPairingURI returns "federation:node.recreateFromPairingURI" action
//
// This function is auto-generated.
func NodeActionRecreateFromPairingURI(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "recreateFromPairingURI",
log: "recreate {{node}} from pairing URI",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionUpdate returns "federation:node.update" action
//
// This function is auto-generated.
func NodeActionUpdate(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "update",
log: "updated {{node}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionDelete returns "federation:node.delete" action
//
// This function is auto-generated.
func NodeActionDelete(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "delete",
log: "deleted {{node}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionUndelete returns "federation:node.undelete" action
//
// This function is auto-generated.
func NodeActionUndelete(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "undelete",
log: "undeleted {{node}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionOttRegenerated returns "federation:node.ottRegenerated" action
//
// This function is auto-generated.
func NodeActionOttRegenerated(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "ottRegenerated",
log: "regenerated one-time-token for {{node}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionPair returns "federation:node.pair" action
//
// This function is auto-generated.
func NodeActionPair(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "pair",
log: "{{node}} pairing started",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionHandshakeInit returns "federation:node.handshakeInit" action
//
// This function is auto-generated.
func NodeActionHandshakeInit(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "handshakeInit",
log: "{{node}} handshake initialized",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionHandshakeConfirm returns "federation:node.handshakeConfirm" action
//
// This function is auto-generated.
func NodeActionHandshakeConfirm(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "handshakeConfirm",
log: "{{node}} handshake confirmed",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeActionHandshakeComplete returns "federation:node.handshakeComplete" action
//
// This function is auto-generated.
func NodeActionHandshakeComplete(props ...*nodeActionProps) *nodeAction {
a := &nodeAction{
timestamp: time.Now(),
resource: "federation:node",
action: "handshakeComplete",
log: "{{node}} handshake completed",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// NodeErrGeneric returns "federation:node.generic" as *errors.Error
//
// This function is auto-generated.
func NodeErrGeneric(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:node"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeLogMetaKey{}, "{err}"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrNotFound returns "federation:node.notFound" as *errors.Error
//
// This function is auto-generated.
func NodeErrNotFound(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node does not exist", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.notFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrInvalidID returns "federation:node.invalidID" as *errors.Error
//
// This function is auto-generated.
func NodeErrInvalidID(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("invalid ID", nil),
errors.Meta("type", "invalidID"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.invalidID"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrPairingURIInvalid returns "federation:node.pairingURIInvalid" as *errors.Error
//
// This function is auto-generated.
func NodeErrPairingURIInvalid(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("pairing URI invalid: {{err}}", nil),
errors.Meta("type", "pairingURIInvalid"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.pairingURIInvalid"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrPairingURITokenInvalid returns "federation:node.pairingURITokenInvalid" as *errors.Error
//
// This function is auto-generated.
func NodeErrPairingURITokenInvalid(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("pairing URI with invalid pairing token", nil),
errors.Meta("type", "pairingURITokenInvalid"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.pairingURITokenInvalid"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrPairingURISourceIDInvalid returns "federation:node.pairingURISourceIDInvalid" as *errors.Error
//
// This function is auto-generated.
func NodeErrPairingURISourceIDInvalid(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("pairing URI without source node ID", nil),
errors.Meta("type", "pairingURISourceIDInvalid"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.pairingURISourceIDInvalid"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrPairingTokenInvalid returns "federation:node.pairingTokenInvalid" as *errors.Error
//
// This function is auto-generated.
func NodeErrPairingTokenInvalid(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("pairing token invalid", nil),
errors.Meta("type", "pairingTokenInvalid"),
errors.Meta("resource", "federation:node"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.pairingTokenInvalid"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrNotAllowedToCreate returns "federation:node.notAllowedToCreate" as *errors.Error
//
// This function is auto-generated.
func NodeErrNotAllowedToCreate(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to create nodes", nil),
errors.Meta("type", "notAllowedToCreate"),
errors.Meta("resource", "federation:node"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeLogMetaKey{}, "could not create nodes; insufficient permissions"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.notAllowedToCreate"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrNotAllowedToSearch returns "federation:node.notAllowedToSearch" as *errors.Error
//
// This function is auto-generated.
func NodeErrNotAllowedToSearch(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to search or list nodes", nil),
errors.Meta("type", "notAllowedToSearch"),
errors.Meta("resource", "federation:node"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeLogMetaKey{}, "could not search or list nodes; insufficient permissions"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.notAllowedToSearch"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrNotAllowedToManage returns "federation:node.notAllowedToManage" as *errors.Error
//
// This function is auto-generated.
func NodeErrNotAllowedToManage(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to manage this node", nil),
errors.Meta("type", "notAllowedToManage"),
errors.Meta("resource", "federation:node"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeLogMetaKey{}, "could not manage {{node}}; insufficient permissions"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.notAllowedToManage"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeErrNotAllowedToPair returns "federation:node.notAllowedToPair" as *errors.Error
//
// This function is auto-generated.
func NodeErrNotAllowedToPair(mm ...*nodeActionProps) *errors.Error {
var p = &nodeActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to pair this node", nil),
errors.Meta("type", "notAllowedToPair"),
errors.Meta("resource", "federation:node"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeLogMetaKey{}, "could not pair {{node}}; insufficient permissions"),
errors.Meta(nodePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node.errors.notAllowedToPair"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc node) recordAction(ctx context.Context, props *nodeActionProps, actionFn func(...*nodeActionProps) *nodeAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(nodeLogMetaKey{}), err)
if p, has := m[nodePropsMetaKey{}]; has {
a.Meta = p.(*nodeActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,100 @@
# List of loggable service actions
resource: federation:node
service: node
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: error
import:
- github.com/cortezaproject/corteza/server/federation/types
props:
- name: node
type: "*types.Node"
fields: [ Name, BaseURL, ID, Status ]
- name: pairingURI
- name: filter
type: "*types.NodeFilter"
fields: [ query, status ]
actions:
- action: search
log: "searched for nodes"
- action: lookup
log: "looked-up for a {{node}}"
severity: info
- action: create
log: "created {{node}}"
- action: createFromPairingURI
log: "created {{node}} from pairing URI"
- action: recreateFromPairingURI
log: "recreate {{node}} from pairing URI"
- action: update
log: "updated {{node}}"
- action: delete
log: "deleted {{node}}"
- action: undelete
log: "undeleted {{node}}"
- action: ottRegenerated
log: "regenerated one-time-token for {{node}}"
- action: pair
log: "{{node}} pairing started"
- action: handshakeInit
log: "{{node}} handshake initialized"
- action: handshakeConfirm
log: "{{node}} handshake confirmed"
- action: handshakeComplete
log: "{{node}} handshake completed"
errors:
- error: notFound
message: "node does not exist"
severity: warning
- error: invalidID
message: "invalid ID"
severity: warning
- error: pairingURIInvalid
message: "pairing URI invalid: {{err}}"
#
- error: pairingURITokenInvalid
message: "pairing URI with invalid pairing token"
- error: pairingURISourceIDInvalid
message: "pairing URI without source node ID"
- error: pairingTokenInvalid
message: "pairing token invalid"
- error: notAllowedToCreate
message: "not allowed to create nodes"
log: "could not create nodes; insufficient permissions"
- error: notAllowedToSearch
message: "not allowed to search or list nodes"
log: "could not search or list nodes; insufficient permissions"
- error: notAllowedToManage
message: "not allowed to manage this node"
log: "could not manage {{node}}; insufficient permissions"
- error: notAllowedToPair
message: "not allowed to pair this node"
log: "could not pair {{node}}; insufficient permissions"

View File

@@ -0,0 +1,97 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/cortezaproject/corteza/server/federation/types"
)
type (
// HttpHandshake performs HTTP handshake with remote node
httpNodeHandshake struct {
httpClient httpNodeHandshaker
}
httpNodeHandshaker interface {
Do(*http.Request) (*http.Response, error)
}
)
func HttpHandshake(client httpNodeHandshaker) *httpNodeHandshake {
return &httpNodeHandshake{httpClient: client}
}
// Init calls handshake init on a remote node via HTTP
//
// Called from node.Pair() and sends POST request to remote Corteza
// There, it is handled with node.HandshakeInit
//
// Fn is not part of the node struct to allow injection
func (h httpNodeHandshake) Init(ctx context.Context, n *types.Node, authToken string) error {
var (
endpoint = fmt.Sprintf("%s/nodes/%d/handshake", n.BaseURL, n.SharedNodeID)
payload = map[string]string{
// Share pairing token, so we can authenticate ourselves
"pairToken": n.PairToken,
// Share auth token for the federation service user for this node
"authToken": authToken,
"sharedNodeID": strconv.FormatUint(n.SharedNodeID, 10),
}
)
return h.send(ctx, endpoint, payload)
}
// Confirm calls handshake init on a remote node via HTTP
//
// Called from node.Confirm and sends POST request to remote Corteza
// There, it's handled with node.HandshakeComplete
func (h httpNodeHandshake) Complete(ctx context.Context, n *types.Node, authToken string) error {
var (
endpoint = fmt.Sprintf("%s/nodes/%d/handshake-complete", n.BaseURL, n.SharedNodeID)
payload = map[string]string{
// Share auth token for the federation service user for this node
"authToken": authToken,
}
)
// use n.AuthToken in context
ctx = context.WithValue(ctx, "authToken", n.AuthToken)
return h.send(ctx, endpoint, payload)
}
func (h httpNodeHandshake) send(ctx context.Context, endpoint string, payload map[string]string) error {
body := bytes.NewBuffer(nil)
if err := json.NewEncoder(body).Encode(payload); err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, endpoint, body)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if authToken := ctx.Value("authToken"); authToken != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", authToken))
}
rsp, err := h.httpClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("pairing failed, remote host retuned non-200 response")
}
return nil
}

View File

@@ -0,0 +1,71 @@
package service
import (
"context"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/store"
)
type (
nodeSync struct {
store store.Storer
actionlog actionlog.Recorder
}
NodeSyncService interface {
Create(ctx context.Context, new *types.NodeSync) (*types.NodeSync, error)
Search(ctx context.Context, f types.NodeSyncFilter) (types.NodeSyncSet, types.NodeSyncFilter, error)
LookupLastSuccessfulSync(ctx context.Context, nodeID uint64, syncType string) (*types.NodeSync, error)
}
)
func NodeSync() NodeSyncService {
return &nodeSync{
store: DefaultStore,
actionlog: DefaultActionlog,
}
}
func (svc nodeSync) Create(ctx context.Context, new *types.NodeSync) (*types.NodeSync, error) {
var (
aProps = &nodeSyncActionProps{nodeSync: new}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
if _, err := DefaultNode.FindByID(ctx, new.NodeID); err != nil {
return NodeSyncErrNodeNotFound()
}
return store.CreateFederationNodeSync(ctx, s, new)
})
return new, svc.recordAction(ctx, aProps, NodeSyncActionCreate, err)
}
func (svc nodeSync) Search(ctx context.Context, f types.NodeSyncFilter) (types.NodeSyncSet, types.NodeSyncFilter, error) {
return store.SearchFederationNodeSyncs(ctx, svc.store, f)
}
func (svc nodeSync) LookupLastSuccessfulSync(ctx context.Context, nodeID uint64, syncType string) (ns *types.NodeSync, err error) {
// todo - filter by sync-type does not work
s, _, err := store.SearchFederationNodeSyncs(ctx, svc.store, types.NodeSyncFilter{
NodeID: nodeID,
SyncType: syncType,
SyncStatus: types.NodeSyncStatusSuccess,
Sorting: filter.Sorting{
Sort: filter.SortExprSet{
&filter.SortExpr{Column: "time_of_action", Descending: true},
},
},
Paging: filter.Paging{Limit: 1},
})
if err != nil || len(s) == 0 {
return nil, err
}
return s[0], nil
}

View File

@@ -0,0 +1,359 @@
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/node_sync_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"strings"
"time"
)
type (
nodeSyncActionProps struct {
nodeSync *types.NodeSync
nodeSyncFilter *types.NodeSyncFilter
}
nodeSyncAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *nodeSyncActionProps
}
nodeSyncLogMetaKey struct{}
nodeSyncPropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setNodeSync updates nodeSyncActionProps's nodeSync
//
// This function is auto-generated.
func (p *nodeSyncActionProps) setNodeSync(nodeSync *types.NodeSync) *nodeSyncActionProps {
p.nodeSync = nodeSync
return p
}
// setNodeSyncFilter updates nodeSyncActionProps's nodeSyncFilter
//
// This function is auto-generated.
func (p *nodeSyncActionProps) setNodeSyncFilter(nodeSyncFilter *types.NodeSyncFilter) *nodeSyncActionProps {
p.nodeSyncFilter = nodeSyncFilter
return p
}
// Serialize converts nodeSyncActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p nodeSyncActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.nodeSync != nil {
m.Set("nodeSync.NodeID", p.nodeSync.NodeID, true)
m.Set("nodeSync.SyncStatus", p.nodeSync.SyncStatus, true)
m.Set("nodeSync.SyncType", p.nodeSync.SyncType, true)
m.Set("nodeSync.TimeOfAction", p.nodeSync.TimeOfAction, true)
}
if p.nodeSyncFilter != nil {
m.Set("nodeSyncFilter.Query", p.nodeSyncFilter.Query, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
func (p nodeSyncActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.nodeSync != nil {
// replacement for "{{nodeSync}}" (in order how fields are defined)
pairs = append(
pairs,
"{{nodeSync}}",
fns(
p.nodeSync.NodeID,
p.nodeSync.SyncStatus,
p.nodeSync.SyncType,
p.nodeSync.TimeOfAction,
),
)
pairs = append(pairs, "{{nodeSync.NodeID}}", fns(p.nodeSync.NodeID))
pairs = append(pairs, "{{nodeSync.SyncStatus}}", fns(p.nodeSync.SyncStatus))
pairs = append(pairs, "{{nodeSync.SyncType}}", fns(p.nodeSync.SyncType))
pairs = append(pairs, "{{nodeSync.TimeOfAction}}", fns(p.nodeSync.TimeOfAction))
}
if p.nodeSyncFilter != nil {
// replacement for "{{nodeSyncFilter}}" (in order how fields are defined)
pairs = append(
pairs,
"{{nodeSyncFilter}}",
fns(
p.nodeSyncFilter.Query,
),
)
pairs = append(pairs, "{{nodeSyncFilter.Query}}", fns(p.nodeSyncFilter.Query))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
func (a *nodeSyncAction) String() string {
var props = &nodeSyncActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *nodeSyncAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// NodeSyncActionLookup returns "federation:node_sync.lookup" action
//
// This function is auto-generated.
func NodeSyncActionLookup(props ...*nodeSyncActionProps) *nodeSyncAction {
a := &nodeSyncAction{
timestamp: time.Now(),
resource: "federation:node_sync",
action: "lookup",
log: "looked-up for the last successful sync",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// NodeSyncActionCreate returns "federation:node_sync.create" action
//
// This function is auto-generated.
func NodeSyncActionCreate(props ...*nodeSyncActionProps) *nodeSyncAction {
a := &nodeSyncAction{
timestamp: time.Now(),
resource: "federation:node_sync",
action: "create",
log: "created node_sync",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// NodeSyncErrGeneric returns "federation:node_sync.generic" as *errors.Error
//
// This function is auto-generated.
func NodeSyncErrGeneric(mm ...*nodeSyncActionProps) *errors.Error {
var p = &nodeSyncActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:node_sync"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(nodeSyncLogMetaKey{}, "{err}"),
errors.Meta(nodeSyncPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node-sync.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeSyncErrNotFound returns "federation:node_sync.notFound" as *errors.Error
//
// This function is auto-generated.
func NodeSyncErrNotFound(mm ...*nodeSyncActionProps) *errors.Error {
var p = &nodeSyncActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node_sync does not exist", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "federation:node_sync"),
errors.Meta(nodeSyncPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node-sync.errors.notFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// NodeSyncErrNodeNotFound returns "federation:node_sync.nodeNotFound" as *errors.Error
//
// This function is auto-generated.
func NodeSyncErrNodeNotFound(mm ...*nodeSyncActionProps) *errors.Error {
var p = &nodeSyncActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node does not exist", nil),
errors.Meta("type", "nodeNotFound"),
errors.Meta("resource", "federation:node_sync"),
errors.Meta(nodeSyncPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "node-sync.errors.nodeNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc nodeSync) recordAction(ctx context.Context, props *nodeSyncActionProps, actionFn func(...*nodeSyncActionProps) *nodeSyncAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(nodeSyncLogMetaKey{}), err)
if p, has := m[nodeSyncPropsMetaKey{}]; has {
a.Meta = p.(*nodeSyncActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,38 @@
# List of loggable service actions
resource: federation:node_sync
service: nodeSync
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: error
import:
- github.com/cortezaproject/corteza/server/federation/types
props:
- name: nodeSync
type: "*types.NodeSync"
fields: [ NodeID, SyncStatus, SyncType, TimeOfAction ]
- name: nodeSyncFilter
type: "*types.NodeSyncFilter"
fields: [ Query ]
actions:
- action: lookup
log: "looked-up for the last successful sync"
severity: info
- action: create
log: "created node_sync"
errors:
- error: notFound
message: "node_sync does not exist"
severity: warning
- error: nodeNotFound
message: "node does not exist"
severity: warning

View File

@@ -0,0 +1,77 @@
package service
import (
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/stretchr/testify/require"
"testing"
)
func TestNode_decodePairingURI(t *testing.T) {
var (
tcc = []struct {
name string
uri string
node *types.Node
err error
}{
{
"happy path",
"https://42:secret-secret-secret-secret-1234@example.tld/federation?name=Noddy",
&types.Node{SharedNodeID: 42, Name: "Noddy", BaseURL: "https://example.tld/federation", PairToken: "secret-secret-secret-secret-1234"},
nil,
},
{
"no name",
"https://42:secret-secret-secret-secret-1234@example.tld",
&types.Node{SharedNodeID: 42, BaseURL: "https://example.tld/", PairToken: "secret-secret-secret-secret-1234"},
nil,
},
{
"no token",
"https://42@example.tld",
nil,
NodeErrPairingURITokenInvalid(),
},
{
"no node id",
"https://:secret-secret-secret-secret-1234@example.tld",
nil,
NodeErrPairingURISourceIDInvalid(),
},
{
"invalid URL",
"https://this is not a valid url",
nil,
NodeErrPairingURIInvalid().Wrap(fmt.Errorf(`invalid character " " in host name`)),
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
out, err = (&node{}).decodePairingURI(tc.uri)
)
if tc.err != nil {
req.Equal(tc.err.Error(), err.Error())
} else {
req.NoError(err)
req.Equal(tc.node, out)
}
})
}
}
func TestNode_makePairingURI(t *testing.T) {
var (
svc = &node{host: "example.tld", baseURL: "federation", name: "Noddy"}
req = require.New(t)
n = &types.Node{ID: 42, PairToken: "secret"}
uri = svc.makePairingURI(n)
)
req.Equal("https://42:secret@example.tld/federation?name=Noddy", uri)
}

View File

@@ -0,0 +1,11 @@
package service
import "context"
type (
Processer interface {
Process(ctx context.Context, payload []byte) (ProcesserResponse, error)
}
ProcesserResponse interface{}
)

View File

@@ -0,0 +1,134 @@
package service
import (
"context"
"fmt"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/service/decoder"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
st "github.com/cortezaproject/corteza/server/system/types"
)
type (
dataProcesser struct {
ID uint64
ComposeModuleID uint64
ComposeNamespaceID uint64
NodeBaseURL string
ModuleMappings *types.ModuleFieldMappingSet
ModuleMappingValues *ct.RecordValueSet
SyncService *Sync
Node *types.Node
User *st.User
}
dataProcesserResponse struct {
Processed int
}
)
// Process gets the payload from syncer and
// uses the decode package to decode the whole set, depending on
// the filtering that was used (limit)
func (dp *dataProcesser) Process(ctx context.Context, payload []byte) (ProcesserResponse, error) {
processed := 0
o, err := decoder.DecodeFederationRecordSync([]byte(payload))
if err != nil {
return dataProcesserResponse{
Processed: processed,
}, err
}
if len(o) == 0 {
return dataProcesserResponse{
Processed: processed,
}, nil
}
ctx = auth.SetIdentityToContext(ctx, auth.FederationUser())
for _, er := range o {
var (
rec *ct.Record
err error
)
dp.SyncService.mapper.Merge(&er.Values, dp.ModuleMappingValues, dp.ModuleMappings)
if er.DeletedAt != nil {
// find the record
if rec, err = dp.findRecordByFederationID(ctx, er.ID, dp.ComposeModuleID, dp.ComposeNamespaceID); err != nil {
continue
}
// Handle edge cases where the data doesn't exist anymore
if rec != nil {
dp.SyncService.DeleteRecord(ctx, rec)
}
processed++
continue
}
if er.UpdatedAt != nil {
if rec, err = dp.findRecordByFederationID(ctx, er.ID, dp.ComposeModuleID, dp.ComposeNamespaceID); err != nil {
// could not find existing record
continue
}
if rec != nil {
rec.Values = *dp.ModuleMappingValues
}
}
// if the record was updated on origin, but we somehow do not have it
// create it anyway
if rec == nil {
rec = &ct.Record{
ModuleID: dp.ComposeModuleID,
NamespaceID: dp.ComposeNamespaceID,
Values: *dp.ModuleMappingValues,
Meta: map[string]any{
"federation": dp.NodeBaseURL,
"federation_extrecord": fmt.Sprintf("%d", er.ID),
},
}
}
if rec.ID != 0 {
_, err = dp.SyncService.UpdateRecord(ctx, rec)
} else {
_, err = dp.SyncService.CreateRecord(ctx, rec)
}
if err != nil {
continue
}
processed++
}
return dataProcesserResponse{
Processed: processed,
}, nil
}
// findRecordByFederationID finds any already existing records via
// federation label
func (dp *dataProcesser) findRecordByFederationID(ctx context.Context, recordID, moduleID, namespaceID uint64) (r *ct.Record, err error) {
filter := ct.RecordFilter{
NamespaceID: namespaceID,
ModuleID: moduleID,
Meta: map[string]any{"federation_extrecord": fmt.Sprintf("%d", recordID)}}
if s, err := dp.SyncService.FindRecords(ctx, filter); err == nil {
if len(s) == 1 {
r = s[0]
}
}
return
}

View File

@@ -0,0 +1,230 @@
package service
import (
"context"
"encoding/json"
"fmt"
"testing"
cs "github.com/cortezaproject/corteza/server/compose/service"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
ss "github.com/cortezaproject/corteza/server/system/service"
st "github.com/cortezaproject/corteza/server/system/types"
"github.com/stretchr/testify/require"
)
type (
testSyncService struct {
Sync
}
testSharedModuleService struct {
SharedModuleService
}
testRecordServicePersistSuccess struct {
cs.RecordService
}
testRecordServiceUpdateSuccess struct {
cs.RecordService
}
testRecordServiceDeleteSuccess struct {
cs.RecordService
}
testRecordServicePersistError struct {
cs.RecordService
}
testUserService struct {
ss.UserService
}
testRoleService struct {
ss.RoleService
}
)
func TestProcesserData_persist(t *testing.T) {
var (
tcc = []struct {
name string
payload string
mappings string
persisted int
err string
values *ct.RecordValueSet
s *Sync
}{
{
"successful persist on valid mapping",
`{"response": {"set": [{"recordID":"1","values":[{"name":"Facebook","value":"foobar"}],"createdAt":"2020-12-05T10:10:10Z"}]}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
1,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"successful update on valid mapping and existing federated record",
`{"response": {"set": [{"recordID":"1","values":[{"name":"Facebook","value":"foobar"}],"createdAt":"2020-12-05T10:10:10Z", "updatedAt":"2020-12-06T10:10:10Z"}]}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
1,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServiceUpdateSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"successful delete on valid mapping and existing federated record",
`{"response": {"set": [{"recordID":"1","values":[{"name":"Facebook","value":"foobar"}],"createdAt":"2020-12-05T10:10:10Z", "deletedAt":"2020-12-07T10:10:10Z"}]}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
1,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServiceDeleteSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"persist error on valid mapping",
`{"response": {"set": [{"recordID":"1","values":[{"name":"Facebook","value":"foobar"}]}]}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
0,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServicePersistError{},
&testUserService{},
&testRoleService{}),
},
{
"persist one record on mixed mappings",
`{"response": {"set": [{"recordID":"1","values":[{"name":"THIS_FIELD_NAME_IS_NOT_ON_ORIGIN","value":"this value will not be set"}]}, {"recordID":"1","values":[{"name":"Facebook","value":"this value WILL be set"}]}]}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
2,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"no records from payload",
`{"response": {"set": []}}`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
0,
"",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"validation error, no persist",
`{"respon`,
`[{"origin":{"kind":"String","name":"Description","label":"Description","isMulti":false},"destination":{"kind":"String","name":"Name","label":"Description","isMulti":false}},{"origin":{"kind":"Url","name":"Facebook","label":"Facebook","isMulti":false},"destination":{"kind":"Url","name":"Fb","label":"Facebook","isMulti":false}}]`,
0,
"unexpected end of JSON input",
&ct.RecordValueSet{&ct.RecordValue{Name: "Fb", Value: ""}},
NewSync(
&Syncer{},
&Mapper{},
&testSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
ctx = context.Background()
req = require.New(t)
)
mm := &types.ModuleFieldMappingSet{}
json.Unmarshal([]byte(tc.mappings), mm)
dp := &dataProcesser{
ID: 1,
ComposeModuleID: 1,
ComposeNamespaceID: 1,
NodeBaseURL: "",
ModuleMappings: mm,
ModuleMappingValues: tc.values,
SyncService: tc.s,
Node: &types.Node{},
User: &st.User{},
}
out, err := dp.Process(ctx, []byte(tc.payload))
if tc.err != "" {
req.Equal(tc.err, err.Error())
} else {
req.NoError(err)
}
req.Equal(tc.persisted, out.(dataProcesserResponse).Processed)
})
}
}
// create success
func (s testRecordServicePersistSuccess) Create(_ context.Context, record *ct.Record) (*ct.Record, *ct.RecordValueErrorSet, error) {
return nil, nil, nil
}
func (s testRecordServicePersistSuccess) Find(_ context.Context, filter ct.RecordFilter) (ct.RecordSet, ct.RecordFilter, error) {
return ct.RecordSet{}, ct.RecordFilter{}, nil
}
// update success
func (s testRecordServiceUpdateSuccess) Update(_ context.Context, record *ct.Record) (*ct.Record, *ct.RecordValueErrorSet, error) {
return nil, nil, nil
}
func (s testRecordServiceUpdateSuccess) Find(_ context.Context, filter ct.RecordFilter) (ct.RecordSet, ct.RecordFilter, error) {
return ct.RecordSet{&ct.Record{ID: 2}}, ct.RecordFilter{}, nil
}
// delete success
func (s testRecordServiceDeleteSuccess) DeleteByID(_ context.Context, namespaceID, moduleID uint64, recordID ...uint64) error {
return nil
}
func (s testRecordServiceDeleteSuccess) Find(_ context.Context, filter ct.RecordFilter) (ct.RecordSet, ct.RecordFilter, error) {
return ct.RecordSet{&ct.Record{ID: 2, ModuleID: 2, NamespaceID: 2}}, ct.RecordFilter{}, nil
}
// create error
func (s testRecordServicePersistError) Create(_ context.Context, record *ct.Record) (*ct.Record, *ct.RecordValueErrorSet, error) {
return nil, nil, fmt.Errorf("mocked error")
}

View File

@@ -0,0 +1,120 @@
package service
import (
"context"
"github.com/cortezaproject/corteza/server/federation/service/decoder"
"github.com/cortezaproject/corteza/server/federation/types"
st "github.com/cortezaproject/corteza/server/system/types"
)
type (
structureProcesser struct {
SyncService *Sync
Node *types.Node
User *st.User
}
structureProcesserResponse struct {
ModuleID uint64
Processed int
}
)
// Process gets the payload from syncer and
// uses the decode package to decode the whole set, depending on
// the filtering that was used (limit)
func (dp *structureProcesser) Process(ctx context.Context, payload []byte) (ProcesserResponse, error) {
processed := 0
o, err := decoder.DecodeFederationModuleSync([]byte(payload))
if err != nil {
return structureProcesserResponse{
Processed: processed,
}, err
}
if len(o) == 0 {
return structureProcesserResponse{
Processed: processed,
}, nil
}
for _, em := range o {
new := &types.SharedModule{
NodeID: dp.Node.ID,
ExternalFederationModuleID: em.ID,
Fields: em.Fields,
Handle: em.Handle,
Name: em.Name,
}
existing, err := dp.SyncService.LookupSharedModule(ctx, new)
if err != nil {
return structureProcesserResponse{
ModuleID: new.ExternalFederationModuleID,
Processed: processed,
}, err
}
// create
{
if existing == nil {
_, err := dp.SyncService.CreateSharedModule(ctx, new)
if err != nil {
return structureProcesserResponse{
ModuleID: new.ExternalFederationModuleID,
Processed: processed,
}, err
}
processed++
continue
}
}
// update
{
canUpdate, err := dp.SyncService.CanUpdateSharedModule(ctx, new, existing)
if err != nil {
return structureProcesserResponse{
ModuleID: existing.ExternalFederationModuleID,
Processed: processed,
}, err
}
// stop the sync for this module
if !canUpdate {
return structureProcesserResponse{
ModuleID: new.ExternalFederationModuleID,
Processed: processed,
}, SharedModuleErrFederationSyncStructureChanged(&sharedModuleActionProps{module: existing})
}
existing.Name = new.Name
existing.Handle = new.Handle
existing.Fields = new.Fields
_, err = dp.SyncService.UpdateSharedModule(ctx, existing)
if err != nil {
return structureProcesserResponse{
ModuleID: existing.ExternalFederationModuleID,
Processed: processed,
}, err
}
processed++
continue
}
}
// use paging, but only one per-time
return structureProcesserResponse{
ModuleID: o[0].ID,
Processed: processed,
}, err
}

View File

@@ -0,0 +1,232 @@
package service
import (
"context"
"fmt"
"testing"
cs "github.com/cortezaproject/corteza/server/compose/service"
"github.com/cortezaproject/corteza/server/federation/types"
ss "github.com/cortezaproject/corteza/server/system/service"
st "github.com/cortezaproject/corteza/server/system/types"
"github.com/stretchr/testify/require"
)
type (
testSyncStructureService struct{ Sync }
testSyncStructureServiceCanUpdateFieldsError struct{ Sync }
testStructureSharedModuleService struct{ SharedModuleService }
testStructureSharedModuleServiceError struct{ SharedModuleService }
testStructureSharedModuleServiceCreateNewModule struct{ SharedModuleService }
testStructureSharedModuleServiceCreateNewModuleErr struct{ SharedModuleService }
testStructureSharedModuleServiceUpdateModule struct{ SharedModuleService }
testStructureSharedModuleServiceUpdateModuleErr struct{ SharedModuleService }
testStructureRecordServicePersistSuccess struct{ cs.RecordService }
testStructureUserService struct{ ss.UserService }
testStructureRoleService struct{ ss.RoleService }
)
func TestProcesserStructure_persist(t *testing.T) {
var (
tcc = []struct {
name string
payload string
persisted int
err string
s *Sync
}{
{
"no modules from payload",
`{"response": {"set": []}}`,
0,
"",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"validation error, no persist",
`{"respon`,
0,
"unexpected end of JSON input",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleService{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"lookup existing module error",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[]}]}}`,
0,
"db error",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceError{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"create new module",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[]}]}}`,
1,
"",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceCreateNewModule{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"create new module error",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[]}]}}`,
0,
"could not create new module",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceCreateNewModuleErr{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"could not update module fields error",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[]}]}}`,
0,
"module structure changed",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceUpdateModule{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"update module fields success",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[{"kind":"String","label":"label","isMulti":false,"name":"existingField"}]}]}}`,
1,
"",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceUpdateModule{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
{
"update module error",
`{"response":{"set": [{"moduleID": "11", "nodeID": "21", "composeModuleID": "31", "composeNamespaceID":"32","handle":"handle","name":"name","fields":[]}]}}`,
0,
"could not update module",
NewSync(
&Syncer{},
&Mapper{},
&testStructureSharedModuleServiceUpdateModuleErr{},
&testRecordServicePersistSuccess{},
&testUserService{},
&testRoleService{}),
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
ctx = context.Background()
req = require.New(t)
)
dp := &structureProcesser{
SyncService: tc.s,
Node: &types.Node{},
User: &st.User{},
}
out, err := dp.Process(ctx, []byte(tc.payload))
if tc.err != "" {
if err != nil {
req.Equal(tc.err, err.Error())
} else {
req.Fail("err should not be nil")
}
} else {
req.NoError(err)
}
req.Equal(tc.persisted, out.(structureProcesserResponse).Processed)
})
}
}
func (s testStructureSharedModuleServiceError) Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error) {
return types.SharedModuleSet{}, types.SharedModuleFilter{}, fmt.Errorf("db error")
}
func (s testStructureSharedModuleServiceCreateNewModule) Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error) {
return types.SharedModuleSet{}, types.SharedModuleFilter{}, nil
}
func (s testStructureSharedModuleServiceCreateNewModule) Create(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error) {
return &types.SharedModule{}, nil
}
func (s testStructureSharedModuleServiceCreateNewModuleErr) Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error) {
return types.SharedModuleSet{}, types.SharedModuleFilter{}, nil
}
func (s testStructureSharedModuleServiceCreateNewModuleErr) Create(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error) {
return nil, fmt.Errorf("could not create new module")
}
func (s testStructureSharedModuleServiceUpdateModule) Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error) {
return types.SharedModuleSet{
&types.SharedModule{
ID: 11,
NodeID: 21,
Handle: "handle",
Name: "name",
Fields: types.ModuleFieldSet{
&types.ModuleField{
Kind: "String",
Label: "label",
IsMulti: false,
Name: "existingField",
},
},
}},
types.SharedModuleFilter{},
nil
}
func (s testStructureSharedModuleServiceUpdateModule) Update(ctx context.Context, updated *types.SharedModule) (*types.SharedModule, error) {
return &types.SharedModule{}, nil
}
func (s testStructureSharedModuleServiceUpdateModuleErr) Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error) {
return types.SharedModuleSet{&types.SharedModule{
Fields: types.ModuleFieldSet{},
}},
types.SharedModuleFilter{},
nil
}
func (s testStructureSharedModuleServiceUpdateModuleErr) Update(ctx context.Context, updated *types.SharedModule) (*types.SharedModule, error) {
return nil, fmt.Errorf("could not update module")
}

View File

@@ -0,0 +1,139 @@
package service
import (
"context"
cs "github.com/cortezaproject/corteza/server/compose/service"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/label"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
ss "github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"go.uber.org/zap"
"time"
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
)
type (
Config struct {
ActionLog options.ActionLogOpt
Storage options.ObjectStoreOpt
Federation options.FederationOpt
Server options.HttpServerOpt
}
)
var (
DefaultStore store.Storer
DefaultLogger *zap.Logger
DefaultAccessControl *accessControl
// CurrentSettings represents current system settings
CurrentSettings = &types.AppSettings{}
DefaultOptions options.FederationOpt
DefaultActionlog actionlog.Recorder
DefaultNode *node
DefaultNodeSync NodeSyncService
DefaultExposedModule ExposedModuleService
DefaultSharedModule SharedModuleService
DefaultModuleMapping ModuleMappingService
// wrapper around time.Now() that will aid service testing
now = func() *time.Time {
c := time.Now().Round(time.Second)
return &c
}
// wrapper around id.Next() that will aid service testing
nextID = func() uint64 {
return id.Next()
}
)
func Initialize(_ context.Context, log *zap.Logger, s store.Storer, c Config) (err error) {
DefaultOptions = c.Federation
// we're doing conversion to avoid having
// store interface exposed or generated inside app package
DefaultStore = s
DefaultLogger = log.Named("service")
// @todo add healthcheck(s) for federation sync services
{
tee := zap.NewNop()
policy := actionlog.MakeProductionPolicy()
if !c.ActionLog.Enabled {
policy = actionlog.MakeDisabledPolicy()
} else if c.ActionLog.Debug {
policy = actionlog.MakeDebugPolicy()
tee = logger.MakeDebugLogger()
}
DefaultActionlog = actionlog.NewService(DefaultStore, log, tee, policy)
}
DefaultAccessControl = AccessControl(s)
DefaultNode = Node(
DefaultStore,
ss.DefaultUser,
DefaultActionlog,
func(ctx context.Context, i auth.Identifiable) (token []byte, err error) {
return auth.TokenIssuer.Issue(
ctx,
auth.WithIdentity(i),
auth.WithScope("api"),
auth.WithAudience("federation"),
)
},
c.Federation,
c.Server,
DefaultAccessControl,
)
DefaultNodeSync = NodeSync()
DefaultExposedModule = ExposedModule()
DefaultSharedModule = SharedModule()
DefaultModuleMapping = ModuleMapping()
return
}
func Watchers(ctx context.Context) {
DefaultLogger.Info("Starting federation - warning, this is still an experimental feature")
syncService := NewSync(
&Syncer{},
&Mapper{},
DefaultSharedModule,
cs.DefaultRecord,
ss.DefaultUser,
ss.DefaultRole)
syncStructure := WorkerStructure(syncService, DefaultLogger)
syncData := WorkerData(syncService, DefaultLogger)
go syncStructure.Watch(
ctx,
DefaultOptions.StructureMonitorInterval,
DefaultOptions.StructurePageSize)
go syncData.Watch(
ctx,
DefaultOptions.DataMonitorInterval,
DefaultOptions.DataPageSize)
}
func AddFederationLabel(entity label.LabeledResource, key string, value string) {
entity.SetLabel(key, labelTypes.LabelValue{Val: value})
}

View File

@@ -0,0 +1,171 @@
package service
import (
"context"
"github.com/cortezaproject/corteza/server/pkg/errors"
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/pkg/auth"
"github.com/cortezaproject/corteza/server/store"
)
type (
sharedModule struct {
node node
ac sharedModuleAccessController
compose composeService.ModuleService
store store.Storer
actionlog actionlog.Recorder
}
sharedModuleAccessController interface {
CanCreateModuleOnNode(ctx context.Context, r *types.Node) bool
}
SharedModuleService interface {
Create(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error)
Update(ctx context.Context, updated *types.SharedModule) (*types.SharedModule, error)
Find(ctx context.Context, filter types.SharedModuleFilter) (types.SharedModuleSet, types.SharedModuleFilter, error)
FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (*types.SharedModule, error)
}
)
func SharedModule() *sharedModule {
return &sharedModule{
ac: DefaultAccessControl,
node: *DefaultNode,
compose: composeService.DefaultModule,
store: DefaultStore,
actionlog: DefaultActionlog,
}
}
func (svc sharedModule) FindByID(ctx context.Context, nodeID uint64, moduleID uint64) (module *types.SharedModule, err error) {
err = func() error {
if module, err = loadSharedModule(ctx, svc.store, nodeID, moduleID); err != nil {
return err
}
return nil
}()
return module, err
}
func (svc sharedModule) Create(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error) {
var (
aProps = &sharedModuleActionProps{changed: new}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
var (
node *types.Node
)
if node, err = svc.node.FindByID(ctx, new.NodeID); err != nil {
return SharedModuleErrNodeNotFound()
}
if !svc.ac.CanCreateModuleOnNode(ctx, node) {
return SharedModuleErrNotAllowedToCreate()
}
if err = svc.uniqueCheck(ctx, new); err != nil {
return err
}
new.ID = nextID()
new.CreatedAt = *now()
new.CreatedBy = auth.GetIdentityFromContext(ctx).Identity()
// check if Fields can be unmarshaled to the fields structure
if new.Fields != nil {
}
aProps.setModule(new)
if err = store.CreateFederationSharedModule(ctx, s, new); err != nil {
return err
}
return nil
})
return new, svc.recordAction(ctx, aProps, SharedModuleActionCreate, err)
}
func (svc sharedModule) Update(ctx context.Context, updated *types.SharedModule) (*types.SharedModule, error) {
var (
aProps = &sharedModuleActionProps{module: updated}
)
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
updated.UpdatedAt = now()
updated.UpdatedBy = auth.GetIdentityFromContext(ctx).Identity()
aProps.setModule(updated)
if _, err = svc.node.FindByID(ctx, updated.NodeID); err != nil {
return SharedModuleErrNodeNotFound()
}
if err = store.UpdateFederationSharedModule(ctx, s, updated); err != nil {
return err
}
return nil
})
return updated, svc.recordAction(ctx, aProps, SharedModuleActionUpdate, err)
}
func (svc sharedModule) uniqueCheck(ctx context.Context, m *types.SharedModule) (err error) {
f := types.SharedModuleFilter{
NodeID: m.NodeID,
Handle: m.Handle,
Name: m.Name,
}
if set, _, err := store.SearchFederationSharedModules(ctx, svc.store, f); len(set) > 0 && err == nil {
return SharedModuleErrNotUnique()
} else if err != nil {
return err
}
return nil
}
func (svc sharedModule) Find(ctx context.Context, filter types.SharedModuleFilter) (set types.SharedModuleSet, f types.SharedModuleFilter, err error) {
var (
aProps = &sharedModuleActionProps{filter: &filter}
)
err = func() error {
if set, f, err = store.SearchFederationSharedModules(ctx, svc.store, filter); err != nil {
return err
}
return nil
}()
return set, f, svc.recordAction(ctx, aProps, SharedModuleActionSearch, err)
}
func loadSharedModule(ctx context.Context, s store.FederationSharedModules, nodeID, ID uint64) (res *types.SharedModule, err error) {
if ID == 0 || nodeID == 0 {
return nil, SharedModuleErrInvalidID()
}
if res, err = store.LookupFederationSharedModuleByID(ctx, s, ID); errors.IsNotFound(err) {
err = SharedModuleErrNotFound()
}
if err == nil && nodeID != res.NodeID {
// Make sure chart belongs to the right namespace
return nil, SharedModuleErrNotFound()
}
return
}

View File

@@ -0,0 +1,717 @@
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/shared_module_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/locale"
"strings"
"time"
)
type (
sharedModuleActionProps struct {
module *types.SharedModule
changed *types.SharedModule
filter *types.SharedModuleFilter
node *types.Node
}
sharedModuleAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *sharedModuleActionProps
}
sharedModuleLogMetaKey struct{}
sharedModulePropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setModule updates sharedModuleActionProps's module
//
// This function is auto-generated.
func (p *sharedModuleActionProps) setModule(module *types.SharedModule) *sharedModuleActionProps {
p.module = module
return p
}
// setChanged updates sharedModuleActionProps's changed
//
// This function is auto-generated.
func (p *sharedModuleActionProps) setChanged(changed *types.SharedModule) *sharedModuleActionProps {
p.changed = changed
return p
}
// setFilter updates sharedModuleActionProps's filter
//
// This function is auto-generated.
func (p *sharedModuleActionProps) setFilter(filter *types.SharedModuleFilter) *sharedModuleActionProps {
p.filter = filter
return p
}
// setNode updates sharedModuleActionProps's node
//
// This function is auto-generated.
func (p *sharedModuleActionProps) setNode(node *types.Node) *sharedModuleActionProps {
p.node = node
return p
}
// Serialize converts sharedModuleActionProps to actionlog.Meta
//
// This function is auto-generated.
func (p sharedModuleActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.module != nil {
m.Set("module.ID", p.module.ID, true)
}
if p.changed != nil {
m.Set("changed.ID", p.changed.ID, 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)
}
if p.node != nil {
m.Set("node.ID", p.node.ID, true)
m.Set("node.Name", p.node.Name, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
func (p sharedModuleActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.module != nil {
// replacement for "{{module}}" (in order how fields are defined)
pairs = append(
pairs,
"{{module}}",
fns(
p.module.ID,
),
)
pairs = append(pairs, "{{module.ID}}", fns(p.module.ID))
}
if p.changed != nil {
// replacement for "{{changed}}" (in order how fields are defined)
pairs = append(
pairs,
"{{changed}}",
fns(
p.changed.ID,
),
)
pairs = append(pairs, "{{changed.ID}}", fns(p.changed.ID))
}
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))
}
if p.node != nil {
// replacement for "{{node}}" (in order how fields are defined)
pairs = append(
pairs,
"{{node}}",
fns(
p.node.ID,
p.node.Name,
),
)
pairs = append(pairs, "{{node.ID}}", fns(p.node.ID))
pairs = append(pairs, "{{node.Name}}", fns(p.node.Name))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
func (a *sharedModuleAction) String() string {
var props = &sharedModuleActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *sharedModuleAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// SharedModuleActionSearch returns "federation:shared_module.search" action
//
// This function is auto-generated.
func SharedModuleActionSearch(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "search",
log: "searched for modules",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// SharedModuleActionLookup returns "federation:shared_module.lookup" action
//
// This function is auto-generated.
func SharedModuleActionLookup(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "lookup",
log: "looked-up for a {{module}}",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// SharedModuleActionCreate returns "federation:shared_module.create" action
//
// This function is auto-generated.
func SharedModuleActionCreate(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "create",
log: "created {{module}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// SharedModuleActionUpdate returns "federation:shared_module.update" action
//
// This function is auto-generated.
func SharedModuleActionUpdate(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "update",
log: "updated {{module}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// SharedModuleActionDelete returns "federation:shared_module.delete" action
//
// This function is auto-generated.
func SharedModuleActionDelete(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "delete",
log: "deleted {{module}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// SharedModuleActionUndelete returns "federation:shared_module.undelete" action
//
// This function is auto-generated.
func SharedModuleActionUndelete(props ...*sharedModuleActionProps) *sharedModuleAction {
a := &sharedModuleAction{
timestamp: time.Now(),
resource: "federation:shared_module",
action: "undelete",
log: "undeleted {{module}}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// SharedModuleErrGeneric returns "federation:shared_module.generic" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrGeneric(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "{err}"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.generic"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNotFound returns "federation:shared_module.notFound" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNotFound(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module does not exist", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "federation:shared_module"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.notFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrInvalidID returns "federation:shared_module.invalidID" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrInvalidID(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("invalid ID", nil),
errors.Meta("type", "invalidID"),
errors.Meta("resource", "federation:shared_module"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.invalidID"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrStaleData returns "federation:shared_module.staleData" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrStaleData(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version", nil),
errors.Meta("type", "staleData"),
errors.Meta("resource", "federation:shared_module"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.staleData"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrFederationSyncStructureChanged returns "federation:shared_module.federationSyncStructureChanged" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrFederationSyncStructureChanged(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("module structure changed", nil),
errors.Meta("type", "federationSyncStructureChanged"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "could not update shared module, structure different"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.federationSyncStructureChanged"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNotUnique returns "federation:shared_module.notUnique" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNotUnique(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node not unique", nil),
errors.Meta("type", "notUnique"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "used duplicate node TODO"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.notUnique"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNodeNotFound returns "federation:shared_module.nodeNotFound" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNodeNotFound(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("node does not exist", nil),
errors.Meta("type", "nodeNotFound"),
errors.Meta("resource", "federation:shared_module"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.nodeNotFound"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNotAllowedToCreate returns "federation:shared_module.notAllowedToCreate" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNotAllowedToCreate(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to create modules", nil),
errors.Meta("type", "notAllowedToCreate"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "could not create modules; insufficient permissions"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.notAllowedToCreate"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNotAllowedToManage returns "federation:shared_module.notAllowedToManage" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNotAllowedToManage(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to manage this module", nil),
errors.Meta("type", "notAllowedToManage"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "could not read {{module}}; insufficient permissions"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.notAllowedToManage"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// SharedModuleErrNotAllowedToMap returns "federation:shared_module.notAllowedToMap" as *errors.Error
//
// This function is auto-generated.
func SharedModuleErrNotAllowedToMap(mm ...*sharedModuleActionProps) *errors.Error {
var p = &sharedModuleActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to map this module", nil),
errors.Meta("type", "notAllowedToMap"),
errors.Meta("resource", "federation:shared_module"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(sharedModuleLogMetaKey{}, "could not map {{module}}; insufficient permissions"),
errors.Meta(sharedModulePropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "federation"),
errors.Meta(locale.ErrorMetaKey{}, "shared-module.errors.notAllowedToMap"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
func (svc sharedModule) recordAction(ctx context.Context, props *sharedModuleActionProps, actionFn func(...*sharedModuleActionProps) *sharedModuleAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(sharedModuleLogMetaKey{}), err)
if p, has := m[sharedModulePropsMetaKey{}]; has {
a.Meta = p.(*sharedModuleActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,86 @@
# List of loggable service actions
resource: federation:shared_module
service: sharedModule
# 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.SharedModule"
fields: [ ID ]
- name: changed
type: "*types.SharedModule"
fields: [ ID ]
- name: filter
type: "*types.SharedModuleFilter"
fields: [ query, sort, limit ]
- name: node
type: "*types.Node"
fields: [ ID, Name ]
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: "module was modified by someone else or by a workflow after you've opened it. Please refresh to see the latest updated version"
severity: warning
- error: federationSyncStructureChanged
message: "module structure changed"
log: "could not update shared module, structure different"
- error: notUnique
message: "node not unique"
log: "used duplicate node TODO"
severity: warning
- error: nodeNotFound
message: "node does not exist"
severity: warning
- error: notAllowedToCreate
message: "not allowed to create modules"
log: "could not create modules; insufficient permissions"
- error: notAllowedToManage
message: "not allowed to manage this module"
log: "could not read {{module}}; insufficient permissions"
- error: notAllowedToMap
message: "not allowed to map this module"
log: "could not map {{module}}; insufficient permissions"

View File

@@ -0,0 +1,228 @@
package service
import (
"context"
"encoding/json"
"fmt"
"io"
"time"
cs "github.com/cortezaproject/corteza/server/compose/service"
ct "github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/filter"
ss "github.com/cortezaproject/corteza/server/system/service"
st "github.com/cortezaproject/corteza/server/system/types"
)
type (
Sync struct {
syncer *Syncer
mapper *Mapper
sharedModuleService SharedModuleService
composeRecordService cs.RecordService
systemUserService ss.UserService
systemRoleService ss.RoleService
}
)
func NewSync(s *Syncer, m *Mapper, sm SharedModuleService, cs cs.RecordService, us ss.UserService, rs ss.RoleService) *Sync {
return &Sync{
syncer: s,
mapper: m,
sharedModuleService: sm,
composeRecordService: cs,
systemUserService: us,
systemRoleService: rs,
}
}
// CanUpdateSharedModule checks the origin and destination module
// compatibility of fields
// It currently checks if all of the fields are exactly the same
// TODO - check if any of the newly missing fields are actually being used so a safe update
// is possible
func (s *Sync) CanUpdateSharedModule(ctx context.Context, new *types.SharedModule, existing *types.SharedModule) (bool, error) {
// check for mapped fields
var (
fstr, f2str []byte
err error
)
if fstr, err = json.Marshal(new.Fields); err != nil {
return false, err
}
if f2str, err = json.Marshal(existing.Fields); err != nil {
return false, err
}
return string(fstr) == string(f2str), nil
}
// ProcessPayload passes the payload to the syncer lib
func (s *Sync) ProcessPayload(ctx context.Context, payload []byte, out chan Url, url types.SyncerURI, processer Processer) (ProcesserResponse, error) {
ctx = auth.SetIdentityToContext(ctx, auth.FederationUser())
return s.syncer.Process(ctx, payload, out, url, processer)
}
// QueueUrl passes the url to the syncer
func (s *Sync) QueueUrl(url Url, out chan Url) {
s.syncer.Queue(url, out)
}
// FetchUrl passes the url to be fetched to the syncer
func (s *Sync) FetchUrl(ctx context.Context, url string) (io.Reader, error) {
return s.syncer.Fetch(ctx, url)
}
// CreateRecord wraps the compose Record service Create
func (s *Sync) CreateRecord(ctx context.Context, rec *ct.Record) (out *ct.Record, err error) {
out, _, err = s.composeRecordService.Create(ctx, rec)
return
}
// UpdateRecord wraps the compose Record service Update
func (s *Sync) UpdateRecord(ctx context.Context, rec *ct.Record) (out *ct.Record, err error) {
out, _, err = s.composeRecordService.Update(ctx, rec)
return
}
// DeleteRecord wraps the compose Record service Update
func (s *Sync) DeleteRecord(ctx context.Context, rec *ct.Record) error {
return s.composeRecordService.DeleteByID(ctx, rec.NamespaceID, rec.ModuleID, rec.ID)
}
// FindRecord find the record via federation label
func (s *Sync) FindRecords(ctx context.Context, filter ct.RecordFilter) (set ct.RecordSet, err error) {
set, _, err = s.composeRecordService.Find(ctx, filter)
return
}
// LookupSharedModule find the shared module if exists
func (s *Sync) LookupSharedModule(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error) {
var sm *types.SharedModule
list, _, err := s.sharedModuleService.Find(ctx, types.SharedModuleFilter{
NodeID: new.NodeID,
ExternalFederationModuleID: new.ExternalFederationModuleID})
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
sm = list[0]
return sm, nil
}
// UpdateSharedModule wraps the federation SharedModule service Update
func (s *Sync) UpdateSharedModule(ctx context.Context, updated *types.SharedModule) (*types.SharedModule, error) {
return s.sharedModuleService.Update(ctx, updated)
}
// CreateSharedModule wraps the federation SharedModule service Create
func (s *Sync) CreateSharedModule(ctx context.Context, new *types.SharedModule) (*types.SharedModule, error) {
return s.sharedModuleService.Create(ctx, new)
}
// GetPairedNodes finds successfuly paired nodes
func (s *Sync) GetPairedNodes(ctx context.Context) (types.NodeSet, error) {
set, _, err := DefaultNode.Search(ctx, types.NodeFilter{Status: types.NodeStatusPaired})
if err != nil {
return nil, err
}
return set, nil
}
func (s *Sync) GetSharedModules(ctx context.Context, nodeID uint64) (types.SharedModuleSet, error) {
set, _, err := DefaultSharedModule.Find(ctx, types.SharedModuleFilter{NodeID: nodeID})
if err != nil {
return nil, err
}
set, _ = set.Filter(func(sm *types.SharedModule) (bool, error) {
return len(sm.Fields) > 0, nil
})
return set, nil
}
func (s *Sync) GetModuleMappings(ctx context.Context, moduleID uint64) (out *types.ModuleMapping, err error) {
out, err = DefaultModuleMapping.FindByID(ctx, moduleID)
return
}
func (s *Sync) PrepareModuleMappings(ctx context.Context, mappings *types.ModuleMapping) (ct.RecordValueSet, error) {
return s.mapper.Prepare((*mappings).FieldMapping), nil
}
func (s *Sync) GetLastStructureSyncStatus(ctx context.Context, nodeID, externalFederationModuleID uint64) (sync types.NodeSync, err error) {
var list types.NodeSyncSet
list, _, err = DefaultNodeSync.Search(ctx, types.NodeSyncFilter{
NodeID: nodeID,
ModuleID: externalFederationModuleID,
SyncType: types.NodeSyncTypeStructure,
Sorting: filter.Sorting{
Sort: filter.SortExprSet{
&filter.SortExpr{Column: "time_of_action", Descending: true},
},
},
Paging: filter.Paging{Limit: 1},
})
if err != nil {
return
}
if len(list) == 0 {
return
}
return *list[0], nil
}
func (s *Sync) GetLastSyncTime(ctx context.Context, nodeID uint64, syncType string) (*time.Time, error) {
ns, err := DefaultNodeSync.LookupLastSuccessfulSync(ctx, nodeID, syncType)
if err != nil || ns == nil {
return nil, err
}
return &ns.TimeOfAction, nil
}
// LoadUserWithRoles gets the federation user, that was
// created at node pairing process
func (s *Sync) LoadUserWithRoles(ctx context.Context, nodeID uint64) (*st.User, error) {
var (
u *st.User
err error
)
// get the federated user, associated for this node
if u, err = s.systemUserService.FindByHandle(ctx, fmt.Sprintf("federation_%d", nodeID)); err != nil {
return nil, err
}
// attach the roles
rr, _, err := s.systemRoleService.Find(ctx, st.RoleFilter{MemberID: u.ID})
if err != nil {
return nil, err
}
u.SetRoles(rr.IDs()...)
return u, nil
}

View File

@@ -0,0 +1,18 @@
package service
import (
"context"
"time"
)
type (
worker interface {
PrepareForNodes(ctx context.Context, sync *Sync, urls chan Url)
Watch(ctx context.Context, delay time.Duration, limit int)
}
)
const (
defaultDelay = time.Second * 10
defaultPage = 10
)

View File

@@ -0,0 +1,281 @@
package service
import (
"context"
"fmt"
"io"
"time"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/logger"
"go.uber.org/zap"
)
type (
syncWorkerData struct {
syncService *Sync
logger *zap.Logger
delay time.Duration
limit int
}
)
func WorkerData(sync *Sync, logger *zap.Logger) *syncWorkerData {
return &syncWorkerData{
syncService: sync,
logger: logger,
limit: defaultPage,
delay: defaultDelay,
}
}
func (w *syncWorkerData) queueUrl(url *types.SyncerURI, urls chan Url, meta Processer) {
t := Url{
Url: *url,
Meta: meta,
}
w.syncService.QueueUrl(t, urls)
}
func (w *syncWorkerData) PrepareForNodes(ctx context.Context, urls chan Url) {
nodes, err := w.syncService.GetPairedNodes(ctx)
if err != nil {
w.logger.Info("could not get paired nodes", zap.Error(err))
return
}
// get all shared modules and their module mappings
for _, n := range nodes {
// get the user, associated for this node
u, err := w.syncService.LoadUserWithRoles(ctx, n.ID)
if err != nil {
w.logger.Info("could not preload federation user, skipping",
logger.Uint64("nodeID", n.ID),
zap.Error(err))
continue
}
set, err := w.syncService.GetSharedModules(ctx, n.ID)
if err != nil {
w.logger.Info("could not get shared modules, skipping",
logger.Uint64("nodeID", n.ID),
zap.Error(err))
continue
}
if len(set) == 0 {
w.logger.Info("there are no valid shared modules, skipping",
logger.Uint64("nodeID", n.ID),
zap.Error(err))
continue
}
// go through set and prepare module mappings for it
for _, sm := range set {
z := []zap.Field{
logger.Uint64("nodeID", n.ID),
logger.Uint64("moduleID", sm.ID),
zap.String("host", n.BaseURL),
zap.String("delay", w.delay.String()),
zap.Int("pagesize", w.limit),
}
// TODO: rename lastSyncStatus to lastSybc prolly!!!!
// if the last sync was error'd, log and skip
lastSyncStatus, err := w.syncService.GetLastStructureSyncStatus(ctx, n.ID, sm.ExternalFederationModuleID)
if err != nil {
w.logger.Info("could not get last sync status, skipping", z...)
continue
}
if lastSyncStatus.SyncStatus == types.NodeSyncStatusError {
w.logger.Info("last structure sync was not complete, admin resolve needed, skipping", z...)
continue
}
mappings, _ := w.syncService.GetModuleMappings(ctx, sm.ID)
if mappings == nil {
w.logger.Info("could not prepare module mappings for shared module, skipping", z...)
continue
}
mappingValues, err := w.syncService.PrepareModuleMappings(ctx, mappings)
if err != nil || mappingValues == nil {
w.logger.Info("could not prepare module mappings for shared module, skipping", z...)
continue
}
// get the last sync per-node
lastSync, _ := w.syncService.GetLastSyncTime(ctx, n.ID, types.NodeSyncTypeData)
// todo this is where we get the last sync value. prolly the issue could be on the fetch!
basePath := fmt.Sprintf("/nodes/%d/modules/%d/records/", n.SharedNodeID, sm.ExternalFederationModuleID)
if lastSync != nil {
z = append(z, zap.Time("lastSync", *lastSync))
}
w.logger.Info("starting data sync", z...)
url := types.SyncerURI{
BaseURL: n.BaseURL,
Path: basePath,
Limit: w.limit,
LastSync: lastSync,
}
processer := &dataProcesser{
ID: sm.ExternalFederationModuleID,
ComposeModuleID: mappings.ComposeModuleID,
ComposeNamespaceID: mappings.ComposeNamespaceID,
ModuleMappings: &mappings.FieldMapping,
ModuleMappingValues: &mappingValues,
SyncService: w.syncService,
User: u,
Node: n,
}
go w.queueUrl(&url, urls, processer)
}
}
}
func (w *syncWorkerData) Watch(ctx context.Context, delay time.Duration, limit int) {
var (
urls = make(chan Url, 100)
payloads = make(chan Payload, 10)
countProcess = 0
)
w.delay = delay
w.limit = limit
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx = auth.SetIdentityToContext(ctx, auth.FederationUser())
ticker := time.NewTicker(delay)
w.PrepareForNodes(ctx, urls)
for {
select {
case <-ctx.Done():
// do any cleanup here
w.logger.Info("stopping sync", zap.Int("processed", countProcess))
return
case <-ticker.C:
// do the whole process again
w.PrepareForNodes(ctx, urls)
case url := <-urls:
select {
case <-ctx.Done():
// do any cleanup here
w.logger.Info("stopping sync", zap.Int("processed", countProcess))
return
default:
}
s, _ := url.Url.String()
meta := url.Meta.(*dataProcesser)
// use the authToken from node pairing
ctx = context.WithValue(ctx, FederationUserToken, meta.Node.AuthToken)
// TODO: Implement logic to handle last sync time before sending request
responseBody, err := w.syncService.FetchUrl(ctx, s)
if err != nil {
w.logger.Error("could not fetch data from url, skipping",
zap.Error(err),
zap.String("url", s),
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
continue
}
spayload := Payload{
Payload: responseBody,
Meta: url.Meta,
}
payloads <- spayload
case p := <-payloads:
meta := p.Meta.(*dataProcesser)
body, err := io.ReadAll(p.Payload)
// handle error
if err != nil {
w.logger.Error("could not read data from synced url, skipping",
zap.Error(err),
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
continue
}
basePath := fmt.Sprintf("/nodes/%d/modules/%d/records/", meta.Node.SharedNodeID, meta.ID)
u := types.SyncerURI{
BaseURL: meta.NodeBaseURL,
Path: basePath,
Limit: w.limit,
}
// the base path is okay
processed, errProcess := w.syncService.ProcessPayload(ctx, body, urls, u, meta)
if response, ok := processed.(dataProcesserResponse); ok {
countProcess += response.Processed
} else {
w.logger.Error("unexpected response type", zap.Any("response", processed))
}
// error raised before the actual persist process
// ignore
syncStatus := types.NodeSyncStatusSuccess
if errProcess != nil {
syncStatus = types.NodeSyncStatusError
}
new := &types.NodeSync{
NodeID: meta.Node.ID,
ModuleID: meta.ID,
SyncStatus: syncStatus,
SyncType: types.NodeSyncTypeData,
TimeOfAction: time.Now().UTC(),
}
new, err = DefaultNodeSync.Create(ctx, new)
if err != nil {
w.logger.Info("could not update sync status", zap.Error(err))
}
if errProcess != nil {
w.logger.Info("error on persisting structure", zap.Error(errProcess))
} else {
w.logger.Info("processed objects",
zap.Int("processed", processed.(dataProcesserResponse).Processed),
logger.Uint64("nodeID", meta.Node.ID))
}
}
}
}

View File

@@ -0,0 +1,230 @@
package service
import (
"context"
"fmt"
"io/ioutil"
"time"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/logger"
"go.uber.org/zap"
)
type (
syncWorkerStructure struct {
syncService *Sync
logger *zap.Logger
delay time.Duration
limit int
}
)
func WorkerStructure(sync *Sync, logger *zap.Logger) *syncWorkerStructure {
return &syncWorkerStructure{
syncService: sync,
logger: logger,
limit: defaultPage,
delay: defaultDelay,
}
}
func (w *syncWorkerStructure) queueUrl(url *types.SyncerURI, urls chan Url, meta Processer) {
t := Url{
Url: *url,
Meta: meta,
}
w.syncService.QueueUrl(t, urls)
}
func (w *syncWorkerStructure) PrepareForNodes(ctx context.Context, urls chan Url) {
nodes, err := w.syncService.GetPairedNodes(ctx)
if err != nil {
return
}
// get all shared modules and their module mappings
for _, n := range nodes {
// get the user, associated for this node
u, err := w.syncService.LoadUserWithRoles(ctx, n.ID)
if err != nil {
w.logger.Info("could not preload federation user, skipping",
logger.Uint64("nodeID", n.ID),
zap.Error(err))
continue
}
// get the last sync per-node
lastSync, _ := w.syncService.GetLastSyncTime(ctx, n.ID, types.NodeSyncTypeStructure)
basePath := fmt.Sprintf("/nodes/%d/modules/exposed/", n.SharedNodeID)
z := []zap.Field{
logger.Uint64("nodeID", n.ID),
zap.String("host", n.BaseURL),
zap.String("delay", w.delay.String()),
zap.Int("pagesize", w.limit),
}
if lastSync != nil {
z = append(z, zap.Time("lastSync", *lastSync))
}
w.logger.Info("starting structure sync", z...)
url := types.SyncerURI{
BaseURL: n.BaseURL,
Path: basePath,
Limit: w.limit,
LastSync: lastSync,
}
processer := &structureProcesser{
SyncService: w.syncService,
User: u,
Node: n,
}
go w.queueUrl(&url, urls, processer)
}
}
func (w *syncWorkerStructure) Watch(ctx context.Context, delay time.Duration, limit int) {
var (
urls = make(chan Url, 100)
payloads = make(chan Payload, 10)
countProcess = 0
)
w.delay = delay
w.limit = limit
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx = auth.SetIdentityToContext(ctx, auth.FederationUser())
ticker := time.NewTicker(delay)
w.PrepareForNodes(ctx, urls)
for {
select {
case <-ctx.Done():
// do any cleanup here
w.logger.Info("stopping sync", zap.Int("processed", countProcess))
return
case <-ticker.C:
// do the whole process again
w.PrepareForNodes(ctx, urls)
case url := <-urls:
select {
case <-ctx.Done():
// do any cleanup here
w.logger.Info("stopping sync", zap.Int("processed", countProcess))
return
default:
}
s, _ := url.Url.String()
meta := url.Meta.(*structureProcesser)
// use the authToken from node pairing
ctx = context.WithValue(ctx, FederationUserToken, meta.Node.AuthToken)
responseBody, err := w.syncService.FetchUrl(ctx, s)
if err != nil {
w.logger.Error("could not fetch data from url, skipping",
zap.Error(err),
zap.String("url", s),
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
continue
}
spayload := Payload{
Payload: responseBody,
Meta: url.Meta,
}
payloads <- spayload
case p := <-payloads:
meta := p.Meta.(*structureProcesser)
body, err := ioutil.ReadAll(p.Payload)
// handle error
if err != nil {
w.logger.Error("could not read data from synced url, skipping",
zap.Error(err),
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
continue
}
basePath := fmt.Sprintf("/nodes/%d/modules/exposed/", meta.Node.SharedNodeID)
u := types.SyncerURI{
BaseURL: p.Meta.(*structureProcesser).Node.BaseURL,
Path: basePath,
Limit: w.limit,
}
processed, errProcess := w.syncService.ProcessPayload(ctx, body, urls, u, meta)
var processedModID uint64
switch v := processed.(type) {
case structureProcesserResponse:
processedModID = v.ModuleID
countProcess += v.Processed
case int:
countProcess += v
}
// error raised before the actual persist process
// ignore
if moduleID := processedModID; moduleID == 0 {
w.logger.Info("no structure change from last change, skipping",
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
continue
}
syncStatus := types.NodeSyncStatusSuccess
if errProcess != nil {
syncStatus = types.NodeSyncStatusError
}
new := &types.NodeSync{
NodeID: meta.Node.ID,
ModuleID: processedModID,
SyncStatus: syncStatus,
SyncType: types.NodeSyncTypeStructure,
TimeOfAction: time.Now().UTC(),
}
new, err = DefaultNodeSync.Create(ctx, new)
if err != nil {
w.logger.Info("could not update sync status", zap.Error(err))
}
if errProcess != nil {
w.logger.Info("error on handling payload", zap.Error(errProcess))
} else {
w.logger.Info("processed objects",
zap.Int("processed", processed.(structureProcesserResponse).Processed),
logger.Uint64("nodeID", meta.Node.ID),
zap.String("host", meta.Node.BaseURL))
}
}
}
}

View File

@@ -0,0 +1,102 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/cortezaproject/corteza/server/federation/types"
)
type (
Syncer struct {
client http.Client
}
Url struct {
Url types.SyncerURI
Meta Processer
}
Payload struct {
Payload io.Reader
Meta Processer
}
AuxResponseSet struct {
Response struct {
Filter struct {
NodeID string
ComposeModuleID string
Query string
Limit int
NextPage string
PrevPage string
} `json:"filter"`
} `json:"response"`
}
)
const FederationUserToken string = "authToken"
func (h *Syncer) Queue(url Url, out chan Url) {
out <- url
}
func (h *Syncer) Fetch(ctx context.Context, url string) (io.Reader, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if authToken := ctx.Value(FederationUserToken); authToken != nil {
req.Header.Add("Authorization", `Bearer `+authToken.(string))
}
req.Header.Add("Accept", "application/json")
resp, err := h.client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(fmt.Sprintf("invalid return status: %d", resp.StatusCode))
}
return resp.Body, nil
}
func (h *Syncer) Process(ctx context.Context, payload []byte, out chan Url, url types.SyncerURI, processer Processer) (ProcesserResponse, error) {
aux, err := h.ParseHeader(ctx, payload)
if err != nil {
return 0, err
}
if aux.Response.Filter.NextPage != "" {
url.NextPage = aux.Response.Filter.NextPage
out <- Url{
Url: url,
Meta: processer,
}
}
return processer.Process(ctx, payload)
}
func (h *Syncer) ParseHeader(ctx context.Context, payload []byte) (aux AuxResponseSet, err error) {
err = json.Unmarshal(payload, &aux)
return
}
func NewSyncer() *Syncer {
return &Syncer{
client: http.Client{
Timeout: time.Duration(3) * time.Second,
},
}
}

View File

@@ -0,0 +1,161 @@
package service
import (
"bytes"
"context"
"io/ioutil"
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/federation/types"
"github.com/stretchr/testify/require"
)
type (
RoundTripFunc func(req *http.Request) *http.Response
testProcesser struct{}
)
func (p *testProcesser) Process(ctx context.Context, payload []byte) (ProcesserResponse, error) {
return 0, nil
}
func TestSyncer_process(t *testing.T) {
var (
req = require.New(t)
syncer = &Syncer{}
ctx = context.Background()
)
c := make(chan Url, 2)
u := types.SyncerURI{
Limit: 10,
NextPage: "123",
}
tp := &testProcesser{}
go syncer.Process(ctx, []byte(`{"response":{"filter":{"limit":1, "nextPage":"456"}}}`), c, u, tp)
select {
case url := <-c:
req.Equal(url.Url.NextPage, "456")
break
}
}
func TestSyncer_parseHeader(t *testing.T) {
var (
req = require.New(t)
syncer = NewSyncer()
ctx = context.Background()
)
c := make(chan Url, 2)
u := types.SyncerURI{
Limit: 10,
NextPage: "123",
}
tp := &testProcesser{}
n, err := syncer.Process(ctx, []byte(`{"response":{"filt`), c, u, tp)
req.EqualError(err, "unexpected end of JSON input")
req.Equal(n, 0)
}
func TestSyncer_response(t *testing.T) {
var (
req = require.New(t)
)
tests := []struct {
name string
url string
expect string
syncer *Syncer
ctx context.Context
}{
{
name: "non 200 request",
url: "http://example.ltd",
syncer: &Syncer{
client: *NewHttpClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 500,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":{"message":"common error response"}}`)),
Header: make(http.Header),
}
}),
},
ctx: context.Background(),
expect: "invalid return status: 500",
},
{
name: "invalid hostname",
url: "http://examp le.ltd",
syncer: &Syncer{
client: *NewHttpClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":{"message":"common error response"}}`)),
Header: make(http.Header),
}
}),
},
ctx: context.Background(),
expect: "parse \"http://examp le.ltd\": invalid character \" \" in host name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.syncer.Fetch(tt.ctx, tt.url)
req.EqualError(err, tt.expect)
})
}
}
func TestSyncer_authTokenCorrectlySet(t *testing.T) {
var (
req = require.New(t)
expectedAuthToken = "TEST_JWT_TOKEN"
actualAuthToken = ""
)
ctx := context.WithValue(
context.Background(),
FederationUserToken,
expectedAuthToken)
syncer := &Syncer{
client: *NewHttpClient(func(r *http.Request) *http.Response {
actualAuthToken = r.Header.Get("Authorization")
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString("OK")),
Header: make(http.Header),
}
}),
}
_, err := syncer.Fetch(ctx, "http://example.ltd")
req.Equal("Bearer "+expectedAuthToken, actualAuthToken)
req.NoError(err)
}
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
func NewHttpClient(rt RoundTripFunc) *http.Client {
return &http.Client{
Transport: RoundTripFunc(rt),
}
}

View File

@@ -0,0 +1,97 @@
package federation
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
sharedModule: {
features: {
labels: false
}
parents: [
{handle: "node"},
]
model: {
ident: "federation_module_shared"
attributes: {
id: schema.IdField
handle: schema.HandleField
node_id: {
sortable: true,
ident: "nodeID",
goType: "uint64",
storeIdent: "rel_node"
dal: { type: "ID" }
}
name: {
sortable: true
dal: {}
}
external_federation_module_id: {
sortable: true,
ident: "externalFederationModuleID",
goType: "uint64",
storeIdent: "xref_module",
dal: { type: "ID" }
}
fields: {
goType: "types.ModuleFieldSet"
dal: { type: "JSON", defaultEmptyObject: true }
omitSetter: true
omitGetter: true
}
created_at: schema.SortableTimestampNowField
updated_at: schema.SortableTimestampNilField
deleted_at: schema.SortableTimestampNilField
created_by: schema.AttributeUserRef
updated_by: schema.AttributeUserRef
deleted_by: schema.AttributeUserRef
}
indexes: {
"primary": { attribute: "id" }
}
}
envoy: {
omit: true
}
filter: {
struct: {
node_id: { goType: "uint64", ident: "nodeID", storeIdent: "rel_node" }
handle: { goType: "string" }
name: { goType: "string" }
external_federation_module_id: { goType: "uint64", storeIdent: "xref_module", ident: "externalFederationModuleID" }
}
query: ["name", "handle"]
byValue: ["handle", "node_id", "name", "external_federation_module_id"]
}
rbac: {
operations: {
"map": description: "Map shared module"
}
}
store: {
ident: "federationSharedModule"
api: {
lookups: [
{
fields: ["id"]
description: """
searches for shared federation module by ID
It returns shared federation module
"""
}
]
}
}
}

View File

@@ -0,0 +1,42 @@
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"`
ComposeNamespaceID uint64 `json:"composeNamespaceID,string"`
Handle string `json:"handle"`
Name string `json:"name"`
Fields ModuleFieldSet `json:"fields"`
CreatedAt time.Time `json:"createdAt,omitempty"`
CreatedBy uint64 `json:"createdBy,string" `
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
UpdatedBy uint64 `json:"updatedBy,string,omitempty" `
DeletedAt *time.Time `json:"deletedAt,omitempty"`
DeletedBy uint64 `json:"deletedBy,string,omitempty" `
}
ExposedModuleFilter struct {
NodeID uint64 `json:"nodeID,string"`
ComposeModuleID uint64 `json:"composeModuleID,string"`
ComposeNamespaceID uint64 `json:"composeNamespaceID,string"`
LastSync uint64 `json:"lastSync"`
Handle string `json:"handle"`
Name string `json:"name"`
Query string `json:"query"`
Check func(*ExposedModule) (bool, error) `json:"-"`
filter.Sorting
filter.Paging
}
)

View File

@@ -0,0 +1,321 @@
package types
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"github.com/cortezaproject/corteza/server/pkg/cast2"
)
func (r Node) GetID() uint64 { return r.ID }
func (r *Node) GetValue(name string, pos uint) (any, error) {
if r == nil {
return nil, nil
}
switch name {
case "authToken", "AuthToken":
return r.AuthToken, nil
case "baseURL", "BaseURL":
return r.BaseURL, nil
case "contact", "Contact":
return r.Contact, nil
case "createdAt", "CreatedAt":
return r.CreatedAt, nil
case "createdBy", "CreatedBy":
return r.CreatedBy, nil
case "deletedAt", "DeletedAt":
return r.DeletedAt, nil
case "deletedBy", "DeletedBy":
return r.DeletedBy, nil
case "id", "ID":
return r.ID, nil
case "name", "Name":
return r.Name, nil
case "pairToken", "PairToken":
return r.PairToken, nil
case "sharedNodeID", "SharedNodeID":
return r.SharedNodeID, nil
case "status", "Status":
return r.Status, nil
case "updatedAt", "UpdatedAt":
return r.UpdatedAt, nil
case "updatedBy", "UpdatedBy":
return r.UpdatedBy, nil
}
return nil, nil
}
func (r *Node) SetValue(name string, pos uint, value any) (err error) {
if r == nil {
r = &Node{}
}
switch name {
case "authToken", "AuthToken":
return cast2.String(value, &r.AuthToken)
case "baseURL", "BaseURL":
return cast2.String(value, &r.BaseURL)
case "contact", "Contact":
return cast2.String(value, &r.Contact)
case "createdAt", "CreatedAt":
return cast2.Time(value, &r.CreatedAt)
case "createdBy", "CreatedBy":
return cast2.Uint64(value, &r.CreatedBy)
case "deletedAt", "DeletedAt":
return cast2.TimePtr(value, &r.DeletedAt)
case "deletedBy", "DeletedBy":
return cast2.Uint64(value, &r.DeletedBy)
case "id", "ID":
return cast2.Uint64(value, &r.ID)
case "name", "Name":
return cast2.String(value, &r.Name)
case "pairToken", "PairToken":
return cast2.String(value, &r.PairToken)
case "sharedNodeID", "SharedNodeID":
return cast2.Uint64(value, &r.SharedNodeID)
case "status", "Status":
return cast2.String(value, &r.Status)
case "updatedAt", "UpdatedAt":
return cast2.TimePtr(value, &r.UpdatedAt)
case "updatedBy", "UpdatedBy":
return cast2.Uint64(value, &r.UpdatedBy)
}
return nil
}
func (r NodeSync) GetID() uint64 {
// The resource does not define an ID field
return 0
}
func (r *NodeSync) GetValue(name string, pos uint) (any, error) {
if r == nil {
return nil, nil
}
switch name {
case "moduleID", "ModuleID":
return r.ModuleID, nil
case "nodeID", "NodeID":
return r.NodeID, nil
case "syncStatus", "SyncStatus":
return r.SyncStatus, nil
case "syncType", "SyncType":
return r.SyncType, nil
case "timeOfAction", "TimeOfAction":
return r.TimeOfAction, nil
}
return nil, nil
}
func (r *NodeSync) SetValue(name string, pos uint, value any) (err error) {
if r == nil {
r = &NodeSync{}
}
switch name {
case "moduleID", "ModuleID":
return cast2.Uint64(value, &r.ModuleID)
case "nodeID", "NodeID":
return cast2.Uint64(value, &r.NodeID)
case "syncStatus", "SyncStatus":
return cast2.String(value, &r.SyncStatus)
case "syncType", "SyncType":
return cast2.String(value, &r.SyncType)
case "timeOfAction", "TimeOfAction":
return cast2.Time(value, &r.TimeOfAction)
}
return nil
}
func (r ExposedModule) GetID() uint64 { return r.ID }
func (r *ExposedModule) GetValue(name string, pos uint) (any, error) {
if r == nil {
return nil, nil
}
switch name {
case "composeModuleID", "ComposeModuleID":
return r.ComposeModuleID, nil
case "composeNamespaceID", "ComposeNamespaceID":
return r.ComposeNamespaceID, nil
case "createdAt", "CreatedAt":
return r.CreatedAt, nil
case "createdBy", "CreatedBy":
return r.CreatedBy, nil
case "deletedAt", "DeletedAt":
return r.DeletedAt, nil
case "deletedBy", "DeletedBy":
return r.DeletedBy, nil
case "handle", "Handle":
return r.Handle, nil
case "id", "ID":
return r.ID, nil
case "name", "Name":
return r.Name, nil
case "nodeID", "NodeID":
return r.NodeID, nil
case "updatedAt", "UpdatedAt":
return r.UpdatedAt, nil
case "updatedBy", "UpdatedBy":
return r.UpdatedBy, nil
}
return nil, nil
}
func (r *ExposedModule) SetValue(name string, pos uint, value any) (err error) {
if r == nil {
r = &ExposedModule{}
}
switch name {
case "composeModuleID", "ComposeModuleID":
return cast2.Uint64(value, &r.ComposeModuleID)
case "composeNamespaceID", "ComposeNamespaceID":
return cast2.Uint64(value, &r.ComposeNamespaceID)
case "createdAt", "CreatedAt":
return cast2.Time(value, &r.CreatedAt)
case "createdBy", "CreatedBy":
return cast2.Uint64(value, &r.CreatedBy)
case "deletedAt", "DeletedAt":
return cast2.TimePtr(value, &r.DeletedAt)
case "deletedBy", "DeletedBy":
return cast2.Uint64(value, &r.DeletedBy)
case "handle", "Handle":
return cast2.String(value, &r.Handle)
case "id", "ID":
return cast2.Uint64(value, &r.ID)
case "name", "Name":
return cast2.String(value, &r.Name)
case "nodeID", "NodeID":
return cast2.Uint64(value, &r.NodeID)
case "updatedAt", "UpdatedAt":
return cast2.TimePtr(value, &r.UpdatedAt)
case "updatedBy", "UpdatedBy":
return cast2.Uint64(value, &r.UpdatedBy)
}
return nil
}
func (r SharedModule) GetID() uint64 { return r.ID }
func (r *SharedModule) GetValue(name string, pos uint) (any, error) {
if r == nil {
return nil, nil
}
switch name {
case "createdAt", "CreatedAt":
return r.CreatedAt, nil
case "createdBy", "CreatedBy":
return r.CreatedBy, nil
case "deletedAt", "DeletedAt":
return r.DeletedAt, nil
case "deletedBy", "DeletedBy":
return r.DeletedBy, nil
case "externalFederationModuleID", "ExternalFederationModuleID":
return r.ExternalFederationModuleID, nil
case "handle", "Handle":
return r.Handle, nil
case "id", "ID":
return r.ID, nil
case "name", "Name":
return r.Name, nil
case "nodeID", "NodeID":
return r.NodeID, nil
case "updatedAt", "UpdatedAt":
return r.UpdatedAt, nil
case "updatedBy", "UpdatedBy":
return r.UpdatedBy, nil
}
return nil, nil
}
func (r *SharedModule) SetValue(name string, pos uint, value any) (err error) {
if r == nil {
r = &SharedModule{}
}
switch name {
case "createdAt", "CreatedAt":
return cast2.Time(value, &r.CreatedAt)
case "createdBy", "CreatedBy":
return cast2.Uint64(value, &r.CreatedBy)
case "deletedAt", "DeletedAt":
return cast2.TimePtr(value, &r.DeletedAt)
case "deletedBy", "DeletedBy":
return cast2.Uint64(value, &r.DeletedBy)
case "externalFederationModuleID", "ExternalFederationModuleID":
return cast2.Uint64(value, &r.ExternalFederationModuleID)
case "handle", "Handle":
return cast2.String(value, &r.Handle)
case "id", "ID":
return cast2.Uint64(value, &r.ID)
case "name", "Name":
return cast2.String(value, &r.Name)
case "nodeID", "NodeID":
return cast2.Uint64(value, &r.NodeID)
case "updatedAt", "UpdatedAt":
return cast2.TimePtr(value, &r.UpdatedAt)
case "updatedBy", "UpdatedBy":
return cast2.Uint64(value, &r.UpdatedBy)
}
return nil
}
func (r ModuleMapping) GetID() uint64 {
// The resource does not define an ID field
return 0
}
func (r *ModuleMapping) GetValue(name string, pos uint) (any, error) {
if r == nil {
return nil, nil
}
switch name {
case "composeModuleID", "ComposeModuleID":
return r.ComposeModuleID, nil
case "composeNamespaceID", "ComposeNamespaceID":
return r.ComposeNamespaceID, nil
case "federationModuleID", "FederationModuleID":
return r.FederationModuleID, nil
case "nodeID", "NodeID":
return r.NodeID, nil
}
return nil, nil
}
func (r *ModuleMapping) SetValue(name string, pos uint, value any) (err error) {
if r == nil {
r = &ModuleMapping{}
}
switch name {
case "composeModuleID", "ComposeModuleID":
return cast2.Uint64(value, &r.ComposeModuleID)
case "composeNamespaceID", "ComposeNamespaceID":
return cast2.Uint64(value, &r.ComposeNamespaceID)
case "federationModuleID", "FederationModuleID":
return cast2.Uint64(value, &r.FederationModuleID)
case "nodeID", "NodeID":
return cast2.Uint64(value, &r.NodeID)
}
return nil
}

View File

@@ -0,0 +1,35 @@
package types
import (
"database/sql/driver"
"encoding/json"
"github.com/cortezaproject/corteza/server/pkg/sql"
"strings"
)
type (
ModuleFieldSet []*ModuleField
ModuleField struct {
Kind string `json:"kind"`
Name string `json:"name"`
Label string `json:"label"`
IsMulti bool `json:"isMulti"`
}
)
// HasField checks if the fieldset has a value by name
// keeping error field to better match the existing Filter()
// methods in generated sets
func (list ModuleFieldSet) HasField(name string) (bool, error) {
for _, f := range list {
if strings.ToLower(f.Name) == strings.ToLower(name) {
return true, nil
}
}
return false, nil
}
func (list *ModuleFieldSet) Scan(src any) error { return sql.ParseJSON(src, list) }
func (list ModuleFieldSet) Value() (driver.Value, error) { return json.Marshal(list) }

View File

@@ -0,0 +1,46 @@
package types
import (
"database/sql/driver"
"encoding/json"
"github.com/cortezaproject/corteza/server/pkg/sql"
)
const (
ModuleFieldMappingSetFindTypeOrigin ModuleFieldMappingSetFindType = iota
ModuleFieldMappingSetFindTypeDestination
)
type (
ModuleFieldMappingSet []*ModuleFieldMapping
ModuleFieldMapping struct {
Origin ModuleField `json:"origin"`
Destination ModuleField `json:"destination"`
}
ModuleFieldMappingSetFindType int
)
// Find looks up a origin or destination mapping
func (list *ModuleFieldMappingSet) FindByName(name string, findType ModuleFieldMappingSetFindType) (*ModuleFieldMapping, error) {
for _, mfm := range *list {
switch findType {
case ModuleFieldMappingSetFindTypeOrigin:
if mfm.Origin.Name == name {
return mfm, nil
}
case ModuleFieldMappingSetFindTypeDestination:
if mfm.Destination.Name == name {
return mfm, nil
}
}
}
return nil, nil
}
func (list *ModuleFieldMappingSet) Scan(src any) error { return sql.ParseJSON(src, list) }
func (list ModuleFieldMappingSet) Value() (driver.Value, error) { return json.Marshal(list) }

View File

@@ -0,0 +1,28 @@
package types
import (
"github.com/cortezaproject/corteza/server/pkg/filter"
)
type (
ModuleMapping struct {
NodeID uint64 `json:"nodeID,string"`
FederationModuleID uint64 `json:"federationModuleID,string"`
ComposeModuleID uint64 `json:"composeModuleID,string"`
ComposeNamespaceID uint64 `json:"composeNamespaceID,string"`
FieldMapping ModuleFieldMappingSet `json:"fields"`
}
ModuleMappingFilter struct {
NodeID uint64 `json:"nodeID"`
ComposeModuleID uint64 `json:"composeModuleID"`
ComposeNamespaceID uint64 `json:"composeNamespaceID"`
FederationModuleID uint64 `json:"federationModuleID"`
Query string `json:"query"`
Check func(*ModuleMapping) (bool, error) `json:"-"`
filter.Sorting
filter.Paging
}
)

View File

@@ -0,0 +1,57 @@
package types
import (
"time"
"github.com/cortezaproject/corteza/server/pkg/filter"
)
var (
NodeStatusPending = "pending"
NodeStatusPairRequested = "pair_requested"
NodeStatusPaired = "paired"
NodeStatusFailed = "failed"
)
type (
Node struct {
ID uint64 `json:"nodeID,string"`
Name string `json:"name"`
Status string `json:"status"`
// Base URL of the remote server
BaseURL string `json:"baseURL"`
Contact string `json:"contact"`
// Node ID on the remote server that points back to us
SharedNodeID uint64 `json:"sharedNodeID,string"`
PairToken string `json:"-"`
AuthToken string `json:"-"`
CreatedAt time.Time `json:"createdAt,omitempty"`
CreatedBy uint64 `json:"createdBy,string" `
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
UpdatedBy uint64 `json:"updatedBy,string,omitempty" `
DeletedAt *time.Time `json:"deletedAt,omitempty"`
DeletedBy uint64 `json:"deletedBy,string,omitempty" `
}
NodeFilter struct {
Query string `json:"name"`
Status string `json:"status"`
// Check fn is called by store backend for each resource found function can
// modify the resource and return false if store should not return it
//
// Store then loads additional resources to satisfy the paging parameters
Check func(node *Node) (bool, error) `json:"-"`
Deleted filter.State `json:"deleted"`
// Standard helpers for paging and sorting
filter.Sorting
filter.Paging
}
)

View File

@@ -0,0 +1,40 @@
package types
import (
"time"
"github.com/cortezaproject/corteza/server/pkg/filter"
)
var (
NodeSyncTypeStructure = "sync_structure"
NodeSyncTypeData = "sync_data"
NodeSyncStatusSuccess = "success"
NodeSyncStatusError = "error"
)
type (
NodeSync struct {
NodeID uint64 `json:"nodeID,string"`
ModuleID uint64 `json:"moduleID,string"`
SyncStatus string `json:"syncStatus"`
SyncType string `json:"syncType"`
TimeOfAction time.Time `json:"timeOfAction"`
}
NodeSyncFilter struct {
NodeID uint64 `json:"nodeID"`
RelNodeID uint64 `json:"relNodeID"`
ModuleID uint64 `json:"moduleID"`
SyncStatus string `json:"syncStatus"`
SyncType string `json:"syncType"`
Query string `json:"name"`
Check func(*NodeSync) (bool, error) `json:"-"`
filter.Sorting
filter.Paging
}
)

149
server/federation/types/rbac.gen.go generated Normal file
View File

@@ -0,0 +1,149 @@
package types
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"fmt"
"strconv"
)
type (
// Component struct serves as a virtual resource type for the federation component
//
// This struct is auto-generated
Component struct{}
)
var (
_ = fmt.Printf
_ = strconv.FormatUint
)
// RbacResource returns string representation of RBAC resource for Node by calling NodeRbacResource fn
//
// RBAC resource is in the corteza::federation:node/... format
//
// This function is auto-generated
func (r Node) RbacResource() string {
return NodeRbacResource(r.ID)
}
// NodeRbacResource returns string representation of RBAC resource for Node
//
// RBAC resource is in the corteza::federation:node/... format
//
// This function is auto-generated
func NodeRbacResource(id uint64) string {
cpts := []interface{}{NodeResourceType}
if id != 0 {
cpts = append(cpts, strconv.FormatUint(id, 10))
} else {
cpts = append(cpts, "*")
}
return fmt.Sprintf(NodeRbacResourceTpl(), cpts...)
}
func NodeRbacResourceTpl() string {
return "%s/%s"
}
// RbacResource returns string representation of RBAC resource for ExposedModule by calling ExposedModuleRbacResource fn
//
// RBAC resource is in the corteza::federation:exposed-module/... format
//
// This function is auto-generated
func (r ExposedModule) RbacResource() string {
return ExposedModuleRbacResource(r.NodeID, r.ID)
}
// ExposedModuleRbacResource returns string representation of RBAC resource for ExposedModule
//
// RBAC resource is in the corteza::federation:exposed-module/... format
//
// This function is auto-generated
func ExposedModuleRbacResource(nodeID uint64, id uint64) string {
cpts := []interface{}{ExposedModuleResourceType}
if nodeID != 0 {
cpts = append(cpts, strconv.FormatUint(nodeID, 10))
} else {
cpts = append(cpts, "*")
}
if id != 0 {
cpts = append(cpts, strconv.FormatUint(id, 10))
} else {
cpts = append(cpts, "*")
}
return fmt.Sprintf(ExposedModuleRbacResourceTpl(), cpts...)
}
func ExposedModuleRbacResourceTpl() string {
return "%s/%s/%s"
}
// RbacResource returns string representation of RBAC resource for SharedModule by calling SharedModuleRbacResource fn
//
// RBAC resource is in the corteza::federation:shared-module/... format
//
// This function is auto-generated
func (r SharedModule) RbacResource() string {
return SharedModuleRbacResource(r.NodeID, r.ID)
}
// SharedModuleRbacResource returns string representation of RBAC resource for SharedModule
//
// RBAC resource is in the corteza::federation:shared-module/... format
//
// This function is auto-generated
func SharedModuleRbacResource(nodeID uint64, id uint64) string {
cpts := []interface{}{SharedModuleResourceType}
if nodeID != 0 {
cpts = append(cpts, strconv.FormatUint(nodeID, 10))
} else {
cpts = append(cpts, "*")
}
if id != 0 {
cpts = append(cpts, strconv.FormatUint(id, 10))
} else {
cpts = append(cpts, "*")
}
return fmt.Sprintf(SharedModuleRbacResourceTpl(), cpts...)
}
func SharedModuleRbacResourceTpl() string {
return "%s/%s/%s"
}
// RbacResource returns string representation of RBAC resource for Component by calling ComponentRbacResource fn
//
// RBAC resource is in the corteza::federation/... format
//
// This function is auto-generated
func (r Component) RbacResource() string {
return ComponentRbacResource()
}
// ComponentRbacResource returns string representation of RBAC resource for Component
//
// RBAC resource is in the corteza::federation/ format
//
// This function is auto-generated
func ComponentRbacResource() string {
return ComponentResourceType + "/"
}
func ComponentRbacResourceTpl() string {
return "%s"
}

16
server/federation/types/resources.gen.go generated Normal file
View File

@@ -0,0 +1,16 @@
package types
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
const (
NodeResourceType = "corteza::federation:node"
NodeSyncResourceType = "corteza::federation:node-sync"
ExposedModuleResourceType = "corteza::federation:exposed-module"
SharedModuleResourceType = "corteza::federation:shared-module"
ModuleMappingResourceType = "corteza::federation:module-mapping"
ComponentResourceType = "corteza::federation"
)

View File

@@ -0,0 +1,39 @@
package types
import (
"time"
"github.com/cortezaproject/corteza/server/pkg/filter"
)
type (
SharedModule struct {
ID uint64 `json:"moduleID,string"`
NodeID uint64 `json:"nodeID,string"`
Handle string `json:"handle"`
Name string `json:"name"`
ExternalFederationModuleID uint64 `json:"externalFederationModuleID,string"`
Fields ModuleFieldSet `json:"fields"`
CreatedAt time.Time `json:"createdAt,omitempty"`
CreatedBy uint64 `json:"createdBy,string" `
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
UpdatedBy uint64 `json:"updatedBy,string,omitempty" `
DeletedAt *time.Time `json:"deletedAt,omitempty"`
DeletedBy uint64 `json:"deletedBy,string,omitempty" `
}
SharedModuleFilter struct {
NodeID uint64 `json:"nodeID,string"`
ExternalFederationModuleID uint64 `json:"externalFederationModuleID,string"`
Handle string `json:"handle"`
Name string `json:"name"`
Query string `json:"query"`
Check func(*SharedModule) (bool, error) `json:"-"`
filter.Sorting
filter.Paging
}
)

View File

@@ -0,0 +1,100 @@
package types
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
)
// SyncerURI serves as a default struct for handling url queues
// for fetcher
type (
SyncerURI struct {
Limit int
LastSync *time.Time
Path string
BaseURL string
NextPage string
LastPage string
}
)
// Does the transformation to string, does not
// url encode page cursor, since it needs to be base64
// encoded
func (s *SyncerURI) String() (string, error) {
var query []string
// using query instead of url.Values
// because pageCursor should not be url encoded
query = append(query, fmt.Sprintf("limit=%d", s.Limit))
if s.NextPage != "" {
query = append(query, fmt.Sprintf("pageCursor=%s", s.NextPage))
} else if s.LastPage != "" {
query = append(query, fmt.Sprintf("pageCursor=%s", s.LastPage))
}
if s.LastSync != nil {
query = append(query, fmt.Sprintf("lastSync=%d", s.LastSync.Unix()))
}
return fmt.Sprintf("%s%s?%s", s.BaseURL, s.Path, strings.Join(query[:], "&")), nil
}
// Parse the uri to the struct
func (s *SyncerURI) Parse(uri string) error {
u, err := url.Parse(uri)
if err != nil {
return err
}
var limit int
l := u.Query().Get("limit")
if l != "" {
if limit, err = strconv.Atoi(l); err != nil {
return err
}
}
s.Path = u.Path
s.Limit = limit
if u.Host != "" {
s.BaseURL = fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}
ls := u.Query().Get("lastSync")
if ls != "" {
parsed, err := parseLastSync(ls)
if err != nil {
return err
}
s.LastSync = parsed
}
return nil
}
func parseLastSync(lastSync string) (*time.Time, error) {
if i, err := strconv.ParseInt(lastSync, 10, 64); err == nil {
t := time.Unix(i, 0)
return &t, nil
}
// try different format if above fails
t, err := time.Parse(time.RFC3339, lastSync)
if err != nil {
return nil, err
}
return &t, nil
}

View File

@@ -0,0 +1,143 @@
package types
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestSyncerURIString(t *testing.T) {
var (
req = require.New(t)
)
now, _ := time.Parse("2006-01-02 15:04:05", "2020-10-23 11:11:11")
tests := []struct {
name string
url *SyncerURI
expect string
}{
{
name: "next page cursor",
url: &SyncerURI{BaseURL: "https://example.url", NextPage: "NEXT_PAGE_CURSOR=="},
expect: "https://example.url?limit=0&pageCursor=NEXT_PAGE_CURSOR==",
},
{
name: "last page cursor",
url: &SyncerURI{BaseURL: "https://example.url", LastPage: "LAST_PAGE_CURSOR=="},
expect: "https://example.url?limit=0&pageCursor=LAST_PAGE_CURSOR==",
},
{
name: "limit results",
url: &SyncerURI{BaseURL: "https://example.url", Limit: 666},
expect: "https://example.url?limit=666",
},
{
name: "base path",
url: &SyncerURI{Path: "/relative/path"},
expect: "/relative/path?limit=0",
},
{
name: "last sync",
url: &SyncerURI{Path: "/relative/path", LastSync: &now},
expect: fmt.Sprintf("/relative/path?limit=0&lastSync=%d", now.Unix()),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, _ := tt.url.String()
req.Equal(tt.expect, r)
})
}
}
func TestSyncerURIParse(t *testing.T) {
var (
req = require.New(t)
)
tests := []struct {
name string
url string
expect *SyncerURI
err string
}{
{
name: "parse limit",
url: "https://example.url?limit=11",
expect: &SyncerURI{BaseURL: "https://example.url", Limit: 11},
err: "",
},
{
name: "parse path",
url: "/path/to/endpoint/",
expect: &SyncerURI{Path: "/path/to/endpoint/"},
err: "",
},
{
name: "parse invalid url",
url: "ht tps:/ /in valid",
expect: &SyncerURI{},
err: `parse "ht tps:/ /in valid": first path segment in URL cannot contain colon`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &SyncerURI{}
err := s.Parse(tt.url)
errString := ""
if err != nil {
errString = err.Error()
}
req.Equal(tt.expect, s)
req.Equal(tt.err, errString)
})
}
}
func TestSyncerURIParseLastSync(t *testing.T) {
var (
req = require.New(t)
)
tests := []struct {
name string
url string
expect time.Time
}{
{
name: "parse timestamp",
url: "https://example.url?limit=11&lastSync=1603451471",
expect: dateToUTC("Fri Oct 23 11:11:11 2020"),
},
{
name: "parse RFC3339",
url: "https://example.url?limit=11&lastSync=2020-10-23T13:11:11Z",
expect: dateToUTC("Fri Oct 23 13:11:11 2020"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &SyncerURI{}
err := s.Parse(tt.url)
req.NoError(err)
req.Equal(tt.expect.Unix(), s.LastSync.UTC().Unix())
})
}
}
func dateToUTC(dt string) time.Time {
d, _ := time.Parse(time.ANSIC, dt)
return d.UTC()
}

265
server/federation/types/type_set.gen.go generated Normal file
View File

@@ -0,0 +1,265 @@
package types
// 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/types/types.yaml
type (
// ExposedModuleSet slice of ExposedModule
//
// This type is auto-generated.
ExposedModuleSet []*ExposedModule
// ModuleMappingSet slice of ModuleMapping
//
// This type is auto-generated.
ModuleMappingSet []*ModuleMapping
// NodeSet slice of Node
//
// This type is auto-generated.
NodeSet []*Node
// NodeSyncSet slice of NodeSync
//
// This type is auto-generated.
NodeSyncSet []*NodeSync
// SharedModuleSet slice of SharedModule
//
// This type is auto-generated.
SharedModuleSet []*SharedModule
)
// 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(ModuleMapping) err
//
// This function is auto-generated.
func (set ModuleMappingSet) Walk(w func(*ModuleMapping) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(ModuleMapping) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set ModuleMappingSet) Filter(f func(*ModuleMapping) (bool, error)) (out ModuleMappingSet, err error) {
var ok bool
out = ModuleMappingSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
// Walk iterates through every slice item and calls w(Node) err
//
// This function is auto-generated.
func (set NodeSet) Walk(w func(*Node) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(Node) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set NodeSet) Filter(f func(*Node) (bool, error)) (out NodeSet, err error) {
var ok bool
out = NodeSet{}
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 NodeSet) FindByID(ID uint64) *Node {
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 NodeSet) 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(NodeSync) err
//
// This function is auto-generated.
func (set NodeSyncSet) Walk(w func(*NodeSync) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(NodeSync) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set NodeSyncSet) Filter(f func(*NodeSync) (bool, error)) (out NodeSyncSet, err error) {
var ok bool
out = NodeSyncSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
// Walk iterates through every slice item and calls w(SharedModule) err
//
// This function is auto-generated.
func (set SharedModuleSet) Walk(w func(*SharedModule) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(SharedModule) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set SharedModuleSet) Filter(f func(*SharedModule) (bool, error)) (out SharedModuleSet, err error) {
var ok bool
out = SharedModuleSet{}
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 SharedModuleSet) FindByID(ID uint64) *SharedModule {
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 SharedModuleSet) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
IDs[i] = set[i].ID
}
return
}

View File

@@ -0,0 +1,397 @@
package types
// 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/types/types.yaml
import (
"fmt"
"github.com/stretchr/testify/require"
"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 TestModuleMappingSetWalk(t *testing.T) {
var (
value = make(ModuleMappingSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*ModuleMapping) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*ModuleMapping) error { return fmt.Errorf("walk error") }))
}
func TestModuleMappingSetFilter(t *testing.T) {
var (
value = make(ModuleMappingSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*ModuleMapping) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*ModuleMapping) (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(*ModuleMapping) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestNodeSetWalk(t *testing.T) {
var (
value = make(NodeSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*Node) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*Node) error { return fmt.Errorf("walk error") }))
}
func TestNodeSetFilter(t *testing.T) {
var (
value = make(NodeSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*Node) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*Node) (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(*Node) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestNodeSetIDs(t *testing.T) {
var (
value = make(NodeSet, 3)
req = require.New(t)
)
// construct objects
value[0] = new(Node)
value[1] = new(Node)
value[2] = new(Node)
// 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 TestNodeSyncSetWalk(t *testing.T) {
var (
value = make(NodeSyncSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*NodeSync) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*NodeSync) error { return fmt.Errorf("walk error") }))
}
func TestNodeSyncSetFilter(t *testing.T) {
var (
value = make(NodeSyncSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*NodeSync) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*NodeSync) (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(*NodeSync) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestSharedModuleSetWalk(t *testing.T) {
var (
value = make(SharedModuleSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*SharedModule) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*SharedModule) error { return fmt.Errorf("walk error") }))
}
func TestSharedModuleSetFilter(t *testing.T) {
var (
value = make(SharedModuleSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*SharedModule) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*SharedModule) (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(*SharedModule) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestSharedModuleSetIDs(t *testing.T) {
var (
value = make(SharedModuleSet, 3)
req = require.New(t)
)
// construct objects
value[0] = new(SharedModule)
value[1] = new(SharedModule)
value[2] = new(SharedModule)
// 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))
}
}

View File

@@ -0,0 +1,8 @@
types:
Node: {}
NodeSync:
noIdField: true
ExposedModule: {}
SharedModule: {}
ModuleMapping:
noIdField: true

View File

@@ -0,0 +1,71 @@
package util
import (
"fmt"
"net/url"
"strconv"
)
var (
ErrorURIMissingToken = fmt.Errorf("uri: token missing")
)
const ()
type (
NodeURIParams struct {
Name string
}
DecodedURI struct {
Domain string
Token string
NodeID uint64
Params NodeURIParams
}
)
func DecodeURI(i string) (du *DecodedURI, err error) {
du = &DecodedURI{
Params: NodeURIParams{},
}
u, err := url.Parse(i)
if err != nil {
return
}
du.Domain = u.Host
nodeS := u.User.Username()
if nodeS == "" {
return nil, ErrorURIMissingToken
}
nodeID, err := strconv.ParseUint(nodeS, 10, 64)
if err != nil {
return nil, err
}
du.NodeID = nodeID
token, has := u.User.Password()
if !has {
return nil, ErrorURIMissingToken
}
du.Token = token
params := u.Query()
if name, has := params["name"]; has && len(name) > 0 {
du.Params.Name = name[0]
}
return
}
func EncodeURI(ott, domain string, nodeID uint64) string {
u := url.URL{
Scheme: "corteza",
User: url.UserPassword(strconv.FormatUint(nodeID, 10), ott),
Host: domain,
}
return u.String()
}