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",
"plugin": "upload"
},
"images": {
"collection": "file",
"via": "related",
"plugin": "upload"
},
"author": {
"model": "user",
"plugin": "users-permissions"
},
"metas": {
"type": "component",
"component": "default.metas",

View File

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

View File

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

View File

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

View File

@ -18,14 +18,16 @@ module.exports = {
// Create loaders for each relational field (exclude core models).
Object.keys(strapi.models)
.filter(model => model !== 'core_store')
.forEach(model => {
this.createLoader(model);
.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(model => {
this.createLoader(model, plugin);
Object.keys(strapi.plugins[plugin].models).forEach(modelKey => {
const model = strapi.plugins[plugin].models[modelKey];
this.createLoader(model.uid);
});
});
},
@ -34,29 +36,25 @@ module.exports = {
this.loaders = {};
},
createLoader: function(model, plugin) {
const name = plugin ? `${plugin}__${model}` : model;
// Exclude polymorphic from loaders.
if (name === undefined) {
return;
createLoader: function(modelUID) {
if (this.loaders[modelUID]) {
return this.loaders[modelUID];
}
if (this.loaders[name]) {
return this.loaders[name];
}
this.loaders[name] = new DataLoader(
this.loaders[modelUID] = new DataLoader(
keys => {
// 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.
return Promise.all(
queries.map(query => this.makeQuery(model, query))
queries.map(query => this.makeQuery(modelUID, query))
).then(results => {
// 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.
return originalMap.map((query, index) => {
// Find the index of where we should extract the results.
@ -77,7 +75,7 @@ module.exports = {
const data = results[indexResults];
// Retrieving referring model.
const ref = this.retrieveModel(model, query.options.source);
const ref = strapi.getModel(modelUID);
if (query.single) {
// 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 astModel = ast
? this.retrieveModel(ast.model || ast.collection, ast.plugin)
: this.retrieveModel(model);
? strapi.getModel(ast.model || ast.collection, ast.plugin)
: strapi.getModel(modelUID);
if (!_.isArray(ids)) {
return data
@ -144,12 +142,12 @@ module.exports = {
};
},
makeQuery: async function(model, query = {}) {
makeQuery: async function(modelUID, query = {}) {
if (_.isEmpty(query.ids)) {
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 params = {
@ -169,15 +167,10 @@ module.exports = {
// Run query and remove duplicated ID.
return strapi.plugins['content-manager'].services[
'contentmanager'
].fetchAll({ model }, params);
].fetchAll({ model: modelUID }, params);
},
retrieveModel: function(model, source) {
// Retrieve refering model.
return source ? strapi.plugins[source].models[model] : strapi.models[model];
},
extractQueries: function(model, keys) {
extractQueries: function(modelUID, keys) {
const queries = [];
const map = [];
@ -188,16 +181,16 @@ module.exports = {
const { query = {}, ...options } = current.options;
// Retrieving referring model.
const ref = this.retrieveModel(model, options.source);
const { primaryKey } = strapi.getModel(modelUID);
// Generate array of IDs to fetch.
const ids = [];
// Only one entry to fetch.
if (single) {
ids.push(params[ref.primaryKey]);
} else if (_.isArray(query[ref.primaryKey])) {
ids.push(...query[ref.primaryKey]);
ids.push(params[primaryKey]);
} else if (_.isArray(query[primaryKey])) {
ids.push(...query[primaryKey]);
} else {
ids.push(query[association.via]);
}
@ -205,7 +198,7 @@ module.exports = {
queries.push({
ids,
options,
alias: _.first(Object.keys(query)) || ref.primaryKey,
alias: _.first(Object.keys(query)) || primaryKey,
});
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 =
strapi.plugins['content-manager'].services['contentmanager'];
@ -117,100 +117,44 @@ const buildAssocResolvers = (model, name, { plugin }) => {
return associations
.filter(association => model.attributes[association.alias].private !== true)
.reduce((resolver, association) => {
const target = association.model || association.collection;
const targetModel = strapi.getModel(target, association.plugin);
switch (association.nature) {
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 'oneToManyMorph':
case 'manyMorphToOne':
case 'manyMorphToMany':
case 'manyToManyMorph': {
resolver[association.alias] = async obj => {
// eslint-disable-line no-unused-vars
const [withRelated, withoutRelated] = await Promise.all([
contentManager.fetch(
{
id: obj[primaryKey],
model: name,
},
plugin,
[association.alias],
false
),
contentManager.fetch(
{
id: obj[primaryKey],
model: name,
},
plugin,
[]
),
]);
if (obj[association.alias]) {
return obj[association.alias];
}
const entry =
withRelated && withRelated.toJSON
? withRelated.toJSON()
: withRelated;
entry[association.alias].map((entry, index) => {
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;
});
const entry = await contentManager.fetch(
{
id: obj[primaryKey],
model: model.uid,
},
[association.alias]
);
return entry[association.alias];
};
break;
}
default: {
resolver[association.alias] = async (obj, options) => {
// Construct parameters object to retrieve the correct related entries.
const params = {
model: association.model || association.collection,
model: targetModel.uid,
};
let queryOpts = {
source: association.plugin,
};
// Get refering model.
const ref = association.plugin
? strapi.plugins[association.plugin].models[params.model]
: strapi.models[params.model];
let queryOpts = {};
if (association.type === 'model') {
params[ref.primaryKey] = _.get(
params[targetModel.primaryKey] = _.get(
obj,
[association.alias, ref.primaryKey],
[association.alias, targetModel.primaryKey],
obj[association.alias]
);
} else {
@ -229,10 +173,10 @@ const buildAssocResolvers = (model, name, { plugin }) => {
) {
_.set(
queryOpts,
['query', ref.primaryKey],
['query', targetModel.primaryKey],
obj[association.alias]
? obj[association.alias]
.map(val => val[ref.primaryKey] || val)
.map(val => val[targetModel.primaryKey] || val)
.sort()
: []
);
@ -240,25 +184,21 @@ const buildAssocResolvers = (model, name, { plugin }) => {
_.set(
queryOpts,
['query', association.via],
obj[ref.primaryKey]
obj[targetModel.primaryKey]
);
}
}
const loaderName = association.plugin
? `${association.plugin}__${params.model}`
: params.model;
return association.model
? strapi.plugins.graphql.services.loaders.loaders[
loaderName
targetModel.uid
].load({
params,
options: queryOpts,
single: true,
})
: strapi.plugins.graphql.services.loaders.loaders[
loaderName
targetModel.uid
].load({
options: queryOpts,
association,
@ -272,16 +212,12 @@ const buildAssocResolvers = (model, name, { plugin }) => {
}, {});
};
const buildModel = (
model,
name,
{ schema, plugin, isComponent = false } = {}
) => {
const buildModel = (model, { schema, isComponent = false } = {}) => {
const { globalId, primaryKey } = model;
schema.resolvers[globalId] = {
id: obj => obj[primaryKey],
...buildAssocResolvers(model, name, { plugin }),
...buildAssocResolvers(model),
};
const initialState = {
@ -552,7 +488,7 @@ const buildShadowCRUD = (models, plugin) => {
}
// Build associations queries.
acc.resolvers[globalId] = buildAssocResolvers(model, name, { plugin });
_.assign(acc.resolvers[globalId], buildAssocResolvers(model));
return acc;
}, initialState);

View File

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

View File

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