UNPKG

@leansdk/leanrc

Version:

LeanRC is a MVC framework for creating graceful applications

351 lines (301 loc) 11.6 kB
# 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' module.exports = (Module)-> { 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:: HTTP_NOT_FOUND = statuses 'not found' UNAUTHORIZED = statuses 'unauthorized' FORBIDDEN = statuses 'forbidden' UPGRADE_REQUIRED = statuses 'upgrade required' class Resource extends SimpleCommand @inheritProtected() @include ConfigurableMixin @include ChainsMixin @implements ResourceInterface @module Module @public entityName: String, get: -> throw new Error 'Not implemented specific property' yield return # 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'] ### @public needsLimitation: Boolean, default: yes @public @async checkApiVersion: Function, default: (args...)-> vVersion = @context.pathParams.v vCurrentVersion = @configs.version unless vCurrentVersion? throw new Error 'No `version` specified in the configuration' [vNeedVersion] = vCurrentVersion.match(/^\d{1,}[.]\d{1,}/) ? [] unless vNeedVersion? throw new Error 'Incorrect `version` specified in the configuration' semver = require 'semver' unless semver.satisfies vCurrentVersion, vVersion @context.throw UPGRADE_REQUIRED, "Upgrade: v#{vCurrentVersion}" yield return args @public @async setOwnerId: Function, default: (args...)-> @recordBody.ownerId = @session.uid ? null yield return args @public @async protectOwnerId: Function, default: (args...)-> @recordBody = _.omit @recordBody, ['ownerId'] yield return args @public @async filterOwnerByCurrentUser: Function, default: (args...)-> unless @session.userIsAdmin @listQuery ?= {} if @listQuery.$filter? @listQuery.$filter = $and: [ @listQuery.$filter , '@doc.ownerId': $eq: @session.uid ] else @listQuery.$filter = '@doc.ownerId': $eq: @session.uid yield return args @public @async checkOwner: Function, default: (args...) -> unless @session.uid? @context.throw UNAUTHORIZED return if @session.userIsAdmin return args unless (key = @context.pathParams[@keyName])? return args doc = yield @collection.find key unless doc? @context.throw HTTP_NOT_FOUND unless doc.ownerId return args if @session.uid isnt doc.ownerId @context.throw FORBIDDEN return yield return args @public @async checkExistence: Function, default: (args...) -> unless @recordId? @context.throw HTTP_NOT_FOUND unless yield @collection.includes @recordId @context.throw HTTP_NOT_FOUND yield return args @public @async adminOnly: Function, default: (args...) -> unless @session.uid? @context.throw UNAUTHORIZED return unless @session.userIsAdmin @context.throw FORBIDDEN return yield return args @public @async checkSchemaVersion: Function, default: (args...)-> voMigrations = @facade.retrieveProxy MIGRATIONS [..., lastMigration] = @Module::MIGRATION_NAMES unless lastMigration? yield return args includes = yield voMigrations.includes lastMigration if includes yield return args else throw new Error 'Code schema version is not equal current DB version' yield return return args @public keyName: String, get: -> inflect.singularize inflect.underscore @entityName @public itemEntityName: String, get: -> inflect.singularize inflect.underscore @entityName @public listEntityName: String, get: -> inflect.pluralize inflect.underscore @entityName @public collectionName: String, get: -> "#{inflect.pluralize inflect.camelize @entityName}Collection" @public collection: CollectionInterface, get: -> @facade.retrieveProxy @collectionName @public context: MaybeG ContextInterface @public listQuery: MaybeG Object @public recordId: MaybeG String @public recordBody: MaybeG Object @public actionResult: MaybeG AnyT @public @static actions: DictG(String, Object), get: -> @metaObject.getGroup 'actions', no @public @static action: FuncG([UnionG Object, TupleG Object, Object]), default: (args...)-> # 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 = @public args... @metaObject.addMetaData 'actions', name, @instanceMethods[name] return @action @async list: FuncG([], StructG { meta: StructG pagination: StructG { limit: UnionG Number, EnumG ['not defined'] offset: UnionG Number, EnumG ['not defined'] } items: ListG Object }), default: -> vlItems = yield (yield @collection.takeAll()).toArray() return { meta: pagination: limit: 'not defined' offset: 'not defined' items: vlItems } @action @async detail: FuncG([], Object), default: -> yield @collection.find @recordId @action @async create: FuncG([], Object), default: -> yield @collection.create @recordBody @action @async update: FuncG([], Object), default: -> yield @collection.update @recordId, @recordBody @action @async delete: Function, default: -> yield @collection.delete @recordId @context.status = 204 yield return @action @async destroy: Function, default: -> yield @collection.destroy @recordId @context.status = 204 yield return # ------------ Chains definitions --------- @chains [ 'list', 'detail', 'create', 'update', 'delete', 'destroy' ] @initialHook 'beforeActionHook' @beforeHook 'getQuery', only: ['list'] @beforeHook 'getRecordId', only: ['detail', 'update', 'delete', 'destroy'] @beforeHook 'checkExistence', only: ['detail', 'update', 'delete', 'destroy'] @beforeHook 'getRecordBody', only: ['create', 'update'] @beforeHook 'omitBody', only: ['create', 'update'] @beforeHook 'beforeUpdate', only: ['update'] @public beforeActionHook: Function, default: (args...)-> [@context] = args return args @public getQuery: Function, default: (args...)-> @listQuery = JSON.parse @context.query['query'] ? "{}" return args @public getRecordId: Function, default: (args...)-> @recordId = @context.pathParams[@keyName] return args @public getRecordBody: Function, default: (args...)-> @recordBody = @context.request.body?[@itemEntityName] return args @public omitBody: Function, default: (args...)-> @recordBody = _.omit @recordBody, [ '_id', '_rev', 'rev', 'type', '_type' '_owner', '_space', '_from', '_to' ] moduleName = @collection.delegate.moduleName() name = @collection.delegate.name @recordBody.type = "#{moduleName}::#{name}" return args @public beforeUpdate: Function, default: (args...)-> @recordBody = assign {}, @recordBody, id: @recordId return args @public @async doAction: FuncG([String, ContextInterface], MaybeG AnyT), default: (action, context)-> voResult = yield @[action]? context @actionResult = voResult yield @saveDelayeds() yield return voResult @public @async writeTransaction: FuncG([String, ContextInterface], Boolean), default: (asAction, aoContext) -> yield return aoContext.method.toUpperCase() isnt 'GET' @public @async saveDelayeds: Function, default: -> resque = @facade.retrieveProxy RESQUE for delayed in yield resque.getDelayed() {queueName, scriptName, data, delay} = delayed queue = yield resque.get queueName ? DELAYED_JOBS_QUEUE yield queue.push scriptName, data, delay yield return @public @async execute: FuncG(NotificationInterface), default: (aoNotification)-> { ERROR, DEBUG, LEVELS, SEND_TO_LOG } = Module::LogMessage resourceName = aoNotification.getName() voBody = aoNotification.getBody() action = aoNotification.getType() service = @facade.retrieveMediator APPLICATION_MEDIATOR .getViewComponent() try if isArangoDB() service.context = voBody.context if service.context? @sendNotification SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION START', LEVELS[DEBUG] voResult = result: yield @doAction action, voBody.context resource: @ @sendNotification SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION END', LEVELS[DEBUG] else @sendNotification SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT CREATE', LEVELS[DEBUG] t1 = Date.now() app = @Module::MainApplication.new Module::LIGHTWEIGHT app.start() @sendNotification SEND_TO_LOG, ">>>>>>>>>> LIGHTWEIGHT START after #{Date.now() - t1}", LEVELS[DEBUG] voResult = yield app.execute resourceName, voBody, action @sendNotification SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT END', LEVELS[DEBUG] t2 = Date.now() app.finish() @sendNotification SEND_TO_LOG, ">>>>>>>>>> LIGHTWEIGHT DESTROYED after #{Date.now() - t2}", LEVELS[DEBUG] catch err voResult = error: err resource: @ @sendNotification HANDLER_RESULT, voResult, voBody.reverse yield return @initialize()