@leansdk/leanrc
Version:
LeanRC is a MVC framework for creating graceful applications
573 lines (510 loc) • 17.2 kB
JavaScript
(function() {
// This file is part of LeanRC.
// LeanRC is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// LeanRC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
// You should have received a copy of the GNU Lesser General Public License
// along with LeanRC. If not, see <https://www.gnu.org/licenses/>.
// semver = require 'semver'
var slice = [].slice;
module.exports = function(Module) {
var APPLICATION_MEDIATOR, AnyT, ChainsMixin, CollectionInterface, ConfigurableMixin, ContextInterface, DELAYED_JOBS_QUEUE, DictG, EnumG, FORBIDDEN, FuncG, HANDLER_RESULT, HTTP_NOT_FOUND, ListG, MIGRATIONS, MaybeG, NotificationInterface, RESQUE, Resource, ResourceInterface, SimpleCommand, StructG, TupleG, UNAUTHORIZED, UPGRADE_REQUIRED, UnionG, _, assign, inflect, isArangoDB, statuses;
({
APPLICATION_MEDIATOR,
HANDLER_RESULT,
DELAYED_JOBS_QUEUE,
RESQUE,
MIGRATIONS,
AnyT,
FuncG,
UnionG,
TupleG,
MaybeG,
DictG,
StructG,
EnumG,
ListG,
ResourceInterface,
CollectionInterface,
ContextInterface,
NotificationInterface,
ConfigurableMixin,
ChainsMixin,
SimpleCommand,
Utils: {_, inflect, assign, statuses, isArangoDB}
} = Module.prototype);
HTTP_NOT_FOUND = statuses('not found');
UNAUTHORIZED = statuses('unauthorized');
FORBIDDEN = statuses('forbidden');
UPGRADE_REQUIRED = statuses('upgrade required');
return Resource = (function() {
class Resource extends SimpleCommand {};
Resource.inheritProtected();
Resource.include(ConfigurableMixin);
Resource.include(ChainsMixin);
Resource.implements(ResourceInterface);
Resource.module(Module);
Resource.public({
entityName: String
}, {
get: function*() {
throw new Error('Not implemented specific property');
}
});
// example of usage
/*
@initialHook 'checkApiVersion'
@initialHook 'checkSession'
@initialHook 'adminOnly', only: ['create'] # required checkSession before
@initialHook 'checkOwner', only: ['detail', 'update', 'delete'] # required checkSession before
@beforeHook 'filterOwnerByCurrentUser', only: ['list']
@beforeHook 'setOwnerId', only: ['create']
@beforeHook 'protectOwnerId', only: ['update']
*/
Resource.public({
needsLimitation: Boolean
}, {
default: true
});
Resource.public(Resource.async({
checkApiVersion: Function
}, {
default: function*(...args) {
var ref, semver, vCurrentVersion, vNeedVersion, vVersion;
vVersion = this.context.pathParams.v;
vCurrentVersion = this.configs.version;
if (vCurrentVersion == null) {
throw new Error('No `version` specified in the configuration');
}
[vNeedVersion] = (ref = vCurrentVersion.match(/^\d{1,}[.]\d{1,}/)) != null ? ref : [];
if (vNeedVersion == null) {
throw new Error('Incorrect `version` specified in the configuration');
}
semver = require('semver');
if (!semver.satisfies(vCurrentVersion, vVersion)) {
this.context.throw(UPGRADE_REQUIRED, `Upgrade: v${vCurrentVersion}`);
}
return args;
}
}));
Resource.public(Resource.async({
setOwnerId: Function
}, {
default: function*(...args) {
var ref;
this.recordBody.ownerId = (ref = this.session.uid) != null ? ref : null;
return args;
}
}));
Resource.public(Resource.async({
protectOwnerId: Function
}, {
default: function*(...args) {
this.recordBody = _.omit(this.recordBody, ['ownerId']);
return args;
}
}));
Resource.public(Resource.async({
filterOwnerByCurrentUser: Function
}, {
default: function*(...args) {
if (!this.session.userIsAdmin) {
if (this.listQuery == null) {
this.listQuery = {};
}
}
if (this.listQuery.$filter != null) {
this.listQuery.$filter = {
$and: [
this.listQuery.$filter,
{
'@doc.ownerId': {
$eq: this.session.uid
}
}
]
};
} else {
this.listQuery.$filter = {
'@doc.ownerId': {
$eq: this.session.uid
}
};
}
return args;
}
}));
Resource.public(Resource.async({
checkOwner: Function
}, {
default: function*(...args) {
var doc, key;
if (this.session.uid == null) {
this.context.throw(UNAUTHORIZED);
return;
}
if (this.session.userIsAdmin) {
return args;
}
if ((key = this.context.pathParams[this.keyName]) == null) {
return args;
}
doc = (yield this.collection.find(key));
if (doc == null) {
this.context.throw(HTTP_NOT_FOUND);
}
if (!doc.ownerId) {
return args;
}
if (this.session.uid !== doc.ownerId) {
this.context.throw(FORBIDDEN);
return;
}
return args;
}
}));
Resource.public(Resource.async({
checkExistence: Function
}, {
default: function*(...args) {
if (this.recordId == null) {
this.context.throw(HTTP_NOT_FOUND);
}
if (!(yield this.collection.includes(this.recordId))) {
this.context.throw(HTTP_NOT_FOUND);
}
return args;
}
}));
Resource.public(Resource.async({
adminOnly: Function
}, {
default: function*(...args) {
if (this.session.uid == null) {
this.context.throw(UNAUTHORIZED);
return;
}
if (!this.session.userIsAdmin) {
this.context.throw(FORBIDDEN);
return;
}
return args;
}
}));
Resource.public(Resource.async({
checkSchemaVersion: Function
}, {
default: function*(...args) {
var includes, lastMigration, ref, voMigrations;
voMigrations = this.facade.retrieveProxy(MIGRATIONS);
ref = this.Module.prototype.MIGRATION_NAMES, [lastMigration] = slice.call(ref, -1);
if (lastMigration == null) {
return args;
}
includes = (yield voMigrations.includes(lastMigration));
if (includes) {
return args;
} else {
throw new Error('Code schema version is not equal current DB version');
return;
}
return args;
}
}));
Resource.public({
keyName: String
}, {
get: function() {
return inflect.singularize(inflect.underscore(this.entityName));
}
});
Resource.public({
itemEntityName: String
}, {
get: function() {
return inflect.singularize(inflect.underscore(this.entityName));
}
});
Resource.public({
listEntityName: String
}, {
get: function() {
return inflect.pluralize(inflect.underscore(this.entityName));
}
});
Resource.public({
collectionName: String
}, {
get: function() {
return `${inflect.pluralize(inflect.camelize(this.entityName))}Collection`;
}
});
Resource.public({
collection: CollectionInterface
}, {
get: function() {
return this.facade.retrieveProxy(this.collectionName);
}
});
Resource.public({
context: MaybeG(ContextInterface)
});
Resource.public({
listQuery: MaybeG(Object)
});
Resource.public({
recordId: MaybeG(String)
});
Resource.public({
recordBody: MaybeG(Object)
});
Resource.public({
actionResult: MaybeG(AnyT)
});
Resource.public(Resource.static({
actions: DictG(String, Object)
}, {
get: function() {
return this.metaObject.getGroup('actions', false);
}
}));
Resource.public(Resource.static({
action: FuncG([UnionG(Object, TupleG(Object, Object))])
}, {
default: function(...args) {
var name;
// default: (nameDefinition, config)->
// [actionName] = Object.keys nameDefinition
// if nameDefinition.attr? and not config?
// @metaObject.addMetaData 'actions', nameDefinition.attr, nameDefinition
// else
// @metaObject.addMetaData 'actions', actionName, config
name = this.public(...args);
this.metaObject.addMetaData('actions', name, this.instanceMethods[name]);
}
}));
Resource.action(Resource.async({
list: FuncG([], StructG({
meta: StructG({
pagination: StructG({
limit: UnionG(Number, EnumG(['not defined'])),
offset: UnionG(Number, EnumG(['not defined']))
})
}),
items: ListG(Object)
}))
}, {
default: function*() {
var vlItems;
vlItems = (yield ((yield this.collection.takeAll())).toArray());
return {
meta: {
pagination: {
limit: 'not defined',
offset: 'not defined'
}
},
items: vlItems
};
}
}));
Resource.action(Resource.async({
detail: FuncG([], Object)
}, {
default: function*() {
return (yield this.collection.find(this.recordId));
}
}));
Resource.action(Resource.async({
create: FuncG([], Object)
}, {
default: function*() {
return (yield this.collection.create(this.recordBody));
}
}));
Resource.action(Resource.async({
update: FuncG([], Object)
}, {
default: function*() {
return (yield this.collection.update(this.recordId, this.recordBody));
}
}));
Resource.action(Resource.async({
delete: Function
}, {
default: function*() {
yield this.collection.delete(this.recordId);
this.context.status = 204;
}
}));
Resource.action(Resource.async({
destroy: Function
}, {
default: function*() {
yield this.collection.destroy(this.recordId);
this.context.status = 204;
}
}));
// ------------ Chains definitions ---------
Resource.chains(['list', 'detail', 'create', 'update', 'delete', 'destroy']);
Resource.initialHook('beforeActionHook');
Resource.beforeHook('getQuery', {
only: ['list']
});
Resource.beforeHook('getRecordId', {
only: ['detail', 'update', 'delete', 'destroy']
});
Resource.beforeHook('checkExistence', {
only: ['detail', 'update', 'delete', 'destroy']
});
Resource.beforeHook('getRecordBody', {
only: ['create', 'update']
});
Resource.beforeHook('omitBody', {
only: ['create', 'update']
});
Resource.beforeHook('beforeUpdate', {
only: ['update']
});
Resource.public({
beforeActionHook: Function
}, {
default: function(...args) {
[this.context] = args;
return args;
}
});
Resource.public({
getQuery: Function
}, {
default: function(...args) {
var ref;
this.listQuery = JSON.parse((ref = this.context.query['query']) != null ? ref : "{}");
return args;
}
});
Resource.public({
getRecordId: Function
}, {
default: function(...args) {
this.recordId = this.context.pathParams[this.keyName];
return args;
}
});
Resource.public({
getRecordBody: Function
}, {
default: function(...args) {
var ref;
this.recordBody = (ref = this.context.request.body) != null ? ref[this.itemEntityName] : void 0;
return args;
}
});
Resource.public({
omitBody: Function
}, {
default: function(...args) {
var moduleName, name;
this.recordBody = _.omit(this.recordBody, ['_id', '_rev', 'rev', 'type', '_type', '_owner', '_space', '_from', '_to']);
moduleName = this.collection.delegate.moduleName();
name = this.collection.delegate.name;
this.recordBody.type = `${moduleName}::${name}`;
return args;
}
});
Resource.public({
beforeUpdate: Function
}, {
default: function(...args) {
this.recordBody = assign({}, this.recordBody, {
id: this.recordId
});
return args;
}
});
Resource.public(Resource.async({
doAction: FuncG([String, ContextInterface], MaybeG(AnyT))
}, {
default: function*(action, context) {
var voResult;
voResult = (yield (typeof this[action] === "function" ? this[action](context) : void 0));
this.actionResult = voResult;
yield this.saveDelayeds();
return voResult;
}
}));
Resource.public(Resource.async({
writeTransaction: FuncG([String, ContextInterface], Boolean)
}, {
default: function*(asAction, aoContext) {
return aoContext.method.toUpperCase() !== 'GET';
}
}));
Resource.public(Resource.async({
saveDelayeds: Function
}, {
default: function*() {
var data, delay, delayed, i, len, queue, queueName, ref, resque, scriptName;
resque = this.facade.retrieveProxy(RESQUE);
ref = (yield resque.getDelayed());
for (i = 0, len = ref.length; i < len; i++) {
delayed = ref[i];
({queueName, scriptName, data, delay} = delayed);
queue = (yield resque.get(queueName != null ? queueName : DELAYED_JOBS_QUEUE));
yield queue.push(scriptName, data, delay);
}
}
}));
Resource.public(Resource.async({
execute: FuncG(NotificationInterface)
}, {
default: function*(aoNotification) {
var DEBUG, ERROR, LEVELS, SEND_TO_LOG, action, app, err, resourceName, service, t1, t2, voBody, voResult;
({ERROR, DEBUG, LEVELS, SEND_TO_LOG} = Module.prototype.LogMessage);
resourceName = aoNotification.getName();
voBody = aoNotification.getBody();
action = aoNotification.getType();
service = this.facade.retrieveMediator(APPLICATION_MEDIATOR).getViewComponent();
try {
if (isArangoDB()) {
service.context = voBody.context;
}
if (service.context != null) {
this.sendNotification(SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION START', LEVELS[DEBUG]);
voResult = {
result: (yield this.doAction(action, voBody.context)),
resource: this
};
this.sendNotification(SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION END', LEVELS[DEBUG]);
} else {
this.sendNotification(SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT CREATE', LEVELS[DEBUG]);
t1 = Date.now();
app = this.Module.prototype.MainApplication.new(Module.prototype.LIGHTWEIGHT);
app.start();
this.sendNotification(SEND_TO_LOG, `>>>>>>>>>> LIGHTWEIGHT START after ${Date.now() - t1}`, LEVELS[DEBUG]);
voResult = (yield app.execute(resourceName, voBody, action));
this.sendNotification(SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT END', LEVELS[DEBUG]);
t2 = Date.now();
app.finish();
this.sendNotification(SEND_TO_LOG, `>>>>>>>>>> LIGHTWEIGHT DESTROYED after ${Date.now() - t2}`, LEVELS[DEBUG]);
}
} catch (error) {
err = error;
voResult = {
error: err,
resource: this
};
}
this.sendNotification(HANDLER_RESULT, voResult, voBody.reverse);
}
}));
Resource.initialize();
return Resource;
}).call(this);
};
}).call(this);