@leansdk/leanrc
Version:
LeanRC is a MVC framework for creating graceful applications
351 lines (301 loc) • 11.6 kB
text/coffeescript
# 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
ConfigurableMixin
ChainsMixin
ResourceInterface
Module
entityName: String,
get: ->
throw new Error 'Not implemented specific property'
yield return
# example of usage
###
'checkApiVersion'
'checkSession'
'adminOnly', only: ['create'] # required checkSession before
'checkOwner', only: ['detail', 'update', 'delete'] # required checkSession before
'filterOwnerByCurrentUser', only: ['list']
'setOwnerId', only: ['create']
'protectOwnerId', only: ['update']
###
needsLimitation: Boolean,
default: yes
checkApiVersion: Function,
default: (args...)->
vVersion = .pathParams.v
vCurrentVersion = .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
.throw UPGRADE_REQUIRED, "Upgrade: v#{vCurrentVersion}"
yield return args
setOwnerId: Function,
default: (args...)->
.ownerId = .uid ? null
yield return args
protectOwnerId: Function,
default: (args...)->
= _.omit , ['ownerId']
yield return args
filterOwnerByCurrentUser: Function,
default: (args...)->
unless .userIsAdmin
?= {}
if .$filter?
.$filter = $and: [
.$filter
,
'@doc.ownerId': $eq: .uid
]
else
.$filter = '@doc.ownerId': $eq: .uid
yield return args
checkOwner: Function,
default: (args...) ->
unless .uid?
.throw UNAUTHORIZED
return
if .userIsAdmin
return args
unless (key = .pathParams[])?
return args
doc = yield .find key
unless doc?
.throw HTTP_NOT_FOUND
unless doc.ownerId
return args
if .uid isnt doc.ownerId
.throw FORBIDDEN
return
yield return args
checkExistence: Function,
default: (args...) ->
unless ?
.throw HTTP_NOT_FOUND
unless yield .includes
.throw HTTP_NOT_FOUND
yield return args
adminOnly: Function,
default: (args...) ->
unless .uid?
.throw UNAUTHORIZED
return
unless .userIsAdmin
.throw FORBIDDEN
return
yield return args
checkSchemaVersion: Function,
default: (args...)->
voMigrations = .retrieveProxy MIGRATIONS
[..., lastMigration] = ::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
keyName: String,
get: ->
inflect.singularize inflect.underscore
itemEntityName: String,
get: ->
inflect.singularize inflect.underscore
listEntityName: String,
get: ->
inflect.pluralize inflect.underscore
collectionName: String,
get: ->
"#{inflect.pluralize inflect.camelize @entityName}Collection"
collection: CollectionInterface,
get: ->
.retrieveProxy
context: MaybeG ContextInterface
listQuery: MaybeG Object
recordId: MaybeG String
recordBody: MaybeG Object
actionResult: MaybeG AnyT
actions: DictG(String, Object),
get: ->
.getGroup 'actions', no
action: FuncG([UnionG Object, TupleG Object, Object]),
default: (args...)->
# default: (nameDefinition, config)->
# [actionName] = Object.keys nameDefinition
# if nameDefinition.attr? and not config?
# .addMetaData 'actions', nameDefinition.attr, nameDefinition
# else
# .addMetaData 'actions', actionName, config
name = args...
.addMetaData 'actions', name, [name]
return
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 .takeAll()).toArray()
return {
meta:
pagination:
limit: 'not defined'
offset: 'not defined'
items: vlItems
}
detail: FuncG([], Object),
default: ->
yield .find
create: FuncG([], Object),
default: ->
yield .create
update: FuncG([], Object),
default: ->
yield .update ,
delete: Function,
default: ->
yield .delete
.status = 204
yield return
destroy: Function,
default: ->
yield .destroy
.status = 204
yield return
# ------------ Chains definitions ---------
[
'list', 'detail', 'create', 'update', 'delete', 'destroy'
]
'beforeActionHook'
'getQuery', only: ['list']
'getRecordId', only: ['detail', 'update', 'delete', 'destroy']
'checkExistence', only: ['detail', 'update', 'delete', 'destroy']
'getRecordBody', only: ['create', 'update']
'omitBody', only: ['create', 'update']
'beforeUpdate', only: ['update']
beforeActionHook: Function,
default: (args...)->
[] = args
return args
getQuery: Function,
default: (args...)->
= JSON.parse .query['query'] ? "{}"
return args
getRecordId: Function,
default: (args...)->
= .pathParams[]
return args
getRecordBody: Function,
default: (args...)->
= .request.body?[]
return args
omitBody: Function,
default: (args...)->
= _.omit , [
'_id', '_rev', 'rev', 'type', '_type'
'_owner', '_space',
'_from', '_to'
]
moduleName = .delegate.moduleName()
name = .delegate.name
.type = "#{moduleName}::#{name}"
return args
beforeUpdate: Function,
default: (args...)->
= assign {}, , id:
return args
doAction: FuncG([String, ContextInterface], MaybeG AnyT),
default: (action, context)->
voResult = yield @[action]? context
= voResult
yield
yield return voResult
writeTransaction: FuncG([String, ContextInterface], Boolean),
default: (asAction, aoContext) ->
yield return aoContext.method.toUpperCase() isnt 'GET'
saveDelayeds: Function,
default: ->
resque = .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
execute: FuncG(NotificationInterface),
default: (aoNotification)->
{ ERROR, DEBUG, LEVELS, SEND_TO_LOG } = Module::LogMessage
resourceName = aoNotification.getName()
voBody = aoNotification.getBody()
action = aoNotification.getType()
service = .retrieveMediator APPLICATION_MEDIATOR
.getViewComponent()
try
if isArangoDB()
service.context = voBody.context
if service.context?
SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION START', LEVELS[DEBUG]
voResult =
result: yield action, voBody.context
resource: @
SEND_TO_LOG, '>>>>>>>>>>>>>> EXECUTION END', LEVELS[DEBUG]
else
SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT CREATE', LEVELS[DEBUG]
t1 = Date.now()
app = ::MainApplication.new Module::LIGHTWEIGHT
app.start()
SEND_TO_LOG, ">>>>>>>>>> LIGHTWEIGHT START after #{Date.now() - t1}", LEVELS[DEBUG]
voResult = yield app.execute resourceName, voBody, action
SEND_TO_LOG, '>>>>>>>>>> LIGHTWEIGHT END', LEVELS[DEBUG]
t2 = Date.now()
app.finish()
SEND_TO_LOG, ">>>>>>>>>> LIGHTWEIGHT DESTROYED after #{Date.now() - t2}", LEVELS[DEBUG]
catch err
voResult =
error: err
resource: @
HANDLER_RESULT, voResult, voBody.reverse
yield return