UNPKG

@resin/pinejs

Version:

Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make

158 lines (150 loc) 6.75 kB
_ = require 'lodash' Promise = require 'bluebird' transactionModel = require './transaction.sbvr' exports.config = models: [ apiRoot: 'transaction' modelText: transactionModel customServerCode: exports ] exports.setup = (app, sbvrUtils) -> exports.addModelHooks = (modelName) -> # TODO: Add checks on POST/PATCH requests as well. sbvrUtils.addPureHook 'PUT', modelName, 'all', ({ tx, request }) -> vocab = request.vocabulary { logger } = sbvrUtils.api[vocab] id = sbvrUtils.getID(vocab, request) tx.executeSql(''' SELECT NOT EXISTS( SELECT 1 FROM "resource" r JOIN "resource-is under-lock" AS rl ON rl."resource" = r."id" WHERE r."resource type" = ? AND r."resource id" = ? ) AS result;''', [request.resourceName, id] ) .catch (err) -> logger.error('Unable to check resource locks', err, err.stack) throw new Error('Unable to check resource locks') .then (result) -> if result.rows[0].result in [false, 0, '0'] throw new Error('The resource is locked and cannot be edited') endTransaction = (transactionID) -> sbvrUtils.db.transaction (tx) -> placeholders = {} getLockedRow = (lockID) -> # 'GET', '/transaction/resource?$select=resource_id&$filter=resource__is_under__lock/lock eq ?' tx.executeSql('''SELECT "resource"."resource id" AS "resource_id" FROM "resource", "resource-is under-lock" WHERE "resource"."id" = "resource-is under-lock"."resource" AND "resource-is under-lock"."lock" = ?;''', [lockID]) getFieldsObject = (conditionalResourceID, clientModel) -> # 'GET', '/transaction/conditional_field?$select=field_name,field_value&$filter=conditional_resource eq ?' tx.executeSql('''SELECT "conditional field"."field name" AS "field_name", "conditional field"."field value" AS "field_value" FROM "conditional field" WHERE "conditional field"."conditional resource" = ?;''', [conditionalResourceID]) .then (fields) -> fieldsObject = {} Promise.all fields.rows.map (field) -> fieldName = field.field_name.replace(clientModel.resourceName + '.', '') fieldValue = field.field_value modelField = _.find(clientModel.fields, { fieldName }) if modelField.dataType == 'ForeignKey' and _.isNaN(Number(fieldValue)) if !placeholders.hasOwnProperty(fieldValue) throw new Error('Cannot resolve placeholder' + fieldValue) else placeholders[fieldValue].promise .then (resolvedID) -> fieldsObject[fieldName] = resolvedID .catch -> throw new Error('Placeholder failed' + fieldValue) else fieldsObject[fieldName] = fieldValue .then -> return fieldsObject # 'GET', '/transaction/conditional_resource?$select=id,lock,resource_type,conditional_type,placeholder&$filter=transaction eq ?' tx.executeSql(''' SELECT "conditional resource"."id", "conditional resource"."lock", "conditional resource"."resource type" AS "resource_type", "conditional resource"."conditional type" AS "conditional_type", "conditional resource"."placeholder" FROM "conditional resource" WHERE "conditional resource"."transaction" = ?; ''', [transactionID]) .then (conditionalResources) -> conditionalResources.rows.forEach (conditionalResource) -> placeholder = conditionalResource.placeholder if placeholder? and placeholder.length > 0 placeholders[placeholder] = {} placeholders[placeholder].promise = new Promise (resolve, reject) -> placeholders[placeholder].resolve = resolve placeholders[placeholder].reject = reject # get conditional resources (if exist) Promise.all conditionalResources.rows.map (conditionalResource) -> placeholder = conditionalResource.placeholder lockID = conditionalResource.lock doCleanup = -> Promise.all([ tx.executeSql('DELETE FROM "conditional field" WHERE "conditional resource" = ?;', [conditionalResource.id]) tx.executeSql('DELETE FROM "conditional resource" WHERE "lock" = ?;', [lockID]) tx.executeSql('DELETE FROM "resource-is under-lock" WHERE "lock" = ?;', [lockID]) tx.executeSql('DELETE FROM "lock" WHERE "id" = ?;', [lockID]) ]) passthrough = { tx } clientModel = clientModels[modelName].resources[conditionalResource.resource_type] url = modelName + '/' + conditionalResource.resource_type switch conditionalResource.conditional_type when 'DELETE' getLockedRow(lockID) .then (lockedRow) -> lockedRow = lockedRow.rows[0] url = url + '?$filter=' + clientModel.idField + ' eq ' + lockedRow.resource_id sbvrUtils.PinejsClient::delete({ url, passthrough }) .then(doCleanup) when 'EDIT' getLockedRow(lockID) .then (lockedRow) -> lockedRow = lockedRow.rows[0] getFieldsObject(conditionalResource.id, clientModel) .then (body) -> body[clientModel.idField] = lockedRow.resource_id sbvrUtils.PinejsClient::put({ url, body, passthrough }) .then(doCleanup) when 'ADD' getFieldsObject(conditionalResource.id, clientModel) .then (body) -> sbvrUtils.PinejsClient::post({ url, body, passthrough }) .then (result) -> placeholders[placeholder].resolve(result.id) .then(doCleanup) .tapCatch (err) -> placeholders[placeholder].reject(err) return .then (err) -> tx.executeSql('DELETE FROM "transaction" WHERE "id" = ?;', [transactionID]) .then (result) -> sbvrUtils.validateModel(tx, modelName) # TODO: these really should be specific to the model - currently they will only work for the first model added app.post '/transaction/execute', (req, res, next) -> id = Number(req.body.id) if _.isNaN(id) res.sendStatus(404) else endTransaction(id) .then -> res.sendStatus(200) .catch (err) -> console.error('Error ending transaction', err, err.stack) res.status(404).json(err) app.get '/transaction', (req, res, next) -> res.json( transactionURI: '/transaction/transaction' conditionalResourceURI: '/transaction/conditional_resource' conditionalFieldURI: '/transaction/conditional_field' lockURI: '/transaction/lock' transactionLockURI: '/transaction/lock__belongs_to__transaction' resourceURI: '/transaction/resource' lockResourceURI: '/transaction/resource__is_under__lock' exclusiveLockURI: '/transaction/lock__is_exclusive' commitTransactionURI: '/transaction/execute' ) app.all('/transaction/*', sbvrUtils.handleODataRequest)