@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.
Files changed (223) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +19 -0
  3. package/admin/src/InjectedComponents/ContentManager/EditSettingViewButton.js +76 -0
  4. package/admin/src/InjectedComponents/ContentManager/EditViewLink.js +44 -0
  5. package/admin/src/assets/images/logo.svg +1 -0
  6. package/admin/src/components/AllowedTypesSelect/MenuList.js +115 -0
  7. package/admin/src/components/AllowedTypesSelect/Text.js +26 -0
  8. package/admin/src/components/AllowedTypesSelect/index.js +55 -0
  9. package/admin/src/components/AttributeOption/Button.js +50 -0
  10. package/admin/src/components/AttributeOption/Card.js +26 -0
  11. package/admin/src/components/AttributeOption/index.js +127 -0
  12. package/admin/src/components/BooleanBox/CT.js +21 -0
  13. package/admin/src/components/BooleanBox/Enumeration.js +10 -0
  14. package/admin/src/components/BooleanBox/EnumerationWrapper.js +14 -0
  15. package/admin/src/components/BooleanBox/Label.js +9 -0
  16. package/admin/src/components/BooleanBox/ST.js +21 -0
  17. package/admin/src/components/BooleanBox/Wrapper.js +75 -0
  18. package/admin/src/components/BooleanBox/icons/CTSelected.js +136 -0
  19. package/admin/src/components/BooleanBox/icons/CTUnselected.js +136 -0
  20. package/admin/src/components/BooleanBox/icons/STSelected.js +80 -0
  21. package/admin/src/components/BooleanBox/icons/STUnselected.js +76 -0
  22. package/admin/src/components/BooleanBox/index.js +103 -0
  23. package/admin/src/components/CheckboxWithDescription/Wrapper.js +12 -0
  24. package/admin/src/components/CheckboxWithDescription/index.js +40 -0
  25. package/admin/src/components/ComponentCard/Close.js +12 -0
  26. package/admin/src/components/ComponentCard/Wrapper.js +84 -0
  27. package/admin/src/components/ComponentCard/index.js +62 -0
  28. package/admin/src/components/ComponentIconPicker/Cell.js +40 -0
  29. package/admin/src/components/ComponentIconPicker/CellRenderer.js +36 -0
  30. package/admin/src/components/ComponentIconPicker/Search.js +12 -0
  31. package/admin/src/components/ComponentIconPicker/SearchWrapper.js +34 -0
  32. package/admin/src/components/ComponentIconPicker/Wrapper.js +60 -0
  33. package/admin/src/components/ComponentIconPicker/index.js +143 -0
  34. package/admin/src/components/ComponentList/index.js +78 -0
  35. package/admin/src/components/ComponentSelect/Category.js +20 -0
  36. package/admin/src/components/ComponentSelect/CategoryName.js +11 -0
  37. package/admin/src/components/ComponentSelect/MenuList.js +110 -0
  38. package/admin/src/components/ComponentSelect/MultipleMenuList.js +271 -0
  39. package/admin/src/components/ComponentSelect/Value.js +83 -0
  40. package/admin/src/components/ComponentSelect/index.js +83 -0
  41. package/admin/src/components/ComponentSelect/utils/HasSomeSubArray.js +5 -0
  42. package/admin/src/components/ComponentSelect/utils/hasSubArray.js +5 -0
  43. package/admin/src/components/CreatableSelect/index.js +58 -0
  44. package/admin/src/components/CustomCheckbox/StyledCustomCheckbox.js +25 -0
  45. package/admin/src/components/CustomCheckbox/index.js +71 -0
  46. package/admin/src/components/CustomLink/P.js +17 -0
  47. package/admin/src/components/CustomLink/StyledCustomLink.js +20 -0
  48. package/admin/src/components/CustomLink/index.js +31 -0
  49. package/admin/src/components/DynamicZoneList/ComponentButton.js +39 -0
  50. package/admin/src/components/DynamicZoneList/index.js +124 -0
  51. package/admin/src/components/HeaderModalNavContainer/index.js +25 -0
  52. package/admin/src/components/HeaderNavLink/Wrapper.js +21 -0
  53. package/admin/src/components/HeaderNavLink/index.js +50 -0
  54. package/admin/src/components/List/List.js +131 -0
  55. package/admin/src/components/List/index.js +295 -0
  56. package/admin/src/components/ListButton/index.js +9 -0
  57. package/admin/src/components/ListHeader/Title.js +19 -0
  58. package/admin/src/components/ListHeader/Wrapper.js +24 -0
  59. package/admin/src/components/ListHeader/index.js +37 -0
  60. package/admin/src/components/ListRow/Wrapper.js +76 -0
  61. package/admin/src/components/ListRow/index.js +334 -0
  62. package/admin/src/components/ModalHeader/ComponentIcon.js +21 -0
  63. package/admin/src/components/ModalHeader/ComponentInfos.js +22 -0
  64. package/admin/src/components/ModalHeader/ComponentInfosWrapper.js +9 -0
  65. package/admin/src/components/ModalHeader/DropdownInfos.js +50 -0
  66. package/admin/src/components/ModalHeader/Icon.js +26 -0
  67. package/admin/src/components/ModalHeader/IconWrapper.js +11 -0
  68. package/admin/src/components/ModalHeader/Item.js +19 -0
  69. package/admin/src/components/ModalHeader/Menu.js +8 -0
  70. package/admin/src/components/ModalHeader/Toggle.js +14 -0
  71. package/admin/src/components/ModalHeader/Wrapper.js +12 -0
  72. package/admin/src/components/ModalHeader/index.js +105 -0
  73. package/admin/src/components/RelationForm/Wrapper.js +9 -0
  74. package/admin/src/components/RelationForm/index.js +66 -0
  75. package/admin/src/components/RelationFormBox/Wrapper.js +34 -0
  76. package/admin/src/components/RelationFormBox/index.js +85 -0
  77. package/admin/src/components/RelationFormNaturePicker/Wrapper.js +53 -0
  78. package/admin/src/components/RelationFormNaturePicker/index.js +112 -0
  79. package/admin/src/components/RelationTargetPicker/Item.js +62 -0
  80. package/admin/src/components/RelationTargetPicker/Wrapper.js +89 -0
  81. package/admin/src/components/RelationTargetPicker/index.js +88 -0
  82. package/admin/src/components/SelectCheckbox/index.js +8 -0
  83. package/admin/src/components/SelectMenuSubUl/ToggleUl.js +23 -0
  84. package/admin/src/components/SelectMenuSubUl/index.js +23 -0
  85. package/admin/src/components/SelectMenuUl/index.js +66 -0
  86. package/admin/src/components/Td/index.js +37 -0
  87. package/admin/src/components/UpperFirst/index.js +14 -0
  88. package/admin/src/components/WrapperSelect/index.js +89 -0
  89. package/admin/src/containers/App/Wrapper.js +14 -0
  90. package/admin/src/containers/App/index.js +39 -0
  91. package/admin/src/containers/App/utils/icons.json +962 -0
  92. package/admin/src/containers/DataManagerProvider/constants.js +15 -0
  93. package/admin/src/containers/DataManagerProvider/index.js +610 -0
  94. package/admin/src/containers/DataManagerProvider/reducer.js +461 -0
  95. package/admin/src/containers/DataManagerProvider/selectors.js +25 -0
  96. package/admin/src/containers/DataManagerProvider/utils/cleanData.js +167 -0
  97. package/admin/src/containers/DataManagerProvider/utils/createDataObject.js +8 -0
  98. package/admin/src/containers/DataManagerProvider/utils/createModifiedDataSchema.js +91 -0
  99. package/admin/src/containers/DataManagerProvider/utils/retrieveComponentsFromSchema.js +55 -0
  100. package/admin/src/containers/DataManagerProvider/utils/retrieveComponentsThatHaveComponents.js +35 -0
  101. package/admin/src/containers/DataManagerProvider/utils/retrieveNestedComponents.js +34 -0
  102. package/admin/src/containers/DataManagerProvider/utils/retrieveSpecificInfoFromComponents.js +12 -0
  103. package/admin/src/containers/FormModal/CustomButton.js +12 -0
  104. package/admin/src/containers/FormModal/attributes/advancedForm.js +269 -0
  105. package/admin/src/containers/FormModal/attributes/attributeOptions.js +99 -0
  106. package/admin/src/containers/FormModal/attributes/baseForm.js +322 -0
  107. package/admin/src/containers/FormModal/attributes/commonBaseForm.js +7 -0
  108. package/admin/src/containers/FormModal/attributes/form.js +9 -0
  109. package/admin/src/containers/FormModal/attributes/index.js +4 -0
  110. package/admin/src/containers/FormModal/attributes/nameField.js +19 -0
  111. package/admin/src/containers/FormModal/attributes/types.js +309 -0
  112. package/admin/src/containers/FormModal/attributes/uiHelpers.js +11 -0
  113. package/admin/src/containers/FormModal/attributes/validation/common.js +151 -0
  114. package/admin/src/containers/FormModal/category/createCategorySchema.js +28 -0
  115. package/admin/src/containers/FormModal/category/form.js +27 -0
  116. package/admin/src/containers/FormModal/category/index.js +3 -0
  117. package/admin/src/containers/FormModal/category/regex.js +3 -0
  118. package/admin/src/containers/FormModal/component/componentField.js +25 -0
  119. package/admin/src/containers/FormModal/component/createComponentSchema.js +49 -0
  120. package/admin/src/containers/FormModal/component/form.js +67 -0
  121. package/admin/src/containers/FormModal/component/index.js +4 -0
  122. package/admin/src/containers/FormModal/constants.js +11 -0
  123. package/admin/src/containers/FormModal/contentType/createContentTypeSchema.js +44 -0
  124. package/admin/src/containers/FormModal/contentType/form.js +116 -0
  125. package/admin/src/containers/FormModal/contentType/index.js +2 -0
  126. package/admin/src/containers/FormModal/dynamicZone/form.js +45 -0
  127. package/admin/src/containers/FormModal/dynamicZone/index.js +2 -0
  128. package/admin/src/containers/FormModal/forms/index.js +164 -0
  129. package/admin/src/containers/FormModal/index.js +1552 -0
  130. package/admin/src/containers/FormModal/reducer.js +293 -0
  131. package/admin/src/containers/FormModal/selectors.js +24 -0
  132. package/admin/src/containers/FormModal/utils/canEditContentType.js +19 -0
  133. package/admin/src/containers/FormModal/utils/createHeadersArray.js +37 -0
  134. package/admin/src/containers/FormModal/utils/createHeadersObjectFromArray.js +27 -0
  135. package/admin/src/containers/FormModal/utils/createUid.js +17 -0
  136. package/admin/src/containers/FormModal/utils/getAttributesToDisplay.js +35 -0
  137. package/admin/src/containers/FormModal/utils/getModalTitleSubHeader.js +25 -0
  138. package/admin/src/containers/FormModal/utils/getNextSearch.js +17 -0
  139. package/admin/src/containers/FormModal/utils/index.js +6 -0
  140. package/admin/src/containers/FormModal/utils/relations.js +9 -0
  141. package/admin/src/containers/FormModal/utils/staticData.js +43 -0
  142. package/admin/src/containers/Initializer/index.js +26 -0
  143. package/admin/src/containers/LeftMenu/Wrapper.js +14 -0
  144. package/admin/src/containers/LeftMenu/index.js +220 -0
  145. package/admin/src/containers/ListView/Wrapper.js +16 -0
  146. package/admin/src/containers/ListView/index.js +322 -0
  147. package/admin/src/containers/NotFoundPage/index.js +20 -0
  148. package/admin/src/containers/RecursivePath/index.js +22 -0
  149. package/admin/src/contexts/DataManagerContext.js +5 -0
  150. package/admin/src/contexts/ListViewContext.js +5 -0
  151. package/admin/src/hooks/useDataManager.js +6 -0
  152. package/admin/src/hooks/useListView.js +6 -0
  153. package/admin/src/icons/Curve.js +26 -0
  154. package/admin/src/icons/ManyToMany.js +98 -0
  155. package/admin/src/icons/ManyToOne.js +64 -0
  156. package/admin/src/icons/ManyWay.js +36 -0
  157. package/admin/src/icons/OneToMany.js +65 -0
  158. package/admin/src/icons/OneToOne.js +47 -0
  159. package/admin/src/icons/OneWay.js +38 -0
  160. package/admin/src/index.js +83 -0
  161. package/admin/src/lifecycles.js +13 -0
  162. package/admin/src/permissions.js +9 -0
  163. package/admin/src/pluginId.js +5 -0
  164. package/admin/src/reducers.js +10 -0
  165. package/admin/src/translations/ar.json +48 -0
  166. package/admin/src/translations/cs.json +151 -0
  167. package/admin/src/translations/de.json +187 -0
  168. package/admin/src/translations/dk.json +181 -0
  169. package/admin/src/translations/en.json +188 -0
  170. package/admin/src/translations/es.json +188 -0
  171. package/admin/src/translations/fr.json +87 -0
  172. package/admin/src/translations/id.json +187 -0
  173. package/admin/src/translations/index.js +51 -0
  174. package/admin/src/translations/it.json +187 -0
  175. package/admin/src/translations/ja.json +50 -0
  176. package/admin/src/translations/ko.json +57 -0
  177. package/admin/src/translations/ms.json +179 -0
  178. package/admin/src/translations/nl.json +171 -0
  179. package/admin/src/translations/pl.json +152 -0
  180. package/admin/src/translations/pt-BR.json +51 -0
  181. package/admin/src/translations/pt.json +51 -0
  182. package/admin/src/translations/ru.json +188 -0
  183. package/admin/src/translations/sk.json +186 -0
  184. package/admin/src/translations/th.json +181 -0
  185. package/admin/src/translations/tr.json +51 -0
  186. package/admin/src/translations/uk.json +180 -0
  187. package/admin/src/translations/zh-Hans.json +188 -0
  188. package/admin/src/translations/zh.json +178 -0
  189. package/admin/src/utils/convertAttrObjToArray.js +7 -0
  190. package/admin/src/utils/formAPI.js +112 -0
  191. package/admin/src/utils/getAttributeDisplayedType.js +31 -0
  192. package/admin/src/utils/getComponents.js +31 -0
  193. package/admin/src/utils/getTrad.js +5 -0
  194. package/admin/src/utils/makeSearch.js +15 -0
  195. package/admin/src/utils/makeUnique.js +3 -0
  196. package/config/functions/bootstrap.js +14 -0
  197. package/config/routes.json +144 -0
  198. package/controllers/Builder.js +7 -0
  199. package/controllers/ComponentCategories.js +43 -0
  200. package/controllers/Components.js +152 -0
  201. package/controllers/Connections.js +9 -0
  202. package/controllers/ContentTypes.js +143 -0
  203. package/controllers/validation/common.js +118 -0
  204. package/controllers/validation/component-category.js +21 -0
  205. package/controllers/validation/component.js +89 -0
  206. package/controllers/validation/content-type.js +161 -0
  207. package/controllers/validation/data-transform.js +32 -0
  208. package/controllers/validation/model-schema.js +91 -0
  209. package/controllers/validation/relations.js +56 -0
  210. package/controllers/validation/types.js +277 -0
  211. package/package.json +63 -0
  212. package/services/Builder.js +7 -0
  213. package/services/ComponentCategories.js +88 -0
  214. package/services/Components.js +103 -0
  215. package/services/ContentTypes.js +263 -0
  216. package/services/api-handler.js +158 -0
  217. package/services/constants.js +59 -0
  218. package/services/schema-builder/component-builder.js +137 -0
  219. package/services/schema-builder/content-type-builder.js +256 -0
  220. package/services/schema-builder/index.js +196 -0
  221. package/services/schema-builder/schema-handler.js +297 -0
  222. package/utils/attributes.js +154 -0
  223. 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
+ };