mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			203 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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: {},
 | 
						|
 | 
						|
  initializeLoader: function() {
 | 
						|
    this.resetLoaders();
 | 
						|
 | 
						|
    // Create loaders for each relational field (exclude core models).
 | 
						|
    Object.keys(strapi.models)
 | 
						|
      .filter(model => model.internal !== true)
 | 
						|
      .forEach(modelKey => {
 | 
						|
        const model = strapi.models[modelKey];
 | 
						|
        this.createLoader(model.uid);
 | 
						|
      });
 | 
						|
 | 
						|
    // Reproduce the same pattern for each plugin.
 | 
						|
    Object.keys(strapi.plugins).forEach(plugin => {
 | 
						|
      Object.keys(strapi.plugins[plugin].models).forEach(modelKey => {
 | 
						|
        const model = strapi.plugins[plugin].models[modelKey];
 | 
						|
        this.createLoader(model.uid);
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    // Add the loader for the AdminUser as well, so we can query `created_by` and `updated_by` AdminUsers
 | 
						|
    this.createLoader('strapi::user');
 | 
						|
  },
 | 
						|
 | 
						|
  resetLoaders: function() {
 | 
						|
    this.loaders = {};
 | 
						|
  },
 | 
						|
 | 
						|
  createLoader: function(modelUID) {
 | 
						|
    if (this.loaders[modelUID]) {
 | 
						|
      return this.loaders[modelUID];
 | 
						|
    }
 | 
						|
 | 
						|
    this.loaders[modelUID] = new DataLoader(
 | 
						|
      keys => {
 | 
						|
        // Extract queries from keys and merge similar queries.
 | 
						|
        const { queries, map } = this.extractQueries(modelUID, _.cloneDeep(keys));
 | 
						|
 | 
						|
        // Run queries in parallel.
 | 
						|
        return Promise.all(queries.map(query => this.makeQuery(modelUID, query))).then(results => {
 | 
						|
          // Use to match initial queries order.
 | 
						|
          return this.mapData(modelUID, keys, map, results);
 | 
						|
        });
 | 
						|
      },
 | 
						|
      {
 | 
						|
        cacheKeyFn: key => {
 | 
						|
          return _.isObjectLike(key) ? JSON.stringify(_.cloneDeep(key)) : key;
 | 
						|
        },
 | 
						|
      }
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  mapData: function(modelUID, originalMap, map, results) {
 | 
						|
    // 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.
 | 
						|
      const indexResults = map.findIndex(queryMap => queryMap.indexOf(index) !== -1);
 | 
						|
      const data = results[indexResults];
 | 
						|
 | 
						|
      // Retrieving referring model.
 | 
						|
      const ref = strapi.getModel(modelUID);
 | 
						|
 | 
						|
      if (query.single) {
 | 
						|
        // Return object instead of array for one-to-many relationship.
 | 
						|
        return data.find(
 | 
						|
          entry =>
 | 
						|
            entry[ref.primaryKey].toString() === (query.params[ref.primaryKey] || '').toString()
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      // Generate constant for skip parameters.
 | 
						|
      // Note: we shouldn't support both way of doing this kind of things in the future.
 | 
						|
      const skip = query.options._start || 0;
 | 
						|
      const limit = _.get(query, 'options._limit', 100); // Take into account the limit if its equal 0
 | 
						|
 | 
						|
      // Extracting ids from original request to map with query results.
 | 
						|
      const ids = this.extractIds(query, ref);
 | 
						|
 | 
						|
      const ast = ref.associations.find(ast => ast.alias === ids.alias);
 | 
						|
      const astModel = ast
 | 
						|
        ? strapi.getModel(ast.model || ast.collection, ast.plugin)
 | 
						|
        : strapi.getModel(modelUID);
 | 
						|
 | 
						|
      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);
 | 
						|
      }
 | 
						|
 | 
						|
      return data
 | 
						|
        .filter(entry => entry !== undefined)
 | 
						|
        .filter(entry => ids.map(id => id.toString()).includes(entry[ref.primaryKey].toString()))
 | 
						|
        .slice(skip, skip + limit);
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  extractIds: (query, ref) => {
 | 
						|
    if (_.get(query.options, `query.${ref.primaryKey}`)) {
 | 
						|
      return _.get(query.options, `query.${ref.primaryKey}`);
 | 
						|
    }
 | 
						|
 | 
						|
    const alias = _.first(Object.keys(query.options.query));
 | 
						|
    const value = query.options.query[alias].toString();
 | 
						|
    return {
 | 
						|
      alias,
 | 
						|
      value,
 | 
						|
    };
 | 
						|
  },
 | 
						|
 | 
						|
  makeQuery: async function(modelUID, query = {}) {
 | 
						|
    if (_.isEmpty(query.ids)) {
 | 
						|
      return [];
 | 
						|
    }
 | 
						|
 | 
						|
    const ref = strapi.getModel(modelUID);
 | 
						|
    const ast = ref.associations.find(ast => ast.alias === query.alias);
 | 
						|
 | 
						|
    const params = {
 | 
						|
      ...query.options,
 | 
						|
      populate: ast ? [query.alias] : [],
 | 
						|
      query: {},
 | 
						|
      _start: 0, // Don't apply start or skip
 | 
						|
      _limit: -1, // Don't apply a limit
 | 
						|
    };
 | 
						|
 | 
						|
    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();
 | 
						|
 | 
						|
    // Run query and remove duplicated ID.
 | 
						|
    return strapi.plugins['content-manager'].services['contentmanager'].fetchAll(modelUID, params);
 | 
						|
  },
 | 
						|
 | 
						|
  extractQueries: function(modelUID, keys) {
 | 
						|
    const queries = [];
 | 
						|
    const map = [];
 | 
						|
 | 
						|
    keys.forEach((current, index) => {
 | 
						|
      // Extract query options.
 | 
						|
      // Note: the `single` means that we've only one entry to fetch.
 | 
						|
      const { single = false, params = {}, association } = current;
 | 
						|
      const { query = {}, ...options } = current.options;
 | 
						|
 | 
						|
      // Retrieving referring model.
 | 
						|
      const { primaryKey } = strapi.getModel(modelUID);
 | 
						|
 | 
						|
      // Generate array of IDs to fetch.
 | 
						|
      const ids = [];
 | 
						|
 | 
						|
      // Only one entry to fetch.
 | 
						|
      if (single) {
 | 
						|
        ids.push(params[primaryKey]);
 | 
						|
      } else if (_.isArray(query[primaryKey])) {
 | 
						|
        ids.push(...query[primaryKey]);
 | 
						|
      } else {
 | 
						|
        ids.push(query[association.via]);
 | 
						|
      }
 | 
						|
 | 
						|
      queries.push({
 | 
						|
        ids,
 | 
						|
        options,
 | 
						|
        alias: _.first(Object.keys(query)) || primaryKey,
 | 
						|
      });
 | 
						|
 | 
						|
      map[queries.length - 1 > 0 ? queries.length - 1 : 0] = [];
 | 
						|
      map[queries.length - 1].push(index);
 | 
						|
    });
 | 
						|
 | 
						|
    return {
 | 
						|
      queries,
 | 
						|
      map,
 | 
						|
    };
 | 
						|
  },
 | 
						|
};
 |