@strapi/content-type-builder 5.56.0 → 5.57.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/components/AIChat/lib/transforms/schemas/toCTB.js +117 -13
- package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.js.map +1 -1
- package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.mjs +118 -13
- package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.mjs.map +1 -1
- package/dist/admin/components/AIChat/providers/SchemaProvider.js +31 -19
- package/dist/admin/components/AIChat/providers/SchemaProvider.js.map +1 -1
- package/dist/admin/components/AIChat/providers/SchemaProvider.mjs +31 -19
- package/dist/admin/components/AIChat/providers/SchemaProvider.mjs.map +1 -1
- package/dist/admin/components/DataManager/DataManagerContext.js.map +1 -1
- package/dist/admin/components/DataManager/DataManagerContext.mjs.map +1 -1
- package/dist/admin/components/DataManager/DataManagerProvider.js +166 -6
- package/dist/admin/components/DataManager/DataManagerProvider.js.map +1 -1
- package/dist/admin/components/DataManager/DataManagerProvider.mjs +165 -7
- package/dist/admin/components/DataManager/DataManagerProvider.mjs.map +1 -1
- package/dist/admin/components/DataManager/RenameMigrationModal.js +285 -0
- package/dist/admin/components/DataManager/RenameMigrationModal.js.map +1 -0
- package/dist/admin/components/DataManager/RenameMigrationModal.mjs +257 -0
- package/dist/admin/components/DataManager/RenameMigrationModal.mjs.map +1 -0
- package/dist/admin/components/DataManager/reducer.js +107 -2
- package/dist/admin/components/DataManager/reducer.js.map +1 -1
- package/dist/admin/components/DataManager/reducer.mjs +106 -2
- package/dist/admin/components/DataManager/reducer.mjs.map +1 -1
- package/dist/admin/components/DataManager/utils/cleanData.js +8 -1
- package/dist/admin/components/DataManager/utils/cleanData.js.map +1 -1
- package/dist/admin/components/DataManager/utils/cleanData.mjs +8 -1
- package/dist/admin/components/DataManager/utils/cleanData.mjs.map +1 -1
- package/dist/admin/components/DataManager/utils/groupRenameChains.js +86 -0
- package/dist/admin/components/DataManager/utils/groupRenameChains.js.map +1 -0
- package/dist/admin/components/DataManager/utils/groupRenameChains.mjs +84 -0
- package/dist/admin/components/DataManager/utils/groupRenameChains.mjs.map +1 -0
- package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.js +29 -0
- package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.js.map +1 -0
- package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.mjs +27 -0
- package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.mjs.map +1 -0
- package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.js +35 -0
- package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.js.map +1 -0
- package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.mjs +33 -0
- package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.mjs.map +1 -0
- package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.js +48 -0
- package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.js.map +1 -0
- package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.mjs +45 -0
- package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.mjs.map +1 -0
- package/dist/admin/components/FormModal/FormModal.js +142 -8
- package/dist/admin/components/FormModal/FormModal.js.map +1 -1
- package/dist/admin/components/FormModal/FormModal.mjs +143 -9
- package/dist/admin/components/FormModal/FormModal.mjs.map +1 -1
- package/dist/admin/components/FormModal/attributes/validation/common.js +6 -5
- package/dist/admin/components/FormModal/attributes/validation/common.js.map +1 -1
- package/dist/admin/components/FormModal/attributes/validation/common.mjs +1 -1
- package/dist/admin/components/FormModal/attributes/validation/common.mjs.map +1 -1
- package/dist/admin/components/FormModal/component/createComponentSchema.js +10 -7
- package/dist/admin/components/FormModal/component/createComponentSchema.js.map +1 -1
- package/dist/admin/components/FormModal/component/createComponentSchema.mjs +1 -1
- package/dist/admin/components/FormModal/component/createComponentSchema.mjs.map +1 -1
- package/dist/admin/components/FormModal/contentType/createContentTypeSchema.js +24 -21
- package/dist/admin/components/FormModal/contentType/createContentTypeSchema.js.map +1 -1
- package/dist/admin/components/FormModal/contentType/createContentTypeSchema.mjs +1 -1
- package/dist/admin/components/FormModal/contentType/createContentTypeSchema.mjs.map +1 -1
- package/dist/admin/components/FormModal/forms/utils/createCollectionName.js +3 -2
- package/dist/admin/components/FormModal/forms/utils/createCollectionName.js.map +1 -1
- package/dist/admin/components/FormModal/forms/utils/createCollectionName.mjs +1 -1
- package/dist/admin/components/FormModal/forms/utils/createCollectionName.mjs.map +1 -1
- package/dist/admin/components/FormModal/utils/getRenameStorageChange.js +73 -0
- package/dist/admin/components/FormModal/utils/getRenameStorageChange.js.map +1 -0
- package/dist/admin/components/FormModal/utils/getRenameStorageChange.mjs +70 -0
- package/dist/admin/components/FormModal/utils/getRenameStorageChange.mjs.map +1 -0
- package/dist/admin/src/components/AIChat/lib/transforms/schemas/toCTB.d.ts +4 -3
- package/dist/admin/src/components/AIChat/lib/types/schema.d.ts +6 -1
- package/dist/admin/src/components/DataManager/DataManagerContext.d.ts +28 -2
- package/dist/admin/src/components/DataManager/RenameMigrationModal.d.ts +73 -0
- package/dist/admin/src/components/DataManager/reducer.d.ts +17 -2
- package/dist/admin/src/components/DataManager/utils/cleanData.d.ts +16 -0
- package/dist/admin/src/components/DataManager/utils/groupRenameChains.d.ts +31 -0
- package/dist/admin/src/components/DataManager/utils/isStorageCompatibleRename.d.ts +10 -0
- package/dist/admin/src/components/DataManager/utils/resolveAfterEditRenameConsent.d.ts +24 -0
- package/dist/admin/src/components/DataManager/utils/splitChainsByInheritedConsent.d.ts +26 -0
- package/dist/admin/src/components/FormModal/utils/getRenameStorageChange.d.ts +40 -0
- package/dist/admin/src/types.d.ts +23 -0
- package/dist/admin/translations/en.json.js +12 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +12 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/server/config.js +24 -3
- package/dist/server/config.js.map +1 -1
- package/dist/server/config.mjs +21 -3
- package/dist/server/config.mjs.map +1 -1
- package/dist/server/controllers/schema.js +2 -2
- package/dist/server/controllers/schema.js.map +1 -1
- package/dist/server/controllers/schema.mjs +1 -1
- package/dist/server/controllers/schema.mjs.map +1 -1
- package/dist/server/controllers/validation/content-type.js +9 -11
- package/dist/server/controllers/validation/content-type.js.map +1 -1
- package/dist/server/controllers/validation/content-type.mjs +7 -9
- package/dist/server/controllers/validation/content-type.mjs.map +1 -1
- package/dist/server/controllers/validation/model-schema.js +2 -3
- package/dist/server/controllers/validation/model-schema.js.map +1 -1
- package/dist/server/controllers/validation/model-schema.mjs +1 -2
- package/dist/server/controllers/validation/model-schema.mjs.map +1 -1
- package/dist/server/controllers/validation/relations.js +1 -2
- package/dist/server/controllers/validation/relations.js.map +1 -1
- package/dist/server/controllers/validation/relations.mjs +1 -2
- package/dist/server/controllers/validation/relations.mjs.map +1 -1
- package/dist/server/controllers/validation/schema.js +42 -23
- package/dist/server/controllers/validation/schema.js.map +1 -1
- package/dist/server/controllers/validation/schema.mjs +43 -24
- package/dist/server/controllers/validation/schema.mjs.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/services/content-types.js +1 -2
- package/dist/server/services/content-types.js.map +1 -1
- package/dist/server/services/content-types.mjs +2 -3
- package/dist/server/services/content-types.mjs.map +1 -1
- package/dist/server/services/migration-builder/index.js +580 -0
- package/dist/server/services/migration-builder/index.js.map +1 -0
- package/dist/server/services/migration-builder/index.mjs +573 -0
- package/dist/server/services/migration-builder/index.mjs.map +1 -0
- package/dist/server/services/schema-builder/content-type-builder.js +31 -1
- package/dist/server/services/schema-builder/content-type-builder.js.map +1 -1
- package/dist/server/services/schema-builder/content-type-builder.mjs +31 -1
- package/dist/server/services/schema-builder/content-type-builder.mjs.map +1 -1
- package/dist/server/services/schema-mutation.js +30 -2
- package/dist/server/services/schema-mutation.js.map +1 -1
- package/dist/server/services/schema-mutation.mjs +10 -2
- package/dist/server/services/schema-mutation.mjs.map +1 -1
- package/dist/server/services/schema.js +259 -15
- package/dist/server/services/schema.js.map +1 -1
- package/dist/server/services/schema.mjs +252 -9
- package/dist/server/services/schema.mjs.map +1 -1
- package/dist/server/src/config.d.ts +23 -2
- package/dist/server/src/config.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/content-type.d.ts +0 -1
- package/dist/server/src/controllers/validation/content-type.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/relations.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/schema.d.ts +28 -0
- package/dist/server/src/controllers/validation/schema.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +10 -2
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/services/content-types.d.ts.map +1 -1
- package/dist/server/src/services/migration-builder/index.d.ts +83 -0
- package/dist/server/src/services/migration-builder/index.d.ts.map +1 -0
- package/dist/server/src/services/schema-builder/content-type-builder.d.ts.map +1 -1
- package/dist/server/src/services/schema-mutation.d.ts +4 -2
- package/dist/server/src/services/schema-mutation.d.ts.map +1 -1
- package/dist/server/src/services/schema.d.ts +19 -0
- package/dist/server/src/services/schema.d.ts.map +1 -1
- package/package.json +7 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/server/config.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const ATTRIBUTE_RENAME_MIGRATION_MODES = [
|
|
6
|
+
'always',
|
|
7
|
+
'never',
|
|
8
|
+
'prompt-after-edit',
|
|
9
|
+
'prompt-before-save'
|
|
10
|
+
];
|
|
3
11
|
var config = {
|
|
4
|
-
default: {
|
|
5
|
-
|
|
12
|
+
default: {
|
|
13
|
+
renameMigrations: {
|
|
14
|
+
attributes: 'prompt-before-save'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
validator (config) {
|
|
18
|
+
const renameMigrations = config.renameMigrations;
|
|
19
|
+
if (renameMigrations === undefined) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (renameMigrations.attributes !== undefined && !ATTRIBUTE_RENAME_MIGRATION_MODES.includes(renameMigrations.attributes)) {
|
|
23
|
+
throw new Error(`[content-type-builder] 'renameMigrations.attributes' must be one of: ${ATTRIBUTE_RENAME_MIGRATION_MODES.join(', ')}. Received: ${JSON.stringify(renameMigrations.attributes)}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
6
26
|
};
|
|
7
27
|
|
|
8
|
-
|
|
28
|
+
exports.ATTRIBUTE_RENAME_MIGRATION_MODES = ATTRIBUTE_RENAME_MIGRATION_MODES;
|
|
29
|
+
exports.default = config;
|
|
9
30
|
//# sourceMappingURL=config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sources":["../../server/src/config.ts"],"sourcesContent":["export default {\n default: {},\n validator() {},\n};\n"],"names":["default","validator"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../../server/src/config.ts"],"sourcesContent":["export const ATTRIBUTE_RENAME_MIGRATION_MODES = [\n 'always',\n 'never',\n 'prompt-after-edit',\n 'prompt-before-save',\n] as const;\n\nexport type AttributeRenameMigrationMode = (typeof ATTRIBUTE_RENAME_MIGRATION_MODES)[number];\n\nexport interface RenameMigrationsConfig {\n /**\n * Attribute rename behavior. A contentTypes setting can be added alongside\n * this later without changing the public configuration shape.\n */\n attributes: AttributeRenameMigrationMode;\n}\n\nexport interface ContentTypeBuilderConfig {\n renameMigrations: RenameMigrationsConfig;\n}\n\ntype ContentTypeBuilderUserConfig = {\n renameMigrations?: {\n attributes?: AttributeRenameMigrationMode;\n };\n};\n\nexport default {\n default: {\n renameMigrations: {\n attributes: 'prompt-before-save',\n },\n } satisfies ContentTypeBuilderConfig,\n validator(config: ContentTypeBuilderUserConfig) {\n const renameMigrations = config.renameMigrations;\n if (renameMigrations === undefined) {\n return;\n }\n\n if (\n renameMigrations.attributes !== undefined &&\n !ATTRIBUTE_RENAME_MIGRATION_MODES.includes(renameMigrations.attributes)\n ) {\n throw new Error(\n `[content-type-builder] 'renameMigrations.attributes' must be one of: ${ATTRIBUTE_RENAME_MIGRATION_MODES.join(\n ', '\n )}. Received: ${JSON.stringify(renameMigrations.attributes)}`\n );\n }\n },\n};\n"],"names":["ATTRIBUTE_RENAME_MIGRATION_MODES","default","renameMigrations","attributes","validator","config","undefined","includes","Error","join","JSON","stringify"],"mappings":";;;;MAAaA,gCAAAA,GAAmC;AAC9C,IAAA,QAAA;AACA,IAAA,OAAA;AACA,IAAA,mBAAA;AACA,IAAA;;AAuBF,aAAe;IACbC,OAAAA,EAAS;QACPC,gBAAAA,EAAkB;YAChBC,UAAAA,EAAY;AACd;AACF,KAAA;AACAC,IAAAA,SAAAA,CAAAA,CAAUC,MAAoC,EAAA;QAC5C,MAAMH,gBAAAA,GAAmBG,OAAOH,gBAAgB;AAChD,QAAA,IAAIA,qBAAqBI,SAAAA,EAAW;AAClC,YAAA;AACF,QAAA;QAEA,IACEJ,gBAAAA,CAAiBC,UAAU,KAAKG,SAAAA,IAChC,CAACN,iCAAiCO,QAAQ,CAACL,gBAAAA,CAAiBC,UAAU,CAAA,EACtE;AACA,YAAA,MAAM,IAAIK,KAAAA,CACR,CAAC,qEAAqE,EAAER,iCAAiCS,IAAI,CAC3G,IAAA,CAAA,CACA,YAAY,EAAEC,IAAAA,CAAKC,SAAS,CAACT,gBAAAA,CAAiBC,UAAU,CAAA,CAAA,CAAG,CAAA;AAEjE,QAAA;AACF,IAAA;AACF,CAAA;;;;;"}
|
package/dist/server/config.mjs
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
+
const ATTRIBUTE_RENAME_MIGRATION_MODES = [
|
|
2
|
+
'always',
|
|
3
|
+
'never',
|
|
4
|
+
'prompt-after-edit',
|
|
5
|
+
'prompt-before-save'
|
|
6
|
+
];
|
|
1
7
|
var config = {
|
|
2
|
-
default: {
|
|
3
|
-
|
|
8
|
+
default: {
|
|
9
|
+
renameMigrations: {
|
|
10
|
+
attributes: 'prompt-before-save'
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
validator (config) {
|
|
14
|
+
const renameMigrations = config.renameMigrations;
|
|
15
|
+
if (renameMigrations === undefined) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (renameMigrations.attributes !== undefined && !ATTRIBUTE_RENAME_MIGRATION_MODES.includes(renameMigrations.attributes)) {
|
|
19
|
+
throw new Error(`[content-type-builder] 'renameMigrations.attributes' must be one of: ${ATTRIBUTE_RENAME_MIGRATION_MODES.join(', ')}. Received: ${JSON.stringify(renameMigrations.attributes)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
4
22
|
};
|
|
5
23
|
|
|
6
|
-
export { config as default };
|
|
24
|
+
export { ATTRIBUTE_RENAME_MIGRATION_MODES, config as default };
|
|
7
25
|
//# sourceMappingURL=config.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","sources":["../../server/src/config.ts"],"sourcesContent":["export default {\n default: {},\n validator() {},\n};\n"],"names":["default","validator"],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.mjs","sources":["../../server/src/config.ts"],"sourcesContent":["export const ATTRIBUTE_RENAME_MIGRATION_MODES = [\n 'always',\n 'never',\n 'prompt-after-edit',\n 'prompt-before-save',\n] as const;\n\nexport type AttributeRenameMigrationMode = (typeof ATTRIBUTE_RENAME_MIGRATION_MODES)[number];\n\nexport interface RenameMigrationsConfig {\n /**\n * Attribute rename behavior. A contentTypes setting can be added alongside\n * this later without changing the public configuration shape.\n */\n attributes: AttributeRenameMigrationMode;\n}\n\nexport interface ContentTypeBuilderConfig {\n renameMigrations: RenameMigrationsConfig;\n}\n\ntype ContentTypeBuilderUserConfig = {\n renameMigrations?: {\n attributes?: AttributeRenameMigrationMode;\n };\n};\n\nexport default {\n default: {\n renameMigrations: {\n attributes: 'prompt-before-save',\n },\n } satisfies ContentTypeBuilderConfig,\n validator(config: ContentTypeBuilderUserConfig) {\n const renameMigrations = config.renameMigrations;\n if (renameMigrations === undefined) {\n return;\n }\n\n if (\n renameMigrations.attributes !== undefined &&\n !ATTRIBUTE_RENAME_MIGRATION_MODES.includes(renameMigrations.attributes)\n ) {\n throw new Error(\n `[content-type-builder] 'renameMigrations.attributes' must be one of: ${ATTRIBUTE_RENAME_MIGRATION_MODES.join(\n ', '\n )}. Received: ${JSON.stringify(renameMigrations.attributes)}`\n );\n }\n },\n};\n"],"names":["ATTRIBUTE_RENAME_MIGRATION_MODES","default","renameMigrations","attributes","validator","config","undefined","includes","Error","join","JSON","stringify"],"mappings":"MAAaA,gCAAAA,GAAmC;AAC9C,IAAA,QAAA;AACA,IAAA,OAAA;AACA,IAAA,mBAAA;AACA,IAAA;;AAuBF,aAAe;IACbC,OAAAA,EAAS;QACPC,gBAAAA,EAAkB;YAChBC,UAAAA,EAAY;AACd;AACF,KAAA;AACAC,IAAAA,SAAAA,CAAAA,CAAUC,MAAoC,EAAA;QAC5C,MAAMH,gBAAAA,GAAmBG,OAAOH,gBAAgB;AAChD,QAAA,IAAIA,qBAAqBI,SAAAA,EAAW;AAClC,YAAA;AACF,QAAA;QAEA,IACEJ,gBAAAA,CAAiBC,UAAU,KAAKG,SAAAA,IAChC,CAACN,iCAAiCO,QAAQ,CAACL,gBAAAA,CAAiBC,UAAU,CAAA,EACtE;AACA,YAAA,MAAM,IAAIK,KAAAA,CACR,CAAC,qEAAqE,EAAER,iCAAiCS,IAAI,CAC3G,IAAA,CAAA,CACA,YAAY,EAAEC,IAAAA,CAAKC,SAAS,CAACT,gBAAAA,CAAiBC,UAAU,CAAA,CAAA,CAAG,CAAA;AAEjE,QAAA;AACF,IAAA;AACF,CAAA;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _ = require('lodash');
|
|
4
4
|
var index = require('../utils/index.js');
|
|
5
5
|
var schema$1 = require('./validation/schema.js');
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ var schema = (()=>{
|
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
23
|
const { data } = await schema$1.validateUpdateSchema(ctx.request.body);
|
|
24
|
-
if (
|
|
24
|
+
if (_.isEmpty(data.components) && _.isEmpty(data.contentTypes) && _.isEmpty(data.contentStructure)) {
|
|
25
25
|
ctx.body = {};
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sources":["../../../server/src/controllers/schema.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport { isEmpty } from 'lodash
|
|
1
|
+
{"version":3,"file":"schema.js","sources":["../../../server/src/controllers/schema.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport { isEmpty } from 'lodash';\n\nimport { getService } from '../utils';\nimport { validateUpdateSchema } from './validation/schema';\n\nexport default () => {\n const internals = {\n isUpdating: false,\n };\n\n return {\n async getSchema(ctx: Context) {\n const schema = await getService('schema').getSchema();\n\n ctx.send({ data: schema });\n },\n\n async updateSchema(ctx: Context) {\n if (internals.isUpdating === true) {\n return ctx.conflict('Schema update is already in progress.');\n }\n\n try {\n const { data } = await validateUpdateSchema(ctx.request.body);\n\n if (\n isEmpty(data.components) &&\n isEmpty(data.contentTypes) &&\n isEmpty(data.contentStructure)\n ) {\n ctx.body = {};\n return;\n }\n\n internals.isUpdating = true;\n strapi.reload.isWatching = false;\n\n await getService('schema').updateSchema(data);\n\n // NOTE: we do not set isUpdating to false here.\n // We want to wait for the server to restart to get the isUpdate = false only\n setImmediate(() => {\n strapi.reload();\n });\n\n ctx.body = {};\n } catch (error) {\n internals.isUpdating = false;\n const errorMessage = error instanceof Error ? error.message : String(error);\n return ctx.send({ error: errorMessage }, 400);\n }\n },\n\n async getUpdateSchemaStatus(ctx: Context) {\n ctx.send({ data: { isUpdating: internals.isUpdating } });\n },\n };\n};\n"],"names":["internals","isUpdating","getSchema","ctx","schema","getService","send","data","updateSchema","conflict","validateUpdateSchema","request","body","isEmpty","components","contentTypes","contentStructure","strapi","reload","isWatching","setImmediate","error","errorMessage","Error","message","String","getUpdateSchemaStatus"],"mappings":";;;;;;AAMA,aAAe,CAAA,IAAA;AACb,IAAA,MAAMA,SAAAA,GAAY;QAChBC,UAAAA,EAAY;AACd,KAAA;IAEA,OAAO;AACL,QAAA,MAAMC,WAAUC,GAAY,EAAA;AAC1B,YAAA,MAAMC,MAAAA,GAAS,MAAMC,gBAAAA,CAAW,QAAA,CAAA,CAAUH,SAAS,EAAA;AAEnDC,YAAAA,GAAAA,CAAIG,IAAI,CAAC;gBAAEC,IAAAA,EAAMH;AAAO,aAAA,CAAA;AAC1B,QAAA,CAAA;AAEA,QAAA,MAAMI,cAAaL,GAAY,EAAA;YAC7B,IAAIH,SAAAA,CAAUC,UAAU,KAAK,IAAA,EAAM;gBACjC,OAAOE,GAAAA,CAAIM,QAAQ,CAAC,uCAAA,CAAA;AACtB,YAAA;YAEA,IAAI;gBACF,MAAM,EAAEF,IAAI,EAAE,GAAG,MAAMG,6BAAAA,CAAqBP,GAAAA,CAAIQ,OAAO,CAACC,IAAI,CAAA;gBAE5D,IACEC,SAAAA,CAAQN,IAAAA,CAAKO,UAAU,CAAA,IACvBD,SAAAA,CAAQN,IAAAA,CAAKQ,YAAY,CAAA,IACzBF,SAAAA,CAAQN,IAAAA,CAAKS,gBAAgB,CAAA,EAC7B;oBACAb,GAAAA,CAAIS,IAAI,GAAG,EAAC;AACZ,oBAAA;AACF,gBAAA;AAEAZ,gBAAAA,SAAAA,CAAUC,UAAU,GAAG,IAAA;gBACvBgB,MAAAA,CAAOC,MAAM,CAACC,UAAU,GAAG,KAAA;gBAE3B,MAAMd,gBAAAA,CAAW,QAAA,CAAA,CAAUG,YAAY,CAACD,IAAAA,CAAAA;;;gBAIxCa,YAAAA,CAAa,IAAA;AACXH,oBAAAA,MAAAA,CAAOC,MAAM,EAAA;AACf,gBAAA,CAAA,CAAA;gBAEAf,GAAAA,CAAIS,IAAI,GAAG,EAAC;AACd,YAAA,CAAA,CAAE,OAAOS,KAAAA,EAAO;AACdrB,gBAAAA,SAAAA,CAAUC,UAAU,GAAG,KAAA;AACvB,gBAAA,MAAMqB,eAAeD,KAAAA,YAAiBE,KAAAA,GAAQF,KAAAA,CAAMG,OAAO,GAAGC,MAAAA,CAAOJ,KAAAA,CAAAA;gBACrE,OAAOlB,GAAAA,CAAIG,IAAI,CAAC;oBAAEe,KAAAA,EAAOC;iBAAa,EAAG,GAAA,CAAA;AAC3C,YAAA;AACF,QAAA,CAAA;AAEA,QAAA,MAAMI,uBAAsBvB,GAAY,EAAA;AACtCA,YAAAA,GAAAA,CAAIG,IAAI,CAAC;gBAAEC,IAAAA,EAAM;AAAEN,oBAAAA,UAAAA,EAAYD,UAAUC;AAAW;AAAE,aAAA,CAAA;AACxD,QAAA;AACF,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.mjs","sources":["../../../server/src/controllers/schema.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport { isEmpty } from 'lodash
|
|
1
|
+
{"version":3,"file":"schema.mjs","sources":["../../../server/src/controllers/schema.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport { isEmpty } from 'lodash';\n\nimport { getService } from '../utils';\nimport { validateUpdateSchema } from './validation/schema';\n\nexport default () => {\n const internals = {\n isUpdating: false,\n };\n\n return {\n async getSchema(ctx: Context) {\n const schema = await getService('schema').getSchema();\n\n ctx.send({ data: schema });\n },\n\n async updateSchema(ctx: Context) {\n if (internals.isUpdating === true) {\n return ctx.conflict('Schema update is already in progress.');\n }\n\n try {\n const { data } = await validateUpdateSchema(ctx.request.body);\n\n if (\n isEmpty(data.components) &&\n isEmpty(data.contentTypes) &&\n isEmpty(data.contentStructure)\n ) {\n ctx.body = {};\n return;\n }\n\n internals.isUpdating = true;\n strapi.reload.isWatching = false;\n\n await getService('schema').updateSchema(data);\n\n // NOTE: we do not set isUpdating to false here.\n // We want to wait for the server to restart to get the isUpdate = false only\n setImmediate(() => {\n strapi.reload();\n });\n\n ctx.body = {};\n } catch (error) {\n internals.isUpdating = false;\n const errorMessage = error instanceof Error ? error.message : String(error);\n return ctx.send({ error: errorMessage }, 400);\n }\n },\n\n async getUpdateSchemaStatus(ctx: Context) {\n ctx.send({ data: { isUpdating: internals.isUpdating } });\n },\n };\n};\n"],"names":["internals","isUpdating","getSchema","ctx","schema","getService","send","data","updateSchema","conflict","validateUpdateSchema","request","body","isEmpty","components","contentTypes","contentStructure","strapi","reload","isWatching","setImmediate","error","errorMessage","Error","message","String","getUpdateSchemaStatus"],"mappings":";;;;AAMA,aAAe,CAAA,IAAA;AACb,IAAA,MAAMA,SAAAA,GAAY;QAChBC,UAAAA,EAAY;AACd,KAAA;IAEA,OAAO;AACL,QAAA,MAAMC,WAAUC,GAAY,EAAA;AAC1B,YAAA,MAAMC,MAAAA,GAAS,MAAMC,UAAAA,CAAW,QAAA,CAAA,CAAUH,SAAS,EAAA;AAEnDC,YAAAA,GAAAA,CAAIG,IAAI,CAAC;gBAAEC,IAAAA,EAAMH;AAAO,aAAA,CAAA;AAC1B,QAAA,CAAA;AAEA,QAAA,MAAMI,cAAaL,GAAY,EAAA;YAC7B,IAAIH,SAAAA,CAAUC,UAAU,KAAK,IAAA,EAAM;gBACjC,OAAOE,GAAAA,CAAIM,QAAQ,CAAC,uCAAA,CAAA;AACtB,YAAA;YAEA,IAAI;gBACF,MAAM,EAAEF,IAAI,EAAE,GAAG,MAAMG,oBAAAA,CAAqBP,GAAAA,CAAIQ,OAAO,CAACC,IAAI,CAAA;gBAE5D,IACEC,OAAAA,CAAQN,IAAAA,CAAKO,UAAU,CAAA,IACvBD,OAAAA,CAAQN,IAAAA,CAAKQ,YAAY,CAAA,IACzBF,OAAAA,CAAQN,IAAAA,CAAKS,gBAAgB,CAAA,EAC7B;oBACAb,GAAAA,CAAIS,IAAI,GAAG,EAAC;AACZ,oBAAA;AACF,gBAAA;AAEAZ,gBAAAA,SAAAA,CAAUC,UAAU,GAAG,IAAA;gBACvBgB,MAAAA,CAAOC,MAAM,CAACC,UAAU,GAAG,KAAA;gBAE3B,MAAMd,UAAAA,CAAW,QAAA,CAAA,CAAUG,YAAY,CAACD,IAAAA,CAAAA;;;gBAIxCa,YAAAA,CAAa,IAAA;AACXH,oBAAAA,MAAAA,CAAOC,MAAM,EAAA;AACf,gBAAA,CAAA,CAAA;gBAEAf,GAAAA,CAAIS,IAAI,GAAG,EAAC;AACd,YAAA,CAAA,CAAE,OAAOS,KAAAA,EAAO;AACdrB,gBAAAA,SAAAA,CAAUC,UAAU,GAAG,KAAA;AACvB,gBAAA,MAAMqB,eAAeD,KAAAA,YAAiBE,KAAAA,GAAQF,KAAAA,CAAMG,OAAO,GAAGC,MAAAA,CAAOJ,KAAAA,CAAAA;gBACrE,OAAOlB,GAAAA,CAAIG,IAAI,CAAC;oBAAEe,KAAAA,EAAOC;iBAAa,EAAG,GAAA,CAAA;AAC3C,YAAA;AACF,QAAA,CAAA;AAEA,QAAA,MAAMI,uBAAsBvB,GAAY,EAAA;AACtCA,YAAAA,GAAAA,CAAIG,IAAI,CAAC;gBAAEC,IAAAA,EAAM;AAAEN,oBAAAA,UAAAA,EAAYD,UAAUC;AAAW;AAAE,aAAA,CAAA;AACxD,QAAA;AACF,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _ = require('lodash');
|
|
4
4
|
var utils = require('@strapi/utils');
|
|
5
5
|
var index = require('../../utils/index.js');
|
|
6
6
|
var constants = require('../../services/constants.js');
|
|
@@ -8,7 +8,6 @@ var modelSchema = require('./model-schema.js');
|
|
|
8
8
|
var dataTransform = require('./data-transform.js');
|
|
9
9
|
var component = require('./component.js');
|
|
10
10
|
|
|
11
|
-
/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format
|
|
12
11
|
/**
|
|
13
12
|
* Allowed relation per type kind
|
|
14
13
|
*/ const VALID_RELATIONS = {
|
|
@@ -43,7 +42,7 @@ var component = require('./component.js');
|
|
|
43
42
|
/**
|
|
44
43
|
* Returns a yup schema to validate a content type payload
|
|
45
44
|
*/ const createContentTypeSchema = (data, { isEdition = false } = {})=>{
|
|
46
|
-
const kind =
|
|
45
|
+
const kind = _.get(data, 'contentType.kind', constants.typeKinds.COLLECTION_TYPE);
|
|
47
46
|
const contentTypeSchema = modelSchema.createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {
|
|
48
47
|
modelType: constants.modelTypes.CONTENT_TYPE
|
|
49
48
|
}).shape({
|
|
@@ -71,13 +70,13 @@ var component = require('./component.js');
|
|
|
71
70
|
/**
|
|
72
71
|
* Validator for content type edition
|
|
73
72
|
*/ const validateUpdateContentTypeInput = (data)=>{
|
|
74
|
-
if (
|
|
73
|
+
if (_.has(data, 'contentType')) {
|
|
75
74
|
dataTransform.removeEmptyDefaults(data.contentType);
|
|
76
75
|
dataTransform.removeDeletedUIDTargetFields(data.contentType);
|
|
77
76
|
}
|
|
78
|
-
if (
|
|
77
|
+
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
|
79
78
|
data.components.forEach((comp)=>{
|
|
80
|
-
if (
|
|
79
|
+
if (_.has(comp, 'uid')) {
|
|
81
80
|
dataTransform.removeEmptyDefaults(comp);
|
|
82
81
|
}
|
|
83
82
|
});
|
|
@@ -100,13 +99,12 @@ const forbiddenContentTypeNameValidator = ()=>{
|
|
|
100
99
|
};
|
|
101
100
|
};
|
|
102
101
|
const nameIsAvailable = (isEdition)=>{
|
|
103
|
-
|
|
104
|
-
const usedNames = fp.flatMap((ct)=>{
|
|
102
|
+
const usedNames = Object.values(strapi.contentTypes).flatMap((ct)=>{
|
|
105
103
|
return [
|
|
106
104
|
ct.info?.singularName,
|
|
107
105
|
ct.info?.pluralName
|
|
108
106
|
];
|
|
109
|
-
})
|
|
107
|
+
});
|
|
110
108
|
return {
|
|
111
109
|
name: 'nameAlreadyUsed',
|
|
112
110
|
message: 'contentType: name `${value}` is already being used by another content type.',
|
|
@@ -118,7 +116,7 @@ const nameIsAvailable = (isEdition)=>{
|
|
|
118
116
|
return true;
|
|
119
117
|
}
|
|
120
118
|
// compare snake case to check the actual column names that will be used in the database
|
|
121
|
-
return usedNames.every((usedName)=>
|
|
119
|
+
return usedNames.every((usedName)=>_.snakeCase(usedName) !== _.snakeCase(value));
|
|
122
120
|
}
|
|
123
121
|
};
|
|
124
122
|
};
|
|
@@ -135,7 +133,7 @@ const nameIsNotExistingCollectionName = (isEdition)=>{
|
|
|
135
133
|
return true;
|
|
136
134
|
}
|
|
137
135
|
// compare snake case to check the actual column names that will be used in the database
|
|
138
|
-
return usedNames.every((usedName)=>
|
|
136
|
+
return usedNames.every((usedName)=>_.snakeCase(usedName) !== _.snakeCase(value));
|
|
139
137
|
}
|
|
140
138
|
};
|
|
141
139
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.js","sources":["../../../../server/src/controllers/validation/content-type.ts"],"sourcesContent":["/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format\n\nimport { flatMap, getOr, has, snakeCase } from 'lodash/fp';\nimport { yup, validateYupSchema } from '@strapi/utils';\n\nimport type { Struct, Internal, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { modelTypes, DEFAULT_TYPES, typeKinds } from '../../services/constants';\nimport { createSchema } from './model-schema';\nimport { removeEmptyDefaults, removeDeletedUIDTargetFields } from './data-transform';\nimport { nestedComponentSchema } from './component';\n\n// Input flattens some fields of the \"info\" into the root type\nexport type CreateContentTypeInput = {\n uid?: UID.ContentType;\n contentType?: Partial<Struct.ContentTypeSchema> & Partial<Struct.ContentTypeSchemaInfo>;\n components?: Array<\n Partial<Struct.ComponentSchema> &\n Partial<Struct.SchemaInfo> & { tmpUID?: Internal.UID.Component }\n >;\n singularName: Struct.ContentTypeSchemaInfo['singularName'];\n attributes: Struct.SchemaAttributes & Record<string, any>;\n kind: Struct.ContentTypeKind;\n collectionName?: Struct.CollectionTypeSchema['collectionName'];\n pluralName: Struct.ContentTypeSchemaInfo['pluralName'];\n displayName: Struct.ContentTypeSchemaInfo['displayName'];\n description?: Struct.ContentTypeSchemaInfo['description'];\n options?: Struct.SchemaOptions;\n draftAndPublish?: Struct.SchemaOptions['draftAndPublish'];\n pluginOptions?: Struct.ContentTypeSchema['pluginOptions'];\n config?: object;\n plugin?: string;\n};\n\n/**\n * Allowed relation per type kind\n */\nconst VALID_RELATIONS = {\n [typeKinds.SINGLE_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n [typeKinds.COLLECTION_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'manyToOne',\n 'manyToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n} as const;\n\n/**\n * Allowed types\n */\nconst VALID_TYPES = [...DEFAULT_TYPES, 'uid', 'component', 'dynamiczone', 'customField'];\n\n/**\n * Returns a yup schema to validate a content type payload\n */\nconst createContentTypeSchema = (data: CreateContentTypeInput, { isEdition = false } = {}) => {\n const kind: keyof typeof VALID_RELATIONS = getOr(\n typeKinds.COLLECTION_TYPE,\n 'contentType.kind',\n data\n );\n\n const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {\n modelType: modelTypes.CONTENT_TYPE,\n })\n .shape({\n displayName: yup.string().min(1).required(),\n singularName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n pluralName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(nameIsNotExistingCollectionName(isEdition)) // TODO: v5: require singularName to not match a collection name\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n })\n .test(\n 'singularName-not-equal-pluralName',\n '${path}: singularName and pluralName should be different',\n (value) => value.singularName !== value.pluralName\n );\n\n return yup\n .object({\n // FIXME .noUnknown(false) will strip off the unwanted properties without throwing an error\n // Why not having .noUnknown() ? Because we want to be able to add options relatable to EE features\n // without having any reference to them in CE.\n // Why not handle an \"options\" object in the content-type ? The admin panel needs lots of rework\n // to be able to send this options object instead of top-level attributes.\n // @nathan-pichon 20/02/2023\n contentType: contentTypeSchema.required().noUnknown(false),\n components: nestedComponentSchema,\n })\n .noUnknown();\n};\n\n/**\n * Validator for content type creation\n */\nexport const validateContentTypeInput = (data: CreateContentTypeInput) => {\n return validateYupSchema(createContentTypeSchema(data))(data);\n};\n\n/**\n * Validator for content type edition\n */\nexport const validateUpdateContentTypeInput = (data: CreateContentTypeInput) => {\n if (has('contentType', data)) {\n removeEmptyDefaults(data.contentType);\n removeDeletedUIDTargetFields(data.contentType as Struct.ContentTypeSchema);\n }\n\n if (has('components', data) && Array.isArray(data.components)) {\n data.components.forEach((comp) => {\n if (has('uid', comp)) {\n removeEmptyDefaults(comp as Struct.ComponentSchema);\n }\n });\n }\n\n return validateYupSchema(createContentTypeSchema(data, { isEdition: true }))(data);\n};\n\nconst forbiddenContentTypeNameValidator = () => {\n const reservedNames = getService('builder').getReservedNames().models;\n\n return {\n name: 'forbiddenContentTypeName',\n message: `Content Type name cannot be one of ${reservedNames.join(', ')}`,\n test(value: unknown) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return !getService('builder').isReservedModelName(value);\n },\n };\n};\n\nconst nameIsAvailable = (isEdition: boolean) => {\n // 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\n const usedNames = flatMap((ct: Struct.ContentTypeSchema) => {\n return [ct.info?.singularName, ct.info?.pluralName];\n })(strapi.contentTypes as any);\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\nconst nameIsNotExistingCollectionName = (isEdition: boolean) => {\n const usedNames = Object.keys(strapi.contentTypes).map(\n (key) => strapi.contentTypes[key as Internal.UID.ContentType].collectionName\n ) as string[];\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\n/**\n * Validates type kind\n */\nconst kindSchema = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]);\n\nexport const validateKind = validateYupSchema(kindSchema);\n"],"names":["VALID_RELATIONS","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","VALID_TYPES","DEFAULT_TYPES","createContentTypeSchema","data","isEdition","kind","getOr","contentTypeSchema","createSchema","modelType","modelTypes","CONTENT_TYPE","shape","displayName","yup","string","min","required","singularName","test","nameIsAvailable","forbiddenContentTypeNameValidator","isKebabCase","pluralName","nameIsNotExistingCollectionName","value","object","contentType","noUnknown","components","nestedComponentSchema","validateContentTypeInput","validateYupSchema","validateUpdateContentTypeInput","has","removeEmptyDefaults","removeDeletedUIDTargetFields","Array","isArray","forEach","comp","reservedNames","getService","getReservedNames","models","name","message","join","isReservedModelName","usedNames","flatMap","ct","info","strapi","contentTypes","every","usedName","snakeCase","Object","keys","map","key","collectionName","kindSchema","oneOf","validateKind"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"content-type.js","sources":["../../../../server/src/controllers/validation/content-type.ts"],"sourcesContent":["import { get, has, snakeCase } from 'lodash';\n/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format\n\nimport { yup, validateYupSchema } from '@strapi/utils';\n\nimport type { Struct, Internal, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { modelTypes, DEFAULT_TYPES, typeKinds } from '../../services/constants';\nimport { createSchema } from './model-schema';\nimport { removeEmptyDefaults, removeDeletedUIDTargetFields } from './data-transform';\nimport { nestedComponentSchema } from './component';\n\n// Input flattens some fields of the \"info\" into the root type\nexport type CreateContentTypeInput = {\n uid?: UID.ContentType;\n contentType?: Partial<Struct.ContentTypeSchema> & Partial<Struct.ContentTypeSchemaInfo>;\n components?: Array<\n Partial<Struct.ComponentSchema> &\n Partial<Struct.SchemaInfo> & { tmpUID?: Internal.UID.Component }\n >;\n singularName: Struct.ContentTypeSchemaInfo['singularName'];\n attributes: Struct.SchemaAttributes & Record<string, any>;\n kind: Struct.ContentTypeKind;\n collectionName?: Struct.CollectionTypeSchema['collectionName'];\n pluralName: Struct.ContentTypeSchemaInfo['pluralName'];\n displayName: Struct.ContentTypeSchemaInfo['displayName'];\n description?: Struct.ContentTypeSchemaInfo['description'];\n options?: Struct.SchemaOptions;\n draftAndPublish?: Struct.SchemaOptions['draftAndPublish'];\n pluginOptions?: Struct.ContentTypeSchema['pluginOptions'];\n config?: object;\n plugin?: string;\n};\n\n/**\n * Allowed relation per type kind\n */\nconst VALID_RELATIONS = {\n [typeKinds.SINGLE_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n [typeKinds.COLLECTION_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'manyToOne',\n 'manyToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n} as const;\n\n/**\n * Allowed types\n */\nconst VALID_TYPES = [...DEFAULT_TYPES, 'uid', 'component', 'dynamiczone', 'customField'];\n\n/**\n * Returns a yup schema to validate a content type payload\n */\nconst createContentTypeSchema = (data: CreateContentTypeInput, { isEdition = false } = {}) => {\n const kind: keyof typeof VALID_RELATIONS = get(\n data,\n 'contentType.kind',\n typeKinds.COLLECTION_TYPE\n );\n\n const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {\n modelType: modelTypes.CONTENT_TYPE,\n })\n .shape({\n displayName: yup.string().min(1).required(),\n singularName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n pluralName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(nameIsNotExistingCollectionName(isEdition)) // TODO: v5: require singularName to not match a collection name\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n })\n .test(\n 'singularName-not-equal-pluralName',\n '${path}: singularName and pluralName should be different',\n (value) => value.singularName !== value.pluralName\n );\n\n return yup\n .object({\n // FIXME .noUnknown(false) will strip off the unwanted properties without throwing an error\n // Why not having .noUnknown() ? Because we want to be able to add options relatable to EE features\n // without having any reference to them in CE.\n // Why not handle an \"options\" object in the content-type ? The admin panel needs lots of rework\n // to be able to send this options object instead of top-level attributes.\n // @nathan-pichon 20/02/2023\n contentType: contentTypeSchema.required().noUnknown(false),\n components: nestedComponentSchema,\n })\n .noUnknown();\n};\n\n/**\n * Validator for content type creation\n */\nexport const validateContentTypeInput = (data: CreateContentTypeInput) => {\n return validateYupSchema(createContentTypeSchema(data))(data);\n};\n\n/**\n * Validator for content type edition\n */\nexport const validateUpdateContentTypeInput = (data: CreateContentTypeInput) => {\n if (has(data, 'contentType')) {\n removeEmptyDefaults(data.contentType);\n removeDeletedUIDTargetFields(data.contentType as Struct.ContentTypeSchema);\n }\n\n if (has(data, 'components') && Array.isArray(data.components)) {\n data.components.forEach((comp) => {\n if (has(comp, 'uid')) {\n removeEmptyDefaults(comp as Struct.ComponentSchema);\n }\n });\n }\n\n return validateYupSchema(createContentTypeSchema(data, { isEdition: true }))(data);\n};\n\nconst forbiddenContentTypeNameValidator = () => {\n const reservedNames = getService('builder').getReservedNames().models;\n\n return {\n name: 'forbiddenContentTypeName',\n message: `Content Type name cannot be one of ${reservedNames.join(', ')}`,\n test(value: unknown) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return !getService('builder').isReservedModelName(value);\n },\n };\n};\n\nconst nameIsAvailable = (isEdition: boolean) => {\n const usedNames = Object.values(strapi.contentTypes).flatMap((ct) => {\n return [ct.info?.singularName, ct.info?.pluralName];\n });\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\nconst nameIsNotExistingCollectionName = (isEdition: boolean) => {\n const usedNames = Object.keys(strapi.contentTypes).map(\n (key) => strapi.contentTypes[key as Internal.UID.ContentType].collectionName\n ) as string[];\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\n/**\n * Validates type kind\n */\nconst kindSchema = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]);\n\nexport const validateKind = validateYupSchema(kindSchema);\n"],"names":["VALID_RELATIONS","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","VALID_TYPES","DEFAULT_TYPES","createContentTypeSchema","data","isEdition","kind","get","contentTypeSchema","createSchema","modelType","modelTypes","CONTENT_TYPE","shape","displayName","yup","string","min","required","singularName","test","nameIsAvailable","forbiddenContentTypeNameValidator","isKebabCase","pluralName","nameIsNotExistingCollectionName","value","object","contentType","noUnknown","components","nestedComponentSchema","validateContentTypeInput","validateYupSchema","validateUpdateContentTypeInput","has","removeEmptyDefaults","removeDeletedUIDTargetFields","Array","isArray","forEach","comp","reservedNames","getService","getReservedNames","models","name","message","join","isReservedModelName","usedNames","Object","values","strapi","contentTypes","flatMap","ct","info","every","usedName","snakeCase","keys","map","key","collectionName","kindSchema","oneOf","validateKind"],"mappings":";;;;;;;;;;AAmCA;;AAEC,IACD,MAAMA,eAAAA,GAAkB;IACtB,CAACC,mBAAAA,CAAUC,WAAW,GAAG;AACvB,QAAA,UAAA;AACA,QAAA,WAAA;AACA,QAAA,UAAA;AACA,QAAA,WAAA;AACA,QAAA,YAAA;AACA,QAAA;AACD,KAAA;IACD,CAACD,mBAAAA,CAAUE,eAAe,GAAG;AAC3B,QAAA,UAAA;AACA,QAAA,WAAA;AACA,QAAA,WAAA;AACA,QAAA,YAAA;AACA,QAAA,UAAA;AACA,QAAA,WAAA;AACA,QAAA,YAAA;AACA,QAAA;AACD;AACH,CAAA;AAEA;;AAEC,IACD,MAAMC,WAAAA,GAAc;AAAIC,IAAAA,GAAAA,uBAAAA;AAAe,IAAA,KAAA;AAAO,IAAA,WAAA;AAAa,IAAA,aAAA;AAAe,IAAA;AAAc,CAAA;AAExF;;IAGA,MAAMC,uBAAAA,GAA0B,CAACC,IAAAA,EAA8B,EAAEC,YAAY,KAAK,EAAE,GAAG,EAAE,GAAA;AACvF,IAAA,MAAMC,IAAAA,GAAqCC,KAAAA,CACzCH,IAAAA,EACA,kBAAA,EACAN,oBAAUE,eAAe,CAAA;IAG3B,MAAMQ,iBAAAA,GAAoBC,yBAAaR,WAAAA,EAAaJ,eAAe,CAACS,IAAAA,CAAK,IAAI,EAAE,EAAE;AAC/EI,QAAAA,SAAAA,EAAWC,qBAAWC;AACxB,KAAA,CAAA,CACGC,KAAK,CAAC;AACLC,QAAAA,WAAAA,EAAaC,UAAIC,MAAM,EAAA,CAAGC,GAAG,CAAC,GAAGC,QAAQ,EAAA;AACzCC,QAAAA,YAAAA,EAAcJ,SAAAA,CACXC,MAAM,EAAA,CACNC,GAAG,CAAC,CAAA,CAAA,CACJG,IAAI,CAACC,eAAAA,CAAgBhB,YACrBe,IAAI,CAACE,iCAAAA,EAAAA,CAAAA,CACLC,WAAW,GACXL,QAAQ,EAAA;AACXM,QAAAA,UAAAA,EAAYT,SAAAA,CACTC,MAAM,EAAA,CACNC,GAAG,CAAC,CAAA,CAAA,CACJG,IAAI,CAACC,eAAAA,CAAgBhB,SAAAA,CAAAA,CAAAA,CACrBe,IAAI,CAACK,+BAAAA,CAAgCpB;AACrCe,SAAAA,IAAI,CAACE,iCAAAA,EAAAA,CAAAA,CACLC,WAAW,EAAA,CACXL,QAAQ;KACb,CAAA,CACCE,IAAI,CACH,mCAAA,EACA,0DAAA,EACA,CAACM,QAAUA,KAAAA,CAAMP,YAAY,KAAKO,KAAAA,CAAMF,UAAU,CAAA;IAGtD,OAAOT,SAAAA,CACJY,MAAM,CAAC;;;;;;;AAONC,QAAAA,WAAAA,EAAapB,iBAAAA,CAAkBU,QAAQ,EAAA,CAAGW,SAAS,CAAC,KAAA,CAAA;QACpDC,UAAAA,EAAYC;AACd,KAAA,CAAA,CACCF,SAAS,EAAA;AACd,CAAA;AAEA;;IAGO,MAAMG,wBAAAA,GAA2B,CAAC5B,IAAAA,GAAAA;IACvC,OAAO6B,uBAAAA,CAAkB9B,wBAAwBC,IAAAA,CAAAA,CAAAA,CAAOA,IAAAA,CAAAA;AAC1D;AAEA;;IAGO,MAAM8B,8BAAAA,GAAiC,CAAC9B,IAAAA,GAAAA;IAC7C,IAAI+B,KAAAA,CAAI/B,MAAM,aAAA,CAAA,EAAgB;AAC5BgC,QAAAA,iCAAAA,CAAoBhC,KAAKwB,WAAW,CAAA;AACpCS,QAAAA,0CAAAA,CAA6BjC,KAAKwB,WAAW,CAAA;AAC/C,IAAA;IAEA,IAAIO,KAAAA,CAAI/B,MAAM,YAAA,CAAA,IAAiBkC,KAAAA,CAAMC,OAAO,CAACnC,IAAAA,CAAK0B,UAAU,CAAA,EAAG;AAC7D1B,QAAAA,IAAAA,CAAK0B,UAAU,CAACU,OAAO,CAAC,CAACC,IAAAA,GAAAA;YACvB,IAAIN,KAAAA,CAAIM,MAAM,KAAA,CAAA,EAAQ;gBACpBL,iCAAAA,CAAoBK,IAAAA,CAAAA;AACtB,YAAA;AACF,QAAA,CAAA,CAAA;AACF,IAAA;IAEA,OAAOR,uBAAAA,CAAkB9B,wBAAwBC,IAAAA,EAAM;QAAEC,SAAAA,EAAW;KAAK,CAAA,CAAA,CAAID,IAAAA,CAAAA;AAC/E;AAEA,MAAMkB,iCAAAA,GAAoC,IAAA;AACxC,IAAA,MAAMoB,aAAAA,GAAgBC,gBAAAA,CAAW,SAAA,CAAA,CAAWC,gBAAgB,GAAGC,MAAM;IAErE,OAAO;QACLC,IAAAA,EAAM,0BAAA;AACNC,QAAAA,OAAAA,EAAS,CAAC,mCAAmC,EAAEL,aAAAA,CAAcM,IAAI,CAAC,IAAA,CAAA,CAAA,CAAO;AACzE5B,QAAAA,IAAAA,CAAAA,CAAKM,KAAc,EAAA;YACjB,IAAI,OAAOA,UAAU,QAAA,EAAU;gBAC7B,OAAO,IAAA;AACT,YAAA;AAEA,YAAA,OAAO,CAACiB,gBAAAA,CAAW,SAAA,CAAA,CAAWM,mBAAmB,CAACvB,KAAAA,CAAAA;AACpD,QAAA;AACF,KAAA;AACF,CAAA;AAEA,MAAML,kBAAkB,CAAChB,SAAAA,GAAAA;IACvB,MAAM6C,SAAAA,GAAYC,OAAOC,MAAM,CAACC,OAAOC,YAAY,CAAA,CAAEC,OAAO,CAAC,CAACC,EAAAA,GAAAA;QAC5D,OAAO;AAACA,YAAAA,EAAAA,CAAGC,IAAI,EAAEtC,YAAAA;AAAcqC,YAAAA,EAAAA,CAAGC,IAAI,EAAEjC;AAAW,SAAA;AACrD,IAAA,CAAA,CAAA;IAEA,OAAO;QACLsB,IAAAA,EAAM,iBAAA;QACNC,OAAAA,EAAS,6EAAA;AACT3B,QAAAA,IAAAA,CAAAA,CAAKM,KAAc,EAAA;;AAEjB,YAAA,IAAIrB,WAAW,OAAO,IAAA;;YAGtB,IAAI,OAAOqB,UAAU,QAAA,EAAU;gBAC7B,OAAO,IAAA;AACT,YAAA;;AAGA,YAAA,OAAOwB,UAAUQ,KAAK,CAAC,CAACC,QAAAA,GAAaC,WAAAA,CAAUD,cAAcC,WAAAA,CAAUlC,KAAAA,CAAAA,CAAAA;AACzE,QAAA;AACF,KAAA;AACF,CAAA;AAEA,MAAMD,kCAAkC,CAACpB,SAAAA,GAAAA;AACvC,IAAA,MAAM6C,YAAYC,MAAAA,CAAOU,IAAI,CAACR,MAAAA,CAAOC,YAAY,CAAA,CAAEQ,GAAG,CACpD,CAACC,MAAQV,MAAAA,CAAOC,YAAY,CAACS,GAAAA,CAAgC,CAACC,cAAc,CAAA;IAG9E,OAAO;QACLlB,IAAAA,EAAM,iBAAA;QACNC,OAAAA,EAAS,6EAAA;AACT3B,QAAAA,IAAAA,CAAAA,CAAKM,KAAc,EAAA;;AAEjB,YAAA,IAAIrB,WAAW,OAAO,IAAA;;YAGtB,IAAI,OAAOqB,UAAU,QAAA,EAAU;gBAC7B,OAAO,IAAA;AACT,YAAA;;AAGA,YAAA,OAAOwB,UAAUQ,KAAK,CAAC,CAACC,QAAAA,GAAaC,WAAAA,CAAUD,cAAcC,WAAAA,CAAUlC,KAAAA,CAAAA,CAAAA;AACzE,QAAA;AACF,KAAA;AACF,CAAA;AAEA;;AAEC,IACD,MAAMuC,UAAAA,GAAalD,SAAAA,CAAIC,MAAM,EAAA,CAAGkD,KAAK,CAAC;AAACpE,IAAAA,mBAAAA,CAAUC,WAAW;AAAED,IAAAA,mBAAAA,CAAUE;AAAgB,CAAA,CAAA;AAEjF,MAAMmE,YAAAA,GAAelC,uBAAAA,CAAkBgC,UAAAA;;;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { has,
|
|
1
|
+
import { has, get, snakeCase } from 'lodash';
|
|
2
2
|
import { yup, validateYupSchema } from '@strapi/utils';
|
|
3
3
|
import { getService } from '../../utils/index.mjs';
|
|
4
4
|
import { typeKinds, modelTypes, DEFAULT_TYPES } from '../../services/constants.mjs';
|
|
@@ -6,7 +6,6 @@ import { createSchema } from './model-schema.mjs';
|
|
|
6
6
|
import { removeEmptyDefaults, removeDeletedUIDTargetFields } from './data-transform.mjs';
|
|
7
7
|
import { nestedComponentSchema } from './component.mjs';
|
|
8
8
|
|
|
9
|
-
/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format
|
|
10
9
|
/**
|
|
11
10
|
* Allowed relation per type kind
|
|
12
11
|
*/ const VALID_RELATIONS = {
|
|
@@ -41,7 +40,7 @@ import { nestedComponentSchema } from './component.mjs';
|
|
|
41
40
|
/**
|
|
42
41
|
* Returns a yup schema to validate a content type payload
|
|
43
42
|
*/ const createContentTypeSchema = (data, { isEdition = false } = {})=>{
|
|
44
|
-
const kind =
|
|
43
|
+
const kind = get(data, 'contentType.kind', typeKinds.COLLECTION_TYPE);
|
|
45
44
|
const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {
|
|
46
45
|
modelType: modelTypes.CONTENT_TYPE
|
|
47
46
|
}).shape({
|
|
@@ -69,13 +68,13 @@ import { nestedComponentSchema } from './component.mjs';
|
|
|
69
68
|
/**
|
|
70
69
|
* Validator for content type edition
|
|
71
70
|
*/ const validateUpdateContentTypeInput = (data)=>{
|
|
72
|
-
if (has('contentType'
|
|
71
|
+
if (has(data, 'contentType')) {
|
|
73
72
|
removeEmptyDefaults(data.contentType);
|
|
74
73
|
removeDeletedUIDTargetFields(data.contentType);
|
|
75
74
|
}
|
|
76
|
-
if (has('components'
|
|
75
|
+
if (has(data, 'components') && Array.isArray(data.components)) {
|
|
77
76
|
data.components.forEach((comp)=>{
|
|
78
|
-
if (has('uid'
|
|
77
|
+
if (has(comp, 'uid')) {
|
|
79
78
|
removeEmptyDefaults(comp);
|
|
80
79
|
}
|
|
81
80
|
});
|
|
@@ -98,13 +97,12 @@ const forbiddenContentTypeNameValidator = ()=>{
|
|
|
98
97
|
};
|
|
99
98
|
};
|
|
100
99
|
const nameIsAvailable = (isEdition)=>{
|
|
101
|
-
|
|
102
|
-
const usedNames = flatMap((ct)=>{
|
|
100
|
+
const usedNames = Object.values(strapi.contentTypes).flatMap((ct)=>{
|
|
103
101
|
return [
|
|
104
102
|
ct.info?.singularName,
|
|
105
103
|
ct.info?.pluralName
|
|
106
104
|
];
|
|
107
|
-
})
|
|
105
|
+
});
|
|
108
106
|
return {
|
|
109
107
|
name: 'nameAlreadyUsed',
|
|
110
108
|
message: 'contentType: name `${value}` is already being used by another content type.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.mjs","sources":["../../../../server/src/controllers/validation/content-type.ts"],"sourcesContent":["/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format\n\nimport { flatMap, getOr, has, snakeCase } from 'lodash/fp';\nimport { yup, validateYupSchema } from '@strapi/utils';\n\nimport type { Struct, Internal, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { modelTypes, DEFAULT_TYPES, typeKinds } from '../../services/constants';\nimport { createSchema } from './model-schema';\nimport { removeEmptyDefaults, removeDeletedUIDTargetFields } from './data-transform';\nimport { nestedComponentSchema } from './component';\n\n// Input flattens some fields of the \"info\" into the root type\nexport type CreateContentTypeInput = {\n uid?: UID.ContentType;\n contentType?: Partial<Struct.ContentTypeSchema> & Partial<Struct.ContentTypeSchemaInfo>;\n components?: Array<\n Partial<Struct.ComponentSchema> &\n Partial<Struct.SchemaInfo> & { tmpUID?: Internal.UID.Component }\n >;\n singularName: Struct.ContentTypeSchemaInfo['singularName'];\n attributes: Struct.SchemaAttributes & Record<string, any>;\n kind: Struct.ContentTypeKind;\n collectionName?: Struct.CollectionTypeSchema['collectionName'];\n pluralName: Struct.ContentTypeSchemaInfo['pluralName'];\n displayName: Struct.ContentTypeSchemaInfo['displayName'];\n description?: Struct.ContentTypeSchemaInfo['description'];\n options?: Struct.SchemaOptions;\n draftAndPublish?: Struct.SchemaOptions['draftAndPublish'];\n pluginOptions?: Struct.ContentTypeSchema['pluginOptions'];\n config?: object;\n plugin?: string;\n};\n\n/**\n * Allowed relation per type kind\n */\nconst VALID_RELATIONS = {\n [typeKinds.SINGLE_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n [typeKinds.COLLECTION_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'manyToOne',\n 'manyToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n} as const;\n\n/**\n * Allowed types\n */\nconst VALID_TYPES = [...DEFAULT_TYPES, 'uid', 'component', 'dynamiczone', 'customField'];\n\n/**\n * Returns a yup schema to validate a content type payload\n */\nconst createContentTypeSchema = (data: CreateContentTypeInput, { isEdition = false } = {}) => {\n const kind: keyof typeof VALID_RELATIONS = getOr(\n typeKinds.COLLECTION_TYPE,\n 'contentType.kind',\n data\n );\n\n const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {\n modelType: modelTypes.CONTENT_TYPE,\n })\n .shape({\n displayName: yup.string().min(1).required(),\n singularName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n pluralName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(nameIsNotExistingCollectionName(isEdition)) // TODO: v5: require singularName to not match a collection name\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n })\n .test(\n 'singularName-not-equal-pluralName',\n '${path}: singularName and pluralName should be different',\n (value) => value.singularName !== value.pluralName\n );\n\n return yup\n .object({\n // FIXME .noUnknown(false) will strip off the unwanted properties without throwing an error\n // Why not having .noUnknown() ? Because we want to be able to add options relatable to EE features\n // without having any reference to them in CE.\n // Why not handle an \"options\" object in the content-type ? The admin panel needs lots of rework\n // to be able to send this options object instead of top-level attributes.\n // @nathan-pichon 20/02/2023\n contentType: contentTypeSchema.required().noUnknown(false),\n components: nestedComponentSchema,\n })\n .noUnknown();\n};\n\n/**\n * Validator for content type creation\n */\nexport const validateContentTypeInput = (data: CreateContentTypeInput) => {\n return validateYupSchema(createContentTypeSchema(data))(data);\n};\n\n/**\n * Validator for content type edition\n */\nexport const validateUpdateContentTypeInput = (data: CreateContentTypeInput) => {\n if (has('contentType', data)) {\n removeEmptyDefaults(data.contentType);\n removeDeletedUIDTargetFields(data.contentType as Struct.ContentTypeSchema);\n }\n\n if (has('components', data) && Array.isArray(data.components)) {\n data.components.forEach((comp) => {\n if (has('uid', comp)) {\n removeEmptyDefaults(comp as Struct.ComponentSchema);\n }\n });\n }\n\n return validateYupSchema(createContentTypeSchema(data, { isEdition: true }))(data);\n};\n\nconst forbiddenContentTypeNameValidator = () => {\n const reservedNames = getService('builder').getReservedNames().models;\n\n return {\n name: 'forbiddenContentTypeName',\n message: `Content Type name cannot be one of ${reservedNames.join(', ')}`,\n test(value: unknown) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return !getService('builder').isReservedModelName(value);\n },\n };\n};\n\nconst nameIsAvailable = (isEdition: boolean) => {\n // 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\n const usedNames = flatMap((ct: Struct.ContentTypeSchema) => {\n return [ct.info?.singularName, ct.info?.pluralName];\n })(strapi.contentTypes as any);\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\nconst nameIsNotExistingCollectionName = (isEdition: boolean) => {\n const usedNames = Object.keys(strapi.contentTypes).map(\n (key) => strapi.contentTypes[key as Internal.UID.ContentType].collectionName\n ) as string[];\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\n/**\n * Validates type kind\n */\nconst kindSchema = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]);\n\nexport const validateKind = validateYupSchema(kindSchema);\n"],"names":["VALID_RELATIONS","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","VALID_TYPES","DEFAULT_TYPES","createContentTypeSchema","data","isEdition","kind","getOr","contentTypeSchema","createSchema","modelType","modelTypes","CONTENT_TYPE","shape","displayName","yup","string","min","required","singularName","test","nameIsAvailable","forbiddenContentTypeNameValidator","isKebabCase","pluralName","nameIsNotExistingCollectionName","value","object","contentType","noUnknown","components","nestedComponentSchema","validateContentTypeInput","validateYupSchema","validateUpdateContentTypeInput","has","removeEmptyDefaults","removeDeletedUIDTargetFields","Array","isArray","forEach","comp","reservedNames","getService","getReservedNames","models","name","message","join","isReservedModelName","usedNames","flatMap","ct","info","strapi","contentTypes","every","usedName","snakeCase","Object","keys","map","key","collectionName","kindSchema","oneOf","validateKind"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"content-type.mjs","sources":["../../../../server/src/controllers/validation/content-type.ts"],"sourcesContent":["import { get, has, snakeCase } from 'lodash';\n/* eslint-disable no-template-curly-in-string */ // yup templates need to be in this format\n\nimport { yup, validateYupSchema } from '@strapi/utils';\n\nimport type { Struct, Internal, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { modelTypes, DEFAULT_TYPES, typeKinds } from '../../services/constants';\nimport { createSchema } from './model-schema';\nimport { removeEmptyDefaults, removeDeletedUIDTargetFields } from './data-transform';\nimport { nestedComponentSchema } from './component';\n\n// Input flattens some fields of the \"info\" into the root type\nexport type CreateContentTypeInput = {\n uid?: UID.ContentType;\n contentType?: Partial<Struct.ContentTypeSchema> & Partial<Struct.ContentTypeSchemaInfo>;\n components?: Array<\n Partial<Struct.ComponentSchema> &\n Partial<Struct.SchemaInfo> & { tmpUID?: Internal.UID.Component }\n >;\n singularName: Struct.ContentTypeSchemaInfo['singularName'];\n attributes: Struct.SchemaAttributes & Record<string, any>;\n kind: Struct.ContentTypeKind;\n collectionName?: Struct.CollectionTypeSchema['collectionName'];\n pluralName: Struct.ContentTypeSchemaInfo['pluralName'];\n displayName: Struct.ContentTypeSchemaInfo['displayName'];\n description?: Struct.ContentTypeSchemaInfo['description'];\n options?: Struct.SchemaOptions;\n draftAndPublish?: Struct.SchemaOptions['draftAndPublish'];\n pluginOptions?: Struct.ContentTypeSchema['pluginOptions'];\n config?: object;\n plugin?: string;\n};\n\n/**\n * Allowed relation per type kind\n */\nconst VALID_RELATIONS = {\n [typeKinds.SINGLE_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n [typeKinds.COLLECTION_TYPE]: [\n 'oneToOne',\n 'oneToMany',\n 'manyToOne',\n 'manyToMany',\n 'morphOne',\n 'morphMany',\n 'morphToOne',\n 'morphToMany',\n ],\n} as const;\n\n/**\n * Allowed types\n */\nconst VALID_TYPES = [...DEFAULT_TYPES, 'uid', 'component', 'dynamiczone', 'customField'];\n\n/**\n * Returns a yup schema to validate a content type payload\n */\nconst createContentTypeSchema = (data: CreateContentTypeInput, { isEdition = false } = {}) => {\n const kind: keyof typeof VALID_RELATIONS = get(\n data,\n 'contentType.kind',\n typeKinds.COLLECTION_TYPE\n );\n\n const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {\n modelType: modelTypes.CONTENT_TYPE,\n })\n .shape({\n displayName: yup.string().min(1).required(),\n singularName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n pluralName: yup\n .string()\n .min(1)\n .test(nameIsAvailable(isEdition))\n .test(nameIsNotExistingCollectionName(isEdition)) // TODO: v5: require singularName to not match a collection name\n .test(forbiddenContentTypeNameValidator())\n .isKebabCase()\n .required(),\n })\n .test(\n 'singularName-not-equal-pluralName',\n '${path}: singularName and pluralName should be different',\n (value) => value.singularName !== value.pluralName\n );\n\n return yup\n .object({\n // FIXME .noUnknown(false) will strip off the unwanted properties without throwing an error\n // Why not having .noUnknown() ? Because we want to be able to add options relatable to EE features\n // without having any reference to them in CE.\n // Why not handle an \"options\" object in the content-type ? The admin panel needs lots of rework\n // to be able to send this options object instead of top-level attributes.\n // @nathan-pichon 20/02/2023\n contentType: contentTypeSchema.required().noUnknown(false),\n components: nestedComponentSchema,\n })\n .noUnknown();\n};\n\n/**\n * Validator for content type creation\n */\nexport const validateContentTypeInput = (data: CreateContentTypeInput) => {\n return validateYupSchema(createContentTypeSchema(data))(data);\n};\n\n/**\n * Validator for content type edition\n */\nexport const validateUpdateContentTypeInput = (data: CreateContentTypeInput) => {\n if (has(data, 'contentType')) {\n removeEmptyDefaults(data.contentType);\n removeDeletedUIDTargetFields(data.contentType as Struct.ContentTypeSchema);\n }\n\n if (has(data, 'components') && Array.isArray(data.components)) {\n data.components.forEach((comp) => {\n if (has(comp, 'uid')) {\n removeEmptyDefaults(comp as Struct.ComponentSchema);\n }\n });\n }\n\n return validateYupSchema(createContentTypeSchema(data, { isEdition: true }))(data);\n};\n\nconst forbiddenContentTypeNameValidator = () => {\n const reservedNames = getService('builder').getReservedNames().models;\n\n return {\n name: 'forbiddenContentTypeName',\n message: `Content Type name cannot be one of ${reservedNames.join(', ')}`,\n test(value: unknown) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return !getService('builder').isReservedModelName(value);\n },\n };\n};\n\nconst nameIsAvailable = (isEdition: boolean) => {\n const usedNames = Object.values(strapi.contentTypes).flatMap((ct) => {\n return [ct.info?.singularName, ct.info?.pluralName];\n });\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\nconst nameIsNotExistingCollectionName = (isEdition: boolean) => {\n const usedNames = Object.keys(strapi.contentTypes).map(\n (key) => strapi.contentTypes[key as Internal.UID.ContentType].collectionName\n ) as string[];\n\n return {\n name: 'nameAlreadyUsed',\n message: 'contentType: name `${value}` is already being used by another content type.',\n test(value: unknown) {\n // don't check on edition\n if (isEdition) return true;\n\n // ignore if not a string (will be caught in another validator)\n if (typeof value !== 'string') {\n return true;\n }\n\n // compare snake case to check the actual column names that will be used in the database\n return usedNames.every((usedName) => snakeCase(usedName) !== snakeCase(value));\n },\n };\n};\n\n/**\n * Validates type kind\n */\nconst kindSchema = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]);\n\nexport const validateKind = validateYupSchema(kindSchema);\n"],"names":["VALID_RELATIONS","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","VALID_TYPES","DEFAULT_TYPES","createContentTypeSchema","data","isEdition","kind","get","contentTypeSchema","createSchema","modelType","modelTypes","CONTENT_TYPE","shape","displayName","yup","string","min","required","singularName","test","nameIsAvailable","forbiddenContentTypeNameValidator","isKebabCase","pluralName","nameIsNotExistingCollectionName","value","object","contentType","noUnknown","components","nestedComponentSchema","validateContentTypeInput","validateYupSchema","validateUpdateContentTypeInput","has","removeEmptyDefaults","removeDeletedUIDTargetFields","Array","isArray","forEach","comp","reservedNames","getService","getReservedNames","models","name","message","join","isReservedModelName","usedNames","Object","values","strapi","contentTypes","flatMap","ct","info","every","usedName","snakeCase","keys","map","key","collectionName","kindSchema","oneOf","validateKind"],"mappings":"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"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('@strapi/utils');
|
|
4
4
|
var _ = require('lodash');
|
|
5
|
-
var fp = require('lodash/fp');
|
|
6
5
|
var constants = require('../../services/constants.js');
|
|
7
6
|
var index = require('../../utils/index.js');
|
|
8
7
|
var common = require('./common.js');
|
|
@@ -58,10 +57,10 @@ const createAttributesValidator = ({ types: types$1, modelType, relations: relat
|
|
|
58
57
|
});
|
|
59
58
|
};
|
|
60
59
|
const isConflictingKey = (key, attributes)=>{
|
|
61
|
-
const snakeCaseKey =
|
|
60
|
+
const snakeCaseKey = _.snakeCase(key);
|
|
62
61
|
return Object.keys(attributes).some((existingKey)=>{
|
|
63
62
|
if (existingKey === key) return false; // don't compare against itself
|
|
64
|
-
return
|
|
63
|
+
return _.snakeCase(existingKey) === snakeCaseKey;
|
|
65
64
|
});
|
|
66
65
|
};
|
|
67
66
|
const isForbiddenKey = (key)=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-schema.js","sources":["../../../../server/src/controllers/validation/model-schema.ts"],"sourcesContent":["import { yup } from '@strapi/utils';\nimport _
|
|
1
|
+
{"version":3,"file":"model-schema.js","sources":["../../../../server/src/controllers/validation/model-schema.ts"],"sourcesContent":["import { yup } from '@strapi/utils';\nimport _, { snakeCase } from 'lodash';\n\nimport { modelTypes, typeKinds } from '../../services/constants';\nimport { getService } from '../../utils';\nimport { isValidKey, isValidCollectionName } from './common';\nimport { getTypeValidator } from './types';\nimport { getRelationValidator } from './relations';\n\ntype ModelTypeInput = (typeof modelTypes)[keyof typeof modelTypes];\n\ntype CreateAttributesInput = {\n types: ReadonlyArray<string>;\n relations: ReadonlyArray<string>;\n modelType?: ModelTypeInput;\n};\n\nexport const createSchema = (\n types: CreateAttributesInput['types'],\n relations: CreateAttributesInput['relations'],\n { modelType }: { modelType?: ModelTypeInput } = {}\n) => {\n const shape = {\n description: yup.string(),\n options: yup.object(),\n pluginOptions: yup.object(),\n collectionName: yup.string().nullable().test(isValidCollectionName),\n attributes: createAttributesValidator({ types, relations, modelType }),\n draftAndPublish: yup.boolean(),\n } as any;\n\n if (modelType === modelTypes.CONTENT_TYPE) {\n shape.kind = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]).nullable();\n }\n\n return yup.object(shape).noUnknown();\n};\n\nconst createAttributesValidator = ({ types, modelType, relations }: CreateAttributesInput) => {\n return yup.lazy((attributes) => {\n return yup\n .object()\n .shape(\n _.mapValues(attributes, (attribute, key) => {\n if (isForbiddenKey(key)) {\n return forbiddenValidator();\n }\n\n if (isConflictingKey(key, attributes)) {\n return conflictingKeysValidator(key);\n }\n\n if (attribute.type === 'relation') {\n return getRelationValidator(attribute, relations).test(isValidKey(key));\n }\n\n if (_.has(attribute, 'type')) {\n return getTypeValidator(attribute, { types, modelType, attributes }).test(\n isValidKey(key)\n );\n }\n\n return typeOrRelationValidator;\n })\n )\n .required('attributes.required');\n });\n};\n\nconst isConflictingKey = (key: string, attributes: Record<string, any>) => {\n const snakeCaseKey = snakeCase(key);\n\n return Object.keys(attributes).some((existingKey) => {\n if (existingKey === key) return false; // don't compare against itself\n return snakeCase(existingKey) === snakeCaseKey;\n });\n};\n\nconst isForbiddenKey = (key: string) => {\n return getService('builder').isReservedAttributeName(key);\n};\n\nconst forbiddenValidator = () => {\n const reservedNames = [...getService('builder').getReservedNames().attributes];\n\n return yup.mixed().test({\n name: 'forbiddenKeys',\n message: `Attribute keys cannot be one of ${reservedNames.join(', ')}`,\n test: () => false,\n });\n};\n\nconst conflictingKeysValidator = (key: string) => {\n return yup.mixed().test({\n name: 'conflictingKeys',\n message: `Attribute ${key} conflicts with an existing key`,\n test: () => false,\n });\n};\n\nconst typeOrRelationValidator = yup.object().test({\n name: 'mustHaveTypeOrTarget',\n message: 'Attribute must have either a type or a target',\n test: () => false,\n});\n"],"names":["createSchema","types","relations","modelType","shape","description","yup","string","options","object","pluginOptions","collectionName","nullable","test","isValidCollectionName","attributes","createAttributesValidator","draftAndPublish","boolean","modelTypes","CONTENT_TYPE","kind","oneOf","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","noUnknown","lazy","_","mapValues","attribute","key","isForbiddenKey","forbiddenValidator","isConflictingKey","conflictingKeysValidator","type","getRelationValidator","isValidKey","has","getTypeValidator","typeOrRelationValidator","required","snakeCaseKey","snakeCase","Object","keys","some","existingKey","getService","isReservedAttributeName","reservedNames","getReservedNames","mixed","name","message","join"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { yup } from '@strapi/utils';
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import { snakeCase } from 'lodash/fp';
|
|
2
|
+
import _, { snakeCase } from 'lodash';
|
|
4
3
|
import { modelTypes, typeKinds } from '../../services/constants.mjs';
|
|
5
4
|
import { getService } from '../../utils/index.mjs';
|
|
6
5
|
import { isValidCollectionName, isValidKey } from './common.mjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-schema.mjs","sources":["../../../../server/src/controllers/validation/model-schema.ts"],"sourcesContent":["import { yup } from '@strapi/utils';\nimport _
|
|
1
|
+
{"version":3,"file":"model-schema.mjs","sources":["../../../../server/src/controllers/validation/model-schema.ts"],"sourcesContent":["import { yup } from '@strapi/utils';\nimport _, { snakeCase } from 'lodash';\n\nimport { modelTypes, typeKinds } from '../../services/constants';\nimport { getService } from '../../utils';\nimport { isValidKey, isValidCollectionName } from './common';\nimport { getTypeValidator } from './types';\nimport { getRelationValidator } from './relations';\n\ntype ModelTypeInput = (typeof modelTypes)[keyof typeof modelTypes];\n\ntype CreateAttributesInput = {\n types: ReadonlyArray<string>;\n relations: ReadonlyArray<string>;\n modelType?: ModelTypeInput;\n};\n\nexport const createSchema = (\n types: CreateAttributesInput['types'],\n relations: CreateAttributesInput['relations'],\n { modelType }: { modelType?: ModelTypeInput } = {}\n) => {\n const shape = {\n description: yup.string(),\n options: yup.object(),\n pluginOptions: yup.object(),\n collectionName: yup.string().nullable().test(isValidCollectionName),\n attributes: createAttributesValidator({ types, relations, modelType }),\n draftAndPublish: yup.boolean(),\n } as any;\n\n if (modelType === modelTypes.CONTENT_TYPE) {\n shape.kind = yup.string().oneOf([typeKinds.SINGLE_TYPE, typeKinds.COLLECTION_TYPE]).nullable();\n }\n\n return yup.object(shape).noUnknown();\n};\n\nconst createAttributesValidator = ({ types, modelType, relations }: CreateAttributesInput) => {\n return yup.lazy((attributes) => {\n return yup\n .object()\n .shape(\n _.mapValues(attributes, (attribute, key) => {\n if (isForbiddenKey(key)) {\n return forbiddenValidator();\n }\n\n if (isConflictingKey(key, attributes)) {\n return conflictingKeysValidator(key);\n }\n\n if (attribute.type === 'relation') {\n return getRelationValidator(attribute, relations).test(isValidKey(key));\n }\n\n if (_.has(attribute, 'type')) {\n return getTypeValidator(attribute, { types, modelType, attributes }).test(\n isValidKey(key)\n );\n }\n\n return typeOrRelationValidator;\n })\n )\n .required('attributes.required');\n });\n};\n\nconst isConflictingKey = (key: string, attributes: Record<string, any>) => {\n const snakeCaseKey = snakeCase(key);\n\n return Object.keys(attributes).some((existingKey) => {\n if (existingKey === key) return false; // don't compare against itself\n return snakeCase(existingKey) === snakeCaseKey;\n });\n};\n\nconst isForbiddenKey = (key: string) => {\n return getService('builder').isReservedAttributeName(key);\n};\n\nconst forbiddenValidator = () => {\n const reservedNames = [...getService('builder').getReservedNames().attributes];\n\n return yup.mixed().test({\n name: 'forbiddenKeys',\n message: `Attribute keys cannot be one of ${reservedNames.join(', ')}`,\n test: () => false,\n });\n};\n\nconst conflictingKeysValidator = (key: string) => {\n return yup.mixed().test({\n name: 'conflictingKeys',\n message: `Attribute ${key} conflicts with an existing key`,\n test: () => false,\n });\n};\n\nconst typeOrRelationValidator = yup.object().test({\n name: 'mustHaveTypeOrTarget',\n message: 'Attribute must have either a type or a target',\n test: () => false,\n});\n"],"names":["createSchema","types","relations","modelType","shape","description","yup","string","options","object","pluginOptions","collectionName","nullable","test","isValidCollectionName","attributes","createAttributesValidator","draftAndPublish","boolean","modelTypes","CONTENT_TYPE","kind","oneOf","typeKinds","SINGLE_TYPE","COLLECTION_TYPE","noUnknown","lazy","_","mapValues","attribute","key","isForbiddenKey","forbiddenValidator","isConflictingKey","conflictingKeysValidator","type","getRelationValidator","isValidKey","has","getTypeValidator","typeOrRelationValidator","required","snakeCaseKey","snakeCase","Object","keys","some","existingKey","getService","isReservedAttributeName","reservedNames","getReservedNames","mixed","name","message","join"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var fp = require('lodash/fp');
|
|
4
3
|
var utils = require('@strapi/utils');
|
|
5
4
|
var constants = require('../../services/constants.js');
|
|
6
5
|
var common = require('./common.js');
|
|
@@ -15,7 +14,7 @@ const isValidRelation = (validNatures)=>function isValidRelationValue(value) {
|
|
|
15
14
|
return true;
|
|
16
15
|
}
|
|
17
16
|
if (this.parent.target === constants.coreUids.STRAPI_USER) {
|
|
18
|
-
if (!validNatures.includes(value) ||
|
|
17
|
+
if (!validNatures.includes(value) || this.parent.targetAttribute !== undefined) {
|
|
19
18
|
return this.createError({
|
|
20
19
|
path: this.path,
|
|
21
20
|
message: `must be one of the following values: ${STRAPI_USER_RELATIONS.join(', ')}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relations.js","sources":["../../../../server/src/controllers/validation/relations.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"relations.js","sources":["../../../../server/src/controllers/validation/relations.ts"],"sourcesContent":["import { yup } from '@strapi/utils';\nimport type { TestContext, TestFunction } from 'yup';\nimport type { Schema, UID } from '@strapi/types';\nimport { typeKinds, coreUids } from '../../services/constants';\nimport { isValidName } from './common';\n\nconst STRAPI_USER_RELATIONS = ['oneToOne', 'oneToMany'];\n\nconst isValidRelation = (validNatures: ReadonlyArray<string>): TestFunction<string | undefined> =>\n function isValidRelationValue(this: TestContext, value) {\n // NOTE: In case of an undefined value, delegate the check to .required()\n if (value === undefined) {\n return true;\n }\n\n if (this.parent.target === coreUids.STRAPI_USER) {\n if (!validNatures.includes(value) || this.parent.targetAttribute !== undefined) {\n return this.createError({\n path: this.path,\n message: `must be one of the following values: ${STRAPI_USER_RELATIONS.join(', ')}`,\n });\n }\n }\n\n return validNatures.includes(value)\n ? true\n : this.createError({\n path: this.path,\n message: `must be one of the following values: ${validNatures.join(', ')}`,\n });\n };\n\nexport const getRelationValidator = (\n attribute: Schema.Attribute.Relation,\n allowedRelations: ReadonlyArray<string>\n) => {\n const contentTypesUIDs = Object.keys(strapi.contentTypes)\n .filter((key) => strapi.contentTypes[key as UID.ContentType].kind === typeKinds.COLLECTION_TYPE)\n .filter((key) => !key.startsWith(coreUids.PREFIX) || key === coreUids.STRAPI_USER)\n .concat(['__self__', '__contentType__']);\n\n const base = {\n type: yup.string().oneOf(['relation']).required(),\n relation: yup.string().test('isValidRelation', isValidRelation(allowedRelations)).required(),\n configurable: yup.boolean().nullable(),\n required: yup.boolean(),\n private: yup.boolean().nullable(),\n pluginOptions: yup.object(),\n };\n\n switch (attribute.relation) {\n case 'oneToOne':\n case 'oneToMany':\n case 'manyToOne':\n case 'manyToMany':\n case 'morphOne':\n case 'morphMany': {\n return yup.object({\n ...base,\n target: yup.string().oneOf(contentTypesUIDs).required(),\n targetAttribute: yup.string().test(isValidName).nullable(),\n });\n }\n case 'morphToOne':\n case 'morphToMany':\n default: {\n return yup.object({ ...base });\n }\n }\n};\n"],"names":["STRAPI_USER_RELATIONS","isValidRelation","validNatures","isValidRelationValue","value","undefined","parent","target","coreUids","STRAPI_USER","includes","targetAttribute","createError","path","message","join","getRelationValidator","attribute","allowedRelations","contentTypesUIDs","Object","keys","strapi","contentTypes","filter","key","kind","typeKinds","COLLECTION_TYPE","startsWith","PREFIX","concat","base","type","yup","string","oneOf","required","relation","test","configurable","boolean","nullable","private","pluginOptions","object","isValidName"],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isUndefined } from 'lodash/fp';
|
|
2
1
|
import { yup } from '@strapi/utils';
|
|
3
2
|
import { typeKinds, coreUids } from '../../services/constants.mjs';
|
|
4
3
|
import { isValidName } from './common.mjs';
|
|
@@ -13,7 +12,7 @@ const isValidRelation = (validNatures)=>function isValidRelationValue(value) {
|
|
|
13
12
|
return true;
|
|
14
13
|
}
|
|
15
14
|
if (this.parent.target === coreUids.STRAPI_USER) {
|
|
16
|
-
if (!validNatures.includes(value) ||
|
|
15
|
+
if (!validNatures.includes(value) || this.parent.targetAttribute !== undefined) {
|
|
17
16
|
return this.createError({
|
|
18
17
|
path: this.path,
|
|
19
18
|
message: `must be one of the following values: ${STRAPI_USER_RELATIONS.join(', ')}`
|