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