UNPKG

simple-odata-server

Version:
364 lines (301 loc) 9.89 kB
/*! * Copyright(c) 2014 Jan Blaha (pofider) * * ODataServer class - main facade */ /* eslint no-useless-escape: 0 */ const Emitter = require('events').EventEmitter const util = require('util') const url = require('url') const metadata = require('./metadata.js') const collections = require('./collections.js') const query = require('./query.js') const insert = require('./insert.js') const update = require('./update.js') const remove = require('./remove.js') const Router = require('./router.js') const prune = require('./prune.js') const Buffer = require('safe-buffer').Buffer function ODataServer (serviceUrl) { this.serviceUrl = serviceUrl this.cfg = { serviceUrl, afterRead: function () {}, beforeQuery: function (col, query, req, cb) { cb() }, executeQuery: ODataServer.prototype.executeQuery.bind(this), beforeInsert: function (col, query, req, cb) { cb() }, executeInsert: ODataServer.prototype.executeInsert.bind(this), beforeUpdate: function (col, query, update, req, cb) { cb() }, executeUpdate: ODataServer.prototype.executeUpdate.bind(this), beforeRemove: function (col, query, req, cb) { cb() }, executeRemove: ODataServer.prototype.executeRemove.bind(this), base64ToBuffer: ODataServer.prototype.base64ToBuffer.bind(this), bufferToBase64: ODataServer.prototype.bufferToBase64.bind(this), pruneResults: ODataServer.prototype.pruneResults.bind(this), addCorsToResponse: ODataServer.prototype.addCorsToResponse.bind(this) } } util.inherits(ODataServer, Emitter) ODataServer.prototype.handle = function (req, res) { if (!this.cfg.serviceUrl && !req.protocol) { throw new Error('Unable to determine service url from the express request or value provided in the ODataServer constructor.') } function escapeRegExp (str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') } // If mounted in express, trim off the subpath (req.url) giving us just the base path const path = (req.originalUrl || '/').replace(new RegExp(escapeRegExp(req.url) + '$'), '') this.cfg.serviceUrl = this.serviceUrl ? this.serviceUrl : (req.protocol + '://' + req.get('host') + path) const prefix = url.parse(this.cfg.serviceUrl).pathname // eslint-disable-line if (!this.router || (prefix !== this.router.prefix)) { this.router = new Router(prefix) this._initializeRoutes() } this.router.dispatch(req, res) } ODataServer.prototype._initializeRoutes = function () { const self = this this.router.get('/\$metadata', function (req, res) { const result = metadata(self.cfg) res.statusCode = 200 res.setHeader('Content-Type', 'application/xml') res.setHeader('DataServiceVersion', '4.0') res.setHeader('OData-Version', '4.0') self.cfg.addCorsToResponse(res) return res.end(result) }) this.router.get('/:collection/\$count', function (req, res) { req.params.$count = true query(self.cfg, req, res) }) this.router.get('/:collection\\(:id\\)', function (req, res) { query(self.cfg, req, res) }) this.router.get('/:collection', function (req, res) { query(self.cfg, req, res) }) this.router.get('/', function (req, res) { const result = collections(self.cfg) res.statusCode = 200 res.setHeader('Content-Type', 'application/json') self.cfg.addCorsToResponse(res) return res.end(result) }) this.router.post('/:collection', function (req, res) { insert(self.cfg, req, res) }) this.router.patch('/:collection\\(:id\\)', function (req, res) { update(self.cfg, req, res) }) this.router.delete('/:collection\\(:id\\)', function (req, res) { remove(self.cfg, req, res) }) if (this.cfg.cors) { this.router.options('/(.*)', function (req, res) { res.statusCode = 200 res.setHeader('Access-Control-Allow-Origin', self.cfg.cors) res.end() }) } this.router.error(function (req, res, error) { function def (e) { self.emit('odata-error', e) res.statusCode = (error.code && error.code >= 100 && error.code < 600) ? error.code : 500 res.setHeader('Content-Type', 'application/json') self.cfg.addCorsToResponse(res) res.end(JSON.stringify({ error: { code: error.code || 500, message: e.message, stack: e.stack, target: req.url, details: [] }, innererror: { } })) } if (self.cfg.error) { self.cfg.error(req, res, error, def) } else { def(error) } }) } ODataServer.prototype.error = function (fn) { this.cfg.error = fn.bind(this) return this } ODataServer.prototype.query = function (fn) { this.cfg.query = fn.bind(this) return this } ODataServer.prototype.cors = function (domains) { this.cfg.cors = domains return this } ODataServer.prototype.beforeQuery = function (fn) { if (fn.length === 3) { console.warn('Listener function should accept request parameter.') const origFn = fn fn = function (col, query, req, cb) { origFn(col, query, cb) } } this.cfg.beforeQuery = fn.bind(this) return this } ODataServer.prototype.executeQuery = function (col, query, req, cb) { const self = this this.cfg.beforeQuery(col, query, req, function (err) { if (err) { return cb(err) } self.cfg.query(col, query, req, function (err, res) { if (err) { return cb(err) } self.cfg.afterRead(col, res, req) cb(null, res) }) }) } ODataServer.prototype.insert = function (fn) { this.cfg.insert = fn.bind(this) return this } ODataServer.prototype.beforeInsert = function (fn) { if (fn.length === 3) { console.warn('Listener function should accept request parameter.') const origFn = fn fn = function (col, doc, req, cb) { origFn(col, doc, cb) } } this.cfg.beforeInsert = fn.bind(this) return this } ODataServer.prototype.executeInsert = function (col, doc, req, cb) { const self = this this.cfg.beforeInsert(col, doc, req, function (err) { if (err) { return cb(err) } self.cfg.insert(col, doc, req, cb) }) } ODataServer.prototype.update = function (fn) { this.cfg.update = fn.bind(this) return this } ODataServer.prototype.beforeUpdate = function (fn) { if (fn.length === 4) { console.warn('Listener function should accept request parameter.') const origFn = fn fn = function (col, query, update, req, cb) { origFn(col, query, update, cb) } } this.cfg.beforeUpdate = fn.bind(this) return this } ODataServer.prototype.executeUpdate = function (col, query, update, req, cb) { const self = this this.cfg.beforeUpdate(col, query, update, req, function (err) { if (err) { return cb(err) } self.cfg.update(col, query, update, req, cb) }) } ODataServer.prototype.remove = function (fn) { this.cfg.remove = fn.bind(this) return this } ODataServer.prototype.beforeRemove = function (fn) { if (fn.length === 3) { console.warn('Listener function should accept request parameter.') const origFn = fn fn = function (col, query, req, cb) { origFn(col, query, cb) } } this.cfg.beforeRemove = fn.bind(this) return this } ODataServer.prototype.executeRemove = function (col, query, req, cb) { const self = this this.cfg.beforeRemove(col, query, req, function (err) { if (err) { return cb(err) } self.cfg.remove(col, query, req, cb) }) } ODataServer.prototype.afterRead = function (fn) { this.cfg.afterRead = fn return this } ODataServer.prototype.model = function (model) { this.cfg.model = model return this } ODataServer.prototype.adapter = function (adapter) { adapter(this) return this } ODataServer.prototype.pruneResults = function (collection, res) { prune(this.cfg.model, collection, res) } ODataServer.prototype.base64ToBuffer = function (collection, doc) { const model = this.cfg.model const entitySet = model.entitySets[collection] const entityType = model.entityTypes[entitySet.entityType.replace(model.namespace + '.', '')] for (const prop in doc) { if (!prop) { continue } const propDef = entityType[prop] if (!propDef) { continue } if (propDef.type === 'Edm.Binary') { doc[prop] = Buffer.from(doc[prop], 'base64') } } } ODataServer.prototype.bufferToBase64 = function (collection, res) { const model = this.cfg.model const entitySet = model.entitySets[collection] const entityType = model.entityTypes[entitySet.entityType.replace(model.namespace + '.', '')] for (const i in res) { const doc = res[i] for (const prop in doc) { if (!prop) { continue } const propDef = entityType[prop] if (!propDef) { continue } if (propDef.type === 'Edm.Binary') { // nedb returns object instead of buffer on node 4 if (!Buffer.isBuffer(doc[prop]) && !doc[prop].length) { let obj = doc[prop] obj = obj.data || obj doc[prop] = Object.keys(obj).map(function (key) { return obj[key] }) } // unwrap mongo style buffers if (doc[prop]._bsontype === 'Binary') { doc[prop] = doc[prop].buffer } doc[prop] = Buffer.from(doc[prop]).toString('base64') } } } } ODataServer.prototype.addCorsToResponse = function (res) { if (this.cfg.cors) { res.setHeader('Access-Control-Allow-Origin', this.cfg.cors) } } module.exports = ODataServer