/*

Default list, single, and total resolvers

*/

import {
  Utils,
  debug,
  debugGroup,
  debugGroupEnd,
  Connectors,
  getTypeName,
  getCollectionName,
  throwError,
} from 'meteor/vulcan:lib';
import isEmpty from 'lodash/isEmpty';

const defaultOptions = {
  cacheMaxAge: 300,
};

// note: for some reason changing resolverOptions to "options" throws error
export function getDefaultResolvers(options) {
  let typeName, collectionName, resolverOptions;
  if (typeof arguments[0] === 'object') {
    // new single-argument API
    typeName = arguments[0].typeName;
    collectionName = arguments[0].collectionName || getCollectionName(typeName);
    resolverOptions = { ...defaultOptions, ...arguments[0].options };
  } else {
    // OpenCRUD backwards compatibility
    collectionName = arguments[0];
    typeName = getTypeName(collectionName);
    resolverOptions = { ...defaultOptions, ...arguments[1] };
  }

  return {
    // resolver for returning a list of documents based on a set of query terms

    multi: {
      description: `A list of ${typeName} documents matching a set of query terms`,

      async resolver(root, { input = {} }, context, { cacheControl }) {
        const { terms = {}, enableCache = false, enableTotal = true } = input;

        debug('');
        debugGroup(
          `--------------- start \x1b[35m${typeName} Multi Resolver\x1b[0m ---------------`
        );
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`Terms: ${JSON.stringify(terms)}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        // get currentUser and Users collection from context
        const { currentUser, Users } = context;

        // get collection based on collectionName argument
        const collection = context[collectionName];

        // get selector and options from terms and perform Mongo query

        let { selector, options, filteredFields } = isEmpty(terms)
          ? Connectors.filter(collection, input)
          : await collection.getParameters(terms, {}, context);

        // make sure all filtered fields are allowed
        Users.checkFields(currentUser, collection, filteredFields);

        options.skip = terms.offset;

        debug({ selector, options });

        const docs = await Connectors.find(collection, selector, options);

        // if collection has a checkAccess function defined, remove any documents that doesn't pass the check
        const viewableDocs = collection.checkAccess
          ? _.filter(docs, doc => collection.checkAccess(currentUser, doc))
          : docs;

        // take the remaining documents and remove any fields that shouldn't be accessible
        const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);

        // prime the cache
        restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));

        debug(`\x1b[33m=> ${restrictedDocs.length} documents returned\x1b[0m`);
        debugGroupEnd();
        debug(`--------------- end \x1b[35m${typeName} Multi Resolver\x1b[0m ---------------`);
        debug('');

        const data = { results: restrictedDocs };

        if (enableTotal) {
          // get total count of documents matching the selector
          data.totalCount = await Connectors.count(collection, selector);
        } else {
          data.totalCount = null;
        }

        // return results
        return data;
      },
    },

    // resolver for returning a single document queried based on id or slug

    single: {
      description: `A single ${typeName} document fetched by ID or slug`,

      async resolver(root, { input = {} }, context, { cacheControl }) {
        const { selector: oldSelector = {}, enableCache = false, allowNull = false } = input;

        let doc;

        debug('');
        debugGroup(
          `--------------- start \x1b[35m${typeName} Single Resolver\x1b[0m ---------------`
        );
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`Selector: ${JSON.stringify(oldSelector)}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        const { currentUser, Users } = context;
        const collection = context[collectionName];

        // use Dataloader if doc is selected by documentId/_id
        const documentId = oldSelector.documentId || oldSelector._id;

        if (documentId) {
          doc = await collection.loader.load(documentId);
        } else {
          let { selector, filteredFields } = Connectors.filter(collection, input);

          // make sure all filtered fields are allowed
          Users.checkFields(currentUser, collection, filteredFields);

          doc = await Connectors.get(collection, selector);
        }

        if (!doc) {
          if (allowNull) {
            return { result: null };
          } else {
            throwError({
              id: 'app.missing_document',
              data: { documentId, oldSelector },
            });
          }
        }

        // if collection has a checkAccess function defined, use it to perform a check on the current document
        // (will throw an error if check doesn't pass)
        if (collection.checkAccess) {
          Utils.performCheck(
            collection.checkAccess,
            currentUser,
            doc,
            collection,
            documentId,
            `${typeName}.read.single`,
            collectionName
          );
        }

        const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);

        debugGroupEnd();
        debug(`--------------- end \x1b[35m${typeName} Single Resolver\x1b[0m ---------------`);
        debug('');

        // filter out disallowed properties and return resulting document
        return { result: restrictedDoc };
      },
    },
  };
}

@@ -4601,16 +4601,55 @@
 tor._id;
+%0A        const slug = oldSelector.slug;
 %0A%0A      
@@ -4726,16 +4726,143 @@
 entId);%0A
+        %7D else if (slug) %7B%0A          // make an exception for slug%0A          doc = await Connectors.get(collection, %7B slug %7D);%0A
         

Fix slug-based selector for single default resolver