@akemona-org/strapi-plugin-content-type-builder 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +19 -0
- package/admin/src/InjectedComponents/ContentManager/EditSettingViewButton.js +76 -0
- package/admin/src/InjectedComponents/ContentManager/EditViewLink.js +44 -0
- package/admin/src/assets/images/logo.svg +1 -0
- package/admin/src/components/AllowedTypesSelect/MenuList.js +115 -0
- package/admin/src/components/AllowedTypesSelect/Text.js +26 -0
- package/admin/src/components/AllowedTypesSelect/index.js +55 -0
- package/admin/src/components/AttributeOption/Button.js +50 -0
- package/admin/src/components/AttributeOption/Card.js +26 -0
- package/admin/src/components/AttributeOption/index.js +127 -0
- package/admin/src/components/BooleanBox/CT.js +21 -0
- package/admin/src/components/BooleanBox/Enumeration.js +10 -0
- package/admin/src/components/BooleanBox/EnumerationWrapper.js +14 -0
- package/admin/src/components/BooleanBox/Label.js +9 -0
- package/admin/src/components/BooleanBox/ST.js +21 -0
- package/admin/src/components/BooleanBox/Wrapper.js +75 -0
- package/admin/src/components/BooleanBox/icons/CTSelected.js +136 -0
- package/admin/src/components/BooleanBox/icons/CTUnselected.js +136 -0
- package/admin/src/components/BooleanBox/icons/STSelected.js +80 -0
- package/admin/src/components/BooleanBox/icons/STUnselected.js +76 -0
- package/admin/src/components/BooleanBox/index.js +103 -0
- package/admin/src/components/CheckboxWithDescription/Wrapper.js +12 -0
- package/admin/src/components/CheckboxWithDescription/index.js +40 -0
- package/admin/src/components/ComponentCard/Close.js +12 -0
- package/admin/src/components/ComponentCard/Wrapper.js +84 -0
- package/admin/src/components/ComponentCard/index.js +62 -0
- package/admin/src/components/ComponentIconPicker/Cell.js +40 -0
- package/admin/src/components/ComponentIconPicker/CellRenderer.js +36 -0
- package/admin/src/components/ComponentIconPicker/Search.js +12 -0
- package/admin/src/components/ComponentIconPicker/SearchWrapper.js +34 -0
- package/admin/src/components/ComponentIconPicker/Wrapper.js +60 -0
- package/admin/src/components/ComponentIconPicker/index.js +143 -0
- package/admin/src/components/ComponentList/index.js +78 -0
- package/admin/src/components/ComponentSelect/Category.js +20 -0
- package/admin/src/components/ComponentSelect/CategoryName.js +11 -0
- package/admin/src/components/ComponentSelect/MenuList.js +110 -0
- package/admin/src/components/ComponentSelect/MultipleMenuList.js +271 -0
- package/admin/src/components/ComponentSelect/Value.js +83 -0
- package/admin/src/components/ComponentSelect/index.js +83 -0
- package/admin/src/components/ComponentSelect/utils/HasSomeSubArray.js +5 -0
- package/admin/src/components/ComponentSelect/utils/hasSubArray.js +5 -0
- package/admin/src/components/CreatableSelect/index.js +58 -0
- package/admin/src/components/CustomCheckbox/StyledCustomCheckbox.js +25 -0
- package/admin/src/components/CustomCheckbox/index.js +71 -0
- package/admin/src/components/CustomLink/P.js +17 -0
- package/admin/src/components/CustomLink/StyledCustomLink.js +20 -0
- package/admin/src/components/CustomLink/index.js +31 -0
- package/admin/src/components/DynamicZoneList/ComponentButton.js +39 -0
- package/admin/src/components/DynamicZoneList/index.js +124 -0
- package/admin/src/components/HeaderModalNavContainer/index.js +25 -0
- package/admin/src/components/HeaderNavLink/Wrapper.js +21 -0
- package/admin/src/components/HeaderNavLink/index.js +50 -0
- package/admin/src/components/List/List.js +131 -0
- package/admin/src/components/List/index.js +295 -0
- package/admin/src/components/ListButton/index.js +9 -0
- package/admin/src/components/ListHeader/Title.js +19 -0
- package/admin/src/components/ListHeader/Wrapper.js +24 -0
- package/admin/src/components/ListHeader/index.js +37 -0
- package/admin/src/components/ListRow/Wrapper.js +76 -0
- package/admin/src/components/ListRow/index.js +334 -0
- package/admin/src/components/ModalHeader/ComponentIcon.js +21 -0
- package/admin/src/components/ModalHeader/ComponentInfos.js +22 -0
- package/admin/src/components/ModalHeader/ComponentInfosWrapper.js +9 -0
- package/admin/src/components/ModalHeader/DropdownInfos.js +50 -0
- package/admin/src/components/ModalHeader/Icon.js +26 -0
- package/admin/src/components/ModalHeader/IconWrapper.js +11 -0
- package/admin/src/components/ModalHeader/Item.js +19 -0
- package/admin/src/components/ModalHeader/Menu.js +8 -0
- package/admin/src/components/ModalHeader/Toggle.js +14 -0
- package/admin/src/components/ModalHeader/Wrapper.js +12 -0
- package/admin/src/components/ModalHeader/index.js +105 -0
- package/admin/src/components/RelationForm/Wrapper.js +9 -0
- package/admin/src/components/RelationForm/index.js +66 -0
- package/admin/src/components/RelationFormBox/Wrapper.js +34 -0
- package/admin/src/components/RelationFormBox/index.js +85 -0
- package/admin/src/components/RelationFormNaturePicker/Wrapper.js +53 -0
- package/admin/src/components/RelationFormNaturePicker/index.js +112 -0
- package/admin/src/components/RelationTargetPicker/Item.js +62 -0
- package/admin/src/components/RelationTargetPicker/Wrapper.js +89 -0
- package/admin/src/components/RelationTargetPicker/index.js +88 -0
- package/admin/src/components/SelectCheckbox/index.js +8 -0
- package/admin/src/components/SelectMenuSubUl/ToggleUl.js +23 -0
- package/admin/src/components/SelectMenuSubUl/index.js +23 -0
- package/admin/src/components/SelectMenuUl/index.js +66 -0
- package/admin/src/components/Td/index.js +37 -0
- package/admin/src/components/UpperFirst/index.js +14 -0
- package/admin/src/components/WrapperSelect/index.js +89 -0
- package/admin/src/containers/App/Wrapper.js +14 -0
- package/admin/src/containers/App/index.js +39 -0
- package/admin/src/containers/App/utils/icons.json +962 -0
- package/admin/src/containers/DataManagerProvider/constants.js +15 -0
- package/admin/src/containers/DataManagerProvider/index.js +610 -0
- package/admin/src/containers/DataManagerProvider/reducer.js +461 -0
- package/admin/src/containers/DataManagerProvider/selectors.js +25 -0
- package/admin/src/containers/DataManagerProvider/utils/cleanData.js +167 -0
- package/admin/src/containers/DataManagerProvider/utils/createDataObject.js +8 -0
- package/admin/src/containers/DataManagerProvider/utils/createModifiedDataSchema.js +91 -0
- package/admin/src/containers/DataManagerProvider/utils/retrieveComponentsFromSchema.js +55 -0
- package/admin/src/containers/DataManagerProvider/utils/retrieveComponentsThatHaveComponents.js +35 -0
- package/admin/src/containers/DataManagerProvider/utils/retrieveNestedComponents.js +34 -0
- package/admin/src/containers/DataManagerProvider/utils/retrieveSpecificInfoFromComponents.js +12 -0
- package/admin/src/containers/FormModal/CustomButton.js +12 -0
- package/admin/src/containers/FormModal/attributes/advancedForm.js +269 -0
- package/admin/src/containers/FormModal/attributes/attributeOptions.js +99 -0
- package/admin/src/containers/FormModal/attributes/baseForm.js +322 -0
- package/admin/src/containers/FormModal/attributes/commonBaseForm.js +7 -0
- package/admin/src/containers/FormModal/attributes/form.js +9 -0
- package/admin/src/containers/FormModal/attributes/index.js +4 -0
- package/admin/src/containers/FormModal/attributes/nameField.js +19 -0
- package/admin/src/containers/FormModal/attributes/types.js +309 -0
- package/admin/src/containers/FormModal/attributes/uiHelpers.js +11 -0
- package/admin/src/containers/FormModal/attributes/validation/common.js +151 -0
- package/admin/src/containers/FormModal/category/createCategorySchema.js +28 -0
- package/admin/src/containers/FormModal/category/form.js +27 -0
- package/admin/src/containers/FormModal/category/index.js +3 -0
- package/admin/src/containers/FormModal/category/regex.js +3 -0
- package/admin/src/containers/FormModal/component/componentField.js +25 -0
- package/admin/src/containers/FormModal/component/createComponentSchema.js +49 -0
- package/admin/src/containers/FormModal/component/form.js +67 -0
- package/admin/src/containers/FormModal/component/index.js +4 -0
- package/admin/src/containers/FormModal/constants.js +11 -0
- package/admin/src/containers/FormModal/contentType/createContentTypeSchema.js +44 -0
- package/admin/src/containers/FormModal/contentType/form.js +116 -0
- package/admin/src/containers/FormModal/contentType/index.js +2 -0
- package/admin/src/containers/FormModal/dynamicZone/form.js +45 -0
- package/admin/src/containers/FormModal/dynamicZone/index.js +2 -0
- package/admin/src/containers/FormModal/forms/index.js +164 -0
- package/admin/src/containers/FormModal/index.js +1552 -0
- package/admin/src/containers/FormModal/reducer.js +293 -0
- package/admin/src/containers/FormModal/selectors.js +24 -0
- package/admin/src/containers/FormModal/utils/canEditContentType.js +19 -0
- package/admin/src/containers/FormModal/utils/createHeadersArray.js +37 -0
- package/admin/src/containers/FormModal/utils/createHeadersObjectFromArray.js +27 -0
- package/admin/src/containers/FormModal/utils/createUid.js +17 -0
- package/admin/src/containers/FormModal/utils/getAttributesToDisplay.js +35 -0
- package/admin/src/containers/FormModal/utils/getModalTitleSubHeader.js +25 -0
- package/admin/src/containers/FormModal/utils/getNextSearch.js +17 -0
- package/admin/src/containers/FormModal/utils/index.js +6 -0
- package/admin/src/containers/FormModal/utils/relations.js +9 -0
- package/admin/src/containers/FormModal/utils/staticData.js +43 -0
- package/admin/src/containers/Initializer/index.js +26 -0
- package/admin/src/containers/LeftMenu/Wrapper.js +14 -0
- package/admin/src/containers/LeftMenu/index.js +220 -0
- package/admin/src/containers/ListView/Wrapper.js +16 -0
- package/admin/src/containers/ListView/index.js +322 -0
- package/admin/src/containers/NotFoundPage/index.js +20 -0
- package/admin/src/containers/RecursivePath/index.js +22 -0
- package/admin/src/contexts/DataManagerContext.js +5 -0
- package/admin/src/contexts/ListViewContext.js +5 -0
- package/admin/src/hooks/useDataManager.js +6 -0
- package/admin/src/hooks/useListView.js +6 -0
- package/admin/src/icons/Curve.js +26 -0
- package/admin/src/icons/ManyToMany.js +98 -0
- package/admin/src/icons/ManyToOne.js +64 -0
- package/admin/src/icons/ManyWay.js +36 -0
- package/admin/src/icons/OneToMany.js +65 -0
- package/admin/src/icons/OneToOne.js +47 -0
- package/admin/src/icons/OneWay.js +38 -0
- package/admin/src/index.js +83 -0
- package/admin/src/lifecycles.js +13 -0
- package/admin/src/permissions.js +9 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/reducers.js +10 -0
- package/admin/src/translations/ar.json +48 -0
- package/admin/src/translations/cs.json +151 -0
- package/admin/src/translations/de.json +187 -0
- package/admin/src/translations/dk.json +181 -0
- package/admin/src/translations/en.json +188 -0
- package/admin/src/translations/es.json +188 -0
- package/admin/src/translations/fr.json +87 -0
- package/admin/src/translations/id.json +187 -0
- package/admin/src/translations/index.js +51 -0
- package/admin/src/translations/it.json +187 -0
- package/admin/src/translations/ja.json +50 -0
- package/admin/src/translations/ko.json +57 -0
- package/admin/src/translations/ms.json +179 -0
- package/admin/src/translations/nl.json +171 -0
- package/admin/src/translations/pl.json +152 -0
- package/admin/src/translations/pt-BR.json +51 -0
- package/admin/src/translations/pt.json +51 -0
- package/admin/src/translations/ru.json +188 -0
- package/admin/src/translations/sk.json +186 -0
- package/admin/src/translations/th.json +181 -0
- package/admin/src/translations/tr.json +51 -0
- package/admin/src/translations/uk.json +180 -0
- package/admin/src/translations/zh-Hans.json +188 -0
- package/admin/src/translations/zh.json +178 -0
- package/admin/src/utils/convertAttrObjToArray.js +7 -0
- package/admin/src/utils/formAPI.js +112 -0
- package/admin/src/utils/getAttributeDisplayedType.js +31 -0
- package/admin/src/utils/getComponents.js +31 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/admin/src/utils/makeSearch.js +15 -0
- package/admin/src/utils/makeUnique.js +3 -0
- package/config/functions/bootstrap.js +14 -0
- package/config/routes.json +144 -0
- package/controllers/Builder.js +7 -0
- package/controllers/ComponentCategories.js +43 -0
- package/controllers/Components.js +152 -0
- package/controllers/Connections.js +9 -0
- package/controllers/ContentTypes.js +143 -0
- package/controllers/validation/common.js +118 -0
- package/controllers/validation/component-category.js +21 -0
- package/controllers/validation/component.js +89 -0
- package/controllers/validation/content-type.js +161 -0
- package/controllers/validation/data-transform.js +32 -0
- package/controllers/validation/model-schema.js +91 -0
- package/controllers/validation/relations.js +56 -0
- package/controllers/validation/types.js +277 -0
- package/package.json +63 -0
- package/services/Builder.js +7 -0
- package/services/ComponentCategories.js +88 -0
- package/services/Components.js +103 -0
- package/services/ContentTypes.js +263 -0
- package/services/api-handler.js +158 -0
- package/services/constants.js +59 -0
- package/services/schema-builder/component-builder.js +137 -0
- package/services/schema-builder/content-type-builder.js +256 -0
- package/services/schema-builder/index.js +196 -0
- package/services/schema-builder/schema-handler.js +297 -0
- package/utils/attributes.js +154 -0
- package/utils/helpers.js +31 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { getOr } = require('lodash/fp');
|
|
5
|
+
const pluralize = require('pluralize');
|
|
6
|
+
const generator = require('strapi-generate');
|
|
7
|
+
|
|
8
|
+
const { nameToSlug, contentTypes: contentTypesUtils } = require('@akemona-org/strapi-utils');
|
|
9
|
+
const { formatAttributes, replaceTemporaryUIDs } = require('../utils/attributes');
|
|
10
|
+
const createBuilder = require('./schema-builder');
|
|
11
|
+
const apiHandler = require('./api-handler');
|
|
12
|
+
const { coreUids, pluginsUids } = require('./constants');
|
|
13
|
+
|
|
14
|
+
const isContentTypeVisible = (model) =>
|
|
15
|
+
getOr(true, 'pluginOptions.content-type-builder.visible', model) === true;
|
|
16
|
+
|
|
17
|
+
const getRestrictRelationsTo = (contentType = {}) => {
|
|
18
|
+
const { uid } = contentType;
|
|
19
|
+
if (uid === coreUids.STRAPI_USER) {
|
|
20
|
+
return ['oneWay', 'manyWay'];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (uid.startsWith(coreUids.PREFIX) || uid === pluginsUids.UPLOAD_FILE) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getformattedName = (contentType = {}) => {
|
|
31
|
+
const { uid, info } = contentType;
|
|
32
|
+
const name = _.get(info, 'name') || _.upperFirst(pluralize(uid));
|
|
33
|
+
|
|
34
|
+
return name;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format a contentType info to be used by the front-end
|
|
39
|
+
* @param {Object} contentType
|
|
40
|
+
*/
|
|
41
|
+
const formatContentType = (contentType) => {
|
|
42
|
+
const { uid, kind, modelName, plugin, connection, collectionName, info, options } = contentType;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
uid,
|
|
46
|
+
plugin,
|
|
47
|
+
apiID: modelName,
|
|
48
|
+
schema: {
|
|
49
|
+
name: getformattedName(contentType),
|
|
50
|
+
description: _.get(info, 'description', ''),
|
|
51
|
+
draftAndPublish: contentTypesUtils.hasDraftAndPublish({ options }),
|
|
52
|
+
pluginOptions: contentType.pluginOptions,
|
|
53
|
+
connection,
|
|
54
|
+
kind: kind || 'collectionType',
|
|
55
|
+
collectionName,
|
|
56
|
+
attributes: formatAttributes(contentType),
|
|
57
|
+
visible: isContentTypeVisible(contentType),
|
|
58
|
+
restrictRelationsTo: getRestrictRelationsTo(contentType),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createContentTypes = async (contentTypes) => {
|
|
64
|
+
const builder = createBuilder();
|
|
65
|
+
const createdContentTypes = [];
|
|
66
|
+
|
|
67
|
+
for (const contentType of contentTypes) {
|
|
68
|
+
createdContentTypes.push(await createContentType(contentType, { defaultBuilder: builder }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await builder.writeFiles();
|
|
72
|
+
|
|
73
|
+
return createdContentTypes;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates a content type and handle the nested components sent with it
|
|
78
|
+
* @param {Object} params params object
|
|
79
|
+
* @param {Object} params.contentType Main component to create
|
|
80
|
+
* @param {Array<Object>} params.components List of nested components to created or edit
|
|
81
|
+
* @param {Object} options
|
|
82
|
+
* @param {Builder} options.defaultBuilder
|
|
83
|
+
*/
|
|
84
|
+
const createContentType = async ({ contentType, components = [] }, options = {}) => {
|
|
85
|
+
const builder = options.defaultBuilder || createBuilder();
|
|
86
|
+
const uidMap = builder.createNewComponentUIDMap(components);
|
|
87
|
+
|
|
88
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
89
|
+
|
|
90
|
+
const newContentType = builder.createContentType(replaceTmpUIDs(contentType));
|
|
91
|
+
|
|
92
|
+
// allow components to target the new contentType
|
|
93
|
+
const targetContentType = (infos) => {
|
|
94
|
+
Object.keys(infos.attributes).forEach((key) => {
|
|
95
|
+
const { target } = infos.attributes[key];
|
|
96
|
+
if (target === '__contentType__') {
|
|
97
|
+
infos.attributes[key].target = newContentType.uid;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return infos;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
components.forEach((component) => {
|
|
105
|
+
const options = replaceTmpUIDs(targetContentType(component));
|
|
106
|
+
|
|
107
|
+
if (!_.has(component, 'uid')) {
|
|
108
|
+
return builder.createComponent(options);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return builder.editComponent(options);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// generate api skeleton
|
|
115
|
+
await generateAPI({
|
|
116
|
+
name: contentType.name,
|
|
117
|
+
kind: contentType.kind,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!options.defaultBuilder) {
|
|
121
|
+
await builder.writeFiles();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return newContentType;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate an API squeleton
|
|
129
|
+
* @param {string} name
|
|
130
|
+
*/
|
|
131
|
+
const generateAPI = ({ name, kind = 'collectionType' }) => {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const scope = {
|
|
134
|
+
generatorType: 'api',
|
|
135
|
+
id: nameToSlug(name),
|
|
136
|
+
name: nameToSlug(name),
|
|
137
|
+
rootPath: strapi.dir,
|
|
138
|
+
args: {
|
|
139
|
+
attributes: {},
|
|
140
|
+
kind,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
generator(scope, {
|
|
145
|
+
success: () => resolve(),
|
|
146
|
+
error: (err) => reject(err),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Edits a contentType and handle the nested contentTypes sent with it
|
|
153
|
+
* @param {String} uid Content-type's uid
|
|
154
|
+
* @param {Object} params params object
|
|
155
|
+
* @param {Object} params.contentType Main contentType to create
|
|
156
|
+
* @param {Array<Object>} params.components List of nested components to created or edit
|
|
157
|
+
*/
|
|
158
|
+
const editContentType = async (uid, { contentType, components = [] }) => {
|
|
159
|
+
const builder = createBuilder();
|
|
160
|
+
|
|
161
|
+
const previousKind = builder.contentTypes.get(uid).schema.kind;
|
|
162
|
+
const newKind = contentType.kind || previousKind;
|
|
163
|
+
|
|
164
|
+
if (newKind !== previousKind && newKind === 'singleType') {
|
|
165
|
+
const entryCount = await strapi.query(uid).count();
|
|
166
|
+
if (entryCount > 1) {
|
|
167
|
+
throw strapi.errors.badRequest(
|
|
168
|
+
'You cannot convert a collectionType to a singleType when having multiple entries in DB'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const uidMap = builder.createNewComponentUIDMap(components);
|
|
174
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
175
|
+
|
|
176
|
+
const updatedContentType = builder.editContentType({
|
|
177
|
+
uid,
|
|
178
|
+
...replaceTmpUIDs(contentType),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
components.forEach((component) => {
|
|
182
|
+
if (!_.has(component, 'uid')) {
|
|
183
|
+
return builder.createComponent(replaceTmpUIDs(component));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return builder.editComponent(replaceTmpUIDs(component));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (newKind !== previousKind) {
|
|
190
|
+
await apiHandler.backup(uid);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await apiHandler.clear(uid);
|
|
194
|
+
|
|
195
|
+
// generate new api skeleton
|
|
196
|
+
await generateAPI({
|
|
197
|
+
name: updatedContentType.schema.info.name,
|
|
198
|
+
kind: updatedContentType.schema.kind,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await builder.writeFiles();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
strapi.log.error(error);
|
|
204
|
+
await apiHandler.rollback(uid);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return updatedContentType;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await builder.writeFiles();
|
|
211
|
+
return updatedContentType;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const deleteContentTypes = async (uids) => {
|
|
215
|
+
const builder = createBuilder();
|
|
216
|
+
|
|
217
|
+
for (const uid of uids) {
|
|
218
|
+
await deleteContentType(uid, builder);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await builder.writeFiles();
|
|
222
|
+
for (const uid of uids) {
|
|
223
|
+
try {
|
|
224
|
+
await apiHandler.clear(uid);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
strapi.log.error(error);
|
|
227
|
+
await apiHandler.rollback(uid);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Deletes a content type and the api files related to it
|
|
234
|
+
* @param {string} uid content type uid
|
|
235
|
+
* @param defaultBuilder
|
|
236
|
+
*/
|
|
237
|
+
const deleteContentType = async (uid, defaultBuilder = undefined) => {
|
|
238
|
+
const builder = defaultBuilder || createBuilder();
|
|
239
|
+
// make a backup
|
|
240
|
+
await apiHandler.backup(uid);
|
|
241
|
+
|
|
242
|
+
const contentType = builder.deleteContentType(uid);
|
|
243
|
+
|
|
244
|
+
if (!defaultBuilder) {
|
|
245
|
+
try {
|
|
246
|
+
await builder.writeFiles();
|
|
247
|
+
await apiHandler.clear(uid);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
await apiHandler.rollback(uid);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return contentType;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
createContentType,
|
|
258
|
+
editContentType,
|
|
259
|
+
deleteContentType,
|
|
260
|
+
formatContentType,
|
|
261
|
+
createContentTypes,
|
|
262
|
+
deleteContentTypes,
|
|
263
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deletes the API folder of a contentType
|
|
9
|
+
* @param {string} uid content type uid
|
|
10
|
+
*/
|
|
11
|
+
async function clear(uid) {
|
|
12
|
+
const { apiName, __filename__ } = strapi.contentTypes[uid];
|
|
13
|
+
|
|
14
|
+
const apiFolder = path.join(strapi.dir, 'api', apiName);
|
|
15
|
+
|
|
16
|
+
// base name of the model file that will be use as comparator
|
|
17
|
+
const baseName = path.basename(__filename__, '.settings.json');
|
|
18
|
+
|
|
19
|
+
await recursiveRemoveFiles(apiFolder, createDeleteApiFunction(baseName));
|
|
20
|
+
await deleteBackup(uid);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Backups the API folder of a contentType
|
|
25
|
+
* @param {string} uid content type uid
|
|
26
|
+
*/
|
|
27
|
+
async function backup(uid) {
|
|
28
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
29
|
+
|
|
30
|
+
const apiFolder = path.join(strapi.dir, 'api', apiName);
|
|
31
|
+
const backupFolder = path.join(strapi.dir, 'api', '.backup', apiName);
|
|
32
|
+
|
|
33
|
+
// backup the api folder
|
|
34
|
+
await fse.copy(apiFolder, backupFolder);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Deletes an API backup folder
|
|
39
|
+
* @param {string} uid content type uid
|
|
40
|
+
*/
|
|
41
|
+
async function deleteBackup(uid) {
|
|
42
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
43
|
+
|
|
44
|
+
const backupFolder = path.join(strapi.dir, 'api', '.backup');
|
|
45
|
+
const apiBackupFolder = path.join(strapi.dir, 'api', '.backup', apiName);
|
|
46
|
+
|
|
47
|
+
await fse.remove(apiBackupFolder);
|
|
48
|
+
|
|
49
|
+
const list = await fse.readdir(backupFolder);
|
|
50
|
+
if (list.length === 0) {
|
|
51
|
+
await fse.remove(backupFolder);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Rollbacks the API folder of a contentType
|
|
57
|
+
* @param {string} uid content type uid
|
|
58
|
+
*/
|
|
59
|
+
async function rollback(uid) {
|
|
60
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
61
|
+
|
|
62
|
+
const apiFolder = path.join(strapi.dir, 'api', apiName);
|
|
63
|
+
const backupFolder = path.join(strapi.dir, 'api', '.backup', apiName);
|
|
64
|
+
|
|
65
|
+
const exists = await fse.exists(backupFolder);
|
|
66
|
+
|
|
67
|
+
if (!exists) {
|
|
68
|
+
throw new Error('Cannot rollback api that was not backed up');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await fse.remove(apiFolder);
|
|
72
|
+
await fse.copy(backupFolder, apiFolder);
|
|
73
|
+
await deleteBackup(uid);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates a delete function to clear an api folder
|
|
78
|
+
* @param {string} baseName
|
|
79
|
+
*/
|
|
80
|
+
const createDeleteApiFunction = baseName => {
|
|
81
|
+
const startWithBaseName = startWithName(baseName + '.');
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Delets a file in an api.
|
|
85
|
+
* Will only update routes.json instead of deleting it if other routes are present
|
|
86
|
+
* @param {string} filePath file path to delete
|
|
87
|
+
*/
|
|
88
|
+
return async filePath => {
|
|
89
|
+
const fileName = path.basename(filePath);
|
|
90
|
+
|
|
91
|
+
if (startWithBaseName(fileName)) return fse.remove(filePath);
|
|
92
|
+
|
|
93
|
+
if (fileName === 'routes.json') {
|
|
94
|
+
const { routes } = await fse.readJSON(filePath);
|
|
95
|
+
|
|
96
|
+
const routesToKeep = routes.filter(route => !startWithBaseName(route.handler));
|
|
97
|
+
|
|
98
|
+
if (routesToKeep.length === 0) {
|
|
99
|
+
return fse.remove(filePath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await fse.writeJSON(
|
|
103
|
+
filePath,
|
|
104
|
+
{
|
|
105
|
+
routes: routesToKeep,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
spaces: 2,
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns a function that checks if the passed string starts with the name
|
|
117
|
+
* @param {string} prefix
|
|
118
|
+
* @returns {Function} a comparing function
|
|
119
|
+
*/
|
|
120
|
+
const startWithName = prefix => {
|
|
121
|
+
/**
|
|
122
|
+
* Checks if str starts with prefix case insensitive
|
|
123
|
+
* @param {string} str string to compare
|
|
124
|
+
*/
|
|
125
|
+
return str => _.startsWith(_.toLower(str), _.toLower(prefix));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Deletes a folder recursively using a delete function
|
|
130
|
+
* @param {string} folder folder to delete
|
|
131
|
+
* @param {Function} deleteFn function to call with the file path to delete
|
|
132
|
+
*/
|
|
133
|
+
const recursiveRemoveFiles = async (folder, deleteFn) => {
|
|
134
|
+
const filesName = await fse.readdir(folder);
|
|
135
|
+
|
|
136
|
+
for (const fileName of filesName) {
|
|
137
|
+
const filePath = path.join(folder, fileName);
|
|
138
|
+
|
|
139
|
+
const stat = await fse.stat(filePath);
|
|
140
|
+
|
|
141
|
+
if (stat.isDirectory()) {
|
|
142
|
+
await recursiveRemoveFiles(filePath, deleteFn);
|
|
143
|
+
} else {
|
|
144
|
+
await deleteFn(filePath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const files = await fse.readdir(folder);
|
|
149
|
+
if (files.length === 0) {
|
|
150
|
+
await fse.remove(folder);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
clear,
|
|
156
|
+
backup,
|
|
157
|
+
rollback,
|
|
158
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CONTENT_TYPE = 'CONTENT_TYPE';
|
|
4
|
+
const COMPONENT = 'COMPONENT';
|
|
5
|
+
|
|
6
|
+
const SINGLE_TYPE = 'singleType';
|
|
7
|
+
const COLLECTION_TYPE = 'collectionType';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TYPES = [
|
|
10
|
+
// advanced types
|
|
11
|
+
'media',
|
|
12
|
+
|
|
13
|
+
// scalar types
|
|
14
|
+
'string',
|
|
15
|
+
'text',
|
|
16
|
+
'richtext',
|
|
17
|
+
'json',
|
|
18
|
+
'enumeration',
|
|
19
|
+
'password',
|
|
20
|
+
'email',
|
|
21
|
+
'integer',
|
|
22
|
+
'biginteger',
|
|
23
|
+
'float',
|
|
24
|
+
'decimal',
|
|
25
|
+
'date',
|
|
26
|
+
'time',
|
|
27
|
+
'datetime',
|
|
28
|
+
'timestamp',
|
|
29
|
+
'boolean',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const VALID_UID_TARGETS = ['string', 'text'];
|
|
33
|
+
|
|
34
|
+
const FORBIDDEN_ATTRIBUTE_NAMES = ['__component', '__contentType'];
|
|
35
|
+
|
|
36
|
+
const PREFIX = 'strapi::';
|
|
37
|
+
const STRAPI_USER = 'strapi::user';
|
|
38
|
+
const UPLOAD_FILE = 'plugins::upload.file';
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
DEFAULT_TYPES,
|
|
42
|
+
typeKinds: {
|
|
43
|
+
SINGLE_TYPE,
|
|
44
|
+
COLLECTION_TYPE,
|
|
45
|
+
},
|
|
46
|
+
modelTypes: {
|
|
47
|
+
CONTENT_TYPE,
|
|
48
|
+
COMPONENT,
|
|
49
|
+
},
|
|
50
|
+
FORBIDDEN_ATTRIBUTE_NAMES,
|
|
51
|
+
VALID_UID_TARGETS,
|
|
52
|
+
coreUids: {
|
|
53
|
+
STRAPI_USER,
|
|
54
|
+
PREFIX,
|
|
55
|
+
},
|
|
56
|
+
pluginsUids: {
|
|
57
|
+
UPLOAD_FILE,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const pluralize = require('pluralize');
|
|
6
|
+
|
|
7
|
+
const { nameToSlug, nameToCollectionName } = require('@akemona-org/strapi-utils');
|
|
8
|
+
const { isConfigurable } = require('../../utils/attributes');
|
|
9
|
+
const createSchemaHandler = require('./schema-handler');
|
|
10
|
+
|
|
11
|
+
module.exports = function createComponentBuilder() {
|
|
12
|
+
return {
|
|
13
|
+
/**
|
|
14
|
+
* Returns a uid from a component infos
|
|
15
|
+
* @param {Object} options options
|
|
16
|
+
* @param {string} options.category component category
|
|
17
|
+
* @param {string} options.name component name
|
|
18
|
+
*/
|
|
19
|
+
createComponentUID({ category, name }) {
|
|
20
|
+
return `${nameToSlug(category)}.${nameToSlug(name)}`;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
createNewComponentUIDMap(components) {
|
|
24
|
+
return components.reduce((uidMap, component) => {
|
|
25
|
+
uidMap[component.tmpUID] = this.createComponentUID(component);
|
|
26
|
+
return uidMap;
|
|
27
|
+
}, {});
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* create a component in the tmpComponent map
|
|
32
|
+
*/
|
|
33
|
+
createComponent(infos) {
|
|
34
|
+
const uid = this.createComponentUID(infos);
|
|
35
|
+
|
|
36
|
+
if (this.components.has(uid)) {
|
|
37
|
+
throw new Error('component.alreadyExists');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handler = createSchemaHandler({
|
|
41
|
+
dir: path.join(strapi.dir, 'components', nameToSlug(infos.category)),
|
|
42
|
+
filename: `${nameToSlug(infos.name)}.json`,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const defaultCollectionName = `components_${nameToCollectionName(
|
|
46
|
+
infos.category
|
|
47
|
+
)}_${nameToCollectionName(pluralize(infos.name))}`;
|
|
48
|
+
|
|
49
|
+
handler
|
|
50
|
+
.setUID(uid)
|
|
51
|
+
.set('collectionName', infos.collectionName || defaultCollectionName)
|
|
52
|
+
.set(['info', 'name'], infos.name)
|
|
53
|
+
.set(['info', 'icon'], infos.icon)
|
|
54
|
+
.set(['info', 'description'], infos.description)
|
|
55
|
+
.set('pluginOptions', infos.pluginOptions)
|
|
56
|
+
.setAttributes(this.convertAttributes(infos.attributes));
|
|
57
|
+
|
|
58
|
+
/* if (this.components.size === 0) {
|
|
59
|
+
strapi.telemetry.send('didCreateFirstComponent');
|
|
60
|
+
} else {
|
|
61
|
+
strapi.telemetry.send('didCreateComponent');
|
|
62
|
+
} */
|
|
63
|
+
|
|
64
|
+
this.components.set(uid, handler);
|
|
65
|
+
|
|
66
|
+
return handler;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* create a component in the tmpComponent map
|
|
71
|
+
*/
|
|
72
|
+
editComponent(infos) {
|
|
73
|
+
const { uid } = infos;
|
|
74
|
+
|
|
75
|
+
if (!this.components.has(uid)) {
|
|
76
|
+
throw new Error('component.notFound');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const component = this.components.get(uid);
|
|
80
|
+
|
|
81
|
+
const [, nameUID] = uid.split('.');
|
|
82
|
+
|
|
83
|
+
const newCategory = nameToSlug(infos.category);
|
|
84
|
+
const newUID = `${newCategory}.${nameUID}`;
|
|
85
|
+
|
|
86
|
+
if (newUID !== uid && this.components.has(newUID)) {
|
|
87
|
+
throw new Error('component.edit.alreadyExists');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const newDir = path.join(strapi.dir, 'components', newCategory);
|
|
91
|
+
|
|
92
|
+
const oldAttributes = component.schema.attributes;
|
|
93
|
+
|
|
94
|
+
const newAttributes = _.omitBy(infos.attributes, (attr, key) => {
|
|
95
|
+
return _.has(oldAttributes, key) && !isConfigurable(oldAttributes[key]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
component
|
|
99
|
+
.setUID(newUID)
|
|
100
|
+
.setDir(newDir)
|
|
101
|
+
.set('collectionName', infos.collectionName)
|
|
102
|
+
.set(['info', 'name'], infos.name)
|
|
103
|
+
.set(['info', 'icon'], infos.icon)
|
|
104
|
+
.set(['info', 'description'], infos.description)
|
|
105
|
+
.set('pluginOptions', infos.pluginOptions)
|
|
106
|
+
.setAttributes(this.convertAttributes(newAttributes));
|
|
107
|
+
|
|
108
|
+
if (newUID !== uid) {
|
|
109
|
+
this.components.forEach((compo) => {
|
|
110
|
+
compo.updateComponent(uid, newUID);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.contentTypes.forEach((ct) => {
|
|
114
|
+
ct.updateComponent(uid, newUID);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return component;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
deleteComponent(uid) {
|
|
122
|
+
if (!this.components.has(uid)) {
|
|
123
|
+
throw new Error('component.notFound');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.components.forEach((compo) => {
|
|
127
|
+
compo.removeComponent(uid);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.contentTypes.forEach((ct) => {
|
|
131
|
+
ct.removeComponent(uid);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return this.components.get(uid).delete();
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
};
|