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