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