mirror of
https://github.com/strapi/strapi.git
synced 2025-07-16 13:32:05 +00:00
Merge branch 'v2' of github.com:wistityhq/strapi into v2
This commit is contained in:
commit
c45ea07a27
@ -1,4 +1,10 @@
|
||||
# Strapi [](https://travis-ci.org/wistityhq/strapi) [](http://slack.strapi.io)
|
||||
# Strapi
|
||||
|
||||
[](https://www.npmjs.org/package/strapi)
|
||||
[](https://www.npmjs.org/package/strapi)
|
||||
[](https://david-dm.org/wistityhq/strapi)
|
||||
[](https://travis-ci.org/wistityhq/strapi)
|
||||
[](http://slack.strapi.io)
|
||||
|
||||
[Website](http://strapi.io/) - [Getting Started](#user-content-getting-started-in-a-minute) - [Documentation](http://strapi.io/documentation/introduction) - [Support](http://strapi.io/support)
|
||||
|
||||
@ -55,7 +61,6 @@ This will generate a Strapi application without:
|
||||
- the built-in `user`, `email` and `upload` APIs,
|
||||
- the `grant` hook,
|
||||
- the open-source admin panel,
|
||||
- the Waterline ORM (`waterline` and `blueprints` hooks disabled),
|
||||
- the Strapi Studio connection (`studio` hook disabled).
|
||||
|
||||
This feature allows you to only use Strapi for your HTTP server structure if you want to.
|
||||
|
@ -39,6 +39,10 @@ cmd = program.command('version');
|
||||
cmd.description('output your version of Strapi');
|
||||
cmd.action(program.versionInformation);
|
||||
|
||||
/**
|
||||
* Basic commands
|
||||
*/
|
||||
|
||||
// `$ strapi new <name>`
|
||||
cmd = program.command('new');
|
||||
cmd.unknownOption = NOOP;
|
||||
@ -64,24 +68,16 @@ cmd.unknownOption = NOOP;
|
||||
cmd.description('open the Strapi framework console');
|
||||
cmd.action(require('./strapi-console'));
|
||||
|
||||
/**
|
||||
* Commands for the Strapi Studio
|
||||
*/
|
||||
|
||||
// `$ strapi link`
|
||||
cmd = program.command('link');
|
||||
cmd.unknownOption = NOOP;
|
||||
cmd.description('link an existing application to the Strapi Studio');
|
||||
cmd.action(require('./strapi-link'));
|
||||
|
||||
// `$ strapi config`
|
||||
cmd = program.command('config');
|
||||
cmd.unknownOption = NOOP;
|
||||
cmd.description('extend the Strapi framework with custom generators');
|
||||
cmd.action(require('./strapi-config'));
|
||||
|
||||
// `$ strapi update`
|
||||
cmd = program.command('update');
|
||||
cmd.unknownOption = NOOP;
|
||||
cmd.description('pull the latest updates of your custom generators');
|
||||
cmd.action(require('./strapi-update'));
|
||||
|
||||
// `$ strapi login`
|
||||
cmd = program.command('login');
|
||||
cmd.unknownOption = NOOP;
|
||||
@ -94,6 +90,22 @@ cmd.unknownOption = NOOP;
|
||||
cmd.description('logout your account from the Strapi Studio');
|
||||
cmd.action(require('./strapi-logout'));
|
||||
|
||||
/**
|
||||
* Customization commands
|
||||
*/
|
||||
|
||||
// `$ strapi config`
|
||||
cmd = program.command('config');
|
||||
cmd.unknownOption = NOOP;
|
||||
cmd.description('extend the Strapi framework with custom generators');
|
||||
cmd.action(require('./strapi-config'));
|
||||
|
||||
// `$ strapi update`
|
||||
cmd = program.command('update');
|
||||
cmd.unknownOption = NOOP;
|
||||
cmd.description('pull the latest updates of your custom generators');
|
||||
cmd.action(require('./strapi-update'));
|
||||
|
||||
/**
|
||||
* Normalize help argument
|
||||
*/
|
||||
|
@ -1,240 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Populate the query according to the specified or default
|
||||
* association attributes.
|
||||
*
|
||||
* @param {Object} query
|
||||
* @param {Object} _ctx
|
||||
* @param {Object} model
|
||||
*
|
||||
* @return {Object} populated query
|
||||
*/
|
||||
|
||||
populateEach: function (query, _ctx, model) {
|
||||
let shouldPopulate = strapi.config.blueprints.populate;
|
||||
let aliasFilter = (_ctx.request.query && _ctx.request.query.populate) || (_ctx.request.body && _ctx.request.body.populate);
|
||||
|
||||
// Convert the string representation of the filter list to an array. We
|
||||
// need this to provide flexibility in the request param. This way both
|
||||
// list string representations are supported:
|
||||
// /model?populate=alias1,alias2,alias3
|
||||
// /model?populate=[alias1,alias2,alias3]
|
||||
if (typeof aliasFilter === 'string') {
|
||||
aliasFilter = aliasFilter.replace(/\[|\]/g, '');
|
||||
aliasFilter = (aliasFilter) ? aliasFilter.split(',') : [];
|
||||
}
|
||||
|
||||
return _(model.associations).reduce(function populateEachAssociation(query, association) {
|
||||
|
||||
// If an alias filter was provided, override the blueprint config.
|
||||
if (aliasFilter) {
|
||||
shouldPopulate = _.contains(aliasFilter, association.alias);
|
||||
}
|
||||
|
||||
// Populate associations and set the according limit.
|
||||
if (shouldPopulate) {
|
||||
return query.populate(association.alias, {
|
||||
limit: strapi.config.blueprints.defaultLimit || 30
|
||||
});
|
||||
} else {
|
||||
return query;
|
||||
}
|
||||
}, query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the model to use
|
||||
*
|
||||
* @param {_ctx} _ctx
|
||||
*
|
||||
* @return {WLCollection}
|
||||
*/
|
||||
|
||||
parseModel: function (_ctx) {
|
||||
|
||||
// Determine the model according to the context.
|
||||
const model = _ctx.model || _ctx.params.model;
|
||||
|
||||
if (!model) {
|
||||
throw new Error({
|
||||
message: 'Please provide a valid model.'
|
||||
});
|
||||
}
|
||||
|
||||
// Select the Waterline model.
|
||||
const Model = strapi.orm.collections[model];
|
||||
if (!Model) {
|
||||
throw new Error({
|
||||
message: 'Invalid Model.'
|
||||
});
|
||||
}
|
||||
|
||||
Model.name = model;
|
||||
|
||||
return Model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse `values` for a Waterline `create` or `update` from all
|
||||
* request parameters.
|
||||
*
|
||||
* @param {Request} _ctx
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
parseValues: function (_ctx) {
|
||||
const values = _ctx.request.body || _ctx.request.query;
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse `criteria` for a Waterline `find` or `update` from all
|
||||
* request parameters.
|
||||
*
|
||||
* @param {Request} _ctx
|
||||
*
|
||||
* @return {Object} the where criteria object
|
||||
*/
|
||||
|
||||
parseCriteria: function (_ctx) {
|
||||
|
||||
// List of properties to remove.
|
||||
const blacklist = ['limit', 'skip', 'sort', 'populate'];
|
||||
|
||||
// Validate blacklist to provide a more helpful error msg.
|
||||
if (blacklist && !_.isArray(blacklist)) {
|
||||
throw new Error('Invalid `_ctx.options.criteria.blacklist`. Should be an array of strings (parameter names.)');
|
||||
}
|
||||
|
||||
// Look for explicitly specified `where` parameter.
|
||||
let where = _ctx.request.query.where;
|
||||
|
||||
// If `where` parameter is a string, try to interpret it as JSON.
|
||||
if (_.isString(where)) {
|
||||
try {
|
||||
where = JSON.parse(where);
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If `where` has not been specified, but other unbound parameter variables
|
||||
// are specified, build the `where` option using them.
|
||||
if (!where) {
|
||||
|
||||
// Prune params which aren't fit to be used as `where` criteria
|
||||
// to build a proper where query.
|
||||
where = _ctx.request.body;
|
||||
|
||||
// Omit built-in runtime config (like query modifiers).
|
||||
where = _.omit(where, blacklist || ['limit', 'skip', 'sort']);
|
||||
|
||||
// Omit any params with undefined values.
|
||||
where = _.omit(where, function (p) {
|
||||
if (_.isUndefined(p)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Merge with `_ctx.options.where` and return.
|
||||
where = _.merge({}, where) || undefined;
|
||||
|
||||
return where;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse primary key value
|
||||
*
|
||||
* @param {Object} _ctx
|
||||
*
|
||||
* @return {Integer|String} pk
|
||||
*/
|
||||
|
||||
parsePk: function (_ctx) {
|
||||
let pk = (_ctx.request.body && _ctx.request.body.where && _ctx.request.body.where.id) || _ctx.params.id;
|
||||
|
||||
// Exclude criteria on id field.
|
||||
pk = _.isPlainObject(pk) ? undefined : pk;
|
||||
return pk;
|
||||
},
|
||||
|
||||
requirePk: function (_ctx) {
|
||||
const pk = module.exports.parsePk(_ctx);
|
||||
|
||||
// Validate the required `id` parameter.
|
||||
if (!pk) {
|
||||
const err = new Error({
|
||||
message: 'No `id` provided'
|
||||
});
|
||||
_ctx.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return pk;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse sort params.
|
||||
*
|
||||
* @param {Object} _ctx
|
||||
*/
|
||||
|
||||
parseSort: function (_ctx) {
|
||||
_ctx.options = _ctx.options || {};
|
||||
let sort = _ctx.request.query.sort || _ctx.options.sort;
|
||||
if (typeof sort === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof sort === 'string') {
|
||||
try {
|
||||
sort = JSON.parse(sort);
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
return sort;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse limit params.
|
||||
*
|
||||
* @param {Object} _ctx
|
||||
*/
|
||||
|
||||
parseLimit: function (_ctx) {
|
||||
_ctx.options = _ctx.options || {};
|
||||
let limit = Number(_ctx.request.query.limit) || strapi.config.blueprints.defaultLimit || 30;
|
||||
if (limit) {
|
||||
limit = +limit;
|
||||
}
|
||||
return limit;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse skip params.
|
||||
*
|
||||
* @param {Object} _ctx
|
||||
*/
|
||||
|
||||
parseSkip: function (_ctx) {
|
||||
_ctx.options = _ctx.options || {};
|
||||
let skip = _ctx.request.query.skip || 0;
|
||||
if (skip) {
|
||||
skip = +skip;
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
};
|
@ -1,184 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
|
||||
/**
|
||||
* Add an entry to a specific parent entry
|
||||
*/
|
||||
|
||||
module.exports = function destroy(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Ensure a model and alias can be deduced from the request.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
const relation = _ctx.params.relation;
|
||||
if (!relation) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject({
|
||||
message: 'Missing required route option, `_ctx.params.relation`.'
|
||||
});
|
||||
}
|
||||
|
||||
// The primary key of the parent record.
|
||||
const parentPk = _ctx.params.parentId;
|
||||
|
||||
// Find the alias key.
|
||||
const associationAttr = _.findWhere(strapi.orm.collections[_ctx.model].associations, {alias: relation});
|
||||
|
||||
// Init the child model.
|
||||
const ChildModel = strapi.orm.collections[associationAttr.collection];
|
||||
const childPkAttr = ChildModel.primaryKey;
|
||||
|
||||
_ctx.options = _ctx.options || {};
|
||||
|
||||
// The child record to associate is defined by either...
|
||||
// a primary key or an object of values.
|
||||
let child;
|
||||
const supposedChildPk = actionUtil.parsePk(_ctx);
|
||||
if (supposedChildPk) {
|
||||
child = {};
|
||||
child[childPkAttr] = supposedChildPk;
|
||||
} else {
|
||||
_ctx.options.values = _ctx.options.values || {};
|
||||
_ctx.options.values.blacklist = _ctx.options.values.blacklist || ['limit', 'skip', 'sort', 'id', 'parentId'];
|
||||
child = actionUtil.parseValues(_ctx);
|
||||
}
|
||||
|
||||
if (!child) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject({
|
||||
message: 'You must specify the record to add (either the primary key of an existing record to link, or a new object without a primary key which will be used to create a record then link it.)'
|
||||
});
|
||||
}
|
||||
|
||||
async.auto({
|
||||
|
||||
// Look up the parent record.
|
||||
parent: function (cb) {
|
||||
Model.findOne(parentPk).exec(function foundParent(err, parentRecord) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
if (!parentRecord) {
|
||||
return cb({status: 404});
|
||||
}
|
||||
if (!parentRecord[relation]) {
|
||||
return cb({status: 404});
|
||||
}
|
||||
cb(null, parentRecord);
|
||||
});
|
||||
},
|
||||
|
||||
// If a primary key was specified in the `child` object we parsed
|
||||
// from the request, look it up to make sure it exists. Send back its primary key value.
|
||||
// This is here because, although you can do this with `.save()`, you can't actually
|
||||
// get ahold of the created child record data, unless you create it first.
|
||||
actualChildPkValue: ['parent', function (cb) {
|
||||
|
||||
// Below, we use the primary key attribute to pull out the primary key value
|
||||
// (which might not have existed until now, if the .add() resulted in a `create()`).
|
||||
// If the primary key was specified for the child record, we should try to find
|
||||
// it before we create it.
|
||||
// Otherwise, it must be referring to a new thing, so create it.
|
||||
if (child[childPkAttr]) {
|
||||
ChildModel.findOne(child[childPkAttr]).exec(function foundChild(err, childRecord) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// Didn't find it? Then try creating it.
|
||||
if (!childRecord) {
|
||||
return createChild();
|
||||
}
|
||||
|
||||
// Otherwise use the one we found.
|
||||
return cb(null, childRecord[childPkAttr]);
|
||||
});
|
||||
} else {
|
||||
return createChild();
|
||||
}
|
||||
|
||||
// Create a new instance and send out any required pub/sub messages.
|
||||
function createChild() {
|
||||
ChildModel.create(child).exec(function createdNewChild(err, newChildRecord) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return cb(null, newChildRecord[childPkAttr]);
|
||||
});
|
||||
}
|
||||
}],
|
||||
|
||||
// Add the child record to the parent's collection.
|
||||
add: ['parent', 'actualChildPkValue', function (cb, asyncData) {
|
||||
|
||||
// `collection` is the parent record's collection we
|
||||
// want to add the child to.
|
||||
try {
|
||||
const collection = asyncData.parent[relation];
|
||||
collection.add(asyncData.actualChildPkValue);
|
||||
return cb();
|
||||
} catch (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return cb();
|
||||
}
|
||||
}]
|
||||
},
|
||||
|
||||
// Save the parent record.
|
||||
function readyToSave(err, asyncData) {
|
||||
if (err) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
asyncData.parent.save(function saved(err) {
|
||||
|
||||
// Ignore `insert` errors for duplicate adds
|
||||
// (but keep in mind, we should not `publishAdd` if this is the case...)
|
||||
const isDuplicateInsertError = (err && typeof err === 'object' && err.length && err[0] && err[0].type === 'insert');
|
||||
if (err && !isDuplicateInsertError) {
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
// Finally, look up the parent record again and populate the relevant collection.
|
||||
let query = Model.findOne(parentPk);
|
||||
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
query.populate(relation);
|
||||
|
||||
query.exec(function (err, matchingRecord) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
if (!matchingRecord) {
|
||||
return deferred.reject({
|
||||
message: 'Matching record not found.'
|
||||
});
|
||||
}
|
||||
if (!matchingRecord[relation]) {
|
||||
return deferred.reject({
|
||||
message: '`matchingRecord[relation]` not found.'
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.resolve(matchingRecord);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
const associationUtil = require('../associationUtil');
|
||||
|
||||
/**
|
||||
* Create an entry
|
||||
*/
|
||||
|
||||
module.exports = function create(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Return the model used.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
|
||||
// Parse the values of the record to create.
|
||||
const values = actionUtil.parseValues(_ctx);
|
||||
|
||||
// Associations validation.
|
||||
const associationsValidationPromises = [];
|
||||
|
||||
// Check if the relations are existing for `OneWay` associations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneWay'}), function (association) {
|
||||
if (values[association.alias] || association.required) {
|
||||
associationsValidationPromises.push(associationUtil.doesRecordExist(association.model, values[association.alias]));
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the relations are existing for `OneToOne` associations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToOne'}), function (association) {
|
||||
if (values[association.alias] || association.required) {
|
||||
associationsValidationPromises.push(associationUtil.doesRecordExist(association.model, values[association.alias]));
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(associationsValidationPromises)
|
||||
.then(function () {
|
||||
Model.create(values).exec(function created(err, newInstance) {
|
||||
if (err) {
|
||||
_ctx.status = 400;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Update `oneToOneRelations`.
|
||||
const relationPromises = [];
|
||||
|
||||
// Update the `oneToOne` relations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToOne'}), function (relation) {
|
||||
relationPromises.push(associationUtil.oneToOneRelationUpdated(_ctx.model || _ctx.params.model, newInstance.id, relation.model, newInstance[relation.alias]));
|
||||
});
|
||||
|
||||
Promise.all(relationPromises)
|
||||
|
||||
// Related records updated.
|
||||
.then(function () {
|
||||
let query = Model.findOne(newInstance[Model.primaryKey]);
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
query.exec(function foundAgain(err, populatedRecord) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Entry created.
|
||||
_ctx.status = 201;
|
||||
deferred.resolve(populatedRecord);
|
||||
});
|
||||
})
|
||||
|
||||
// Error during related records update.
|
||||
.catch(function (err) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// Error during related records check.
|
||||
.catch(function (err) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,80 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
const associationUtil = require('../associationUtil');
|
||||
|
||||
/**
|
||||
* Destroy an entry
|
||||
*/
|
||||
|
||||
module.exports = function destroy(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Return the model used.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
|
||||
// Locate and validate the required `id` parameter.
|
||||
const pk = actionUtil.requirePk(_ctx);
|
||||
|
||||
// First, check if the record exists.
|
||||
const query = Model.findOne(pk);
|
||||
query.exec(function foundRecord(err, record) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
// Record not found.
|
||||
if (!record) {
|
||||
_ctx.status = 404;
|
||||
deferred.reject({
|
||||
message: 'No record found with the specified `id`.'
|
||||
});
|
||||
}
|
||||
|
||||
// Destroy the record.
|
||||
Model.destroy(pk).exec(function destroyedRecord(err, deletedRecords) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Select the first object of the updated records.
|
||||
const deletedRecord = deletedRecords[0];
|
||||
|
||||
// Update the `oneToOne` relations.
|
||||
const relationPromises = [];
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToOne'}), function (relation) {
|
||||
relationPromises.push(associationUtil.removeRelationsOut(_ctx.model || _ctx.params.model, deletedRecord.id, relation.model));
|
||||
});
|
||||
|
||||
// Update the `oneToMany` relations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToMany'}), function (relation) {
|
||||
relationPromises.push(associationUtil.removeRelationsOut(_ctx.model || _ctx.params.model, deletedRecord.id, relation.collection));
|
||||
});
|
||||
|
||||
Promise.all(relationPromises)
|
||||
|
||||
// Related records updated.
|
||||
.then(function () {
|
||||
deferred.resolve(deletedRecord);
|
||||
})
|
||||
|
||||
// Error during related records update.
|
||||
.catch(function (err) {
|
||||
_ctx.status = 500;
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,44 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
|
||||
/**
|
||||
* Find entries
|
||||
*/
|
||||
|
||||
module.exports = function find(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Use the `findOne` action if an `id` is specified.
|
||||
if (actionUtil.parsePk(_ctx)) {
|
||||
return require('./findOne')(_ctx);
|
||||
}
|
||||
|
||||
// Look up the model.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
|
||||
// Init the query.
|
||||
let query = Model.find()
|
||||
.where(actionUtil.parseCriteria(_ctx))
|
||||
.limit(actionUtil.parseLimit(_ctx))
|
||||
.skip(actionUtil.parseSkip(_ctx))
|
||||
.sort(actionUtil.parseSort(_ctx));
|
||||
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
query.exec(function found(err, matchingRecords) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
// Records found.
|
||||
deferred.resolve(matchingRecords);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,44 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
|
||||
/**
|
||||
* Find a specific entry
|
||||
*/
|
||||
|
||||
module.exports = function destroy(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Return the model used.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
|
||||
// Locate and validate the required `id` parameter.
|
||||
const pk = actionUtil.requirePk(_ctx);
|
||||
|
||||
// Init the query.
|
||||
let query = Model.findOne(pk);
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
query.exec(function found(err, matchingRecord) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
deferred.reject(err);
|
||||
}
|
||||
if (!matchingRecord) {
|
||||
_ctx.status = 404;
|
||||
|
||||
return deferred.reject({
|
||||
message: 'No ' + Model.name + ' found with the specified `id`.'
|
||||
});
|
||||
}
|
||||
|
||||
// Record found.
|
||||
deferred.resolve(matchingRecord);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,109 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
const associationUtil = require('../associationUtil');
|
||||
|
||||
/**
|
||||
* Remove an entry to a specific parent entry
|
||||
*/
|
||||
|
||||
module.exports = function remove(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Ensure a model and alias can be deduced from the request.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
_ctx.options = _ctx.options || {};
|
||||
const relation = _ctx.params.relation;
|
||||
const associationAttr = _.findWhere(strapi.orm.collections[_ctx.model].associations, {alias: relation});
|
||||
|
||||
if (!associationAttr) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject({
|
||||
message: 'Missing required route option, `_ctx.options.alias`.'
|
||||
});
|
||||
}
|
||||
|
||||
// The primary key of the parent record.
|
||||
const parentPk = _ctx.params.parentId;
|
||||
|
||||
// The primary key of the child record to remove
|
||||
// from the aliased collection.
|
||||
let childPk = actionUtil.parsePk(_ctx);
|
||||
|
||||
// Check if the `childPk` is defined.
|
||||
if (_.isUndefined(childPk)) {
|
||||
_ctx.status = 400;
|
||||
return deferred.reject({
|
||||
message: 'Missing required child PK.'
|
||||
});
|
||||
}
|
||||
|
||||
// Find the parent object.
|
||||
Model.findOne(parentPk)
|
||||
.populate(relation)
|
||||
.exec(function found(err, parentRecord) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Format `childPk` for the `findWhere` used next.
|
||||
childPk = isNaN(childPk) ? childPk : Number(childPk);
|
||||
|
||||
if (!parentRecord || !parentRecord[relation] || (!_.findWhere(parentRecord[relation], {id: childPk})) && parentRecord[relation].id !== childPk) {
|
||||
_ctx.status = 404;
|
||||
return deferred.reject({
|
||||
message: 'Not found'
|
||||
});
|
||||
}
|
||||
|
||||
const relationPromises = [];
|
||||
|
||||
if (parentRecord[relation].id === childPk) {
|
||||
|
||||
// Set to null
|
||||
parentRecord[relation] = null;
|
||||
relationPromises.push(associationUtil.removeRelationsOut(_ctx.model || _ctx.params.model, parentRecord.id, relation));
|
||||
} else if (_.findWhere(parentRecord[relation], {id: childPk})) {
|
||||
|
||||
// Remove.
|
||||
parentRecord[relation].remove(childPk);
|
||||
}
|
||||
|
||||
// Save.
|
||||
parentRecord.save(function (err) {
|
||||
if (err) {
|
||||
_ctx.status = 400;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
Promise.all(relationPromises)
|
||||
.then(function () {
|
||||
|
||||
// New query to `findOne` and properly populate it.
|
||||
let query = Model.findOne(parentPk);
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
query.exec(function found(err, parentRecord) {
|
||||
if (err || !parentRecord) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
return deferred.resolve(parentRecord);
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,119 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Local utils.
|
||||
const actionUtil = require('../actionUtil');
|
||||
const associationUtil = require('../associationUtil');
|
||||
|
||||
/**
|
||||
* Destroy an entry
|
||||
*/
|
||||
|
||||
module.exports = function update(_ctx) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Return the model used.
|
||||
const Model = actionUtil.parseModel(_ctx);
|
||||
|
||||
// Locate and validate the required `id` parameter.
|
||||
const pk = actionUtil.requirePk(_ctx);
|
||||
|
||||
// Parse the values of the record to update.
|
||||
const values = actionUtil.parseValues(_ctx);
|
||||
|
||||
// No matter what, don't allow changing the `pk` via the update blueprint
|
||||
// (you should just drop and re-add the record if that's what you really want).
|
||||
if (typeof values[Model.primaryKey] !== 'undefined' && values[Model.primaryKey] !== pk) {
|
||||
strapi.log.warn('Cannot change primary key via update action; ignoring value sent for `' + Model.primaryKey + '`');
|
||||
}
|
||||
|
||||
// Make sure the primary key is unchanged.
|
||||
values[Model.primaryKey] = pk;
|
||||
|
||||
Model.findOne(pk).exec(function found(err, matchingRecord) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
if (!matchingRecord) {
|
||||
_ctx.status = 404;
|
||||
return deferred.reject('Record not found');
|
||||
}
|
||||
|
||||
// Associations validation.
|
||||
const associationsValidationPromises = [];
|
||||
|
||||
// One way associations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneWay'}), function (association) {
|
||||
if (values[association.alias] || association.required) {
|
||||
associationsValidationPromises.push(associationUtil.doesRecordExist(association.model, values[association.alias]));
|
||||
}
|
||||
});
|
||||
|
||||
// One to one associations.
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToOne'}), function (association) {
|
||||
if (values[association.alias] || association.required) {
|
||||
associationsValidationPromises.push(associationUtil.doesRecordExist(association.model, values[association.alias]));
|
||||
}
|
||||
});
|
||||
|
||||
// Check relations params.
|
||||
Promise.all(associationsValidationPromises)
|
||||
.then(function () {
|
||||
|
||||
Model.update(pk, values).exec(function updated(err, records) {
|
||||
if (err) {
|
||||
_ctx.status = 400;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Select the first and only one record.
|
||||
const updatedRecord = records[0];
|
||||
|
||||
// Update `oneToOneRelations`.
|
||||
const relationPromises = [];
|
||||
_.forEach(_.where(Model.associations, {nature: 'oneToOne'}), function (relation) {
|
||||
relationPromises.push(associationUtil.oneToOneRelationUpdated(_ctx.model || _ctx.params.model, pk, relation.model, updatedRecord[relation.alias]));
|
||||
});
|
||||
|
||||
// Update the related records.
|
||||
Promise.all(relationPromises)
|
||||
.then(function () {
|
||||
|
||||
// Extra query to find and populate the updated record.
|
||||
let query = Model.findOne(updatedRecord[Model.primaryKey]);
|
||||
query = actionUtil.populateEach(query, _ctx, Model);
|
||||
|
||||
query.exec(function foundAgain(err, populatedRecord) {
|
||||
if (err) {
|
||||
_ctx.status = 500;
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
deferred.resolve(populatedRecord);
|
||||
});
|
||||
})
|
||||
|
||||
// Error during related records update.
|
||||
.catch(function (err) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// Error during the new related records check.
|
||||
.catch(function (err) {
|
||||
_ctx.status = 400;
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
@ -1,215 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Helper which returns a promise and then
|
||||
* the found record
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {string|int} id
|
||||
*
|
||||
* @return {Function|promise}
|
||||
*/
|
||||
|
||||
doesRecordExist: function doesRecordExist(model, id) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
strapi.orm
|
||||
.collections[model]
|
||||
.findOne(id)
|
||||
.exec(function (err, foundRecord) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
if (!foundRecord) {
|
||||
return deferred.reject({
|
||||
message: 'No ' + model + ' found with the specified `id`.'
|
||||
});
|
||||
}
|
||||
|
||||
deferred.resolve(foundRecord);
|
||||
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper which remove the relations of a specific entry and
|
||||
* update the new relation if a relationId is specified
|
||||
*
|
||||
* @param originalModelAlias
|
||||
* @param originalModelId
|
||||
* @param relationModel
|
||||
* @param relationId
|
||||
*
|
||||
* @return {Function|promise}
|
||||
*/
|
||||
|
||||
oneToOneRelationUpdated: function oneToOneRelationUpdated(originalModelAlias, originalModelId, relationModel, relationId) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// First remove all relations
|
||||
const promises = [];
|
||||
|
||||
// Update the relation of the origin model
|
||||
promises.push(module.exports.removeRelationsOut(originalModelAlias, originalModelId, relationModel));
|
||||
|
||||
// Update the entries of the same collection
|
||||
// of the original model.
|
||||
promises.push(module.exports.removeRelationsIn(originalModelAlias, originalModelId, relationModel, relationId));
|
||||
|
||||
Promise.all(promises)
|
||||
.then(function () {
|
||||
|
||||
// If a relationId is provided, update the new linked entry.
|
||||
if (relationId) {
|
||||
strapi.orm.collections[relationModel]
|
||||
.findOne(relationId)
|
||||
.exec(function (err, record) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
if (!record) {
|
||||
return deferred.reject({
|
||||
message: 'Relation not found'
|
||||
});
|
||||
}
|
||||
record[originalModelAlias] = originalModelId;
|
||||
record.save(function (err, record) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
deferred.resolve(record);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper which remove all the relations
|
||||
* of a specific model
|
||||
*
|
||||
* @param originalModelAlias
|
||||
* @param originalModelId
|
||||
* @param relationModel
|
||||
*
|
||||
* @return {Function|promise}
|
||||
*/
|
||||
|
||||
removeRelationsOut: function removeRelationsOut(originalModelAlias, originalModelId, relationModel) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
if (!originalModelAlias) {
|
||||
return deferred.reject({
|
||||
message: 'originalModelAlias invalid.'
|
||||
});
|
||||
}
|
||||
|
||||
// Params object used for the `find`function.
|
||||
const findParams = {};
|
||||
findParams[originalModelAlias] = originalModelId;
|
||||
|
||||
// Find all the matching entries of the original model.
|
||||
strapi.orm.collections[relationModel]
|
||||
.find(findParams)
|
||||
.exec(function (err, records) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Init the array of promises.
|
||||
const savePromises = [];
|
||||
|
||||
// Set the relation to null.
|
||||
// Save the entry and add the promise in the array.
|
||||
_.forEach(records, function (record) {
|
||||
record[originalModelAlias] = null;
|
||||
savePromises.push(record.save());
|
||||
});
|
||||
|
||||
Promise.all(savePromises)
|
||||
.then(function () {
|
||||
deferred.resolve(records);
|
||||
})
|
||||
.catch(function (err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper which remove all the relations
|
||||
* of a specific model
|
||||
*
|
||||
* @param originalModelAlias
|
||||
* @param originalModelId
|
||||
* @param relationModel
|
||||
* @param {number|string}relationId
|
||||
*
|
||||
* @return {Function|promise}
|
||||
*/
|
||||
|
||||
removeRelationsIn: function removeRelationsIn(originalModelAlias, originalModelId, relationModel, relationId) {
|
||||
const deferred = Promise.defer();
|
||||
|
||||
// Params object used for the `find` function.
|
||||
const findParams = {};
|
||||
findParams[relationModel] = relationId;
|
||||
findParams.id = {
|
||||
'!': originalModelId
|
||||
};
|
||||
|
||||
// Find all the matching entries of the original model.
|
||||
strapi.orm.collections[originalModelAlias]
|
||||
.find(findParams)
|
||||
.exec(function (err, records) {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
}
|
||||
|
||||
// Init the array of promises.
|
||||
const savePromises = [];
|
||||
|
||||
_.forEach(records, function (record) {
|
||||
|
||||
// Set the relation to null
|
||||
if (record[relationModel]) {
|
||||
record[relationModel] = null;
|
||||
}
|
||||
|
||||
// Save the entry and add the promise in the array
|
||||
savePromises.push(record.save());
|
||||
});
|
||||
|
||||
Promise.all(savePromises)
|
||||
.then(function () {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Blueprints hook
|
||||
*/
|
||||
|
||||
module.exports = function () {
|
||||
const hook = {
|
||||
|
||||
/**
|
||||
* Default options
|
||||
*/
|
||||
|
||||
defaults: {
|
||||
blueprints: {
|
||||
defaultLimit: 30,
|
||||
populate: true
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Export functions
|
||||
*/
|
||||
|
||||
// Utils
|
||||
actionUtil: require('./actionUtil'),
|
||||
associationUtil: require('./associationUtil'),
|
||||
|
||||
// Actions
|
||||
find: require('./actions/find'),
|
||||
findOne: require('./actions/findOne'),
|
||||
create: require('./actions/create'),
|
||||
update: require('./actions/update'),
|
||||
destroy: require('./actions/destroy'),
|
||||
remove: require('./actions/remove'),
|
||||
add: require('./actions/add'),
|
||||
|
||||
/**
|
||||
* Initialize the hook
|
||||
*/
|
||||
|
||||
initialize: function (cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
return hook;
|
||||
};
|
@ -8,7 +8,6 @@ module.exports = {
|
||||
_config: true,
|
||||
_api: true,
|
||||
responseTime: true,
|
||||
waterline: true,
|
||||
bodyParser: true,
|
||||
session: true,
|
||||
grant: true,
|
||||
@ -22,7 +21,6 @@ module.exports = {
|
||||
i18n: true,
|
||||
cron: true,
|
||||
logger: true,
|
||||
blueprints: true,
|
||||
views: true,
|
||||
router: true,
|
||||
static: true,
|
||||
|
@ -103,20 +103,6 @@ module.exports = function (strapi) {
|
||||
}
|
||||
});
|
||||
|
||||
// Define GraphQL route with modified Waterline models to GraphQL schema
|
||||
// or disable the global variable
|
||||
if (strapi.config.graphql.enabled === true) {
|
||||
// Wait GraphQL schemas generation
|
||||
strapi.once('waterline:graphql:ready', function () {
|
||||
strapi.router.get(strapi.config.graphql.route, strapi.middlewares.graphql({
|
||||
schema: strapi.schemas,
|
||||
pretty: true
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
global.graphql = undefined;
|
||||
}
|
||||
|
||||
// Let the router use our routes and allowed methods.
|
||||
strapi.app.use(strapi.router.routes());
|
||||
strapi.app.use(strapi.router.allowedMethods());
|
||||
|
@ -1,49 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Locale helpers.
|
||||
const isManyToOneAssociation = require('./isManyToOneAssociation');
|
||||
const isOneToOneAssociation = require('./isOneToOneAssociation');
|
||||
const isOneWayAssociation = require('./isOneWayAssociation');
|
||||
const isOneToManyAssociation = require('./isOneToManyAssociation');
|
||||
const isManyToManyAssociation = require('./isManyToManyAssociation');
|
||||
|
||||
/**
|
||||
* Helper which returns the association type of the attribute
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getAssociationType: function (currentModel, association) {
|
||||
let associationType;
|
||||
|
||||
if (association.type === 'model') {
|
||||
if (isManyToOneAssociation(currentModel, association)) {
|
||||
associationType = 'manyToOne';
|
||||
} else if (isOneToOneAssociation(currentModel, association)) {
|
||||
associationType = 'oneToOne';
|
||||
} else if (isOneWayAssociation(currentModel, association)) {
|
||||
associationType = 'oneWay';
|
||||
} else {
|
||||
associationType = 'unknown';
|
||||
}
|
||||
} else if (association.type === 'collection') {
|
||||
if (isOneToManyAssociation(currentModel, association)) {
|
||||
associationType = 'oneToMany';
|
||||
} else if (isManyToManyAssociation(currentModel, association)) {
|
||||
associationType = 'manyToMany';
|
||||
} else {
|
||||
associationType = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
return associationType;
|
||||
}
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Helper which returns a boolean. True if the type
|
||||
* of the relation is `manyToMany`.
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isManyToManyAssociation(currentModel, association) {
|
||||
return _.findWhere(strapi.models[association.collection] && strapi.models[association.collection].associations, {
|
||||
collection: currentModel
|
||||
});
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Helper which returns a boolean. True if the type
|
||||
* of the relation is `manyToOne`.
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isManyToOneAssociation(currentModel, association) {
|
||||
return _.findWhere(strapi.models[association.model] && strapi.models[association.model].associations, {
|
||||
collection: currentModel
|
||||
});
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Helper which returns a boolean. True if the type
|
||||
* of the relation is `oneToMany`.
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isOneToManyAssociation(currentModel, association) {
|
||||
return _.findWhere(strapi.models[association.collection] && strapi.models[association.collection].associations, {
|
||||
model: currentModel
|
||||
});
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Helper which returns a boolean. True if the type
|
||||
* of the relation is `oneToOne`.
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isOneToOneAssociation(currentModel, association) {
|
||||
return _.findWhere(strapi.models[association.model] && strapi.models[association.model].associations, {
|
||||
model: currentModel
|
||||
});
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Locale helpers.
|
||||
const isOneToManyAssociation = require('./isOneToManyAssociation');
|
||||
const isOneToOneAssociation = require('./isOneToOneAssociation');
|
||||
|
||||
/**
|
||||
* Helper which returns a boolean. True if the type
|
||||
* of the relation is `oneToOne`.
|
||||
*
|
||||
* @param {Object} currentModel
|
||||
* @param {Object} association
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isOneWayAssociation(currentModel, association) {
|
||||
return !(isOneToManyAssociation(currentModel, association) || isOneToOneAssociation(currentModel, association));
|
||||
};
|
@ -1,302 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Node.js core.
|
||||
const cluster = require('cluster');
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const Waterline = require('waterline');
|
||||
const WaterlineGraphQL = require('waterline-graphql');
|
||||
|
||||
// Local utilities.
|
||||
const helpers = require('./helpers/index');
|
||||
|
||||
/**
|
||||
* Waterline ORM hook
|
||||
*/
|
||||
|
||||
module.exports = function (strapi) {
|
||||
const hook = {
|
||||
|
||||
/**
|
||||
* Default options
|
||||
*/
|
||||
|
||||
defaults: {
|
||||
orm: {
|
||||
adapters: {
|
||||
disk: 'sails-disk'
|
||||
},
|
||||
defaultConnection: 'default',
|
||||
connections: {
|
||||
default: {
|
||||
adapter: 'disk',
|
||||
filePath: '.tmp/',
|
||||
fileName: 'default.db',
|
||||
migrate: 'alter'
|
||||
},
|
||||
permanent: {
|
||||
adapter: 'disk',
|
||||
filePath: './data/',
|
||||
fileName: 'permanent.db',
|
||||
migrate: 'alter'
|
||||
}
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
models: true
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the hook
|
||||
*/
|
||||
|
||||
initialize: function (cb) {
|
||||
if (_.isPlainObject(strapi.config.orm) && !_.isEmpty(strapi.config.orm) && (((cluster.isWorker && strapi.config.reload.workers > 0) || (cluster.isMaster && strapi.config.reload.workers < 1)) || !strapi.config.reload && cluster.isMaster)) {
|
||||
strapi.adapters = {};
|
||||
strapi.collections = [];
|
||||
|
||||
// Expose a new instance of Waterline.
|
||||
if (!strapi.orm) {
|
||||
strapi.orm = new Waterline();
|
||||
}
|
||||
|
||||
// Prefix every adapter and require them from the
|
||||
// `node_modules` directory of the application.
|
||||
_.forEach(strapi.config.orm.adapters, function (adapter, name) {
|
||||
try {
|
||||
strapi.adapters[name] = require(path.resolve(strapi.config.appPath, 'node_modules', adapter));
|
||||
} catch (err) {
|
||||
strapi.log.error('The adapter `' + adapter + '` is not installed.');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the adapter in every connections exists.
|
||||
_.forEach(strapi.config.orm.connections, function (settings, name) {
|
||||
if (!_.has(strapi.config.orm.adapters, settings.adapter)) {
|
||||
strapi.log.error('Unknown adapter `' + settings.adapter + '` for connection `' + name + '`.');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Parse each models.
|
||||
_.forEach(strapi.models, function (definition, model) {
|
||||
_.bindAll(definition);
|
||||
|
||||
// Make sure the model has a connection.
|
||||
// If not, use the default connection.
|
||||
if (_.isEmpty(definition.connection)) {
|
||||
definition.connection = strapi.config.orm.defaultConnection;
|
||||
}
|
||||
|
||||
// Make sure this connection exists.
|
||||
if (!_.has(strapi.config.orm.connections, definition.connection)) {
|
||||
strapi.log.error('The connection `' + definition.connection + '` specified in the `' + model + '` model does not exist.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Make sure this connection has an appropriate migrate strategy.
|
||||
// If not, use the appropriate strategy.
|
||||
if (!_.has(strapi.config.orm.connections[definition.connection], 'migrate')) {
|
||||
if (strapi.config.environment === 'production') {
|
||||
strapi.log.warn('Setting the migrate strategy of the `' + model + '` model to `safe`.');
|
||||
strapi.config.orm.connections[definition.connection].migrate = 'safe';
|
||||
} else {
|
||||
strapi.log.warn('Setting the migrate strategy of the `' + model + '` model to `alter`.');
|
||||
strapi.config.orm.connections[definition.connection].migrate = 'alter';
|
||||
}
|
||||
} else if (strapi.config.environment === 'production' && strapi.config.orm.connections[definition.connection].migrate === ('alter' || 'drop')) {
|
||||
strapi.log.warn('Setting the migrate strategy of the `' + model + '` model to `safe`.');
|
||||
strapi.config.orm.connections[definition.connection].migrate = 'safe';
|
||||
}
|
||||
|
||||
// Apply the migrate strategy to the model.
|
||||
definition.migrate = strapi.config.orm.connections[definition.connection].migrate;
|
||||
|
||||
// Derive information about this model's associations from its schema
|
||||
// and attach/expose the metadata as `SomeModel.associations` (an array).
|
||||
definition.associations = _.reduce(definition.attributes, function (associatedWith, attrDef, attrName) {
|
||||
if (typeof attrDef === 'object' && (attrDef.model || attrDef.collection)) {
|
||||
const assoc = {
|
||||
alias: attrName,
|
||||
type: attrDef.model ? 'model' : 'collection'
|
||||
};
|
||||
|
||||
if (attrDef.model) {
|
||||
assoc.model = attrDef.model;
|
||||
}
|
||||
|
||||
if (attrDef.collection) {
|
||||
assoc.collection = attrDef.collection;
|
||||
}
|
||||
|
||||
if (attrDef.via) {
|
||||
assoc.via = attrDef.via;
|
||||
}
|
||||
|
||||
associatedWith.push(assoc);
|
||||
}
|
||||
|
||||
return associatedWith;
|
||||
}, []);
|
||||
|
||||
// Finally, load the collection in the Waterline instance.
|
||||
try {
|
||||
const collection = strapi.orm.loadCollection(Waterline.Collection.extend(definition));
|
||||
|
||||
if (_.isFunction(collection)) {
|
||||
strapi.collections.push(collection);
|
||||
}
|
||||
} catch (err) {
|
||||
strapi.log.error('Impossible to register the `' + model + '` model.');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, initialize the Waterline ORM and
|
||||
// globally expose models.
|
||||
strapi.orm.initialize({
|
||||
adapters: strapi.adapters,
|
||||
connections: strapi.config.orm.connections,
|
||||
collections: strapi.collections,
|
||||
defaults: {
|
||||
connection: strapi.config.orm.defaultConnection
|
||||
}
|
||||
}, function () {
|
||||
if (strapi.config.globals.models === true) {
|
||||
_.forEach(strapi.models, function (definition, model) {
|
||||
const globalName = _.capitalize(strapi.models[model].globalId);
|
||||
global[globalName] = strapi.orm.collections[model];
|
||||
});
|
||||
}
|
||||
|
||||
// Parse each models and look for associations.
|
||||
_.forEach(strapi.orm.collections, function (definition, model) {
|
||||
_.forEach(definition.associations, function (association) {
|
||||
association.nature = helpers.getAssociationType(model, association);
|
||||
});
|
||||
});
|
||||
|
||||
if (strapi.config.graphql.enabled === true) {
|
||||
// Parse each models and add associations array
|
||||
_.forEach(strapi.orm.collections, function (collection, key) {
|
||||
if (strapi.models.hasOwnProperty(key)) {
|
||||
collection.associations = strapi.models[key].associations || [];
|
||||
}
|
||||
});
|
||||
|
||||
// Expose the GraphQL schemas at `strapi.schemas`
|
||||
WaterlineGraphQL.getGraphQLSchema({
|
||||
collections: strapi.orm.collections,
|
||||
usefulFunctions: true
|
||||
}, function (schemas) {
|
||||
strapi.schemas = schemas;
|
||||
|
||||
strapi.emit('waterline:graphql:ready');
|
||||
});
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reload the hook
|
||||
*/
|
||||
|
||||
reload: function () {
|
||||
hook.teardown(function () {
|
||||
delete strapi.orm;
|
||||
|
||||
hook.initialize(function (err) {
|
||||
if (err) {
|
||||
strapi.log.error('Failed to reinitialize the ORM hook.');
|
||||
strapi.stop();
|
||||
} else {
|
||||
strapi.emit('hook:waterline:reloaded');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Teardown adapters
|
||||
*/
|
||||
|
||||
teardown: function (cb) {
|
||||
cb = cb || function (err) {
|
||||
if (err) {
|
||||
strapi.log.error('Failed to teardown ORM adapters.');
|
||||
strapi.stop();
|
||||
}
|
||||
};
|
||||
async.forEach(Object.keys(strapi.adapters || {}), function (name, next) {
|
||||
if (strapi.adapters[name].teardown) {
|
||||
strapi.adapters[name].teardown(null, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}, cb);
|
||||
},
|
||||
|
||||
/**
|
||||
* Installation adapters
|
||||
*/
|
||||
|
||||
installation: function () {
|
||||
const done = _.after(_.size(strapi.config.orm.adapters), function () {
|
||||
strapi.emit('hook:waterline:installed');
|
||||
});
|
||||
|
||||
_.forEach(strapi.config.orm.adapters, function (adapter) {
|
||||
try {
|
||||
require(path.resolve(strapi.config.appPath, 'node_modules', adapter));
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
if (strapi.config.environment === 'development') {
|
||||
strapi.log.warn('Installing the `' + adapter + '` adapter, please wait...');
|
||||
console.log();
|
||||
|
||||
const process = spawn('npm', ['install', adapter, '--save']);
|
||||
|
||||
process.on('error', function (error) {
|
||||
strapi.log.error('The adapter `' + adapter + '` has not been installed.');
|
||||
strapi.log.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('close', function (code) {
|
||||
if (code !== 0) {
|
||||
strapi.log.error('The adapter `' + adapter + '` has not been installed.');
|
||||
strapi.log.error('Code: ' + code);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
strapi.log.info('`' + adapter + '` successfully installed');
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
strapi.log.error('The adapter `' + adapter + '` is not installed.');
|
||||
strapi.log.error('Execute `$ npm install ' + adapter + ' --save` to install it.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return hook;
|
||||
};
|
@ -39,10 +39,8 @@ module.exports = function (strapi) {
|
||||
|
||||
// Remove undesired hooks when this is a `dry` application.
|
||||
if (strapi.config.dry) {
|
||||
delete hooks.blueprints;
|
||||
delete hooks.grant;
|
||||
delete hooks.studio;
|
||||
delete hooks.waterline;
|
||||
}
|
||||
|
||||
// Handle folder-defined modules (default to `./lib/index.js`)
|
||||
@ -130,7 +128,7 @@ module.exports = function (strapi) {
|
||||
|
||||
// Prepare all other hooks.
|
||||
prepare: function prepareHooks(cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router', 'waterline'), function (id, cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router'), function (id, cb) {
|
||||
prepareHook(id);
|
||||
process.nextTick(cb);
|
||||
}, cb);
|
||||
@ -138,7 +136,7 @@ module.exports = function (strapi) {
|
||||
|
||||
// Apply the default config for all other hooks.
|
||||
defaults: function defaultConfigHooks(cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router', 'waterline'), function (id, cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router'), function (id, cb) {
|
||||
const hook = hooks[id];
|
||||
applyDefaults(hook);
|
||||
process.nextTick(cb);
|
||||
@ -147,7 +145,7 @@ module.exports = function (strapi) {
|
||||
|
||||
// Load all other hooks.
|
||||
load: function loadOtherHooks(cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router', 'waterline'), function (id, cb) {
|
||||
async.each(_.without(_.keys(hooks), '_config', '_api', 'studio', 'router'), function (id, cb) {
|
||||
loadHook(id, cb);
|
||||
}, cb);
|
||||
},
|
||||
@ -160,16 +158,6 @@ module.exports = function (strapi) {
|
||||
prepareHook('router');
|
||||
applyDefaults(hooks.router);
|
||||
loadHook('router', cb);
|
||||
},
|
||||
|
||||
// Load the waterline hook.
|
||||
waterline: function loadWaterlineHook(cb) {
|
||||
if (!hooks.waterline) {
|
||||
return cb();
|
||||
}
|
||||
prepareHook('waterline');
|
||||
applyDefaults(hooks.waterline);
|
||||
loadHook('waterline', cb);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -55,7 +55,6 @@ module.exports = cb => {
|
||||
|
||||
// Run adapters installation
|
||||
if (cluster.isMaster) {
|
||||
strapi.hooks.waterline.installation();
|
||||
|
||||
++count;
|
||||
|
||||
@ -72,44 +71,34 @@ module.exports = cb => {
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Teardown Waterline adapters and
|
||||
// reload the Waterline ORM.
|
||||
strapi.after('hook:waterline:reloaded', () => {
|
||||
strapi.after('hook:router:reloaded', () => {
|
||||
process.nextTick(() => cb());
|
||||
// Reload the router.
|
||||
strapi.after('hook:router:reloaded', () => {
|
||||
process.nextTick(() => cb());
|
||||
|
||||
// Update `strapi` status.
|
||||
strapi.reloaded = true;
|
||||
strapi.reloading = false;
|
||||
// Update `strapi` status.
|
||||
strapi.reloaded = true;
|
||||
strapi.reloading = false;
|
||||
|
||||
// Finally inform the developer everything seems ok.
|
||||
if (cluster.isMaster && _.isPlainObject(strapi.config.reload) && !_.isEmpty(strapi.config.reload) && strapi.config.reload.workers < 1) {
|
||||
strapi.log.info('Application\'s dictionnary updated');
|
||||
strapi.log.warn('You still need to restart your server to fully enjoy changes...');
|
||||
}
|
||||
// Finally inform the developer everything seems ok.
|
||||
if (cluster.isMaster && _.isPlainObject(strapi.config.reload) && !_.isEmpty(strapi.config.reload) && strapi.config.reload.workers < 1) {
|
||||
strapi.log.info('Application\'s dictionnary updated');
|
||||
strapi.log.warn('You still need to restart your server to fully enjoy changes...');
|
||||
}
|
||||
|
||||
strapi.once('restart:done', function () {
|
||||
strapi.log.info('Application successfully restarted');
|
||||
});
|
||||
|
||||
if (cluster.isMaster) {
|
||||
_.forEach(cluster.workers, worker => worker.on('message', () => strapi.emit('restart:done')));
|
||||
}
|
||||
|
||||
// Kill every worker processes.
|
||||
_.forEach(cluster.workers, () => process.kill(process.pid, 'SIGHUP'));
|
||||
strapi.once('restart:done', function () {
|
||||
strapi.log.info('Application successfully restarted');
|
||||
});
|
||||
|
||||
// Reloading the router.
|
||||
strapi.hooks.router.reload();
|
||||
if (cluster.isMaster) {
|
||||
_.forEach(cluster.workers, worker => worker.on('message', () => strapi.emit('restart:done')));
|
||||
}
|
||||
|
||||
// Kill every worker processes.
|
||||
_.forEach(cluster.workers, () => process.kill(process.pid, 'SIGHUP'));
|
||||
});
|
||||
|
||||
// Reloading the ORM.
|
||||
strapi.hooks.waterline.reload();
|
||||
});
|
||||
|
||||
strapi.after('hook:waterline:installed', () => {
|
||||
installed();
|
||||
// Reloading the router.
|
||||
strapi.hooks.router.reload();
|
||||
});
|
||||
|
||||
strapi.after('hook:views:installed', () => {
|
||||
|
@ -22,7 +22,6 @@
|
||||
"security",
|
||||
"socket.io",
|
||||
"sockets",
|
||||
"waterline",
|
||||
"websockets"
|
||||
],
|
||||
"directories": {
|
||||
@ -67,9 +66,9 @@
|
||||
"node-schedule": "~0.6.0",
|
||||
"prompt": "~0.2.14",
|
||||
"request": "~2.67.0",
|
||||
"sails-disk": "~0.10.8",
|
||||
"socket.io": "~1.3.7",
|
||||
"socket.io-client": "~1.3.7",
|
||||
"strapi-bookshelf": "~1.5.0",
|
||||
"strapi-generate": "~1.5.0",
|
||||
"strapi-generate-admin": "~1.5.0",
|
||||
"strapi-generate-api": "~1.5.0",
|
||||
@ -78,8 +77,6 @@
|
||||
"strapi-generate-upload": "~1.5.0",
|
||||
"strapi-generate-users": "~1.5.0",
|
||||
"unzip2": "~0.2.5",
|
||||
"waterline": "~0.10.28",
|
||||
"waterline-graphql": "~1.1.0",
|
||||
"winston": "~2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -167,8 +164,8 @@
|
||||
"url": "https://github.com/wistityhq/strapi/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12.0",
|
||||
"npm": ">= 2.0.0"
|
||||
"node": ">= 4.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"license": "MIT"
|
||||
|
Loading…
x
Reference in New Issue
Block a user