Cleanup graphql and use model uids

This commit is contained in:
Alexandre Bodin 2019-12-10 16:21:21 +01:00
parent 9b5bc9c367
commit b5cceb8760
8 changed files with 108 additions and 146 deletions

View File

@ -20,6 +20,15 @@
"via": "related", "via": "related",
"plugin": "upload" "plugin": "upload"
}, },
"images": {
"collection": "file",
"via": "related",
"plugin": "upload"
},
"author": {
"model": "user",
"plugin": "users-permissions"
},
"metas": { "metas": {
"type": "component", "type": "component",
"component": "default.metas", "component": "default.metas",

View File

@ -547,11 +547,10 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
: relation; : relation;
// Retrieve opposite model. // Retrieve opposite model.
const model = association.plugin const model = strapi.getModel(
? strapi.plugins[association.plugin].models[ association.collection || association.model,
association.collection || association.model association.plugin
] );
: strapi.models[association.collection || association.model];
// Reformat data by bypassing the many-to-many relationship. // Reformat data by bypassing the many-to-many relationship.
switch (association.nature) { switch (association.nature) {
@ -564,15 +563,36 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
rel => rel[model.collectionName] rel => rel[model.collectionName]
); );
break; break;
case 'oneMorphToOne': case 'oneMorphToOne': {
attrs[association.alias] = const obj = attrs[association.alias];
attrs[association.alias].related || null;
if (obj === undefined || obj === null) {
break;
}
const contentType = strapi.db.getModelByCollectionName(
obj[`${association.alias}_type`]
);
attrs[association.alias] = {
__contentType: contentType ? contentType.globalId : null,
...obj.related,
};
break; break;
}
case 'manyMorphToOne': case 'manyMorphToOne':
case 'manyMorphToMany': case 'manyMorphToMany':
attrs[association.alias] = attrs[association.alias].map( attrs[association.alias] = attrs[association.alias].map(obj => {
obj => obj.related const contentType = strapi.db.getModelByCollectionName(
); obj[`${association.alias}_type`]
);
return {
__contentType: contentType ? contentType.globalId : null,
...obj.related,
};
});
break; break;
default: default:
} }

View File

@ -107,6 +107,12 @@ class DatabaseManager {
_.get(strapi, ['components', key]) _.get(strapi, ['components', key])
); );
} }
getModelByCollectionName(collectionName) {
return Array.from(this.models.values()).find(model => {
return model.collectionName === collectionName;
});
}
} }
function createDatabaseManager(strapi) { function createDatabaseManager(strapi) {

View File

@ -6,8 +6,8 @@ const uploadFiles = require('../utils/upload-files');
* A set of functions called "actions" for `ContentManager` * A set of functions called "actions" for `ContentManager`
*/ */
module.exports = { module.exports = {
fetch(params) { fetch(params, populate) {
return strapi.query(params.model).findOne({ id: params.id }); return strapi.query(params.model).findOne({ id: params.id }, populate);
}, },
fetchAll(params, query) { fetchAll(params, query) {

View File

@ -18,14 +18,16 @@ module.exports = {
// Create loaders for each relational field (exclude core models). // Create loaders for each relational field (exclude core models).
Object.keys(strapi.models) Object.keys(strapi.models)
.filter(model => model !== 'core_store') .filter(model => model !== 'core_store')
.forEach(model => { .forEach(modelKey => {
this.createLoader(model); const model = strapi.models[modelKey];
this.createLoader(model.uid);
}); });
// Reproduce the same pattern for each plugin. // Reproduce the same pattern for each plugin.
Object.keys(strapi.plugins).forEach(plugin => { Object.keys(strapi.plugins).forEach(plugin => {
Object.keys(strapi.plugins[plugin].models).forEach(model => { Object.keys(strapi.plugins[plugin].models).forEach(modelKey => {
this.createLoader(model, plugin); const model = strapi.plugins[plugin].models[modelKey];
this.createLoader(model.uid);
}); });
}); });
}, },
@ -34,29 +36,25 @@ module.exports = {
this.loaders = {}; this.loaders = {};
}, },
createLoader: function(model, plugin) { createLoader: function(modelUID) {
const name = plugin ? `${plugin}__${model}` : model; if (this.loaders[modelUID]) {
return this.loaders[modelUID];
// Exclude polymorphic from loaders.
if (name === undefined) {
return;
} }
if (this.loaders[name]) { this.loaders[modelUID] = new DataLoader(
return this.loaders[name];
}
this.loaders[name] = new DataLoader(
keys => { keys => {
// Extract queries from keys and merge similar queries. // Extract queries from keys and merge similar queries.
const { queries, map } = this.extractQueries(model, _.cloneDeep(keys)); const { queries, map } = this.extractQueries(
modelUID,
_.cloneDeep(keys)
);
// Run queries in parallel. // Run queries in parallel.
return Promise.all( return Promise.all(
queries.map(query => this.makeQuery(model, query)) queries.map(query => this.makeQuery(modelUID, query))
).then(results => { ).then(results => {
// Use to match initial queries order. // Use to match initial queries order.
return this.mapData(model, keys, map, results); return this.mapData(modelUID, keys, map, results);
}); });
}, },
{ {
@ -67,7 +65,7 @@ module.exports = {
); );
}, },
mapData: function(model, originalMap, map, results) { mapData: function(modelUID, originalMap, map, results) {
// Use map to re-dispatch data correctly based on initial keys. // Use map to re-dispatch data correctly based on initial keys.
return originalMap.map((query, index) => { return originalMap.map((query, index) => {
// Find the index of where we should extract the results. // Find the index of where we should extract the results.
@ -77,7 +75,7 @@ module.exports = {
const data = results[indexResults]; const data = results[indexResults];
// Retrieving referring model. // Retrieving referring model.
const ref = this.retrieveModel(model, query.options.source); const ref = strapi.getModel(modelUID);
if (query.single) { if (query.single) {
// Return object instead of array for one-to-many relationship. // Return object instead of array for one-to-many relationship.
@ -98,8 +96,8 @@ module.exports = {
const ast = ref.associations.find(ast => ast.alias === ids.alias); const ast = ref.associations.find(ast => ast.alias === ids.alias);
const astModel = ast const astModel = ast
? this.retrieveModel(ast.model || ast.collection, ast.plugin) ? strapi.getModel(ast.model || ast.collection, ast.plugin)
: this.retrieveModel(model); : strapi.getModel(modelUID);
if (!_.isArray(ids)) { if (!_.isArray(ids)) {
return data return data
@ -144,12 +142,12 @@ module.exports = {
}; };
}, },
makeQuery: async function(model, query = {}) { makeQuery: async function(modelUID, query = {}) {
if (_.isEmpty(query.ids)) { if (_.isEmpty(query.ids)) {
return []; return [];
} }
const ref = this.retrieveModel(model, query.options.source); const ref = strapi.getModel(modelUID);
const ast = ref.associations.find(ast => ast.alias === query.alias); const ast = ref.associations.find(ast => ast.alias === query.alias);
const params = { const params = {
@ -169,15 +167,10 @@ module.exports = {
// Run query and remove duplicated ID. // Run query and remove duplicated ID.
return strapi.plugins['content-manager'].services[ return strapi.plugins['content-manager'].services[
'contentmanager' 'contentmanager'
].fetchAll({ model }, params); ].fetchAll({ model: modelUID }, params);
}, },
retrieveModel: function(model, source) { extractQueries: function(modelUID, keys) {
// Retrieve refering model.
return source ? strapi.plugins[source].models[model] : strapi.models[model];
},
extractQueries: function(model, keys) {
const queries = []; const queries = [];
const map = []; const map = [];
@ -188,16 +181,16 @@ module.exports = {
const { query = {}, ...options } = current.options; const { query = {}, ...options } = current.options;
// Retrieving referring model. // Retrieving referring model.
const ref = this.retrieveModel(model, options.source); const { primaryKey } = strapi.getModel(modelUID);
// Generate array of IDs to fetch. // Generate array of IDs to fetch.
const ids = []; const ids = [];
// Only one entry to fetch. // Only one entry to fetch.
if (single) { if (single) {
ids.push(params[ref.primaryKey]); ids.push(params[primaryKey]);
} else if (_.isArray(query[ref.primaryKey])) { } else if (_.isArray(query[primaryKey])) {
ids.push(...query[ref.primaryKey]); ids.push(...query[primaryKey]);
} else { } else {
ids.push(query[association.via]); ids.push(query[association.via]);
} }
@ -205,7 +198,7 @@ module.exports = {
queries.push({ queries.push({
ids, ids,
options, options,
alias: _.first(Object.keys(query)) || ref.primaryKey, alias: _.first(Object.keys(query)) || primaryKey,
}); });
map[queries.length - 1 > 0 ? queries.length - 1 : 0] = []; map[queries.length - 1 > 0 ? queries.length - 1 : 0] = [];

View File

@ -108,7 +108,7 @@ const mutateAssocAttributes = (associations = [], attributes) => {
}); });
}; };
const buildAssocResolvers = (model, name, { plugin }) => { const buildAssocResolvers = model => {
const contentManager = const contentManager =
strapi.plugins['content-manager'].services['contentmanager']; strapi.plugins['content-manager'].services['contentmanager'];
@ -117,100 +117,44 @@ const buildAssocResolvers = (model, name, { plugin }) => {
return associations return associations
.filter(association => model.attributes[association.alias].private !== true) .filter(association => model.attributes[association.alias].private !== true)
.reduce((resolver, association) => { .reduce((resolver, association) => {
const target = association.model || association.collection;
const targetModel = strapi.getModel(target, association.plugin);
switch (association.nature) { switch (association.nature) {
case 'oneToManyMorph': { case 'oneToManyMorph':
resolver[association.alias] = async obj => {
const entry = await contentManager.fetch(
{
id: obj[primaryKey],
model: name,
},
plugin,
[association.alias]
);
// Set the _type only when the value is defined
if (entry[association.alias]) {
entry[association.alias]._type = _.upperFirst(association.model);
}
return entry[association.alias];
};
break;
}
case 'manyMorphToOne': case 'manyMorphToOne':
case 'manyMorphToMany': case 'manyMorphToMany':
case 'manyToManyMorph': { case 'manyToManyMorph': {
resolver[association.alias] = async obj => { resolver[association.alias] = async obj => {
// eslint-disable-line no-unused-vars if (obj[association.alias]) {
const [withRelated, withoutRelated] = await Promise.all([ return obj[association.alias];
contentManager.fetch( }
{
id: obj[primaryKey],
model: name,
},
plugin,
[association.alias],
false
),
contentManager.fetch(
{
id: obj[primaryKey],
model: name,
},
plugin,
[]
),
]);
const entry = const entry = await contentManager.fetch(
withRelated && withRelated.toJSON {
? withRelated.toJSON() id: obj[primaryKey],
: withRelated; model: model.uid,
},
entry[association.alias].map((entry, index) => { [association.alias]
const type = );
_.get(withoutRelated, `${association.alias}.${index}.kind`) ||
_.upperFirst(
_.camelCase(
_.get(
withoutRelated,
`${association.alias}.${index}.${association.alias}_type`
)
)
) ||
_.upperFirst(_.camelCase(association[association.type]));
entry._type = type;
return entry;
});
return entry[association.alias]; return entry[association.alias];
}; };
break; break;
} }
default: { default: {
resolver[association.alias] = async (obj, options) => { resolver[association.alias] = async (obj, options) => {
// Construct parameters object to retrieve the correct related entries. // Construct parameters object to retrieve the correct related entries.
const params = { const params = {
model: association.model || association.collection, model: targetModel.uid,
}; };
let queryOpts = { let queryOpts = {};
source: association.plugin,
};
// Get refering model.
const ref = association.plugin
? strapi.plugins[association.plugin].models[params.model]
: strapi.models[params.model];
if (association.type === 'model') { if (association.type === 'model') {
params[ref.primaryKey] = _.get( params[targetModel.primaryKey] = _.get(
obj, obj,
[association.alias, ref.primaryKey], [association.alias, targetModel.primaryKey],
obj[association.alias] obj[association.alias]
); );
} else { } else {
@ -229,10 +173,10 @@ const buildAssocResolvers = (model, name, { plugin }) => {
) { ) {
_.set( _.set(
queryOpts, queryOpts,
['query', ref.primaryKey], ['query', targetModel.primaryKey],
obj[association.alias] obj[association.alias]
? obj[association.alias] ? obj[association.alias]
.map(val => val[ref.primaryKey] || val) .map(val => val[targetModel.primaryKey] || val)
.sort() .sort()
: [] : []
); );
@ -240,25 +184,21 @@ const buildAssocResolvers = (model, name, { plugin }) => {
_.set( _.set(
queryOpts, queryOpts,
['query', association.via], ['query', association.via],
obj[ref.primaryKey] obj[targetModel.primaryKey]
); );
} }
} }
const loaderName = association.plugin
? `${association.plugin}__${params.model}`
: params.model;
return association.model return association.model
? strapi.plugins.graphql.services.loaders.loaders[ ? strapi.plugins.graphql.services.loaders.loaders[
loaderName targetModel.uid
].load({ ].load({
params, params,
options: queryOpts, options: queryOpts,
single: true, single: true,
}) })
: strapi.plugins.graphql.services.loaders.loaders[ : strapi.plugins.graphql.services.loaders.loaders[
loaderName targetModel.uid
].load({ ].load({
options: queryOpts, options: queryOpts,
association, association,
@ -272,16 +212,12 @@ const buildAssocResolvers = (model, name, { plugin }) => {
}, {}); }, {});
}; };
const buildModel = ( const buildModel = (model, { schema, isComponent = false } = {}) => {
model,
name,
{ schema, plugin, isComponent = false } = {}
) => {
const { globalId, primaryKey } = model; const { globalId, primaryKey } = model;
schema.resolvers[globalId] = { schema.resolvers[globalId] = {
id: obj => obj[primaryKey], id: obj => obj[primaryKey],
...buildAssocResolvers(model, name, { plugin }), ...buildAssocResolvers(model),
}; };
const initialState = { const initialState = {
@ -552,7 +488,7 @@ const buildShadowCRUD = (models, plugin) => {
} }
// Build associations queries. // Build associations queries.
acc.resolvers[globalId] = buildAssocResolvers(model, name, { plugin }); _.assign(acc.resolvers[globalId], buildAssocResolvers(model));
return acc; return acc;
}, initialState); }, initialState);

View File

@ -143,7 +143,7 @@ const schemaBuilder = {
definition, definition,
query, query,
mutation, mutation,
resolver, resolvers,
} = Resolvers.buildShadowCRUD(strapi.plugins[plugin].models, plugin); } = Resolvers.buildShadowCRUD(strapi.plugins[plugin].models, plugin);
// We cannot put this in the merge because it's a string. // We cannot put this in the merge because it's a string.
@ -151,7 +151,7 @@ const schemaBuilder = {
return _.merge(acc, { return _.merge(acc, {
query, query,
resolver, resolvers,
mutation, mutation,
}); });
}, modelCruds); }, modelCruds);
@ -163,8 +163,7 @@ const schemaBuilder = {
}; };
Object.keys(strapi.components).forEach(key => Object.keys(strapi.components).forEach(key =>
Resolvers.buildModel(strapi.components[key], key, { Resolvers.buildModel(strapi.components[key], {
plugin: null,
isComponent: true, isComponent: true,
schema: componentsSchema, schema: componentsSchema,
}) })

View File

@ -222,8 +222,7 @@ module.exports = {
polymorphicResolver: { polymorphicResolver: {
Morph: { Morph: {
__resolveType(obj) { __resolveType(obj) {
// eslint-disable-line no-unused-vars return obj.kind || obj.__contentType || null;
return obj.kind || obj._type;
}, },
}, },
}, },