mirror of
https://github.com/strapi/strapi.git
synced 2025-09-27 01:09:49 +00:00
Merge remote-tracking branch 'origin/main' into webhooks/edit-view
This commit is contained in:
commit
96bfeca3db
@ -71,6 +71,9 @@ const strapiMock = {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
webhookStore: {
|
||||||
|
addAllowedEvent: jest.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const reviewWorkflowsService = reviewWorkflowsServiceFactory({ strapi: strapiMock });
|
const reviewWorkflowsService = reviewWorkflowsServiceFactory({ strapi: strapiMock });
|
||||||
|
@ -108,12 +108,16 @@ function persistStagesJoinTables({ strapi }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const registerWebhookEvents = async ({ strapi }) =>
|
||||||
|
strapi.webhookStore.addAllowedEvent('WORKFLOW_UPDATE_STAGE', 'workflow.updateEntryStage');
|
||||||
|
|
||||||
module.exports = ({ strapi }) => {
|
module.exports = ({ strapi }) => {
|
||||||
const workflowsService = getService('workflows', { strapi });
|
const workflowsService = getService('workflows', { strapi });
|
||||||
const stagesService = getService('stages', { strapi });
|
const stagesService = getService('stages', { strapi });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async bootstrap() {
|
async bootstrap() {
|
||||||
|
await registerWebhookEvents({ strapi });
|
||||||
await initDefaultWorkflow({ workflowsService, stagesService, strapi });
|
await initDefaultWorkflow({ workflowsService, stagesService, strapi });
|
||||||
},
|
},
|
||||||
async register() {
|
async register() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { yup, webhook: webhookUtils, validateYupSchema } = require('@strapi/utils');
|
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||||
|
|
||||||
const urlRegex =
|
const urlRegex =
|
||||||
/^(?:([a-z0-9+.-]+):\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9_]-*)*[a-z\u00a1-\uffff0-9_]+)(?:\.(?:[a-z\u00a1-\uffff0-9_]-*)*[a-z\u00a1-\uffff0-9_]+)*\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/;
|
/^(?:([a-z0-9+.-]+):\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9_]-*)*[a-z\u00a1-\uffff0-9_]+)(?:\.(?:[a-z\u00a1-\uffff0-9_]-*)*[a-z\u00a1-\uffff0-9_]+)*\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/;
|
||||||
@ -23,7 +23,7 @@ const webhookValidator = yup
|
|||||||
)
|
)
|
||||||
.required();
|
.required();
|
||||||
}),
|
}),
|
||||||
events: yup.array().of(yup.string().oneOf(_.values(webhookUtils.webhookEvents)).required()),
|
events: yup.array().of(yup.string()).required(),
|
||||||
})
|
})
|
||||||
.noUnknown();
|
.noUnknown();
|
||||||
|
|
||||||
@ -111,10 +111,10 @@ module.exports = {
|
|||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||||
|
|
||||||
if (!webhook) continue;
|
if (webhook) {
|
||||||
|
await strapi.webhookStore.deleteWebhook(id);
|
||||||
await strapi.webhookStore.deleteWebhook(id);
|
strapi.webhookRunner.remove(webhook);
|
||||||
strapi.webhookRunner.remove(webhook);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.send({ data: ids });
|
ctx.send({ data: ids });
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { getService } = require('./utils');
|
const { getService } = require('./utils');
|
||||||
|
const { ALLOWED_WEBHOOK_EVENTS } = require('./constants');
|
||||||
|
|
||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||||
|
strapi.webhookStore.addAllowedEvent(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
await getService('components').syncConfigurations();
|
await getService('components').syncConfigurations();
|
||||||
await getService('content-types').syncConfigurations();
|
await getService('content-types').syncConfigurations();
|
||||||
await getService('permission').registerPermissions();
|
await getService('permission').registerPermissions();
|
||||||
|
10
packages/core/content-manager/server/constants/index.js
Normal file
10
packages/core/content-manager/server/constants/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ALLOWED_WEBHOOK_EVENTS = {
|
||||||
|
ENTRY_PUBLISH: 'entry.publish',
|
||||||
|
ENTRY_UNPUBLISH: 'entry.unpublish',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ALLOWED_WEBHOOK_EVENTS,
|
||||||
|
};
|
@ -382,7 +382,7 @@ describe('Single Types', () => {
|
|||||||
|
|
||||||
await singleTypes.publish(ctx);
|
await singleTypes.publish(ctx);
|
||||||
|
|
||||||
expect(publishFn).toHaveBeenCalledWith(entity, { updatedBy: state.user.id }, modelUid);
|
expect(publishFn).toHaveBeenCalledWith(entity, modelUid, { updatedBy: state.user.id });
|
||||||
expect(permissionChecker.cannot.publish).toHaveBeenCalledWith(entity);
|
expect(permissionChecker.cannot.publish).toHaveBeenCalledWith(entity);
|
||||||
expect(permissionChecker.sanitizeOutput).toHaveBeenCalled();
|
expect(permissionChecker.sanitizeOutput).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -479,7 +479,7 @@ describe('Single Types', () => {
|
|||||||
|
|
||||||
await singleTypes.unpublish(ctx);
|
await singleTypes.unpublish(ctx);
|
||||||
|
|
||||||
expect(unpublishFn).toHaveBeenCalledWith(entity, { updatedBy: state.user.id }, modelUid);
|
expect(unpublishFn).toHaveBeenCalledWith(entity, modelUid, { updatedBy: state.user.id });
|
||||||
expect(permissionChecker.cannot.unpublish).toHaveBeenCalledWith(entity);
|
expect(permissionChecker.cannot.unpublish).toHaveBeenCalledWith(entity);
|
||||||
expect(permissionChecker.sanitizeOutput).toHaveBeenCalled();
|
expect(permissionChecker.sanitizeOutput).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -174,8 +174,8 @@ module.exports = {
|
|||||||
|
|
||||||
const result = await entityManager.publish(
|
const result = await entityManager.publish(
|
||||||
entity,
|
entity,
|
||||||
setCreatorFields({ user, isEdition: true })({}),
|
model,
|
||||||
model
|
setCreatorFields({ user, isEdition: true })({})
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = await permissionChecker.sanitizeOutput(result);
|
ctx.body = await permissionChecker.sanitizeOutput(result);
|
||||||
@ -204,8 +204,8 @@ module.exports = {
|
|||||||
|
|
||||||
const result = await entityManager.unpublish(
|
const result = await entityManager.unpublish(
|
||||||
entity,
|
entity,
|
||||||
setCreatorFields({ user, isEdition: true })({}),
|
model,
|
||||||
model
|
setCreatorFields({ user, isEdition: true })({})
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = await permissionChecker.sanitizeOutput(result);
|
ctx.body = await permissionChecker.sanitizeOutput(result);
|
||||||
|
@ -148,8 +148,8 @@ module.exports = {
|
|||||||
|
|
||||||
const publishedEntity = await entityManager.publish(
|
const publishedEntity = await entityManager.publish(
|
||||||
entity,
|
entity,
|
||||||
setCreatorFields({ user, isEdition: true })({}),
|
model,
|
||||||
model
|
setCreatorFields({ user, isEdition: true })({})
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = await permissionChecker.sanitizeOutput(publishedEntity);
|
ctx.body = await permissionChecker.sanitizeOutput(publishedEntity);
|
||||||
@ -181,8 +181,8 @@ module.exports = {
|
|||||||
|
|
||||||
const unpublishedEntity = await entityManager.unpublish(
|
const unpublishedEntity = await entityManager.unpublish(
|
||||||
entity,
|
entity,
|
||||||
setCreatorFields({ user, isEdition: true })({}),
|
model,
|
||||||
model
|
setCreatorFields({ user, isEdition: true })({})
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = await permissionChecker.sanitizeOutput(unpublishedEntity);
|
ctx.body = await permissionChecker.sanitizeOutput(unpublishedEntity);
|
||||||
|
@ -26,6 +26,9 @@ describe('Content-Manager', () => {
|
|||||||
config: {
|
config: {
|
||||||
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
||||||
},
|
},
|
||||||
|
webhookStore: {
|
||||||
|
allowedEvents: new Map([['ENTRY_PUBLISH', 'entry.publish']]),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
entityManager = entityManagerLoader({ strapi });
|
entityManager = entityManagerLoader({ strapi });
|
||||||
});
|
});
|
||||||
@ -37,7 +40,7 @@ describe('Content-Manager', () => {
|
|||||||
test('Publish a content-type', async () => {
|
test('Publish a content-type', async () => {
|
||||||
const uid = 'api::test.test';
|
const uid = 'api::test.test';
|
||||||
const entity = { id: 1, publishedAt: null };
|
const entity = { id: 1, publishedAt: null };
|
||||||
await entityManager.publish(entity, {}, uid);
|
await entityManager.publish(entity, uid, {});
|
||||||
|
|
||||||
expect(strapi.entityService.update).toBeCalledWith(uid, entity.id, {
|
expect(strapi.entityService.update).toBeCalledWith(uid, entity.id, {
|
||||||
data: { publishedAt: expect.any(Date) },
|
data: { publishedAt: expect.any(Date) },
|
||||||
@ -58,6 +61,9 @@ describe('Content-Manager', () => {
|
|||||||
config: {
|
config: {
|
||||||
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
||||||
},
|
},
|
||||||
|
webhookStore: {
|
||||||
|
allowedEvents: new Map([['ENTRY_UNPUBLISH', 'entry.unpublish']]),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
entityManager = entityManagerLoader({ strapi });
|
entityManager = entityManagerLoader({ strapi });
|
||||||
});
|
});
|
||||||
@ -69,7 +75,7 @@ describe('Content-Manager', () => {
|
|||||||
test('Unpublish a content-type', async () => {
|
test('Unpublish a content-type', async () => {
|
||||||
const uid = 'api::test.test';
|
const uid = 'api::test.test';
|
||||||
const entity = { id: 1, publishedAt: new Date() };
|
const entity = { id: 1, publishedAt: new Date() };
|
||||||
await entityManager.unpublish(entity, {}, uid);
|
await entityManager.unpublish(entity, uid, {});
|
||||||
|
|
||||||
expect(strapi.entityService.update).toHaveBeenCalledWith(uid, entity.id, {
|
expect(strapi.entityService.update).toHaveBeenCalledWith(uid, entity.id, {
|
||||||
data: { publishedAt: null },
|
data: { publishedAt: null },
|
||||||
|
@ -7,15 +7,17 @@ const { ApplicationError } = require('@strapi/utils').errors;
|
|||||||
const { getDeepPopulate, getDeepPopulateDraftCount } = require('./utils/populate');
|
const { getDeepPopulate, getDeepPopulateDraftCount } = require('./utils/populate');
|
||||||
const { getDeepRelationsCount } = require('./utils/count');
|
const { getDeepRelationsCount } = require('./utils/count');
|
||||||
const { sumDraftCounts } = require('./utils/draft');
|
const { sumDraftCounts } = require('./utils/draft');
|
||||||
|
const {
|
||||||
|
ALLOWED_WEBHOOK_EVENTS: { ENTRY_PUBLISH, ENTRY_UNPUBLISH },
|
||||||
|
} = require('../constants');
|
||||||
|
|
||||||
const { hasDraftAndPublish } = strapiUtils.contentTypes;
|
const { hasDraftAndPublish } = strapiUtils.contentTypes;
|
||||||
const { PUBLISHED_AT_ATTRIBUTE, CREATED_BY_ATTRIBUTE } = strapiUtils.contentTypes.constants;
|
const { PUBLISHED_AT_ATTRIBUTE, CREATED_BY_ATTRIBUTE } = strapiUtils.contentTypes.constants;
|
||||||
const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = strapiUtils.webhook.webhookEvents;
|
|
||||||
|
|
||||||
const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
|
const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
|
||||||
|
|
||||||
const emitEvent = async (event, entity, modelUid) => {
|
const emitEvent = async (uid, event, entity) => {
|
||||||
const modelDef = strapi.getModel(modelUid);
|
const modelDef = strapi.getModel(uid);
|
||||||
const sanitizedEntity = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
|
const sanitizedEntity = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
|
||||||
modelDef,
|
modelDef,
|
||||||
entity
|
entity
|
||||||
@ -229,7 +231,7 @@ module.exports = ({ strapi }) => ({
|
|||||||
return strapi.entityService.deleteMany(uid, params);
|
return strapi.entityService.deleteMany(uid, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
async publish(entity, body = {}, uid) {
|
async publish(entity, uid, body = {}) {
|
||||||
if (entity[PUBLISHED_AT_ATTRIBUTE]) {
|
if (entity[PUBLISHED_AT_ATTRIBUTE]) {
|
||||||
throw new ApplicationError('already.published');
|
throw new ApplicationError('already.published');
|
||||||
}
|
}
|
||||||
@ -254,7 +256,7 @@ module.exports = ({ strapi }) => ({
|
|||||||
|
|
||||||
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
||||||
|
|
||||||
await emitEvent(ENTRY_PUBLISH, updatedEntity, uid);
|
await emitEvent(uid, ENTRY_PUBLISH, updatedEntity);
|
||||||
|
|
||||||
const mappedEntity = await this.mapEntity(updatedEntity, uid);
|
const mappedEntity = await this.mapEntity(updatedEntity, uid);
|
||||||
|
|
||||||
@ -266,7 +268,7 @@ module.exports = ({ strapi }) => ({
|
|||||||
return mappedEntity;
|
return mappedEntity;
|
||||||
},
|
},
|
||||||
|
|
||||||
async unpublish(entity, body = {}, uid) {
|
async unpublish(entity, uid, body = {}) {
|
||||||
if (!entity[PUBLISHED_AT_ATTRIBUTE]) {
|
if (!entity[PUBLISHED_AT_ATTRIBUTE]) {
|
||||||
throw new ApplicationError('already.draft');
|
throw new ApplicationError('already.draft');
|
||||||
}
|
}
|
||||||
@ -283,7 +285,7 @@ module.exports = ({ strapi }) => ({
|
|||||||
|
|
||||||
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
||||||
|
|
||||||
await emitEvent(ENTRY_UNPUBLISH, updatedEntity, uid);
|
await emitEvent(uid, ENTRY_UNPUBLISH, updatedEntity);
|
||||||
|
|
||||||
const mappedEntity = await this.mapEntity(updatedEntity, uid);
|
const mappedEntity = await this.mapEntity(updatedEntity, uid);
|
||||||
|
|
||||||
|
@ -20,6 +20,9 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
instance = createEntityService({
|
instance = createEntityService({
|
||||||
strapi: {
|
strapi: {
|
||||||
getModel: () => model,
|
getModel: () => model,
|
||||||
|
webhookStore: {
|
||||||
|
addAllowedEvent: jest.fn(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
transaction: (cb) => cb(),
|
transaction: (cb) => cb(),
|
||||||
@ -39,9 +42,6 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
getModel: () => model,
|
getModel: () => model,
|
||||||
config: {
|
|
||||||
get: () => [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
query: jest.fn(() => ({})),
|
query: jest.fn(() => ({})),
|
||||||
|
webhookStore: {
|
||||||
|
allowedEvents: new Map([['ENTRY_CREATE', 'entry.create']]),
|
||||||
|
addAllowedEvent: jest.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Decorator', () => {
|
describe('Decorator', () => {
|
||||||
@ -25,7 +29,7 @@ describe('Entity service', () => {
|
|||||||
'Can decorate',
|
'Can decorate',
|
||||||
async (method) => {
|
async (method) => {
|
||||||
const instance = createEntityService({
|
const instance = createEntityService({
|
||||||
strapi: {},
|
strapi: global.strapi,
|
||||||
db: {},
|
db: {},
|
||||||
eventHub: new EventEmitter(),
|
eventHub: new EventEmitter(),
|
||||||
});
|
});
|
||||||
@ -61,6 +65,7 @@ describe('Entity service', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fakeStrapi = {
|
const fakeStrapi = {
|
||||||
|
...global.strapi,
|
||||||
getModel: jest.fn(() => {
|
getModel: jest.fn(() => {
|
||||||
return { kind: 'singleType' };
|
return { kind: 'singleType' };
|
||||||
}),
|
}),
|
||||||
@ -98,12 +103,15 @@ describe('Entity service', () => {
|
|||||||
global.strapi.getModel.mockImplementation((modelName) => fakeModels[modelName]);
|
global.strapi.getModel.mockImplementation((modelName) => fakeModels[modelName]);
|
||||||
global.strapi.query.mockImplementation(() => fakeQuery);
|
global.strapi.query.mockImplementation(() => fakeQuery);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
global.strapi.getModel.mockImplementation(() => ({}));
|
global.strapi.getModel.mockImplementation(() => ({}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('assign default values', () => {
|
describe('assign default values', () => {
|
||||||
let instance;
|
let instance;
|
||||||
const entityUID = 'api::entity.entity';
|
const entityUID = 'api::entity.entity';
|
||||||
@ -373,10 +381,13 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
global.strapi = fakeStrapi;
|
global.strapi = {
|
||||||
|
...global.strapi,
|
||||||
|
...fakeStrapi,
|
||||||
|
};
|
||||||
|
|
||||||
instance = createEntityService({
|
instance = createEntityService({
|
||||||
strapi: fakeStrapi,
|
strapi: global.strapi,
|
||||||
db: fakeDB,
|
db: fakeDB,
|
||||||
eventHub: new EventEmitter(),
|
eventHub: new EventEmitter(),
|
||||||
entityValidator,
|
entityValidator,
|
||||||
@ -522,6 +533,7 @@ describe('Entity service', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
|
...global.strapi,
|
||||||
getModel: jest.fn((uid) => {
|
getModel: jest.fn((uid) => {
|
||||||
return fakeModels[uid];
|
return fakeModels[uid];
|
||||||
}),
|
}),
|
||||||
|
@ -4,11 +4,7 @@ const _ = require('lodash');
|
|||||||
const delegate = require('delegates');
|
const delegate = require('delegates');
|
||||||
const { InvalidTimeError, InvalidDateError, InvalidDateTimeError, InvalidRelationError } =
|
const { InvalidTimeError, InvalidDateError, InvalidDateTimeError, InvalidRelationError } =
|
||||||
require('@strapi/database').errors;
|
require('@strapi/database').errors;
|
||||||
const {
|
const { contentTypes: contentTypesUtils, sanitize } = require('@strapi/utils');
|
||||||
webhook: webhookUtils,
|
|
||||||
contentTypes: contentTypesUtils,
|
|
||||||
sanitize,
|
|
||||||
} = require('@strapi/utils');
|
|
||||||
const { ValidationError } = require('@strapi/utils').errors;
|
const { ValidationError } = require('@strapi/utils').errors;
|
||||||
const { isAnyToMany } = require('@strapi/utils').relations;
|
const { isAnyToMany } = require('@strapi/utils').relations;
|
||||||
const { transformParamsToQuery } = require('@strapi/utils').convertQueryParams;
|
const { transformParamsToQuery } = require('@strapi/utils').convertQueryParams;
|
||||||
@ -31,9 +27,6 @@ const transformLoadParamsToQuery = (uid, field, params = {}, pagination = {}) =>
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: those should be strapi events used by the webhooks not the other way arround
|
|
||||||
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
|
||||||
|
|
||||||
const databaseErrorsToTransform = [
|
const databaseErrorsToTransform = [
|
||||||
InvalidTimeError,
|
InvalidTimeError,
|
||||||
InvalidDateTimeError,
|
InvalidDateTimeError,
|
||||||
@ -49,6 +42,12 @@ const updatePipeline = (data, context) => {
|
|||||||
return applyTransforms(data, context);
|
return applyTransforms(data, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ALLOWED_WEBHOOK_EVENTS = {
|
||||||
|
ENTRY_CREATE: 'entry.create',
|
||||||
|
ENTRY_UPDATE: 'entry.update',
|
||||||
|
ENTRY_DELETE: 'entry.delete',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('.').default}
|
* @type {import('.').default}
|
||||||
*/
|
*/
|
||||||
@ -180,6 +179,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
|
|
||||||
entity = await this.wrapResult(entity, { uid, action: 'create' });
|
entity = await this.wrapResult(entity, { uid, action: 'create' });
|
||||||
|
|
||||||
|
const { ENTRY_CREATE } = ALLOWED_WEBHOOK_EVENTS;
|
||||||
await this.emitEvent(uid, ENTRY_CREATE, entity);
|
await this.emitEvent(uid, ENTRY_CREATE, entity);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
@ -233,6 +233,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
|
|
||||||
entity = await this.wrapResult(entity, { uid, action: 'update' });
|
entity = await this.wrapResult(entity, { uid, action: 'update' });
|
||||||
|
|
||||||
|
const { ENTRY_UPDATE } = ALLOWED_WEBHOOK_EVENTS;
|
||||||
await this.emitEvent(uid, ENTRY_UPDATE, entity);
|
await this.emitEvent(uid, ENTRY_UPDATE, entity);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
@ -260,6 +261,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
|
|
||||||
entityToDelete = await this.wrapResult(entityToDelete, { uid, action: 'delete' });
|
entityToDelete = await this.wrapResult(entityToDelete, { uid, action: 'delete' });
|
||||||
|
|
||||||
|
const { ENTRY_DELETE } = ALLOWED_WEBHOOK_EVENTS;
|
||||||
await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
||||||
|
|
||||||
return entityToDelete;
|
return entityToDelete;
|
||||||
@ -290,6 +292,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
entitiesToDelete = await this.wrapResult(entitiesToDelete, { uid, action: 'delete' });
|
entitiesToDelete = await this.wrapResult(entitiesToDelete, { uid, action: 'delete' });
|
||||||
|
|
||||||
// Trigger webhooks. One for each entity
|
// Trigger webhooks. One for each entity
|
||||||
|
const { ENTRY_DELETE } = ALLOWED_WEBHOOK_EVENTS;
|
||||||
await Promise.all(entitiesToDelete.map((entity) => this.emitEvent(uid, ENTRY_DELETE, entity)));
|
await Promise.all(entitiesToDelete.map((entity) => this.emitEvent(uid, ENTRY_DELETE, entity)));
|
||||||
|
|
||||||
return deletedEntities;
|
return deletedEntities;
|
||||||
@ -331,6 +334,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.exports = (ctx) => {
|
module.exports = (ctx) => {
|
||||||
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||||
|
ctx.strapi.webhookStore.addAllowedEvent(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
const implementation = createDefaultImplementation(ctx);
|
const implementation = createDefaultImplementation(ctx);
|
||||||
|
|
||||||
const service = {
|
const service = {
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { mapAsync } = require('@strapi/utils');
|
||||||
|
const { ValidationError } = require('@strapi/utils').errors;
|
||||||
|
|
||||||
const webhookModel = {
|
const webhookModel = {
|
||||||
uid: 'webhook',
|
uid: 'webhook',
|
||||||
collectionName: 'strapi_webhooks',
|
collectionName: 'strapi_webhooks',
|
||||||
@ -47,30 +50,56 @@ const fromDBObject = (row) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const webhookEventValidator = async (allowedEvents, events) => {
|
||||||
|
const allowedValues = Array.from(allowedEvents.values());
|
||||||
|
|
||||||
|
await mapAsync(events, (event) => {
|
||||||
|
if (allowedValues.includes(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ValidationError(`Webhook event ${event} is not supported`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const createWebhookStore = ({ db }) => {
|
const createWebhookStore = ({ db }) => {
|
||||||
const webhookQueries = db.query('webhook');
|
const webhookQueries = db.query('webhook');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
allowedEvents: new Map([]),
|
||||||
|
addAllowedEvent(key, value) {
|
||||||
|
this.allowedEvents.set(key, value);
|
||||||
|
},
|
||||||
|
removeAllowedEvent(key) {
|
||||||
|
this.allowedEvents.delete(key);
|
||||||
|
},
|
||||||
|
listAllowedEvents() {
|
||||||
|
return Array.from(this.allowedEvents.keys());
|
||||||
|
},
|
||||||
|
getAllowedEvent(key) {
|
||||||
|
return this.allowedEvents.get(key);
|
||||||
|
},
|
||||||
async findWebhooks() {
|
async findWebhooks() {
|
||||||
const results = await webhookQueries.findMany();
|
const results = await webhookQueries.findMany();
|
||||||
|
|
||||||
return results.map(fromDBObject);
|
return results.map(fromDBObject);
|
||||||
},
|
},
|
||||||
|
|
||||||
async findWebhook(id) {
|
async findWebhook(id) {
|
||||||
const result = await webhookQueries.findOne({ where: { id } });
|
const result = await webhookQueries.findOne({ where: { id } });
|
||||||
return result ? fromDBObject(result) : null;
|
return result ? fromDBObject(result) : null;
|
||||||
},
|
},
|
||||||
|
async createWebhook(data) {
|
||||||
|
await webhookEventValidator(this.allowedEvents, data.events);
|
||||||
|
|
||||||
createWebhook(data) {
|
|
||||||
return webhookQueries
|
return webhookQueries
|
||||||
.create({
|
.create({
|
||||||
data: toDBObject({ ...data, isEnabled: true }),
|
data: toDBObject({ ...data, isEnabled: true }),
|
||||||
})
|
})
|
||||||
.then(fromDBObject);
|
.then(fromDBObject);
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateWebhook(id, data) {
|
async updateWebhook(id, data) {
|
||||||
|
await webhookEventValidator(this.allowedEvents, data.events);
|
||||||
|
|
||||||
const webhook = await webhookQueries.update({
|
const webhook = await webhookQueries.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: toDBObject(data),
|
data: toDBObject(data),
|
||||||
@ -78,7 +107,6 @@ const createWebhookStore = ({ db }) => {
|
|||||||
|
|
||||||
return webhook ? fromDBObject(webhook) : null;
|
return webhook ? fromDBObject(webhook) : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteWebhook(id) {
|
async deleteWebhook(id) {
|
||||||
const webhook = await webhookQueries.delete({ where: { id } });
|
const webhook = await webhookQueries.delete({ where: { id } });
|
||||||
return webhook ? fromDBObject(webhook) : null;
|
return webhook ? fromDBObject(webhook) : null;
|
||||||
|
@ -68,6 +68,9 @@ describe('Upload plugin bootstrap function', () => {
|
|||||||
set: setStore,
|
set: setStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
webhookStore: {
|
||||||
|
addAllowedEvent: jest.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await bootstrap({ strapi });
|
await bootstrap({ strapi });
|
||||||
|
8
packages/core/upload/server/bootstrap.js
vendored
8
packages/core/upload/server/bootstrap.js
vendored
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { getService } = require('./utils');
|
const { getService } = require('./utils');
|
||||||
const { ALLOWED_SORT_STRINGS } = require('./constants');
|
const { ALLOWED_SORT_STRINGS, ALLOWED_WEBHOOK_EVENTS } = require('./constants');
|
||||||
|
|
||||||
module.exports = async ({ strapi }) => {
|
module.exports = async ({ strapi }) => {
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
@ -36,6 +36,7 @@ module.exports = async ({ strapi }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await registerPermissionActions();
|
await registerPermissionActions();
|
||||||
|
await registerWebhookEvents();
|
||||||
|
|
||||||
await getService('weeklyMetrics').registerCron();
|
await getService('weeklyMetrics').registerCron();
|
||||||
getService('metrics').sendUploadPluginMetrics();
|
getService('metrics').sendUploadPluginMetrics();
|
||||||
@ -47,6 +48,11 @@ module.exports = async ({ strapi }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const registerWebhookEvents = async () =>
|
||||||
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||||
|
strapi.webhookStore.addAllowedEvent(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
const registerPermissionActions = async () => {
|
const registerPermissionActions = async () => {
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
|
@ -19,10 +19,17 @@ const ALLOWED_SORT_STRINGS = [
|
|||||||
'updatedAt:ASC',
|
'updatedAt:ASC',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ALLOWED_WEBHOOK_EVENTS = {
|
||||||
|
MEDIA_CREATE: 'media.create',
|
||||||
|
MEDIA_UPDATE: 'media.update',
|
||||||
|
MEDIA_DELETE: 'media.delete',
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
FOLDER_MODEL_UID: 'plugin::upload.folder',
|
FOLDER_MODEL_UID: 'plugin::upload.folder',
|
||||||
FILE_MODEL_UID: 'plugin::upload.file',
|
FILE_MODEL_UID: 'plugin::upload.file',
|
||||||
API_UPLOAD_FOLDER_BASE_NAME: 'API Uploads',
|
API_UPLOAD_FOLDER_BASE_NAME: 'API Uploads',
|
||||||
ALLOWED_SORT_STRINGS,
|
ALLOWED_SORT_STRINGS,
|
||||||
|
ALLOWED_WEBHOOK_EVENTS,
|
||||||
};
|
};
|
||||||
|
@ -17,14 +17,14 @@ const {
|
|||||||
sanitize,
|
sanitize,
|
||||||
nameToSlug,
|
nameToSlug,
|
||||||
contentTypes: contentTypesUtils,
|
contentTypes: contentTypesUtils,
|
||||||
webhook: webhookUtils,
|
|
||||||
errors: { ApplicationError, NotFoundError },
|
errors: { ApplicationError, NotFoundError },
|
||||||
file: { bytesToKbytes },
|
file: { bytesToKbytes },
|
||||||
} = require('@strapi/utils');
|
} = require('@strapi/utils');
|
||||||
|
|
||||||
const { MEDIA_UPDATE, MEDIA_CREATE, MEDIA_DELETE } = webhookUtils.webhookEvents;
|
const {
|
||||||
|
FILE_MODEL_UID,
|
||||||
const { FILE_MODEL_UID } = require('../constants');
|
ALLOWED_WEBHOOK_EVENTS: { MEDIA_CREATE, MEDIA_UPDATE, MEDIA_DELETE },
|
||||||
|
} = require('../constants');
|
||||||
const { getService } = require('../utils');
|
const { getService } = require('../utils');
|
||||||
|
|
||||||
const { UPDATED_BY_ATTRIBUTE, CREATED_BY_ATTRIBUTE } = contentTypesUtils.constants;
|
const { UPDATED_BY_ATTRIBUTE, CREATED_BY_ATTRIBUTE } = contentTypesUtils.constants;
|
||||||
|
@ -28,7 +28,6 @@ const { removeUndefined, keysDeep } = require('./object-formatting');
|
|||||||
const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
|
const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
|
||||||
const { generateTimestampCode } = require('./code-generator');
|
const { generateTimestampCode } = require('./code-generator');
|
||||||
const contentTypes = require('./content-types');
|
const contentTypes = require('./content-types');
|
||||||
const webhook = require('./webhook');
|
|
||||||
const env = require('./env-helper');
|
const env = require('./env-helper');
|
||||||
const relations = require('./relations');
|
const relations = require('./relations');
|
||||||
const setCreatorFields = require('./set-creator-fields');
|
const setCreatorFields = require('./set-creator-fields');
|
||||||
@ -75,7 +74,6 @@ module.exports = {
|
|||||||
isCamelCase,
|
isCamelCase,
|
||||||
toKebabCase,
|
toKebabCase,
|
||||||
contentTypes,
|
contentTypes,
|
||||||
webhook,
|
|
||||||
env,
|
env,
|
||||||
relations,
|
relations,
|
||||||
setCreatorFields,
|
setCreatorFields,
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const webhookEvents = {
|
|
||||||
ENTRY_CREATE: 'entry.create',
|
|
||||||
ENTRY_UPDATE: 'entry.update',
|
|
||||||
ENTRY_DELETE: 'entry.delete',
|
|
||||||
ENTRY_PUBLISH: 'entry.publish',
|
|
||||||
ENTRY_UNPUBLISH: 'entry.unpublish',
|
|
||||||
MEDIA_CREATE: 'media.create',
|
|
||||||
MEDIA_UPDATE: 'media.update',
|
|
||||||
MEDIA_DELETE: 'media.delete',
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
webhookEvents,
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user