diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 259c01f..67a8a12 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -135,7 +135,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ResourceRequestBody' + $ref: '#/definitions/CreateResourceRequestBody' responses: '200': description: OK - the request was successful @@ -741,6 +741,32 @@ definitions: type: string format: UUID description: The resource role ID + CreateResourceRequestBody: + type: object + required: + - challengeId + - memberHandle + - memberId + - roleId + properties: + challengeId: + type: string + format: UUID + description: The challenge id + memberHandle: + type: string + description: The member handle + memberId: + type: string + description: The member id + roleId: + type: string + format: UUID + description: The resource role ID + sendEmail: + type: boolean + default: true + description: Set to `false` to create the resource without emitting the registration email event. ResourceRolePhaseDependencyRequestBody: type: object required: diff --git a/src/services/ResourceService.js b/src/services/ResourceService.js index 335f96e..6305168 100644 --- a/src/services/ResourceService.js +++ b/src/services/ResourceService.js @@ -492,12 +492,16 @@ async function init (currentUser, challengeId, resource, isCreated) { * Create resource for a challenge. * @param {Object} currentUser the current user * @param {Object} resource the resource to be created + * @param {Boolean} [resource.sendEmail=true] whether submitter registration should emit + * the registration email event * @returns {Object} the created resource */ async function createResource (currentUser, resource) { try { const challengeId = resource.challengeId const { memberId, handle, email, challenge, closeRegistration } = await init(currentUser, challengeId, resource, true) + const shouldSendRegistrationEmail = resource.sendEmail !== false + const resourceData = _.omit(resource, 'sendEmail') const timelineTemplateId = _.get(challenge, 'timelineTemplateId', null) @@ -507,7 +511,7 @@ async function createResource (currentUser, resource) { createdBy: _.toString(memberId), createdAt: moment().utc().format(), memberHandle: handle - }, resource) + }, resourceData) const createdResource = await prisma.resource.create({ data: prismaData, include: { @@ -537,7 +541,9 @@ async function createResource (currentUser, resource) { logger.warn(`Failed to increment numOfRegistrants for challenge ${challengeId}: ${e}`) } } - if (!_.get(challenge, 'task.isTask', false) && resource.roleId === config.SUBMITTER_RESOURCE_ROLE_ID) { + if (!_.get(challenge, 'task.isTask', false) && + resource.roleId === config.SUBMITTER_RESOURCE_ROLE_ID && + shouldSendRegistrationEmail) { const forumUrl = _.get(challenge, 'discussions[0].url') let templateId = config.REGISTRATION_EMAIL.SENDGRID_TEMPLATE_ID if (_.isUndefined(forumUrl)) { @@ -595,13 +601,15 @@ createResource.schema = { challengeId: Joi.id(), memberId: Joi.string(), memberHandle: Joi.string().required(), - roleId: Joi.id() + roleId: Joi.id(), + sendEmail: Joi.boolean().default(true) }), Joi.object().keys({ challengeId: Joi.id(), memberId: Joi.string().required(), memberHandle: Joi.string(), - roleId: Joi.id() + roleId: Joi.id(), + sendEmail: Joi.boolean().default(true) }) ) } diff --git a/test/unit/createResource.test.js b/test/unit/createResource.test.js index 68416e2..8b52055 100644 --- a/test/unit/createResource.test.js +++ b/test/unit/createResource.test.js @@ -265,6 +265,69 @@ module.exports = describe('Create resource', () => { await assertResource(ret.id, ret) }) + it('create submitter resource sends registration email by default', async () => { + const entity = resources.createBody('emailnotifyuser', submitterRoleId, challengeId3) + const postedEvents = [] + const originalPostEvent = helper.postEvent + let createdResourceId + + helper.postEvent = async (topic, payload) => { + postedEvents.push({ topic, payload }) + } + + try { + const ret = await service.createResource(user.m2m, entity) + createdResourceId = ret.id + const emailEvent = postedEvents.find((event) => event.topic === config.EMAIL_NOTIFICATIN_TOPIC) + + should.exist(emailEvent) + should.deepEqual(emailEvent.payload.recipients, ['test@topcoder.com']) + should.equal(emailEvent.payload.data.handle, 'emailnotifyuser') + } finally { + helper.postEvent = originalPostEvent + if (createdResourceId) { + await prisma.resource.deleteMany({ + where: { id: createdResourceId } + }) + } + } + }) + + it('create submitter resource skips registration email when sendEmail is false', async () => { + const entity = { + ...resources.createBody('emailnotifyuser-noemail', submitterRoleId, challengeId3), + sendEmail: false + } + const postedEvents = [] + const originalPostEvent = helper.postEvent + let createdResourceId + + helper.postEvent = async (topic, payload) => { + postedEvents.push({ topic, payload }) + } + + try { + const ret = await service.createResource(user.m2m, entity) + createdResourceId = ret.id + + should.equal( + postedEvents.some((event) => event.topic === config.EMAIL_NOTIFICATIN_TOPIC), + false + ) + should.equal( + postedEvents.some((event) => event.topic === config.RESOURCE_CREATE_TOPIC), + true + ) + } finally { + helper.postEvent = originalPostEvent + if (createdResourceId) { + await prisma.resource.deleteMany({ + where: { id: createdResourceId } + }) + } + } + }) + it('copilot can manage resources without full access flags', async () => { const originalRole = await helper.getById('ResourceRole', copilotRoleId) await ResourceRoleService.updateResourceRole(user.admin, copilotRoleId, { @@ -385,6 +448,19 @@ module.exports = describe('Create resource', () => { }) } + it('test invalid parameters, sendEmail must be boolean', async () => { + try { + const entity = { + ..._.cloneDeep(testBody), + sendEmail: 'invalid' + } + await service.createResource(user.m2m, entity) + throw new Error('should not throw error here') + } catch (err) { + assertValidationError(err, '"sendEmail" must be a boolean') + } + }) + for (const requiredField of requiredFields) { it(`test invalid parameters, required field ${requiredField} is missing`, async () => { let entity = _.cloneDeep(testBody)