@strapi/core 5.0.0-beta.8 → 5.0.0-rc.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/Strapi.d.ts +3 -1
- package/dist/Strapi.d.ts.map +1 -1
- package/dist/Strapi.js +57 -13
- package/dist/Strapi.js.map +1 -1
- package/dist/Strapi.mjs +38 -13
- package/dist/Strapi.mjs.map +1 -1
- package/dist/core-api/service/pagination.js +1 -1
- package/dist/core-api/service/pagination.js.map +1 -1
- package/dist/core-api/service/pagination.mjs +2 -2
- package/dist/core-api/service/pagination.mjs.map +1 -1
- package/dist/middlewares/cors.d.ts.map +1 -1
- package/dist/middlewares/cors.js +6 -8
- package/dist/middlewares/cors.js.map +1 -1
- package/dist/middlewares/cors.mjs +6 -8
- package/dist/middlewares/cors.mjs.map +1 -1
- package/dist/middlewares/security.d.ts.map +1 -1
- package/dist/middlewares/security.js +10 -3
- package/dist/middlewares/security.js.map +1 -1
- package/dist/middlewares/security.mjs +11 -4
- package/dist/middlewares/security.mjs.map +1 -1
- package/dist/services/document-service/utils/populate.d.ts.map +1 -1
- package/dist/services/document-service/utils/populate.js +4 -0
- package/dist/services/document-service/utils/populate.js.map +1 -1
- package/dist/services/document-service/utils/populate.mjs +4 -0
- package/dist/services/document-service/utils/populate.mjs.map +1 -1
- package/dist/services/entity-validator/index.d.ts +14 -1
- package/dist/services/entity-validator/index.d.ts.map +1 -1
- package/dist/services/entity-validator/index.js +87 -30
- package/dist/services/entity-validator/index.js.map +1 -1
- package/dist/services/entity-validator/index.mjs +87 -30
- package/dist/services/entity-validator/index.mjs.map +1 -1
- package/dist/services/entity-validator/validators.d.ts +10 -8
- package/dist/services/entity-validator/validators.d.ts.map +1 -1
- package/dist/services/entity-validator/validators.js +60 -19
- package/dist/services/entity-validator/validators.js.map +1 -1
- package/dist/services/entity-validator/validators.mjs +53 -11
- package/dist/services/entity-validator/validators.mjs.map +1 -1
- package/dist/services/server/index.js +1 -1
- package/dist/services/server/index.js.map +1 -1
- package/dist/services/server/index.mjs +1 -1
- package/dist/services/server/index.mjs.map +1 -1
- package/dist/services/webhook-runner.d.ts +2 -1
- package/dist/services/webhook-runner.d.ts.map +1 -1
- package/dist/services/webhook-runner.js.map +1 -1
- package/dist/services/webhook-runner.mjs.map +1 -1
- package/dist/services/webhook-store.d.ts +2 -8
- package/dist/services/webhook-store.d.ts.map +1 -1
- package/dist/services/webhook-store.js +1 -1
- package/dist/services/webhook-store.js.map +1 -1
- package/dist/services/webhook-store.mjs +1 -1
- package/dist/services/webhook-store.mjs.map +1 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +4 -3
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/fetch.mjs +4 -3
- package/dist/utils/fetch.mjs.map +1 -1
- package/dist/utils/transform-content-types-to-models.d.ts.map +1 -1
- package/dist/utils/transform-content-types-to-models.js +10 -0
- package/dist/utils/transform-content-types-to-models.js.map +1 -1
- package/dist/utils/transform-content-types-to-models.mjs +10 -0
- package/dist/utils/transform-content-types-to-models.mjs.map +1 -1
- package/package.json +16 -14
@@ -4,8 +4,6 @@ const strapiUtils = require("@strapi/utils");
|
|
4
4
|
const blocksValidator = require("./blocks-validator.js");
|
5
5
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
6
6
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
7
|
-
const strapiUtils__default = /* @__PURE__ */ _interopDefault(strapiUtils);
|
8
|
-
const { yup } = strapiUtils__default.default;
|
9
7
|
const addMinLengthValidator = (validator, {
|
10
8
|
attr
|
11
9
|
}, { isDraft }) => {
|
@@ -37,7 +35,8 @@ const addUniqueValidator = (validator, {
|
|
37
35
|
attr,
|
38
36
|
model,
|
39
37
|
updatedAttribute,
|
40
|
-
entity
|
38
|
+
entity,
|
39
|
+
componentContext
|
41
40
|
}, options) => {
|
42
41
|
if (attr.type !== "uid" && !attr.unique) {
|
43
42
|
return validator;
|
@@ -50,19 +49,61 @@ const addUniqueValidator = (validator, {
|
|
50
49
|
if (!isPublish && value === entity?.[updatedAttribute.name]) {
|
51
50
|
return true;
|
52
51
|
}
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
let queryUid;
|
53
|
+
let queryWhere = {};
|
54
|
+
if (componentContext) {
|
55
|
+
const hasRepeatableData = componentContext.repeatableData.length > 0;
|
56
|
+
if (hasRepeatableData) {
|
57
|
+
const { name: updatedName, value: updatedValue } = updatedAttribute;
|
58
|
+
const pathToCheck = [...componentContext.pathToComponent.slice(1), updatedName].join(".");
|
59
|
+
const values = componentContext.repeatableData.map((item) => {
|
60
|
+
return pathToCheck.split(".").reduce((acc, key) => acc[key], item);
|
61
|
+
});
|
62
|
+
const isUpdatedAttributeRepeatedInThisEntity = values.filter((value2) => value2 === updatedValue).length > 1;
|
63
|
+
if (isUpdatedAttributeRepeatedInThisEntity) {
|
64
|
+
return false;
|
65
|
+
}
|
59
66
|
}
|
60
|
-
|
61
|
-
|
67
|
+
const {
|
68
|
+
model: parentModel,
|
69
|
+
options: parentOptions,
|
70
|
+
id: excludeId
|
71
|
+
} = componentContext.parentContent;
|
72
|
+
queryUid = parentModel.uid;
|
73
|
+
const whereConditions = {};
|
74
|
+
const isParentDraft = parentOptions && parentOptions.isDraft;
|
75
|
+
whereConditions.publishedAt = isParentDraft ? null : { $notNull: true };
|
76
|
+
if (parentOptions?.locale) {
|
77
|
+
whereConditions.locale = parentOptions.locale;
|
78
|
+
}
|
79
|
+
if (excludeId && !Number.isNaN(excludeId)) {
|
80
|
+
whereConditions.id = { $ne: excludeId };
|
81
|
+
}
|
82
|
+
queryWhere = {
|
83
|
+
...componentContext.pathToComponent.reduceRight((acc, key) => ({ [key]: acc }), {
|
84
|
+
[updatedAttribute.name]: value
|
85
|
+
}),
|
86
|
+
...whereConditions
|
87
|
+
};
|
88
|
+
} else {
|
89
|
+
queryUid = model.uid;
|
90
|
+
const scalarAttributeWhere = {
|
91
|
+
[updatedAttribute.name]: value
|
92
|
+
};
|
93
|
+
scalarAttributeWhere.publishedAt = options.isDraft ? null : { $notNull: true };
|
94
|
+
if (options?.locale) {
|
95
|
+
scalarAttributeWhere.locale = options.locale;
|
96
|
+
}
|
97
|
+
if (entity?.id) {
|
98
|
+
scalarAttributeWhere.id = { $ne: entity.id };
|
99
|
+
}
|
100
|
+
queryWhere = scalarAttributeWhere;
|
101
|
+
}
|
102
|
+
return !await strapi.db.query(queryUid).findOne({ where: queryWhere });
|
62
103
|
});
|
63
104
|
};
|
64
105
|
const stringValidator = (metas, options) => {
|
65
|
-
let schema = yup.string().transform((val, originalVal) => originalVal);
|
106
|
+
let schema = strapiUtils.yup.string().transform((val, originalVal) => originalVal);
|
66
107
|
schema = addMinLengthValidator(schema, metas, options);
|
67
108
|
schema = addMaxLengthValidator(schema, metas);
|
68
109
|
schema = addStringRegexValidator(schema, metas);
|
@@ -78,28 +119,28 @@ const uidValidator = (metas, options) => {
|
|
78
119
|
return schema.matches(/^[A-Za-z0-9-_.~]*$/);
|
79
120
|
};
|
80
121
|
const enumerationValidator = ({ attr }) => {
|
81
|
-
return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
122
|
+
return strapiUtils.yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
82
123
|
};
|
83
124
|
const integerValidator = (metas, options) => {
|
84
|
-
let schema = yup.number().integer();
|
125
|
+
let schema = strapiUtils.yup.number().integer();
|
85
126
|
schema = addMinIntegerValidator(schema, metas);
|
86
127
|
schema = addMaxIntegerValidator(schema, metas);
|
87
128
|
schema = addUniqueValidator(schema, metas, options);
|
88
129
|
return schema;
|
89
130
|
};
|
90
131
|
const floatValidator = (metas, options) => {
|
91
|
-
let schema = yup.number();
|
132
|
+
let schema = strapiUtils.yup.number();
|
92
133
|
schema = addMinFloatValidator(schema, metas);
|
93
134
|
schema = addMaxFloatValidator(schema, metas);
|
94
135
|
schema = addUniqueValidator(schema, metas, options);
|
95
136
|
return schema;
|
96
137
|
};
|
97
138
|
const bigintegerValidator = (metas, options) => {
|
98
|
-
const schema = yup.mixed();
|
139
|
+
const schema = strapiUtils.yup.mixed();
|
99
140
|
return addUniqueValidator(schema, metas, options);
|
100
141
|
};
|
101
142
|
const datesValidator = (metas, options) => {
|
102
|
-
const schema = yup.mixed();
|
143
|
+
const schema = strapiUtils.yup.mixed();
|
103
144
|
return addUniqueValidator(schema, metas, options);
|
104
145
|
};
|
105
146
|
const validators = {
|
@@ -109,9 +150,9 @@ const validators = {
|
|
109
150
|
password: stringValidator,
|
110
151
|
email: emailValidator,
|
111
152
|
enumeration: enumerationValidator,
|
112
|
-
boolean: () => yup.boolean(),
|
153
|
+
boolean: () => strapiUtils.yup.boolean(),
|
113
154
|
uid: uidValidator,
|
114
|
-
json: () => yup.mixed(),
|
155
|
+
json: () => strapiUtils.yup.mixed(),
|
115
156
|
integer: integerValidator,
|
116
157
|
biginteger: bigintegerValidator,
|
117
158
|
float: floatValidator,
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"validators.js","sources":["../../../src/services/entity-validator/validators.ts"],"sourcesContent":["import _ from 'lodash';\nimport strapiUtils from '@strapi/utils';\nimport type { Schema, Struct } from '@strapi/types';\nimport blocksValidator from './blocks-validator';\n\nconst { yup } = strapiUtils;\n\ninterface ValidatorMetas<TAttribute extends Schema.Attribute.AnyAttribute> {\n attr: TAttribute;\n model: Struct.ContentTypeSchema;\n updatedAttribute: { name: string; value: unknown };\n entity: Record<string, unknown> | null;\n}\n\ninterface ValidatorOptions {\n isDraft: boolean;\n locale?: string;\n}\n\n/* Validator utils */\n\n/**\n * Adds minLength validator\n */\nconst addMinLengthValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n },\n { isDraft }: ValidatorOptions\n) => {\n return attr.minLength && _.isInteger(attr.minLength) && !isDraft\n ? validator.min(attr.minLength)\n : validator;\n};\n\n/**\n * Adds maxLength validator\n * @returns {StringSchema}\n */\nconst addMaxLengthValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return attr.maxLength && _.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;\n};\n\n/**\n * Adds min integer validator\n * @returns {NumberSchema}\n */\nconst addMinIntegerValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator);\n\n/**\n * Adds max integer validator\n */\nconst addMaxIntegerValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator);\n\n/**\n * Adds min float/decimal validator\n */\nconst addMinFloatValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.min) ? validator.min(attr.min) : validator);\n\n/**\n * Adds max float/decimal validator\n */\nconst addMaxFloatValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.max) ? validator.max(attr.max) : validator);\n\n/**\n * Adds regex validator\n */\nconst addStringRegexValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return 'regex' in attr && !_.isUndefined(attr.regex)\n ? validator.matches(new RegExp(attr.regex), { excludeEmptyString: !attr.required })\n : validator;\n};\n\n/**\n * Adds unique validator\n */\nconst addUniqueValidator = <T extends strapiUtils.yup.AnySchema>(\n validator: T,\n {\n attr,\n model,\n updatedAttribute,\n entity,\n }: ValidatorMetas<Schema.Attribute.AnyAttribute & Schema.Attribute.UniqueOption>,\n options: ValidatorOptions\n): T => {\n if (attr.type !== 'uid' && !attr.unique) {\n return validator;\n }\n\n return validator.test('unique', 'This attribute must be unique', async (value) => {\n const isPublish = options.isDraft === false;\n\n /**\n * If the attribute value is `null` we want to skip the unique validation.\n * Otherwise it'll only accept a single `null` entry in the database.\n */\n if (_.isNil(value)) {\n return true;\n }\n\n /**\n * If we are updating a draft and the value is unchanged we skip the unique verification. This will\n * prevent the validator to be triggered in case the user activated the\n * unique constraint after already creating multiple entries with\n * the same attribute value for that field.\n */\n if (!isPublish && value === entity?.[updatedAttribute.name]) {\n return true;\n }\n\n /**\n * At this point we know that we are creating a new entry, publishing an entry or that the unique field value has changed\n * We check if there is an entry of this content type in the same locale, publication state and with the same unique field value\n */\n const record = await strapi.db.query(model.uid).findOne({\n where: {\n locale: options.locale,\n publishedAt: options.isDraft ? null : { $notNull: true },\n [updatedAttribute.name]: value,\n ...(entity?.id ? { id: { $ne: entity.id } } : {}),\n },\n });\n\n return !record;\n });\n};\n\n/* Type validators */\n\nconst stringValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID\n >,\n options: ValidatorOptions\n) => {\n let schema = yup.string().transform((val, originalVal) => originalVal);\n\n schema = addMinLengthValidator(schema, metas, options);\n schema = addMaxLengthValidator(schema, metas);\n schema = addStringRegexValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst emailValidator = (\n metas: ValidatorMetas<Schema.Attribute.Email>,\n options: ValidatorOptions\n) => {\n const schema = stringValidator(metas, options);\n return schema.email().min(1, '${path} cannot be empty');\n};\n\nconst uidValidator = (metas: ValidatorMetas<Schema.Attribute.UID>, options: ValidatorOptions) => {\n const schema = stringValidator(metas, options);\n\n return schema.matches(/^[A-Za-z0-9-_.~]*$/);\n};\n\nconst enumerationValidator = ({ attr }: { attr: Schema.Attribute.Enumeration }) => {\n return yup\n .string()\n .oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null as any));\n};\n\nconst integerValidator = (\n metas: ValidatorMetas<Schema.Attribute.Integer | Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n let schema = yup.number().integer();\n\n schema = addMinIntegerValidator(schema, metas);\n schema = addMaxIntegerValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst floatValidator = (\n metas: ValidatorMetas<Schema.Attribute.Decimal | Schema.Attribute.Float>,\n options: ValidatorOptions\n) => {\n let schema = yup.number();\n schema = addMinFloatValidator(schema, metas);\n schema = addMaxFloatValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst bigintegerValidator = (\n metas: ValidatorMetas<Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nconst datesValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.Date\n | Schema.Attribute.DateTime\n | Schema.Attribute.Time\n | Schema.Attribute.Timestamp\n >,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nexport default {\n string: stringValidator,\n text: stringValidator,\n richtext: stringValidator,\n password: stringValidator,\n email: emailValidator,\n enumeration: enumerationValidator,\n boolean: () => yup.boolean(),\n uid: uidValidator,\n json: () => yup.mixed(),\n integer: integerValidator,\n biginteger: bigintegerValidator,\n float: floatValidator,\n decimal: floatValidator,\n date: datesValidator,\n time: datesValidator,\n datetime: datesValidator,\n timestamp: datesValidator,\n blocks: blocksValidator,\n};\n"],"names":["strapiUtils","_"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"validators.js","sources":["../../../src/services/entity-validator/validators.ts"],"sourcesContent":["import _ from 'lodash';\nimport { yup } from '@strapi/utils';\nimport type { Schema, Struct, Modules } from '@strapi/types';\nimport blocksValidator from './blocks-validator';\n\nimport type { ComponentContext } from '.';\n\ninterface ValidatorMetas<TAttribute extends Schema.Attribute.AnyAttribute> {\n attr: TAttribute;\n model: Struct.ContentTypeSchema;\n updatedAttribute: { name: string; value: unknown };\n entity: Modules.EntityValidator.Entity;\n componentContext: ComponentContext;\n}\n\ninterface ValidatorOptions {\n isDraft: boolean;\n locale?: string;\n}\n\n/* Validator utils */\n\n/**\n * Adds minLength validator\n */\nconst addMinLengthValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n },\n { isDraft }: ValidatorOptions\n) => {\n return attr.minLength && _.isInteger(attr.minLength) && !isDraft\n ? validator.min(attr.minLength)\n : validator;\n};\n\n/**\n * Adds maxLength validator\n * @returns {StringSchema}\n */\nconst addMaxLengthValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return attr.maxLength && _.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;\n};\n\n/**\n * Adds min integer validator\n * @returns {NumberSchema}\n */\nconst addMinIntegerValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator);\n\n/**\n * Adds max integer validator\n */\nconst addMaxIntegerValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator);\n\n/**\n * Adds min float/decimal validator\n */\nconst addMinFloatValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.min) ? validator.min(attr.min) : validator);\n\n/**\n * Adds max float/decimal validator\n */\nconst addMaxFloatValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.max) ? validator.max(attr.max) : validator);\n\n/**\n * Adds regex validator\n */\nconst addStringRegexValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return 'regex' in attr && !_.isUndefined(attr.regex)\n ? validator.matches(new RegExp(attr.regex), { excludeEmptyString: !attr.required })\n : validator;\n};\n\n/**\n * Adds unique validator\n */\nconst addUniqueValidator = <T extends yup.AnySchema>(\n validator: T,\n {\n attr,\n model,\n updatedAttribute,\n entity,\n componentContext,\n }: ValidatorMetas<Schema.Attribute.AnyAttribute & Schema.Attribute.UniqueOption>,\n options: ValidatorOptions\n): T => {\n if (attr.type !== 'uid' && !attr.unique) {\n return validator;\n }\n\n return validator.test('unique', 'This attribute must be unique', async (value) => {\n const isPublish = options.isDraft === false;\n\n /**\n * If the attribute value is `null` we want to skip the unique validation.\n * Otherwise it'll only accept a single `null` entry in the database.\n */\n if (_.isNil(value)) {\n return true;\n }\n\n /**\n * If we are updating a draft and the value is unchanged we skip the unique verification. This will\n * prevent the validator to be triggered in case the user activated the\n * unique constraint after already creating multiple entries with\n * the same attribute value for that field.\n */\n if (!isPublish && value === entity?.[updatedAttribute.name]) {\n return true;\n }\n\n let queryUid: string;\n let queryWhere: Record<string, any> = {};\n\n if (componentContext) {\n const hasRepeatableData = componentContext.repeatableData.length > 0;\n if (hasRepeatableData) {\n // If we are validating a unique field within a repeatable component,\n // we first need to ensure that the repeatable in the current entity is\n // valid against itself.\n\n const { name: updatedName, value: updatedValue } = updatedAttribute;\n // Construct the full path to the unique field within the component.\n const pathToCheck = [...componentContext.pathToComponent.slice(1), updatedName].join('.');\n\n // Extract the values from the repeatable data using the constructed path\n const values = componentContext.repeatableData.map((item) => {\n return pathToCheck.split('.').reduce((acc, key) => acc[key], item as any);\n });\n\n // Check if the value is repeated in the current entity\n const isUpdatedAttributeRepeatedInThisEntity =\n values.filter((value) => value === updatedValue).length > 1;\n\n if (isUpdatedAttributeRepeatedInThisEntity) {\n return false;\n }\n }\n\n /**\n * When `componentContext` is present it means we are dealing with a unique\n * field within a component.\n *\n * The unique validation must consider the specific context of the\n * component, which will always be contained within a parent content type\n * and may also be nested within another component.\n *\n * We construct a query that takes into account the parent's model UID,\n * dimensions (such as draft and publish state/locale) and excludes the current\n * content type entity by its ID if provided.\n */\n const {\n model: parentModel,\n options: parentOptions,\n id: excludeId,\n } = componentContext.parentContent;\n queryUid = parentModel.uid;\n\n const whereConditions: Record<string, any> = {};\n const isParentDraft = parentOptions && parentOptions.isDraft;\n\n whereConditions.publishedAt = isParentDraft ? null : { $notNull: true };\n\n if (parentOptions?.locale) {\n whereConditions.locale = parentOptions.locale;\n }\n\n if (excludeId && !Number.isNaN(excludeId)) {\n whereConditions.id = { $ne: excludeId };\n }\n\n queryWhere = {\n ...componentContext.pathToComponent.reduceRight((acc, key) => ({ [key]: acc }), {\n [updatedAttribute.name]: value,\n }),\n\n ...whereConditions,\n };\n } else {\n /**\n * Here we are validating a scalar unique field from the content type's schema.\n * We construct a query to check if the value is unique\n * considering dimensions (e.g. locale, publication state) and excluding the current entity by its ID if available.\n */\n queryUid = model.uid;\n const scalarAttributeWhere: Record<string, any> = {\n [updatedAttribute.name]: value,\n };\n\n scalarAttributeWhere.publishedAt = options.isDraft ? null : { $notNull: true };\n\n if (options?.locale) {\n scalarAttributeWhere.locale = options.locale;\n }\n\n if (entity?.id) {\n scalarAttributeWhere.id = { $ne: entity.id };\n }\n\n queryWhere = scalarAttributeWhere;\n }\n\n // The validation should pass if there is no other record found from the query\n // TODO query not working for dynamic zones (type === relation)\n return !(await strapi.db.query(queryUid).findOne({ where: queryWhere }));\n });\n};\n\n/* Type validators */\n\nconst stringValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID\n >,\n options: ValidatorOptions\n) => {\n let schema = yup.string().transform((val, originalVal) => originalVal);\n\n schema = addMinLengthValidator(schema, metas, options);\n schema = addMaxLengthValidator(schema, metas);\n schema = addStringRegexValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst emailValidator = (\n metas: ValidatorMetas<Schema.Attribute.Email>,\n options: ValidatorOptions\n) => {\n const schema = stringValidator(metas, options);\n return schema.email().min(1, '${path} cannot be empty');\n};\n\nconst uidValidator = (metas: ValidatorMetas<Schema.Attribute.UID>, options: ValidatorOptions) => {\n const schema = stringValidator(metas, options);\n\n return schema.matches(/^[A-Za-z0-9-_.~]*$/);\n};\n\nconst enumerationValidator = ({ attr }: { attr: Schema.Attribute.Enumeration }) => {\n return yup\n .string()\n .oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null as any));\n};\n\nconst integerValidator = (\n metas: ValidatorMetas<Schema.Attribute.Integer | Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n let schema = yup.number().integer();\n\n schema = addMinIntegerValidator(schema, metas);\n schema = addMaxIntegerValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst floatValidator = (\n metas: ValidatorMetas<Schema.Attribute.Decimal | Schema.Attribute.Float>,\n options: ValidatorOptions\n) => {\n let schema = yup.number();\n schema = addMinFloatValidator(schema, metas);\n schema = addMaxFloatValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst bigintegerValidator = (\n metas: ValidatorMetas<Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nconst datesValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.Date\n | Schema.Attribute.DateTime\n | Schema.Attribute.Time\n | Schema.Attribute.Timestamp\n >,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nexport default {\n string: stringValidator,\n text: stringValidator,\n richtext: stringValidator,\n password: stringValidator,\n email: emailValidator,\n enumeration: enumerationValidator,\n boolean: () => yup.boolean(),\n uid: uidValidator,\n json: () => yup.mixed(),\n integer: integerValidator,\n biginteger: bigintegerValidator,\n float: floatValidator,\n decimal: floatValidator,\n date: datesValidator,\n time: datesValidator,\n datetime: datesValidator,\n timestamp: datesValidator,\n blocks: blocksValidator,\n};\n"],"names":["_","value","yup"],"mappings":"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"}
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import _ from "lodash";
|
2
|
-
import
|
2
|
+
import { yup } from "@strapi/utils";
|
3
3
|
import blocksValidator from "./blocks-validator.mjs";
|
4
|
-
const { yup } = strapiUtils;
|
5
4
|
const addMinLengthValidator = (validator, {
|
6
5
|
attr
|
7
6
|
}, { isDraft }) => {
|
@@ -33,7 +32,8 @@ const addUniqueValidator = (validator, {
|
|
33
32
|
attr,
|
34
33
|
model,
|
35
34
|
updatedAttribute,
|
36
|
-
entity
|
35
|
+
entity,
|
36
|
+
componentContext
|
37
37
|
}, options) => {
|
38
38
|
if (attr.type !== "uid" && !attr.unique) {
|
39
39
|
return validator;
|
@@ -46,15 +46,57 @@ const addUniqueValidator = (validator, {
|
|
46
46
|
if (!isPublish && value === entity?.[updatedAttribute.name]) {
|
47
47
|
return true;
|
48
48
|
}
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
let queryUid;
|
50
|
+
let queryWhere = {};
|
51
|
+
if (componentContext) {
|
52
|
+
const hasRepeatableData = componentContext.repeatableData.length > 0;
|
53
|
+
if (hasRepeatableData) {
|
54
|
+
const { name: updatedName, value: updatedValue } = updatedAttribute;
|
55
|
+
const pathToCheck = [...componentContext.pathToComponent.slice(1), updatedName].join(".");
|
56
|
+
const values = componentContext.repeatableData.map((item) => {
|
57
|
+
return pathToCheck.split(".").reduce((acc, key) => acc[key], item);
|
58
|
+
});
|
59
|
+
const isUpdatedAttributeRepeatedInThisEntity = values.filter((value2) => value2 === updatedValue).length > 1;
|
60
|
+
if (isUpdatedAttributeRepeatedInThisEntity) {
|
61
|
+
return false;
|
62
|
+
}
|
55
63
|
}
|
56
|
-
|
57
|
-
|
64
|
+
const {
|
65
|
+
model: parentModel,
|
66
|
+
options: parentOptions,
|
67
|
+
id: excludeId
|
68
|
+
} = componentContext.parentContent;
|
69
|
+
queryUid = parentModel.uid;
|
70
|
+
const whereConditions = {};
|
71
|
+
const isParentDraft = parentOptions && parentOptions.isDraft;
|
72
|
+
whereConditions.publishedAt = isParentDraft ? null : { $notNull: true };
|
73
|
+
if (parentOptions?.locale) {
|
74
|
+
whereConditions.locale = parentOptions.locale;
|
75
|
+
}
|
76
|
+
if (excludeId && !Number.isNaN(excludeId)) {
|
77
|
+
whereConditions.id = { $ne: excludeId };
|
78
|
+
}
|
79
|
+
queryWhere = {
|
80
|
+
...componentContext.pathToComponent.reduceRight((acc, key) => ({ [key]: acc }), {
|
81
|
+
[updatedAttribute.name]: value
|
82
|
+
}),
|
83
|
+
...whereConditions
|
84
|
+
};
|
85
|
+
} else {
|
86
|
+
queryUid = model.uid;
|
87
|
+
const scalarAttributeWhere = {
|
88
|
+
[updatedAttribute.name]: value
|
89
|
+
};
|
90
|
+
scalarAttributeWhere.publishedAt = options.isDraft ? null : { $notNull: true };
|
91
|
+
if (options?.locale) {
|
92
|
+
scalarAttributeWhere.locale = options.locale;
|
93
|
+
}
|
94
|
+
if (entity?.id) {
|
95
|
+
scalarAttributeWhere.id = { $ne: entity.id };
|
96
|
+
}
|
97
|
+
queryWhere = scalarAttributeWhere;
|
98
|
+
}
|
99
|
+
return !await strapi.db.query(queryUid).findOne({ where: queryWhere });
|
58
100
|
});
|
59
101
|
};
|
60
102
|
const stringValidator = (metas, options) => {
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"validators.mjs","sources":["../../../src/services/entity-validator/validators.ts"],"sourcesContent":["import _ from 'lodash';\nimport strapiUtils from '@strapi/utils';\nimport type { Schema, Struct } from '@strapi/types';\nimport blocksValidator from './blocks-validator';\n\nconst { yup } = strapiUtils;\n\ninterface ValidatorMetas<TAttribute extends Schema.Attribute.AnyAttribute> {\n attr: TAttribute;\n model: Struct.ContentTypeSchema;\n updatedAttribute: { name: string; value: unknown };\n entity: Record<string, unknown> | null;\n}\n\ninterface ValidatorOptions {\n isDraft: boolean;\n locale?: string;\n}\n\n/* Validator utils */\n\n/**\n * Adds minLength validator\n */\nconst addMinLengthValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n },\n { isDraft }: ValidatorOptions\n) => {\n return attr.minLength && _.isInteger(attr.minLength) && !isDraft\n ? validator.min(attr.minLength)\n : validator;\n};\n\n/**\n * Adds maxLength validator\n * @returns {StringSchema}\n */\nconst addMaxLengthValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return attr.maxLength && _.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;\n};\n\n/**\n * Adds min integer validator\n * @returns {NumberSchema}\n */\nconst addMinIntegerValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator);\n\n/**\n * Adds max integer validator\n */\nconst addMaxIntegerValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator);\n\n/**\n * Adds min float/decimal validator\n */\nconst addMinFloatValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.min) ? validator.min(attr.min) : validator);\n\n/**\n * Adds max float/decimal validator\n */\nconst addMaxFloatValidator = (\n validator: strapiUtils.yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.max) ? validator.max(attr.max) : validator);\n\n/**\n * Adds regex validator\n */\nconst addStringRegexValidator = (\n validator: strapiUtils.yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return 'regex' in attr && !_.isUndefined(attr.regex)\n ? validator.matches(new RegExp(attr.regex), { excludeEmptyString: !attr.required })\n : validator;\n};\n\n/**\n * Adds unique validator\n */\nconst addUniqueValidator = <T extends strapiUtils.yup.AnySchema>(\n validator: T,\n {\n attr,\n model,\n updatedAttribute,\n entity,\n }: ValidatorMetas<Schema.Attribute.AnyAttribute & Schema.Attribute.UniqueOption>,\n options: ValidatorOptions\n): T => {\n if (attr.type !== 'uid' && !attr.unique) {\n return validator;\n }\n\n return validator.test('unique', 'This attribute must be unique', async (value) => {\n const isPublish = options.isDraft === false;\n\n /**\n * If the attribute value is `null` we want to skip the unique validation.\n * Otherwise it'll only accept a single `null` entry in the database.\n */\n if (_.isNil(value)) {\n return true;\n }\n\n /**\n * If we are updating a draft and the value is unchanged we skip the unique verification. This will\n * prevent the validator to be triggered in case the user activated the\n * unique constraint after already creating multiple entries with\n * the same attribute value for that field.\n */\n if (!isPublish && value === entity?.[updatedAttribute.name]) {\n return true;\n }\n\n /**\n * At this point we know that we are creating a new entry, publishing an entry or that the unique field value has changed\n * We check if there is an entry of this content type in the same locale, publication state and with the same unique field value\n */\n const record = await strapi.db.query(model.uid).findOne({\n where: {\n locale: options.locale,\n publishedAt: options.isDraft ? null : { $notNull: true },\n [updatedAttribute.name]: value,\n ...(entity?.id ? { id: { $ne: entity.id } } : {}),\n },\n });\n\n return !record;\n });\n};\n\n/* Type validators */\n\nconst stringValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID\n >,\n options: ValidatorOptions\n) => {\n let schema = yup.string().transform((val, originalVal) => originalVal);\n\n schema = addMinLengthValidator(schema, metas, options);\n schema = addMaxLengthValidator(schema, metas);\n schema = addStringRegexValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst emailValidator = (\n metas: ValidatorMetas<Schema.Attribute.Email>,\n options: ValidatorOptions\n) => {\n const schema = stringValidator(metas, options);\n return schema.email().min(1, '${path} cannot be empty');\n};\n\nconst uidValidator = (metas: ValidatorMetas<Schema.Attribute.UID>, options: ValidatorOptions) => {\n const schema = stringValidator(metas, options);\n\n return schema.matches(/^[A-Za-z0-9-_.~]*$/);\n};\n\nconst enumerationValidator = ({ attr }: { attr: Schema.Attribute.Enumeration }) => {\n return yup\n .string()\n .oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null as any));\n};\n\nconst integerValidator = (\n metas: ValidatorMetas<Schema.Attribute.Integer | Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n let schema = yup.number().integer();\n\n schema = addMinIntegerValidator(schema, metas);\n schema = addMaxIntegerValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst floatValidator = (\n metas: ValidatorMetas<Schema.Attribute.Decimal | Schema.Attribute.Float>,\n options: ValidatorOptions\n) => {\n let schema = yup.number();\n schema = addMinFloatValidator(schema, metas);\n schema = addMaxFloatValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst bigintegerValidator = (\n metas: ValidatorMetas<Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nconst datesValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.Date\n | Schema.Attribute.DateTime\n | Schema.Attribute.Time\n | Schema.Attribute.Timestamp\n >,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nexport default {\n string: stringValidator,\n text: stringValidator,\n richtext: stringValidator,\n password: stringValidator,\n email: emailValidator,\n enumeration: enumerationValidator,\n boolean: () => yup.boolean(),\n uid: uidValidator,\n json: () => yup.mixed(),\n integer: integerValidator,\n biginteger: bigintegerValidator,\n float: floatValidator,\n decimal: floatValidator,\n date: datesValidator,\n time: datesValidator,\n datetime: datesValidator,\n timestamp: datesValidator,\n blocks: blocksValidator,\n};\n"],"names":[],"mappings":"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"}
|
1
|
+
{"version":3,"file":"validators.mjs","sources":["../../../src/services/entity-validator/validators.ts"],"sourcesContent":["import _ from 'lodash';\nimport { yup } from '@strapi/utils';\nimport type { Schema, Struct, Modules } from '@strapi/types';\nimport blocksValidator from './blocks-validator';\n\nimport type { ComponentContext } from '.';\n\ninterface ValidatorMetas<TAttribute extends Schema.Attribute.AnyAttribute> {\n attr: TAttribute;\n model: Struct.ContentTypeSchema;\n updatedAttribute: { name: string; value: unknown };\n entity: Modules.EntityValidator.Entity;\n componentContext: ComponentContext;\n}\n\ninterface ValidatorOptions {\n isDraft: boolean;\n locale?: string;\n}\n\n/* Validator utils */\n\n/**\n * Adds minLength validator\n */\nconst addMinLengthValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n },\n { isDraft }: ValidatorOptions\n) => {\n return attr.minLength && _.isInteger(attr.minLength) && !isDraft\n ? validator.min(attr.minLength)\n : validator;\n};\n\n/**\n * Adds maxLength validator\n * @returns {StringSchema}\n */\nconst addMaxLengthValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return attr.maxLength && _.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;\n};\n\n/**\n * Adds min integer validator\n * @returns {NumberSchema}\n */\nconst addMinIntegerValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator);\n\n/**\n * Adds max integer validator\n */\nconst addMaxIntegerValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Integer | Schema.Attribute.BigInteger;\n }\n) => (_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator);\n\n/**\n * Adds min float/decimal validator\n */\nconst addMinFloatValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.min) ? validator.min(attr.min) : validator);\n\n/**\n * Adds max float/decimal validator\n */\nconst addMaxFloatValidator = (\n validator: yup.NumberSchema,\n {\n attr,\n }: {\n attr: Schema.Attribute.Decimal | Schema.Attribute.Float;\n }\n) => (_.isNumber(attr.max) ? validator.max(attr.max) : validator);\n\n/**\n * Adds regex validator\n */\nconst addStringRegexValidator = (\n validator: yup.StringSchema,\n {\n attr,\n }: {\n attr:\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID;\n }\n) => {\n return 'regex' in attr && !_.isUndefined(attr.regex)\n ? validator.matches(new RegExp(attr.regex), { excludeEmptyString: !attr.required })\n : validator;\n};\n\n/**\n * Adds unique validator\n */\nconst addUniqueValidator = <T extends yup.AnySchema>(\n validator: T,\n {\n attr,\n model,\n updatedAttribute,\n entity,\n componentContext,\n }: ValidatorMetas<Schema.Attribute.AnyAttribute & Schema.Attribute.UniqueOption>,\n options: ValidatorOptions\n): T => {\n if (attr.type !== 'uid' && !attr.unique) {\n return validator;\n }\n\n return validator.test('unique', 'This attribute must be unique', async (value) => {\n const isPublish = options.isDraft === false;\n\n /**\n * If the attribute value is `null` we want to skip the unique validation.\n * Otherwise it'll only accept a single `null` entry in the database.\n */\n if (_.isNil(value)) {\n return true;\n }\n\n /**\n * If we are updating a draft and the value is unchanged we skip the unique verification. This will\n * prevent the validator to be triggered in case the user activated the\n * unique constraint after already creating multiple entries with\n * the same attribute value for that field.\n */\n if (!isPublish && value === entity?.[updatedAttribute.name]) {\n return true;\n }\n\n let queryUid: string;\n let queryWhere: Record<string, any> = {};\n\n if (componentContext) {\n const hasRepeatableData = componentContext.repeatableData.length > 0;\n if (hasRepeatableData) {\n // If we are validating a unique field within a repeatable component,\n // we first need to ensure that the repeatable in the current entity is\n // valid against itself.\n\n const { name: updatedName, value: updatedValue } = updatedAttribute;\n // Construct the full path to the unique field within the component.\n const pathToCheck = [...componentContext.pathToComponent.slice(1), updatedName].join('.');\n\n // Extract the values from the repeatable data using the constructed path\n const values = componentContext.repeatableData.map((item) => {\n return pathToCheck.split('.').reduce((acc, key) => acc[key], item as any);\n });\n\n // Check if the value is repeated in the current entity\n const isUpdatedAttributeRepeatedInThisEntity =\n values.filter((value) => value === updatedValue).length > 1;\n\n if (isUpdatedAttributeRepeatedInThisEntity) {\n return false;\n }\n }\n\n /**\n * When `componentContext` is present it means we are dealing with a unique\n * field within a component.\n *\n * The unique validation must consider the specific context of the\n * component, which will always be contained within a parent content type\n * and may also be nested within another component.\n *\n * We construct a query that takes into account the parent's model UID,\n * dimensions (such as draft and publish state/locale) and excludes the current\n * content type entity by its ID if provided.\n */\n const {\n model: parentModel,\n options: parentOptions,\n id: excludeId,\n } = componentContext.parentContent;\n queryUid = parentModel.uid;\n\n const whereConditions: Record<string, any> = {};\n const isParentDraft = parentOptions && parentOptions.isDraft;\n\n whereConditions.publishedAt = isParentDraft ? null : { $notNull: true };\n\n if (parentOptions?.locale) {\n whereConditions.locale = parentOptions.locale;\n }\n\n if (excludeId && !Number.isNaN(excludeId)) {\n whereConditions.id = { $ne: excludeId };\n }\n\n queryWhere = {\n ...componentContext.pathToComponent.reduceRight((acc, key) => ({ [key]: acc }), {\n [updatedAttribute.name]: value,\n }),\n\n ...whereConditions,\n };\n } else {\n /**\n * Here we are validating a scalar unique field from the content type's schema.\n * We construct a query to check if the value is unique\n * considering dimensions (e.g. locale, publication state) and excluding the current entity by its ID if available.\n */\n queryUid = model.uid;\n const scalarAttributeWhere: Record<string, any> = {\n [updatedAttribute.name]: value,\n };\n\n scalarAttributeWhere.publishedAt = options.isDraft ? null : { $notNull: true };\n\n if (options?.locale) {\n scalarAttributeWhere.locale = options.locale;\n }\n\n if (entity?.id) {\n scalarAttributeWhere.id = { $ne: entity.id };\n }\n\n queryWhere = scalarAttributeWhere;\n }\n\n // The validation should pass if there is no other record found from the query\n // TODO query not working for dynamic zones (type === relation)\n return !(await strapi.db.query(queryUid).findOne({ where: queryWhere }));\n });\n};\n\n/* Type validators */\n\nconst stringValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.String\n | Schema.Attribute.Text\n | Schema.Attribute.RichText\n | Schema.Attribute.Password\n | Schema.Attribute.Email\n | Schema.Attribute.UID\n >,\n options: ValidatorOptions\n) => {\n let schema = yup.string().transform((val, originalVal) => originalVal);\n\n schema = addMinLengthValidator(schema, metas, options);\n schema = addMaxLengthValidator(schema, metas);\n schema = addStringRegexValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst emailValidator = (\n metas: ValidatorMetas<Schema.Attribute.Email>,\n options: ValidatorOptions\n) => {\n const schema = stringValidator(metas, options);\n return schema.email().min(1, '${path} cannot be empty');\n};\n\nconst uidValidator = (metas: ValidatorMetas<Schema.Attribute.UID>, options: ValidatorOptions) => {\n const schema = stringValidator(metas, options);\n\n return schema.matches(/^[A-Za-z0-9-_.~]*$/);\n};\n\nconst enumerationValidator = ({ attr }: { attr: Schema.Attribute.Enumeration }) => {\n return yup\n .string()\n .oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null as any));\n};\n\nconst integerValidator = (\n metas: ValidatorMetas<Schema.Attribute.Integer | Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n let schema = yup.number().integer();\n\n schema = addMinIntegerValidator(schema, metas);\n schema = addMaxIntegerValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst floatValidator = (\n metas: ValidatorMetas<Schema.Attribute.Decimal | Schema.Attribute.Float>,\n options: ValidatorOptions\n) => {\n let schema = yup.number();\n schema = addMinFloatValidator(schema, metas);\n schema = addMaxFloatValidator(schema, metas);\n schema = addUniqueValidator(schema, metas, options);\n\n return schema;\n};\n\nconst bigintegerValidator = (\n metas: ValidatorMetas<Schema.Attribute.BigInteger>,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nconst datesValidator = (\n metas: ValidatorMetas<\n | Schema.Attribute.Date\n | Schema.Attribute.DateTime\n | Schema.Attribute.Time\n | Schema.Attribute.Timestamp\n >,\n options: ValidatorOptions\n) => {\n const schema = yup.mixed();\n return addUniqueValidator(schema, metas, options);\n};\n\nexport default {\n string: stringValidator,\n text: stringValidator,\n richtext: stringValidator,\n password: stringValidator,\n email: emailValidator,\n enumeration: enumerationValidator,\n boolean: () => yup.boolean(),\n uid: uidValidator,\n json: () => yup.mixed(),\n integer: integerValidator,\n biginteger: bigintegerValidator,\n float: floatValidator,\n decimal: floatValidator,\n date: datesValidator,\n time: datesValidator,\n datetime: datesValidator,\n timestamp: datesValidator,\n blocks: blocksValidator,\n};\n"],"names":["value"],"mappings":"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"}
|
@@ -17,7 +17,7 @@ const healthCheck = async (ctx) => {
|
|
17
17
|
};
|
18
18
|
const createServer = (strapi) => {
|
19
19
|
const app = koa({
|
20
|
-
proxy: strapi.config.get("server.proxy"),
|
20
|
+
proxy: strapi.config.get("server.proxy.koa"),
|
21
21
|
keys: strapi.config.get("server.app.keys")
|
22
22
|
});
|
23
23
|
app.use((ctx, next) => requestContext.run(ctx, () => next()));
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/services/server/index.ts"],"sourcesContent":["import Router from '@koa/router';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { createHTTPServer } from './http-server';\nimport { createRouteManager } from './routing';\nimport { createAdminAPI } from './admin-api';\nimport { createContentAPI } from './content-api';\nimport registerAllRoutes from './register-routes';\nimport registerApplicationMiddlewares from './register-middlewares';\nimport createKoaApp from './koa';\nimport requestCtx from '../request-context';\n\nconst healthCheck: Core.MiddlewareHandler = async (ctx) => {\n ctx.set('strapi', 'You are so French!');\n ctx.status = 204;\n};\n\nconst createServer = (strapi: Core.Strapi): Modules.Server.Server => {\n const app = createKoaApp({\n proxy: strapi.config.get('server.proxy'),\n keys: strapi.config.get('server.app.keys'),\n });\n\n app.use((ctx, next) => requestCtx.run(ctx, () => next()));\n\n const router = new Router();\n\n const routeManager = createRouteManager(strapi);\n\n const httpServer = createHTTPServer(strapi, app);\n\n const apis = {\n 'content-api': createContentAPI(strapi),\n admin: createAdminAPI(strapi),\n };\n\n // init health check\n router.all('/_health', healthCheck);\n\n const state = {\n mounted: false,\n };\n\n return {\n app,\n router,\n httpServer,\n\n api(name) {\n return apis[name];\n },\n\n use(...args) {\n app.use(...args);\n return this;\n },\n\n routes(routes: Core.Router | Omit<Core.Route, 'info'>[]) {\n if (!Array.isArray(routes) && routes.type) {\n const api = apis[routes.type];\n if (!api) {\n throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`);\n }\n\n apis[routes.type].routes(routes);\n return this;\n }\n\n routeManager.addRoutes(routes, router);\n return this;\n },\n\n mount() {\n state.mounted = true;\n\n Object.values(apis).forEach((api) => api.mount(router));\n app.use(router.routes()).use(router.allowedMethods());\n\n return this;\n },\n\n initRouting() {\n registerAllRoutes(strapi);\n\n return this;\n },\n\n async initMiddlewares() {\n await registerApplicationMiddlewares(strapi);\n\n return this;\n },\n\n listRoutes() {\n return [...router.stack];\n },\n\n listen(...args: any[]) {\n if (!state.mounted) {\n this.mount();\n }\n\n return httpServer.listen(...args);\n },\n\n async destroy() {\n await httpServer.destroy();\n },\n };\n};\n\nexport { createServer };\n"],"names":["createKoaApp","requestCtx","Router","createRouteManager","httpServer","createHTTPServer","createContentAPI","createAdminAPI","registerAllRoutes","registerApplicationMiddlewares"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,cAAsC,OAAO,QAAQ;AACrD,MAAA,IAAI,UAAU,oBAAoB;AACtC,MAAI,SAAS;AACf;AAEM,MAAA,eAAe,CAAC,WAA+C;AACnE,QAAM,MAAMA,IAAa;AAAA,IACvB,OAAO,OAAO,OAAO,IAAI,
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/services/server/index.ts"],"sourcesContent":["import Router from '@koa/router';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { createHTTPServer } from './http-server';\nimport { createRouteManager } from './routing';\nimport { createAdminAPI } from './admin-api';\nimport { createContentAPI } from './content-api';\nimport registerAllRoutes from './register-routes';\nimport registerApplicationMiddlewares from './register-middlewares';\nimport createKoaApp from './koa';\nimport requestCtx from '../request-context';\n\nconst healthCheck: Core.MiddlewareHandler = async (ctx) => {\n ctx.set('strapi', 'You are so French!');\n ctx.status = 204;\n};\n\nconst createServer = (strapi: Core.Strapi): Modules.Server.Server => {\n const app = createKoaApp({\n proxy: strapi.config.get('server.proxy.koa'),\n keys: strapi.config.get('server.app.keys'),\n });\n\n app.use((ctx, next) => requestCtx.run(ctx, () => next()));\n\n const router = new Router();\n\n const routeManager = createRouteManager(strapi);\n\n const httpServer = createHTTPServer(strapi, app);\n\n const apis = {\n 'content-api': createContentAPI(strapi),\n admin: createAdminAPI(strapi),\n };\n\n // init health check\n router.all('/_health', healthCheck);\n\n const state = {\n mounted: false,\n };\n\n return {\n app,\n router,\n httpServer,\n\n api(name) {\n return apis[name];\n },\n\n use(...args) {\n app.use(...args);\n return this;\n },\n\n routes(routes: Core.Router | Omit<Core.Route, 'info'>[]) {\n if (!Array.isArray(routes) && routes.type) {\n const api = apis[routes.type];\n if (!api) {\n throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`);\n }\n\n apis[routes.type].routes(routes);\n return this;\n }\n\n routeManager.addRoutes(routes, router);\n return this;\n },\n\n mount() {\n state.mounted = true;\n\n Object.values(apis).forEach((api) => api.mount(router));\n app.use(router.routes()).use(router.allowedMethods());\n\n return this;\n },\n\n initRouting() {\n registerAllRoutes(strapi);\n\n return this;\n },\n\n async initMiddlewares() {\n await registerApplicationMiddlewares(strapi);\n\n return this;\n },\n\n listRoutes() {\n return [...router.stack];\n },\n\n listen(...args: any[]) {\n if (!state.mounted) {\n this.mount();\n }\n\n return httpServer.listen(...args);\n },\n\n async destroy() {\n await httpServer.destroy();\n },\n };\n};\n\nexport { createServer };\n"],"names":["createKoaApp","requestCtx","Router","createRouteManager","httpServer","createHTTPServer","createContentAPI","createAdminAPI","registerAllRoutes","registerApplicationMiddlewares"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,cAAsC,OAAO,QAAQ;AACrD,MAAA,IAAI,UAAU,oBAAoB;AACtC,MAAI,SAAS;AACf;AAEM,MAAA,eAAe,CAAC,WAA+C;AACnE,QAAM,MAAMA,IAAa;AAAA,IACvB,OAAO,OAAO,OAAO,IAAI,kBAAkB;AAAA,IAC3C,MAAM,OAAO,OAAO,IAAI,iBAAiB;AAAA,EAAA,CAC1C;AAEG,MAAA,IAAI,CAAC,KAAK,SAASC,eAAW,IAAI,KAAK,MAAM,KAAM,CAAA,CAAC;AAElD,QAAA,SAAS,IAAIC,gBAAAA;AAEb,QAAA,eAAeC,2BAAmB,MAAM;AAExC,QAAAC,eAAaC,WAAAA,iBAAiB,QAAQ,GAAG;AAE/C,QAAM,OAAO;AAAA,IACX,eAAeC,4BAAiB,MAAM;AAAA,IACtC,OAAOC,wBAAe,MAAM;AAAA,EAAA;AAIvB,SAAA,IAAI,YAAY,WAAW;AAElC,QAAM,QAAQ;AAAA,IACZ,SAAS;AAAA,EAAA;AAGJ,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IAAA,YACAH;AAAAA,IAEA,IAAI,MAAM;AACR,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IAEA,OAAO,MAAM;AACP,UAAA,IAAI,GAAG,IAAI;AACR,aAAA;AAAA,IACT;AAAA,IAEA,OAAO,QAAkD;AACvD,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,MAAM;AACnC,cAAA,MAAM,KAAK,OAAO,IAAI;AAC5B,YAAI,CAAC,KAAK;AACF,gBAAA,IAAI,MAAM,OAAO,OAAO,IAAI,iCAAiC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,QACxF;AAEA,aAAK,OAAO,IAAI,EAAE,OAAO,MAAM;AACxB,eAAA;AAAA,MACT;AAEa,mBAAA,UAAU,QAAQ,MAAM;AAC9B,aAAA;AAAA,IACT;AAAA,IAEA,QAAQ;AACN,YAAM,UAAU;AAET,aAAA,OAAO,IAAI,EAAE,QAAQ,CAAC,QAAQ,IAAI,MAAM,MAAM,CAAC;AAClD,UAAA,IAAI,OAAO,OAAO,CAAC,EAAE,IAAI,OAAO,gBAAgB;AAE7C,aAAA;AAAA,IACT;AAAA,IAEA,cAAc;AACZI,qBAAkB,MAAM;AAEjB,aAAA;AAAA,IACT;AAAA,IAEA,MAAM,kBAAkB;AACtB,YAAMC,oBAA+B,MAAM;AAEpC,aAAA;AAAA,IACT;AAAA,IAEA,aAAa;AACJ,aAAA,CAAC,GAAG,OAAO,KAAK;AAAA,IACzB;AAAA,IAEA,UAAU,MAAa;AACjB,UAAA,CAAC,MAAM,SAAS;AAClB,aAAK,MAAM;AAAA,MACb;AAEO,aAAAL,aAAW,OAAO,GAAG,IAAI;AAAA,IAClC;AAAA,IAEA,MAAM,UAAU;AACd,YAAMA,aAAW;IACnB;AAAA,EAAA;AAEJ;;"}
|
@@ -13,7 +13,7 @@ const healthCheck = async (ctx) => {
|
|
13
13
|
};
|
14
14
|
const createServer = (strapi) => {
|
15
15
|
const app = createKoaApp({
|
16
|
-
proxy: strapi.config.get("server.proxy"),
|
16
|
+
proxy: strapi.config.get("server.proxy.koa"),
|
17
17
|
keys: strapi.config.get("server.app.keys")
|
18
18
|
});
|
19
19
|
app.use((ctx, next) => requestCtx.run(ctx, () => next()));
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../src/services/server/index.ts"],"sourcesContent":["import Router from '@koa/router';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { createHTTPServer } from './http-server';\nimport { createRouteManager } from './routing';\nimport { createAdminAPI } from './admin-api';\nimport { createContentAPI } from './content-api';\nimport registerAllRoutes from './register-routes';\nimport registerApplicationMiddlewares from './register-middlewares';\nimport createKoaApp from './koa';\nimport requestCtx from '../request-context';\n\nconst healthCheck: Core.MiddlewareHandler = async (ctx) => {\n ctx.set('strapi', 'You are so French!');\n ctx.status = 204;\n};\n\nconst createServer = (strapi: Core.Strapi): Modules.Server.Server => {\n const app = createKoaApp({\n proxy: strapi.config.get('server.proxy'),\n keys: strapi.config.get('server.app.keys'),\n });\n\n app.use((ctx, next) => requestCtx.run(ctx, () => next()));\n\n const router = new Router();\n\n const routeManager = createRouteManager(strapi);\n\n const httpServer = createHTTPServer(strapi, app);\n\n const apis = {\n 'content-api': createContentAPI(strapi),\n admin: createAdminAPI(strapi),\n };\n\n // init health check\n router.all('/_health', healthCheck);\n\n const state = {\n mounted: false,\n };\n\n return {\n app,\n router,\n httpServer,\n\n api(name) {\n return apis[name];\n },\n\n use(...args) {\n app.use(...args);\n return this;\n },\n\n routes(routes: Core.Router | Omit<Core.Route, 'info'>[]) {\n if (!Array.isArray(routes) && routes.type) {\n const api = apis[routes.type];\n if (!api) {\n throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`);\n }\n\n apis[routes.type].routes(routes);\n return this;\n }\n\n routeManager.addRoutes(routes, router);\n return this;\n },\n\n mount() {\n state.mounted = true;\n\n Object.values(apis).forEach((api) => api.mount(router));\n app.use(router.routes()).use(router.allowedMethods());\n\n return this;\n },\n\n initRouting() {\n registerAllRoutes(strapi);\n\n return this;\n },\n\n async initMiddlewares() {\n await registerApplicationMiddlewares(strapi);\n\n return this;\n },\n\n listRoutes() {\n return [...router.stack];\n },\n\n listen(...args: any[]) {\n if (!state.mounted) {\n this.mount();\n }\n\n return httpServer.listen(...args);\n },\n\n async destroy() {\n await httpServer.destroy();\n },\n };\n};\n\nexport { createServer };\n"],"names":[],"mappings":";;;;;;;;;AAYA,MAAM,cAAsC,OAAO,QAAQ;AACrD,MAAA,IAAI,UAAU,oBAAoB;AACtC,MAAI,SAAS;AACf;AAEM,MAAA,eAAe,CAAC,WAA+C;AACnE,QAAM,MAAM,aAAa;AAAA,IACvB,OAAO,OAAO,OAAO,IAAI,
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../src/services/server/index.ts"],"sourcesContent":["import Router from '@koa/router';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { createHTTPServer } from './http-server';\nimport { createRouteManager } from './routing';\nimport { createAdminAPI } from './admin-api';\nimport { createContentAPI } from './content-api';\nimport registerAllRoutes from './register-routes';\nimport registerApplicationMiddlewares from './register-middlewares';\nimport createKoaApp from './koa';\nimport requestCtx from '../request-context';\n\nconst healthCheck: Core.MiddlewareHandler = async (ctx) => {\n ctx.set('strapi', 'You are so French!');\n ctx.status = 204;\n};\n\nconst createServer = (strapi: Core.Strapi): Modules.Server.Server => {\n const app = createKoaApp({\n proxy: strapi.config.get('server.proxy.koa'),\n keys: strapi.config.get('server.app.keys'),\n });\n\n app.use((ctx, next) => requestCtx.run(ctx, () => next()));\n\n const router = new Router();\n\n const routeManager = createRouteManager(strapi);\n\n const httpServer = createHTTPServer(strapi, app);\n\n const apis = {\n 'content-api': createContentAPI(strapi),\n admin: createAdminAPI(strapi),\n };\n\n // init health check\n router.all('/_health', healthCheck);\n\n const state = {\n mounted: false,\n };\n\n return {\n app,\n router,\n httpServer,\n\n api(name) {\n return apis[name];\n },\n\n use(...args) {\n app.use(...args);\n return this;\n },\n\n routes(routes: Core.Router | Omit<Core.Route, 'info'>[]) {\n if (!Array.isArray(routes) && routes.type) {\n const api = apis[routes.type];\n if (!api) {\n throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`);\n }\n\n apis[routes.type].routes(routes);\n return this;\n }\n\n routeManager.addRoutes(routes, router);\n return this;\n },\n\n mount() {\n state.mounted = true;\n\n Object.values(apis).forEach((api) => api.mount(router));\n app.use(router.routes()).use(router.allowedMethods());\n\n return this;\n },\n\n initRouting() {\n registerAllRoutes(strapi);\n\n return this;\n },\n\n async initMiddlewares() {\n await registerApplicationMiddlewares(strapi);\n\n return this;\n },\n\n listRoutes() {\n return [...router.stack];\n },\n\n listen(...args: any[]) {\n if (!state.mounted) {\n this.mount();\n }\n\n return httpServer.listen(...args);\n },\n\n async destroy() {\n await httpServer.destroy();\n },\n };\n};\n\nexport { createServer };\n"],"names":[],"mappings":";;;;;;;;;AAYA,MAAM,cAAsC,OAAO,QAAQ;AACrD,MAAA,IAAI,UAAU,oBAAoB;AACtC,MAAI,SAAS;AACf;AAEM,MAAA,eAAe,CAAC,WAA+C;AACnE,QAAM,MAAM,aAAa;AAAA,IACvB,OAAO,OAAO,OAAO,IAAI,kBAAkB;AAAA,IAC3C,MAAM,OAAO,OAAO,IAAI,iBAAiB;AAAA,EAAA,CAC1C;AAEG,MAAA,IAAI,CAAC,KAAK,SAAS,WAAW,IAAI,KAAK,MAAM,KAAM,CAAA,CAAC;AAElD,QAAA,SAAS,IAAI;AAEb,QAAA,eAAe,mBAAmB,MAAM;AAExC,QAAA,aAAa,iBAAiB,QAAQ,GAAG;AAE/C,QAAM,OAAO;AAAA,IACX,eAAe,iBAAiB,MAAM;AAAA,IACtC,OAAO,eAAe,MAAM;AAAA,EAAA;AAIvB,SAAA,IAAI,YAAY,WAAW;AAElC,QAAM,QAAQ;AAAA,IACZ,SAAS;AAAA,EAAA;AAGJ,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IAEA,IAAI,MAAM;AACR,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IAEA,OAAO,MAAM;AACP,UAAA,IAAI,GAAG,IAAI;AACR,aAAA;AAAA,IACT;AAAA,IAEA,OAAO,QAAkD;AACvD,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,MAAM;AACnC,cAAA,MAAM,KAAK,OAAO,IAAI;AAC5B,YAAI,CAAC,KAAK;AACF,gBAAA,IAAI,MAAM,OAAO,OAAO,IAAI,iCAAiC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,QACxF;AAEA,aAAK,OAAO,IAAI,EAAE,OAAO,MAAM;AACxB,eAAA;AAAA,MACT;AAEa,mBAAA,UAAU,QAAQ,MAAM;AAC9B,aAAA;AAAA,IACT;AAAA,IAEA,QAAQ;AACN,YAAM,UAAU;AAET,aAAA,OAAO,IAAI,EAAE,QAAQ,CAAC,QAAQ,IAAI,MAAM,MAAM,CAAC;AAClD,UAAA,IAAI,OAAO,OAAO,CAAC,EAAE,IAAI,OAAO,gBAAgB;AAE7C,aAAA;AAAA,IACT;AAAA,IAEA,cAAc;AACZ,wBAAkB,MAAM;AAEjB,aAAA;AAAA,IACT;AAAA,IAEA,MAAM,kBAAkB;AACtB,YAAM,+BAA+B,MAAM;AAEpC,aAAA;AAAA,IACT;AAAA,IAEA,aAAa;AACJ,aAAA,CAAC,GAAG,OAAO,KAAK;AAAA,IACzB;AAAA,IAEA,UAAU,MAAa;AACjB,UAAA,CAAC,MAAM,SAAS;AAClB,aAAK,MAAM;AAAA,MACb;AAEO,aAAA,WAAW,OAAO,GAAG,IAAI;AAAA,IAClC;AAAA,IAEA,MAAM,UAAU;AACd,YAAM,WAAW;IACnB;AAAA,EAAA;AAEJ;"}
|
@@ -2,9 +2,10 @@
|
|
2
2
|
* The event hub is Strapi's event control center.
|
3
3
|
*/
|
4
4
|
import type { Logger } from '@strapi/logger';
|
5
|
-
import type {
|
5
|
+
import type { Modules } from '@strapi/types';
|
6
6
|
import type { EventHub } from './event-hub';
|
7
7
|
import type { Fetch } from '../utils/fetch';
|
8
|
+
type Webhook = Modules.WebhookStore.Webhook;
|
8
9
|
interface ConstructorParameters {
|
9
10
|
eventHub: EventHub;
|
10
11
|
logger: Logger;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webhook-runner.d.ts","sourceRoot":"","sources":["../../src/services/webhook-runner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
1
|
+
{"version":3,"file":"webhook-runner.d.ts","sourceRoot":"","sources":["../../src/services/webhook-runner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,KAAK,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC;AAM5C,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,KAAK,EAAE,KAAK,CAAC;CACd;AAED,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAUD,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAW;IAE3B,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,WAAW,CAAqC;IAExD,OAAO,CAAC,SAAS,CAAoC;IAErD,OAAO,CAAC,KAAK,CAA2B;IAExC,OAAO,CAAC,KAAK,CAAQ;gBAET,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAkB,EAAE,KAAK,EAAE,EAAE,qBAAqB;IAmBlF,cAAc,CAAC,KAAK,EAAE,MAAM;IAW5B,cAAc,CAAC,KAAK,EAAE,MAAM;IAgBtB,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK;IAa5C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,KAAK;;;;;;;;;;IAsC9C,GAAG,CAAC,OAAO,EAAE,OAAO;IAcpB,MAAM,CAAC,OAAO,EAAE,OAAO;IAMvB,MAAM,CAAC,OAAO,EAAE,OAAO;CAexB;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,aAAa,CAEtF;AAED,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webhook-runner.js","sources":["../../src/services/webhook-runner.ts"],"sourcesContent":["/**\n * The event hub is Strapi's event control center.\n */\n\nimport createdDebugger from 'debug';\nimport _ from 'lodash';\nimport type { Logger } from '@strapi/logger';\n\nimport
|
1
|
+
{"version":3,"file":"webhook-runner.js","sources":["../../src/services/webhook-runner.ts"],"sourcesContent":["/**\n * The event hub is Strapi's event control center.\n */\n\nimport createdDebugger from 'debug';\nimport _ from 'lodash';\nimport type { Logger } from '@strapi/logger';\n\nimport type { Modules } from '@strapi/types';\nimport WorkerQueue from './worker-queue';\nimport type { EventHub } from './event-hub';\nimport type { Fetch } from '../utils/fetch';\n\ntype Webhook = Modules.WebhookStore.Webhook;\n\ninterface Config {\n defaultHeaders: Record<string, string>;\n}\n\ninterface ConstructorParameters {\n eventHub: EventHub;\n logger: Logger;\n configuration?: Record<string, unknown>;\n fetch: Fetch;\n}\n\ninterface Event {\n event: string;\n info: Record<string, unknown>;\n}\n\ntype Listener = (info: Record<string, unknown>) => Promise<void>;\n\nconst debug = createdDebugger('strapi:webhook');\n\nconst defaultConfiguration: Config = {\n defaultHeaders: {},\n};\n\nclass WebhookRunner {\n private eventHub: EventHub;\n\n private logger: Logger;\n\n private config: Config;\n\n private webhooksMap: Map<string, Webhook[]> = new Map();\n\n private listeners: Map<string, Listener> = new Map();\n\n private queue: WorkerQueue<Event, void>;\n\n private fetch: Fetch;\n\n constructor({ eventHub, logger, configuration = {}, fetch }: ConstructorParameters) {\n debug('Initialized webhook runner');\n this.eventHub = eventHub;\n this.logger = logger;\n this.fetch = fetch;\n\n if (typeof configuration !== 'object') {\n throw new Error(\n 'Invalid configuration provided to the webhookRunner.\\nCheck your server.json -> webhooks configuration'\n );\n }\n\n this.config = _.merge(defaultConfiguration, configuration);\n\n this.queue = new WorkerQueue({ logger, concurrency: 5 });\n\n this.queue.subscribe(this.executeListener.bind(this));\n }\n\n deleteListener(event: string) {\n debug(`Deleting listener for event '${event}'`);\n\n const fn = this.listeners.get(event);\n\n if (fn !== undefined) {\n this.eventHub.off(event, fn);\n this.listeners.delete(event);\n }\n }\n\n createListener(event: string) {\n debug(`Creating listener for event '${event}'`);\n if (this.listeners.has(event)) {\n this.logger.error(\n `The webhook runner is already listening for the event '${event}'. Did you mean to call .register() ?`\n );\n }\n\n const listen = async (info: Event['info']) => {\n this.queue.enqueue({ event, info });\n };\n\n this.listeners.set(event, listen);\n this.eventHub.on(event, listen);\n }\n\n async executeListener({ event, info }: Event) {\n debug(`Executing webhook for event '${event}'`);\n const webhooks = this.webhooksMap.get(event) || [];\n const activeWebhooks = webhooks.filter((webhook) => webhook.isEnabled === true);\n\n for (const webhook of activeWebhooks) {\n await this.run(webhook, event, info).catch((error: unknown) => {\n this.logger.error('Error running webhook');\n this.logger.error(error);\n });\n }\n }\n\n run(webhook: Webhook, event: string, info = {}) {\n const { url, headers } = webhook;\n\n return this.fetch(url, {\n method: 'post',\n body: JSON.stringify({\n event,\n createdAt: new Date(),\n ...info,\n }),\n headers: {\n ...this.config.defaultHeaders,\n ...headers,\n 'X-Strapi-Event': event,\n 'Content-Type': 'application/json',\n },\n signal: AbortSignal.timeout(10000),\n })\n .then(async (res) => {\n if (res.ok) {\n return {\n statusCode: res.status,\n };\n }\n\n return {\n statusCode: res.status,\n message: await res.text(),\n };\n })\n .catch((err) => {\n return {\n statusCode: 500,\n message: err.message,\n };\n });\n }\n\n add(webhook: Webhook) {\n debug(`Registering webhook '${webhook.id}'`);\n const { events } = webhook;\n\n events.forEach((event) => {\n if (this.webhooksMap.has(event)) {\n this.webhooksMap.get(event)?.push(webhook);\n } else {\n this.webhooksMap.set(event, [webhook]);\n this.createListener(event);\n }\n });\n }\n\n update(webhook: Webhook) {\n debug(`Refreshing webhook '${webhook.id}'`);\n this.remove(webhook);\n this.add(webhook);\n }\n\n remove(webhook: Webhook) {\n debug(`Unregistering webhook '${webhook.id}'`);\n\n this.webhooksMap.forEach((webhooks, event) => {\n const filteredWebhooks = webhooks.filter((value) => value.id !== webhook.id);\n\n // Cleanup hanging listeners\n if (filteredWebhooks.length === 0) {\n this.webhooksMap.delete(event);\n this.deleteListener(event);\n } else {\n this.webhooksMap.set(event, filteredWebhooks);\n }\n });\n }\n}\n\n/**\n * Expose a factory function instead of the class\n */\nexport default function createWebhookRunner(opts: ConstructorParameters): WebhookRunner {\n return new WebhookRunner(opts);\n}\n\nexport type { WebhookRunner };\n"],"names":["createdDebugger","_","WorkerQueue"],"mappings":"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"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webhook-runner.mjs","sources":["../../src/services/webhook-runner.ts"],"sourcesContent":["/**\n * The event hub is Strapi's event control center.\n */\n\nimport createdDebugger from 'debug';\nimport _ from 'lodash';\nimport type { Logger } from '@strapi/logger';\n\nimport
|
1
|
+
{"version":3,"file":"webhook-runner.mjs","sources":["../../src/services/webhook-runner.ts"],"sourcesContent":["/**\n * The event hub is Strapi's event control center.\n */\n\nimport createdDebugger from 'debug';\nimport _ from 'lodash';\nimport type { Logger } from '@strapi/logger';\n\nimport type { Modules } from '@strapi/types';\nimport WorkerQueue from './worker-queue';\nimport type { EventHub } from './event-hub';\nimport type { Fetch } from '../utils/fetch';\n\ntype Webhook = Modules.WebhookStore.Webhook;\n\ninterface Config {\n defaultHeaders: Record<string, string>;\n}\n\ninterface ConstructorParameters {\n eventHub: EventHub;\n logger: Logger;\n configuration?: Record<string, unknown>;\n fetch: Fetch;\n}\n\ninterface Event {\n event: string;\n info: Record<string, unknown>;\n}\n\ntype Listener = (info: Record<string, unknown>) => Promise<void>;\n\nconst debug = createdDebugger('strapi:webhook');\n\nconst defaultConfiguration: Config = {\n defaultHeaders: {},\n};\n\nclass WebhookRunner {\n private eventHub: EventHub;\n\n private logger: Logger;\n\n private config: Config;\n\n private webhooksMap: Map<string, Webhook[]> = new Map();\n\n private listeners: Map<string, Listener> = new Map();\n\n private queue: WorkerQueue<Event, void>;\n\n private fetch: Fetch;\n\n constructor({ eventHub, logger, configuration = {}, fetch }: ConstructorParameters) {\n debug('Initialized webhook runner');\n this.eventHub = eventHub;\n this.logger = logger;\n this.fetch = fetch;\n\n if (typeof configuration !== 'object') {\n throw new Error(\n 'Invalid configuration provided to the webhookRunner.\\nCheck your server.json -> webhooks configuration'\n );\n }\n\n this.config = _.merge(defaultConfiguration, configuration);\n\n this.queue = new WorkerQueue({ logger, concurrency: 5 });\n\n this.queue.subscribe(this.executeListener.bind(this));\n }\n\n deleteListener(event: string) {\n debug(`Deleting listener for event '${event}'`);\n\n const fn = this.listeners.get(event);\n\n if (fn !== undefined) {\n this.eventHub.off(event, fn);\n this.listeners.delete(event);\n }\n }\n\n createListener(event: string) {\n debug(`Creating listener for event '${event}'`);\n if (this.listeners.has(event)) {\n this.logger.error(\n `The webhook runner is already listening for the event '${event}'. Did you mean to call .register() ?`\n );\n }\n\n const listen = async (info: Event['info']) => {\n this.queue.enqueue({ event, info });\n };\n\n this.listeners.set(event, listen);\n this.eventHub.on(event, listen);\n }\n\n async executeListener({ event, info }: Event) {\n debug(`Executing webhook for event '${event}'`);\n const webhooks = this.webhooksMap.get(event) || [];\n const activeWebhooks = webhooks.filter((webhook) => webhook.isEnabled === true);\n\n for (const webhook of activeWebhooks) {\n await this.run(webhook, event, info).catch((error: unknown) => {\n this.logger.error('Error running webhook');\n this.logger.error(error);\n });\n }\n }\n\n run(webhook: Webhook, event: string, info = {}) {\n const { url, headers } = webhook;\n\n return this.fetch(url, {\n method: 'post',\n body: JSON.stringify({\n event,\n createdAt: new Date(),\n ...info,\n }),\n headers: {\n ...this.config.defaultHeaders,\n ...headers,\n 'X-Strapi-Event': event,\n 'Content-Type': 'application/json',\n },\n signal: AbortSignal.timeout(10000),\n })\n .then(async (res) => {\n if (res.ok) {\n return {\n statusCode: res.status,\n };\n }\n\n return {\n statusCode: res.status,\n message: await res.text(),\n };\n })\n .catch((err) => {\n return {\n statusCode: 500,\n message: err.message,\n };\n });\n }\n\n add(webhook: Webhook) {\n debug(`Registering webhook '${webhook.id}'`);\n const { events } = webhook;\n\n events.forEach((event) => {\n if (this.webhooksMap.has(event)) {\n this.webhooksMap.get(event)?.push(webhook);\n } else {\n this.webhooksMap.set(event, [webhook]);\n this.createListener(event);\n }\n });\n }\n\n update(webhook: Webhook) {\n debug(`Refreshing webhook '${webhook.id}'`);\n this.remove(webhook);\n this.add(webhook);\n }\n\n remove(webhook: Webhook) {\n debug(`Unregistering webhook '${webhook.id}'`);\n\n this.webhooksMap.forEach((webhooks, event) => {\n const filteredWebhooks = webhooks.filter((value) => value.id !== webhook.id);\n\n // Cleanup hanging listeners\n if (filteredWebhooks.length === 0) {\n this.webhooksMap.delete(event);\n this.deleteListener(event);\n } else {\n this.webhooksMap.set(event, filteredWebhooks);\n }\n });\n }\n}\n\n/**\n * Expose a factory function instead of the class\n */\nexport default function createWebhookRunner(opts: ConstructorParameters): WebhookRunner {\n return new WebhookRunner(opts);\n}\n\nexport type { WebhookRunner };\n"],"names":["createdDebugger"],"mappings":"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"}
|
@@ -2,15 +2,9 @@
|
|
2
2
|
* Webhook store is the implementation of webhook storage over the core_store
|
3
3
|
*/
|
4
4
|
import type { Model, Database } from '@strapi/database';
|
5
|
+
import type { Modules } from '@strapi/types';
|
5
6
|
declare const webhookModel: Model;
|
6
|
-
|
7
|
-
id: string;
|
8
|
-
name: string;
|
9
|
-
url: string;
|
10
|
-
headers: Record<string, string>;
|
11
|
-
events: string[];
|
12
|
-
isEnabled: boolean;
|
13
|
-
}
|
7
|
+
type Webhook = Modules.WebhookStore.Webhook;
|
14
8
|
export interface WebhookStore {
|
15
9
|
allowedEvents: Map<string, string>;
|
16
10
|
addAllowedEvent(key: string, value: string): void;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webhook-store.d.ts","sourceRoot":"","sources":["../../src/services/webhook-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
1
|
+
{"version":3,"file":"webhook-store.d.ts","sourceRoot":"","sources":["../../src/services/webhook-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,QAAA,MAAM,YAAY,EAAE,KAwBnB,CAAC;AAEF,KAAK,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC;AAqC5C,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,iBAAiB,IAAI,MAAM,EAAE,CAAC;IAC9B,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACjD,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACjD,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAClE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,QAAA,MAAM,kBAAkB,WAAY;IAAE,EAAE,EAAE,QAAQ,CAAA;CAAE,KAAG,YAwDtD,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webhook-store.js","sources":["../../src/services/webhook-store.ts"],"sourcesContent":["/**\n * Webhook store is the implementation of webhook storage over the core_store\n */\n\nimport { errors } from '@strapi/utils';\nimport type { Model, Database } from '@strapi/database';\n\nconst { ValidationError } = errors;\n\nconst webhookModel: Model = {\n uid: 'strapi::webhook',\n singularName: 'strapi_webhooks',\n tableName: 'strapi_webhooks',\n attributes: {\n id: {\n type: 'increments',\n },\n name: {\n type: 'string',\n },\n url: {\n type: 'text',\n },\n headers: {\n type: 'json',\n },\n events: {\n type: 'json',\n },\n enabled: {\n type: 'boolean',\n },\n },\n};\n\
|
1
|
+
{"version":3,"file":"webhook-store.js","sources":["../../src/services/webhook-store.ts"],"sourcesContent":["/**\n * Webhook store is the implementation of webhook storage over the core_store\n */\n\nimport { errors } from '@strapi/utils';\nimport type { Model, Database } from '@strapi/database';\nimport type { Modules } from '@strapi/types';\n\nconst { ValidationError } = errors;\n\nconst webhookModel: Model = {\n uid: 'strapi::webhook',\n singularName: 'strapi_webhooks',\n tableName: 'strapi_webhooks',\n attributes: {\n id: {\n type: 'increments',\n },\n name: {\n type: 'string',\n },\n url: {\n type: 'text',\n },\n headers: {\n type: 'json',\n },\n events: {\n type: 'json',\n },\n enabled: {\n type: 'boolean',\n },\n },\n};\n\ntype Webhook = Modules.WebhookStore.Webhook;\ntype DBOutput = Omit<Webhook, 'id' | 'isEnabled'> & { id: string | number; enabled: boolean };\ntype DBInput = Omit<DBOutput, 'id'>;\n\nconst toDBObject = (data: Webhook): DBInput => {\n return {\n name: data.name,\n url: data.url,\n headers: data.headers,\n events: data.events,\n enabled: data.isEnabled,\n };\n};\n\nconst fromDBObject = (row: DBOutput): Webhook => {\n return {\n id: typeof row.id === 'number' ? row.id.toString() : row.id,\n name: row.name,\n url: row.url,\n headers: row.headers,\n events: row.events,\n isEnabled: row.enabled,\n };\n};\n\nconst webhookEventValidator = async (allowedEvents: Map<string, string>, events: string[]) => {\n const allowedValues = Array.from(allowedEvents.values());\n\n events.forEach((event) => {\n if (allowedValues.includes(event)) {\n return;\n }\n\n throw new ValidationError(`Webhook event ${event} is not supported`);\n });\n};\n\nexport interface WebhookStore {\n allowedEvents: Map<string, string>;\n addAllowedEvent(key: string, value: string): void;\n removeAllowedEvent(key: string): void;\n listAllowedEvents(): string[];\n getAllowedEvent(key: string): string | undefined;\n findWebhooks(): Promise<Webhook[]>;\n findWebhook(id: string): Promise<Webhook | null>;\n createWebhook(data: Webhook): Promise<Webhook>;\n updateWebhook(id: string, data: Webhook): Promise<Webhook | null>;\n deleteWebhook(id: string): Promise<Webhook | null>;\n}\n\nconst createWebhookStore = ({ db }: { db: Database }): WebhookStore => {\n return {\n allowedEvents: new Map([\n ['ENTRY_CREATE', 'entry.create'],\n ['ENTRY_UPDATE', 'entry.update'],\n ['ENTRY_DELETE', 'entry.delete'],\n ['ENTRY_PUBLISH', 'entry.publish'],\n ['ENTRY_UNPUBLISH', 'entry.unpublish'],\n ['ENTRY_DRAFT_DISCARD', 'entry.draft-discard'],\n ]),\n addAllowedEvent(key, value) {\n this.allowedEvents.set(key, value);\n },\n removeAllowedEvent(key) {\n this.allowedEvents.delete(key);\n },\n listAllowedEvents() {\n return Array.from(this.allowedEvents.keys());\n },\n getAllowedEvent(key) {\n return this.allowedEvents.get(key);\n },\n async findWebhooks() {\n const results = await db.query('strapi::webhook').findMany();\n\n return results.map(fromDBObject);\n },\n async findWebhook(id) {\n const result = await db.query('strapi::webhook').findOne({ where: { id } });\n return result ? fromDBObject(result) : null;\n },\n async createWebhook(data) {\n await webhookEventValidator(this.allowedEvents, data.events);\n\n return db\n .query('strapi::webhook')\n .create({\n data: toDBObject({ ...data, isEnabled: true }),\n })\n .then(fromDBObject);\n },\n async updateWebhook(id, data) {\n await webhookEventValidator(this.allowedEvents, data.events);\n\n const webhook = await db.query('strapi::webhook').update({\n where: { id },\n data: toDBObject(data),\n });\n\n return webhook ? fromDBObject(webhook) : null;\n },\n async deleteWebhook(id) {\n const webhook = await db.query('strapi::webhook').delete({ where: { id } });\n return webhook ? fromDBObject(webhook) : null;\n },\n };\n};\n\nexport { webhookModel, createWebhookStore };\n"],"names":["errors"],"mappings":";;;AAQA,MAAM,EAAE,gBAAoB,IAAAA;AAE5B,MAAM,eAAsB;AAAA,EAC1B,KAAK;AAAA,EACL,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,IAAI;AAAA,MACF,MAAM;AAAA,IACR;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,IACR;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAMA,MAAM,aAAa,CAAC,SAA2B;AACtC,SAAA;AAAA,IACL,MAAM,KAAK;AAAA,IACX,KAAK,KAAK;AAAA,IACV,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK;AAAA,EAAA;AAElB;AAEA,MAAM,eAAe,CAAC,QAA2B;AACxC,SAAA;AAAA,IACL,IAAI,OAAO,IAAI,OAAO,WAAW,IAAI,GAAG,aAAa,IAAI;AAAA,IACzD,MAAM,IAAI;AAAA,IACV,KAAK,IAAI;AAAA,IACT,SAAS,IAAI;AAAA,IACb,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,EAAA;AAEnB;AAEA,MAAM,wBAAwB,OAAO,eAAoC,WAAqB;AAC5F,QAAM,gBAAgB,MAAM,KAAK,cAAc,OAAQ,CAAA;AAEhD,SAAA,QAAQ,CAAC,UAAU;AACpB,QAAA,cAAc,SAAS,KAAK,GAAG;AACjC;AAAA,IACF;AAEA,UAAM,IAAI,gBAAgB,iBAAiB,KAAK,mBAAmB;AAAA,EAAA,CACpE;AACH;AAeA,MAAM,qBAAqB,CAAC,EAAE,SAAyC;AAC9D,SAAA;AAAA,IACL,mCAAmB,IAAI;AAAA,MACrB,CAAC,gBAAgB,cAAc;AAAA,MAC/B,CAAC,gBAAgB,cAAc;AAAA,MAC/B,CAAC,gBAAgB,cAAc;AAAA,MAC/B,CAAC,iBAAiB,eAAe;AAAA,MACjC,CAAC,mBAAmB,iBAAiB;AAAA,MACrC,CAAC,uBAAuB,qBAAqB;AAAA,IAAA,CAC9C;AAAA,IACD,gBAAgB,KAAK,OAAO;AACrB,WAAA,cAAc,IAAI,KAAK,KAAK;AAAA,IACnC;AAAA,IACA,mBAAmB,KAAK;AACjB,WAAA,cAAc,OAAO,GAAG;AAAA,IAC/B;AAAA,IACA,oBAAoB;AAClB,aAAO,MAAM,KAAK,KAAK,cAAc,KAAM,CAAA;AAAA,IAC7C;AAAA,IACA,gBAAgB,KAAK;AACZ,aAAA,KAAK,cAAc,IAAI,GAAG;AAAA,IACnC;AAAA,IACA,MAAM,eAAe;AACnB,YAAM,UAAU,MAAM,GAAG,MAAM,iBAAiB,EAAE;AAE3C,aAAA,QAAQ,IAAI,YAAY;AAAA,IACjC;AAAA,IACA,MAAM,YAAY,IAAI;AACpB,YAAM,SAAS,MAAM,GAAG,MAAM,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAG,CAAA;AACnE,aAAA,SAAS,aAAa,MAAM,IAAI;AAAA,IACzC;AAAA,IACA,MAAM,cAAc,MAAM;AACxB,YAAM,sBAAsB,KAAK,eAAe,KAAK,MAAM;AAE3D,aAAO,GACJ,MAAM,iBAAiB,EACvB,OAAO;AAAA,QACN,MAAM,WAAW,EAAE,GAAG,MAAM,WAAW,MAAM;AAAA,MAAA,CAC9C,EACA,KAAK,YAAY;AAAA,IACtB;AAAA,IACA,MAAM,cAAc,IAAI,MAAM;AAC5B,YAAM,sBAAsB,KAAK,eAAe,KAAK,MAAM;AAE3D,YAAM,UAAU,MAAM,GAAG,MAAM,iBAAiB,EAAE,OAAO;AAAA,QACvD,OAAO,EAAE,GAAG;AAAA,QACZ,MAAM,WAAW,IAAI;AAAA,MAAA,CACtB;AAEM,aAAA,UAAU,aAAa,OAAO,IAAI;AAAA,IAC3C;AAAA,IACA,MAAM,cAAc,IAAI;AACtB,YAAM,UAAU,MAAM,GAAG,MAAM,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAG,CAAA;AACnE,aAAA,UAAU,aAAa,OAAO,IAAI;AAAA,IAC3C;AAAA,EAAA;AAEJ;;;"}
|