mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-14 00:02:57 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
'use strict';
|
||||
|
||||
const Model = require('../../../model');
|
||||
const CloudStore = require('../../../api/cloudstore');
|
||||
|
||||
function _addBaseInfo(def, opts) {
|
||||
const _includeInputProperties = opts === undefined || opts.includeInputProperties;
|
||||
const _includeRelations = opts !== undefined && opts.includeRelations;
|
||||
|
||||
Object.assign(def.node, {
|
||||
category: 'Data',
|
||||
color: 'data',
|
||||
inputs: def.node.inputs || {},
|
||||
outputs: def.node.outputs || {},
|
||||
methods: def.node.methods || {}
|
||||
});
|
||||
|
||||
// Outputs
|
||||
Object.assign(def.node.outputs, {
|
||||
failure: {
|
||||
type: 'signal',
|
||||
displayName: 'Failure',
|
||||
group: 'Events'
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
displayName: 'Error',
|
||||
group: 'Error',
|
||||
getter: function () {
|
||||
return this._internal.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Methods
|
||||
Object.assign(def.node.methods, {
|
||||
scheduleOnce: function (type, cb) {
|
||||
const _this = this;
|
||||
const _type = 'hasScheduled' + type;
|
||||
if (this._internal[_type]) return;
|
||||
this._internal[_type] = true;
|
||||
this.scheduleAfterInputsHaveUpdated(function () {
|
||||
_this._internal[_type] = false;
|
||||
cb();
|
||||
});
|
||||
},
|
||||
checkWarningsBeforeCloudOp() {
|
||||
//clear all errors first
|
||||
this.clearWarnings();
|
||||
|
||||
if (!this._internal.collectionId) {
|
||||
this.setError('No class name specified');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
setError: function (err) {
|
||||
this._internal.error = err;
|
||||
this.flagOutputDirty('error');
|
||||
this.sendSignalOnOutput('failure');
|
||||
|
||||
if (this.context.editorConnection) {
|
||||
this.context.editorConnection.sendWarning(this.nodeScope.componentOwner.name, this.id, 'storage-op-warning', {
|
||||
message: err,
|
||||
showGlobally: true
|
||||
});
|
||||
}
|
||||
},
|
||||
clearWarnings() {
|
||||
if (this.context.editorConnection) {
|
||||
this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'storage-op-warning');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Setup
|
||||
Object.assign(def, {
|
||||
setup: function (context, graphModel) {
|
||||
if (!context.editorConnection || !context.editorConnection.isRunningLocally()) {
|
||||
return;
|
||||
}
|
||||
|
||||
function _managePortsForNode(node) {
|
||||
function _updatePorts() {
|
||||
var ports = [];
|
||||
|
||||
const dbCollections = graphModel.getMetaData('dbCollections');
|
||||
const systemCollections = graphModel.getMetaData('systemCollections');
|
||||
|
||||
const _systemClasses = [
|
||||
{ label: 'User', value: '_User' },
|
||||
{ label: 'Role', value: '_Role' }
|
||||
];
|
||||
|
||||
const parameters = node.parameters;
|
||||
|
||||
ports.push({
|
||||
name: 'collectionName',
|
||||
displayName: 'Class',
|
||||
group: 'General',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: _systemClasses.concat(
|
||||
dbCollections !== undefined
|
||||
? dbCollections.map((c) => {
|
||||
return { value: c.name, label: c.name };
|
||||
})
|
||||
: []
|
||||
),
|
||||
allowEditOnly: true
|
||||
},
|
||||
plug: 'input'
|
||||
});
|
||||
|
||||
if (_includeRelations && parameters.collectionName && dbCollections) {
|
||||
// Fetch ports from collection keys
|
||||
var c = dbCollections.find((c) => c.name === parameters.collectionName);
|
||||
if (c === undefined && systemCollections)
|
||||
c = systemCollections.find((c) => c.name === parameters.collectionName);
|
||||
if (c && c.schema && c.schema.properties) {
|
||||
const props = c.schema.properties;
|
||||
const enums = Object.keys(props)
|
||||
.filter((key) => props[key].type === 'Relation')
|
||||
.map((key) => ({ label: key, value: key }));
|
||||
|
||||
ports.push({
|
||||
name: 'relationProperty',
|
||||
displayName: 'Relation',
|
||||
group: 'General',
|
||||
type: { name: 'enum', enums: enums, allowEditOnly: true },
|
||||
plug: 'input'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_includeInputProperties && parameters.collectionName && dbCollections) {
|
||||
const _typeMap = {
|
||||
String: 'string',
|
||||
Boolean: 'boolean',
|
||||
Number: 'number',
|
||||
Date: 'date'
|
||||
};
|
||||
|
||||
// Fetch ports from collection keys
|
||||
var c = dbCollections.find((c) => c.name === parameters.collectionName);
|
||||
if (c === undefined && systemCollections)
|
||||
c = systemCollections.find((c) => c.name === parameters.collectionName);
|
||||
if (c && c.schema && c.schema.properties) {
|
||||
var props = c.schema.properties;
|
||||
for (var key in props) {
|
||||
var p = props[key];
|
||||
if (ports.find((_p) => _p.name === key)) continue;
|
||||
|
||||
ports.push({
|
||||
type: {
|
||||
name: _typeMap[p.type] ? _typeMap[p.type] : '*'
|
||||
},
|
||||
plug: 'input',
|
||||
group: 'Properties',
|
||||
name: 'prop-' + key,
|
||||
displayName: key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def._additionalDynamicPorts && def._additionalDynamicPorts(node, ports, graphModel);
|
||||
|
||||
context.editorConnection.sendDynamicPorts(node.id, ports);
|
||||
}
|
||||
|
||||
_updatePorts();
|
||||
|
||||
node.on('parameterUpdated', function (event) {
|
||||
_updatePorts();
|
||||
});
|
||||
|
||||
graphModel.on('metadataChanged.dbCollections', function (data) {
|
||||
CloudStore.invalidateCollections();
|
||||
_updatePorts();
|
||||
});
|
||||
|
||||
graphModel.on('metadataChanged.systemCollections', function (data) {
|
||||
CloudStore.invalidateCollections();
|
||||
_updatePorts();
|
||||
});
|
||||
}
|
||||
|
||||
graphModel.on('editorImportComplete', () => {
|
||||
graphModel.on('nodeAdded.' + def.node.name, function (node) {
|
||||
_managePortsForNode(node);
|
||||
});
|
||||
|
||||
for (const node of graphModel.getNodesWithType(def.node.name)) {
|
||||
_managePortsForNode(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _addModelId(def, opts) {
|
||||
var _def = { node: Object.assign({}, def.node), setup: def.setup };
|
||||
var _methods = Object.assign({}, def.node.methods);
|
||||
|
||||
const _includeInputs = opts === undefined || opts.includeInputs;
|
||||
const _includeOutputs = opts === undefined || opts.includeOutputs;
|
||||
|
||||
Object.assign(def.node, {
|
||||
inputs: def.node.inputs || {},
|
||||
outputs: def.node.outputs || {},
|
||||
methods: def.node.methods || {}
|
||||
});
|
||||
|
||||
if (_includeInputs) {
|
||||
Object.assign(def.node, {
|
||||
usePortAsLabel: 'collectionName'
|
||||
});
|
||||
|
||||
def.node.dynamicports = (def.node.dynamicports || []).concat([
|
||||
{
|
||||
name: 'conditionalports/extended',
|
||||
condition: 'idSource = explicit OR idSource NOT SET',
|
||||
inputs: ['modelId']
|
||||
}
|
||||
]);
|
||||
|
||||
// Inputs
|
||||
Object.assign(def.node.inputs, {
|
||||
idSource: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ label: 'Specify explicitly', value: 'explicit' },
|
||||
{ label: 'From repeater', value: 'foreach' }
|
||||
],
|
||||
allowEditOnly: true
|
||||
},
|
||||
default: 'explicit',
|
||||
displayName: 'Id Source',
|
||||
group: 'General',
|
||||
tooltip:
|
||||
'Choose if you want to specify the Id explicitly, \n or if you want it to be that of the current record in a repeater.',
|
||||
set: function (value) {
|
||||
if (value === 'foreach') {
|
||||
this.scheduleAfterInputsHaveUpdated(() => {
|
||||
// Find closest nodescope that have a _forEachModel
|
||||
var component = this.nodeScope.componentOwner;
|
||||
while (component !== undefined && component._forEachModel === undefined && component.parentNodeScope) {
|
||||
component = component.parentNodeScope.componentOwner;
|
||||
}
|
||||
this.setModel(component !== undefined ? component._forEachModel : undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
modelId: {
|
||||
type: {
|
||||
name: 'string',
|
||||
identifierOf: 'ModelName',
|
||||
identifierDisplayName: 'Object Ids'
|
||||
},
|
||||
displayName: 'Id',
|
||||
group: 'General',
|
||||
set: function (value) {
|
||||
if (value instanceof Model) value = value.getId(); // Can be passed as model as well
|
||||
this._internal.modelId = value; // Wait to fetch data
|
||||
this.setModelID(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Outputs
|
||||
if (_includeOutputs) {
|
||||
Object.assign(def.node.outputs, {
|
||||
id: {
|
||||
type: 'string',
|
||||
displayName: 'Id',
|
||||
group: 'General',
|
||||
getter: function () {
|
||||
return this._internal.model ? this._internal.model.getId() : this._internal.modelId;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Methods
|
||||
Object.assign(def.node.methods, {
|
||||
setCollectionID: function (id) {
|
||||
this._internal.collectionId = id;
|
||||
this.clearWarnings();
|
||||
},
|
||||
setModelID: function (id) {
|
||||
var model = (this.nodeScope.modelScope || Model).get(id);
|
||||
this.setModel(model);
|
||||
},
|
||||
setModel: function (model) {
|
||||
this._internal.model = model;
|
||||
this.flagOutputDirty('id');
|
||||
},
|
||||
registerInputIfNeeded: function (name) {
|
||||
if (this.hasInput(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'collectionName')
|
||||
this.registerInput(name, {
|
||||
set: this.setCollectionID.bind(this)
|
||||
});
|
||||
|
||||
_methods && _methods.registerInputIfNeeded && _methods.registerInputIfNeeded.call(this, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _addInputProperties(def) {
|
||||
var _def = { node: Object.assign({}, def.node), setup: def.setup };
|
||||
var _methods = Object.assign({}, def.node.methods);
|
||||
|
||||
Object.assign(def.node, {
|
||||
inputs: def.node.inputs || {},
|
||||
outputs: def.node.outputs || {},
|
||||
methods: def.node.methods || {}
|
||||
});
|
||||
|
||||
Object.assign(def.node, {
|
||||
initialize: function () {
|
||||
var internal = this._internal;
|
||||
internal.inputValues = {};
|
||||
|
||||
_def.node.initialize && _def.node.initialize.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
// Outputs
|
||||
Object.assign(def.node.outputs, {});
|
||||
|
||||
// Inputs
|
||||
Object.assign(def.node.inputs, {});
|
||||
|
||||
// Methods
|
||||
Object.assign(def.node.methods, {
|
||||
registerInputIfNeeded: function (name) {
|
||||
if (this.hasInput(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.startsWith('prop-'))
|
||||
this.registerInput(name, {
|
||||
set: this._setInputValue.bind(this, name.substring('prop-'.length))
|
||||
});
|
||||
|
||||
_methods && _methods.registerInputIfNeeded && _methods.registerInputIfNeeded.call(this, name);
|
||||
},
|
||||
_setInputValue: function (name, value) {
|
||||
this._internal.inputValues[name] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _addRelationProperty(def) {
|
||||
var _def = { node: Object.assign({}, def.node), setup: def.setup };
|
||||
var _methods = Object.assign({}, def.node.methods);
|
||||
|
||||
Object.assign(def.node, {
|
||||
inputs: def.node.inputs || {},
|
||||
outputs: def.node.outputs || {},
|
||||
methods: def.node.methods || {}
|
||||
});
|
||||
|
||||
// Inputs
|
||||
Object.assign(def.node.inputs, {
|
||||
targetId: {
|
||||
type: { name: 'string', allowConnectionsOnly: true },
|
||||
displayName: 'Target Record Id',
|
||||
group: 'General',
|
||||
set: function (value) {
|
||||
this._internal.targetModelId = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Methods
|
||||
Object.assign(def.node.methods, {
|
||||
registerInputIfNeeded: function (name) {
|
||||
if (this.hasInput(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'relationProperty')
|
||||
this.registerInput(name, {
|
||||
set: this.setRelationProperty.bind(this)
|
||||
});
|
||||
|
||||
_methods && _methods.registerInputIfNeeded && _methods.registerInputIfNeeded.call(this, name);
|
||||
},
|
||||
setRelationProperty: function (value) {
|
||||
this._internal.relationProperty = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _getCurrentUser(modelScope) {
|
||||
if (typeof _noodl_cloud_runtime_version === 'undefined') {
|
||||
// We are running in browser, try to find the current user
|
||||
|
||||
var _cu = localStorage['Parse/' + CloudStore.instance.appId + '/currentUser'];
|
||||
if (_cu !== undefined) {
|
||||
let cu;
|
||||
try {
|
||||
cu = JSON.parse(_cu);
|
||||
} catch (e) {}
|
||||
|
||||
return cu !== undefined ? cu.objectId : undefined;
|
||||
}
|
||||
} else {
|
||||
// Assume we are running in cloud runtime
|
||||
const request = modelScope.get('Request');
|
||||
return request.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
function _addAccessControl(def) {
|
||||
var _def = { node: Object.assign({}, def.node), setup: def.setup };
|
||||
var _methods = Object.assign({}, def.node.methods);
|
||||
|
||||
Object.assign(def.node, {
|
||||
inputs: def.node.inputs || {},
|
||||
outputs: def.node.outputs || {},
|
||||
methods: def.node.methods || {}
|
||||
});
|
||||
|
||||
Object.assign(def.node, {
|
||||
initialize: function () {
|
||||
var internal = this._internal;
|
||||
internal.accessControl = {};
|
||||
|
||||
_def.node.initialize && _def.node.initialize.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
// Inputs
|
||||
Object.assign(def.node.inputs, {
|
||||
accessControl: {
|
||||
type: { name: 'proplist', autoName: 'Rule', allowEditOnly: true },
|
||||
index: 1000,
|
||||
displayName: 'Access Control Rules',
|
||||
group: 'Access Control Rules',
|
||||
set: function (value) {
|
||||
this._internal.accessControlRules = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic ports
|
||||
const _super = def._additionalDynamicPorts;
|
||||
def._additionalDynamicPorts = function (node, ports, graphModel) {
|
||||
if (node.parameters['accessControl'] !== undefined && node.parameters['accessControl'].length > 0) {
|
||||
node.parameters['accessControl'].forEach((ac) => {
|
||||
const prefix = 'acl-' + ac.id;
|
||||
// User or role?
|
||||
ports.push({
|
||||
name: prefix + '-target',
|
||||
displayName: 'Target',
|
||||
editorName: ac.label + ' | Target',
|
||||
plug: 'input',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ value: 'user', label: 'User' },
|
||||
{ value: 'everyone', label: 'Everyone' },
|
||||
{ value: 'role', label: 'Role' }
|
||||
],
|
||||
allowEditOnly: true
|
||||
},
|
||||
group: ac.label + ' Access Rule',
|
||||
default: 'user',
|
||||
parent: 'accessControl',
|
||||
parentItemId: ac.id
|
||||
});
|
||||
|
||||
if (node.parameters[prefix + '-target'] === 'role') {
|
||||
ports.push({
|
||||
name: prefix + '-role',
|
||||
displayName: 'Role',
|
||||
editorName: ac.label + ' | Role',
|
||||
group: ac.label + ' Access Rule',
|
||||
plug: 'input',
|
||||
type: 'string',
|
||||
parent: 'accessControl',
|
||||
parentItemId: ac.id
|
||||
});
|
||||
} else if (
|
||||
node.parameters[prefix + '-target'] === undefined ||
|
||||
node.parameters[prefix + '-target'] === 'user'
|
||||
) {
|
||||
ports.push({
|
||||
name: prefix + '-userid',
|
||||
displayName: 'User Id',
|
||||
group: ac.label + ' Access Rule',
|
||||
editorName: ac.label + ' | User Id',
|
||||
plug: 'input',
|
||||
type: { name: 'string', allowConnectionsOnly: true },
|
||||
parent: 'accessControl',
|
||||
parentItemId: ac.id
|
||||
});
|
||||
}
|
||||
|
||||
// Read
|
||||
ports.push({
|
||||
name: prefix + '-read',
|
||||
displayName: 'Read',
|
||||
editorName: ac.label + ' | Read',
|
||||
group: ac.label + ' Access Rule',
|
||||
plug: 'input',
|
||||
type: { name: 'boolean' },
|
||||
default: true,
|
||||
parent: 'accessControl',
|
||||
parentItemId: ac.id
|
||||
});
|
||||
|
||||
// Write
|
||||
ports.push({
|
||||
name: prefix + '-write',
|
||||
displayName: 'Write',
|
||||
editorName: ac.label + ' | Write',
|
||||
group: ac.label + ' Access Rule',
|
||||
plug: 'input',
|
||||
type: { name: 'boolean' },
|
||||
default: true,
|
||||
parent: 'accessControl',
|
||||
parentItemId: ac.id
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_super && _super(node, ports, graphModel);
|
||||
};
|
||||
|
||||
// Methods
|
||||
Object.assign(def.node.methods, {
|
||||
registerInputIfNeeded: function (name) {
|
||||
if (this.hasInput(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.startsWith('acl-'))
|
||||
this.registerInput(name, {
|
||||
set: this.setAccessControl.bind(this, name)
|
||||
});
|
||||
|
||||
_methods && _methods.registerInputIfNeeded && _methods.registerInputIfNeeded.call(this, name);
|
||||
},
|
||||
_getACL: function () {
|
||||
let acl = {};
|
||||
|
||||
function _rule(rule) {
|
||||
return {
|
||||
read: rule.read === undefined ? true : rule.read,
|
||||
write: rule.write === undefined ? true : rule.write
|
||||
};
|
||||
}
|
||||
|
||||
const currentUserId = _getCurrentUser(this.nodeScope.modelScope);
|
||||
|
||||
if (this._internal.accessControlRules !== undefined) {
|
||||
this._internal.accessControlRules.forEach((r) => {
|
||||
const rule = this._internal.accessControl[r.id];
|
||||
|
||||
if (rule === undefined) {
|
||||
const userId = currentUserId;
|
||||
if (userId !== undefined) acl[userId] = { write: true, read: true };
|
||||
} else if (rule.target === 'everyone') {
|
||||
acl['*'] = _rule(rule);
|
||||
} else if (rule.target === 'user') {
|
||||
const userId = rule.userid || currentUserId;
|
||||
acl[userId] = _rule(rule);
|
||||
} else if (rule.target === 'role') {
|
||||
acl['role:' + rule.role] = _rule(rule);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(acl).length > 0 ? acl : undefined;
|
||||
},
|
||||
setAccessControl: function (name, value) {
|
||||
const _parts = name.split('-');
|
||||
|
||||
if (this._internal.accessControl[_parts[1]] === undefined) this._internal.accessControl[_parts[1]] = {};
|
||||
this._internal.accessControl[_parts[1]][_parts[2]] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addInputProperties: _addInputProperties,
|
||||
addModelId: _addModelId,
|
||||
addBaseInfo: _addBaseInfo,
|
||||
addRelationProperty: _addRelationProperty,
|
||||
addAccessControl: _addAccessControl
|
||||
};
|
||||
Reference in New Issue
Block a user