2018-11-22 16:40:52 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loaders.js service
|
|
|
|
*
|
|
|
|
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const _ = require('lodash');
|
|
|
|
const DataLoader = require('dataloader');
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
loaders: {},
|
|
|
|
|
2019-01-05 18:14:00 +01:00
|
|
|
initializeLoader: function() {
|
2019-01-16 17:26:15 +01:00
|
|
|
this.resetLoaders();
|
|
|
|
|
2019-01-05 18:14:00 +01:00
|
|
|
// Create loaders for each relational field (exclude core models).
|
|
|
|
Object.keys(strapi.models)
|
|
|
|
.filter(model => model !== 'core_store')
|
2019-12-10 16:21:21 +01:00
|
|
|
.forEach(modelKey => {
|
|
|
|
const model = strapi.models[modelKey];
|
|
|
|
this.createLoader(model.uid);
|
2019-01-05 18:14:00 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// Reproduce the same pattern for each plugin.
|
|
|
|
Object.keys(strapi.plugins).forEach(plugin => {
|
2019-12-10 16:21:21 +01:00
|
|
|
Object.keys(strapi.plugins[plugin].models).forEach(modelKey => {
|
|
|
|
const model = strapi.plugins[plugin].models[modelKey];
|
|
|
|
this.createLoader(model.uid);
|
2019-01-05 18:14:00 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-03-25 16:37:46 +01:00
|
|
|
resetLoaders: function() {
|
2019-01-16 17:26:15 +01:00
|
|
|
this.loaders = {};
|
|
|
|
},
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
createLoader: function(modelUID) {
|
|
|
|
if (this.loaders[modelUID]) {
|
|
|
|
return this.loaders[modelUID];
|
2019-01-16 17:26:15 +01:00
|
|
|
}
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
this.loaders[modelUID] = new DataLoader(
|
2019-03-25 16:37:46 +01:00
|
|
|
keys => {
|
2019-10-01 17:45:16 +02:00
|
|
|
// Extract queries from keys and merge similar queries.
|
2019-12-10 16:21:21 +01:00
|
|
|
const { queries, map } = this.extractQueries(
|
|
|
|
modelUID,
|
|
|
|
_.cloneDeep(keys)
|
|
|
|
);
|
2019-10-01 17:45:16 +02:00
|
|
|
|
|
|
|
// Run queries in parallel.
|
|
|
|
return Promise.all(
|
2019-12-10 16:21:21 +01:00
|
|
|
queries.map(query => this.makeQuery(modelUID, query))
|
2019-10-01 17:45:16 +02:00
|
|
|
).then(results => {
|
|
|
|
// Use to match initial queries order.
|
2019-12-10 16:21:21 +01:00
|
|
|
return this.mapData(modelUID, keys, map, results);
|
2019-03-25 16:37:46 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
{
|
|
|
|
cacheKeyFn: key => {
|
|
|
|
return _.isObjectLike(key) ? JSON.stringify(_.cloneDeep(key)) : key;
|
|
|
|
},
|
2018-11-22 16:40:52 +01:00
|
|
|
}
|
2019-03-25 16:37:46 +01:00
|
|
|
);
|
2018-11-22 16:40:52 +01:00
|
|
|
},
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
mapData: function(modelUID, originalMap, map, results) {
|
2018-11-22 16:40:52 +01:00
|
|
|
// Use map to re-dispatch data correctly based on initial keys.
|
|
|
|
return originalMap.map((query, index) => {
|
|
|
|
// Find the index of where we should extract the results.
|
2019-04-17 16:27:25 +02:00
|
|
|
const indexResults = map.findIndex(
|
|
|
|
queryMap => queryMap.indexOf(index) !== -1
|
|
|
|
);
|
2018-11-22 16:40:52 +01:00
|
|
|
const data = results[indexResults];
|
|
|
|
|
|
|
|
// Retrieving referring model.
|
2019-12-10 16:21:21 +01:00
|
|
|
const ref = strapi.getModel(modelUID);
|
2018-11-22 18:32:33 +01:00
|
|
|
|
|
|
|
if (query.single) {
|
|
|
|
// Return object instead of array for one-to-many relationship.
|
2019-03-25 16:37:46 +01:00
|
|
|
return data.find(
|
|
|
|
entry =>
|
2019-04-17 16:27:25 +02:00
|
|
|
entry[ref.primaryKey].toString() ===
|
|
|
|
(query.params[ref.primaryKey] || '').toString()
|
2019-03-25 16:37:46 +01:00
|
|
|
);
|
2018-11-22 18:32:33 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 17:16:28 +01:00
|
|
|
// Generate constant for skip parameters.
|
|
|
|
// Note: we shouldn't support both way of doing this kind of things in the future.
|
2019-10-01 17:45:16 +02:00
|
|
|
const skip = query.options._start || 0;
|
2019-02-13 21:26:37 +01:00
|
|
|
const limit = _.get(query, 'options._limit', 100); // Take into account the limit if its equal 0
|
2018-11-22 19:57:26 +01:00
|
|
|
|
2019-01-15 17:16:28 +01:00
|
|
|
// Extracting ids from original request to map with query results.
|
2019-03-26 17:59:06 +01:00
|
|
|
const ids = this.extractIds(query, ref);
|
|
|
|
|
2019-02-02 22:16:14 +01:00
|
|
|
const ast = ref.associations.find(ast => ast.alias === ids.alias);
|
2019-03-26 17:59:06 +01:00
|
|
|
const astModel = ast
|
2019-12-10 16:21:21 +01:00
|
|
|
? strapi.getModel(ast.model || ast.collection, ast.plugin)
|
|
|
|
: strapi.getModel(modelUID);
|
2019-03-26 17:59:06 +01:00
|
|
|
|
|
|
|
if (!_.isArray(ids)) {
|
|
|
|
return data
|
|
|
|
.filter(entry => entry !== undefined)
|
|
|
|
.filter(entry => {
|
|
|
|
const aliasEntry = entry[ids.alias];
|
|
|
|
|
|
|
|
if (_.isArray(aliasEntry)) {
|
|
|
|
return _.find(
|
|
|
|
aliasEntry,
|
|
|
|
value => value[astModel.primaryKey].toString() === ids.value
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const entryValue = aliasEntry[astModel.primaryKey].toString();
|
|
|
|
return entryValue === ids.value;
|
|
|
|
})
|
|
|
|
.slice(skip, skip + limit);
|
|
|
|
}
|
2018-11-22 16:40:52 +01:00
|
|
|
|
2018-12-06 18:11:53 +01:00
|
|
|
return data
|
|
|
|
.filter(entry => entry !== undefined)
|
2019-04-17 16:27:25 +02:00
|
|
|
.filter(entry =>
|
|
|
|
ids
|
|
|
|
.map(id => id.toString())
|
|
|
|
.includes(entry[ref.primaryKey].toString())
|
|
|
|
)
|
2019-02-02 13:33:26 +01:00
|
|
|
.slice(skip, skip + limit);
|
2018-11-22 16:40:52 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-03-26 17:59:06 +01:00
|
|
|
extractIds: (query, ref) => {
|
|
|
|
if (_.get(query.options, `query.${ref.primaryKey}`)) {
|
|
|
|
return _.get(query.options, `query.${ref.primaryKey}`);
|
|
|
|
}
|
|
|
|
|
2019-01-15 17:16:28 +01:00
|
|
|
const alias = _.first(Object.keys(query.options.query));
|
2019-03-25 16:37:46 +01:00
|
|
|
const value = query.options.query[alias].toString();
|
2019-01-15 17:16:28 +01:00
|
|
|
return {
|
|
|
|
alias,
|
2019-02-02 22:16:14 +01:00
|
|
|
value,
|
2019-01-15 17:16:28 +01:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
makeQuery: async function(modelUID, query = {}) {
|
2018-11-27 18:48:37 +01:00
|
|
|
if (_.isEmpty(query.ids)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
const ref = strapi.getModel(modelUID);
|
2019-10-02 16:38:06 +02:00
|
|
|
const ast = ref.associations.find(ast => ast.alias === query.alias);
|
|
|
|
|
2018-11-22 19:57:26 +01:00
|
|
|
const params = {
|
2018-11-22 16:40:52 +01:00
|
|
|
...query.options,
|
2019-10-02 16:38:06 +02:00
|
|
|
populate: ast ? [query.alias] : [],
|
2019-02-02 13:33:26 +01:00
|
|
|
query: {},
|
2019-02-13 21:26:37 +01:00
|
|
|
_start: 0, // Don't apply start or skip
|
|
|
|
_limit: -1, // Don't apply a limit
|
2018-11-22 19:57:26 +01:00
|
|
|
};
|
|
|
|
|
2019-02-02 13:33:26 +01:00
|
|
|
params.query[`${query.alias}_in`] = _.chain(query.ids)
|
|
|
|
.filter(id => !_.isEmpty(id) || _.isInteger(id)) // Only keep valid ids
|
|
|
|
.map(id => id.toString()) // convert ids to string
|
|
|
|
.uniq() // Remove redundant ids
|
|
|
|
.value();
|
2018-11-22 19:57:26 +01:00
|
|
|
|
|
|
|
// Run query and remove duplicated ID.
|
2019-10-01 17:45:16 +02:00
|
|
|
return strapi.plugins['content-manager'].services[
|
2019-04-17 16:27:25 +02:00
|
|
|
'contentmanager'
|
2019-12-10 16:21:21 +01:00
|
|
|
].fetchAll({ model: modelUID }, params);
|
2018-11-22 16:40:52 +01:00
|
|
|
},
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
extractQueries: function(modelUID, keys) {
|
2018-11-22 16:40:52 +01:00
|
|
|
const queries = [];
|
|
|
|
const map = [];
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2018-11-22 16:40:52 +01:00
|
|
|
keys.forEach((current, index) => {
|
|
|
|
// Extract query options.
|
2019-01-15 17:16:28 +01:00
|
|
|
// Note: the `single` means that we've only one entry to fetch.
|
2018-11-22 19:57:26 +01:00
|
|
|
const { single = false, params = {}, association } = current;
|
2018-11-22 18:32:33 +01:00
|
|
|
const { query = {}, ...options } = current.options;
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2018-11-22 16:40:52 +01:00
|
|
|
// Retrieving referring model.
|
2019-12-10 16:21:21 +01:00
|
|
|
const { primaryKey } = strapi.getModel(modelUID);
|
2018-11-22 16:40:52 +01:00
|
|
|
|
2019-01-15 17:16:28 +01:00
|
|
|
// Generate array of IDs to fetch.
|
2019-03-25 16:37:46 +01:00
|
|
|
const ids = [];
|
|
|
|
|
2019-01-15 17:16:28 +01:00
|
|
|
// Only one entry to fetch.
|
|
|
|
if (single) {
|
2019-12-10 16:21:21 +01:00
|
|
|
ids.push(params[primaryKey]);
|
|
|
|
} else if (_.isArray(query[primaryKey])) {
|
|
|
|
ids.push(...query[primaryKey]);
|
2019-01-15 17:16:28 +01:00
|
|
|
} else {
|
|
|
|
ids.push(query[association.via]);
|
|
|
|
}
|
2018-11-22 19:57:26 +01:00
|
|
|
|
2019-04-17 16:27:25 +02:00
|
|
|
queries.push({
|
|
|
|
ids,
|
|
|
|
options,
|
2019-12-10 16:21:21 +01:00
|
|
|
alias: _.first(Object.keys(query)) || primaryKey,
|
2019-04-17 16:27:25 +02:00
|
|
|
});
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2019-04-17 16:27:25 +02:00
|
|
|
map[queries.length - 1 > 0 ? queries.length - 1 : 0] = [];
|
|
|
|
map[queries.length - 1].push(index);
|
2018-11-22 16:40:52 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
queries,
|
2019-03-25 16:37:46 +01:00
|
|
|
map,
|
2018-11-22 16:40:52 +01:00
|
|
|
};
|
2019-03-25 16:37:46 +01:00
|
|
|
},
|
|
|
|
};
|