Skip to content

Commit

Permalink
Introducing a READONLY flag, that disables the CUD in CRUD
Browse files Browse the repository at this point in the history
  • Loading branch information
ecaron committed Dec 24, 2016
1 parent 5417240 commit 99fa092
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 24 deletions.
7 changes: 7 additions & 0 deletions README.md
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion config/default.json
Expand Up @@ -2,5 +2,6 @@
"host": "localhost",
"name": "Best Buy API Playground",
"port": 3030,
"public": "../public/"
"public": "../public/",
"readonly": false
}
13 changes: 8 additions & 5 deletions 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
Expand Down Expand Up @@ -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();
};
6 changes: 4 additions & 2 deletions 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) {
Expand Down
8 changes: 4 additions & 4 deletions src/services/categories/hooks/index.js
Expand Up @@ -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 = {
Expand Down
8 changes: 4 additions & 4 deletions src/services/products/hooks/index.js
Expand Up @@ -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 = {
Expand Down
8 changes: 4 additions & 4 deletions src/services/services/hooks/index.js
Expand Up @@ -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 = {
Expand Down
8 changes: 4 additions & 4 deletions src/services/stores/hooks/index.js
Expand Up @@ -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 = {
Expand Down
1 change: 1 addition & 0 deletions src/services/utilities/index.js
Expand Up @@ -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],
Expand Down
16 changes: 16 additions & 0 deletions test/services/categories/create.test.js
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions test/services/products/create.test.js
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions test/services/services/create.test.js
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions test/services/stores/create.test.js
Expand Up @@ -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)
Expand Down

0 comments on commit 99fa092

Please sign in to comment.