Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loopback 3.x #15

Merged
merged 6 commits into from Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
@@ -0,0 +1,2 @@
/coverage/
test/fixtures/simple-app
4 changes: 4 additions & 0 deletions .eslintrc
@@ -0,0 +1,4 @@
{
"extends": "fullcube",
"root": true
}
23 changes: 0 additions & 23 deletions .jscsrc

This file was deleted.

26 changes: 0 additions & 26 deletions .jshintrc

This file was deleted.

4 changes: 2 additions & 2 deletions README.md
@@ -1,7 +1,7 @@
READONLY
=============

[![Circle CI](https://circleci.com/gh/fullcube/loopback-ds-readonly-mixin.svg?style=svg)](https://circleci.com/gh/fullcube/loopback-ds-readonly-mixin) [![Coverage Status](https://coveralls.io/repos/fullcube/loopback-ds-readonly-mixin/badge.svg?branch=master&service=github)](https://coveralls.io/github/fullcube/loopback-ds-readonly-mixin?branch=master) [![Dependencies](http://img.shields.io/david/fullcube/loopback-ds-readonly-mixin.svg?style=flat)](https://david-dm.org/fullcube/loopback-ds-readonly-mixin)
[![Circle CI](https://circleci.com/gh/fullcube/loopback-ds-readonly-mixin.svg?style=svg)](https://circleci.com/gh/fullcube/loopback-ds-readonly-mixin) [![Coverage Status](https://coveralls.io/repos/fullcube/loopback-ds-readonly-mixin/badge.svg?branch=master&service=github)](https://coveralls.io/github/fullcube/loopback-ds-readonly-mixin?branch=master) [![Dependencies](http://img.shields.io/david/fullcube/loopback-ds-readonly-mixin.svg?style=flat)](https://david-dm.org/fullcube/loopback-ds-readonly-mixin) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)


This module is designed for the [Strongloop Loopback](https://github.com/strongloop/loopback) framework.
Expand Down Expand Up @@ -138,5 +138,5 @@ Run the tests in `test.js`
Run with debugging output on:

```bash
DEBUG='loopback-ds-readonly-mixin' npm test
DEBUG='loopback:mixin:readonly' npm test
```
8 changes: 8 additions & 0 deletions circle.yml
@@ -1,3 +1,11 @@
machine:
node:
version: 6.1.0
test:
post:
- npm run coverage
deployment:
master:
branch: master
commands:
- npm run semantic-release
14 changes: 7 additions & 7 deletions lib/index.js
@@ -1,10 +1,10 @@
var deprecate = require('depd')('loopback-ds-readonly-mixin');
var readOnly = require('./read-only');
const deprecate = require('depd')('loopback-ds-readonly-mixin')
const readOnly = require('./read-only')

module.exports = function mixin(app) {
'use strict';
'use strict'
app.loopback.modelBuilder.mixins.define = deprecate.function(app.loopback.modelBuilder.mixins.define,
'app.modelBuilder.mixins.define: Use mixinSources instead; ' +
'see https://github.com/fullcube/loopback-ds-readonly-mixin#mixinsources');
app.loopback.modelBuilder.mixins.define('ReadOnly', readOnly);
};
'app.modelBuilder.mixins.define: Use mixinSources instead ' +
'see https://github.com/fullcube/loopback-ds-readonly-mixin#mixinsources')
app.loopback.modelBuilder.mixins.define('ReadOnly', readOnly)
}
128 changes: 65 additions & 63 deletions lib/read-only.js
@@ -1,80 +1,82 @@
var debug = require('debug')('loopback-ds-readonly-mixin');
const debug = require('debug')('loopback:mixin:readonly')

module.exports = function(Model) {
'use strict';
module.exports = Model => {
'use strict'

debug('ReadOnly mixin for Model %s', Model.modelName);
debug('ReadOnly mixin for Model %s', Model.modelName)

var app;
Model.on('attached', () => {
Model.stripReadOnlyProperties = (modelName, ctx, next) => {
debug('stripReadOnlyProperties for model %s (via remote method %o)', modelName, ctx.methodString)
const body = ctx.req.body

Model.stripReadOnlyProperties = function(modelName, ctx, next) {
debug('stripReadOnlyProperties for model %s', modelName);
var body = ctx.req.body;
if (!body) {
return next();
}
if (!body) {
return next()
}

const AffectedModel = Model.app.loopback.getModel(modelName);
const options = AffectedModel.settings.mixins.ReadOnly;
const AffectedModel = Model.app.loopback.getModel(modelName)
const options = AffectedModel.settings.mixins.ReadOnly
const properties = (Object.keys(options).length) ? options : null

var properties = (Object.keys(options).length) ? options : null;
if (properties) {
debug('Found read only properties for model %s: %o', modelName, properties);
Object.keys(properties).forEach(function(key) {
debug('The \'%s\' property is read only, removing incoming data', key);
delete body[key];
});
next();
} else {
var err = new Error('Unable to update: ' + modelName + ' is read only.');
err.statusCode = 403;
next(err);
}
};
if (properties) {
debug('Found read only properties for model %s: %o', modelName, properties)
Object.keys(properties).forEach(key => {
debug('The \'%s\' property is read only, removing incoming data', key)
delete body[key]
})
return next()
}
const err = new Error(`Unable to update: ${modelName} is read only.`)

Model.on('attached', function(a) {
app = a;
err.statusCode = 403
return next(err)
}

// Handle native model methods.
Model.beforeRemote('create', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('upsert', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('replaceOrCreate', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('prototype.updateAttributes', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('prototype.patchAttributes', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('updateAll', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('upsertWithWhere', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('replaceById', function(ctx, modelInstance, next) {
Model.stripReadOnlyProperties(Model.modelName, ctx, next);
});
Model.beforeRemote('create', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('upsert', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('replaceOrCreate', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('patchOrCreate', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('prototype.updateAttributes', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('prototype.patchAttributes', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('updateAll', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('upsertWithWhere', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})
Model.beforeRemote('replaceById', (ctx, modelInstance, next) => {
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
})

// Handle updates via relationship.
Object.keys(Model.definition.settings.relations).forEach(relationName => {
var relation = Model.definition.settings.relations[relationName];
const relation = Model.definition.settings.relations[relationName]

if (relation.type.startsWith('has')) {
var modelName = relation.model;
var AffectedModel = Model.app.loopback.getModel(modelName);
Model.beforeRemote(`prototype.__updateById__${relationName}`, function(ctx, modelInstance, next) {
const modelName = relation.model
const AffectedModel = Model.app.loopback.getModel(modelName)

Model.beforeRemote(`prototype.__updateById__${relationName}`, (ctx, modelInstance, next) => {
if (typeof AffectedModel.stripReadOnlyProperties === 'function') {
return AffectedModel.stripReadOnlyProperties(modelName, ctx, next);
return AffectedModel.stripReadOnlyProperties(modelName, ctx, next)
}
return next();
});
return next()
})
}
});
})

});
};
})
}
40 changes: 22 additions & 18 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "loopback-ds-readonly-mixin",
"description": "A mixin to enable loopback Model properties to be marked as readonly.",
"version": "1.1.0",
"version": "0.0.0-development",
"main": "./lib/index.js",
"author": "Tom Kirkpatrick @mrfelton",
"contributors": [
Expand All @@ -27,32 +27,36 @@
"test"
],
"scripts": {
"lint": "jscs lib && jshint lib",
"test": "nyc mocha -R spec --timeout 10000 test",
"test:watch": "npm run test -- -w",
"dev": "DEBUG=loopback:mixin:readonly NODE_ENV=development nodemon test/fixtures/simple-app/server/server.js --watch ./lib --watch ./test --ignore db.json --ext js,json",
"lint": "eslint .",
"pretest": "npm run lint",
"test": "NODE_ENV=test nyc --reporter=lcov --reporter=text --reporter=text-summary mocha test/*test.js",
"test:watch": "npm run test -- -w",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"outdated": "npm outdated --depth=0"
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"dependencies": {
"debug": "^2.6.3",
"debug": "^2.6.4",
"depd": "^1.1.0"
},
"peerDependencies": {
"loopback-datasource-juggler": "^2.18.1"
},
"devDependencies": {
"bluebird": "latest",
"chai": "latest",
"coveralls": "latest",
"jscs": "latest",
"jshint": "latest",
"loopback": "^2.22.0",
"chai": "^3.5.0",
"condition-circle": "^1.5.0",
"coveralls": "^2.13.0",
"dirty-chai": "^1.2.2",
"eslint": "^2.11.1",
"eslint-config-fullcube": "^1.0.46",
"loopback": "^3.6.0",
"loopback-boot": "^2.24.0",
"loopback-component-explorer": "^2.1.1",
"loopback-component-explorer": "^4.2.0",
"loopback-testing": "^1.4.0",
"mocha": "latest",
"nyc": "latest",
"mocha": "^3.3.0",
"nodemon": "^1.11.0",
"nyc": "^10.2.0",
"semantic-release": "^6.3.2",
"supertest": "^3.0.0"
},
"release": {
"verifyConditions": "condition-circle"
}
}
3 changes: 3 additions & 0 deletions test/.eslintrc
@@ -0,0 +1,3 @@
{
"extends": "fullcube/mocha"
}
1 change: 1 addition & 0 deletions test/fixtures/simple-app/common/models/product.json
Expand Up @@ -2,6 +2,7 @@
"name": "Product",
"base": "PersistedModel",
"idInjection": true,
"forceId": false,
"options": {
"validateUpsert": true
},
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/simple-app/server/middleware.json
@@ -1,4 +1,5 @@
{
"initial:before": {},
"initial": {},
"session": {},
"auth": {},
Expand Down