globalstorage
Version:
Global Storage is a Global Distributed Data Warehouse
449 lines (422 loc) • 13.7 kB
JavaScript
;
const common = require('@metarhia/common');
const jstp = require('@metarhia/jstp');
const metasync = require('metasync');
const { GSError, codes: errorCodes } = require('./errors');
const {
checkPermission,
checkPermissionComplex,
checkExecutePermission,
filterCategories,
filterCategoriesWithPermissions,
filterActions,
filterApplications,
} = require('./permission');
const validate = {
string: value => typeof value === 'string',
object: value => typeof value === 'object' && !Array.isArray(value),
objectArray: value => Array.isArray(value) && value.every(validate.object),
stringArray: value => Array.isArray(value) && value.every(validate.string),
};
const createPermissionChecker = (provider, accessType, userId) => (
category,
options,
callback
) => {
if (!options) options = {};
if (options.accessType) {
accessType = options.accessType;
}
const categorySchema = provider.schema.categories.get(category);
const cb = (err, ok) => {
if (err) {
callback(new GSError(errorCodes.INTERNAL_PROVIDER_ERROR, err));
} else if (!ok) {
callback(new GSError(errorCodes.INSUFFICIENT_PERMISSIONS));
} else {
callback();
}
};
if (!categorySchema.catalog && !categorySchema.subsystem) {
checkPermission(provider, accessType, category, userId, cb);
} else {
checkPermissionComplex(provider, accessType, category, userId, options, cb);
}
};
const createEntityFilterer = (provider, userId, filterEntities) => (
entities,
callback
) => {
filterEntities(provider, entities, userId, (err, entities) => {
if (err) {
callback(new GSError(errorCodes.INTERNAL_PROVIDER_ERROR, err));
} else {
callback(null, entities);
}
});
};
const constructLogContext = (connection, userId) => ({
SystemUser: userId,
IP: connection.remoteAddress,
ProcessToken: common.generateGUID(),
ConnectionId: connection.session.id,
});
// Create JSTP API that can be passed to JSTP application
// gsProvider <StorageProvider> JSTP calls will be passed to this provider,
// it must be already in an `open` state
// cursorFactory <Function> factory to be used to create new cursors
// gsProvider <StorageProvider> provider instance to create the cursor from
// category <string> category name to be passed to the cursor
// jsql <Object[]> jsql to be passed to the cursor
// Returns: <Cursor> created cursor
// Returns: <Object> JSTP API
const createRemoteProviderJstpApi = (gsProvider, cursorFactory) => ({
provider: {
get(connection, id, callback) {
if (!validate.string(id)) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.enableLogging(constructLogContext(connection, userId)).get(
id,
(err, record) => {
callback(err && err.code, record);
},
createPermissionChecker(gsProvider, 'read', userId)
);
},
getDetails(connection, category, id, fieldName, callback) {
if (
!validate.string(category) ||
!validate.string(id) ||
!validate.string(fieldName)
) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider
.enableLogging(constructLogContext(connection, userId))
.getDetails(
category,
id,
fieldName,
(err, res) => {
callback(err && err.code, res);
},
createPermissionChecker(gsProvider, 'read', userId)
);
},
set(connection, record, callback) {
if (!validate.object(record)) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.enableLogging(constructLogContext(connection, userId)).set(
record,
err => {
callback(err && err.code);
},
createPermissionChecker(gsProvider, 'update', userId)
);
},
create(connection, category, record, callback) {
if (!validate.string(category) || !validate.object(record)) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.enableLogging(constructLogContext(connection, userId)).create(
category,
record,
(err, id) => {
callback(err && err.code, id && id.toString());
},
createPermissionChecker(gsProvider, 'insert', userId)
);
},
update(connection, category, query, patch, callback) {
if (
!validate.string(category) ||
!validate.object(query) ||
!validate.object(patch)
) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.enableLogging(constructLogContext(connection, userId)).update(
category,
query,
patch,
(err, count) => {
callback(err && err.code, count);
},
createPermissionChecker(gsProvider, 'update', userId)
);
},
linkDetails(connection, category, field, fromId, toIds, callback) {
if (
!validate.string(category) ||
!validate.string(field) ||
!validate.string(fromId) ||
!(validate.string(toIds) || validate.stringArray(toIds))
) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider
.enableLogging(constructLogContext(connection, userId))
.linkDetails(
category,
field,
fromId,
toIds,
err => {
callback(err && err.code);
},
createPermissionChecker(gsProvider, 'update', userId)
);
},
unlinkDetails(connection, category, field, fromId, toIds, callback) {
if (
!validate.string(category) ||
!validate.string(field) ||
!validate.string(fromId) ||
!(validate.string(toIds) || validate.stringArray(toIds))
) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider
.enableLogging(constructLogContext(connection, userId))
.unlinkDetails(
category,
field,
fromId,
toIds,
err => {
callback(err && err.code);
},
createPermissionChecker(gsProvider, 'update', userId)
);
},
delete(connection, category, query, callback) {
if (!validate.string(category) || !validate.object(query)) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.enableLogging(constructLogContext(connection, userId)).delete(
category,
query,
(err, count) => {
callback(err && err.code, count);
},
createPermissionChecker(gsProvider, 'delete', userId)
);
},
select(connection, category, jsql, callback) {
if (!validate.string(category) || !validate.objectArray(jsql)) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
const cursor = cursorFactory(gsProvider, category, jsql).enableLogging(
gsProvider,
constructLogContext(connection, userId),
[category, jsql]
);
cursor.fetch((err, records) => {
callback(err && err.code, records);
}, createPermissionChecker(gsProvider, 'read', userId));
},
execute(connection, category, action, args, callback) {
if (
(category !== null && !validate.string(category)) ||
!validate.string(action) ||
!validate.object(args)
) {
callback(jstp.ERR_INVALID_SIGNATURE);
return;
}
const { session } = connection;
const userId = session.get('userId');
gsProvider.enableLogging(constructLogContext(connection, userId)).execute(
category,
action,
[session, args],
(err, ...res) => {
callback(err && (err.code || err), ...res);
},
(category, action, callback) => {
checkExecutePermission(
gsProvider,
category,
action,
userId,
(err, ok) => {
if (err) {
callback(new GSError(errorCodes.INTERNAL_PROVIDER_ERROR, err));
} else if (!ok) {
callback(new GSError(errorCodes.INSUFFICIENT_PERMISSIONS));
} else {
callback();
}
}
);
}
);
},
getSchemaSources(connection, callback) {
const userId = connection.session.get('userId');
let ops;
if (!userId) {
ops = [
(ctx, callback) => {
gsProvider.listActions((err, actions) => {
ctx.actionLists = { public: actions.public, private: {} };
callback(err);
});
},
];
} else {
ops = [
(ctx, callback) => {
gsProvider.listActions((err, actions) => {
ctx.actionLists = actions;
callback(err);
}, createEntityFilterer(gsProvider, userId, filterActions));
},
(ctx, callback) => {
gsProvider.listCategories((err, categories) => {
ctx.categoryList = categories;
callback(err);
}, createEntityFilterer(gsProvider, userId, filterCategories));
},
(ctx, callback) => {
gsProvider.listApplications((err, applications) => {
ctx.appList = applications;
callback(err);
}, createEntityFilterer(gsProvider, userId, filterApplications));
},
];
}
metasync.parallel(ops, (err, options) => {
if (err) {
callback(err.code);
} else {
if (!options.categoryList) options.categoryList = [];
if (!options.appList) options.appList = [];
gsProvider.getSchemaSources(callback, options);
}
});
},
listCategories(connection, callback) {
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.listCategories((err, categories) => {
callback(err && err.code, categories);
}, createEntityFilterer(gsProvider, userId, filterCategories));
},
listCategoriesPermissions(connection, callback) {
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.listCategories((err, categories) => {
callback(err && err.code, categories);
}, createEntityFilterer(gsProvider, userId, filterCategoriesWithPermissions));
},
listActions(connection, callback) {
const userId = connection.session.get('userId');
if (!userId) {
gsProvider.listActions((err, actions) => {
if (err) {
callback(err.code);
return;
}
callback(null, { public: actions.public });
});
} else {
gsProvider.listActions((err, actions) => {
callback(err && err.code, actions);
}, createEntityFilterer(gsProvider, userId, filterActions));
}
},
listApplications(connection, callback) {
const userId = connection.session.get('userId');
if (!userId) {
callback(errorCodes.INSUFFICIENT_PERMISSIONS);
return;
}
gsProvider.listApplications((err, applications) => {
callback(err && err.code, applications);
}, createEntityFilterer(gsProvider, userId, filterApplications));
},
},
l10n: {
getCategory(connection, langTag, category, callback) {
gsProvider.getCategoryL10n(langTag, category, callback);
},
getDomains(connection, langTag, callback) {
gsProvider.getDomainsL10n(langTag, callback);
},
getCommon(connection, langTag, callback) {
gsProvider.getCommonL10n(langTag, callback);
},
getForm(connection, langTag, category, form, callback) {
gsProvider.getFormL10n(langTag, category, form, callback);
},
getAction(connection, langTag, category, action, callback) {
gsProvider.getActionL10n(langTag, category, action, callback);
},
},
});
module.exports = {
createRemoteProviderJstpApi,
};