diff --git a/notifications.yml b/notifications.yml index 3a58c2c9..111b3848 100644 --- a/notifications.yml +++ b/notifications.yml @@ -53,6 +53,8 @@ monitoring: notification_providers: email: + #multi-provider strategy. Values can be fallback | no-fallback | roundrobin + multi_provider_strategy: ${EMAIL_MULTI_PROVIDER_STRATEGY}:fallback smtp: host: ${EMAIL_SMTP_HOST}:localhost port: ${EMAIL_SMTP_PORT}:1025 @@ -145,7 +147,7 @@ recipients: - name: admin rules: - rule: - type: GLOBAL_COMMUNITY_ADMIN + type: GLOBAL_ADMIN_COMMUNITY resource_id: - rule: type: GLOBAL_ADMIN diff --git a/package-lock.json b/package-lock.json index 7f6ffb24..c0812563 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "alkemio-notifications", - "version": "0.3.3", + "version": "0.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "alkemio-notifications", - "version": "0.3.3", + "version": "0.4.2", "license": "EUPL-1.2", "dependencies": { - "@alkemio/client-lib": "^0.10.1", + "@alkemio/client-lib": "^0.10.2", "@nestjs/axios": "^0.0.1", "@nestjs/common": "^8.0.5", "@nestjs/config": "^1.0.1", @@ -74,9 +74,9 @@ } }, "node_modules/@alkemio/client-lib": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@alkemio/client-lib/-/client-lib-0.10.1.tgz", - "integrity": "sha512-lbtTSi+fMnkgod3S3NeBMNJ8PTLduei16Z0tTQkqI4gv9mYYkGWiU/so8/FgTajbplx7S6ix3tncFZ8Kqk9Ebw==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@alkemio/client-lib/-/client-lib-0.10.2.tgz", + "integrity": "sha512-JZNslgaGIIc2FlsIvET5x/JHGtq05y8MpVhUJj8U3ivjentCaHEApSGwl1i4/2yHwbvY7TbHYDga/dzDcc6hzA==", "dependencies": { "axios": "^0.21.1", "dotenv": "^8.2.0", @@ -17355,9 +17355,9 @@ }, "dependencies": { "@alkemio/client-lib": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@alkemio/client-lib/-/client-lib-0.10.1.tgz", - "integrity": "sha512-lbtTSi+fMnkgod3S3NeBMNJ8PTLduei16Z0tTQkqI4gv9mYYkGWiU/so8/FgTajbplx7S6ix3tncFZ8Kqk9Ebw==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@alkemio/client-lib/-/client-lib-0.10.2.tgz", + "integrity": "sha512-JZNslgaGIIc2FlsIvET5x/JHGtq05y8MpVhUJj8U3ivjentCaHEApSGwl1i4/2yHwbvY7TbHYDga/dzDcc6hzA==", "requires": { "axios": "^0.21.1", "dotenv": "^8.2.0", diff --git a/package.json b/package.json index b3698bc8..90254271 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alkemio-notifications", - "version": "0.4.0", + "version": "0.4.2", "description": "Alkemio notifications service", "author": "Cherrytwist Foundation", "private": false, @@ -34,7 +34,7 @@ "validate-connection": "ts-node src/utils/validate-connection.ts" }, "dependencies": { - "@alkemio/client-lib": "^0.10.1", + "@alkemio/client-lib": "^0.10.2", "@nestjs/axios": "^0.0.1", "@nestjs/common": "^8.0.5", "@nestjs/config": "^1.0.1", diff --git a/src/app.controller.ts b/src/app.controller.ts index cab276a3..4779e8a8 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -65,7 +65,7 @@ export class AppController { this.sendNotifications( eventPayload, context, - this.notificationService.sendCommunicationUpdateddNotification( + this.notificationService.sendCommunicationUpdatedNotification( eventPayload ), COMMUNICATION_UPDATE_SENT diff --git a/src/core/contracts/notification.recipient.template.provider.interface.ts b/src/core/contracts/notification.recipient.template.provider.interface.ts index 800063e5..f76a5cfe 100644 --- a/src/core/contracts/notification.recipient.template.provider.interface.ts +++ b/src/core/contracts/notification.recipient.template.provider.interface.ts @@ -17,6 +17,7 @@ export type TemplateConfig = { application_created?: TemplateRuleSet[]; user_registered?: TemplateRuleSet[]; communication_update_sent?: TemplateRuleSet[]; + communication_discussion_created?: TemplateRuleSet[]; }; export interface INotificationRecipientTemplateProvider { diff --git a/src/core/models/user.ts b/src/core/models/user.ts index 4e480459..505e4752 100644 --- a/src/core/models/user.ts +++ b/src/core/models/user.ts @@ -5,4 +5,18 @@ export type User = { lastName: string; displayName: string; email: string; + preferences?: UserPreference[]; +}; + +export type UserPreference = { + definition: UserPreferenceDefinition; + value: string; +}; + +export type UserPreferenceDefinition = { + group: string; + displayName: string; + description: string; + valueType: string; + type: string; }; diff --git a/src/services/application/alkemio-client-adapter/alkemio.client.adapter.spec.ts b/src/services/application/alkemio-client-adapter/alkemio.client.adapter.spec.ts index bc376bc1..a8bcee1a 100644 --- a/src/services/application/alkemio-client-adapter/alkemio.client.adapter.spec.ts +++ b/src/services/application/alkemio-client-adapter/alkemio.client.adapter.spec.ts @@ -9,7 +9,7 @@ import { AlkemioClientAdapterModule } from './alkemio.client.adapter.module'; import * as challengeAdminsData from '@test/data/challenge.admins.json'; import * as opportunityAdminsData from '@test/data/opportunity.admins.json'; import * as hubAdminsData from '@test/data/hub.admins.json'; -import * as eventPayload from '@test/data/event.payload.json'; +import * as eventPayload from '@test/data/event.application.created.payload.json'; import { AlkemioClientAdapter } from './alkemio.client.adapter'; const testData = { @@ -64,9 +64,7 @@ describe('AlkemioAdapter', () => { it('Should throw an error', async () => { jest.spyOn(alkemioClient, 'user').mockResolvedValue(undefined); - expect( - alkemioAdapter.getUser(testData.eventPayload.data.hub.id) - ).rejects.toThrow(); + expect(alkemioAdapter.getUser(testData.data.hub.id)).rejects.toThrow(); }); it('Should return true', async () => { diff --git a/src/services/application/alkemio-client-adapter/alkemio.client.adapter.ts b/src/services/application/alkemio-client-adapter/alkemio.client.adapter.ts index fd652d8c..9d40b4b7 100644 --- a/src/services/application/alkemio-client-adapter/alkemio.client.adapter.ts +++ b/src/services/application/alkemio-client-adapter/alkemio.client.adapter.ts @@ -41,7 +41,8 @@ export class AlkemioClientAdapter implements IFeatureFlagProvider { const users = await this.alkemioClient.usersWithAuthorizationCredential( credentialCriteria.type, - resourceID + resourceID, + true ); if (!users) return []; return users; diff --git a/src/services/domain/builders/application-created/application.created.notification.builder.ts b/src/services/domain/builders/application-created/application.created.notification.builder.ts index d71e2dbc..84ce241e 100644 --- a/src/services/domain/builders/application-created/application.created.notification.builder.ts +++ b/src/services/domain/builders/application-created/application.created.notification.builder.ts @@ -14,6 +14,7 @@ import { ApplicationCreatedEventPayload } from '@src/types/application.created.e import { EmailTemplate } from '@src/common/enums/email.template'; import { AlkemioClientAdapter } from '@src/services'; import { AlkemioUrlGenerator } from '@src/services/application/alkemio-url-generator'; +import { UserPreferenceType } from '@alkemio/client-lib'; @Injectable() export class ApplicationCreatedNotificationBuilder { @@ -40,14 +41,16 @@ export class ApplicationCreatedNotificationBuilder { eventPayload, 'admin', EmailTemplate.USER_APPLICATION_ADMIN, - applicant + applicant, + UserPreferenceType.NotificationApplicationReceived ); const applicantNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'applicant', EmailTemplate.USER_APPLICATION_APPLICANT, - applicant + applicant, + UserPreferenceType.NotificationApplicationSubmitted ); return Promise.all([ ...adminNotificationPromises, @@ -59,7 +62,8 @@ export class ApplicationCreatedNotificationBuilder { eventPayload: any, recipientRole: string, emailTemplate: EmailTemplate, - applicant: User + applicant: User, + preferenceType: UserPreferenceType ): Promise { this.logger.verbose?.( `Notifications [${emailTemplate}] - role '${recipientRole}`, @@ -82,12 +86,27 @@ export class ApplicationCreatedNotificationBuilder { credentialCriterias ); + const filteredRecipients: User[] = []; + for (const recipient of recipients) { + if (recipient.preferences) { + if ( + recipient.preferences.find( + preference => + preference.definition.group === 'Notification' && + preference.definition.type === preferenceType && + preference.value === 'true' + ) + ) + filteredRecipients.push(recipient); + } + } + this.logger.verbose?.( - `Notifications [${emailTemplate}] - identified ${recipients.length} recipients`, + `Notifications [${emailTemplate}] - identified ${filteredRecipients.length} recipients`, LogContext.NOTIFICATIONS ); - const notifications = recipients.map(recipient => + const notifications = filteredRecipients.map(recipient => this.buildNotification(eventPayload, recipient, emailTemplate, applicant) ); @@ -140,9 +159,9 @@ export class ApplicationCreatedNotificationBuilder { applicant.nameID ); const communityURL = this.alkemioUrlGenerator.createCommunityURL( - eventPayload.hub.id, - eventPayload.hub.challenge?.id, - eventPayload.hub.challenge?.opportunity?.id + eventPayload.hub.nameID, + eventPayload.hub.challenge?.nameID, + eventPayload.hub.challenge?.opportunity?.nameID ); return { emailFrom: 'info@alkem.io', diff --git a/src/services/domain/builders/communication-discussion-created/communication.discussion.created.notification.builder.ts b/src/services/domain/builders/communication-discussion-created/communication.discussion.created.notification.builder.ts index 831f89dd..22007349 100644 --- a/src/services/domain/builders/communication-discussion-created/communication.discussion.created.notification.builder.ts +++ b/src/services/domain/builders/communication-discussion-created/communication.discussion.created.notification.builder.ts @@ -16,6 +16,7 @@ import { ConfigService } from '@nestjs/config'; import { AlkemioClientAdapter } from '@src/services'; import { CommunicationDiscussionCreatedEventPayload } from '@src/types/communication.discussion.created.event.payload'; import { AlkemioUrlGenerator } from '@src/services/application/alkemio-url-generator'; +import { UserPreferenceType } from '@alkemio/client-lib'; @Injectable() export class CommunicationDiscussionCreatedNotificationBuilder { @@ -38,7 +39,7 @@ export class CommunicationDiscussionCreatedNotificationBuilder { )?.webclient_endpoint; } - async sendNotifications( + async buildNotifications( eventPayload: CommunicationDiscussionCreatedEventPayload ) { this.logger.verbose?.( @@ -52,18 +53,19 @@ export class CommunicationDiscussionCreatedNotificationBuilder { eventPayload.discussion.createdBy ); - const adminNotificationPromises = await this.sendNotificationsForRole( + const adminNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'admin', - EmailTemplate.COMMUNICATION_UPDATE_ADMIN, + EmailTemplate.COMMUNICATION_DISCUSSION_CREATED_ADMIN, sender ); - const memberNotificationPromises = await this.sendNotificationsForRole( + const memberNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'member', - EmailTemplate.COMMUNICATION_UPDATE_MEMBER, - sender + EmailTemplate.COMMUNICATION_DISCUSSION_CREATED_MEMBER, + sender, + UserPreferenceType.NotificationCommunicationDiscussionCreated ); return Promise.all([ @@ -72,11 +74,12 @@ export class CommunicationDiscussionCreatedNotificationBuilder { ]); } - async sendNotificationsForRole( + async buildNotificationsForRole( eventPayload: CommunicationDiscussionCreatedEventPayload, recipientRole: string, emailTemplate: EmailTemplate, - sender: User + sender: User, + preferenceType?: UserPreferenceType ): Promise { this.logger.verbose?.( `Notifications [${emailTemplate}] - recipients role: '${recipientRole}`, @@ -85,7 +88,8 @@ export class CommunicationDiscussionCreatedNotificationBuilder { // Get the lookup map const lookupMap = this.createLookupMap(eventPayload); const userRegistrationRuleSets = - this.recipientTemplateProvider.getTemplate().user_registered; + this.recipientTemplateProvider.getTemplate() + .communication_discussion_created; const credentialCriterias = this.recipientTemplateProvider.getCredentialCriterias( @@ -99,12 +103,28 @@ export class CommunicationDiscussionCreatedNotificationBuilder { credentialCriterias ); + const filteredRecipients: User[] = []; + for (const recipient of recipients) { + if (recipient.preferences) { + if ( + !preferenceType || + recipient.preferences.find( + preference => + preference.definition.group === 'Notification' && + preference.definition.type === preferenceType && + preference.value === 'true' + ) + ) + filteredRecipients.push(recipient); + } + } + this.logger.verbose?.( - `Notifications [${emailTemplate}] - identified ${recipients.length} recipients`, + `Notifications [${emailTemplate}] - identified ${filteredRecipients.length} recipients`, LogContext.NOTIFICATIONS ); - const notifications = recipients.map(recipient => + const notifications = filteredRecipients.map(recipient => this.buildNotification(eventPayload, recipient, emailTemplate, sender) ); @@ -153,9 +173,9 @@ export class CommunicationDiscussionCreatedNotificationBuilder { sender: User ): any { const communityURL = this.alkemioUrlGenerator.createCommunityURL( - eventPayload.hub.id, - eventPayload.hub.challenge?.id, - eventPayload.hub.challenge?.opportunity?.id + eventPayload.hub.nameID, + eventPayload.hub.challenge?.nameID, + eventPayload.hub.challenge?.opportunity?.nameID ); const senderProfile = this.alkemioUrlGenerator.createUserURL(sender.nameID); return { diff --git a/src/services/domain/builders/communication-updated/communication.updated.notification.builder.ts b/src/services/domain/builders/communication-updated/communication.updated.notification.builder.ts index 768fc85f..ddfa3a89 100644 --- a/src/services/domain/builders/communication-updated/communication.updated.notification.builder.ts +++ b/src/services/domain/builders/communication-updated/communication.updated.notification.builder.ts @@ -16,6 +16,7 @@ import { ConfigService } from '@nestjs/config'; import { CommunicationUpdateEventPayload } from '@src/types/communication.update.event.payload'; import { AlkemioClientAdapter } from '@src/services'; import { AlkemioUrlGenerator } from '@src/services/application/alkemio-url-generator'; +import { UserPreferenceType } from '@alkemio/client-lib'; @Injectable() export class CommunicationUpdateNotificationBuilder { @@ -38,7 +39,7 @@ export class CommunicationUpdateNotificationBuilder { )?.webclient_endpoint; } - async sendNotifications(eventPayload: CommunicationUpdateEventPayload) { + async buildNotifications(eventPayload: CommunicationUpdateEventPayload) { this.logger.verbose?.( `[Notifications: communication update]: ${JSON.stringify(eventPayload)}`, LogContext.NOTIFICATIONS @@ -48,18 +49,19 @@ export class CommunicationUpdateNotificationBuilder { eventPayload.update.createdBy ); - const adminNotificationPromises = await this.sendNotificationsForRole( + const adminNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'admin', EmailTemplate.COMMUNICATION_UPDATE_ADMIN, sender ); - const memberNotificationPromises = await this.sendNotificationsForRole( + const memberNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'member', EmailTemplate.COMMUNICATION_UPDATE_MEMBER, - sender + sender, + UserPreferenceType.NotificationCommunicationUpdates ); return Promise.all([ @@ -68,11 +70,12 @@ export class CommunicationUpdateNotificationBuilder { ]); } - async sendNotificationsForRole( + async buildNotificationsForRole( eventPayload: CommunicationUpdateEventPayload, recipientRole: string, emailTemplate: EmailTemplate, - sender: User + sender: User, + preferenceType?: UserPreferenceType ): Promise { this.logger.verbose?.( `Notifications [${emailTemplate}] - recipients role: '${recipientRole}`, @@ -81,7 +84,7 @@ export class CommunicationUpdateNotificationBuilder { // Get the lookup map const lookupMap = this.createLookupMap(eventPayload); const userRegistrationRuleSets = - this.recipientTemplateProvider.getTemplate().user_registered; + this.recipientTemplateProvider.getTemplate().communication_update_sent; const credentialCriterias = this.recipientTemplateProvider.getCredentialCriterias( @@ -95,12 +98,28 @@ export class CommunicationUpdateNotificationBuilder { credentialCriterias ); + const filteredRecipients: User[] = []; + for (const recipient of recipients) { + if (recipient.preferences) { + if ( + !preferenceType || + recipient.preferences.find( + preference => + preference.definition.group === 'Notification' && + preference.definition.type === preferenceType && + preference.value === 'true' + ) + ) + filteredRecipients.push(recipient); + } + } + this.logger.verbose?.( - `Notifications [${emailTemplate}] - identified ${recipients.length} recipients`, + `Notifications [${emailTemplate}] - identified ${filteredRecipients.length} recipients`, LogContext.NOTIFICATIONS ); - const notifications = recipients.map(recipient => + const notifications = filteredRecipients.map(recipient => this.buildNotification(eventPayload, recipient, emailTemplate, sender) ); @@ -124,10 +143,13 @@ export class CommunicationUpdateNotificationBuilder { sender ) as any; - return await this.notificationTemplateBuilder.buildTemplate( - templateName, - templatePayload - ); + const populatedNotification = + await this.notificationTemplateBuilder.buildTemplate( + templateName, + templatePayload + ); + + return populatedNotification; } createLookupMap( @@ -149,9 +171,9 @@ export class CommunicationUpdateNotificationBuilder { sender: User ): any { const communityURL = this.alkemioUrlGenerator.createCommunityURL( - eventPayload.hub.id, - eventPayload.hub.challenge?.id, - eventPayload.hub.challenge?.opportunity?.id + eventPayload.hub.nameID, + eventPayload.hub.challenge?.nameID, + eventPayload.hub.challenge?.opportunity?.nameID ); const senderProfile = this.alkemioUrlGenerator.createUserURL(sender.nameID); return { diff --git a/src/services/domain/builders/user-registered/user.registered.notification.builder.ts b/src/services/domain/builders/user-registered/user.registered.notification.builder.ts index 6fbb9030..3bc20f1a 100644 --- a/src/services/domain/builders/user-registered/user.registered.notification.builder.ts +++ b/src/services/domain/builders/user-registered/user.registered.notification.builder.ts @@ -14,6 +14,7 @@ import { EmailTemplate } from '@src/common/enums/email.template'; import { UserRegistrationEventPayload } from '@src/types/user.registration.event.payload'; import { AlkemioClientAdapter } from '@src/services'; import { AlkemioUrlGenerator } from '@src/services/application/alkemio-url-generator'; +import { UserPreferenceType } from '@alkemio/client-lib'; @Injectable() export class UserRegisteredNotificationBuilder { @@ -30,7 +31,7 @@ export class UserRegisteredNotificationBuilder { private readonly recipientTemplateProvider: INotificationRecipientTemplateProvider ) {} - async sendNotifications(eventPayload: UserRegistrationEventPayload) { + async buildNotifications(eventPayload: UserRegistrationEventPayload) { this.logger.verbose?.( `[Notifications: userRegistration]: ${JSON.stringify(eventPayload)}`, LogContext.NOTIFICATIONS @@ -38,14 +39,15 @@ export class UserRegisteredNotificationBuilder { // Get additional data const registrant = await this.alkemioAdapter.getUser(eventPayload.userID); - const adminNotificationPromises = await this.sendNotificationsForRole( + const adminNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'admin', EmailTemplate.USER_REGISTRATION_ADMIN, - registrant + registrant, + UserPreferenceType.NotificationUserSignUp ); - const registrantNotificationPromises = await this.sendNotificationsForRole( + const registrantNotificationPromises = await this.buildNotificationsForRole( eventPayload, 'registrant', EmailTemplate.USER_REGISTRATION_REGISTRANT, @@ -58,11 +60,12 @@ export class UserRegisteredNotificationBuilder { ]); } - async sendNotificationsForRole( + async buildNotificationsForRole( eventPayload: UserRegistrationEventPayload, recipientRole: string, emailTemplate: EmailTemplate, - registrant: User + registrant: User, + preferenceType?: UserPreferenceType ): Promise { this.logger.verbose?.( `Notifications [${emailTemplate}] - recipients role: '${recipientRole}`, @@ -85,12 +88,28 @@ export class UserRegisteredNotificationBuilder { credentialCriterias ); + const filteredRecipients: User[] = []; + for (const recipient of recipients) { + if (recipient.preferences) { + if ( + !preferenceType || + recipient.preferences.find( + preference => + preference.definition.group === 'Notification' && + preference.definition.type === preferenceType && + preference.value === 'true' + ) + ) + filteredRecipients.push(recipient); + } + } + this.logger.verbose?.( - `Notifications [${emailTemplate}] - identified ${recipients.length} recipients`, + `Notifications [${emailTemplate}] - identified ${filteredRecipients.length} recipients`, LogContext.NOTIFICATIONS ); - const notifications = recipients.map(recipient => + const notifications = filteredRecipients.map(recipient => this.buildNotification(eventPayload, recipient, emailTemplate, registrant) ); diff --git a/src/services/domain/notification/notification.service.spec.ts b/src/services/domain/notification/notification.service.spec.ts index 8f8e8903..e22ff904 100644 --- a/src/services/domain/notification/notification.service.spec.ts +++ b/src/services/domain/notification/notification.service.spec.ts @@ -8,7 +8,7 @@ import { ALKEMIO_CLIENT_ADAPTER, ALKEMIO_URL_GENERATOR } from '@src/common'; import * as challengeAdminsData from '@test/data/challenge.admins.json'; import * as opportunityAdminsData from '@test/data/opportunity.admins.json'; import * as hubAdminsData from '@test/data/hub.admins.json'; -import * as eventPayload from '@test/data/event.payload.json'; +import * as eventPayload from '@test/data/event.application.created.payload.json'; import * as adminUser from '@test/data/admin.user.json'; import { INotifiedUsersProvider } from '@core/contracts'; import { ApplicationCreatedEventPayload } from '@src/types'; @@ -91,7 +91,7 @@ describe('NotificationService', () => { .mockResolvedValue(testData.adminUser); const res = await notificationService.sendApplicationCreatedNotifications( - testData.eventPayload.data as ApplicationCreatedEventPayload + testData.data as ApplicationCreatedEventPayload ); for (const notificationStatus of res) { expect( @@ -117,7 +117,7 @@ describe('NotificationService', () => { .mockResolvedValue(testData.adminUser); const res = await notificationService.sendApplicationCreatedNotifications( - testData.eventPayload.data as ApplicationCreatedEventPayload + testData.data as ApplicationCreatedEventPayload ); for (const notificationStatus of res) { @@ -136,7 +136,7 @@ describe('NotificationService', () => { .mockRejectedValue(new Error('Applicant not found!')); expect( notificationService.sendApplicationCreatedNotifications( - testData.eventPayload.data as ApplicationCreatedEventPayload + testData.data as ApplicationCreatedEventPayload ) ).rejects.toThrow(); }); diff --git a/src/services/domain/notification/notification.service.ts b/src/services/domain/notification/notification.service.ts index dc05008d..0a832d8a 100644 --- a/src/services/domain/notification/notification.service.ts +++ b/src/services/domain/notification/notification.service.ts @@ -52,7 +52,7 @@ export class NotificationService { ); } - async sendCommunicationUpdateddNotification( + async sendCommunicationUpdatedNotification( payload: CommunicationUpdateEventPayload ): Promise[]> { return this.sendNotifications( diff --git a/src/services/external/notifme/notifme.sdk.factory.ts b/src/services/external/notifme/notifme.sdk.factory.ts index a2735e5d..679be849 100644 --- a/src/services/external/notifme/notifme.sdk.factory.ts +++ b/src/services/external/notifme/notifme.sdk.factory.ts @@ -11,7 +11,9 @@ export async function notifmeSdkFactory( const notifmeSdk = new NotifmeSdk({ channels: { email: { - multiProviderStrategy: 'fallback', + multiProviderStrategy: configService.get( + ConfigurationTypes.NOTIFICATION_PROVIDERS + )?.email?.multi_provider_strategy, providers: [ { type: 'smtp', diff --git a/src/templates/communication.discussion.created.admin.js b/src/templates/communication.discussion.created.admin.js index ae1ba7ea..6081e54d 100644 --- a/src/templates/communication.discussion.created.admin.js +++ b/src/templates/communication.discussion.created.admin.js @@ -1,20 +1,19 @@ /* eslint-disable quotes */ module.exports = () => ({ name: 'communication-discussion-created-admin', - title: - 'New discussion created on {{community.displayName}}: {{discussion.title}}', + title: 'New discussion created on {{community.name}}: {{discussion.title}}', version: 1, channels: { email: { from: '{{emailFrom}}', to: '{{recipient.email}}', subject: - 'New discussion created on {{community.displayName}}: {{discussion.title}}', + 'New discussion created on {{community.name}}: {{discussion.title}}', html: `{% extends "src/templates/_layouts/email-transactional.html" %} {% block content %} Hi {{recipient.firstname}},

- A new discussion was created by {{discussion.createdBy.firstname}} on {{community.displayName}}: {{discussion.title}}

+ A new discussion was created by {{createdBy.firstname}} on {{community.name}}: {{discussion.title}}

Sincerely yours, Team Alkemio diff --git a/src/templates/communication.discussion.created.member.js b/src/templates/communication.discussion.created.member.js index 93333f17..0316234d 100644 --- a/src/templates/communication.discussion.created.member.js +++ b/src/templates/communication.discussion.created.member.js @@ -1,20 +1,19 @@ /* eslint-disable quotes */ module.exports = () => ({ name: 'communication-discussion-created-member', - title: - 'New discussion created on {{community.displayName}}: {{discussion.title}}', + title: 'New discussion created on {{community.name}}: {{discussion.title}}', version: 1, channels: { email: { from: '{{emailFrom}}', to: '{{recipient.email}}', subject: - 'New discussion created on {{community.displayName}}: {{discussion.title}}', + 'New discussion created on {{community.name}}: {{discussion.title}}', html: `{% extends "src/templates/_layouts/email-transactional.html" %} {% block content %} Hi {{recipient.firstname}},

- A new discussion was created by {{discussion.createdBy.firstname} on your community {{community.displayName}}: {{discussion.title}}

+ A new discussion was created by {{createdBy.firstname} on your community {{community.name}}: {{discussion.title}}

Sincerely yours, Team Alkemio diff --git a/src/templates/communication.update.admin.js b/src/templates/communication.update.admin.js index 7c2d4017..5a3c8814 100644 --- a/src/templates/communication.update.admin.js +++ b/src/templates/communication.update.admin.js @@ -1,18 +1,18 @@ /* eslint-disable quotes */ module.exports = () => ({ name: 'communication-update-admin', - title: 'New update sent to community {{community.displayName}}', + title: 'New update sent to community {{community.name}}', version: 1, channels: { email: { from: '{{emailFrom}}', to: '{{recipient.email}}', - subject: 'New update shared with community {{community.displayName}}', + subject: 'New update shared with community {{community.name}}!', html: `{% extends "src/templates/_layouts/email-transactional.html" %} {% block content %} Hi {{recipient.firstname}},

- A new update was shared by {{update.sender.firstname} on the following community: {{community.displayName}}.

+ A new update was shared by {{sender.firstname}} on the following community: {{community.name}}.

Sincerely yours, Team Alkemio diff --git a/src/templates/communication.update.member.js b/src/templates/communication.update.member.js index c3409eff..c86ddf80 100644 --- a/src/templates/communication.update.member.js +++ b/src/templates/communication.update.member.js @@ -1,19 +1,19 @@ /* eslint-disable quotes */ module.exports = () => ({ - name: 'communication-update-admin', - title: 'New update sent to community {{community.displayName}}', + name: 'communication-update-member', + title: 'New update sent to community {{community.name}}', version: 1, channels: { email: { from: '{{emailFrom}}', to: '{{recipient.email}}', - subject: 'New update shared with community {{community.displayName}}', + subject: 'New update shared with community {{community.name}}', html: `{% extends "src/templates/_layouts/email-transactional.html" %} {% block content %} Hi {{recipient.firstname}},

- A new update was shared by {{update.sender.firstname} with your community.
- To view the update please navigate to {{community.displayName}}.

+ A new update was shared by {{sender.firstname}} with your community.
+ To view the update please navigate to {{community.name}}.

Sincerely yours, Team Alkemio diff --git a/test/data/admin.user.json b/test/data/admin.user.json index 5b084792..ad981262 100644 --- a/test/data/admin.user.json +++ b/test/data/admin.user.json @@ -5,6 +5,68 @@ "displayName": "admin alkemio", "firstName": "admin", "lastName": "alkemio", - "email": "admin@alkem.io" + "email": "admin@alkem.io", + "preferences": [ + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "[Admin] Community Applications", + "description": "Receive notification when a new application is received for a community for which I am an administrator", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_RECEIVED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Updates", + "description": "Receive notification when a new update is shared with a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_UPDATES" + } + }, + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "Community Application", + "description": "Receive notification when I apply to join a community", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_SUBMITTED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion response", + "description": "Receive notification when a response is sent to a discussion I contributed to", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_RESPONSE" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion created", + "description": "Receive notification when a new discussion is created on a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_CREATED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "[Admin] New user sign up", + "description": "Receive notification when a new user signs up", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_USER_SIGN_UP" + } + } + ] } } diff --git a/test/data/challenge.admins.json b/test/data/challenge.admins.json index 9a5f3392..b41603c9 100644 --- a/test/data/challenge.admins.json +++ b/test/data/challenge.admins.json @@ -6,7 +6,69 @@ "displayName": "Madalyn Jerold", "firstName": "Madalyn", "lastName": "Jerold", - "email": "Madalyn@Jerold.com" + "email": "Madalyn@Jerold.com", + "preferences": [ + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "[Admin] Community Applications", + "description": "Receive notification when a new application is received for a community for which I am an administrator", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_RECEIVED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Updates", + "description": "Receive notification when a new update is shared with a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_UPDATES" + } + }, + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "Community Application", + "description": "Receive notification when I apply to join a community", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_SUBMITTED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion response", + "description": "Receive notification when a response is sent to a discussion I contributed to", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_RESPONSE" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion created", + "description": "Receive notification when a new discussion is created on a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_CREATED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "[Admin] New user sign up", + "description": "Receive notification when a new user signs up", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_USER_SIGN_UP" + } + } + ] } ] } diff --git a/test/data/event.application.created.payload.json b/test/data/event.application.created.payload.json new file mode 100644 index 00000000..61e30ea5 --- /dev/null +++ b/test/data/event.application.created.payload.json @@ -0,0 +1,20 @@ +{ + "pattern": "communityApplicationCreated", + "data": { + "applicationCreatorID": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", + "applicantID": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", + "community": { + "name": "02 Zero Hunger", + "type": "challenge" + }, + "hub": { + "id": "32818605-ef2f-4395-bb49-1dc2835c23de", + "challenge": { + "id": "7b86f954-d8c3-4fac-a652-b922c80e5c20", + "opportunity": { + "id": "636be60f-b64a-4742-8b50-69e608601935" + } + } + } + } +} diff --git a/test/data/event.communication.discussion.created.payload.json b/test/data/event.communication.discussion.created.payload.json new file mode 100644 index 00000000..5db46068 --- /dev/null +++ b/test/data/event.communication.discussion.created.payload.json @@ -0,0 +1,24 @@ +{ + "pattern": "communicationDiscussionCreated", + "data": { + "discussion": { + "id": "yikes", + "createdBy": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", + "title": "My discussion title", + "description": "description" + }, + "community": { + "name": "02 Zero Hunger", + "type": "challenge" + }, + "hub": { + "id": "32818605-ef2f-4395-bb49-1dc2835c23de", + "challenge": { + "id": "7b86f954-d8c3-4fac-a652-b922c80e5c20", + "opportunity": { + "id": "636be60f-b64a-4742-8b50-69e608601935" + } + } + } + } +} diff --git a/test/data/event.communication.update.sent.json b/test/data/event.communication.update.sent.json new file mode 100644 index 00000000..42c2be7f --- /dev/null +++ b/test/data/event.communication.update.sent.json @@ -0,0 +1,22 @@ +{ + "pattern": "communicationUpdateSent", + "data": { + "update": { + "id": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", + "createdBy": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8" + }, + "community": { + "name": "02 Zero Hunger", + "type": "challenge" + }, + "hub": { + "id": "32818605-ef2f-4395-bb49-1dc2835c23de", + "challenge": { + "id": "7b86f954-d8c3-4fac-a652-b922c80e5c20", + "opportunity": { + "id": "636be60f-b64a-4742-8b50-69e608601935" + } + } + } + } +} diff --git a/test/data/event.payload.json b/test/data/event.payload.json deleted file mode 100644 index 7ea07a8e..00000000 --- a/test/data/event.payload.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "eventPayload": { - "pattern": "communityApplicationCreated", - "data": { - "applicationCreatorID": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", - "applicantID": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8", - "community": { - "name": "02 Zero Hunger", - "type": "challenge" - }, - "hub": { - "id": "32818605-ef2f-4395-bb49-1dc2835c23de", - "challenge": { - "id": "7b86f954-d8c3-4fac-a652-b922c80e5c20", - "opportunity": { - "id": "636be60f-b64a-4742-8b50-69e608601935" - } - } - } - } - } -} diff --git a/test/data/user.registered.event.payload.json b/test/data/event.user.registered.payload.json similarity index 68% rename from test/data/user.registered.event.payload.json rename to test/data/event.user.registered.payload.json index bf994b1d..ca1664c5 100644 --- a/test/data/user.registered.event.payload.json +++ b/test/data/event.user.registered.payload.json @@ -1,5 +1,5 @@ { - "pattern": "userRegistration", + "pattern": "userRegistered", "data": { "userID": "f0a47bad-eca5-4942-84ac-4dc9f085b7b8" } diff --git a/test/data/opportunity.admins.json b/test/data/opportunity.admins.json index a6802076..1a775c53 100644 --- a/test/data/opportunity.admins.json +++ b/test/data/opportunity.admins.json @@ -6,7 +6,69 @@ "displayName": "Kathern Keira", "firstName": "Kathern", "lastName": "Keira", - "email": "Kathern@Keira.com" + "email": "Kathern@Keira.com", + "preferences": [ + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "[Admin] Community Applications", + "description": "Receive notification when a new application is received for a community for which I am an administrator", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_RECEIVED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Updates", + "description": "Receive notification when a new update is shared with a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_UPDATES" + } + }, + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "Community Application", + "description": "Receive notification when I apply to join a community", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_SUBMITTED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion response", + "description": "Receive notification when a response is sent to a discussion I contributed to", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_RESPONSE" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion created", + "description": "Receive notification when a new discussion is created on a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_CREATED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "[Admin] New user sign up", + "description": "Receive notification when a new user signs up", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_USER_SIGN_UP" + } + } + ] }, { "id": "d2c354a9-afad-4e4d-9969-cba1a925b302", @@ -14,7 +76,69 @@ "displayName": "Madalyn Jerold", "firstName": "Madalyn", "lastName": "Jerold", - "email": "Madalyn@Jerold.com" + "email": "Madalyn@Jerold.com", + "preferences": [ + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "[Admin] Community Applications", + "description": "Receive notification when a new application is received for a community for which I am an administrator", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_RECEIVED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Updates", + "description": "Receive notification when a new update is shared with a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_UPDATES" + } + }, + { + "value": "true", + "definition": { + "group": "Notification", + "displayName": "Community Application", + "description": "Receive notification when I apply to join a community", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_APPLICATION_SUBMITTED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion response", + "description": "Receive notification when a response is sent to a discussion I contributed to", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_RESPONSE" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "Community Discussion created", + "description": "Receive notification when a new discussion is created on a community I am a member of", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_COMMUNICATION_DISCUSSION_CREATED" + } + }, + { + "value": "false", + "definition": { + "group": "Notification", + "displayName": "[Admin] New user sign up", + "description": "Receive notification when a new user signs up", + "valueType": "BOOLEAN", + "type": "NOTIFICATION_USER_SIGN_UP" + } + } + ] } ] } diff --git a/test/mocks/notification.recipients.yml.adapter.mock.ts b/test/mocks/notification.recipients.yml.adapter.mock.ts index 84ebbd14..9eca4c53 100644 --- a/test/mocks/notification.recipients.yml.adapter.mock.ts +++ b/test/mocks/notification.recipients.yml.adapter.mock.ts @@ -1,7 +1,6 @@ import { Provider } from '@nestjs/common'; import { MockType } from '@test/utils/mock.type'; import { NotificationRecipientsYmlAdapter } from '@src/services'; -import { NOTIFICATION_RECIPIENTS_YML_ADAPTER } from '@src/common'; export class MockNotificationRecipientsYmlAdapter implements MockType