@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,143 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ // const { hasDraftAndPublish } = require('@akemona-org/strapi-utils').contentTypes;
6
+
7
+ const {
8
+ validateContentTypeInput,
9
+ validateUpdateContentTypeInput,
10
+ validateKind,
11
+ } = require('./validation/content-type');
12
+
13
+ module.exports = {
14
+ async getContentTypes(ctx) {
15
+ const { kind } = ctx.query;
16
+
17
+ try {
18
+ await validateKind(kind);
19
+ } catch (error) {
20
+ return ctx.send({ error }, 400);
21
+ }
22
+
23
+ const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
24
+
25
+ const contentTypes = Object.keys(strapi.contentTypes)
26
+ .filter((uid) => !kind || _.get(strapi.contentTypes[uid], 'kind', 'collectionType') === kind)
27
+ .map((uid) => contentTypeService.formatContentType(strapi.contentTypes[uid]));
28
+
29
+ ctx.send({
30
+ data: contentTypes,
31
+ });
32
+ },
33
+
34
+ getContentType(ctx) {
35
+ const { uid } = ctx.params;
36
+
37
+ const contentType = strapi.contentTypes[uid];
38
+
39
+ if (!contentType) {
40
+ return ctx.send({ error: 'contentType.notFound' }, 404);
41
+ }
42
+
43
+ const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
44
+
45
+ ctx.send({ data: contentTypeService.formatContentType(contentType) });
46
+ },
47
+
48
+ async createContentType(ctx) {
49
+ const { body } = ctx.request;
50
+
51
+ try {
52
+ await validateContentTypeInput(body);
53
+ } catch (error) {
54
+ return ctx.send({ error }, 400);
55
+ }
56
+
57
+ try {
58
+ strapi.reload.isWatching = false;
59
+
60
+ const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
61
+
62
+ const contentType = await contentTypeService.createContentType({
63
+ contentType: body.contentType,
64
+ components: body.components,
65
+ });
66
+
67
+ // const metricsProperties = {
68
+ // kind: contentType.kind,
69
+ // hasDraftAndPublish: hasDraftAndPublish(contentType.schema),
70
+ // };
71
+
72
+ /* if (_.isEmpty(strapi.api)) {
73
+ await strapi.telemetry.send('didCreateFirstContentType', metricsProperties);
74
+ } else {
75
+ await strapi.telemetry.send('didCreateContentType', metricsProperties);
76
+ } */
77
+
78
+ setImmediate(() => strapi.reload());
79
+
80
+ ctx.send({ data: { uid: contentType.uid } }, 201);
81
+ } catch (error) {
82
+ strapi.log.error(error);
83
+ // await strapi.telemetry.send('didNotCreateContentType', { error: error.message });
84
+ ctx.send({ error: error.message }, 400);
85
+ }
86
+ },
87
+
88
+ async updateContentType(ctx) {
89
+ const { uid } = ctx.params;
90
+ const { body } = ctx.request;
91
+
92
+ if (!_.has(strapi.contentTypes, uid)) {
93
+ return ctx.send({ error: 'contentType.notFound' }, 404);
94
+ }
95
+
96
+ try {
97
+ await validateUpdateContentTypeInput(body);
98
+ } catch (error) {
99
+ return ctx.send({ error }, 400);
100
+ }
101
+
102
+ try {
103
+ strapi.reload.isWatching = false;
104
+
105
+ const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
106
+
107
+ const component = await contentTypeService.editContentType(uid, {
108
+ contentType: body.contentType,
109
+ components: body.components,
110
+ });
111
+
112
+ setImmediate(() => strapi.reload());
113
+
114
+ ctx.send({ data: { uid: component.uid } }, 201);
115
+ } catch (error) {
116
+ strapi.log.error(error);
117
+ ctx.send({ error: error.message }, 400);
118
+ }
119
+ },
120
+
121
+ async deleteContentType(ctx) {
122
+ const { uid } = ctx.params;
123
+
124
+ if (!_.has(strapi.contentTypes, uid)) {
125
+ return ctx.send({ error: 'contentType.notFound' }, 404);
126
+ }
127
+
128
+ try {
129
+ strapi.reload.isWatching = false;
130
+
131
+ const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
132
+
133
+ const component = await contentTypeService.deleteContentType(uid);
134
+
135
+ setImmediate(() => strapi.reload());
136
+
137
+ ctx.send({ data: { uid: component.uid } });
138
+ } catch (error) {
139
+ strapi.log.error(error);
140
+ ctx.send({ error: error.message }, 400);
141
+ }
142
+ },
143
+ };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ const yup = require('yup');
4
+ const _ = require('lodash');
5
+
6
+ const validators = {
7
+ required: yup.boolean(),
8
+ unique: yup.boolean(),
9
+ minLength: yup
10
+ .number()
11
+ .integer()
12
+ .positive(),
13
+ maxLength: yup
14
+ .number()
15
+ .integer()
16
+ .positive(),
17
+ };
18
+
19
+ const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$');
20
+ const COLLECTION_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
21
+ const CATEGORY_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
22
+ const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$');
23
+ const ICON_REGEX = new RegExp('^[A-Za-z0-9][-A-Za-z0-9]*$');
24
+ const UID_REGEX = new RegExp('^[A-Za-z0-9-_.~]*$');
25
+
26
+ const isValidName = {
27
+ name: 'isValidName',
28
+ message: '${path} must match the following regex: ' + NAME_REGEX,
29
+ test: val => val === '' || NAME_REGEX.test(val),
30
+ };
31
+
32
+ const isValidUID = {
33
+ name: 'isValidUID',
34
+ message: '${path} must match the following regex: ' + UID_REGEX,
35
+ test: val => val === '' || UID_REGEX.test(val),
36
+ };
37
+
38
+ const isValidCategoryName = {
39
+ name: 'isValidCategoryName',
40
+ message: '${path} must match the following regex: ' + CATEGORY_NAME_REGEX,
41
+ test: val => val === '' || CATEGORY_NAME_REGEX.test(val),
42
+ };
43
+
44
+ const isValidCollectionName = {
45
+ name: 'isValidCollectionName',
46
+ message: '${path} must match the following regex: ' + COLLECTION_NAME_REGEX,
47
+ test: val => val === '' || COLLECTION_NAME_REGEX.test(val),
48
+ };
49
+
50
+ const isValidIcon = {
51
+ name: 'isValidIcon',
52
+ message: '${path} must match the following regex: ' + ICON_REGEX,
53
+ test: val => val === '' || ICON_REGEX.test(val),
54
+ };
55
+
56
+ const isValidKey = key => ({
57
+ name: 'isValidKey',
58
+ message: `Attribute name '${key}' must match the following regex: ${NAME_REGEX}`,
59
+ test: () => NAME_REGEX.test(key),
60
+ });
61
+
62
+ const isValidEnum = {
63
+ name: 'isValidEnum',
64
+ message: '${path} must match the following regex: ' + ENUM_REGEX,
65
+ test: val => val === '' || ENUM_REGEX.test(val),
66
+ };
67
+
68
+ const areEnumValuesUnique = {
69
+ name: 'areEnumValuesUnique',
70
+ message: '${path} cannot contain duplicate values',
71
+ test: values => {
72
+ const filtered = [...new Set(values)];
73
+
74
+ return filtered.length === values.length;
75
+ },
76
+ };
77
+
78
+ const isValidRegExpPattern = {
79
+ name: 'isValidRegExpPattern',
80
+ message: '${path} must be a valid RexExp pattern string',
81
+ test: val => val === '' || new RegExp(val),
82
+ };
83
+
84
+ const isValidDefaultJSON = {
85
+ name: 'isValidDefaultJSON',
86
+ message: '${path} is not a valid JSON',
87
+ test: val => {
88
+ if (val === undefined) {
89
+ return true;
90
+ }
91
+
92
+ if (_.isNumber(val) || _.isNull(val) || _.isObject(val) || _.isArray(val)) {
93
+ return true;
94
+ }
95
+
96
+ try {
97
+ JSON.parse(val);
98
+
99
+ return true;
100
+ } catch (err) {
101
+ return false;
102
+ }
103
+ },
104
+ };
105
+
106
+ module.exports = {
107
+ validators,
108
+ areEnumValuesUnique,
109
+ isValidCollectionName,
110
+ isValidCategoryName,
111
+ isValidDefaultJSON,
112
+ isValidName,
113
+ isValidIcon,
114
+ isValidKey,
115
+ isValidEnum,
116
+ isValidUID,
117
+ isValidRegExpPattern,
118
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const yup = require('yup');
4
+ const { formatYupErrors } = require('@akemona-org/strapi-utils');
5
+
6
+ const { isValidCategoryName } = require('./common');
7
+
8
+ module.exports = (data) => {
9
+ return componentCategorySchema
10
+ .validate(data, {
11
+ strict: true,
12
+ abortEarly: false,
13
+ })
14
+ .catch((error) => Promise.reject(formatYupErrors(error)));
15
+ };
16
+
17
+ const componentCategorySchema = yup
18
+ .object({
19
+ name: yup.string().min(3).test(isValidCategoryName).required('name.required'),
20
+ })
21
+ .noUnknown();
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const yup = require('yup');
5
+ const { formatYupErrors } = require('@akemona-org/strapi-utils');
6
+
7
+ const { modelTypes, DEFAULT_TYPES } = require('../../services/constants');
8
+ const { isValidCategoryName, isValidIcon } = require('./common');
9
+ const createSchema = require('./model-schema');
10
+ const { removeEmptyDefaults } = require('./data-transform');
11
+
12
+ const VALID_RELATIONS = ['oneWay', 'manyWay'];
13
+ const VALID_TYPES = [...DEFAULT_TYPES, 'component'];
14
+
15
+ const componentSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
16
+ modelType: modelTypes.COMPONENT,
17
+ })
18
+ .shape({
19
+ icon: yup.string().nullable().test(isValidIcon).required('icon.required'),
20
+ category: yup.string().nullable().test(isValidCategoryName).required('category.required'),
21
+ })
22
+ .required()
23
+ .noUnknown();
24
+
25
+ const nestedComponentSchema = yup.array().of(
26
+ componentSchema
27
+ .shape({
28
+ uid: yup.string(),
29
+ tmpUID: yup.string(),
30
+ })
31
+ .test({
32
+ name: 'mustHaveUIDOrTmpUID',
33
+ message: 'Component must have a uid or a tmpUID',
34
+ test: (attr) => {
35
+ if (_.has(attr, 'uid') && _.has(attr, 'tmpUID')) return false;
36
+ if (!_.has(attr, 'uid') && !_.has(attr, 'tmpUID')) return false;
37
+ return true;
38
+ },
39
+ })
40
+ .required()
41
+ .noUnknown()
42
+ );
43
+
44
+ const validateComponentInput = (data) => {
45
+ return yup
46
+ .object({
47
+ component: componentSchema,
48
+ components: nestedComponentSchema,
49
+ })
50
+ .noUnknown()
51
+ .validate(data, {
52
+ strict: true,
53
+ abortEarly: false,
54
+ })
55
+ .catch((error) => Promise.reject(formatYupErrors(error)));
56
+ };
57
+
58
+ const validateUpdateComponentInput = (data) => {
59
+ if (_.has(data, 'component')) {
60
+ removeEmptyDefaults(data.component);
61
+ }
62
+
63
+ if (_.has(data, 'components') && Array.isArray(data.components)) {
64
+ data.components.forEach((data) => {
65
+ if (_.has(data, 'uid')) {
66
+ removeEmptyDefaults(data);
67
+ }
68
+ });
69
+ }
70
+
71
+ return yup
72
+ .object({
73
+ component: componentSchema,
74
+ components: nestedComponentSchema,
75
+ })
76
+ .noUnknown()
77
+ .validate(data, {
78
+ strict: true,
79
+ abortEarly: false,
80
+ })
81
+ .catch((error) => Promise.reject(formatYupErrors(error)));
82
+ };
83
+
84
+ module.exports = {
85
+ validateComponentInput,
86
+ validateUpdateComponentInput,
87
+
88
+ nestedComponentSchema,
89
+ };
@@ -0,0 +1,161 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const yup = require('yup');
5
+ const { formatYupErrors, nameToSlug } = require('@akemona-org/strapi-utils');
6
+ const pluralize = require('pluralize');
7
+
8
+ const { modelTypes, DEFAULT_TYPES, typeKinds } = require('../../services/constants');
9
+ const createSchema = require('./model-schema');
10
+ const { removeEmptyDefaults, removeDeletedUIDTargetFields } = require('./data-transform');
11
+ const { nestedComponentSchema } = require('./component');
12
+
13
+ /**
14
+ * Allowed relation per type kind
15
+ */
16
+ const VALID_RELATIONS = {
17
+ [typeKinds.SINGLE_TYPE]: ['oneWay', 'manyWay'],
18
+ [typeKinds.COLLECTION_TYPE]: [
19
+ 'oneWay',
20
+ 'manyWay',
21
+ 'oneToOne',
22
+ 'oneToMany',
23
+ 'manyToOne',
24
+ 'manyToMany',
25
+ ],
26
+ };
27
+
28
+ /**
29
+ * Allowed types
30
+ */
31
+ const VALID_TYPES = [...DEFAULT_TYPES, 'uid', 'component', 'dynamiczone'];
32
+
33
+ /**
34
+ * Returns a yup schema to validate a content type payload
35
+ * @param {Object} data payload
36
+ */
37
+ const createContentTypeSchema = (data, { isEdition = false } = {}) => {
38
+ const kind = _.get(data, 'contentType.kind', typeKinds.COLLECTION_TYPE);
39
+
40
+ const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {
41
+ modelType: modelTypes.CONTENT_TYPE,
42
+ }).shape({
43
+ name: yup
44
+ .string()
45
+ .test(hasPluralName)
46
+ .test(alreadyUsedContentTypeName(isEdition))
47
+ .test(forbiddenContentTypeNameValidator())
48
+ .min(1)
49
+ .required(),
50
+ });
51
+
52
+ return yup
53
+ .object({
54
+ contentType: contentTypeSchema.required().noUnknown(),
55
+ components: nestedComponentSchema,
56
+ })
57
+ .noUnknown();
58
+ };
59
+
60
+ /**
61
+ * Validator for content type creation
62
+ */
63
+ const validateContentTypeInput = (data) => {
64
+ return createContentTypeSchema(data)
65
+ .validate(data, {
66
+ strict: true,
67
+ abortEarly: false,
68
+ })
69
+ .catch((error) => Promise.reject(formatYupErrors(error)));
70
+ };
71
+
72
+ /**
73
+ * Validator for content type edition
74
+ */
75
+ const validateUpdateContentTypeInput = (data) => {
76
+ if (_.has(data, 'contentType')) {
77
+ removeEmptyDefaults(data.contentType);
78
+ }
79
+
80
+ if (_.has(data, 'components') && Array.isArray(data.components)) {
81
+ data.components.forEach((data) => {
82
+ if (_.has(data, 'uid')) {
83
+ removeEmptyDefaults(data);
84
+ }
85
+ });
86
+ }
87
+
88
+ removeDeletedUIDTargetFields(data.contentType);
89
+
90
+ return createContentTypeSchema(data, { isEdition: true })
91
+ .validate(data, {
92
+ strict: true,
93
+ abortEarly: false,
94
+ })
95
+ .catch((error) => Promise.reject(formatYupErrors(error)));
96
+ };
97
+
98
+ const forbiddenContentTypeNameValidator = () => {
99
+ const reservedNames =
100
+ strapi.plugins['content-type-builder'].services.builder.getReservedNames().models;
101
+
102
+ return {
103
+ name: 'forbiddenContentTypeName',
104
+ message: `Content Type name cannot be one of ${reservedNames.join(', ')}`,
105
+ test: (value) => {
106
+ if (reservedNames.includes(nameToSlug(value))) {
107
+ return false;
108
+ }
109
+
110
+ return true;
111
+ },
112
+ };
113
+ };
114
+
115
+ const hasPluralName = {
116
+ name: 'hasPluralName',
117
+ message:
118
+ 'Content Type name `${value}` cannot be pluralized. \nSuggestion: add Item after the name (e.g News -> NewsItem).',
119
+ test: (value) => {
120
+ if (pluralize.singular(value) === pluralize(value)) {
121
+ return false;
122
+ }
123
+
124
+ return true;
125
+ },
126
+ };
127
+
128
+ const alreadyUsedContentTypeName = (isEdition) => {
129
+ const usedNames = Object.values(strapi.contentTypes).map((ct) => ct.modelName);
130
+
131
+ return {
132
+ name: 'nameAlreadyUsed',
133
+ message: 'Content Type name `${value}` is already being used.',
134
+ test: (value) => {
135
+ // don't check on edition
136
+ if (isEdition) return true;
137
+
138
+ if (usedNames.includes(nameToSlug(value))) {
139
+ return false;
140
+ }
141
+ return true;
142
+ },
143
+ };
144
+ };
145
+
146
+ /**
147
+ * Validates type kind
148
+ */
149
+ const validateKind = (kind) => {
150
+ return yup
151
+ .string()
152
+ .oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE])
153
+ .validate(kind)
154
+ .catch((error) => Promise.reject(formatYupErrors(error)));
155
+ };
156
+
157
+ module.exports = {
158
+ validateContentTypeInput,
159
+ validateUpdateContentTypeInput,
160
+ validateKind,
161
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ const removeEmptyDefaults = data => {
6
+ if (_.has(data, 'attributes')) {
7
+ Object.keys(data.attributes).forEach(attribute => {
8
+ if (data.attributes[attribute].default === '') {
9
+ data.attributes[attribute].default = undefined;
10
+ }
11
+ });
12
+ }
13
+ };
14
+
15
+ const removeDeletedUIDTargetFields = data => {
16
+ if (_.has(data, 'attributes')) {
17
+ Object.values(data.attributes).forEach(attribute => {
18
+ if (
19
+ attribute.type === 'uid' &&
20
+ !_.isUndefined(attribute.targetField) &&
21
+ !_.has(data.attributes, attribute.targetField)
22
+ ) {
23
+ attribute.targetField = undefined;
24
+ }
25
+ });
26
+ }
27
+ };
28
+
29
+ module.exports = {
30
+ removeDeletedUIDTargetFields,
31
+ removeEmptyDefaults,
32
+ };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const yup = require('yup');
5
+
6
+ const { modelTypes, FORBIDDEN_ATTRIBUTE_NAMES, typeKinds } = require('../../services/constants');
7
+ const { isValidCollectionName, isValidKey } = require('./common');
8
+ const getTypeValidator = require('./types');
9
+ const getRelationValidator = require('./relations');
10
+
11
+ const createSchema = (types, relations, { modelType } = {}) => {
12
+ const shape = {
13
+ name: yup
14
+ .string()
15
+ .min(1)
16
+ .required('name.required'),
17
+ description: yup.string(),
18
+ draftAndPublish: yup.boolean(),
19
+ pluginOptions: yup.object(),
20
+ connection: yup.string(),
21
+ collectionName: yup
22
+ .string()
23
+ .nullable()
24
+ .test(isValidCollectionName),
25
+ attributes: createAttributesValidator({ types, relations, modelType }),
26
+ };
27
+
28
+ if (modelType === modelTypes.CONTENT_TYPE) {
29
+ shape.kind = yup
30
+ .string()
31
+ .oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE])
32
+ .nullable();
33
+ }
34
+
35
+ return yup.object(shape).noUnknown();
36
+ };
37
+
38
+ const createAttributesValidator = ({ types, modelType, relations }) => {
39
+ return yup.lazy(attributes => {
40
+ return yup
41
+ .object()
42
+ .shape(
43
+ _.mapValues(attributes, (attribute, key) => {
44
+ if (isForbiddenKey(key)) {
45
+ return forbiddenValidator();
46
+ }
47
+
48
+ if (_.has(attribute, 'type')) {
49
+ return getTypeValidator(attribute, { types, modelType, attributes }).test(
50
+ isValidKey(key)
51
+ );
52
+ }
53
+
54
+ if (_.has(attribute, 'target')) {
55
+ return yup.object(getRelationValidator(attribute, relations)).test(isValidKey(key));
56
+ }
57
+
58
+ return typeOrRelationValidator;
59
+ })
60
+ )
61
+ .required('attributes.required');
62
+ });
63
+ };
64
+
65
+ const isForbiddenKey = key => {
66
+ return [
67
+ ...FORBIDDEN_ATTRIBUTE_NAMES,
68
+ ...strapi.plugins['content-type-builder'].services.builder.getReservedNames().attributes,
69
+ ].includes(key);
70
+ };
71
+
72
+ const forbiddenValidator = () => {
73
+ const reservedNames = [
74
+ ...FORBIDDEN_ATTRIBUTE_NAMES,
75
+ ...strapi.plugins['content-type-builder'].services.builder.getReservedNames().attributes,
76
+ ];
77
+
78
+ return yup.mixed().test({
79
+ name: 'forbiddenKeys',
80
+ message: `Attribute keys cannot be one of ${reservedNames.join(', ')}`,
81
+ test: () => false,
82
+ });
83
+ };
84
+
85
+ const typeOrRelationValidator = yup.object().test({
86
+ name: 'mustHaveTypeOrTarget',
87
+ message: 'Attribute must have either a type or a target',
88
+ test: () => false,
89
+ });
90
+
91
+ module.exports = createSchema;