@strapi/content-type-builder 0.0.0 → 5.0.0-beta.7
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/README.md +1 -1
- package/dist/_chunks/ListView-BuXQ605A.js +959 -0
- package/dist/_chunks/ListView-BuXQ605A.js.map +1 -0
- package/dist/_chunks/ListView-C4hI-wBm.mjs +954 -0
- package/dist/_chunks/ListView-C4hI-wBm.mjs.map +1 -0
- package/dist/_chunks/ar-BYDB75EB.mjs +51 -0
- package/dist/_chunks/ar-BYDB75EB.mjs.map +1 -0
- package/dist/_chunks/ar-OCxhAFUy.js +51 -0
- package/dist/_chunks/ar-OCxhAFUy.js.map +1 -0
- package/dist/_chunks/cs-ChL4LaFY.mjs +139 -0
- package/dist/_chunks/cs-ChL4LaFY.mjs.map +1 -0
- package/dist/_chunks/cs-Ci3js5EC.js +139 -0
- package/dist/_chunks/cs-Ci3js5EC.js.map +1 -0
- package/dist/_chunks/de-DnlblIOh.js +193 -0
- package/dist/_chunks/de-DnlblIOh.js.map +1 -0
- package/dist/_chunks/de-DsHQNzp2.mjs +193 -0
- package/dist/_chunks/de-DsHQNzp2.mjs.map +1 -0
- package/dist/_chunks/dk-BC7NAQR2.mjs +183 -0
- package/dist/_chunks/dk-BC7NAQR2.mjs.map +1 -0
- package/dist/_chunks/dk-D3XnOjYz.js +183 -0
- package/dist/_chunks/dk-D3XnOjYz.js.map +1 -0
- package/dist/_chunks/en-BbczxQBr.mjs +216 -0
- package/dist/_chunks/en-BbczxQBr.mjs.map +1 -0
- package/dist/_chunks/en-BnToboMV.js +216 -0
- package/dist/_chunks/en-BnToboMV.js.map +1 -0
- package/dist/_chunks/es-BE_zx2_w.mjs +183 -0
- package/dist/_chunks/es-BE_zx2_w.mjs.map +1 -0
- package/dist/_chunks/es-DL8lez9W.js +183 -0
- package/dist/_chunks/es-DL8lez9W.js.map +1 -0
- package/dist/_chunks/fr-DnTxugIo.js +75 -0
- package/dist/_chunks/fr-DnTxugIo.js.map +1 -0
- package/dist/_chunks/fr-lU_OMJma.mjs +75 -0
- package/dist/_chunks/fr-lU_OMJma.mjs.map +1 -0
- package/dist/_chunks/id-DYuTgqcc.js +166 -0
- package/dist/_chunks/id-DYuTgqcc.js.map +1 -0
- package/dist/_chunks/id-W1sKBFEw.mjs +166 -0
- package/dist/_chunks/id-W1sKBFEw.mjs.map +1 -0
- package/dist/_chunks/index-2ofTO2HM.js +6738 -0
- package/dist/_chunks/index-2ofTO2HM.js.map +1 -0
- package/dist/_chunks/index-CH93OgkC.js +1209 -0
- package/dist/_chunks/index-CH93OgkC.js.map +1 -0
- package/dist/_chunks/index-CPBF498m.mjs +6701 -0
- package/dist/_chunks/index-CPBF498m.mjs.map +1 -0
- package/dist/_chunks/index-DmqlavBo.mjs +1183 -0
- package/dist/_chunks/index-DmqlavBo.mjs.map +1 -0
- package/dist/_chunks/it-D04lb2Wc.mjs +167 -0
- package/dist/_chunks/it-D04lb2Wc.mjs.map +1 -0
- package/dist/_chunks/it-DS4sM3km.js +167 -0
- package/dist/_chunks/it-DS4sM3km.js.map +1 -0
- package/dist/_chunks/ja-BHLK_2_g.mjs +50 -0
- package/dist/_chunks/ja-BHLK_2_g.mjs.map +1 -0
- package/dist/_chunks/ja-BjouJgZf.js +50 -0
- package/dist/_chunks/ja-BjouJgZf.js.map +1 -0
- package/dist/_chunks/ko-D_71Pdfn.js +183 -0
- package/dist/_chunks/ko-D_71Pdfn.js.map +1 -0
- package/dist/_chunks/ko-DoNsXHXA.mjs +183 -0
- package/dist/_chunks/ko-DoNsXHXA.mjs.map +1 -0
- package/dist/_chunks/ms-BtGFDB9t.mjs +163 -0
- package/dist/_chunks/ms-BtGFDB9t.mjs.map +1 -0
- package/dist/_chunks/ms-Re1pSHmx.js +163 -0
- package/dist/_chunks/ms-Re1pSHmx.js.map +1 -0
- package/dist/_chunks/nl-BaTAuelQ.mjs +156 -0
- package/dist/_chunks/nl-BaTAuelQ.mjs.map +1 -0
- package/dist/_chunks/nl-DQjrDEw0.js +156 -0
- package/dist/_chunks/nl-DQjrDEw0.js.map +1 -0
- package/dist/_chunks/pl-BGwXgwH7.js +193 -0
- package/dist/_chunks/pl-BGwXgwH7.js.map +1 -0
- package/dist/_chunks/pl-CP2Zgp01.mjs +193 -0
- package/dist/_chunks/pl-CP2Zgp01.mjs.map +1 -0
- package/dist/_chunks/pt-BR-CCQGwXs0.mjs +193 -0
- package/dist/_chunks/pt-BR-CCQGwXs0.mjs.map +1 -0
- package/dist/_chunks/pt-BR-DPd5nRnl.js +193 -0
- package/dist/_chunks/pt-BR-DPd5nRnl.js.map +1 -0
- package/dist/_chunks/pt-CJoUDTHQ.js +51 -0
- package/dist/_chunks/pt-CJoUDTHQ.js.map +1 -0
- package/dist/_chunks/pt-DMeTMW2x.mjs +51 -0
- package/dist/_chunks/pt-DMeTMW2x.mjs.map +1 -0
- package/dist/_chunks/ru-C8A_4j0w.js +168 -0
- package/dist/_chunks/ru-C8A_4j0w.js.map +1 -0
- package/dist/_chunks/ru-DGSjru5m.mjs +168 -0
- package/dist/_chunks/ru-DGSjru5m.mjs.map +1 -0
- package/dist/_chunks/sk-DVK4HfSC.mjs +167 -0
- package/dist/_chunks/sk-DVK4HfSC.mjs.map +1 -0
- package/dist/_chunks/sk-raWRcmPT.js +167 -0
- package/dist/_chunks/sk-raWRcmPT.js.map +1 -0
- package/dist/_chunks/sv-BGb12eW3.mjs +202 -0
- package/dist/_chunks/sv-BGb12eW3.mjs.map +1 -0
- package/dist/_chunks/sv-BNN71SFE.js +202 -0
- package/dist/_chunks/sv-BNN71SFE.js.map +1 -0
- package/dist/_chunks/th--u3VqsON.mjs +164 -0
- package/dist/_chunks/th--u3VqsON.mjs.map +1 -0
- package/dist/_chunks/th-C83Bb_kR.js +164 -0
- package/dist/_chunks/th-C83Bb_kR.js.map +1 -0
- package/dist/_chunks/tr-BW20CfcO.js +179 -0
- package/dist/_chunks/tr-BW20CfcO.js.map +1 -0
- package/dist/_chunks/tr-DsUerr-c.mjs +179 -0
- package/dist/_chunks/tr-DsUerr-c.mjs.map +1 -0
- package/dist/_chunks/uk-Bx5IlOKX.mjs +164 -0
- package/dist/_chunks/uk-Bx5IlOKX.mjs.map +1 -0
- package/dist/_chunks/uk-VwB0oiuV.js +164 -0
- package/dist/_chunks/uk-VwB0oiuV.js.map +1 -0
- package/dist/_chunks/zh-BiOCwPJu.js +202 -0
- package/dist/_chunks/zh-BiOCwPJu.js.map +1 -0
- package/dist/_chunks/zh-CsUDN13W.mjs +202 -0
- package/dist/_chunks/zh-CsUDN13W.mjs.map +1 -0
- package/dist/_chunks/zh-Hans-CLTLm_nt.js +147 -0
- package/dist/_chunks/zh-Hans-CLTLm_nt.js.map +1 -0
- package/dist/_chunks/zh-Hans-Cc0M5PXr.mjs +147 -0
- package/dist/_chunks/zh-Hans-Cc0M5PXr.mjs.map +1 -0
- package/dist/admin/index.js +5 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/index.mjs +6 -0
- package/dist/admin/index.mjs.map +1 -0
- package/dist/admin/src/components/AllowedTypesSelect.d.ts +9 -0
- package/dist/admin/src/components/AttributeIcon.d.ts +9 -0
- package/dist/admin/src/components/AttributeOptions/AttributeList.d.ts +6 -0
- package/dist/admin/src/components/AttributeOptions/AttributeOption.d.ts +11 -0
- package/dist/admin/src/components/AttributeOptions/AttributeOptions.d.ts +13 -0
- package/dist/admin/src/components/AttributeOptions/CustomFieldOption.d.ts +22 -0
- package/dist/admin/src/components/AttributeOptions/CustomFieldsList.d.ts +1 -0
- package/dist/admin/src/components/AttributeOptions/EmptyAttributes.d.ts +2 -0
- package/dist/admin/src/components/AttributeOptions/OptionBoxWrapper.d.ts +4 -0
- package/dist/admin/src/components/AutoReloadOverlayBlocker.d.ts +25 -0
- package/dist/admin/src/components/BooleanDefaultValueSelect.d.ts +20 -0
- package/dist/admin/src/components/BooleanRadioGroup.d.ts +8 -0
- package/dist/admin/src/components/BoxWrapper.d.ts +4 -0
- package/dist/admin/src/components/CheckboxWithNumberField.d.ts +11 -0
- package/dist/admin/src/components/ComponentCard/ComponentCard.d.ts +10 -0
- package/dist/admin/src/components/ComponentCard/ComponentIcon/ComponentIcon.d.ts +7 -0
- package/dist/admin/src/components/ComponentCard/ComponentIcon/index.d.ts +1 -0
- package/dist/admin/src/components/ComponentCard/index.d.ts +1 -0
- package/dist/admin/src/components/ComponentList.d.ts +10 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/ContentTypeBuilderNav.d.ts +1 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.d.ts +18 -0
- package/dist/admin/src/components/ContentTypeRadioGroup.d.ts +15 -0
- package/dist/admin/src/components/CustomRadioGroup/CustomRadioGroup.d.ts +15 -0
- package/dist/admin/src/components/CustomRadioGroup/Styles.d.ts +5 -0
- package/dist/admin/src/components/CustomRadioGroup/index.d.ts +1 -0
- package/dist/admin/src/components/DataManagerProvider/DataManagerProvider.d.ts +6 -0
- package/dist/admin/src/components/DataManagerProvider/constants.d.ts +17 -0
- package/dist/admin/src/components/DataManagerProvider/reducer.d.ts +12 -0
- package/dist/admin/src/components/DataManagerProvider/selectors.d.ts +17 -0
- package/dist/admin/src/components/DataManagerProvider/utils/cleanData.d.ts +23 -0
- package/dist/admin/src/components/DataManagerProvider/utils/createDataObject.d.ts +2 -0
- package/dist/admin/src/components/DataManagerProvider/utils/createModifiedDataSchema.d.ts +6 -0
- package/dist/admin/src/components/DataManagerProvider/utils/formatSchemas.d.ts +6 -0
- package/dist/admin/src/components/DataManagerProvider/utils/retrieveComponentsFromSchema.d.ts +4 -0
- package/dist/admin/src/components/DataManagerProvider/utils/retrieveComponentsThatHaveComponents.d.ts +4 -0
- package/dist/admin/src/components/DataManagerProvider/utils/retrieveNestedComponents.d.ts +1 -0
- package/dist/admin/src/components/DataManagerProvider/utils/retrieveSpecificInfoFromComponents.d.ts +1 -0
- package/dist/admin/src/components/DataManagerProvider/utils/serverRestartWatcher.d.ts +6 -0
- package/dist/admin/src/components/DataManagerProvider/utils/validateSchema.d.ts +1 -0
- package/dist/admin/src/components/DisplayedType.d.ts +7 -0
- package/dist/admin/src/components/DraftAndPublishToggle.d.ts +27 -0
- package/dist/admin/src/components/DynamicZoneList.d.ts +10 -0
- package/dist/admin/src/components/FormModal/FormModal.d.ts +1 -0
- package/dist/admin/src/components/FormModal/attributes/advancedForm.d.ts +416 -0
- package/dist/admin/src/components/FormModal/attributes/attributeOptions.d.ts +90 -0
- package/dist/admin/src/components/FormModal/attributes/baseForm.d.ts +376 -0
- package/dist/admin/src/components/FormModal/attributes/commonBaseForm.d.ts +17 -0
- package/dist/admin/src/components/FormModal/attributes/form.d.ts +792 -0
- package/dist/admin/src/components/FormModal/attributes/nameField.d.ts +12 -0
- package/dist/admin/src/components/FormModal/attributes/types.d.ts +325 -0
- package/dist/admin/src/components/FormModal/attributes/validation/common.d.ts +29 -0
- package/dist/admin/src/components/FormModal/category/createCategorySchema.d.ts +5 -0
- package/dist/admin/src/components/FormModal/category/form.d.ts +20 -0
- package/dist/admin/src/components/FormModal/category/regex.d.ts +1 -0
- package/dist/admin/src/components/FormModal/component/componentField.d.ts +20 -0
- package/dist/admin/src/components/FormModal/component/componentForm.d.ts +25 -0
- package/dist/admin/src/components/FormModal/component/createComponentSchema.d.ts +10 -0
- package/dist/admin/src/components/FormModal/constants.d.ts +12 -0
- package/dist/admin/src/components/FormModal/contentType/contentTypeForm.d.ts +116 -0
- package/dist/admin/src/components/FormModal/contentType/createContentTypeSchema.d.ts +24 -0
- package/dist/admin/src/components/FormModal/dynamiczoneForm.d.ts +68 -0
- package/dist/admin/src/components/FormModal/forms/forms.d.ts +207 -0
- package/dist/admin/src/components/FormModal/forms/utils/addItemsToFormSection.d.ts +38 -0
- package/dist/admin/src/components/FormModal/forms/utils/createCollectionName.d.ts +2 -0
- package/dist/admin/src/components/FormModal/forms/utils/getUsedAttributeNames.d.ts +11 -0
- package/dist/admin/src/components/FormModal/reducer.d.ts +4 -0
- package/dist/admin/src/components/FormModal/selectors.d.ts +16 -0
- package/dist/admin/src/components/FormModal/utils/canEditContentType.d.ts +18 -0
- package/dist/admin/src/components/FormModal/utils/createUid.d.ts +4 -0
- package/dist/admin/src/components/FormModal/utils/customFieldDefaultOptionsReducer.d.ts +1 -0
- package/dist/admin/src/components/FormModal/utils/getAttributesToDisplay.d.ts +3 -0
- package/dist/admin/src/components/FormModal/utils/getFormInputNames.d.ts +1 -0
- package/dist/admin/src/components/FormModal/utils/relations.d.ts +4 -0
- package/dist/admin/src/components/FormModalEndActions.d.ts +42 -0
- package/dist/admin/src/components/FormModalHeader.d.ts +18 -0
- package/dist/admin/src/components/FormModalNavigationProvider/FormModalNavigationProvider.d.ts +22 -0
- package/dist/admin/src/components/FormModalNavigationProvider/constants.d.ts +16 -0
- package/dist/admin/src/components/FormModalSubHeader.d.ts +22 -0
- package/dist/admin/src/components/GenericInputs.d.ts +57 -0
- package/dist/admin/src/components/IconPicker/IconPicker.d.ts +13 -0
- package/dist/admin/src/components/IconPicker/constants.d.ts +5 -0
- package/dist/admin/src/components/IconPicker/index.d.ts +1 -0
- package/dist/admin/src/components/List.d.ts +18 -0
- package/dist/admin/src/components/ListRow.d.ts +22 -0
- package/dist/admin/src/components/NestedFooter.d.ts +9 -0
- package/dist/admin/src/components/PluralName.d.ts +22 -0
- package/dist/admin/src/components/Relation/Relation.d.ts +9 -0
- package/dist/admin/src/components/Relation/RelationField/RelationField.d.ts +13 -0
- package/dist/admin/src/components/Relation/RelationField/RelationTargetPicker/RelationTargetPicker.d.ts +6 -0
- package/dist/admin/src/components/Relation/RelationNaturePicker/Components.d.ts +14 -0
- package/dist/admin/src/components/Relation/RelationNaturePicker/RelationNaturePicker.d.ts +8 -0
- package/dist/admin/src/components/SelectCategory.d.ts +19 -0
- package/dist/admin/src/components/SelectComponent.d.ts +19 -0
- package/dist/admin/src/components/SelectComponents.d.ts +19 -0
- package/dist/admin/src/components/SelectDateType.d.ts +38 -0
- package/dist/admin/src/components/SelectNumber.d.ts +42 -0
- package/dist/admin/src/components/SingularName.d.ts +17 -0
- package/dist/admin/src/components/TabForm.d.ts +9 -0
- package/dist/admin/src/components/TextareaEnum.d.ts +20 -0
- package/dist/admin/src/components/Tr.d.ts +5 -0
- package/dist/admin/src/components/UpperFirst.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +6 -0
- package/dist/admin/src/contexts/DataManagerContext.d.ts +41 -0
- package/dist/admin/src/contexts/FormModalNavigationContext.d.ts +38 -0
- package/dist/admin/src/hooks/useDataManager.d.ts +1 -0
- package/dist/admin/src/hooks/useFormModalNavigation.d.ts +1 -0
- package/dist/admin/src/icons/Curve.d.ts +5 -0
- package/dist/admin/src/index.d.ts +16 -0
- package/dist/admin/src/pages/App/index.d.ts +2 -0
- package/dist/admin/src/pages/ListView/LinkToCMSettingsView.d.ts +10 -0
- package/dist/admin/src/pages/ListView/ListView.d.ts +2 -0
- package/dist/admin/src/pages/RecursivePath/RecursivePath.d.ts +1 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/reducers.d.ts +3 -0
- package/dist/admin/src/types.d.ts +57 -0
- package/dist/admin/src/utils/findAttribute.d.ts +2 -0
- package/dist/admin/src/utils/formAPI.d.ts +1 -0
- package/dist/admin/src/utils/getAttributeDisplayedType.d.ts +1 -0
- package/dist/admin/src/utils/getRelationType.d.ts +6 -0
- package/dist/admin/src/utils/getTrad.d.ts +1 -0
- package/dist/admin/src/utils/getYupInnerErrors.d.ts +7 -0
- package/dist/admin/src/utils/index.d.ts +2 -0
- package/dist/admin/src/utils/isAllowedContentTypesForRelations.d.ts +2 -0
- package/dist/admin/src/utils/makeUnique.d.ts +2 -0
- package/dist/admin/src/utils/nameToSlug.d.ts +1 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/startsWithANumber.d.ts +1 -0
- package/dist/admin/src/utils/toRegressedEnumValue.d.ts +1 -0
- package/dist/server/index.js +2312 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +2291 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/server/src/bootstrap.d.ts +6 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/config.d.ts +6 -0
- package/dist/server/src/config.d.ts.map +1 -0
- package/dist/server/src/controllers/builder.d.ts +6 -0
- package/dist/server/src/controllers/builder.d.ts.map +1 -0
- package/dist/server/src/controllers/component-categories.d.ts +7 -0
- package/dist/server/src/controllers/component-categories.d.ts.map +1 -0
- package/dist/server/src/controllers/components.d.ts +38 -0
- package/dist/server/src/controllers/components.d.ts.map +1 -0
- package/dist/server/src/controllers/content-types.d.ts +10 -0
- package/dist/server/src/controllers/content-types.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +25 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/common.d.ts +25 -0
- package/dist/server/src/controllers/validation/common.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/component-category.d.ts +5 -0
- package/dist/server/src/controllers/validation/component-category.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/component.d.ts +25 -0
- package/dist/server/src/controllers/validation/component.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/content-type.d.ts +36 -0
- package/dist/server/src/controllers/validation/content-type.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/data-transform.d.ts +4 -0
- package/dist/server/src/controllers/validation/data-transform.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/model-schema.d.ts +12 -0
- package/dist/server/src/controllers/validation/model-schema.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/relations.d.ts +16 -0
- package/dist/server/src/controllers/validation/relations.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/types.d.ts +9 -0
- package/dist/server/src/controllers/validation/types.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +68 -0
- package/dist/server/src/index.d.ts.map +1 -0
- package/dist/server/src/routes/admin.d.ts +18 -0
- package/dist/server/src/routes/admin.d.ts.map +1 -0
- package/dist/server/src/routes/content-api.d.ts +10 -0
- package/dist/server/src/routes/content-api.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts +28 -0
- package/dist/server/src/routes/index.d.ts.map +1 -0
- package/dist/server/src/services/api-handler.d.ts +15 -0
- package/dist/server/src/services/api-handler.d.ts.map +1 -0
- package/dist/server/src/services/builder.d.ts +5 -0
- package/dist/server/src/services/builder.d.ts.map +1 -0
- package/dist/server/src/services/component-categories.d.ts +13 -0
- package/dist/server/src/services/component-categories.d.ts.map +1 -0
- package/dist/server/src/services/components.d.ts +54 -0
- package/dist/server/src/services/components.d.ts.map +1 -0
- package/dist/server/src/services/constants.d.ts +19 -0
- package/dist/server/src/services/constants.d.ts.map +1 -0
- package/dist/server/src/services/content-types.d.ts +60 -0
- package/dist/server/src/services/content-types.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +14 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/schema-builder/component-builder.d.ts +39 -0
- package/dist/server/src/services/schema-builder/component-builder.d.ts.map +1 -0
- package/dist/server/src/services/schema-builder/content-type-builder.d.ts +38 -0
- package/dist/server/src/services/schema-builder/content-type-builder.d.ts.map +1 -0
- package/dist/server/src/services/schema-builder/index.d.ts +78 -0
- package/dist/server/src/services/schema-builder/index.d.ts.map +1 -0
- package/dist/server/src/services/schema-builder/schema-handler.d.ts +36 -0
- package/dist/server/src/services/schema-builder/schema-handler.d.ts.map +1 -0
- package/dist/server/src/utils/attributes.d.ts +192 -0
- package/dist/server/src/utils/attributes.d.ts.map +1 -0
- package/dist/server/src/utils/helpers.d.ts +3 -0
- package/dist/server/src/utils/helpers.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +15 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/server/src/utils/typeguards.d.ts +3 -0
- package/dist/server/src/utils/typeguards.d.ts.map +1 -0
- package/package.json +91 -3
- package/strapi-server.js +3 -0
|
@@ -0,0 +1,2291 @@
|
|
|
1
|
+
import _, { get, has } from "lodash";
|
|
2
|
+
import { getOr, isUndefined, snakeCase, has as has$1, flatMap } from "lodash/fp";
|
|
3
|
+
import utils, { errors, strings, contentTypes as contentTypes$2, yup, validateYupSchema } from "@strapi/utils";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import path__default, { join } from "path";
|
|
6
|
+
import * as fse from "fs-extra";
|
|
7
|
+
import fse__default from "fs-extra";
|
|
8
|
+
import pluralize from "pluralize";
|
|
9
|
+
const config = {
|
|
10
|
+
default: {},
|
|
11
|
+
validator() {
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
15
|
+
const actions = [
|
|
16
|
+
{
|
|
17
|
+
section: "plugins",
|
|
18
|
+
displayName: "Read",
|
|
19
|
+
uid: "read",
|
|
20
|
+
pluginName: "content-type-builder"
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
await strapi2.service("admin::permission").actionProvider.registerMany(actions);
|
|
24
|
+
};
|
|
25
|
+
const { ApplicationError: ApplicationError$3 } = errors;
|
|
26
|
+
const hasComponent = (model) => {
|
|
27
|
+
const compoKeys = Object.keys(model.attributes || {}).filter((key) => {
|
|
28
|
+
return model.attributes[key].type === "component";
|
|
29
|
+
});
|
|
30
|
+
return compoKeys.length > 0;
|
|
31
|
+
};
|
|
32
|
+
const isConfigurable = (attribute) => _.get(attribute, "configurable", true);
|
|
33
|
+
const isRelation = (attribute) => attribute.type === "relation";
|
|
34
|
+
const formatAttributes = (model) => {
|
|
35
|
+
const { getVisibleAttributes } = utils.contentTypes;
|
|
36
|
+
return getVisibleAttributes(model).reduce((acc, key) => {
|
|
37
|
+
acc[key] = formatAttribute(model.attributes[key]);
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
|
40
|
+
};
|
|
41
|
+
const formatAttribute = (attribute) => {
|
|
42
|
+
const { configurable, required, autoPopulate, pluginOptions } = attribute;
|
|
43
|
+
if (attribute.type === "media") {
|
|
44
|
+
return {
|
|
45
|
+
type: "media",
|
|
46
|
+
multiple: !!attribute.multiple,
|
|
47
|
+
required: !!required,
|
|
48
|
+
configurable: configurable === false ? false : void 0,
|
|
49
|
+
private: !!attribute.private,
|
|
50
|
+
allowedTypes: attribute.allowedTypes,
|
|
51
|
+
pluginOptions
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (attribute.type === "relation") {
|
|
55
|
+
return {
|
|
56
|
+
...attribute,
|
|
57
|
+
type: "relation",
|
|
58
|
+
target: attribute.target,
|
|
59
|
+
targetAttribute: attribute.inversedBy || attribute.mappedBy || null,
|
|
60
|
+
configurable: configurable === false ? false : void 0,
|
|
61
|
+
private: !!attribute.private,
|
|
62
|
+
pluginOptions,
|
|
63
|
+
// TODO: remove
|
|
64
|
+
autoPopulate
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return attribute;
|
|
68
|
+
};
|
|
69
|
+
const replaceTemporaryUIDs = (uidMap) => (schema) => {
|
|
70
|
+
return {
|
|
71
|
+
...schema,
|
|
72
|
+
attributes: Object.keys(schema.attributes).reduce((acc, key) => {
|
|
73
|
+
const attr = schema.attributes[key];
|
|
74
|
+
if (attr.type === "component") {
|
|
75
|
+
if (_.has(uidMap, attr.component)) {
|
|
76
|
+
acc[key] = {
|
|
77
|
+
...attr,
|
|
78
|
+
component: uidMap[attr.component]
|
|
79
|
+
};
|
|
80
|
+
return acc;
|
|
81
|
+
}
|
|
82
|
+
if (!_.has(strapi.components, attr.component)) {
|
|
83
|
+
throw new ApplicationError$3("component.notFound");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (attr.type === "dynamiczone" && _.intersection(attr.components, Object.keys(uidMap)).length > 0) {
|
|
87
|
+
acc[key] = {
|
|
88
|
+
...attr,
|
|
89
|
+
components: attr.components.map((value) => {
|
|
90
|
+
if (_.has(uidMap, value))
|
|
91
|
+
return uidMap[value];
|
|
92
|
+
if (!_.has(strapi.components, value)) {
|
|
93
|
+
throw new ApplicationError$3("component.notFound");
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
})
|
|
97
|
+
};
|
|
98
|
+
return acc;
|
|
99
|
+
}
|
|
100
|
+
acc[key] = attr;
|
|
101
|
+
return acc;
|
|
102
|
+
}, {})
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
function createSchemaHandler(infos) {
|
|
106
|
+
const { category, modelName, plugin, uid, dir, filename, schema } = infos;
|
|
107
|
+
const initialState = {
|
|
108
|
+
modelName,
|
|
109
|
+
plugin,
|
|
110
|
+
category,
|
|
111
|
+
uid,
|
|
112
|
+
dir,
|
|
113
|
+
filename,
|
|
114
|
+
schema: schema || {
|
|
115
|
+
info: {},
|
|
116
|
+
options: {},
|
|
117
|
+
attributes: {}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const state = _.cloneDeep(initialState);
|
|
121
|
+
Object.freeze(initialState.schema);
|
|
122
|
+
let modified = false;
|
|
123
|
+
let deleted = false;
|
|
124
|
+
return {
|
|
125
|
+
get modelName() {
|
|
126
|
+
return initialState.modelName;
|
|
127
|
+
},
|
|
128
|
+
get plugin() {
|
|
129
|
+
return initialState.plugin;
|
|
130
|
+
},
|
|
131
|
+
get category() {
|
|
132
|
+
return initialState.category;
|
|
133
|
+
},
|
|
134
|
+
get kind() {
|
|
135
|
+
return _.get(state.schema, "kind", "collectionType");
|
|
136
|
+
},
|
|
137
|
+
get uid() {
|
|
138
|
+
return state.uid;
|
|
139
|
+
},
|
|
140
|
+
get writable() {
|
|
141
|
+
return _.get(state, "plugin") !== "admin";
|
|
142
|
+
},
|
|
143
|
+
setUID(val) {
|
|
144
|
+
modified = true;
|
|
145
|
+
state.uid = val;
|
|
146
|
+
return this;
|
|
147
|
+
},
|
|
148
|
+
setDir(val) {
|
|
149
|
+
modified = true;
|
|
150
|
+
state.dir = val;
|
|
151
|
+
return this;
|
|
152
|
+
},
|
|
153
|
+
get schema() {
|
|
154
|
+
return _.cloneDeep(state.schema);
|
|
155
|
+
},
|
|
156
|
+
setSchema(val) {
|
|
157
|
+
modified = true;
|
|
158
|
+
state.schema = _.cloneDeep(val);
|
|
159
|
+
return this;
|
|
160
|
+
},
|
|
161
|
+
// get a particular path inside the schema
|
|
162
|
+
get(path2) {
|
|
163
|
+
return _.get(state.schema, path2);
|
|
164
|
+
},
|
|
165
|
+
// set a particular path inside the schema
|
|
166
|
+
set(path2, val) {
|
|
167
|
+
if (!state.schema)
|
|
168
|
+
return this;
|
|
169
|
+
modified = true;
|
|
170
|
+
const value = _.defaultTo(val, _.get(state.schema, path2));
|
|
171
|
+
_.set(state.schema, path2, value);
|
|
172
|
+
return this;
|
|
173
|
+
},
|
|
174
|
+
// delete a particular path inside the schema
|
|
175
|
+
unset(path2) {
|
|
176
|
+
modified = true;
|
|
177
|
+
_.unset(state.schema, path2);
|
|
178
|
+
return this;
|
|
179
|
+
},
|
|
180
|
+
delete() {
|
|
181
|
+
deleted = true;
|
|
182
|
+
return this;
|
|
183
|
+
},
|
|
184
|
+
getAttribute(key) {
|
|
185
|
+
return this.get(["attributes", key]);
|
|
186
|
+
},
|
|
187
|
+
setAttribute(key, attribute) {
|
|
188
|
+
return this.set(["attributes", key], attribute);
|
|
189
|
+
},
|
|
190
|
+
deleteAttribute(key) {
|
|
191
|
+
return this.unset(["attributes", key]);
|
|
192
|
+
},
|
|
193
|
+
setAttributes(newAttributes) {
|
|
194
|
+
if (!this.schema)
|
|
195
|
+
return this;
|
|
196
|
+
for (const key in this.schema.attributes) {
|
|
197
|
+
if (isConfigurable(this.schema.attributes[key])) {
|
|
198
|
+
this.deleteAttribute(key);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const key of Object.keys(newAttributes)) {
|
|
202
|
+
this.setAttribute(key, newAttributes[key]);
|
|
203
|
+
}
|
|
204
|
+
return this;
|
|
205
|
+
},
|
|
206
|
+
removeContentType(uid2) {
|
|
207
|
+
if (!state.schema)
|
|
208
|
+
return this;
|
|
209
|
+
const attributes = state.schema.attributes;
|
|
210
|
+
Object.keys(attributes).forEach((key) => {
|
|
211
|
+
const attribute = attributes[key];
|
|
212
|
+
if (attribute.target === uid2) {
|
|
213
|
+
this.deleteAttribute(key);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return this;
|
|
217
|
+
},
|
|
218
|
+
// utils
|
|
219
|
+
removeComponent(uid2) {
|
|
220
|
+
if (!state.schema)
|
|
221
|
+
return this;
|
|
222
|
+
const attributes = state.schema.attributes;
|
|
223
|
+
Object.keys(attributes).forEach((key) => {
|
|
224
|
+
const attr = attributes[key];
|
|
225
|
+
if (attr.type === "component" && attr.component === uid2) {
|
|
226
|
+
this.deleteAttribute(key);
|
|
227
|
+
}
|
|
228
|
+
if (attr.type === "dynamiczone" && Array.isArray(attr.components) && attr.components.includes(uid2)) {
|
|
229
|
+
const updatedComponentList = attributes[key].components.filter(
|
|
230
|
+
(val) => val !== uid2
|
|
231
|
+
);
|
|
232
|
+
this.set(["attributes", key, "components"], updatedComponentList);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return this;
|
|
236
|
+
},
|
|
237
|
+
updateComponent(uid2, newUID) {
|
|
238
|
+
if (!state.schema)
|
|
239
|
+
return this;
|
|
240
|
+
const attributes = state.schema.attributes;
|
|
241
|
+
Object.keys(attributes).forEach((key) => {
|
|
242
|
+
const attr = attributes[key];
|
|
243
|
+
if (attr.type === "component" && attr.component === uid2) {
|
|
244
|
+
this.set(["attributes", key, "component"], newUID);
|
|
245
|
+
}
|
|
246
|
+
if (attr.type === "dynamiczone" && Array.isArray(attr.components) && attr.components.includes(uid2)) {
|
|
247
|
+
const updatedComponentList = attr.components.map(
|
|
248
|
+
(val) => val === uid2 ? newUID : val
|
|
249
|
+
);
|
|
250
|
+
this.set(["attributes", key, "components"], updatedComponentList);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
return this;
|
|
254
|
+
},
|
|
255
|
+
// save the schema to disk
|
|
256
|
+
async flush() {
|
|
257
|
+
if (!this.writable) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const initialPath = path__default.join(initialState.dir, initialState.filename);
|
|
261
|
+
const filePath = path__default.join(state.dir, state.filename);
|
|
262
|
+
if (deleted) {
|
|
263
|
+
await fse__default.remove(initialPath);
|
|
264
|
+
const list = await fse__default.readdir(initialState.dir);
|
|
265
|
+
if (list.length === 0) {
|
|
266
|
+
await fse__default.remove(initialState.dir);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (modified) {
|
|
271
|
+
if (!state.schema)
|
|
272
|
+
return Promise.resolve();
|
|
273
|
+
await fse__default.ensureFile(filePath);
|
|
274
|
+
await fse__default.writeJSON(
|
|
275
|
+
filePath,
|
|
276
|
+
{
|
|
277
|
+
kind: state.schema.kind,
|
|
278
|
+
collectionName: state.schema.collectionName,
|
|
279
|
+
info: state.schema.info,
|
|
280
|
+
options: state.schema.options,
|
|
281
|
+
pluginOptions: state.schema.pluginOptions,
|
|
282
|
+
attributes: state.schema.attributes,
|
|
283
|
+
config: state.schema.config
|
|
284
|
+
},
|
|
285
|
+
{ spaces: 2 }
|
|
286
|
+
);
|
|
287
|
+
if (initialPath !== filePath) {
|
|
288
|
+
await fse__default.remove(initialPath);
|
|
289
|
+
const list = await fse__default.readdir(initialState.dir);
|
|
290
|
+
if (list.length === 0) {
|
|
291
|
+
await fse__default.remove(initialState.dir);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return Promise.resolve();
|
|
297
|
+
},
|
|
298
|
+
// reset the schema to its initial value
|
|
299
|
+
async rollback() {
|
|
300
|
+
if (!this.writable) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const initialPath = path__default.join(initialState.dir, initialState.filename);
|
|
304
|
+
const filePath = path__default.join(state.dir, state.filename);
|
|
305
|
+
if (!initialState.uid) {
|
|
306
|
+
await fse__default.remove(filePath);
|
|
307
|
+
const list = await fse__default.readdir(state.dir);
|
|
308
|
+
if (list.length === 0) {
|
|
309
|
+
await fse__default.remove(state.dir);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (modified || deleted) {
|
|
314
|
+
await fse__default.ensureFile(initialPath);
|
|
315
|
+
await fse__default.writeJSON(initialPath, initialState.schema, { spaces: 2 });
|
|
316
|
+
if (initialPath !== filePath) {
|
|
317
|
+
await fse__default.remove(filePath);
|
|
318
|
+
const list = await fse__default.readdir(state.dir);
|
|
319
|
+
if (list.length === 0) {
|
|
320
|
+
await fse__default.remove(state.dir);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return Promise.resolve();
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const { ApplicationError: ApplicationError$2 } = errors;
|
|
329
|
+
function createComponentBuilder$1() {
|
|
330
|
+
return {
|
|
331
|
+
createComponentUID({ category, displayName }) {
|
|
332
|
+
return `${strings.nameToSlug(category)}.${strings.nameToSlug(displayName)}`;
|
|
333
|
+
},
|
|
334
|
+
createNewComponentUIDMap(components2) {
|
|
335
|
+
return components2.reduce((uidMap, component) => {
|
|
336
|
+
uidMap[component.tmpUID] = this.createComponentUID(component);
|
|
337
|
+
return uidMap;
|
|
338
|
+
}, {});
|
|
339
|
+
},
|
|
340
|
+
/**
|
|
341
|
+
* create a component in the tmpComponent map
|
|
342
|
+
*/
|
|
343
|
+
createComponent(infos) {
|
|
344
|
+
const uid = this.createComponentUID(infos);
|
|
345
|
+
if (this.components.has(uid)) {
|
|
346
|
+
throw new ApplicationError$2("component.alreadyExists");
|
|
347
|
+
}
|
|
348
|
+
const handler = createSchemaHandler({
|
|
349
|
+
dir: path__default.join(strapi.dirs.app.components, strings.nameToSlug(infos.category)),
|
|
350
|
+
filename: `${strings.nameToSlug(infos.displayName)}.json`
|
|
351
|
+
});
|
|
352
|
+
const collectionName = `components_${strings.nameToCollectionName(
|
|
353
|
+
infos.category
|
|
354
|
+
)}_${strings.nameToCollectionName(pluralize(infos.displayName))}`;
|
|
355
|
+
this.components.forEach((compo) => {
|
|
356
|
+
if (compo.schema.collectionName === collectionName) {
|
|
357
|
+
throw new ApplicationError$2("component.alreadyExists");
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
handler.setUID(uid).set("collectionName", collectionName).set(["info", "displayName"], infos.displayName).set(["info", "icon"], infos.icon).set(["info", "description"], infos.description).set("pluginOptions", infos.pluginOptions).set("config", infos.config).setAttributes(this.convertAttributes(infos.attributes));
|
|
361
|
+
if (this.components.size === 0) {
|
|
362
|
+
strapi.telemetry.send("didCreateFirstComponent");
|
|
363
|
+
} else {
|
|
364
|
+
strapi.telemetry.send("didCreateComponent");
|
|
365
|
+
}
|
|
366
|
+
this.components.set(uid, handler);
|
|
367
|
+
return handler;
|
|
368
|
+
},
|
|
369
|
+
/**
|
|
370
|
+
* create a component in the tmpComponent map
|
|
371
|
+
*/
|
|
372
|
+
editComponent(infos) {
|
|
373
|
+
const { uid } = infos;
|
|
374
|
+
if (!this.components.has(uid)) {
|
|
375
|
+
throw new errors.ApplicationError("component.notFound");
|
|
376
|
+
}
|
|
377
|
+
const component = this.components.get(uid);
|
|
378
|
+
const [, nameUID] = uid.split(".");
|
|
379
|
+
const newCategory = strings.nameToSlug(infos.category);
|
|
380
|
+
const newUID = `${newCategory}.${nameUID}`;
|
|
381
|
+
if (newUID !== uid && this.components.has(newUID)) {
|
|
382
|
+
throw new errors.ApplicationError("component.edit.alreadyExists");
|
|
383
|
+
}
|
|
384
|
+
const newDir = path__default.join(strapi.dirs.app.components, newCategory);
|
|
385
|
+
const oldAttributes = component.schema.attributes;
|
|
386
|
+
const newAttributes = _.omitBy(infos.attributes, (attr, key) => {
|
|
387
|
+
return _.has(oldAttributes, key) && !isConfigurable(oldAttributes[key]);
|
|
388
|
+
});
|
|
389
|
+
component.setUID(newUID).setDir(newDir).set(["info", "displayName"], infos.displayName).set(["info", "icon"], infos.icon).set(["info", "description"], infos.description).set("pluginOptions", infos.pluginOptions).setAttributes(this.convertAttributes(newAttributes));
|
|
390
|
+
if (newUID !== uid) {
|
|
391
|
+
this.components.forEach((compo) => {
|
|
392
|
+
compo.updateComponent(uid, newUID);
|
|
393
|
+
});
|
|
394
|
+
this.contentTypes.forEach((ct) => {
|
|
395
|
+
ct.updateComponent(uid, newUID);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return component;
|
|
399
|
+
},
|
|
400
|
+
deleteComponent(uid) {
|
|
401
|
+
if (!this.components.has(uid)) {
|
|
402
|
+
throw new errors.ApplicationError("component.notFound");
|
|
403
|
+
}
|
|
404
|
+
this.components.forEach((compo) => {
|
|
405
|
+
compo.removeComponent(uid);
|
|
406
|
+
});
|
|
407
|
+
this.contentTypes.forEach((ct) => {
|
|
408
|
+
ct.removeComponent(uid);
|
|
409
|
+
});
|
|
410
|
+
return this.components.get(uid).delete();
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const modelTypes = {
|
|
415
|
+
CONTENT_TYPE: "CONTENT_TYPE",
|
|
416
|
+
COMPONENT: "COMPONENT"
|
|
417
|
+
};
|
|
418
|
+
const typeKinds = {
|
|
419
|
+
SINGLE_TYPE: "singleType",
|
|
420
|
+
COLLECTION_TYPE: "collectionType"
|
|
421
|
+
};
|
|
422
|
+
const DEFAULT_TYPES = [
|
|
423
|
+
// advanced types
|
|
424
|
+
"media",
|
|
425
|
+
// scalar types
|
|
426
|
+
"string",
|
|
427
|
+
"text",
|
|
428
|
+
"richtext",
|
|
429
|
+
"blocks",
|
|
430
|
+
"json",
|
|
431
|
+
"enumeration",
|
|
432
|
+
"password",
|
|
433
|
+
"email",
|
|
434
|
+
"integer",
|
|
435
|
+
"biginteger",
|
|
436
|
+
"float",
|
|
437
|
+
"decimal",
|
|
438
|
+
"date",
|
|
439
|
+
"time",
|
|
440
|
+
"datetime",
|
|
441
|
+
"timestamp",
|
|
442
|
+
"boolean",
|
|
443
|
+
"relation"
|
|
444
|
+
];
|
|
445
|
+
const VALID_UID_TARGETS = ["string", "text"];
|
|
446
|
+
const FORBIDDEN_ATTRIBUTE_NAMES = ["__component", "__contentType"];
|
|
447
|
+
const coreUids = {
|
|
448
|
+
STRAPI_USER: "admin::user",
|
|
449
|
+
PREFIX: "strapi::"
|
|
450
|
+
};
|
|
451
|
+
const pluginsUids = {
|
|
452
|
+
UPLOAD_FILE: "plugin::upload.file"
|
|
453
|
+
};
|
|
454
|
+
const { ApplicationError: ApplicationError$1 } = errors;
|
|
455
|
+
const reuseUnsetPreviousProperties = (newAttribute, oldAttribute) => {
|
|
456
|
+
_.defaults(
|
|
457
|
+
newAttribute,
|
|
458
|
+
_.omit(oldAttribute, [
|
|
459
|
+
"configurable",
|
|
460
|
+
"required",
|
|
461
|
+
"private",
|
|
462
|
+
"unique",
|
|
463
|
+
"pluginOptions",
|
|
464
|
+
"inversedBy",
|
|
465
|
+
"mappedBy"
|
|
466
|
+
])
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
function createComponentBuilder() {
|
|
470
|
+
return {
|
|
471
|
+
setRelation({ key, uid, attribute }) {
|
|
472
|
+
if (!_.has(attribute, "target")) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const targetCT = this.contentTypes.get(attribute.target);
|
|
476
|
+
const targetAttribute = targetCT.getAttribute(attribute.targetAttribute);
|
|
477
|
+
if (!attribute.targetAttribute) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
targetCT.setAttribute(
|
|
481
|
+
attribute.targetAttribute,
|
|
482
|
+
generateRelation({ key, attribute, uid, targetAttribute })
|
|
483
|
+
);
|
|
484
|
+
},
|
|
485
|
+
unsetRelation(attribute) {
|
|
486
|
+
if (!_.has(attribute, "target")) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const targetCT = this.contentTypes.get(attribute.target);
|
|
490
|
+
const targetAttributeName = attribute.inversedBy || attribute.mappedBy;
|
|
491
|
+
const targetAttribute = targetCT.getAttribute(targetAttributeName);
|
|
492
|
+
if (!targetAttribute)
|
|
493
|
+
return;
|
|
494
|
+
return targetCT.deleteAttribute(targetAttributeName);
|
|
495
|
+
},
|
|
496
|
+
/**
|
|
497
|
+
* Creates a content type in memory to be written to files later on
|
|
498
|
+
*/
|
|
499
|
+
createContentType(infos) {
|
|
500
|
+
const uid = createContentTypeUID(infos);
|
|
501
|
+
if (this.contentTypes.has(uid)) {
|
|
502
|
+
throw new ApplicationError$1("contentType.alreadyExists");
|
|
503
|
+
}
|
|
504
|
+
const contentType = createSchemaHandler({
|
|
505
|
+
modelName: infos.singularName,
|
|
506
|
+
dir: path__default.join(
|
|
507
|
+
strapi.dirs.app.api,
|
|
508
|
+
infos.singularName,
|
|
509
|
+
"content-types",
|
|
510
|
+
infos.singularName
|
|
511
|
+
),
|
|
512
|
+
filename: `schema.json`
|
|
513
|
+
});
|
|
514
|
+
this.contentTypes.set(uid, contentType);
|
|
515
|
+
Object.keys(infos.attributes).forEach((key) => {
|
|
516
|
+
const { target } = infos.attributes[key];
|
|
517
|
+
if (target === "__self__") {
|
|
518
|
+
infos.attributes[key].target = uid;
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
contentType.setUID(uid).set("kind", infos.kind || typeKinds.COLLECTION_TYPE).set(
|
|
522
|
+
"collectionName",
|
|
523
|
+
infos.collectionName || strings.nameToCollectionName(infos.pluralName)
|
|
524
|
+
).set("info", {
|
|
525
|
+
singularName: infos.singularName,
|
|
526
|
+
pluralName: infos.pluralName,
|
|
527
|
+
displayName: infos.displayName,
|
|
528
|
+
description: infos.description
|
|
529
|
+
}).set("options", {
|
|
530
|
+
...infos.options ?? {},
|
|
531
|
+
draftAndPublish: infos.draftAndPublish
|
|
532
|
+
}).set("pluginOptions", infos.pluginOptions).set("config", infos.config).setAttributes(this.convertAttributes(infos.attributes));
|
|
533
|
+
Object.keys(infos.attributes).forEach((key) => {
|
|
534
|
+
const attribute = infos.attributes[key];
|
|
535
|
+
if (isRelation(attribute)) {
|
|
536
|
+
if (["manyToMany", "oneToOne"].includes(attribute.relation)) {
|
|
537
|
+
attribute.dominant = true;
|
|
538
|
+
}
|
|
539
|
+
this.setRelation({
|
|
540
|
+
key,
|
|
541
|
+
uid,
|
|
542
|
+
attribute
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
return contentType;
|
|
547
|
+
},
|
|
548
|
+
editContentType(infos) {
|
|
549
|
+
const { uid } = infos;
|
|
550
|
+
if (!this.contentTypes.has(uid)) {
|
|
551
|
+
throw new ApplicationError$1("contentType.notFound");
|
|
552
|
+
}
|
|
553
|
+
const contentType = this.contentTypes.get(uid);
|
|
554
|
+
const oldAttributes = contentType.schema.attributes;
|
|
555
|
+
const newAttributes = _.omitBy(infos.attributes, (attr, key) => {
|
|
556
|
+
return _.has(oldAttributes, key) && !isConfigurable(oldAttributes[key]);
|
|
557
|
+
});
|
|
558
|
+
const newKeys = _.difference(Object.keys(newAttributes), Object.keys(oldAttributes));
|
|
559
|
+
const deletedKeys = _.difference(Object.keys(oldAttributes), Object.keys(newAttributes));
|
|
560
|
+
const remainingKeys = _.intersection(Object.keys(oldAttributes), Object.keys(newAttributes));
|
|
561
|
+
deletedKeys.forEach((key) => {
|
|
562
|
+
const attribute = oldAttributes[key];
|
|
563
|
+
const targetAttributeName = attribute.inversedBy || attribute.mappedBy;
|
|
564
|
+
if (isConfigurable(attribute) && isRelation(attribute) && !_.isNil(targetAttributeName)) {
|
|
565
|
+
this.unsetRelation(attribute);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
remainingKeys.forEach((key) => {
|
|
569
|
+
const oldAttribute = oldAttributes[key];
|
|
570
|
+
const newAttribute = newAttributes[key];
|
|
571
|
+
if (!isRelation(oldAttribute) && isRelation(newAttribute)) {
|
|
572
|
+
return this.setRelation({
|
|
573
|
+
key,
|
|
574
|
+
uid,
|
|
575
|
+
attribute: newAttributes[key]
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
if (isRelation(oldAttribute) && !isRelation(newAttribute)) {
|
|
579
|
+
return this.unsetRelation(oldAttribute);
|
|
580
|
+
}
|
|
581
|
+
if (isRelation(oldAttribute) && isRelation(newAttribute)) {
|
|
582
|
+
const oldTargetAttributeName = oldAttribute.inversedBy || oldAttribute.mappedBy;
|
|
583
|
+
const sameRelation = oldAttribute.relation === newAttribute.relation;
|
|
584
|
+
const targetAttributeHasChanged = oldTargetAttributeName !== newAttribute.targetAttribute;
|
|
585
|
+
if (!sameRelation || targetAttributeHasChanged) {
|
|
586
|
+
this.unsetRelation(oldAttribute);
|
|
587
|
+
}
|
|
588
|
+
reuseUnsetPreviousProperties(newAttribute, oldAttribute);
|
|
589
|
+
if (oldAttribute.inversedBy) {
|
|
590
|
+
newAttribute.dominant = true;
|
|
591
|
+
} else if (oldAttribute.mappedBy) {
|
|
592
|
+
newAttribute.dominant = false;
|
|
593
|
+
}
|
|
594
|
+
return this.setRelation({
|
|
595
|
+
key,
|
|
596
|
+
uid,
|
|
597
|
+
attribute: newAttribute
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
newKeys.forEach((key) => {
|
|
602
|
+
const attribute = newAttributes[key];
|
|
603
|
+
if (isRelation(attribute)) {
|
|
604
|
+
if (["manyToMany", "oneToOne"].includes(attribute.relation)) {
|
|
605
|
+
attribute.dominant = true;
|
|
606
|
+
}
|
|
607
|
+
this.setRelation({
|
|
608
|
+
key,
|
|
609
|
+
uid,
|
|
610
|
+
attribute
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
contentType.set("kind", infos.kind || contentType.schema.kind).set(["info", "displayName"], infos.displayName).set(["info", "description"], infos.description).set("options", {
|
|
615
|
+
...infos.options ?? {},
|
|
616
|
+
draftAndPublish: infos.draftAndPublish
|
|
617
|
+
}).set("pluginOptions", infos.pluginOptions).setAttributes(this.convertAttributes(newAttributes));
|
|
618
|
+
return contentType;
|
|
619
|
+
},
|
|
620
|
+
deleteContentType(uid) {
|
|
621
|
+
if (!this.contentTypes.has(uid)) {
|
|
622
|
+
throw new ApplicationError$1("contentType.notFound");
|
|
623
|
+
}
|
|
624
|
+
this.components.forEach((compo) => {
|
|
625
|
+
compo.removeContentType(uid);
|
|
626
|
+
});
|
|
627
|
+
this.contentTypes.forEach((ct) => {
|
|
628
|
+
ct.removeContentType(uid);
|
|
629
|
+
});
|
|
630
|
+
return this.contentTypes.get(uid).delete();
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const createContentTypeUID = ({
|
|
635
|
+
singularName
|
|
636
|
+
}) => `api::${singularName}.${singularName}`;
|
|
637
|
+
const generateRelation = ({ key, attribute, uid, targetAttribute = {} }) => {
|
|
638
|
+
const opts = {
|
|
639
|
+
type: "relation",
|
|
640
|
+
target: uid,
|
|
641
|
+
autoPopulate: targetAttribute.autoPopulate,
|
|
642
|
+
private: targetAttribute.private || void 0,
|
|
643
|
+
pluginOptions: targetAttribute.pluginOptions || void 0
|
|
644
|
+
};
|
|
645
|
+
switch (attribute.relation) {
|
|
646
|
+
case "oneToOne": {
|
|
647
|
+
opts.relation = "oneToOne";
|
|
648
|
+
if (attribute.dominant) {
|
|
649
|
+
opts.mappedBy = key;
|
|
650
|
+
} else {
|
|
651
|
+
opts.inversedBy = key;
|
|
652
|
+
}
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
case "oneToMany": {
|
|
656
|
+
opts.relation = "manyToOne";
|
|
657
|
+
opts.inversedBy = key;
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "manyToOne": {
|
|
661
|
+
opts.relation = "oneToMany";
|
|
662
|
+
opts.mappedBy = key;
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
case "manyToMany": {
|
|
666
|
+
opts.relation = "manyToMany";
|
|
667
|
+
if (attribute.dominant) {
|
|
668
|
+
opts.mappedBy = key;
|
|
669
|
+
} else {
|
|
670
|
+
opts.inversedBy = key;
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const { type, relation, target, ...restOptions } = opts;
|
|
676
|
+
return {
|
|
677
|
+
type,
|
|
678
|
+
relation,
|
|
679
|
+
target,
|
|
680
|
+
...restOptions
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
function createBuilder() {
|
|
684
|
+
const components2 = Object.values(strapi.components).map((componentInput) => ({
|
|
685
|
+
category: componentInput.category,
|
|
686
|
+
modelName: componentInput.modelName,
|
|
687
|
+
plugin: componentInput.modelName,
|
|
688
|
+
uid: componentInput.uid,
|
|
689
|
+
filename: componentInput.__filename__,
|
|
690
|
+
dir: join(strapi.dirs.app.components, componentInput.category),
|
|
691
|
+
schema: componentInput.__schema__,
|
|
692
|
+
config: componentInput.config
|
|
693
|
+
}));
|
|
694
|
+
const contentTypes2 = Object.values(strapi.contentTypes).map((contentTypeInput) => {
|
|
695
|
+
const dir = contentTypeInput.plugin ? join(
|
|
696
|
+
strapi.dirs.app.extensions,
|
|
697
|
+
contentTypeInput.plugin,
|
|
698
|
+
"content-types",
|
|
699
|
+
contentTypeInput.info.singularName
|
|
700
|
+
) : join(
|
|
701
|
+
strapi.dirs.app.api,
|
|
702
|
+
contentTypeInput.apiName,
|
|
703
|
+
"content-types",
|
|
704
|
+
contentTypeInput.info.singularName
|
|
705
|
+
);
|
|
706
|
+
return {
|
|
707
|
+
modelName: contentTypeInput.modelName,
|
|
708
|
+
plugin: contentTypeInput.plugin,
|
|
709
|
+
uid: contentTypeInput.uid,
|
|
710
|
+
filename: "schema.json",
|
|
711
|
+
dir,
|
|
712
|
+
schema: contentTypeInput.__schema__,
|
|
713
|
+
config: contentTypeInput.config
|
|
714
|
+
};
|
|
715
|
+
});
|
|
716
|
+
return createSchemaBuilder({
|
|
717
|
+
components: components2,
|
|
718
|
+
contentTypes: contentTypes2
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function createSchemaBuilder({ components: components2, contentTypes: contentTypes2 }) {
|
|
722
|
+
const tmpComponents = /* @__PURE__ */ new Map();
|
|
723
|
+
const tmpContentTypes = /* @__PURE__ */ new Map();
|
|
724
|
+
Object.keys(contentTypes2).forEach((key) => {
|
|
725
|
+
tmpContentTypes.set(contentTypes2[key].uid, createSchemaHandler(contentTypes2[key]));
|
|
726
|
+
});
|
|
727
|
+
Object.keys(components2).forEach((key) => {
|
|
728
|
+
tmpComponents.set(components2[key].uid, createSchemaHandler(components2[key]));
|
|
729
|
+
});
|
|
730
|
+
return {
|
|
731
|
+
get components() {
|
|
732
|
+
return tmpComponents;
|
|
733
|
+
},
|
|
734
|
+
get contentTypes() {
|
|
735
|
+
return tmpContentTypes;
|
|
736
|
+
},
|
|
737
|
+
/**
|
|
738
|
+
* Convert Attributes received from the API to the right syntax
|
|
739
|
+
*/
|
|
740
|
+
convertAttributes(attributes) {
|
|
741
|
+
return Object.keys(attributes).reduce(
|
|
742
|
+
(acc, key) => {
|
|
743
|
+
const attribute = attributes[key];
|
|
744
|
+
const { configurable, private: isPrivate } = attribute;
|
|
745
|
+
const baseProperties = {
|
|
746
|
+
private: isPrivate === true ? true : void 0,
|
|
747
|
+
configurable: configurable === false ? false : void 0
|
|
748
|
+
};
|
|
749
|
+
if (attribute.type === "relation") {
|
|
750
|
+
const { target, relation, targetAttribute, dominant, ...restOfProperties } = attribute;
|
|
751
|
+
const attr = {
|
|
752
|
+
type: "relation",
|
|
753
|
+
relation,
|
|
754
|
+
target,
|
|
755
|
+
...restOfProperties,
|
|
756
|
+
...baseProperties
|
|
757
|
+
};
|
|
758
|
+
acc[key] = attr;
|
|
759
|
+
if (target && !this.contentTypes.has(target)) {
|
|
760
|
+
throw new errors.ApplicationError(`target: ${target} does not exist`);
|
|
761
|
+
}
|
|
762
|
+
if (_.isNil(targetAttribute)) {
|
|
763
|
+
return acc;
|
|
764
|
+
}
|
|
765
|
+
if (["oneToOne", "manyToMany"].includes(relation) && dominant === true) {
|
|
766
|
+
attr.inversedBy = targetAttribute;
|
|
767
|
+
} else if (["oneToOne", "manyToMany"].includes(relation) && dominant === false) {
|
|
768
|
+
attr.mappedBy = targetAttribute;
|
|
769
|
+
} else if (["oneToOne", "manyToOne", "manyToMany"].includes(relation)) {
|
|
770
|
+
attr.inversedBy = targetAttribute;
|
|
771
|
+
} else if (["oneToMany"].includes(relation)) {
|
|
772
|
+
attr.mappedBy = targetAttribute;
|
|
773
|
+
}
|
|
774
|
+
return acc;
|
|
775
|
+
}
|
|
776
|
+
acc[key] = {
|
|
777
|
+
...attribute,
|
|
778
|
+
...baseProperties
|
|
779
|
+
};
|
|
780
|
+
return acc;
|
|
781
|
+
},
|
|
782
|
+
{}
|
|
783
|
+
);
|
|
784
|
+
},
|
|
785
|
+
...createComponentBuilder$1(),
|
|
786
|
+
...createComponentBuilder(),
|
|
787
|
+
/**
|
|
788
|
+
* Write all type to files
|
|
789
|
+
*/
|
|
790
|
+
writeFiles() {
|
|
791
|
+
const schemas = [
|
|
792
|
+
...Array.from(tmpComponents.values()),
|
|
793
|
+
...Array.from(tmpContentTypes.values())
|
|
794
|
+
];
|
|
795
|
+
return Promise.all(schemas.map((schema) => schema.flush())).catch((error) => {
|
|
796
|
+
strapi.log.error("Error writing schema files");
|
|
797
|
+
strapi.log.error(error);
|
|
798
|
+
return this.rollback();
|
|
799
|
+
}).catch((error) => {
|
|
800
|
+
strapi.log.error(
|
|
801
|
+
"Error rolling back schema files. You might need to fix your files manually"
|
|
802
|
+
);
|
|
803
|
+
strapi.log.error(error);
|
|
804
|
+
throw new errors.ApplicationError("Invalid schema edition");
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
/**
|
|
808
|
+
* rollback all files
|
|
809
|
+
*/
|
|
810
|
+
rollback() {
|
|
811
|
+
return Promise.all(
|
|
812
|
+
[...Array.from(tmpComponents.values()), ...Array.from(tmpContentTypes.values())].map(
|
|
813
|
+
(schema) => schema.rollback()
|
|
814
|
+
)
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
const { ApplicationError } = errors;
|
|
820
|
+
const isContentTypeVisible = (model) => getOr(true, "pluginOptions.content-type-builder.visible", model) === true;
|
|
821
|
+
const getRestrictRelationsTo = (contentType) => {
|
|
822
|
+
const { uid } = contentType;
|
|
823
|
+
if (uid === coreUids.STRAPI_USER) {
|
|
824
|
+
return ["oneWay", "manyWay"];
|
|
825
|
+
}
|
|
826
|
+
if (uid.startsWith(coreUids.PREFIX) || uid === pluginsUids.UPLOAD_FILE || !isContentTypeVisible(contentType)) {
|
|
827
|
+
return [];
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
};
|
|
831
|
+
const formatContentType = (contentType) => {
|
|
832
|
+
const { uid, kind, modelName, plugin, collectionName, info } = contentType;
|
|
833
|
+
return {
|
|
834
|
+
uid,
|
|
835
|
+
plugin,
|
|
836
|
+
apiID: modelName,
|
|
837
|
+
schema: {
|
|
838
|
+
...contentTypes$2.getOptions(contentType),
|
|
839
|
+
displayName: info.displayName,
|
|
840
|
+
singularName: info.singularName,
|
|
841
|
+
pluralName: info.pluralName,
|
|
842
|
+
description: _.get(info, "description", ""),
|
|
843
|
+
pluginOptions: contentType.pluginOptions,
|
|
844
|
+
kind: kind || "collectionType",
|
|
845
|
+
collectionName,
|
|
846
|
+
attributes: formatAttributes(contentType),
|
|
847
|
+
visible: isContentTypeVisible(contentType),
|
|
848
|
+
restrictRelationsTo: getRestrictRelationsTo(contentType)
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
};
|
|
852
|
+
const createContentTypes = async (contentTypes2) => {
|
|
853
|
+
const builder2 = createBuilder();
|
|
854
|
+
const createdContentTypes = [];
|
|
855
|
+
for (const contentType of contentTypes2) {
|
|
856
|
+
createdContentTypes.push(await createContentType(contentType, { defaultBuilder: builder2 }));
|
|
857
|
+
}
|
|
858
|
+
await builder2.writeFiles();
|
|
859
|
+
return createdContentTypes;
|
|
860
|
+
};
|
|
861
|
+
const createContentType = async ({ contentType, components: components2 }, options = {}) => {
|
|
862
|
+
const builder2 = options.defaultBuilder || createBuilder();
|
|
863
|
+
const uidMap = builder2.createNewComponentUIDMap(components2 || []);
|
|
864
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
865
|
+
const newContentType = builder2.createContentType(replaceTmpUIDs(contentType));
|
|
866
|
+
const targetContentType = (infos) => {
|
|
867
|
+
Object.keys(infos.attributes).forEach((key) => {
|
|
868
|
+
const { target } = infos.attributes[key];
|
|
869
|
+
if (target === "__contentType__") {
|
|
870
|
+
infos.attributes[key].target = newContentType.uid;
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
return infos;
|
|
874
|
+
};
|
|
875
|
+
components2?.forEach((component) => {
|
|
876
|
+
const options2 = replaceTmpUIDs(targetContentType(component));
|
|
877
|
+
if (!_.has(component, "uid")) {
|
|
878
|
+
return builder2.createComponent(options2);
|
|
879
|
+
}
|
|
880
|
+
return builder2.editComponent(options2);
|
|
881
|
+
});
|
|
882
|
+
await generateAPI({
|
|
883
|
+
displayName: contentType.displayName || contentType.info.displayName,
|
|
884
|
+
singularName: contentType.singularName,
|
|
885
|
+
pluralName: contentType.pluralName,
|
|
886
|
+
kind: contentType.kind
|
|
887
|
+
});
|
|
888
|
+
if (!options.defaultBuilder) {
|
|
889
|
+
await builder2.writeFiles();
|
|
890
|
+
}
|
|
891
|
+
strapi.eventHub.emit("content-type.create", { contentType: newContentType });
|
|
892
|
+
return newContentType;
|
|
893
|
+
};
|
|
894
|
+
const generateAPI = ({
|
|
895
|
+
singularName,
|
|
896
|
+
kind = "collectionType",
|
|
897
|
+
pluralName,
|
|
898
|
+
displayName
|
|
899
|
+
}) => {
|
|
900
|
+
const strapiGenerators = require("@strapi/generators");
|
|
901
|
+
return strapiGenerators.generate(
|
|
902
|
+
"content-type",
|
|
903
|
+
{
|
|
904
|
+
kind,
|
|
905
|
+
singularName,
|
|
906
|
+
id: singularName,
|
|
907
|
+
pluralName,
|
|
908
|
+
displayName,
|
|
909
|
+
destination: "new",
|
|
910
|
+
bootstrapApi: true,
|
|
911
|
+
attributes: []
|
|
912
|
+
},
|
|
913
|
+
{ dir: strapi.dirs.app.root }
|
|
914
|
+
);
|
|
915
|
+
};
|
|
916
|
+
const editContentType = async (uid, { contentType, components: components2 = [] }) => {
|
|
917
|
+
const builder2 = createBuilder();
|
|
918
|
+
const previousSchema = builder2.contentTypes.get(uid).schema;
|
|
919
|
+
const previousKind = previousSchema.kind;
|
|
920
|
+
const newKind = contentType.kind || previousKind;
|
|
921
|
+
const previousAttributes = previousSchema.attributes;
|
|
922
|
+
const prevNonVisibleAttributes = contentTypes$2.getNonVisibleAttributes(previousSchema).reduce((acc, key) => {
|
|
923
|
+
if (key in previousAttributes) {
|
|
924
|
+
acc[key] = previousAttributes[key];
|
|
925
|
+
}
|
|
926
|
+
return acc;
|
|
927
|
+
}, {});
|
|
928
|
+
contentType.attributes = _.merge(prevNonVisibleAttributes, contentType.attributes);
|
|
929
|
+
if (newKind !== previousKind && newKind === "singleType") {
|
|
930
|
+
const entryCount = await strapi.db.query(uid).count();
|
|
931
|
+
if (entryCount > 1) {
|
|
932
|
+
throw new ApplicationError(
|
|
933
|
+
"You cannot convert a collectionType to a singleType when having multiple entries in DB"
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const uidMap = builder2.createNewComponentUIDMap(components2);
|
|
938
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
939
|
+
const updatedContentType = builder2.editContentType({
|
|
940
|
+
uid,
|
|
941
|
+
...replaceTmpUIDs(contentType)
|
|
942
|
+
});
|
|
943
|
+
components2.forEach((component) => {
|
|
944
|
+
if (!_.has(component, "uid")) {
|
|
945
|
+
return builder2.createComponent(replaceTmpUIDs(component));
|
|
946
|
+
}
|
|
947
|
+
return builder2.editComponent(replaceTmpUIDs(component));
|
|
948
|
+
});
|
|
949
|
+
if (newKind !== previousKind) {
|
|
950
|
+
const apiHandler2 = strapi.plugin("content-type-builder").service("api-handler");
|
|
951
|
+
await apiHandler2.backup(uid);
|
|
952
|
+
try {
|
|
953
|
+
await apiHandler2.clear(uid);
|
|
954
|
+
await generateAPI({
|
|
955
|
+
displayName: updatedContentType.schema.info.displayName,
|
|
956
|
+
singularName: updatedContentType.schema.info.singularName,
|
|
957
|
+
pluralName: updatedContentType.schema.info.pluralName,
|
|
958
|
+
kind: updatedContentType.schema.kind
|
|
959
|
+
});
|
|
960
|
+
await builder2.writeFiles();
|
|
961
|
+
} catch (error) {
|
|
962
|
+
strapi.log.error(error);
|
|
963
|
+
await apiHandler2.rollback(uid);
|
|
964
|
+
}
|
|
965
|
+
return updatedContentType;
|
|
966
|
+
}
|
|
967
|
+
await builder2.writeFiles();
|
|
968
|
+
strapi.eventHub.emit("content-type.update", { contentType: updatedContentType });
|
|
969
|
+
return updatedContentType;
|
|
970
|
+
};
|
|
971
|
+
const deleteContentTypes = async (uids) => {
|
|
972
|
+
const builder2 = createBuilder();
|
|
973
|
+
const apiHandler2 = strapi.plugin("content-type-builder").service("api-handler");
|
|
974
|
+
for (const uid of uids) {
|
|
975
|
+
await deleteContentType(uid, builder2);
|
|
976
|
+
}
|
|
977
|
+
await builder2.writeFiles();
|
|
978
|
+
for (const uid of uids) {
|
|
979
|
+
try {
|
|
980
|
+
await apiHandler2.clear(uid);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
strapi.log.error(error);
|
|
983
|
+
await apiHandler2.rollback(uid);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
const deleteContentType = async (uid, defaultBuilder = void 0) => {
|
|
988
|
+
const builder2 = defaultBuilder || createBuilder();
|
|
989
|
+
const apiHandler2 = strapi.plugin("content-type-builder").service("api-handler");
|
|
990
|
+
await apiHandler2.backup(uid);
|
|
991
|
+
const contentType = builder2.deleteContentType(uid);
|
|
992
|
+
if (!defaultBuilder) {
|
|
993
|
+
try {
|
|
994
|
+
await builder2.writeFiles();
|
|
995
|
+
await apiHandler2.clear(uid);
|
|
996
|
+
} catch (error) {
|
|
997
|
+
await apiHandler2.rollback(uid);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
strapi.eventHub.emit("content-type.delete", { contentType });
|
|
1001
|
+
return contentType;
|
|
1002
|
+
};
|
|
1003
|
+
const contentTypes$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1004
|
+
__proto__: null,
|
|
1005
|
+
createContentType,
|
|
1006
|
+
createContentTypes,
|
|
1007
|
+
deleteContentType,
|
|
1008
|
+
deleteContentTypes,
|
|
1009
|
+
editContentType,
|
|
1010
|
+
formatContentType,
|
|
1011
|
+
generateAPI,
|
|
1012
|
+
getRestrictRelationsTo,
|
|
1013
|
+
isContentTypeVisible
|
|
1014
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1015
|
+
const formatComponent = (component) => {
|
|
1016
|
+
const { uid, modelName, connection, collectionName, info, category } = component;
|
|
1017
|
+
return {
|
|
1018
|
+
uid,
|
|
1019
|
+
category,
|
|
1020
|
+
apiId: modelName,
|
|
1021
|
+
schema: {
|
|
1022
|
+
displayName: get(info, "displayName"),
|
|
1023
|
+
description: get(info, "description", ""),
|
|
1024
|
+
icon: get(info, "icon"),
|
|
1025
|
+
connection,
|
|
1026
|
+
collectionName,
|
|
1027
|
+
pluginOptions: component.pluginOptions,
|
|
1028
|
+
attributes: formatAttributes(component)
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
};
|
|
1032
|
+
const createComponent = async ({ component, components: components2 = [] }) => {
|
|
1033
|
+
const builder2 = createBuilder();
|
|
1034
|
+
const uidMap = builder2.createNewComponentUIDMap(components2);
|
|
1035
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
1036
|
+
const newComponent = builder2.createComponent(replaceTmpUIDs(component));
|
|
1037
|
+
components2.forEach((component2) => {
|
|
1038
|
+
if (!has(component2, "uid")) {
|
|
1039
|
+
return builder2.createComponent(replaceTmpUIDs(component2));
|
|
1040
|
+
}
|
|
1041
|
+
return builder2.editComponent(replaceTmpUIDs(component2));
|
|
1042
|
+
});
|
|
1043
|
+
await builder2.writeFiles();
|
|
1044
|
+
strapi.eventHub.emit("component.create", { component: newComponent });
|
|
1045
|
+
return newComponent;
|
|
1046
|
+
};
|
|
1047
|
+
const editComponent = async (uid, { component, components: components2 = [] }) => {
|
|
1048
|
+
const builder2 = createBuilder();
|
|
1049
|
+
const uidMap = builder2.createNewComponentUIDMap(components2);
|
|
1050
|
+
const replaceTmpUIDs = replaceTemporaryUIDs(uidMap);
|
|
1051
|
+
const updatedComponent = builder2.editComponent({
|
|
1052
|
+
uid,
|
|
1053
|
+
...replaceTmpUIDs(component)
|
|
1054
|
+
});
|
|
1055
|
+
components2.forEach((component2) => {
|
|
1056
|
+
if (!has(component2, "uid")) {
|
|
1057
|
+
return builder2.createComponent(replaceTmpUIDs(component2));
|
|
1058
|
+
}
|
|
1059
|
+
return builder2.editComponent(replaceTmpUIDs(component2));
|
|
1060
|
+
});
|
|
1061
|
+
await builder2.writeFiles();
|
|
1062
|
+
strapi.eventHub.emit("component.update", { component: updatedComponent });
|
|
1063
|
+
return updatedComponent;
|
|
1064
|
+
};
|
|
1065
|
+
const deleteComponent = async (uid) => {
|
|
1066
|
+
const builder2 = createBuilder();
|
|
1067
|
+
const deletedComponent = builder2.deleteComponent(uid);
|
|
1068
|
+
await builder2.writeFiles();
|
|
1069
|
+
strapi.eventHub.emit("component.delete", { component: deletedComponent });
|
|
1070
|
+
return deletedComponent;
|
|
1071
|
+
};
|
|
1072
|
+
const components$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1073
|
+
__proto__: null,
|
|
1074
|
+
createComponent,
|
|
1075
|
+
deleteComponent,
|
|
1076
|
+
editComponent,
|
|
1077
|
+
formatComponent
|
|
1078
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1079
|
+
const editCategory = async (name, infos) => {
|
|
1080
|
+
const newName = strings.nameToSlug(infos.name);
|
|
1081
|
+
if (name === newName)
|
|
1082
|
+
return;
|
|
1083
|
+
if (!categoryExists(name)) {
|
|
1084
|
+
throw new errors.ApplicationError("category not found");
|
|
1085
|
+
}
|
|
1086
|
+
if (categoryExists(newName)) {
|
|
1087
|
+
throw new errors.ApplicationError("Name already taken");
|
|
1088
|
+
}
|
|
1089
|
+
const builder2 = createBuilder();
|
|
1090
|
+
builder2.components.forEach((component) => {
|
|
1091
|
+
const oldUID = component.uid;
|
|
1092
|
+
const newUID = `${newName}.${component.modelName}`;
|
|
1093
|
+
if (component.category !== name)
|
|
1094
|
+
return;
|
|
1095
|
+
component.setUID(newUID).setDir(join(strapi.dirs.app.components, newName));
|
|
1096
|
+
builder2.components.forEach((compo) => {
|
|
1097
|
+
compo.updateComponent(oldUID, newUID);
|
|
1098
|
+
});
|
|
1099
|
+
builder2.contentTypes.forEach((ct) => {
|
|
1100
|
+
ct.updateComponent(oldUID, newUID);
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
await builder2.writeFiles();
|
|
1104
|
+
return newName;
|
|
1105
|
+
};
|
|
1106
|
+
const deleteCategory = async (name) => {
|
|
1107
|
+
if (!categoryExists(name)) {
|
|
1108
|
+
throw new errors.ApplicationError("category not found");
|
|
1109
|
+
}
|
|
1110
|
+
const builder2 = createBuilder();
|
|
1111
|
+
builder2.components.forEach((component) => {
|
|
1112
|
+
if (component.category === name) {
|
|
1113
|
+
builder2.deleteComponent(component.uid);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
await builder2.writeFiles();
|
|
1117
|
+
};
|
|
1118
|
+
const categoryExists = (name) => {
|
|
1119
|
+
const matchingIndex = Object.values(strapi.components).findIndex(
|
|
1120
|
+
(component) => component.category === name
|
|
1121
|
+
);
|
|
1122
|
+
return matchingIndex > -1;
|
|
1123
|
+
};
|
|
1124
|
+
const componentCategories$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1125
|
+
__proto__: null,
|
|
1126
|
+
deleteCategory,
|
|
1127
|
+
editCategory
|
|
1128
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1129
|
+
const getReservedNames = () => {
|
|
1130
|
+
return {
|
|
1131
|
+
// use kebab case everywhere since singularName and pluralName are validated that way
|
|
1132
|
+
models: [
|
|
1133
|
+
"boolean",
|
|
1134
|
+
"date",
|
|
1135
|
+
"date-time",
|
|
1136
|
+
"time",
|
|
1137
|
+
"upload",
|
|
1138
|
+
"document",
|
|
1139
|
+
"then"
|
|
1140
|
+
// no longer an issue but still restricting for being a javascript keyword
|
|
1141
|
+
],
|
|
1142
|
+
// attributes are compared with snake_case(name), so only snake_case is needed here and camelCase + UPPER_CASE matches will still be caught
|
|
1143
|
+
attributes: [
|
|
1144
|
+
// TODO: these need to come from a centralized place so we don't break things accidentally in the future and can share them outside the CTB, for example on Strapi bootstrap prior to schema db sync
|
|
1145
|
+
// ID fields
|
|
1146
|
+
"id",
|
|
1147
|
+
"document_id",
|
|
1148
|
+
// Creator fields
|
|
1149
|
+
"created_at",
|
|
1150
|
+
"updated_at",
|
|
1151
|
+
"published_at",
|
|
1152
|
+
"created_by_id",
|
|
1153
|
+
"updated_by_id",
|
|
1154
|
+
// does not actually conflict because the fields are called *_by_id but we'll leave it to avoid confusion
|
|
1155
|
+
"created_by",
|
|
1156
|
+
"updated_by",
|
|
1157
|
+
// Used for Strapi functionality
|
|
1158
|
+
"entry_id",
|
|
1159
|
+
"status",
|
|
1160
|
+
"localizations",
|
|
1161
|
+
"meta",
|
|
1162
|
+
"locale",
|
|
1163
|
+
// TODO: remove these in favor of restricting the strapi_ prefix
|
|
1164
|
+
"strapi",
|
|
1165
|
+
"strapi_stage",
|
|
1166
|
+
"strapi_assignee"
|
|
1167
|
+
]
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1170
|
+
const builder$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1171
|
+
__proto__: null,
|
|
1172
|
+
getReservedNames
|
|
1173
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1174
|
+
async function clear(uid) {
|
|
1175
|
+
const { apiName, modelName } = strapi.contentTypes[uid];
|
|
1176
|
+
const apiFolder = path.join(strapi.dirs.app.api, apiName);
|
|
1177
|
+
await recursiveRemoveFiles(apiFolder, createDeleteApiFunction(modelName));
|
|
1178
|
+
await deleteBackup(uid);
|
|
1179
|
+
}
|
|
1180
|
+
async function backup(uid) {
|
|
1181
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
1182
|
+
const apiFolder = path.join(strapi.dirs.app.api, apiName);
|
|
1183
|
+
const backupFolder = path.join(strapi.dirs.app.api, ".backup", apiName);
|
|
1184
|
+
await fse.copy(apiFolder, backupFolder);
|
|
1185
|
+
}
|
|
1186
|
+
async function deleteBackup(uid) {
|
|
1187
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
1188
|
+
const backupFolder = path.join(strapi.dirs.app.api, ".backup");
|
|
1189
|
+
const apiBackupFolder = path.join(strapi.dirs.app.api, ".backup", apiName);
|
|
1190
|
+
await fse.remove(apiBackupFolder);
|
|
1191
|
+
const list = await fse.readdir(backupFolder);
|
|
1192
|
+
if (list.length === 0) {
|
|
1193
|
+
await fse.remove(backupFolder);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
async function rollback(uid) {
|
|
1197
|
+
const { apiName } = strapi.contentTypes[uid];
|
|
1198
|
+
const apiFolder = path.join(strapi.dirs.app.api, apiName);
|
|
1199
|
+
const backupFolder = path.join(strapi.dirs.app.api, ".backup", apiName);
|
|
1200
|
+
try {
|
|
1201
|
+
await fse.access(backupFolder);
|
|
1202
|
+
} catch {
|
|
1203
|
+
throw new Error("Cannot rollback api that was not backed up");
|
|
1204
|
+
}
|
|
1205
|
+
await fse.remove(apiFolder);
|
|
1206
|
+
await fse.copy(backupFolder, apiFolder);
|
|
1207
|
+
await deleteBackup(uid);
|
|
1208
|
+
}
|
|
1209
|
+
const createDeleteApiFunction = (baseName) => {
|
|
1210
|
+
return async (filePath) => {
|
|
1211
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
1212
|
+
const isSchemaFile = filePath.endsWith(`${baseName}/schema.json`);
|
|
1213
|
+
if (fileName === baseName || isSchemaFile) {
|
|
1214
|
+
return fse.remove(filePath);
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
};
|
|
1218
|
+
const recursiveRemoveFiles = async (folder, deleteFn) => {
|
|
1219
|
+
const filesName = await fse.readdir(folder);
|
|
1220
|
+
for (const fileName of filesName) {
|
|
1221
|
+
const filePath = path.join(folder, fileName);
|
|
1222
|
+
const stat = await fse.stat(filePath);
|
|
1223
|
+
if (stat.isDirectory()) {
|
|
1224
|
+
await recursiveRemoveFiles(filePath, deleteFn);
|
|
1225
|
+
} else {
|
|
1226
|
+
await deleteFn(filePath);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const files = await fse.readdir(folder);
|
|
1230
|
+
if (files.length === 0) {
|
|
1231
|
+
await fse.remove(folder);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
const apiHandler = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1235
|
+
__proto__: null,
|
|
1236
|
+
backup,
|
|
1237
|
+
clear,
|
|
1238
|
+
rollback
|
|
1239
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1240
|
+
const services = {
|
|
1241
|
+
"content-types": contentTypes$1,
|
|
1242
|
+
components: components$1,
|
|
1243
|
+
"component-categories": componentCategories$1,
|
|
1244
|
+
builder: builder$1,
|
|
1245
|
+
"api-handler": apiHandler
|
|
1246
|
+
};
|
|
1247
|
+
function getService(name) {
|
|
1248
|
+
return strapi.plugin("content-type-builder").service(name);
|
|
1249
|
+
}
|
|
1250
|
+
const builder = {
|
|
1251
|
+
getReservedNames(ctx) {
|
|
1252
|
+
ctx.body = getService("builder").getReservedNames();
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
const validators = {
|
|
1256
|
+
required: yup.boolean(),
|
|
1257
|
+
unique: yup.boolean(),
|
|
1258
|
+
minLength: yup.number().integer().positive(),
|
|
1259
|
+
maxLength: yup.number().integer().positive()
|
|
1260
|
+
};
|
|
1261
|
+
const NAME_REGEX = /^[A-Za-z][_0-9A-Za-z]*$/;
|
|
1262
|
+
const COLLECTION_NAME_REGEX = /^[A-Za-z][-_0-9A-Za-z]*$/;
|
|
1263
|
+
const CATEGORY_NAME_REGEX = /^[A-Za-z][-_0-9A-Za-z]*$/;
|
|
1264
|
+
const ICON_REGEX = /^[A-Za-z0-9][-A-Za-z0-9]*$/;
|
|
1265
|
+
const UID_REGEX = /^[A-Za-z0-9-_.~]*$/;
|
|
1266
|
+
const isValidName = {
|
|
1267
|
+
name: "isValidName",
|
|
1268
|
+
message: `\${path} must match the following regex: ${NAME_REGEX}`,
|
|
1269
|
+
test: (val) => val === "" || NAME_REGEX.test(val)
|
|
1270
|
+
};
|
|
1271
|
+
const isValidIcon = {
|
|
1272
|
+
name: "isValidIcon",
|
|
1273
|
+
message: `\${path} is not a valid icon name. Make sure your icon name starts with an alphanumeric character and only includes alphanumeric characters or dashes.`,
|
|
1274
|
+
test: (val) => val === "" || ICON_REGEX.test(val)
|
|
1275
|
+
};
|
|
1276
|
+
const isValidUID = {
|
|
1277
|
+
name: "isValidUID",
|
|
1278
|
+
message: `\${path} must match the following regex: ${UID_REGEX}`,
|
|
1279
|
+
test: (val) => val === "" || UID_REGEX.test(val)
|
|
1280
|
+
};
|
|
1281
|
+
const isValidCategoryName = {
|
|
1282
|
+
name: "isValidCategoryName",
|
|
1283
|
+
message: `\${path} must match the following regex: ${CATEGORY_NAME_REGEX}`,
|
|
1284
|
+
test: (val) => val === "" || CATEGORY_NAME_REGEX.test(val)
|
|
1285
|
+
};
|
|
1286
|
+
const isValidCollectionName = {
|
|
1287
|
+
name: "isValidCollectionName",
|
|
1288
|
+
message: `\${path} must match the following regex: ${COLLECTION_NAME_REGEX}`,
|
|
1289
|
+
test: (val) => val === "" || COLLECTION_NAME_REGEX.test(val)
|
|
1290
|
+
};
|
|
1291
|
+
const isValidKey = (key) => ({
|
|
1292
|
+
name: "isValidKey",
|
|
1293
|
+
message: `Attribute name '${key}' must match the following regex: ${NAME_REGEX}`,
|
|
1294
|
+
test: () => NAME_REGEX.test(key)
|
|
1295
|
+
});
|
|
1296
|
+
const isValidEnum = {
|
|
1297
|
+
name: "isValidEnum",
|
|
1298
|
+
message: "${path} should not start with number",
|
|
1299
|
+
test: (val) => val === "" || !strings.startsWithANumber(val)
|
|
1300
|
+
};
|
|
1301
|
+
const areEnumValuesUnique = {
|
|
1302
|
+
name: "areEnumValuesUnique",
|
|
1303
|
+
message: "${path} cannot contain duplicate values",
|
|
1304
|
+
test(values) {
|
|
1305
|
+
const filtered = [...new Set(values)];
|
|
1306
|
+
return filtered.length === values.length;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
const isValidRegExpPattern = {
|
|
1310
|
+
name: "isValidRegExpPattern",
|
|
1311
|
+
message: "${path} must be a valid RexExp pattern string",
|
|
1312
|
+
test: (val) => val === "" || !!new RegExp(val)
|
|
1313
|
+
};
|
|
1314
|
+
const isValidDefaultJSON = {
|
|
1315
|
+
name: "isValidDefaultJSON",
|
|
1316
|
+
message: "${path} is not a valid JSON",
|
|
1317
|
+
test(val) {
|
|
1318
|
+
if (val === void 0) {
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
if (_.isNumber(val) || _.isNull(val) || _.isObject(val) || _.isArray(val)) {
|
|
1322
|
+
return true;
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
JSON.parse(val);
|
|
1326
|
+
return true;
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
const componentCategorySchema = yup.object({
|
|
1333
|
+
name: yup.string().min(3).test(isValidCategoryName).required("name.required")
|
|
1334
|
+
}).noUnknown();
|
|
1335
|
+
const validateComponentCategory = validateYupSchema(componentCategorySchema);
|
|
1336
|
+
const componentCategories = {
|
|
1337
|
+
async editCategory(ctx) {
|
|
1338
|
+
const body = ctx.request.body;
|
|
1339
|
+
try {
|
|
1340
|
+
await validateComponentCategory(body);
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
return ctx.send({ error }, 400);
|
|
1343
|
+
}
|
|
1344
|
+
const { name } = ctx.params;
|
|
1345
|
+
strapi.reload.isWatching = false;
|
|
1346
|
+
const componentCategoryService = getService("component-categories");
|
|
1347
|
+
const newName = await componentCategoryService.editCategory(name, body);
|
|
1348
|
+
setImmediate(() => strapi.reload());
|
|
1349
|
+
ctx.send({ name: newName });
|
|
1350
|
+
},
|
|
1351
|
+
async deleteCategory(ctx) {
|
|
1352
|
+
const { name } = ctx.params;
|
|
1353
|
+
strapi.reload.isWatching = false;
|
|
1354
|
+
const componentCategoryService = getService("component-categories");
|
|
1355
|
+
await componentCategoryService.deleteCategory(name);
|
|
1356
|
+
setImmediate(() => strapi.reload());
|
|
1357
|
+
ctx.send({ name });
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
const maxLengthIsGreaterThanOrEqualToMinLength = {
|
|
1361
|
+
name: "isGreaterThanMin",
|
|
1362
|
+
message: "maxLength must be greater or equal to minLength",
|
|
1363
|
+
test(value) {
|
|
1364
|
+
const { minLength } = this.parent;
|
|
1365
|
+
return !(!_.isUndefined(minLength) && !_.isUndefined(value) && value < minLength);
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
const getTypeValidator = (attribute, { types, modelType, attributes }) => {
|
|
1369
|
+
return yup.object({
|
|
1370
|
+
type: yup.string().oneOf([...types]).required(),
|
|
1371
|
+
configurable: yup.boolean().nullable(),
|
|
1372
|
+
private: yup.boolean().nullable(),
|
|
1373
|
+
pluginOptions: yup.object(),
|
|
1374
|
+
...getTypeShape(attribute, { modelType, attributes })
|
|
1375
|
+
});
|
|
1376
|
+
};
|
|
1377
|
+
const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|
1378
|
+
switch (attribute.type) {
|
|
1379
|
+
case "media": {
|
|
1380
|
+
return {
|
|
1381
|
+
multiple: yup.boolean(),
|
|
1382
|
+
required: validators.required,
|
|
1383
|
+
allowedTypes: yup.array().of(yup.string().oneOf(["images", "videos", "files", "audios"])).min(1)
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
case "uid": {
|
|
1387
|
+
return {
|
|
1388
|
+
required: validators.required,
|
|
1389
|
+
targetField: yup.string().oneOf(
|
|
1390
|
+
Object.keys(attributes).filter(
|
|
1391
|
+
(key) => VALID_UID_TARGETS.includes(_.get(attributes[key], "type"))
|
|
1392
|
+
)
|
|
1393
|
+
).nullable(),
|
|
1394
|
+
default: yup.string().test(
|
|
1395
|
+
"isValidDefaultUID",
|
|
1396
|
+
"cannot define a default UID if the targetField is set",
|
|
1397
|
+
function(value) {
|
|
1398
|
+
const { targetField } = this.parent;
|
|
1399
|
+
return !!(_.isNil(targetField) || _.isNil(value));
|
|
1400
|
+
}
|
|
1401
|
+
).test(isValidUID),
|
|
1402
|
+
minLength: validators.minLength,
|
|
1403
|
+
maxLength: validators.maxLength.max(256).test(maxLengthIsGreaterThanOrEqualToMinLength),
|
|
1404
|
+
options: yup.object().shape({
|
|
1405
|
+
separator: yup.string(),
|
|
1406
|
+
lowercase: yup.boolean(),
|
|
1407
|
+
decamelize: yup.boolean(),
|
|
1408
|
+
customReplacements: yup.array().of(yup.array().of(yup.string()).min(2).max(2)),
|
|
1409
|
+
preserveLeadingUnderscore: yup.boolean()
|
|
1410
|
+
})
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
case "string":
|
|
1414
|
+
case "text": {
|
|
1415
|
+
return {
|
|
1416
|
+
default: yup.string(),
|
|
1417
|
+
required: validators.required,
|
|
1418
|
+
unique: validators.unique,
|
|
1419
|
+
minLength: validators.minLength,
|
|
1420
|
+
maxLength: validators.maxLength,
|
|
1421
|
+
regex: yup.string().test(isValidRegExpPattern)
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
case "richtext": {
|
|
1425
|
+
return {
|
|
1426
|
+
default: yup.string(),
|
|
1427
|
+
required: validators.required,
|
|
1428
|
+
minLength: validators.minLength,
|
|
1429
|
+
maxLength: validators.maxLength
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
case "blocks": {
|
|
1433
|
+
return {
|
|
1434
|
+
required: validators.required
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
case "json": {
|
|
1438
|
+
return {
|
|
1439
|
+
default: yup.mixed().test(isValidDefaultJSON),
|
|
1440
|
+
required: validators.required
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
case "enumeration": {
|
|
1444
|
+
return {
|
|
1445
|
+
enum: yup.array().of(yup.string().test(isValidEnum).required()).min(1).test(areEnumValuesUnique).required(),
|
|
1446
|
+
default: yup.string().when("enum", (enumVal) => yup.string().oneOf(enumVal)),
|
|
1447
|
+
enumName: yup.string().test(isValidName),
|
|
1448
|
+
required: validators.required
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
case "password": {
|
|
1452
|
+
return {
|
|
1453
|
+
required: validators.required,
|
|
1454
|
+
minLength: validators.minLength,
|
|
1455
|
+
maxLength: validators.maxLength
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
case "email": {
|
|
1459
|
+
return {
|
|
1460
|
+
default: yup.string().email(),
|
|
1461
|
+
required: validators.required,
|
|
1462
|
+
unique: validators.unique,
|
|
1463
|
+
minLength: validators.minLength,
|
|
1464
|
+
maxLength: validators.maxLength
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
case "integer": {
|
|
1468
|
+
return {
|
|
1469
|
+
default: yup.number().integer(),
|
|
1470
|
+
required: validators.required,
|
|
1471
|
+
unique: validators.unique,
|
|
1472
|
+
min: yup.number().integer(),
|
|
1473
|
+
max: yup.number().integer()
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
case "biginteger": {
|
|
1477
|
+
return {
|
|
1478
|
+
default: yup.string().nullable().matches(/^\d*$/),
|
|
1479
|
+
required: validators.required,
|
|
1480
|
+
unique: validators.unique,
|
|
1481
|
+
min: yup.string().nullable().matches(/^\d*$/),
|
|
1482
|
+
max: yup.string().nullable().matches(/^\d*$/)
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
case "float": {
|
|
1486
|
+
return {
|
|
1487
|
+
default: yup.number(),
|
|
1488
|
+
required: validators.required,
|
|
1489
|
+
unique: validators.unique,
|
|
1490
|
+
min: yup.number(),
|
|
1491
|
+
max: yup.number()
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
case "decimal": {
|
|
1495
|
+
return {
|
|
1496
|
+
default: yup.number(),
|
|
1497
|
+
required: validators.required,
|
|
1498
|
+
unique: validators.unique,
|
|
1499
|
+
min: yup.number(),
|
|
1500
|
+
max: yup.number()
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
case "time":
|
|
1504
|
+
case "datetime":
|
|
1505
|
+
case "date": {
|
|
1506
|
+
return {
|
|
1507
|
+
default: yup.string(),
|
|
1508
|
+
required: validators.required,
|
|
1509
|
+
unique: validators.unique
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
case "boolean": {
|
|
1513
|
+
return {
|
|
1514
|
+
default: yup.boolean(),
|
|
1515
|
+
required: validators.required
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
case "component": {
|
|
1519
|
+
return {
|
|
1520
|
+
required: validators.required,
|
|
1521
|
+
repeatable: yup.boolean(),
|
|
1522
|
+
component: yup.string().test({
|
|
1523
|
+
name: "Check max component nesting is 1 lvl",
|
|
1524
|
+
test(compoUID) {
|
|
1525
|
+
const targetCompo = strapi.components[compoUID];
|
|
1526
|
+
if (!targetCompo)
|
|
1527
|
+
return true;
|
|
1528
|
+
if (modelType === modelTypes.COMPONENT && hasComponent(targetCompo)) {
|
|
1529
|
+
return this.createError({
|
|
1530
|
+
path: this.path,
|
|
1531
|
+
message: `${targetCompo.modelName} already is a nested component. You cannot have more than one level of nesting inside your components.`
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
}).required(),
|
|
1537
|
+
min: yup.number(),
|
|
1538
|
+
max: yup.number()
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
case "dynamiczone": {
|
|
1542
|
+
return {
|
|
1543
|
+
required: validators.required,
|
|
1544
|
+
components: yup.array().of(yup.string().required()).test("isArray", "${path} must be an array", (value) => Array.isArray(value)).min(1),
|
|
1545
|
+
min: yup.number(),
|
|
1546
|
+
max: yup.number()
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
default: {
|
|
1550
|
+
return {};
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
const STRAPI_USER_RELATIONS = ["oneToOne", "oneToMany"];
|
|
1555
|
+
const isValidRelation = (validNatures) => function(value) {
|
|
1556
|
+
if (value === void 0) {
|
|
1557
|
+
return true;
|
|
1558
|
+
}
|
|
1559
|
+
if (this.parent.target === coreUids.STRAPI_USER) {
|
|
1560
|
+
if (!validNatures.includes(value) || !isUndefined(this.parent.targetAttribute)) {
|
|
1561
|
+
return this.createError({
|
|
1562
|
+
path: this.path,
|
|
1563
|
+
message: `must be one of the following values: ${STRAPI_USER_RELATIONS.join(", ")}`
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return validNatures.includes(value) ? true : this.createError({
|
|
1568
|
+
path: this.path,
|
|
1569
|
+
message: `must be one of the following values: ${validNatures.join(", ")}`
|
|
1570
|
+
});
|
|
1571
|
+
};
|
|
1572
|
+
const getRelationValidator = (attribute, allowedRelations) => {
|
|
1573
|
+
const contentTypesUIDs = Object.keys(strapi.contentTypes).filter((key) => strapi.contentTypes[key].kind === typeKinds.COLLECTION_TYPE).filter((key) => !key.startsWith(coreUids.PREFIX) || key === coreUids.STRAPI_USER).concat(["__self__", "__contentType__"]);
|
|
1574
|
+
const base = {
|
|
1575
|
+
type: yup.string().oneOf(["relation"]).required(),
|
|
1576
|
+
relation: yup.string().test("isValidRelation", isValidRelation(allowedRelations)).required(),
|
|
1577
|
+
configurable: yup.boolean().nullable(),
|
|
1578
|
+
private: yup.boolean().nullable(),
|
|
1579
|
+
pluginOptions: yup.object()
|
|
1580
|
+
};
|
|
1581
|
+
switch (attribute.relation) {
|
|
1582
|
+
case "oneToOne":
|
|
1583
|
+
case "oneToMany":
|
|
1584
|
+
case "manyToOne":
|
|
1585
|
+
case "manyToMany":
|
|
1586
|
+
case "morphOne":
|
|
1587
|
+
case "morphMany": {
|
|
1588
|
+
return yup.object({
|
|
1589
|
+
...base,
|
|
1590
|
+
target: yup.string().oneOf(contentTypesUIDs).required(),
|
|
1591
|
+
targetAttribute: yup.string().test(isValidName).nullable()
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
case "morphToOne":
|
|
1595
|
+
case "morphToMany":
|
|
1596
|
+
default: {
|
|
1597
|
+
return yup.object({ ...base });
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
const createSchema = (types, relations, { modelType } = {}) => {
|
|
1602
|
+
const shape = {
|
|
1603
|
+
description: yup.string(),
|
|
1604
|
+
options: yup.object(),
|
|
1605
|
+
pluginOptions: yup.object(),
|
|
1606
|
+
collectionName: yup.string().nullable().test(isValidCollectionName),
|
|
1607
|
+
attributes: createAttributesValidator({ types, relations, modelType }),
|
|
1608
|
+
reviewWorkflows: yup.boolean(),
|
|
1609
|
+
draftAndPublish: yup.boolean()
|
|
1610
|
+
};
|
|
1611
|
+
if (modelType === modelTypes.CONTENT_TYPE) {
|
|
1612
|
+
shape.kind = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]).nullable();
|
|
1613
|
+
}
|
|
1614
|
+
return yup.object(shape).noUnknown();
|
|
1615
|
+
};
|
|
1616
|
+
const createAttributesValidator = ({ types, modelType, relations }) => {
|
|
1617
|
+
return yup.lazy((attributes) => {
|
|
1618
|
+
return yup.object().shape(
|
|
1619
|
+
_.mapValues(attributes, (attribute, key) => {
|
|
1620
|
+
if (isForbiddenKey(key)) {
|
|
1621
|
+
return forbiddenValidator();
|
|
1622
|
+
}
|
|
1623
|
+
if (isConflictingKey(key, attributes)) {
|
|
1624
|
+
return conflictingKeysValidator(key);
|
|
1625
|
+
}
|
|
1626
|
+
if (attribute.type === "relation") {
|
|
1627
|
+
return getRelationValidator(attribute, relations).test(isValidKey(key));
|
|
1628
|
+
}
|
|
1629
|
+
if (_.has(attribute, "type")) {
|
|
1630
|
+
return getTypeValidator(attribute, { types, modelType, attributes }).test(
|
|
1631
|
+
isValidKey(key)
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
return typeOrRelationValidator;
|
|
1635
|
+
})
|
|
1636
|
+
).required("attributes.required");
|
|
1637
|
+
});
|
|
1638
|
+
};
|
|
1639
|
+
const isConflictingKey = (key, attributes) => {
|
|
1640
|
+
const snakeCaseKey = snakeCase(key);
|
|
1641
|
+
return Object.keys(attributes).some((existingKey) => {
|
|
1642
|
+
if (existingKey === key)
|
|
1643
|
+
return false;
|
|
1644
|
+
return snakeCase(existingKey) === snakeCaseKey;
|
|
1645
|
+
});
|
|
1646
|
+
};
|
|
1647
|
+
const isForbiddenKey = (key) => {
|
|
1648
|
+
const snakeCaseKey = snakeCase(key);
|
|
1649
|
+
const reservedNames = [
|
|
1650
|
+
...FORBIDDEN_ATTRIBUTE_NAMES,
|
|
1651
|
+
...getService("builder").getReservedNames().attributes
|
|
1652
|
+
];
|
|
1653
|
+
return reservedNames.some((reserved) => {
|
|
1654
|
+
return snakeCase(reserved) === snakeCaseKey;
|
|
1655
|
+
});
|
|
1656
|
+
};
|
|
1657
|
+
const forbiddenValidator = () => {
|
|
1658
|
+
const reservedNames = [
|
|
1659
|
+
...FORBIDDEN_ATTRIBUTE_NAMES,
|
|
1660
|
+
...getService("builder").getReservedNames().attributes
|
|
1661
|
+
];
|
|
1662
|
+
return yup.mixed().test({
|
|
1663
|
+
name: "forbiddenKeys",
|
|
1664
|
+
message: `Attribute keys cannot be one of ${reservedNames.join(", ")}`,
|
|
1665
|
+
test: () => false
|
|
1666
|
+
});
|
|
1667
|
+
};
|
|
1668
|
+
const conflictingKeysValidator = (key) => {
|
|
1669
|
+
return yup.mixed().test({
|
|
1670
|
+
name: "conflictingKeys",
|
|
1671
|
+
message: `Attribute ${key} conflicts with an existing key`,
|
|
1672
|
+
test: () => false
|
|
1673
|
+
});
|
|
1674
|
+
};
|
|
1675
|
+
const typeOrRelationValidator = yup.object().test({
|
|
1676
|
+
name: "mustHaveTypeOrTarget",
|
|
1677
|
+
message: "Attribute must have either a type or a target",
|
|
1678
|
+
test: () => false
|
|
1679
|
+
});
|
|
1680
|
+
const hasDefaultAttribute = (attribute) => {
|
|
1681
|
+
return "default" in attribute;
|
|
1682
|
+
};
|
|
1683
|
+
const removeEmptyDefaults = (data) => {
|
|
1684
|
+
const { attributes } = data || {};
|
|
1685
|
+
Object.keys(attributes).forEach((attributeName) => {
|
|
1686
|
+
const attribute = attributes[attributeName];
|
|
1687
|
+
if (hasDefaultAttribute(attribute) && attribute.default === "") {
|
|
1688
|
+
attribute.default = void 0;
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
};
|
|
1692
|
+
const removeDeletedUIDTargetFields = (data) => {
|
|
1693
|
+
if (_.has(data, "attributes")) {
|
|
1694
|
+
Object.values(data.attributes).forEach((attribute) => {
|
|
1695
|
+
if (attribute.type === "uid" && !_.isUndefined(attribute.targetField) && !_.has(data.attributes, attribute.targetField)) {
|
|
1696
|
+
attribute.targetField = void 0;
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
const VALID_RELATIONS$1 = ["oneToOne", "oneToMany"];
|
|
1702
|
+
const VALID_TYPES$1 = [...DEFAULT_TYPES, "component", "customField"];
|
|
1703
|
+
const componentSchema = createSchema(VALID_TYPES$1, VALID_RELATIONS$1, {
|
|
1704
|
+
modelType: modelTypes.COMPONENT
|
|
1705
|
+
}).shape({
|
|
1706
|
+
displayName: yup.string().min(1).required("displayName.required"),
|
|
1707
|
+
icon: yup.string().nullable().test(isValidIcon),
|
|
1708
|
+
category: yup.string().nullable().test(isValidCategoryName).required("category.required")
|
|
1709
|
+
}).required().noUnknown();
|
|
1710
|
+
const nestedComponentSchema = yup.array().of(
|
|
1711
|
+
componentSchema.shape({
|
|
1712
|
+
uid: yup.string(),
|
|
1713
|
+
tmpUID: yup.string()
|
|
1714
|
+
}).test({
|
|
1715
|
+
name: "mustHaveUIDOrTmpUID",
|
|
1716
|
+
message: "Component must have a uid or a tmpUID",
|
|
1717
|
+
test(attr) {
|
|
1718
|
+
if (_.has(attr, "uid") && _.has(attr, "tmpUID"))
|
|
1719
|
+
return false;
|
|
1720
|
+
if (!_.has(attr, "uid") && !_.has(attr, "tmpUID"))
|
|
1721
|
+
return false;
|
|
1722
|
+
return true;
|
|
1723
|
+
}
|
|
1724
|
+
}).required().noUnknown()
|
|
1725
|
+
);
|
|
1726
|
+
const componentInputSchema = yup.object({
|
|
1727
|
+
component: componentSchema,
|
|
1728
|
+
components: nestedComponentSchema
|
|
1729
|
+
}).noUnknown();
|
|
1730
|
+
const validateComponentInput = validateYupSchema(componentInputSchema);
|
|
1731
|
+
const updateComponentInputSchema = yup.object({
|
|
1732
|
+
component: componentSchema,
|
|
1733
|
+
components: nestedComponentSchema
|
|
1734
|
+
}).noUnknown();
|
|
1735
|
+
const validateUpdateComponentInput = (data) => {
|
|
1736
|
+
if (_.has(data, "component") && data.component) {
|
|
1737
|
+
removeEmptyDefaults(data.component);
|
|
1738
|
+
}
|
|
1739
|
+
if (_.has(data, "components") && Array.isArray(data.components)) {
|
|
1740
|
+
data.components.forEach((data2) => {
|
|
1741
|
+
if (_.has(data2, "uid")) {
|
|
1742
|
+
removeEmptyDefaults(data2);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
return validateYupSchema(updateComponentInputSchema)(data);
|
|
1747
|
+
};
|
|
1748
|
+
const components = {
|
|
1749
|
+
/**
|
|
1750
|
+
* GET /components handler
|
|
1751
|
+
* Returns a list of available components
|
|
1752
|
+
* @param {Object} ctx - koa context
|
|
1753
|
+
*/
|
|
1754
|
+
async getComponents(ctx) {
|
|
1755
|
+
const componentService = getService("components");
|
|
1756
|
+
const componentUIDs = Object.keys(strapi.components);
|
|
1757
|
+
const data = componentUIDs.map((uid) => {
|
|
1758
|
+
return componentService.formatComponent(strapi.components[uid]);
|
|
1759
|
+
});
|
|
1760
|
+
ctx.send({ data });
|
|
1761
|
+
},
|
|
1762
|
+
/**
|
|
1763
|
+
* GET /components/:uid
|
|
1764
|
+
* Returns a specific component
|
|
1765
|
+
* @param {Object} ctx - koa context
|
|
1766
|
+
*/
|
|
1767
|
+
async getComponent(ctx) {
|
|
1768
|
+
const { uid } = ctx.params;
|
|
1769
|
+
const component = strapi.components[uid];
|
|
1770
|
+
if (!component) {
|
|
1771
|
+
return ctx.send({ error: "component.notFound" }, 404);
|
|
1772
|
+
}
|
|
1773
|
+
const componentService = getService("components");
|
|
1774
|
+
ctx.send({ data: componentService.formatComponent(component) });
|
|
1775
|
+
},
|
|
1776
|
+
/**
|
|
1777
|
+
* POST /components
|
|
1778
|
+
* Creates a component and returns its infos
|
|
1779
|
+
* @param {Object} ctx - koa context
|
|
1780
|
+
*/
|
|
1781
|
+
async createComponent(ctx) {
|
|
1782
|
+
const body = ctx.request.body;
|
|
1783
|
+
try {
|
|
1784
|
+
await validateComponentInput(body);
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
return ctx.send({ error }, 400);
|
|
1787
|
+
}
|
|
1788
|
+
try {
|
|
1789
|
+
strapi.reload.isWatching = false;
|
|
1790
|
+
const componentService = getService("components");
|
|
1791
|
+
const component = await componentService.createComponent({
|
|
1792
|
+
component: body.component,
|
|
1793
|
+
components: body.components
|
|
1794
|
+
});
|
|
1795
|
+
setImmediate(() => strapi.reload());
|
|
1796
|
+
ctx.send({ data: { uid: component.uid } }, 201);
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
strapi.log.error(error);
|
|
1799
|
+
ctx.send({ error: error?.message || "Unknown error" }, 400);
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
/**
|
|
1803
|
+
* PUT /components/:uid
|
|
1804
|
+
* Updates a component and return its infos
|
|
1805
|
+
* @param {Object} ctx - koa context - enhanced koa context
|
|
1806
|
+
*/
|
|
1807
|
+
async updateComponent(ctx) {
|
|
1808
|
+
const { uid } = ctx.params;
|
|
1809
|
+
const body = ctx.request.body;
|
|
1810
|
+
if (!_.has(strapi.components, uid)) {
|
|
1811
|
+
return ctx.send({ error: "component.notFound" }, 404);
|
|
1812
|
+
}
|
|
1813
|
+
try {
|
|
1814
|
+
await validateUpdateComponentInput(body);
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
return ctx.send({ error }, 400);
|
|
1817
|
+
}
|
|
1818
|
+
try {
|
|
1819
|
+
strapi.reload.isWatching = false;
|
|
1820
|
+
const componentService = getService("components");
|
|
1821
|
+
const component = await componentService.editComponent(uid, {
|
|
1822
|
+
component: body.component,
|
|
1823
|
+
components: body.components
|
|
1824
|
+
});
|
|
1825
|
+
setImmediate(() => strapi.reload());
|
|
1826
|
+
ctx.send({ data: { uid: component.uid } });
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
strapi.log.error(error);
|
|
1829
|
+
ctx.send({ error: error?.message || "Unknown error" }, 400);
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
/**
|
|
1833
|
+
* DELETE /components/:uid
|
|
1834
|
+
* Deletes a components and returns its old infos
|
|
1835
|
+
* @param {Object} ctx - koa context
|
|
1836
|
+
*/
|
|
1837
|
+
async deleteComponent(ctx) {
|
|
1838
|
+
const { uid } = ctx.params;
|
|
1839
|
+
if (!_.has(strapi.components, uid)) {
|
|
1840
|
+
return ctx.send({ error: "component.notFound" }, 404);
|
|
1841
|
+
}
|
|
1842
|
+
try {
|
|
1843
|
+
strapi.reload.isWatching = false;
|
|
1844
|
+
const componentService = getService("components");
|
|
1845
|
+
const component = await componentService.deleteComponent(uid);
|
|
1846
|
+
setImmediate(() => strapi.reload());
|
|
1847
|
+
ctx.send({ data: { uid: component.uid } });
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
strapi.log.error(error);
|
|
1850
|
+
ctx.send({ error: error?.message || "Unknown error" }, 400);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
const VALID_RELATIONS = {
|
|
1855
|
+
[typeKinds.SINGLE_TYPE]: [
|
|
1856
|
+
"oneToOne",
|
|
1857
|
+
"oneToMany",
|
|
1858
|
+
"morphOne",
|
|
1859
|
+
"morphMany",
|
|
1860
|
+
"morphToOne",
|
|
1861
|
+
"morphToMany"
|
|
1862
|
+
],
|
|
1863
|
+
[typeKinds.COLLECTION_TYPE]: [
|
|
1864
|
+
"oneToOne",
|
|
1865
|
+
"oneToMany",
|
|
1866
|
+
"manyToOne",
|
|
1867
|
+
"manyToMany",
|
|
1868
|
+
"morphOne",
|
|
1869
|
+
"morphMany",
|
|
1870
|
+
"morphToOne",
|
|
1871
|
+
"morphToMany"
|
|
1872
|
+
]
|
|
1873
|
+
};
|
|
1874
|
+
const VALID_TYPES = [...DEFAULT_TYPES, "uid", "component", "dynamiczone", "customField"];
|
|
1875
|
+
const createContentTypeSchema = (data, { isEdition = false } = {}) => {
|
|
1876
|
+
const kind = getOr(
|
|
1877
|
+
typeKinds.COLLECTION_TYPE,
|
|
1878
|
+
"contentType.kind",
|
|
1879
|
+
data
|
|
1880
|
+
);
|
|
1881
|
+
const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {
|
|
1882
|
+
modelType: modelTypes.CONTENT_TYPE
|
|
1883
|
+
}).shape({
|
|
1884
|
+
displayName: yup.string().min(1).required(),
|
|
1885
|
+
singularName: yup.string().min(1).test(nameIsAvailable(isEdition)).test(forbiddenContentTypeNameValidator()).isKebabCase().required(),
|
|
1886
|
+
pluralName: yup.string().min(1).test(nameIsAvailable(isEdition)).test(nameIsNotExistingCollectionName(isEdition)).test(forbiddenContentTypeNameValidator()).isKebabCase().required()
|
|
1887
|
+
}).test(
|
|
1888
|
+
"singularName-not-equal-pluralName",
|
|
1889
|
+
"${path}: singularName and pluralName should be different",
|
|
1890
|
+
(value) => value.singularName !== value.pluralName
|
|
1891
|
+
);
|
|
1892
|
+
return yup.object({
|
|
1893
|
+
// FIXME .noUnknown(false) will strip off the unwanted properties without throwing an error
|
|
1894
|
+
// Why not having .noUnknown() ? Because we want to be able to add options relatable to EE features
|
|
1895
|
+
// without having any reference to them in CE.
|
|
1896
|
+
// Why not handle an "options" object in the content-type ? The admin panel needs lots of rework
|
|
1897
|
+
// to be able to send this options object instead of top-level attributes.
|
|
1898
|
+
// @nathan-pichon 20/02/2023
|
|
1899
|
+
contentType: contentTypeSchema.required().noUnknown(false),
|
|
1900
|
+
components: nestedComponentSchema
|
|
1901
|
+
}).noUnknown();
|
|
1902
|
+
};
|
|
1903
|
+
const validateContentTypeInput = (data) => {
|
|
1904
|
+
return validateYupSchema(createContentTypeSchema(data))(data);
|
|
1905
|
+
};
|
|
1906
|
+
const validateUpdateContentTypeInput = (data) => {
|
|
1907
|
+
if (has$1("contentType", data)) {
|
|
1908
|
+
removeEmptyDefaults(data.contentType);
|
|
1909
|
+
removeDeletedUIDTargetFields(data.contentType);
|
|
1910
|
+
}
|
|
1911
|
+
if (has$1("components", data) && Array.isArray(data.components)) {
|
|
1912
|
+
data.components.forEach((comp) => {
|
|
1913
|
+
if (has$1("uid", comp)) {
|
|
1914
|
+
removeEmptyDefaults(comp);
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
return validateYupSchema(createContentTypeSchema(data, { isEdition: true }))(data);
|
|
1919
|
+
};
|
|
1920
|
+
const forbiddenContentTypeNameValidator = () => {
|
|
1921
|
+
const reservedNames = getService("builder").getReservedNames().models;
|
|
1922
|
+
return {
|
|
1923
|
+
name: "forbiddenContentTypeName",
|
|
1924
|
+
message: `Content Type name cannot be one of ${reservedNames.join(", ")}`,
|
|
1925
|
+
test(value) {
|
|
1926
|
+
if (typeof value !== "string") {
|
|
1927
|
+
return true;
|
|
1928
|
+
}
|
|
1929
|
+
return reservedNames.every((reservedName) => snakeCase(reservedName) !== snakeCase(value));
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
};
|
|
1933
|
+
const nameIsAvailable = (isEdition) => {
|
|
1934
|
+
const usedNames = flatMap((ct) => {
|
|
1935
|
+
return [ct.info?.singularName, ct.info?.pluralName];
|
|
1936
|
+
})(strapi.contentTypes);
|
|
1937
|
+
return {
|
|
1938
|
+
name: "nameAlreadyUsed",
|
|
1939
|
+
message: "contentType: name `${value}` is already being used by another content type.",
|
|
1940
|
+
test(value) {
|
|
1941
|
+
if (isEdition)
|
|
1942
|
+
return true;
|
|
1943
|
+
if (typeof value !== "string") {
|
|
1944
|
+
return true;
|
|
1945
|
+
}
|
|
1946
|
+
return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
};
|
|
1950
|
+
const nameIsNotExistingCollectionName = (isEdition) => {
|
|
1951
|
+
const usedNames = Object.keys(strapi.contentTypes).map(
|
|
1952
|
+
(key) => strapi.contentTypes[key].collectionName
|
|
1953
|
+
);
|
|
1954
|
+
return {
|
|
1955
|
+
name: "nameAlreadyUsed",
|
|
1956
|
+
message: "contentType: name `${value}` is already being used by another content type.",
|
|
1957
|
+
test(value) {
|
|
1958
|
+
if (isEdition)
|
|
1959
|
+
return true;
|
|
1960
|
+
if (typeof value !== "string") {
|
|
1961
|
+
return true;
|
|
1962
|
+
}
|
|
1963
|
+
return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
};
|
|
1967
|
+
const kindSchema = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]);
|
|
1968
|
+
const validateKind = validateYupSchema(kindSchema);
|
|
1969
|
+
const contentTypes = {
|
|
1970
|
+
async getContentTypes(ctx) {
|
|
1971
|
+
const { kind } = ctx.query;
|
|
1972
|
+
try {
|
|
1973
|
+
await validateKind(kind);
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
return ctx.send({ error }, 400);
|
|
1976
|
+
}
|
|
1977
|
+
const contentTypeService = getService("content-types");
|
|
1978
|
+
const contentTypes2 = Object.keys(strapi.contentTypes).filter(
|
|
1979
|
+
(uid) => !kind || _.get(strapi.contentTypes[uid], "kind", "collectionType") === kind
|
|
1980
|
+
).map(
|
|
1981
|
+
(uid) => contentTypeService.formatContentType(strapi.contentTypes[uid])
|
|
1982
|
+
);
|
|
1983
|
+
ctx.send({
|
|
1984
|
+
data: contentTypes2
|
|
1985
|
+
});
|
|
1986
|
+
},
|
|
1987
|
+
getContentType(ctx) {
|
|
1988
|
+
const { uid } = ctx.params;
|
|
1989
|
+
const contentType = strapi.contentTypes[uid];
|
|
1990
|
+
if (!contentType) {
|
|
1991
|
+
return ctx.send({ error: "contentType.notFound" }, 404);
|
|
1992
|
+
}
|
|
1993
|
+
const contentTypeService = getService("content-types");
|
|
1994
|
+
ctx.send({ data: contentTypeService.formatContentType(contentType) });
|
|
1995
|
+
},
|
|
1996
|
+
async createContentType(ctx) {
|
|
1997
|
+
const body = ctx.request.body;
|
|
1998
|
+
try {
|
|
1999
|
+
await validateContentTypeInput(body);
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
return ctx.send({ error }, 400);
|
|
2002
|
+
}
|
|
2003
|
+
try {
|
|
2004
|
+
strapi.reload.isWatching = false;
|
|
2005
|
+
const contentTypeService = getService("content-types");
|
|
2006
|
+
const contentType = await contentTypeService.createContentType({
|
|
2007
|
+
contentType: body.contentType,
|
|
2008
|
+
components: body.components
|
|
2009
|
+
});
|
|
2010
|
+
const metricsPayload = {
|
|
2011
|
+
eventProperties: {
|
|
2012
|
+
kind: contentType.kind
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
if (_.isEmpty(strapi.apis)) {
|
|
2016
|
+
await strapi.telemetry.send("didCreateFirstContentType", metricsPayload);
|
|
2017
|
+
} else {
|
|
2018
|
+
await strapi.telemetry.send("didCreateContentType", metricsPayload);
|
|
2019
|
+
}
|
|
2020
|
+
setImmediate(() => strapi.reload());
|
|
2021
|
+
ctx.send({ data: { uid: contentType.uid } }, 201);
|
|
2022
|
+
} catch (err) {
|
|
2023
|
+
strapi.log.error(err);
|
|
2024
|
+
await strapi.telemetry.send("didNotCreateContentType", {
|
|
2025
|
+
eventProperties: { error: err.message || err }
|
|
2026
|
+
});
|
|
2027
|
+
ctx.send({ error: err.message || "Unknown error" }, 400);
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
async updateContentType(ctx) {
|
|
2031
|
+
const { uid } = ctx.params;
|
|
2032
|
+
const body = ctx.request.body;
|
|
2033
|
+
if (!_.has(strapi.contentTypes, uid)) {
|
|
2034
|
+
return ctx.send({ error: "contentType.notFound" }, 404);
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
await validateUpdateContentTypeInput(body);
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
return ctx.send({ error }, 400);
|
|
2040
|
+
}
|
|
2041
|
+
try {
|
|
2042
|
+
strapi.reload.isWatching = false;
|
|
2043
|
+
const contentTypeService = getService("content-types");
|
|
2044
|
+
const component = await contentTypeService.editContentType(uid, {
|
|
2045
|
+
contentType: body.contentType,
|
|
2046
|
+
components: body.components
|
|
2047
|
+
});
|
|
2048
|
+
setImmediate(() => strapi.reload());
|
|
2049
|
+
ctx.send({ data: { uid: component.uid } }, 201);
|
|
2050
|
+
} catch (error) {
|
|
2051
|
+
strapi.log.error(error);
|
|
2052
|
+
ctx.send({ error: error?.message || "Unknown error" }, 400);
|
|
2053
|
+
}
|
|
2054
|
+
},
|
|
2055
|
+
async deleteContentType(ctx) {
|
|
2056
|
+
const { uid } = ctx.params;
|
|
2057
|
+
if (!_.has(strapi.contentTypes, uid)) {
|
|
2058
|
+
return ctx.send({ error: "contentType.notFound" }, 404);
|
|
2059
|
+
}
|
|
2060
|
+
try {
|
|
2061
|
+
strapi.reload.isWatching = false;
|
|
2062
|
+
const contentTypeService = getService("content-types");
|
|
2063
|
+
const component = await contentTypeService.deleteContentType(uid);
|
|
2064
|
+
setImmediate(() => strapi.reload());
|
|
2065
|
+
ctx.send({ data: { uid: component.uid } });
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
strapi.log.error(error);
|
|
2068
|
+
ctx.send({ error: error?.message || "Unknown error" }, 400);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
const exportObject = {
|
|
2073
|
+
builder,
|
|
2074
|
+
"component-categories": componentCategories,
|
|
2075
|
+
components,
|
|
2076
|
+
"content-types": contentTypes
|
|
2077
|
+
};
|
|
2078
|
+
const admin = {
|
|
2079
|
+
type: "admin",
|
|
2080
|
+
routes: [
|
|
2081
|
+
{
|
|
2082
|
+
method: "GET",
|
|
2083
|
+
path: "/reserved-names",
|
|
2084
|
+
handler: "builder.getReservedNames",
|
|
2085
|
+
config: {
|
|
2086
|
+
policies: [
|
|
2087
|
+
{
|
|
2088
|
+
name: "admin::hasPermissions",
|
|
2089
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2090
|
+
}
|
|
2091
|
+
]
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
method: "GET",
|
|
2096
|
+
path: "/content-types",
|
|
2097
|
+
handler: "content-types.getContentTypes",
|
|
2098
|
+
config: {
|
|
2099
|
+
policies: [
|
|
2100
|
+
{
|
|
2101
|
+
name: "admin::hasPermissions",
|
|
2102
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2103
|
+
}
|
|
2104
|
+
]
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
method: "GET",
|
|
2109
|
+
path: "/content-types/:uid",
|
|
2110
|
+
handler: "content-types.getContentType",
|
|
2111
|
+
config: {
|
|
2112
|
+
policies: [
|
|
2113
|
+
{
|
|
2114
|
+
name: "admin::hasPermissions",
|
|
2115
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2116
|
+
}
|
|
2117
|
+
]
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
method: "POST",
|
|
2122
|
+
path: "/content-types",
|
|
2123
|
+
handler: "content-types.createContentType",
|
|
2124
|
+
config: {
|
|
2125
|
+
policies: [
|
|
2126
|
+
{
|
|
2127
|
+
name: "admin::hasPermissions",
|
|
2128
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2129
|
+
}
|
|
2130
|
+
]
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
method: "PUT",
|
|
2135
|
+
path: "/content-types/:uid",
|
|
2136
|
+
handler: "content-types.updateContentType",
|
|
2137
|
+
config: {
|
|
2138
|
+
policies: [
|
|
2139
|
+
{
|
|
2140
|
+
name: "admin::hasPermissions",
|
|
2141
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2142
|
+
}
|
|
2143
|
+
]
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
{
|
|
2147
|
+
method: "DELETE",
|
|
2148
|
+
path: "/content-types/:uid",
|
|
2149
|
+
handler: "content-types.deleteContentType",
|
|
2150
|
+
config: {
|
|
2151
|
+
policies: [
|
|
2152
|
+
{
|
|
2153
|
+
name: "admin::hasPermissions",
|
|
2154
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2155
|
+
}
|
|
2156
|
+
]
|
|
2157
|
+
}
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
method: "GET",
|
|
2161
|
+
path: "/components",
|
|
2162
|
+
handler: "components.getComponents",
|
|
2163
|
+
config: {
|
|
2164
|
+
policies: [
|
|
2165
|
+
{
|
|
2166
|
+
name: "admin::hasPermissions",
|
|
2167
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2168
|
+
}
|
|
2169
|
+
]
|
|
2170
|
+
}
|
|
2171
|
+
},
|
|
2172
|
+
{
|
|
2173
|
+
method: "GET",
|
|
2174
|
+
path: "/components/:uid",
|
|
2175
|
+
handler: "components.getComponent",
|
|
2176
|
+
config: {
|
|
2177
|
+
policies: [
|
|
2178
|
+
{
|
|
2179
|
+
name: "admin::hasPermissions",
|
|
2180
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2181
|
+
}
|
|
2182
|
+
]
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
method: "POST",
|
|
2187
|
+
path: "/components",
|
|
2188
|
+
handler: "components.createComponent",
|
|
2189
|
+
config: {
|
|
2190
|
+
policies: [
|
|
2191
|
+
{
|
|
2192
|
+
name: "admin::hasPermissions",
|
|
2193
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2194
|
+
}
|
|
2195
|
+
]
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
method: "PUT",
|
|
2200
|
+
path: "/components/:uid",
|
|
2201
|
+
handler: "components.updateComponent",
|
|
2202
|
+
config: {
|
|
2203
|
+
policies: [
|
|
2204
|
+
{
|
|
2205
|
+
name: "admin::hasPermissions",
|
|
2206
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2207
|
+
}
|
|
2208
|
+
]
|
|
2209
|
+
}
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
method: "DELETE",
|
|
2213
|
+
path: "/components/:uid",
|
|
2214
|
+
handler: "components.deleteComponent",
|
|
2215
|
+
config: {
|
|
2216
|
+
policies: [
|
|
2217
|
+
{
|
|
2218
|
+
name: "admin::hasPermissions",
|
|
2219
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2220
|
+
}
|
|
2221
|
+
]
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
method: "PUT",
|
|
2226
|
+
path: "/component-categories/:name",
|
|
2227
|
+
handler: "component-categories.editCategory",
|
|
2228
|
+
config: {
|
|
2229
|
+
policies: [
|
|
2230
|
+
{
|
|
2231
|
+
name: "admin::hasPermissions",
|
|
2232
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2233
|
+
}
|
|
2234
|
+
]
|
|
2235
|
+
}
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
method: "DELETE",
|
|
2239
|
+
path: "/component-categories/:name",
|
|
2240
|
+
handler: "component-categories.deleteCategory",
|
|
2241
|
+
config: {
|
|
2242
|
+
policies: [
|
|
2243
|
+
{
|
|
2244
|
+
name: "admin::hasPermissions",
|
|
2245
|
+
config: { actions: ["plugin::content-type-builder.read"] }
|
|
2246
|
+
}
|
|
2247
|
+
]
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
]
|
|
2251
|
+
};
|
|
2252
|
+
const contentApi = {
|
|
2253
|
+
type: "content-api",
|
|
2254
|
+
routes: [
|
|
2255
|
+
{
|
|
2256
|
+
method: "GET",
|
|
2257
|
+
path: "/content-types",
|
|
2258
|
+
handler: "content-types.getContentTypes"
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
method: "GET",
|
|
2262
|
+
path: "/content-types/:uid",
|
|
2263
|
+
handler: "content-types.getContentType"
|
|
2264
|
+
},
|
|
2265
|
+
{
|
|
2266
|
+
method: "GET",
|
|
2267
|
+
path: "/components",
|
|
2268
|
+
handler: "components.getComponents"
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
method: "GET",
|
|
2272
|
+
path: "/components/:uid",
|
|
2273
|
+
handler: "components.getComponent"
|
|
2274
|
+
}
|
|
2275
|
+
]
|
|
2276
|
+
};
|
|
2277
|
+
const routes = {
|
|
2278
|
+
admin,
|
|
2279
|
+
"content-api": contentApi
|
|
2280
|
+
};
|
|
2281
|
+
const index = () => ({
|
|
2282
|
+
config,
|
|
2283
|
+
bootstrap,
|
|
2284
|
+
services,
|
|
2285
|
+
controllers: exportObject,
|
|
2286
|
+
routes
|
|
2287
|
+
});
|
|
2288
|
+
export {
|
|
2289
|
+
index as default
|
|
2290
|
+
};
|
|
2291
|
+
//# sourceMappingURL=index.mjs.map
|