mirror of
https://github.com/strapi/strapi.git
synced 2025-11-12 08:08:05 +00:00
Merge pull request #5005 from strapi/single-types/add-single-type-kind
Add single type kind property to content types
This commit is contained in:
commit
c47fae0638
@ -5,10 +5,19 @@ const _ = require('lodash');
|
|||||||
const {
|
const {
|
||||||
validateContentTypeInput,
|
validateContentTypeInput,
|
||||||
validateUpdateContentTypeInput,
|
validateUpdateContentTypeInput,
|
||||||
|
validateKind,
|
||||||
} = require('./validation/content-type');
|
} = require('./validation/content-type');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getContentTypes(ctx) {
|
async getContentTypes(ctx) {
|
||||||
|
const { kind } = ctx.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await validateKind(kind);
|
||||||
|
} catch (error) {
|
||||||
|
return ctx.send({ error }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService =
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
|
|
||||||
@ -17,6 +26,13 @@ module.exports = {
|
|||||||
if (uid.startsWith('strapi::')) return false;
|
if (uid.startsWith('strapi::')) return false;
|
||||||
if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead
|
if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead
|
||||||
|
|
||||||
|
if (
|
||||||
|
kind &&
|
||||||
|
_.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map(uid =>
|
.map(uid =>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const yup = require('yup');
|
|||||||
const { isValidCategoryName, isValidIcon } = require('./common');
|
const { isValidCategoryName, isValidIcon } = require('./common');
|
||||||
const formatYupErrors = require('./yup-formatter');
|
const formatYupErrors = require('./yup-formatter');
|
||||||
const createSchema = require('./model-schema');
|
const createSchema = require('./model-schema');
|
||||||
|
const removeEmptyDefaults = require('./remove-empty-defaults');
|
||||||
const { modelTypes, DEFAULT_TYPES } = require('./constants');
|
const { modelTypes, DEFAULT_TYPES } = require('./constants');
|
||||||
|
|
||||||
const VALID_RELATIONS = ['oneWay', 'manyWay'];
|
const VALID_RELATIONS = ['oneWay', 'manyWay'];
|
||||||
@ -63,26 +64,7 @@ const validateComponentInput = data => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateUpdateComponentInput = data => {
|
const validateUpdateComponentInput = data => {
|
||||||
// convert zero length string on default attributes to undefined
|
removeEmptyDefaults(data);
|
||||||
if (_.has(data, ['component', 'attributes'])) {
|
|
||||||
Object.keys(data.component.attributes).forEach(attribute => {
|
|
||||||
if (data.component.attributes[attribute].default === '') {
|
|
||||||
data.component.attributes[attribute].default = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
|
||||||
data.components.forEach(data => {
|
|
||||||
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
|
||||||
Object.keys(data.attributes).forEach(attribute => {
|
|
||||||
if (data.attributes[attribute].default === '') {
|
|
||||||
data.attributes[attribute].default = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return yup
|
return yup
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
const CONTENT_TYPE = 'CONTENT_TYPE';
|
const CONTENT_TYPE = 'CONTENT_TYPE';
|
||||||
const COMPONENT = 'COMPONENT';
|
const COMPONENT = 'COMPONENT';
|
||||||
|
|
||||||
|
const SINGLE_TYPE = 'singleType';
|
||||||
|
const COLLECTION_TYPE = 'collectionType';
|
||||||
|
|
||||||
const DEFAULT_TYPES = [
|
const DEFAULT_TYPES = [
|
||||||
// advanced types
|
// advanced types
|
||||||
'media',
|
'media',
|
||||||
@ -30,6 +33,10 @@ const FORBIDDEN_ATTRIBUTE_NAMES = ['__component', '__contentType'];
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DEFAULT_TYPES,
|
DEFAULT_TYPES,
|
||||||
|
typeKinds: {
|
||||||
|
SINGLE_TYPE,
|
||||||
|
COLLECTION_TYPE,
|
||||||
|
},
|
||||||
modelTypes: {
|
modelTypes: {
|
||||||
CONTENT_TYPE,
|
CONTENT_TYPE,
|
||||||
COMPONENT,
|
COMPONENT,
|
||||||
|
|||||||
@ -2,35 +2,61 @@
|
|||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const yup = require('yup');
|
const yup = require('yup');
|
||||||
|
|
||||||
const formatYupErrors = require('./yup-formatter');
|
const formatYupErrors = require('./yup-formatter');
|
||||||
|
|
||||||
const createSchema = require('./model-schema');
|
const createSchema = require('./model-schema');
|
||||||
|
const removeEmptyDefaults = require('./remove-empty-defaults');
|
||||||
const { nestedComponentSchema } = require('./component');
|
const { nestedComponentSchema } = require('./component');
|
||||||
const { modelTypes, DEFAULT_TYPES } = require('./constants');
|
const { modelTypes, DEFAULT_TYPES, typeKinds } = require('./constants');
|
||||||
|
|
||||||
const VALID_RELATIONS = [
|
/**
|
||||||
'oneWay',
|
* Allowed relation per type kind
|
||||||
'manyWay',
|
*/
|
||||||
'oneToOne',
|
const VALID_RELATIONS = {
|
||||||
'oneToMany',
|
[typeKinds.SINGLE_TYPE]: ['oneWay', 'manyWay'],
|
||||||
'manyToOne',
|
[typeKinds.COLLECTION_TYPE]: [
|
||||||
'manyToMany',
|
'oneWay',
|
||||||
];
|
'manyWay',
|
||||||
|
'oneToOne',
|
||||||
|
'oneToMany',
|
||||||
|
'manyToOne',
|
||||||
|
'manyToMany',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed types
|
||||||
|
*/
|
||||||
const VALID_TYPES = [...DEFAULT_TYPES, 'component', 'dynamiczone'];
|
const VALID_TYPES = [...DEFAULT_TYPES, 'component', 'dynamiczone'];
|
||||||
|
|
||||||
const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
|
/**
|
||||||
modelType: modelTypes.CONTENT_TYPE,
|
* Returns a yup schema to validate a content type payload
|
||||||
});
|
* @param {Object} data payload
|
||||||
|
*/
|
||||||
|
const createContentTypeSchema = data => {
|
||||||
|
const kind = _.get(data, 'kind', typeKinds.COLLECTION_TYPE);
|
||||||
|
|
||||||
const createContentTypeSchema = yup
|
const contentTypeSchema = createSchema(
|
||||||
.object({
|
VALID_TYPES,
|
||||||
contentType: contentTypeSchema.required().noUnknown(),
|
VALID_RELATIONS[kind] || [],
|
||||||
components: nestedComponentSchema,
|
{
|
||||||
})
|
modelType: modelTypes.CONTENT_TYPE,
|
||||||
.noUnknown();
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return yup
|
||||||
|
.object({
|
||||||
|
contentType: contentTypeSchema.required().noUnknown(),
|
||||||
|
components: nestedComponentSchema,
|
||||||
|
})
|
||||||
|
.noUnknown();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator for content type creation
|
||||||
|
*/
|
||||||
const validateContentTypeInput = data => {
|
const validateContentTypeInput = data => {
|
||||||
return createContentTypeSchema
|
return createContentTypeSchema(data)
|
||||||
.validate(data, {
|
.validate(data, {
|
||||||
strict: true,
|
strict: true,
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
@ -38,29 +64,13 @@ const validateContentTypeInput = data => {
|
|||||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator for content type edition
|
||||||
|
*/
|
||||||
const validateUpdateContentTypeInput = data => {
|
const validateUpdateContentTypeInput = data => {
|
||||||
// convert zero length string on default attributes to undefined
|
removeEmptyDefaults(data);
|
||||||
if (_.has(data, 'attributes')) {
|
|
||||||
Object.keys(data.attributes).forEach(attribute => {
|
|
||||||
if (data.attributes[attribute].default === '') {
|
|
||||||
data.attributes[attribute].default = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
return createContentTypeSchema(data)
|
||||||
data.components.forEach(data => {
|
|
||||||
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
|
||||||
Object.keys(data.attributes).forEach(attribute => {
|
|
||||||
if (data.attributes[attribute].default === '') {
|
|
||||||
data.attributes[attribute].default = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return createContentTypeSchema
|
|
||||||
.validate(data, {
|
.validate(data, {
|
||||||
strict: true,
|
strict: true,
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
@ -68,7 +78,20 @@ const validateUpdateContentTypeInput = data => {
|
|||||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates type kind
|
||||||
|
*/
|
||||||
|
const validateKind = kind => {
|
||||||
|
return yup
|
||||||
|
.string()
|
||||||
|
.oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE])
|
||||||
|
.nullable()
|
||||||
|
.validate(kind)
|
||||||
|
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateContentTypeInput,
|
validateContentTypeInput,
|
||||||
validateUpdateContentTypeInput,
|
validateUpdateContentTypeInput,
|
||||||
|
validateKind,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,72 +3,88 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const yup = require('yup');
|
const yup = require('yup');
|
||||||
|
|
||||||
const { FORBIDDEN_ATTRIBUTE_NAMES } = require('./constants');
|
const {
|
||||||
|
modelTypes,
|
||||||
|
FORBIDDEN_ATTRIBUTE_NAMES,
|
||||||
|
typeKinds,
|
||||||
|
} = require('./constants');
|
||||||
const { isValidCollectionName, isValidKey } = require('./common');
|
const { isValidCollectionName, isValidKey } = require('./common');
|
||||||
const { getTypeShape } = require('./types');
|
const { getTypeShape } = require('./types');
|
||||||
const getRelationValidator = require('./relations');
|
const getRelationValidator = require('./relations');
|
||||||
|
|
||||||
const createSchema = (types, relations, { modelType } = {}) =>
|
const createSchema = (types, relations, { modelType } = {}) => {
|
||||||
yup
|
const schema = yup.object({
|
||||||
.object({
|
name: yup
|
||||||
name: yup
|
.string()
|
||||||
.string()
|
.min(1)
|
||||||
.min(1)
|
.required('name.required'),
|
||||||
.required('name.required'),
|
description: yup.string(),
|
||||||
description: yup.string(),
|
connection: yup.string(),
|
||||||
connection: yup.string(),
|
collectionName: yup
|
||||||
collectionName: yup
|
.string()
|
||||||
.string()
|
.nullable()
|
||||||
.nullable()
|
.test(isValidCollectionName),
|
||||||
.test(isValidCollectionName),
|
attributes: yup.lazy(attributes => {
|
||||||
attributes: yup.lazy(attributes => {
|
return yup
|
||||||
return yup
|
.object()
|
||||||
.object()
|
.shape(
|
||||||
.shape(
|
_.mapValues(attributes, (attribute, key) => {
|
||||||
_.mapValues(attributes, (attribute, key) => {
|
if (FORBIDDEN_ATTRIBUTE_NAMES.includes(key)) {
|
||||||
if (FORBIDDEN_ATTRIBUTE_NAMES.includes(key)) {
|
|
||||||
return yup.object().test({
|
|
||||||
name: 'forbiddenKeys',
|
|
||||||
message: `Attribute keys cannot be one of ${FORBIDDEN_ATTRIBUTE_NAMES.join(
|
|
||||||
', '
|
|
||||||
)}`,
|
|
||||||
test: () => false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.has(attribute, 'type')) {
|
|
||||||
const shape = {
|
|
||||||
type: yup
|
|
||||||
.string()
|
|
||||||
.oneOf(types)
|
|
||||||
.required(),
|
|
||||||
configurable: yup.boolean().nullable(),
|
|
||||||
private: yup.boolean().nullable(),
|
|
||||||
...getTypeShape(attribute, { modelType }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return yup
|
|
||||||
.object(shape)
|
|
||||||
.test(isValidKey(key))
|
|
||||||
.noUnknown();
|
|
||||||
} else if (_.has(attribute, 'target')) {
|
|
||||||
const shape = getRelationValidator(attribute, relations);
|
|
||||||
|
|
||||||
return yup
|
|
||||||
.object(shape)
|
|
||||||
.test(isValidKey(key))
|
|
||||||
.noUnknown();
|
|
||||||
}
|
|
||||||
return yup.object().test({
|
return yup.object().test({
|
||||||
name: 'mustHaveTypeOrTarget',
|
name: 'forbiddenKeys',
|
||||||
message: 'Attribute must have either a type or a target',
|
message: `Attribute keys cannot be one of ${FORBIDDEN_ATTRIBUTE_NAMES.join(
|
||||||
|
', '
|
||||||
|
)}`,
|
||||||
test: () => false,
|
test: () => false,
|
||||||
});
|
});
|
||||||
})
|
}
|
||||||
)
|
|
||||||
.required('attributes.required');
|
if (_.has(attribute, 'type')) {
|
||||||
}),
|
const shape = {
|
||||||
})
|
type: yup
|
||||||
.noUnknown();
|
.string()
|
||||||
|
.oneOf(types)
|
||||||
|
.required(),
|
||||||
|
configurable: yup.boolean().nullable(),
|
||||||
|
private: yup.boolean().nullable(),
|
||||||
|
...getTypeShape(attribute, { modelType }),
|
||||||
|
};
|
||||||
|
|
||||||
|
return yup
|
||||||
|
.object(shape)
|
||||||
|
.test(isValidKey(key))
|
||||||
|
.noUnknown();
|
||||||
|
} else if (_.has(attribute, 'target')) {
|
||||||
|
const shape = getRelationValidator(attribute, relations);
|
||||||
|
|
||||||
|
return yup
|
||||||
|
.object(shape)
|
||||||
|
.test(isValidKey(key))
|
||||||
|
.noUnknown();
|
||||||
|
}
|
||||||
|
return yup.object().test({
|
||||||
|
name: 'mustHaveTypeOrTarget',
|
||||||
|
message: 'Attribute must have either a type or a target',
|
||||||
|
test: () => false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.required('attributes.required');
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modelType === modelTypes.CONTENT_TYPE) {
|
||||||
|
return schema
|
||||||
|
.shape({
|
||||||
|
kind: yup
|
||||||
|
.string()
|
||||||
|
.oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE])
|
||||||
|
.nullable(),
|
||||||
|
})
|
||||||
|
.noUnknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema.noUnknown();
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = createSchema;
|
module.exports = createSchema;
|
||||||
|
|||||||
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
const yup = require('yup');
|
const yup = require('yup');
|
||||||
const { validators, isValidName } = require('./common');
|
const { validators, isValidName } = require('./common');
|
||||||
|
const { typeKinds } = require('./constants');
|
||||||
|
|
||||||
const REVERSE_RELATIONS = ['oneToOne', 'oneToMany', 'manyToOne', 'manyToMany'];
|
const REVERSE_RELATIONS = ['oneToOne', 'oneToMany', 'manyToOne', 'manyToMany'];
|
||||||
|
|
||||||
module.exports = (obj, validNatures) => {
|
module.exports = (obj, validNatures) => {
|
||||||
const contentTypesUIDs = Object.keys(strapi.contentTypes).concat([
|
const contentTypesUIDs = Object.keys(strapi.contentTypes)
|
||||||
'__self__',
|
.filter(key => strapi.contentTypes[key].kind === typeKinds.COLLECTION_TYPE)
|
||||||
'__contentType__',
|
.concat(['__self__', '__contentType__']);
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
target: yup
|
target: yup
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert zero length string on default attributes to undefined
|
||||||
|
*/
|
||||||
|
module.exports = data => {
|
||||||
|
if (_.has(data, 'attributes')) {
|
||||||
|
Object.keys(data.attributes).forEach(attribute => {
|
||||||
|
if (data.attributes[attribute].default === '') {
|
||||||
|
data.attributes[attribute].default = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
||||||
|
data.components.forEach(data => {
|
||||||
|
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
||||||
|
Object.keys(data.attributes).forEach(attribute => {
|
||||||
|
if (data.attributes[attribute].default === '') {
|
||||||
|
data.attributes[attribute].default = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@ -17,7 +17,7 @@ const { nameToSlug } = require('../utils/helpers');
|
|||||||
* @param {Object} contentType
|
* @param {Object} contentType
|
||||||
*/
|
*/
|
||||||
const formatContentType = contentType => {
|
const formatContentType = contentType => {
|
||||||
const { uid, plugin, connection, collectionName, info } = contentType;
|
const { uid, kind, plugin, connection, collectionName, info } = contentType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid,
|
uid,
|
||||||
@ -26,6 +26,7 @@ const formatContentType = contentType => {
|
|||||||
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)),
|
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)),
|
||||||
description: _.get(info, 'description', ''),
|
description: _.get(info, 'description', ''),
|
||||||
connection,
|
connection,
|
||||||
|
kind: kind || 'collectionType',
|
||||||
collectionName,
|
collectionName,
|
||||||
attributes: formatAttributes(contentType),
|
attributes: formatAttributes(contentType),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Content types service format ContentType Returns consistent schemas 1`] = `
|
||||||
|
Object {
|
||||||
|
"plugin": "some-plugin",
|
||||||
|
"schema": Object {
|
||||||
|
"attributes": Object {
|
||||||
|
"title": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"collectionName": "tests",
|
||||||
|
"connection": "default",
|
||||||
|
"description": "My description",
|
||||||
|
"kind": "singleType",
|
||||||
|
"name": "My name",
|
||||||
|
},
|
||||||
|
"uid": "test-uid",
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
const { formatContentType } = require('../ContentTypes');
|
||||||
|
|
||||||
|
describe('Content types service', () => {
|
||||||
|
describe('format ContentType', () => {
|
||||||
|
const contentType = {
|
||||||
|
uid: 'test-uid',
|
||||||
|
kind: 'singleType',
|
||||||
|
plugin: 'some-plugin',
|
||||||
|
connection: 'default',
|
||||||
|
collectionName: 'tests',
|
||||||
|
info: {
|
||||||
|
name: 'My name',
|
||||||
|
description: 'My description',
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Returns consistent schemas', () => {
|
||||||
|
expect(formatContentType(contentType)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Sets default kind', () => {
|
||||||
|
expect(
|
||||||
|
formatContentType({
|
||||||
|
...contentType,
|
||||||
|
kind: undefined,
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
schema: {
|
||||||
|
kind: 'collectionType',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Generates a default name', () => {
|
||||||
|
expect(
|
||||||
|
formatContentType({
|
||||||
|
...contentType,
|
||||||
|
info: {
|
||||||
|
name: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
schema: {
|
||||||
|
name: 'Test-uids',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -6,6 +6,7 @@ const pluralize = require('pluralize');
|
|||||||
|
|
||||||
const { isRelation, toUID, isConfigurable } = require('../../utils/attributes');
|
const { isRelation, toUID, isConfigurable } = require('../../utils/attributes');
|
||||||
const { nameToSlug, nameToCollectionName } = require('../../utils/helpers');
|
const { nameToSlug, nameToCollectionName } = require('../../utils/helpers');
|
||||||
|
const { typeKinds } = require('../../controllers/validation/constants');
|
||||||
const createSchemaHandler = require('./schema-handler');
|
const createSchemaHandler = require('./schema-handler');
|
||||||
|
|
||||||
module.exports = function createComponentBuilder() {
|
module.exports = function createComponentBuilder() {
|
||||||
@ -80,6 +81,7 @@ module.exports = function createComponentBuilder() {
|
|||||||
contentType
|
contentType
|
||||||
.setUID(uid)
|
.setUID(uid)
|
||||||
.set('connection', infos.connection || defaultConnection)
|
.set('connection', infos.connection || defaultConnection)
|
||||||
|
.set('kind', infos.kind || typeKinds.COLLECTION_TYPE)
|
||||||
.set('collectionName', infos.collectionName || defaultCollectionName)
|
.set('collectionName', infos.collectionName || defaultCollectionName)
|
||||||
.set(['info', 'name'], infos.name)
|
.set(['info', 'name'], infos.name)
|
||||||
.set(['info', 'description'], infos.description)
|
.set(['info', 'description'], infos.description)
|
||||||
@ -197,9 +199,12 @@ module.exports = function createComponentBuilder() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: handle kind change => update routes.json file somehow
|
||||||
|
|
||||||
contentType
|
contentType
|
||||||
.set('connection', infos.connection)
|
.set('connection', infos.connection)
|
||||||
.set('collectionName', infos.collectionName)
|
.set('collectionName', infos.collectionName)
|
||||||
|
.set('kind', infos.kind || contentType.schema.kind)
|
||||||
.set(['info', 'name'], infos.name)
|
.set(['info', 'name'], infos.name)
|
||||||
.set(['info', 'description'], infos.description)
|
.set(['info', 'description'], infos.description)
|
||||||
.setAttributes(this.convertAttributes(newAttributes));
|
.setAttributes(this.convertAttributes(newAttributes));
|
||||||
@ -220,7 +225,6 @@ module.exports = function createComponentBuilder() {
|
|||||||
ct.removeContentType(uid);
|
ct.removeContentType(uid);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: clear api when a contentType is deleted
|
|
||||||
return this.contentTypes.get(uid).delete();
|
return this.contentTypes.get(uid).delete();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Content Type Builder - Content types Collection Types Get collection type returns full schema and informations 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"attributes": Object {
|
||||||
|
"title": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"collectionName": "test_collection_types",
|
||||||
|
"connection": "default",
|
||||||
|
"description": "",
|
||||||
|
"kind": "collectionType",
|
||||||
|
"name": "Test Collection Type",
|
||||||
|
},
|
||||||
|
"uid": "application::test-collection-type.test-collection-type",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Content Type Builder - Content types Single Types Get single type returns full schema and informations 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"attributes": Object {
|
||||||
|
"title": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"collectionName": "test_single_types",
|
||||||
|
"connection": "default",
|
||||||
|
"description": "",
|
||||||
|
"kind": "singleType",
|
||||||
|
"name": "Test Single Type",
|
||||||
|
},
|
||||||
|
"uid": "application::test-single-type.test-single-type",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -4,7 +4,7 @@ const waitRestart = require('../../../test/helpers/waitRestart');
|
|||||||
|
|
||||||
let rq;
|
let rq;
|
||||||
|
|
||||||
describe.only('Content Type Builder - Components', () => {
|
describe('Content Type Builder - Components', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const token = await registerAndLogin();
|
const token = await registerAndLogin();
|
||||||
rq = createAuthRequest(token);
|
rq = createAuthRequest(token);
|
||||||
|
|||||||
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Integration test for the content-type-buidler content types managment apis
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { registerAndLogin } = require('../../../test/helpers/auth');
|
||||||
|
const { createAuthRequest } = require('../../../test/helpers/request');
|
||||||
|
const waitRestart = require('../../../test/helpers/waitRestart');
|
||||||
|
|
||||||
|
let rq;
|
||||||
|
|
||||||
|
describe('Content Type Builder - Content types', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const token = await registerAndLogin();
|
||||||
|
rq = createAuthRequest(token);
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
afterEach(() => waitRestart());
|
||||||
|
|
||||||
|
describe('Collection Types', () => {
|
||||||
|
const collectionTypeUID =
|
||||||
|
'application::test-collection-type.test-collection-type';
|
||||||
|
|
||||||
|
test('Successfull creation of a collection type', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/content-type-builder/content-types',
|
||||||
|
body: {
|
||||||
|
contentType: {
|
||||||
|
name: 'Test Collection Type',
|
||||||
|
attributes: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(201);
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
data: {
|
||||||
|
uid: collectionTypeUID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Get collection type returns full schema and informations', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/content-type-builder/content-types/${collectionTypeUID}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Single Types', () => {
|
||||||
|
const singleTypeUID = 'application::test-single-type.test-single-type';
|
||||||
|
|
||||||
|
test('Successfull creation of a single type', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/content-type-builder/content-types',
|
||||||
|
body: {
|
||||||
|
contentType: {
|
||||||
|
kind: 'singleType',
|
||||||
|
name: 'Test Single Type',
|
||||||
|
attributes: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(201);
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
data: {
|
||||||
|
uid: singleTypeUID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Get single type returns full schema and informations', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/content-type-builder/content-types/${singleTypeUID}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -11,19 +11,6 @@ const _ = require('lodash');
|
|||||||
const uuid = require('uuid/v4');
|
const uuid = require('uuid/v4');
|
||||||
|
|
||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
if (!_.get(strapi.plugins['users-permissions'], 'config.jwtSecret')) {
|
|
||||||
const jwtSecret = uuid();
|
|
||||||
_.set(strapi.plugins['users-permissions'], 'config.jwtSecret', jwtSecret);
|
|
||||||
|
|
||||||
strapi.reload.isWatching = false;
|
|
||||||
await strapi.fs.writePluginFile(
|
|
||||||
'users-permissions',
|
|
||||||
'config/jwt.json',
|
|
||||||
JSON.stringify({ jwtSecret }, null, 2)
|
|
||||||
);
|
|
||||||
strapi.reload.isWatching = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pluginStore = strapi.store({
|
const pluginStore = strapi.store({
|
||||||
environment: '',
|
environment: '',
|
||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
@ -173,7 +160,22 @@ module.exports = async () => {
|
|||||||
await pluginStore.set({ key: 'advanced', value });
|
await pluginStore.set({ key: 'advanced', value });
|
||||||
}
|
}
|
||||||
|
|
||||||
return strapi.plugins[
|
await strapi.plugins[
|
||||||
'users-permissions'
|
'users-permissions'
|
||||||
].services.userspermissions.initialize();
|
].services.userspermissions.initialize();
|
||||||
|
|
||||||
|
if (!_.get(strapi.plugins['users-permissions'], 'config.jwtSecret')) {
|
||||||
|
const jwtSecret = uuid();
|
||||||
|
_.set(strapi.plugins['users-permissions'], 'config.jwtSecret', jwtSecret);
|
||||||
|
|
||||||
|
strapi.reload.isWatching = false;
|
||||||
|
|
||||||
|
await strapi.fs.writePluginFile(
|
||||||
|
'users-permissions',
|
||||||
|
'config/jwt.json',
|
||||||
|
JSON.stringify({ jwtSecret }, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
strapi.reload.isWatching = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -474,18 +474,17 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create two first default roles.
|
// Create two first default roles.
|
||||||
await Promise.all([
|
await strapi.query('role', 'users-permissions').create({
|
||||||
strapi.query('role', 'users-permissions').create({
|
name: 'Authenticated',
|
||||||
name: 'Authenticated',
|
description: 'Default role given to authenticated user.',
|
||||||
description: 'Default role given to authenticated user.',
|
type: 'authenticated',
|
||||||
type: 'authenticated',
|
});
|
||||||
}),
|
|
||||||
strapi.query('role', 'users-permissions').create({
|
await strapi.query('role', 'users-permissions').create({
|
||||||
name: 'Public',
|
name: 'Public',
|
||||||
description: 'Default role given to unauthenticated user.',
|
description: 'Default role given to unauthenticated user.',
|
||||||
type: 'public',
|
type: 'public',
|
||||||
}),
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
return this.updatePermissions();
|
return this.updatePermissions();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,18 +13,9 @@ const chalk = require('chalk');
|
|||||||
const CLITable = require('cli-table3');
|
const CLITable = require('cli-table3');
|
||||||
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const {
|
const loadModules = require('./core/load-modules');
|
||||||
loadConfig,
|
const bootstrap = require('./core/bootstrap');
|
||||||
loadApis,
|
const initCoreStore = require('./core/init-core-store');
|
||||||
loadAdmin,
|
|
||||||
loadPlugins,
|
|
||||||
loadMiddlewares,
|
|
||||||
loadHooks,
|
|
||||||
bootstrap,
|
|
||||||
loadExtensions,
|
|
||||||
initCoreStore,
|
|
||||||
loadComponents,
|
|
||||||
} = require('./core');
|
|
||||||
const initializeMiddlewares = require('./middlewares');
|
const initializeMiddlewares = require('./middlewares');
|
||||||
const initializeHooks = require('./hooks');
|
const initializeHooks = require('./hooks');
|
||||||
const createStrapiFs = require('./core/fs');
|
const createStrapiFs = require('./core/fs');
|
||||||
@ -304,51 +295,16 @@ class Strapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const modules = await loadModules(this);
|
||||||
config,
|
|
||||||
api,
|
|
||||||
admin,
|
|
||||||
plugins,
|
|
||||||
middlewares,
|
|
||||||
hook,
|
|
||||||
extensions,
|
|
||||||
components,
|
|
||||||
] = await Promise.all([
|
|
||||||
loadConfig(this),
|
|
||||||
loadApis(this),
|
|
||||||
loadAdmin(this),
|
|
||||||
loadPlugins(this),
|
|
||||||
loadMiddlewares(this),
|
|
||||||
loadHooks(this.config),
|
|
||||||
loadExtensions(this.config),
|
|
||||||
loadComponents(this),
|
|
||||||
]);
|
|
||||||
|
|
||||||
_.merge(this.config, config);
|
_.merge(this.config, modules.config);
|
||||||
|
|
||||||
this.api = api;
|
this.api = modules.api;
|
||||||
this.admin = admin;
|
this.admin = modules.admin;
|
||||||
this.components = components;
|
this.components = modules.components;
|
||||||
this.plugins = plugins;
|
this.plugins = modules.plugins;
|
||||||
this.middleware = middlewares;
|
this.middleware = modules.middlewares;
|
||||||
this.hook = hook;
|
this.hook = modules.hook;
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle plugin extensions
|
|
||||||
*/
|
|
||||||
// merge extensions config folders
|
|
||||||
_.mergeWith(this.plugins, extensions.merges, (objValue, srcValue, key) => {
|
|
||||||
// concat routes
|
|
||||||
if (_.isArray(srcValue) && _.isArray(objValue) && key === 'routes') {
|
|
||||||
return srcValue.concat(objValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// overwrite plugins with extensions overwrites
|
|
||||||
extensions.overwrites.forEach(({ path, mod }) => {
|
|
||||||
_.assign(_.get(this.plugins, path), mod);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Populate AST with configurations.
|
|
||||||
|
|
||||||
await bootstrap(this);
|
await bootstrap(this);
|
||||||
|
|
||||||
|
|||||||
16
packages/strapi/lib/core/bootstrap.js
vendored
16
packages/strapi/lib/core/bootstrap.js
vendored
@ -5,9 +5,11 @@ const _ = require('lodash');
|
|||||||
const { createController, createService } = require('../core-api');
|
const { createController, createService } = require('../core-api');
|
||||||
const getURLFromSegments = require('../utils/url-from-segments');
|
const getURLFromSegments = require('../utils/url-from-segments');
|
||||||
|
|
||||||
const pickSchema = obj =>
|
const getKind = obj => obj.kind || 'collectionType';
|
||||||
_.cloneDeep(
|
|
||||||
_.pick(obj, [
|
const pickSchema = model => {
|
||||||
|
const schema = _.cloneDeep(
|
||||||
|
_.pick(model, [
|
||||||
'connection',
|
'connection',
|
||||||
'collectionName',
|
'collectionName',
|
||||||
'info',
|
'info',
|
||||||
@ -16,6 +18,10 @@ const pickSchema = obj =>
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
schema.kind = getKind(model);
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = function(strapi) {
|
module.exports = function(strapi) {
|
||||||
// Retrieve Strapi version.
|
// Retrieve Strapi version.
|
||||||
strapi.config.uuid = _.get(strapi.config.info, 'strapi.uuid', '');
|
strapi.config.uuid = _.get(strapi.config.info, 'strapi.uuid', '');
|
||||||
@ -75,10 +81,12 @@ module.exports = function(strapi) {
|
|||||||
|
|
||||||
Object.assign(model, {
|
Object.assign(model, {
|
||||||
__schema__: pickSchema(model),
|
__schema__: pickSchema(model),
|
||||||
|
kind: getKind(model),
|
||||||
modelType: 'contentType',
|
modelType: 'contentType',
|
||||||
uid: `application::${apiName}.${modelName}`,
|
uid: `application::${apiName}.${modelName}`,
|
||||||
apiName,
|
apiName,
|
||||||
modelName,
|
modelName,
|
||||||
|
|
||||||
globalId: model.globalId || _.upperFirst(_.camelCase(modelName)),
|
globalId: model.globalId || _.upperFirst(_.camelCase(modelName)),
|
||||||
collectionName:
|
collectionName:
|
||||||
model.collectionName || `${modelName}`.toLocaleLowerCase(),
|
model.collectionName || `${modelName}`.toLocaleLowerCase(),
|
||||||
@ -157,6 +165,7 @@ module.exports = function(strapi) {
|
|||||||
Object.assign(model, {
|
Object.assign(model, {
|
||||||
__schema__: pickSchema(model),
|
__schema__: pickSchema(model),
|
||||||
modelType: 'contentType',
|
modelType: 'contentType',
|
||||||
|
kind: getKind(model),
|
||||||
uid: `strapi::${key}`,
|
uid: `strapi::${key}`,
|
||||||
plugin: 'admin',
|
plugin: 'admin',
|
||||||
modelName: key,
|
modelName: key,
|
||||||
@ -192,6 +201,7 @@ module.exports = function(strapi) {
|
|||||||
Object.assign(model, {
|
Object.assign(model, {
|
||||||
__schema__: pickSchema(model),
|
__schema__: pickSchema(model),
|
||||||
modelType: 'contentType',
|
modelType: 'contentType',
|
||||||
|
kind: getKind(model),
|
||||||
modelName: key,
|
modelName: key,
|
||||||
uid: `plugins::${pluginName}.${key}`,
|
uid: `plugins::${pluginName}.${key}`,
|
||||||
plugin: pluginName,
|
plugin: pluginName,
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const loadConfig = require('./load-config');
|
|
||||||
const loadApis = require('./load-apis');
|
|
||||||
const loadAdmin = require('./load-admin');
|
|
||||||
const loadPlugins = require('./load-plugins');
|
|
||||||
const loadMiddlewares = require('./load-middlewares');
|
|
||||||
const loadExtensions = require('./load-extensions');
|
|
||||||
const loadHooks = require('./load-hooks');
|
|
||||||
const bootstrap = require('./bootstrap');
|
|
||||||
const initCoreStore = require('./init-core-store');
|
|
||||||
const loadComponents = require('./load-components');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
loadConfig,
|
|
||||||
loadApis,
|
|
||||||
loadAdmin,
|
|
||||||
loadPlugins,
|
|
||||||
loadMiddlewares,
|
|
||||||
loadHooks,
|
|
||||||
loadExtensions,
|
|
||||||
loadComponents,
|
|
||||||
bootstrap,
|
|
||||||
initCoreStore,
|
|
||||||
};
|
|
||||||
73
packages/strapi/lib/core/load-modules.js
Normal file
73
packages/strapi/lib/core/load-modules.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Load Modules is the root module loader.
|
||||||
|
* This is where all the strapi enviornment is laoded
|
||||||
|
* - APIs
|
||||||
|
* - Plugins
|
||||||
|
* - Hooks
|
||||||
|
* - Middlewres
|
||||||
|
* - Components
|
||||||
|
* - ContentTypes
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const loadConfig = require('./load-config');
|
||||||
|
const loadApis = require('./load-apis');
|
||||||
|
const loadAdmin = require('./load-admin');
|
||||||
|
const loadPlugins = require('./load-plugins');
|
||||||
|
const loadMiddlewares = require('./load-middlewares');
|
||||||
|
const loadExtensions = require('./load-extensions');
|
||||||
|
const loadHooks = require('./load-hooks');
|
||||||
|
const loadComponents = require('./load-components');
|
||||||
|
|
||||||
|
module.exports = async strapi => {
|
||||||
|
const [
|
||||||
|
config,
|
||||||
|
api,
|
||||||
|
admin,
|
||||||
|
plugins,
|
||||||
|
middlewares,
|
||||||
|
hook,
|
||||||
|
extensions,
|
||||||
|
components,
|
||||||
|
] = await Promise.all([
|
||||||
|
loadConfig(strapi),
|
||||||
|
loadApis(strapi),
|
||||||
|
loadAdmin(strapi),
|
||||||
|
loadPlugins(strapi),
|
||||||
|
loadMiddlewares(strapi),
|
||||||
|
loadHooks(strapi.config),
|
||||||
|
loadExtensions(strapi.config),
|
||||||
|
loadComponents(strapi),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: move this into the appropriate loaders
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle plugin extensions
|
||||||
|
*/
|
||||||
|
// merge extensions config folders
|
||||||
|
_.mergeWith(plugins, extensions.merges, (objValue, srcValue, key) => {
|
||||||
|
// concat routes
|
||||||
|
if (_.isArray(srcValue) && _.isArray(objValue) && key === 'routes') {
|
||||||
|
return srcValue.concat(objValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// overwrite plugins with extensions overwrites
|
||||||
|
extensions.overwrites.forEach(({ path, mod }) => {
|
||||||
|
_.assign(_.get(plugins, path), mod);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
api,
|
||||||
|
admin,
|
||||||
|
plugins,
|
||||||
|
middlewares,
|
||||||
|
hook,
|
||||||
|
extensions,
|
||||||
|
components,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,8 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const glob = require('./glob');
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const glob = require('./glob');
|
||||||
const filePathToPath = require('./filepath-to-prop-path');
|
const filePathToPath = require('./filepath-to-prop-path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user