2017-07-03 10:54:10 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Default list, single, and total resolvers
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2018-06-17 07:49:33 +09:00
|
|
|
import { Utils, debug, debugGroup, debugGroupEnd, Connectors, getTypeName, getCollectionName } from 'meteor/vulcan:lib';
|
2018-01-04 09:41:48 +09:00
|
|
|
import { createError } from 'apollo-errors';
|
2017-09-15 11:14:09 +02:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
const defaultOptions = {
|
2018-01-29 10:09:31 +09:00
|
|
|
cacheMaxAge: 300,
|
|
|
|
};
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-06-02 18:49:53 +09:00
|
|
|
// note: for some reason changing resolverOptions to "options" throws error
|
2018-06-17 07:49:33 +09:00
|
|
|
export function getDefaultResolvers(options) {
|
|
|
|
|
|
|
|
let typeName, collectionName, resolverOptions;
|
|
|
|
if (typeof arguments[0] === 'object') {
|
|
|
|
// new single-argument API
|
|
|
|
typeName = arguments[0].typeName;
|
2018-08-10 11:24:38 +02:00
|
|
|
collectionName = arguments[0].collectionName || getCollectionName(typeName);
|
merge mutation and resolver options
This makes possible to only specify a subset of options.
As an example, imagine we want to pass a customized `updateCheck` function to `getDefaultMutations`. Before this commit options weren't merged, so all of them had to be specified:
```
getDefaultMutations({
typeName,
collectionName,
options: {
create: true,
update: true,
upsert: true,
delete: true,
updateCheck: () => true,
});
```
After the commit:
```
getDefaultMutations({
typeName,
collectionName,
options: {
updateCheck: () => true,
});
```
2018-08-10 11:39:20 +02:00
|
|
|
resolverOptions = { ...defaultOptions, ...arguments[0].options };
|
2018-06-17 07:49:33 +09:00
|
|
|
} else {
|
|
|
|
// OpenCRUD backwards compatibility
|
|
|
|
collectionName = arguments[0];
|
|
|
|
typeName = getTypeName(collectionName);
|
merge mutation and resolver options
This makes possible to only specify a subset of options.
As an example, imagine we want to pass a customized `updateCheck` function to `getDefaultMutations`. Before this commit options weren't merged, so all of them had to be specified:
```
getDefaultMutations({
typeName,
collectionName,
options: {
create: true,
update: true,
upsert: true,
delete: true,
updateCheck: () => true,
});
```
After the commit:
```
getDefaultMutations({
typeName,
collectionName,
options: {
updateCheck: () => true,
});
```
2018-08-10 11:39:20 +02:00
|
|
|
resolverOptions = { ...defaultOptions, ...arguments[1] };
|
2018-06-17 07:49:33 +09:00
|
|
|
}
|
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
return {
|
|
|
|
// resolver for returning a list of documents based on a set of query terms
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-06-05 10:04:20 +09:00
|
|
|
multi: {
|
2018-06-02 18:49:53 +09:00
|
|
|
description: `A list of ${typeName} documents matching a set of query terms`,
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-06-05 12:12:04 +09:00
|
|
|
async resolver(root, { input = {} }, context, { cacheControl }) {
|
2018-06-08 13:31:15 +09:00
|
|
|
const { terms = {}, enableCache = false, enableTotal = true } = input;
|
2018-06-06 09:07:13 +09:00
|
|
|
|
2018-01-29 10:09:31 +09:00
|
|
|
debug('');
|
2018-06-25 08:41:44 +02:00
|
|
|
debugGroup(`--------------- start \x1b[35m${typeName} Multi Resolver\x1b[0m ---------------`);
|
2018-01-06 13:46:31 +01:00
|
|
|
debug(`Options: ${JSON.stringify(resolverOptions)}`);
|
2018-04-24 10:21:20 +09:00
|
|
|
debug(`Terms: ${JSON.stringify(terms)}`);
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-12-13 18:39:19 +09:00
|
|
|
if (cacheControl && enableCache) {
|
2017-12-08 20:54:23 +09:00
|
|
|
const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
|
|
|
|
cacheControl.setCacheHint({ maxAge });
|
2017-11-30 23:08:27 +09:00
|
|
|
}
|
2017-09-15 11:14:09 +02:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// get currentUser and Users collection from context
|
|
|
|
const { currentUser, Users } = context;
|
2018-06-17 07:49:33 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// get collection based on collectionName argument
|
|
|
|
const collection = context[collectionName];
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// get selector and options from terms and perform Mongo query
|
2018-01-06 13:40:40 +01:00
|
|
|
let { selector, options } = await collection.getParameters(terms, {}, context);
|
2017-11-30 23:08:27 +09:00
|
|
|
options.skip = terms.offset;
|
|
|
|
|
|
|
|
debug({ selector, options });
|
|
|
|
|
2018-03-03 11:39:56 +09:00
|
|
|
const docs = await Connectors.find(collection, selector, options);
|
2017-11-30 23:08:27 +09:00
|
|
|
|
|
|
|
// if collection has a checkAccess function defined, remove any documents that doesn't pass the check
|
2018-01-29 10:09:31 +09:00
|
|
|
const viewableDocs = collection.checkAccess
|
|
|
|
? _.filter(docs, doc => collection.checkAccess(currentUser, doc))
|
|
|
|
: docs;
|
2018-01-06 13:40:40 +01:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// take the remaining documents and remove any fields that shouldn't be accessible
|
|
|
|
const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// prime the cache
|
|
|
|
restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-01-06 13:40:40 +01:00
|
|
|
debug(`\x1b[33m=> ${restrictedDocs.length} documents returned\x1b[0m`);
|
|
|
|
debugGroupEnd();
|
2018-06-25 08:41:44 +02:00
|
|
|
debug(`--------------- end \x1b[35m${typeName} Multi Resolver\x1b[0m ---------------`);
|
2018-01-29 10:09:31 +09:00
|
|
|
debug('');
|
2017-11-30 23:08:27 +09:00
|
|
|
|
2018-06-08 13:31:15 +09:00
|
|
|
const data = { results: restrictedDocs };
|
|
|
|
|
|
|
|
if (enableTotal) {
|
|
|
|
// get total count of documents matching the selector
|
|
|
|
data.totalCount = await Connectors.count(collection, selector);
|
|
|
|
}
|
2018-06-05 11:51:25 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// return results
|
2018-06-08 13:31:15 +09:00
|
|
|
return data;
|
2017-11-30 23:08:27 +09:00
|
|
|
},
|
2017-07-03 10:54:10 +09:00
|
|
|
},
|
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// resolver for returning a single document queried based on id or slug
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
single: {
|
2018-06-02 18:49:53 +09:00
|
|
|
description: `A single ${typeName} document fetched by ID or slug`,
|
2018-01-02 13:04:33 +09:00
|
|
|
|
2018-06-05 12:12:04 +09:00
|
|
|
async resolver(root, { input = {} }, context, { cacheControl }) {
|
2018-09-10 06:16:00 -04:00
|
|
|
const { selector = {}, enableCache = false, allowNull = false } = input;
|
2018-06-05 12:12:04 +09:00
|
|
|
|
2018-01-29 10:09:31 +09:00
|
|
|
debug('');
|
2018-06-25 08:41:44 +02:00
|
|
|
debugGroup(`--------------- start \x1b[35m${typeName} Single Resolver\x1b[0m ---------------`);
|
2018-01-06 13:46:31 +01:00
|
|
|
debug(`Options: ${JSON.stringify(resolverOptions)}`);
|
2018-08-29 20:36:30 +09:00
|
|
|
debug(`Selector: ${JSON.stringify(selector)}`);
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-12-13 18:39:19 +09:00
|
|
|
if (cacheControl && enableCache) {
|
2017-12-08 20:54:23 +09:00
|
|
|
const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
|
|
|
|
cacheControl.setCacheHint({ maxAge });
|
2017-11-30 23:08:27 +09:00
|
|
|
}
|
2017-09-15 11:14:09 +02:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
const { currentUser, Users } = context;
|
|
|
|
const collection = context[collectionName];
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-08-29 20:36:30 +09:00
|
|
|
// use Dataloader if doc is selected by documentId/_id
|
|
|
|
const documentId = selector.documentId || selector._id;
|
2018-01-29 10:09:31 +09:00
|
|
|
const doc = documentId
|
|
|
|
? await collection.loader.load(documentId)
|
2018-08-29 20:36:30 +09:00
|
|
|
: await Connectors.get(collection, selector);
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2018-01-04 09:41:48 +09:00
|
|
|
if (!doc) {
|
2018-09-10 06:16:00 -04:00
|
|
|
if (allowNull) {
|
2018-09-10 06:48:14 -04:00
|
|
|
return { result: null };
|
2018-09-10 06:16:00 -04:00
|
|
|
} else {
|
|
|
|
const MissingDocumentError = createError('app.missing_document', { message: 'app.missing_document' });
|
2018-09-13 10:46:10 -04:00
|
|
|
throw new MissingDocumentError({ data: { documentId, selector } });
|
2018-09-10 06:16:00 -04:00
|
|
|
}
|
2018-01-04 09:41:48 +09:00
|
|
|
}
|
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// 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);
|
|
|
|
}
|
2017-07-03 10:54:10 +09:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);
|
2017-09-15 11:14:09 +02:00
|
|
|
|
2018-01-06 13:40:40 +01:00
|
|
|
debugGroupEnd();
|
2018-06-25 08:41:44 +02:00
|
|
|
debug(`--------------- end \x1b[35m${typeName} Single Resolver\x1b[0m ---------------`);
|
2018-01-29 10:09:31 +09:00
|
|
|
debug('');
|
2017-09-15 11:14:09 +02:00
|
|
|
|
2017-11-30 23:08:27 +09:00
|
|
|
// filter out disallowed properties and return resulting document
|
2018-06-05 11:51:25 +09:00
|
|
|
return { result: restrictedDoc };
|
2017-11-30 23:08:27 +09:00
|
|
|
},
|
2017-07-03 10:54:10 +09:00
|
|
|
},
|
2018-01-29 10:09:31 +09:00
|
|
|
};
|
2018-09-10 06:16:00 -04:00
|
|
|
}
|