diff --git a/example/custom_service/index.js b/example/custom_service/index.js index 9cb0374172..eaefd0dafa 100644 --- a/example/custom_service/index.js +++ b/example/custom_service/index.js @@ -14,7 +14,7 @@ var users = [ ]; var service = { - index : function (params, cb) { + find: function (params, cb) { cb(null, users); }, diff --git a/example/rest_memory/index.js b/example/rest_memory/index.js index fbb49868b5..5e88072d0f 100644 --- a/example/rest_memory/index.js +++ b/example/rest_memory/index.js @@ -1,6 +1,6 @@ var feathers = require('../../lib/feathers'); var Proto = require('uberproto'); -var memoryService = feathers.memory(); +var memoryService = feathers.service.memory(); var express = require('express'); feathers.createServer({ port: 3000 }) diff --git a/lib/feathers.js b/lib/feathers.js index ad5aa4036d..71edac8e1d 100644 --- a/lib/feathers.js +++ b/lib/feathers.js @@ -19,4 +19,6 @@ _.each(services, function(Service, name) { service[name] = function(options) { return Proto.create.call(Service, options); }; + + service[name].Service = Service; }); diff --git a/lib/mixins/event.js b/lib/mixins/event.js index 77f6fbaf84..5a435a409e 100644 --- a/lib/mixins/event.js +++ b/lib/mixins/event.js @@ -13,7 +13,7 @@ var eventMappings = { * * @type {{setup: Function}} */ -var Mixin = { +var EventMixin = { setup: function() { var emitter = this._rubberDuck = rubberduck.emitter(this); var self = this; @@ -47,7 +47,7 @@ var Mixin = { // Add EventEmitter prototype methods (if they don't already exist) _.each(EventEmitter.prototype, function(fn, name) { - Mixin[name] = function() { + EventMixin[name] = function() { if(this._super) { return this._super.apply(this, arguments); } @@ -55,4 +55,8 @@ _.each(EventEmitter.prototype, function(fn, name) { } }); -module.exports = Mixin; +module.exports = function(service) { + service.mixin && service.mixin(EventMixin); +}; + +module.exports.Mixin = EventMixin; diff --git a/lib/mixins/index.js b/lib/mixins/index.js index bc4ae63d06..56c6a3bb19 100644 --- a/lib/mixins/index.js +++ b/lib/mixins/index.js @@ -1,5 +1,4 @@ -exports.Event = require('./event'); -exports.Validation = require('./validation'); - -// TODO exports.Association = require('./association'); -// TODO exports.Authentication = require('./authentication'); +module.exports = [ + require('./event'), + require('./validation') +]; diff --git a/lib/mixins/validation.js b/lib/mixins/validation.js index 41f917fcfb..e367a4e67e 100644 --- a/lib/mixins/validation.js +++ b/lib/mixins/validation.js @@ -1,7 +1,7 @@ var _ = require('underscore'); var ValidationError = require('../errors').ValidationError; -module.exports = { +var ValidationMixin = { create: function(data, params, cb) { var self = this; this.validate(data, _.extend({ validates: 'create' }, params), function(errors) { @@ -22,3 +22,11 @@ module.exports = { }); } }; + +module.exports = function(service) { + if(typeof service.validate === 'function' && service.mixin) { + service.mixin(ValidationMixin); + } +}; + +module.exports.Mixin = ValidationMixin; \ No newline at end of file diff --git a/lib/providers/rest.js b/lib/providers/rest.js index 3b2d7d3639..0b5332633e 100644 --- a/lib/providers/rest.js +++ b/lib/providers/rest.js @@ -39,39 +39,39 @@ var RestProvider = Proto.extend({ this.services[path] = service; }, + _getParams: function(req) { + var query = req.query || {}; + return _.extend({ query: query }, req.feathers); + }, + _service: function(app, service, path) { var uri = toUri(path); + var self = this; // TODO throw 405 Method Not Allowed with allowed methods // GET / -> resource.index(cb, params) app.get(uri, function (req, res, next) { - service.find(req.query, wrapper(req, res, next)); + service.find(self._getParams(req), wrapper(req, res, next)); }); // GET /:id -> resource.get(cb, id, params) app.get(uri + '/:id', function (req, res, next) { - service.get(req.params.id, req.query, wrapper(req, res, next)); + service.get(req.params.id, self._getParams(req), wrapper(req, res, next)); }); // POST -> resource.create(cb, data, params) app.post(uri, function (req, res, next) { -// if (_.isEmpty(req.body)) { -// return next(new errors.UnsupportedMediaType('No request body received')); -// } - service.create(req.body, req.query, wrapper(req, res, next)); + service.create(req.body, self._getParams(req), wrapper(req, res, next)); }); // PUT /:id -> resource.update(cb, id, data, params) app.put(uri + '/:id', function (req, res, next) { -// if (_.isEmpty(req.body)) { -// return next(new errors.UnsupportedMediaType('No request body received')); -// } - service.update(req.params.id, req.body, req.query, wrapper(req, res, next)); + service.update(req.params.id, req.body, self._getParams(req), wrapper(req, res, next)); }); // DELETE /:id -> resource.destroy(cb, id, params) app.del(uri + '/:id', function (req, res, next) { - service.destroy(req.params.id, req.query, wrapper(req, res, next)); + service.destroy(req.params.id, self._getParams(req), wrapper(req, res, next)); }); }, @@ -92,6 +92,10 @@ var RestProvider = Proto.extend({ responder(req, res); }; + if(typeof config.before === 'function') { + config.before.call(this, server.config.app, server.config.engine); + } + // TODO (EK): We might not need this explicit use of express body parser. // We might just be able to use config because it is either passed // through the engine or our default engine (wich is express). @@ -101,6 +105,10 @@ var RestProvider = Proto.extend({ server.use(config.handler || _responder); server.use(config.errorHandler || _errorHandler); + if(typeof config.after === 'function') { + config.after.call(this, server.config.app, server.config.engine); + } + console.log('Feathers REST provider initialized on port %s', server.get('port')); return this; diff --git a/lib/server.js b/lib/server.js index 6a63ba989b..421c7dbbe9 100644 --- a/lib/server.js +++ b/lib/server.js @@ -20,16 +20,7 @@ var Server = Proto.extend({ methods: [ 'find', 'get', 'create', 'update', 'destroy' ], // Mixins to add when registering a service. // An array of functions that get passed the service object and can do something with it. - mixins: [ - function(service) { - service.mixin && service.mixin(mixins.Event); - }, - function(service) { - if(typeof service.validate === 'function' && service.mixin) { - service.mixin(mixins.Validation); - } - } - ] + mixins: mixins }); this.services = {}; @@ -73,7 +64,7 @@ var Server = Proto.extend({ return this.config.app.get(setting); }, - service: function (location, service, options) { + service: function (location, service) { var protoService = Proto.extend(service); // Add all the mixins diff --git a/lib/services/memory.js b/lib/services/memory.js index 7505f05218..0cfe751d2f 100644 --- a/lib/services/memory.js +++ b/lib/services/memory.js @@ -40,10 +40,12 @@ var MemoryService = Proto.extend({ cb = params; } + params.query = params.query || {}; + var values = _.values(this.store); _.each(filters, function(handler, name) { - values = params[name] ? handler(values, params[name]) : values; + values = params.query[name] ? handler(values, params.query[name]) : values; }); cb(null, values); diff --git a/lib/services/mongodb.js b/lib/services/mongodb.js index d1b92bed40..a84393f6c6 100644 --- a/lib/services/mongodb.js +++ b/lib/services/mongodb.js @@ -31,33 +31,35 @@ var MongoService = Proto.extend({ this.type = 'mongodb'; this._id = options.idField || '_id'; - this.store = options.store || null; + this.connectionString = options.connectionString || null; this.collection = options.collection || null; - // TODO (EK): We need to get the collection somehow. + // NOTE (EK): We need to get the collection somehow. // We have 3 options: // 1. Pass in the path on each request // 2. Initialize separate instances and pass it in there // 3. Set the collection when we register each service // - // We are currently using option number 3. This could be bad. + // We are currently using option number 3. This could be a bad assumption. - if (!this.store){ + if (this.connectionString){ + this.store = mongo.db(this.connectionString); + } + else { this._connect(options); } }, // NOTE (EK): We create a new database connection for every MongoService. - // This may not be good but I think you could share connections by - // passing the store as an option to the MongoService. The rational for this + // This may not be good but... in the mean time the rational for this // design is because each user of a MongoService instance could be a separate // app residing on a totally different server. // TODO (EK): We need to handle replica sets. _connect: function(options){ - this.host = options.host || process.env.MONGO_HOST || 'localhost'; - this.port = options.port || process.env.MONGO_PORT || 27017; - this.db = options.db || process.env.MONGO_DB || 'feathers'; + this.host = options.host || process.env.MONGODB_HOST || 'localhost'; + this.port = options.port || process.env.MONGODB_PORT || 27017; + this.db = options.db || process.env.MONGODB_DB || 'feathers'; ackOptions = { w: options.w || 1, // write acknowledgment @@ -81,8 +83,6 @@ var MongoService = Proto.extend({ }, find: function (params, cb) { - - console.log('finding', params); var id = null; if (_.isFunction(params)){ @@ -98,16 +98,22 @@ var MongoService = Proto.extend({ // ie. sort, limit, fields, skip, etc... if (id){ - this.store.collection(this.collection).findById(id, params, cb); + this.store.collection(this.collection).findById(id, params.query, cb); } else { - this.store.collection(this.collection).find(params).toArray(cb); + this.store.collection(this.collection).find(params.query).toArray(cb); } }, get: function (id, params, cb) { + + if (_.isString(id)){ + id = id.toLowerCase(); + } + if (_.isFunction(params)){ cb = params; + params = {}; } if (!this.collection) return cb(new Error('No collection specified')); diff --git a/package.json b/package.json index 11e7ae4806..bd707b1882 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "feathers", "description": "An ultra scalable, feather weight, data oriented framework", - "version": "0.0.2", + "version": "0.0.3", "homepage": "https://github.com/yycjs/feathers", "repository": { "type": "git", diff --git a/test/mixins/event.test.js b/test/mixins/event.test.js index 2d1d22fd19..446df0adb3 100644 --- a/test/mixins/event.test.js +++ b/test/mixins/event.test.js @@ -1,7 +1,8 @@ var assert = require('assert'); var _ = require('underscore'); var Proto = require('uberproto'); -var EventMixin = require('../../lib/mixins/event'); +var mixinEvent = require('../../lib/mixins/event'); +var EventMixin = mixinEvent.Mixin; describe('Event mixin', function () { it('initializes', function () { @@ -11,7 +12,7 @@ describe('Event mixin', function () { } }); - FixtureService.mixin(EventMixin); + mixinEvent(FixtureService); assert.equal(typeof FixtureService.setup, 'function'); assert.equal(typeof FixtureService.on, 'function'); @@ -27,7 +28,7 @@ describe('Event mixin', function () { }, emit: function() { - return 'Original emit' + return 'Original emit'; } } @@ -47,7 +48,7 @@ describe('Event mixin', function () { } }); - FixtureService.mixin(EventMixin); + mixinEvent(FixtureService); var instance = Proto.create.call(FixtureService); instance.setup(); @@ -75,7 +76,7 @@ describe('Event mixin', function () { } }); - FixtureService.mixin(EventMixin); + mixinEvent(FixtureService); var instance = Proto.create.call(FixtureService); instance.setup(); @@ -103,7 +104,7 @@ describe('Event mixin', function () { } }); - FixtureService.mixin(EventMixin); + mixinEvent(FixtureService); var instance = Proto.create.call(FixtureService); instance.setup(); @@ -128,7 +129,7 @@ describe('Event mixin', function () { } }); - FixtureService.mixin(EventMixin); + mixinEvent(FixtureService); var instance = Proto.create.call(FixtureService); instance.setup(); diff --git a/test/mixins/validations.test.js b/test/mixins/validations.test.js index 619b484f9a..40ee26b9a2 100644 --- a/test/mixins/validations.test.js +++ b/test/mixins/validations.test.js @@ -2,7 +2,7 @@ var assert = require('assert'); var _ = require('underscore'); var Proto = require('uberproto'); var errors = require('../../lib/errors'); -var ValidationMixin = require('../../lib/mixins/validation'); +var mixinValidation = require('../../lib/mixins/validation'); describe('Validation mixin', function () { it('initializes', function () { @@ -17,7 +17,7 @@ describe('Validation mixin', function () { } }); - ValidationService.mixin(ValidationMixin); + mixinValidation(ValidationService); assert.equal(typeof ValidationService.create, 'function'); assert.equal(typeof ValidationService.update, 'function'); @@ -58,7 +58,7 @@ describe('Validation mixin', function () { } }); - ValidationService.mixin(ValidationMixin); + mixinValidation(ValidationService); var instance = Proto.create.call(ValidationService); instance.create({ name: 'Tester' }, {}, function(error, data) { @@ -96,7 +96,7 @@ describe('Validation mixin', function () { } }); - ValidationService.mixin(ValidationMixin); + mixinValidation(ValidationService); var instance = Proto.create.call(ValidationService); instance.update(14, { name: 'Tester' }, {}, function(error, data) { diff --git a/test/services/memory.test.js b/test/services/memory.test.js index 40e2db63ab..7033947a1d 100644 --- a/test/services/memory.test.js +++ b/test/services/memory.test.js @@ -150,7 +150,7 @@ describe('Memory Service', function () { } ]; - service.find({}, function(err, items){ + service.find({ query: {} }, function(err, items){ expect(err).to.be.null; expect(items).to.deep.equal(expected); done(); @@ -173,7 +173,11 @@ describe('Memory Service', function () { } ]; - service.find({ sort: 'name'}, function(err, items){ + var query = { + query: { sort: 'name' } + }; + + service.find(query, function(err, items){ expect(err).to.be.null; expect(items).to.deep.equal(expected); done(); @@ -196,7 +200,11 @@ describe('Memory Service', function () { } ]; - service.find({ sort: 'name', order: true }, function(err, items){ + var query = { + query: { sort: 'name', order: true } + }; + + service.find(query, function(err, items){ expect(err).to.be.null; expect(items).to.deep.equal(expected); done(); @@ -215,7 +223,11 @@ describe('Memory Service', function () { } ]; - service.find({ limit: 2 }, function(err, items){ + var query = { + query: { limit: 2 } + }; + + service.find(query, function(err, items){ expect(err).to.be.null; expect(items).to.deep.equal(expected); done(); @@ -230,7 +242,11 @@ describe('Memory Service', function () { } ]; - service.find({ skip: 2 }, function(err, items){ + var query = { + query: { skip: 2 } + }; + + service.find(query, function(err, items){ expect(err).to.be.null; expect(items).to.deep.equal(expected); done(); diff --git a/test/services/mongo.test.js b/test/services/mongo.test.js deleted file mode 100644 index 3129bf8936..0000000000 --- a/test/services/mongo.test.js +++ /dev/null @@ -1,38 +0,0 @@ -var assert = require('assert'); -var MongoService = require('../../lib/services/mongodb'); -var Proto = require('uberproto'); - -describe('Mongo Service', function () { - describe('get', function () { - it('should return an instance that exists'); - it('should return an error on db error'); - }); - - describe('create', function () { - it('should create a new instance'); - it('should return an error on db error'); - }); - - describe('update', function () { - it('should update an existing instance'); - it('should return an error on db error'); - }); - - describe('destroy', function () { - it('should delete an existing instance'); - it('should return an error on db error'); - }); - - describe('init', function () { - it('should setup a mongo connection based on config'); - it('should setup a mongo connection based on ENV vars'); - }); - - describe('index', function () { - it('should return all items'); - it('should return all items sorted in ascending order'); - it('should return all items sorted in descending order'); - it('should return the number of items set by the limit'); - it('should skip over the number of items set by skip'); - }); -}); diff --git a/test/services/mongodb.test.js b/test/services/mongodb.test.js index 3129bf8936..a927c047aa 100644 --- a/test/services/mongodb.test.js +++ b/test/services/mongodb.test.js @@ -1,15 +1,94 @@ -var assert = require('assert'); +var chai = require('chai'); +var expect = chai.expect; var MongoService = require('../../lib/services/mongodb'); var Proto = require('uberproto'); +var service; +var _id; + +// TODO (EK): Mock out mongodb or something so that we +// can actually run these tests on CI describe('Mongo Service', function () { + beforeEach(function(done){ + service = Proto.create.call(MongoService, { + collection: 'test' + }); + service.create({ + // _id: '51d2325334244ade98000001', + name: 'Test 1' + }, function(error, data) { + _id = data[0]._id; + done(); + }); + }); + + afterEach(function(done){ + service.destroy(_id, function(err){ + done(); + }); + }); + + describe('init', function () { + it('should setup a mongo connection based on config'); + it('should setup a mongo connection based on ENV vars'); + it('should setup a mongo connection based on a connection string'); + }); + + describe('index', function () { + it('should return all items'); + it('should return all items sorted in ascending order'); + it('should return all items sorted in descending order'); + it('should return the number of items set by the limit'); + it('should skip over the number of items set by skip'); + }); + describe('get', function () { - it('should return an instance that exists'); + it('should return an instance that exists', function(done){ + service.get(_id, function(error, data) { + + expect(error).to.be.null; + expect(data._id.toString()).to.equal(_id.toString()); + expect(data.name).to.equal('Test 1'); + done(); + }); + }); + it('should return an error on db error'); }); describe('create', function () { - it('should create a new instance'); + it('should create a single new instance', function(done){ + service.create({ + name: 'Test 2' + }, function(error, data) { + expect(error).to.be.null; + expect(data).to.be.instanceof(Array); + expect(data).to.not.be.empty; + expect(data[0].name).to.equal('Test 2'); + done(); + }); + }); + + it('should create multiple new instances', function(done){ + var items = [ + { + name: 'Test 3' + }, + { + name: 'Test 4' + } + ]; + + service.create(items, function(error, data) { + expect(error).to.be.null; + expect(data).to.be.instanceof(Array); + expect(data).to.not.be.empty; + expect(data[0].name).to.equal('Test 3'); + expect(data[1].name).to.equal('Test 4'); + done(); + }); + }); + it('should return an error on db error'); }); @@ -22,17 +101,4 @@ describe('Mongo Service', function () { it('should delete an existing instance'); it('should return an error on db error'); }); - - describe('init', function () { - it('should setup a mongo connection based on config'); - it('should setup a mongo connection based on ENV vars'); - }); - - describe('index', function () { - it('should return all items'); - it('should return all items sorted in ascending order'); - it('should return all items sorted in descending order'); - it('should return the number of items set by the limit'); - it('should skip over the number of items set by skip'); - }); });