diff --git a/README.md b/README.md index 8b9966a..a369faf 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,13 @@ npm start Now open http://localhost:3030 in your browser to begin exploring the API. From there we'll guide you on using tools such as Swagger and Postman to get meaningful experience interacting with APIs. +## Configuration Options + +Configuration settings are managed using [Feathers Configuration](https://docs.feathersjs.com/configuration/readme.html). The options +that you may want to adjust, depending on your usage, are: +* `port` - HTTP port where the API is listening. Defaults to 3030. +* `readonly` - If true, database cannot be modified (i.e. create, update, patch & remove operations are disabled). Defaults to false. + ## Things That Power the Playground Beyond all the great libraries (which are mentioned within the [package.json](package.json), such as [Sequelize](http://sequelizejs.com/)), here are some of the crucial components and resources that made assembling with API Playground possible: diff --git a/config/default.json b/config/default.json index a94c731..3afa076 100644 --- a/config/default.json +++ b/config/default.json @@ -2,5 +2,6 @@ "host": "localhost", "name": "Best Buy API Playground", "port": 3030, - "public": "../public/" + "public": "../public/", + "readonly": false } diff --git a/src/hooks/index.js b/src/hooks/index.js index 8d987f0..eb32192 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,10 +1,6 @@ 'use strict'; -// Add any common hooks you want to share across services in here. -// -// Below is an example of how a hook is written and exported. Please -// see http://docs.feathersjs.com/hooks/readme.html for more details -// on hooks. +const errors = require('feathers-errors'); exports.allowNull = function (options) { // convert all strings that are "null" to null @@ -36,3 +32,10 @@ exports.wildcardsInLike = function (options) { }); }; }; + +exports.errorIfReadonly = function (hook, next) { + if (hook.app.get('readonly')) { + return next(new errors.MethodNotAllowed('This HTTP method is not allowed when application is in read-only mode.')); + } + return next(); +}; diff --git a/src/hooks/validate-schema.js b/src/hooks/validate-schema.js index 213813f..3e21765 100644 --- a/src/hooks/validate-schema.js +++ b/src/hooks/validate-schema.js @@ -1,5 +1,7 @@ -var Ajv = require('ajv'); -var errors = require('feathers-errors'); +'use strict'; + +const Ajv = require('ajv'); +const errors = require('feathers-errors'); function formatErrorMessage (err) { if (err.dataPath) { diff --git a/src/services/categories/hooks/index.js b/src/services/categories/hooks/index.js index d19f1d9..017f285 100644 --- a/src/services/categories/hooks/index.js +++ b/src/services/categories/hooks/index.js @@ -8,10 +8,10 @@ exports.before = { all: [], find: [includeAssociatedModels, globalHooks.allowNull(), globalHooks.wildcardsInLike()], get: [includeAssociatedModels], - create: [validateSchema(categorySchema)], - update: [validateSchema(categorySchema)], - patch: [], - remove: [] + create: [globalHooks.errorIfReadonly, validateSchema(categorySchema)], + update: [globalHooks.errorIfReadonly, validateSchema(categorySchema)], + patch: [globalHooks.errorIfReadonly], + remove: [globalHooks.errorIfReadonly] }; exports.after = { diff --git a/src/services/products/hooks/index.js b/src/services/products/hooks/index.js index 8c50842..fac5329 100644 --- a/src/services/products/hooks/index.js +++ b/src/services/products/hooks/index.js @@ -8,10 +8,10 @@ exports.before = { all: [], find: [includeAssociatedModels, findbyCategoryName, findCategoryById, globalHooks.allowNull(), globalHooks.wildcardsInLike()], get: [includeAssociatedModels], - create: [validateSchema(productSchema)], - update: [validateSchema(productSchema)], - patch: [], - remove: [] + create: [globalHooks.errorIfReadonly, validateSchema(productSchema)], + update: [globalHooks.errorIfReadonly, validateSchema(productSchema)], + patch: [globalHooks.errorIfReadonly], + remove: [globalHooks.errorIfReadonly] }; exports.after = { diff --git a/src/services/services/hooks/index.js b/src/services/services/hooks/index.js index 0d91907..b16cb78 100644 --- a/src/services/services/hooks/index.js +++ b/src/services/services/hooks/index.js @@ -8,10 +8,10 @@ exports.before = { all: [], find: [globalHooks.allowNull(), globalHooks.wildcardsInLike()], get: [], - create: [validateSchema(serviceSchema)], - update: [validateSchema(serviceSchema)], - patch: [], - remove: [] + create: [globalHooks.errorIfReadonly, validateSchema(serviceSchema)], + update: [globalHooks.errorIfReadonly, validateSchema(serviceSchema)], + patch: [globalHooks.errorIfReadonly], + remove: [globalHooks.errorIfReadonly] }; exports.after = { diff --git a/src/services/stores/hooks/index.js b/src/services/stores/hooks/index.js index f9a95b0..d0f1d1a 100644 --- a/src/services/stores/hooks/index.js +++ b/src/services/stores/hooks/index.js @@ -9,10 +9,10 @@ exports.before = { all: [], find: [findNearby, includeAssociatedModels, findServiceByName, findServiceById, globalHooks.allowNull(), globalHooks.wildcardsInLike()], get: [includeAssociatedModels], - create: [validateSchema(storeSchema)], - update: [validateSchema(storeSchema)], - patch: [], - remove: [] + create: [globalHooks.errorIfReadonly, validateSchema(storeSchema)], + update: [globalHooks.errorIfReadonly, validateSchema(storeSchema)], + patch: [globalHooks.errorIfReadonly], + remove: [globalHooks.errorIfReadonly] }; exports.after = { diff --git a/src/services/utilities/index.js b/src/services/utilities/index.js index 858afe8..daebb05 100644 --- a/src/services/utilities/index.js +++ b/src/services/utilities/index.js @@ -17,6 +17,7 @@ module.exports = function () { ]).then(dbValues => { res.send({ uptime: process.uptime(), + readonly: app.get('readonly'), documents: { products: dbValues[0], stores: dbValues[1], diff --git a/test/services/categories/create.test.js b/test/services/categories/create.test.js index 66f7693..4dc20d2 100644 --- a/test/services/categories/create.test.js +++ b/test/services/categories/create.test.js @@ -6,10 +6,26 @@ var request = require('supertest'); var categorySchema = require('../../../src/services/categories/schema'); describe('categories service', function () { + beforeEach(function (done) { + app.set('readonly', false); + done(); + }); + it('registered the categories service', () => { assert.ok(app.service('categories')); }); + it('denies write attempts when in readonly mode', function (done) { + app.set('readonly', true); + request(app) + .post('/categories') + .expect(405) + .end(function (err, result) { + if (err) { assert.ifError(err); } + done(); + }); + }); + it('honors required fields when validating', function (done) { // Post a blank body so we get all the required errors request(app) diff --git a/test/services/products/create.test.js b/test/services/products/create.test.js index 4369e15..7f0760b 100644 --- a/test/services/products/create.test.js +++ b/test/services/products/create.test.js @@ -6,10 +6,26 @@ var request = require('supertest'); var productSchema = require('../../../src/services/products/schema'); describe('products service', function () { + beforeEach(function (done) { + app.set('readonly', false); + done(); + }); + it('registered the products service', () => { assert.ok(app.service('products')); }); + it('denies write attempts when in readonly mode', function (done) { + app.set('readonly', true); + request(app) + .post('/products') + .expect(405) + .end(function (err, result) { + if (err) { assert.ifError(err); } + done(); + }); + }); + it('honors required fields when validating', function (done) { // Post a blank body so we get all the required errors request(app) diff --git a/test/services/services/create.test.js b/test/services/services/create.test.js index 45deb47..fc3e34f 100644 --- a/test/services/services/create.test.js +++ b/test/services/services/create.test.js @@ -6,10 +6,26 @@ var request = require('supertest'); var servicesSchema = require('../../../src/services/services/schema'); describe('services service', function () { + beforeEach(function (done) { + app.set('readonly', false); + done(); + }); + it('registered the services service', () => { assert.ok(app.service('services')); }); + it('denies write attempts when in readonly mode', function (done) { + app.set('readonly', true); + request(app) + .post('/services') + .expect(405) + .end(function (err, result) { + if (err) { assert.ifError(err); } + done(); + }); + }); + it('honors required fields when validating', function (done) { // Post a blank body so we get all the required errors request(app) diff --git a/test/services/stores/create.test.js b/test/services/stores/create.test.js index d80f26d..4cf87cd 100644 --- a/test/services/stores/create.test.js +++ b/test/services/stores/create.test.js @@ -6,10 +6,26 @@ var request = require('supertest'); var storesSchema = require('../../../src/services/stores/schema'); describe('stores service', function () { + beforeEach(function (done) { + app.set('readonly', false); + done(); + }); + it('registered the stores service', () => { assert.ok(app.service('stores')); }); + it('denies write attempts when in readonly mode', function (done) { + app.set('readonly', true); + request(app) + .post('/stores') + .expect(405) + .end(function (err, result) { + if (err) { assert.ifError(err); } + done(); + }); + }); + it('honors required fields when validating', function (done) { // Post a blank body so we get all the required errors request(app)