2018-09-10 16:05:00 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GraphQL.js service
|
|
|
|
*
|
|
|
|
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const _ = require('lodash');
|
2019-11-28 16:10:19 +01:00
|
|
|
|
|
|
|
const DynamicZoneScalar = require('../types/dynamiczoneScalar');
|
2019-11-04 11:29:19 +01:00
|
|
|
|
2018-09-10 16:05:00 +08:00
|
|
|
const Aggregator = require('./Aggregator');
|
|
|
|
const Types = require('./Types.js');
|
|
|
|
const Schema = require('./Schema.js');
|
2020-02-10 14:19:54 +01:00
|
|
|
const {
|
|
|
|
mergeSchemas,
|
|
|
|
convertToParams,
|
|
|
|
convertToQuery,
|
|
|
|
amountLimiting,
|
|
|
|
} = require('./utils');
|
2020-02-10 17:51:31 +01:00
|
|
|
const { toSDL, getTypeDescription } = require('./schema-definitions');
|
2019-11-04 11:29:19 +01:00
|
|
|
const { toSingular, toPlural } = require('./naming');
|
2020-01-24 15:01:17 +01:00
|
|
|
|
2020-01-27 11:23:41 +01:00
|
|
|
const isQueryEnabled = (schema, name) => {
|
|
|
|
return _.get(schema, ['resolver', 'Query', name]) !== false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const isMutationEnabled = (schema, name) => {
|
|
|
|
return _.get(schema, ['resolver', 'Mutation', name]) !== false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildTypeDefObj = model => {
|
|
|
|
const { associations = [], attributes, primaryKey, globalId } = model;
|
|
|
|
|
|
|
|
const typeDef = {
|
|
|
|
id: 'ID!',
|
|
|
|
[primaryKey]: 'ID!',
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add timestamps attributes.
|
|
|
|
if (_.isArray(_.get(model, 'options.timestamps'))) {
|
|
|
|
const [createdAtKey, updatedAtKey] = model.options.timestamps;
|
|
|
|
typeDef[createdAtKey] = 'DateTime!';
|
|
|
|
typeDef[updatedAtKey] = 'DateTime!';
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys(attributes)
|
|
|
|
.filter(attributeName => attributes[attributeName].private !== true)
|
|
|
|
.forEach(attributeName => {
|
|
|
|
const attribute = attributes[attributeName];
|
2019-07-17 15:45:26 +02:00
|
|
|
// Convert our type to the GraphQL type.
|
2020-01-27 11:23:41 +01:00
|
|
|
typeDef[attributeName] = Types.convertType({
|
|
|
|
attribute,
|
2019-07-17 15:45:26 +02:00
|
|
|
modelName: globalId,
|
2020-01-27 11:23:41 +01:00
|
|
|
attributeName,
|
2019-07-17 15:45:26 +02:00
|
|
|
});
|
2020-01-27 11:23:41 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// Change field definition for collection relations
|
|
|
|
associations
|
|
|
|
.filter(association => association.type === 'collection')
|
|
|
|
.forEach(association => {
|
|
|
|
typeDef[
|
|
|
|
`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`
|
|
|
|
] = typeDef[association.alias];
|
|
|
|
|
|
|
|
delete typeDef[association.alias];
|
|
|
|
});
|
|
|
|
|
|
|
|
return typeDef;
|
2019-07-18 10:55:13 +02:00
|
|
|
};
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-07-18 10:55:13 +02:00
|
|
|
const generateEnumDefinitions = (attributes, globalId) => {
|
|
|
|
return Object.keys(attributes)
|
2019-07-17 15:45:26 +02:00
|
|
|
.filter(attribute => attributes[attribute].type === 'enumeration')
|
|
|
|
.map(attribute => {
|
|
|
|
const definition = attributes[attribute];
|
|
|
|
|
2019-07-18 10:55:13 +02:00
|
|
|
const name = Types.convertEnumType(definition, globalId, attribute);
|
|
|
|
const values = definition.enum.map(v => `\t${v}`).join('\n');
|
|
|
|
return `enum ${name} {\n${values}\n}\n`;
|
2019-07-17 15:45:26 +02:00
|
|
|
})
|
2019-07-18 10:55:13 +02:00
|
|
|
.join('');
|
|
|
|
};
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
const generateDynamicZoneDefinitions = (attributes, globalId, schema) => {
|
|
|
|
Object.keys(attributes)
|
|
|
|
.filter(attribute => attributes[attribute].type === 'dynamiczone')
|
|
|
|
.forEach(attribute => {
|
|
|
|
const { components } = attributes[attribute];
|
|
|
|
|
|
|
|
const typeName = `${globalId}${_.upperFirst(
|
|
|
|
_.camelCase(attribute)
|
|
|
|
)}DynamicZone`;
|
|
|
|
|
|
|
|
if (components.length === 0) {
|
|
|
|
// Create dummy type because graphql doesn't support empty ones
|
|
|
|
|
2019-11-28 16:17:46 +01:00
|
|
|
schema.definition += `type ${typeName} { _:Boolean}`;
|
|
|
|
schema.definition += `\nscalar EmptyQuery\n`;
|
|
|
|
} else {
|
|
|
|
const componentsTypeNames = components.map(componentUID => {
|
|
|
|
const compo = strapi.components[componentUID];
|
|
|
|
if (!compo) {
|
|
|
|
throw new Error(
|
|
|
|
`Trying to creating dynamiczone type with unkown component ${componentUID}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return compo.globalId;
|
|
|
|
});
|
|
|
|
|
|
|
|
const unionType = `union ${typeName} = ${componentsTypeNames.join(
|
|
|
|
' | '
|
|
|
|
)}`;
|
|
|
|
|
|
|
|
schema.definition += `\n${unionType}\n`;
|
|
|
|
}
|
2019-11-28 15:33:37 +01:00
|
|
|
|
|
|
|
const inputTypeName = `${typeName}Input`;
|
2019-11-28 16:17:46 +01:00
|
|
|
schema.definition += `\nscalar ${inputTypeName}\n`;
|
2019-11-28 15:33:37 +01:00
|
|
|
|
|
|
|
schema.resolvers[typeName] = {
|
|
|
|
__resolveType(obj) {
|
|
|
|
return strapi.components[obj.__component].globalId;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-11-28 16:10:19 +01:00
|
|
|
schema.resolvers[inputTypeName] = new DynamicZoneScalar({
|
2019-11-28 15:33:37 +01:00
|
|
|
name: inputTypeName,
|
2019-11-28 16:10:19 +01:00
|
|
|
attribute,
|
|
|
|
globalId,
|
|
|
|
components,
|
2019-11-28 15:33:37 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
const buildAssocResolvers = model => {
|
2019-07-18 10:55:13 +02:00
|
|
|
const contentManager =
|
|
|
|
strapi.plugins['content-manager'].services['contentmanager'];
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-07-18 10:55:13 +02:00
|
|
|
const { primaryKey, associations = [] } = model;
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-11-07 09:19:35 +01:00
|
|
|
return associations
|
2019-11-28 15:33:37 +01:00
|
|
|
.filter(association => model.attributes[association.alias].private !== true)
|
|
|
|
.reduce((resolver, association) => {
|
2019-12-10 16:21:21 +01:00
|
|
|
const target = association.model || association.collection;
|
|
|
|
const targetModel = strapi.getModel(target, association.plugin);
|
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
switch (association.nature) {
|
2019-12-10 16:21:21 +01:00
|
|
|
case 'oneToManyMorph':
|
|
|
|
case 'manyMorphToOne':
|
|
|
|
case 'manyMorphToMany':
|
|
|
|
case 'manyToManyMorph': {
|
2019-11-28 15:33:37 +01:00
|
|
|
resolver[association.alias] = async obj => {
|
2019-12-10 16:21:21 +01:00
|
|
|
if (obj[association.alias]) {
|
|
|
|
return obj[association.alias];
|
|
|
|
}
|
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
const entry = await contentManager.fetch(
|
2019-07-17 15:45:26 +02:00
|
|
|
{
|
2019-07-18 10:55:13 +02:00
|
|
|
id: obj[primaryKey],
|
2019-12-10 16:21:21 +01:00
|
|
|
model: model.uid,
|
2019-07-17 15:45:26 +02:00
|
|
|
},
|
2019-11-28 15:33:37 +01:00
|
|
|
[association.alias]
|
|
|
|
);
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
return entry[association.alias];
|
2019-07-17 15:45:26 +02:00
|
|
|
};
|
2019-11-28 15:33:37 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
resolver[association.alias] = async (obj, options) => {
|
|
|
|
// Construct parameters object to retrieve the correct related entries.
|
|
|
|
const params = {
|
2019-12-10 16:21:21 +01:00
|
|
|
model: targetModel.uid,
|
2019-11-28 15:33:37 +01:00
|
|
|
};
|
2019-07-18 10:55:13 +02:00
|
|
|
|
2019-12-10 16:21:21 +01:00
|
|
|
let queryOpts = {};
|
2019-11-28 15:33:37 +01:00
|
|
|
|
|
|
|
if (association.type === 'model') {
|
2019-12-10 16:21:21 +01:00
|
|
|
params[targetModel.primaryKey] = _.get(
|
2019-11-28 15:33:37 +01:00
|
|
|
obj,
|
2019-12-10 16:21:21 +01:00
|
|
|
[association.alias, targetModel.primaryKey],
|
2019-10-01 17:56:52 +02:00
|
|
|
obj[association.alias]
|
2019-07-18 10:55:13 +02:00
|
|
|
);
|
|
|
|
} else {
|
2020-02-10 14:19:54 +01:00
|
|
|
const queryParams = amountLimiting(options);
|
2019-11-28 15:33:37 +01:00
|
|
|
queryOpts = {
|
|
|
|
...queryOpts,
|
2020-02-10 14:19:54 +01:00
|
|
|
...convertToParams(_.omit(queryParams, 'where')), // Convert filters (sort, limit and start/skip)
|
|
|
|
...convertToQuery(queryParams.where),
|
2019-11-28 15:33:37 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if (
|
|
|
|
((association.nature === 'manyToMany' &&
|
|
|
|
association.dominant) ||
|
|
|
|
association.nature === 'manyWay') &&
|
|
|
|
_.has(obj, association.alias) // if populated
|
|
|
|
) {
|
|
|
|
_.set(
|
|
|
|
queryOpts,
|
2019-12-10 16:21:21 +01:00
|
|
|
['query', targetModel.primaryKey],
|
2019-11-28 15:33:37 +01:00
|
|
|
obj[association.alias]
|
|
|
|
? obj[association.alias]
|
2019-12-10 16:21:21 +01:00
|
|
|
.map(val => val[targetModel.primaryKey] || val)
|
2019-11-28 15:33:37 +01:00
|
|
|
.sort()
|
|
|
|
: []
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
_.set(
|
|
|
|
queryOpts,
|
|
|
|
['query', association.via],
|
2019-12-10 16:21:21 +01:00
|
|
|
obj[targetModel.primaryKey]
|
2019-11-28 15:33:37 +01:00
|
|
|
);
|
|
|
|
}
|
2019-07-18 10:55:13 +02:00
|
|
|
}
|
2019-07-17 15:45:26 +02:00
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
return association.model
|
|
|
|
? strapi.plugins.graphql.services.loaders.loaders[
|
2019-12-10 16:21:21 +01:00
|
|
|
targetModel.uid
|
2019-11-28 15:33:37 +01:00
|
|
|
].load({
|
|
|
|
params,
|
|
|
|
options: queryOpts,
|
|
|
|
single: true,
|
|
|
|
})
|
|
|
|
: strapi.plugins.graphql.services.loaders.loaders[
|
2019-12-10 16:21:21 +01:00
|
|
|
targetModel.uid
|
2019-11-28 15:33:37 +01:00
|
|
|
].load({
|
|
|
|
options: queryOpts,
|
|
|
|
association,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2019-07-18 10:55:13 +02:00
|
|
|
}
|
|
|
|
|
2019-11-28 15:33:37 +01:00
|
|
|
return resolver;
|
|
|
|
}, {});
|
2019-07-18 10:55:13 +02:00
|
|
|
};
|
|
|
|
|
2019-03-25 16:37:46 +01:00
|
|
|
/**
|
|
|
|
* Construct the GraphQL query & definition and apply the right resolvers.
|
|
|
|
*
|
|
|
|
* @return Object
|
|
|
|
*/
|
2020-02-10 18:48:57 +01:00
|
|
|
const buildModels = models => {
|
|
|
|
return models.map(model => {
|
|
|
|
const { kind, modelType } = model;
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-02-10 18:48:57 +01:00
|
|
|
if (modelType === 'component') {
|
|
|
|
return buildComponent(model);
|
|
|
|
}
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
switch (kind) {
|
2020-01-27 15:11:16 +01:00
|
|
|
case 'singleType':
|
|
|
|
return buildSingleType(model);
|
2020-01-24 15:01:17 +01:00
|
|
|
default:
|
|
|
|
return buildCollectionType(model);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-02-10 17:51:31 +01:00
|
|
|
const buildModelDefinition = (model, globalType = {}) => {
|
2020-01-30 17:27:11 +01:00
|
|
|
const { globalId, primaryKey } = model;
|
2020-01-27 15:11:16 +01:00
|
|
|
|
2020-01-30 17:27:11 +01:00
|
|
|
const schema = {
|
2020-01-27 15:11:16 +01:00
|
|
|
definition: '',
|
|
|
|
query: {},
|
|
|
|
mutation: {},
|
|
|
|
resolvers: {
|
|
|
|
Query: {},
|
|
|
|
Mutation: {},
|
|
|
|
[globalId]: {
|
2020-01-30 17:27:11 +01:00
|
|
|
id: parent => parent[primaryKey] || parent.id,
|
2020-01-27 15:11:16 +01:00
|
|
|
...buildAssocResolvers(model),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const typeDefObj = buildTypeDefObj(model);
|
|
|
|
|
2020-01-30 17:27:11 +01:00
|
|
|
schema.definition += generateEnumDefinitions(model.attributes, globalId);
|
|
|
|
generateDynamicZoneDefinitions(model.attributes, globalId, schema);
|
2020-01-27 15:11:16 +01:00
|
|
|
|
2020-02-10 17:51:31 +01:00
|
|
|
const description = getTypeDescription(globalType, model);
|
|
|
|
const fields = toSDL(typeDefObj, globalType, model);
|
2020-01-27 15:11:16 +01:00
|
|
|
const typeDef = `${description}type ${globalId} {${fields}}\n`;
|
|
|
|
|
2020-01-30 17:27:11 +01:00
|
|
|
schema.definition += typeDef;
|
|
|
|
return schema;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildComponent = component => {
|
|
|
|
const { globalId } = component;
|
|
|
|
const schema = buildModelDefinition(component);
|
|
|
|
|
|
|
|
schema.definition += Types.generateInputModel(component, globalId, {
|
|
|
|
allowIds: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
return schema;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildSingleType = model => {
|
|
|
|
const { uid, modelName } = model;
|
|
|
|
|
|
|
|
const singularName = toSingular(modelName);
|
|
|
|
|
|
|
|
const _schema = _.cloneDeep(
|
|
|
|
_.get(strapi.plugins, 'graphql.config._schema.graphql', {})
|
|
|
|
);
|
|
|
|
|
|
|
|
const globalType = _.get(_schema, ['type', model.globalId], {});
|
|
|
|
|
2020-02-10 17:51:31 +01:00
|
|
|
const localSchema = buildModelDefinition(model, globalType);
|
2020-01-27 15:11:16 +01:00
|
|
|
|
|
|
|
// Add definition to the schema but this type won't be "queriable" or "mutable".
|
|
|
|
if (globalType === false) {
|
|
|
|
return localSchema;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isQueryEnabled(_schema, singularName)) {
|
|
|
|
_.merge(localSchema, {
|
|
|
|
query: {
|
|
|
|
// TODO: support all the unique fields
|
|
|
|
[singularName]: model.globalId,
|
|
|
|
},
|
|
|
|
resolvers: {
|
|
|
|
Query: {
|
2020-01-31 17:40:02 +01:00
|
|
|
[singularName]: Schema.buildQuery(singularName, {
|
|
|
|
resolver: `${uid}.find`,
|
|
|
|
..._.get(_schema, ['resolver', 'Query', singularName], {}),
|
|
|
|
}),
|
2020-01-27 15:11:16 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add model Input definition.
|
|
|
|
localSchema.definition += Types.generateInputModel(model, modelName);
|
|
|
|
|
|
|
|
// build every mutation
|
|
|
|
['update', 'delete'].forEach(action => {
|
|
|
|
const mutationScheam = buildMutation({ model, action }, { _schema });
|
|
|
|
|
|
|
|
mergeSchemas(localSchema, mutationScheam);
|
|
|
|
});
|
|
|
|
|
|
|
|
return localSchema;
|
|
|
|
};
|
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
const buildCollectionType = model => {
|
2020-01-31 17:40:02 +01:00
|
|
|
const { globalId, plugin, modelName, uid } = model;
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
const singularName = toSingular(modelName);
|
|
|
|
const pluralName = toPlural(modelName);
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
const _schema = _.cloneDeep(
|
|
|
|
_.get(strapi.plugins, 'graphql.config._schema.graphql', {})
|
|
|
|
);
|
2019-07-17 13:13:07 +02:00
|
|
|
|
2020-01-27 11:23:41 +01:00
|
|
|
const globalType = _.get(_schema, ['type', model.globalId], {});
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
const localSchema = {
|
|
|
|
definition: '',
|
|
|
|
query: {},
|
|
|
|
mutation: {},
|
2020-01-27 11:23:41 +01:00
|
|
|
resolvers: {
|
|
|
|
Query: {},
|
|
|
|
Mutation: {},
|
|
|
|
// define default resolver for this model
|
|
|
|
[globalId]: {
|
|
|
|
id: parent => parent[model.primaryKey] || parent.id,
|
|
|
|
...buildAssocResolvers(model),
|
|
|
|
},
|
2020-01-24 15:01:17 +01:00
|
|
|
},
|
|
|
|
};
|
2019-07-18 10:55:13 +02:00
|
|
|
|
2020-01-27 11:23:41 +01:00
|
|
|
const typeDefObj = buildTypeDefObj(model);
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
localSchema.definition += generateEnumDefinitions(model.attributes, globalId);
|
|
|
|
generateDynamicZoneDefinitions(model.attributes, globalId, localSchema);
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-02-10 17:51:31 +01:00
|
|
|
const description = getTypeDescription(globalType, model);
|
|
|
|
const fields = toSDL(typeDefObj, globalType, model);
|
2020-01-24 15:01:17 +01:00
|
|
|
const typeDef = `${description}type ${globalId} {${fields}}\n`;
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
localSchema.definition += typeDef;
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
// Add definition to the schema but this type won't be "queriable" or "mutable".
|
2020-01-27 11:23:41 +01:00
|
|
|
if (globalType === false) {
|
2020-01-24 15:01:17 +01:00
|
|
|
return localSchema;
|
|
|
|
}
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
if (isQueryEnabled(_schema, singularName)) {
|
2020-01-24 15:01:17 +01:00
|
|
|
_.merge(localSchema, {
|
|
|
|
query: {
|
|
|
|
[`${singularName}(id: ID!)`]: model.globalId,
|
|
|
|
},
|
|
|
|
resolvers: {
|
|
|
|
Query: {
|
2020-01-31 17:40:02 +01:00
|
|
|
[singularName]: Schema.buildQuery(singularName, {
|
|
|
|
resolver: `${uid}.findOne`,
|
|
|
|
..._.get(_schema, ['resolver', 'Query', singularName], {}),
|
|
|
|
}),
|
2020-01-24 15:01:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
if (isQueryEnabled(_schema, pluralName)) {
|
2020-01-31 17:40:02 +01:00
|
|
|
const resolverOpts = {
|
|
|
|
resolver: `${uid}.find`,
|
|
|
|
..._.get(_schema, ['resolver', 'Query', pluralName], {}),
|
2020-01-29 15:30:53 +01:00
|
|
|
};
|
|
|
|
|
2020-01-31 17:40:02 +01:00
|
|
|
const resolverFn = Schema.buildQuery(pluralName, resolverOpts);
|
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
_.merge(localSchema, {
|
|
|
|
query: {
|
|
|
|
[`${pluralName}(sort: String, limit: Int, start: Int, where: JSON)`]: `[${model.globalId}]`,
|
|
|
|
},
|
|
|
|
resolvers: {
|
|
|
|
Query: {
|
2020-01-31 17:40:02 +01:00
|
|
|
[pluralName]: resolverFn,
|
2020-01-24 15:01:17 +01:00
|
|
|
},
|
|
|
|
},
|
2019-03-25 16:37:46 +01:00
|
|
|
});
|
2020-01-30 10:49:42 +01:00
|
|
|
|
|
|
|
// TODO: Add support for Graphql Aggregation in Bookshelf ORM
|
|
|
|
if (model.orm === 'mongoose') {
|
|
|
|
// Generation the aggregation for the given model
|
|
|
|
const aggregationSchema = Aggregator.formatModelConnectionsGQL({
|
|
|
|
fields: typeDefObj,
|
|
|
|
model,
|
|
|
|
name: modelName,
|
2020-01-31 17:40:02 +01:00
|
|
|
resolver: resolverOpts,
|
2020-01-30 10:49:42 +01:00
|
|
|
plugin,
|
|
|
|
});
|
|
|
|
|
|
|
|
mergeSchemas(localSchema, aggregationSchema);
|
|
|
|
}
|
2020-01-24 15:01:17 +01:00
|
|
|
}
|
2019-03-25 16:37:46 +01:00
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
// Add model Input definition.
|
|
|
|
localSchema.definition += Types.generateInputModel(model, modelName);
|
|
|
|
|
|
|
|
// build every mutation
|
|
|
|
['create', 'update', 'delete'].forEach(action => {
|
|
|
|
const mutationScheam = buildMutation({ model, action }, { _schema });
|
2018-09-10 16:05:00 +08:00
|
|
|
|
2020-01-27 10:30:52 +01:00
|
|
|
mergeSchemas(localSchema, mutationScheam);
|
2020-01-24 15:01:17 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return localSchema;
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// - Implement batch methods (need to update the content-manager as well).
|
|
|
|
// - Implement nested transactional methods (create/update).
|
|
|
|
const buildMutation = ({ model, action }, { _schema }) => {
|
2020-01-29 15:30:53 +01:00
|
|
|
const { uid } = model;
|
2020-01-24 15:01:17 +01:00
|
|
|
const capitalizedName = _.upperFirst(toSingular(model.modelName));
|
|
|
|
const mutationName = `${action}${capitalizedName}`;
|
|
|
|
|
|
|
|
const definition = Types.generateInputPayloadArguments({
|
|
|
|
model,
|
|
|
|
name: model.modelName,
|
|
|
|
mutationName,
|
|
|
|
action,
|
|
|
|
});
|
|
|
|
|
|
|
|
// ignore if disabled
|
2020-01-27 11:23:41 +01:00
|
|
|
if (!isMutationEnabled(_schema, mutationName)) {
|
2020-01-24 15:01:17 +01:00
|
|
|
return {
|
|
|
|
definition,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-01-30 15:50:31 +01:00
|
|
|
const { kind } = model;
|
|
|
|
|
|
|
|
let mutationDef = `${mutationName}(input: ${mutationName}Input)`;
|
|
|
|
if (kind === 'singleType' && action === 'delete') {
|
|
|
|
mutationDef = mutationName;
|
|
|
|
}
|
|
|
|
|
2020-01-24 15:01:17 +01:00
|
|
|
return {
|
|
|
|
definition,
|
|
|
|
mutation: {
|
2020-01-30 15:50:31 +01:00
|
|
|
[mutationDef]: `${mutationName}Payload`,
|
2020-01-24 15:01:17 +01:00
|
|
|
},
|
|
|
|
resolvers: {
|
|
|
|
Mutation: {
|
2020-01-31 17:40:02 +01:00
|
|
|
[mutationName]: Schema.buildMutation(mutationName, {
|
2020-01-29 15:30:53 +01:00
|
|
|
resolver: `${uid}.${action}`,
|
|
|
|
transformOutput: result => ({
|
|
|
|
[toSingular(model.modelName)]: result,
|
|
|
|
}),
|
2020-01-31 17:40:02 +01:00
|
|
|
..._.get(_schema, ['resolver', 'Mutation', mutationName], {}),
|
|
|
|
}),
|
2020-01-24 15:01:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
2019-03-25 16:37:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
2020-02-10 18:48:57 +01:00
|
|
|
buildModels,
|
2018-09-10 16:05:00 +08:00
|
|
|
};
|