mirror of
https://github.com/strapi/strapi.git
synced 2025-09-03 13:50:38 +00:00
Handle polymorphic types & relations, clean relational attributes code
This commit is contained in:
parent
0e8025df6b
commit
aa2d2bf29a
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const { unionType } = require('nexus');
|
||||
const { prop } = require('lodash/fp');
|
||||
|
||||
const {
|
||||
constants: { GENERIC_MORPH_TYPENAME },
|
||||
} = require('../../types');
|
||||
|
||||
module.exports = ({ registry }) => ({
|
||||
buildGenericMorphDefinition() {
|
||||
return unionType({
|
||||
name: GENERIC_MORPH_TYPENAME,
|
||||
|
||||
resolveType: prop('__typename'),
|
||||
|
||||
definition(t) {
|
||||
const members = registry
|
||||
.where(({ config }) => ['types', 'components'].includes(config.kind))
|
||||
.map(prop('name'));
|
||||
|
||||
t.members(...members);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@ -18,6 +18,8 @@ const mutations = require('./mutations');
|
||||
const filters = require('./filters');
|
||||
const inputs = require('./input');
|
||||
|
||||
const genericMorph = require('./generic-morph');
|
||||
|
||||
const buildersFactories = [
|
||||
enums,
|
||||
dynamicZone,
|
||||
@ -30,6 +32,7 @@ const buildersFactories = [
|
||||
mutations,
|
||||
filters,
|
||||
inputs,
|
||||
genericMorph,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const { isArray } = require('lodash/fp');
|
||||
const { isArray, isString, isUndefined } = require('lodash/fp');
|
||||
const { objectType } = require('nexus');
|
||||
|
||||
const { contentTypes } = require('@strapi/utils');
|
||||
|
||||
const { mappers, utils: typeUtils } = require('../../types');
|
||||
const { mappers, utils: typeUtils, constants } = require('../../types');
|
||||
const { buildAssociationResolver } = require('../resolvers');
|
||||
|
||||
/**
|
||||
@ -62,7 +62,9 @@ module.exports = context => ({
|
||||
* - Component
|
||||
* - Dynamic Zone
|
||||
* - Enum
|
||||
* - Relation
|
||||
* - Media
|
||||
* - Polymorphic Relations
|
||||
* - Regular Relations
|
||||
*
|
||||
* Here, we iterate over each non-private attribute
|
||||
* and add it to the type definition based on its type
|
||||
@ -107,9 +109,19 @@ module.exports = context => ({
|
||||
addEnumAttribute(options);
|
||||
}
|
||||
|
||||
// Relations
|
||||
// Media
|
||||
else if (typeUtils.isMedia(attribute)) {
|
||||
addMediaAttribute(options);
|
||||
}
|
||||
|
||||
// Polymorphic Relations
|
||||
else if (typeUtils.isMorphRelation(attribute)) {
|
||||
addPolymorphicRelationalAttribute(options);
|
||||
}
|
||||
|
||||
// Regular Relations
|
||||
else if (typeUtils.isRelation(attribute) || typeUtils.isMedia(attribute)) {
|
||||
addRelationalAttribute(options);
|
||||
addRegularRelationalAttribute(options);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -178,10 +190,10 @@ const addEnumAttribute = ({ builder, attributeName, contentType }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a relational attribute to the type definition
|
||||
* Add a media attribute to the type definition
|
||||
* @param {TypeBuildersOptions} options
|
||||
*/
|
||||
const addRelationalAttribute = options => {
|
||||
const addMediaAttribute = options => {
|
||||
let { builder } = options;
|
||||
const {
|
||||
attributeName,
|
||||
@ -190,10 +202,37 @@ const addRelationalAttribute = options => {
|
||||
context: { strapi },
|
||||
} = options;
|
||||
|
||||
// todo[V4]: Clean the logic below
|
||||
if (attribute.multiple) {
|
||||
builder = builder.list;
|
||||
}
|
||||
|
||||
const isMorphLike = typeUtils.isMorphRelation(attribute);
|
||||
const isToManyRelation = typeUtils.isRelation(attribute) && attribute.relation.endsWith('Many');
|
||||
const fileContentType = strapi.getModel('plugins::upload.file');
|
||||
const type = typeUtils.getTypeName(fileContentType);
|
||||
|
||||
const associationResolver = buildAssociationResolver({
|
||||
contentTypeUID: contentType.uid,
|
||||
attributeName,
|
||||
strapi,
|
||||
});
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a polymorphic relational attribute to the type definition
|
||||
* @param {TypeBuildersOptions} options
|
||||
*/
|
||||
const addPolymorphicRelationalAttribute = options => {
|
||||
let { builder } = options;
|
||||
const {
|
||||
attributeName,
|
||||
attribute,
|
||||
contentType,
|
||||
context: { strapi },
|
||||
} = options;
|
||||
|
||||
const { target } = attribute;
|
||||
const isToManyRelation = attribute.relation.endsWith('Many');
|
||||
|
||||
if (isToManyRelation) {
|
||||
builder = builder.list;
|
||||
@ -205,34 +244,53 @@ const addRelationalAttribute = options => {
|
||||
strapi,
|
||||
});
|
||||
|
||||
if (typeUtils.isMedia(attribute)) {
|
||||
const fileContentType = strapi.getModel('plugins::upload.file');
|
||||
const type = typeUtils.getTypeName(fileContentType);
|
||||
// If there is no specific target specified, then use the GenericMorph type
|
||||
if (isUndefined(target)) {
|
||||
builder.field(attributeName, {
|
||||
type: constants.GENERIC_MORPH_TYPENAME,
|
||||
resolve: associationResolver,
|
||||
});
|
||||
}
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
} else if (isMorphLike) {
|
||||
const { target } = attribute;
|
||||
|
||||
if (typeof target === 'string') {
|
||||
const targetContentType = strapi.getModel(target);
|
||||
const type = typeUtils.getTypeName(targetContentType);
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
} else if (Array.isArray(target)) {
|
||||
const type = typeUtils.getMorphRelationTypeName(contentType, attributeName);
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
} else if (!target) {
|
||||
builder.field(attributeName, { type: 'GenericMorph', resolve: associationResolver });
|
||||
}
|
||||
} else {
|
||||
const targetContentType = strapi.getModel(attribute.target);
|
||||
const type = typeUtils.getTypeName(targetContentType);
|
||||
// If the target is an array of string, resolve the associated morph type and use it
|
||||
else if (isArray(target) && target.every(isString)) {
|
||||
const type = typeUtils.getMorphRelationTypeName(contentType, attributeName);
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a regular relational attribute to the type definition
|
||||
* @param {TypeBuildersOptions} options
|
||||
*/
|
||||
const addRegularRelationalAttribute = options => {
|
||||
let { builder } = options;
|
||||
const {
|
||||
attributeName,
|
||||
attribute,
|
||||
contentType,
|
||||
context: { strapi },
|
||||
} = options;
|
||||
|
||||
const isToManyRelation = attribute.relation.endsWith('Many');
|
||||
|
||||
if (isToManyRelation) {
|
||||
builder = builder.list;
|
||||
}
|
||||
|
||||
const associationResolver = buildAssociationResolver({
|
||||
contentTypeUID: contentType.uid,
|
||||
attributeName,
|
||||
strapi,
|
||||
});
|
||||
|
||||
const targetContentType = strapi.getModel(attribute.target);
|
||||
const type = typeUtils.getTypeName(targetContentType);
|
||||
|
||||
builder.field(attributeName, { type, resolve: associationResolver });
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind a content type on an attribute privacy checker
|
||||
*
|
||||
|
@ -4,7 +4,7 @@ const { prop } = require('lodash/fp');
|
||||
const { makeSchema, unionType } = require('nexus');
|
||||
|
||||
const createBuilders = require('../builders');
|
||||
const { utils, scalars, buildInternals } = require('../../types');
|
||||
const { utils, constants, scalars, buildInternals } = require('../../types');
|
||||
|
||||
const { create: createTypeRegistry } = require('../../type-registry');
|
||||
|
||||
@ -42,56 +42,45 @@ module.exports = strapi => {
|
||||
|
||||
const registerMorphTypes = contentTypes => {
|
||||
// Create & register a union type that includes every type or component registered
|
||||
registry.register(
|
||||
'GenericMorph',
|
||||
|
||||
unionType({
|
||||
name: 'GenericMorph',
|
||||
|
||||
resolveType(obj) {
|
||||
return obj.__typename;
|
||||
},
|
||||
|
||||
definition(t) {
|
||||
const members = registry
|
||||
.where(({ config: { kind } }) => ['types', 'components'].includes(kind))
|
||||
.map(prop('name'));
|
||||
|
||||
t.members(...members);
|
||||
},
|
||||
}),
|
||||
{ kind: 'morphs' }
|
||||
);
|
||||
const genericMorphType = builders.buildGenericMorphDefinition();
|
||||
registry.register(constants.GENERIC_MORPH_TYPENAME, genericMorphType, { kind: 'morphs' });
|
||||
|
||||
// For every content type
|
||||
contentTypes.forEach(contentType => {
|
||||
const { attributes = {} } = contentType;
|
||||
|
||||
// Isolate its polymorphic attributes
|
||||
const morphAttributes = Object.entries(attributes).filter(([, attribute]) =>
|
||||
utils.isMorphRelation(attribute)
|
||||
);
|
||||
|
||||
// For each one of those polymorphic attribute
|
||||
for (const [attributeName, attribute] of morphAttributes) {
|
||||
const name = utils.getMorphRelationTypeName(contentType, attributeName);
|
||||
const { target } = attribute;
|
||||
|
||||
// Ignore those whose target is not an array
|
||||
if (!Array.isArray(target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transform target UIDs into types names
|
||||
const members = target
|
||||
// Get content types definitions
|
||||
.map(uid => strapi.getModel(uid))
|
||||
// Resolve types names
|
||||
.map(contentType => utils.getTypeName(contentType));
|
||||
|
||||
// Register the new polymorphic union type
|
||||
registry.register(
|
||||
name,
|
||||
|
||||
unionType({
|
||||
name,
|
||||
|
||||
resolveType(obj) {
|
||||
return obj.__typename;
|
||||
},
|
||||
resolveType: prop('__typename'),
|
||||
|
||||
definition(t) {
|
||||
// const members = backLinks.map(prop('definition'));
|
||||
const members = target || ['GenericMorph'];
|
||||
|
||||
t.members(...members);
|
||||
},
|
||||
}),
|
||||
|
@ -32,10 +32,13 @@ const STRAPI_SCALARS = [
|
||||
'timestamp',
|
||||
];
|
||||
|
||||
const GENERIC_MORPH_TYPENAME = 'GenericMorph';
|
||||
|
||||
module.exports = {
|
||||
PAGINATION_TYPE_NAME,
|
||||
RESPONSE_COLLECTION_META_TYPE_NAME,
|
||||
PUBLICATION_STATE_TYPE_NAME,
|
||||
GRAPHQL_SCALARS,
|
||||
STRAPI_SCALARS,
|
||||
GENERIC_MORPH_TYPENAME,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user