diff --git a/packages/api/src/routes/v2/microcredit/create.ts b/packages/api/src/routes/v2/microcredit/create.ts index 0b4ff255c..1f9402860 100644 --- a/packages/api/src/routes/v2/microcredit/create.ts +++ b/packages/api/src/routes/v2/microcredit/create.ts @@ -90,7 +90,7 @@ export default (route: Router): void => { * tags: * - "microcredit" * summary: "Update microcredit applications" - * description: "Status can be 0: pending, 1: submitted, 2: in-review, 3: requested-changes, 4: approved, 5: rejected" + * description: "Status can be 0: pending, 1: submitted, 2: in-review, 3: requested-changes, 4: interview, 5: approved, 6: rejected" * requestBody: * content: * application/json: diff --git a/packages/core/src/interfaces/app/appNotification.ts b/packages/core/src/interfaces/app/appNotification.ts index a059d22c0..f72a9584f 100644 --- a/packages/core/src/interfaces/app/appNotification.ts +++ b/packages/core/src/interfaces/app/appNotification.ts @@ -33,15 +33,31 @@ */ export enum NotificationType { - STORY_LIKED, - BENEFICIARY_ADDED, - MANAGER_ADDED, - COMMUNITY_CREATED, - LOAN_ADDED, - LEARN_AND_EARN_DO_NEW_LESSON, - LOAN_STATUS_CHANGED, - LOAN_APPLICATION_SUBMITTED, - NEW_LOAN_SUBMITTED + STORY_LIKED = 0, + BENEFICIARY_ADDED = 1, + MANAGER_ADDED = 2, + COMMUNITY_CREATED = 3, + // LOAN_ADDED = 4, // removed + LEARN_AND_EARN_DO_NEW_LESSON = 5, + // LOAN_STATUS_CHANGED = 6, // removed + LOAN_APPLICATION_SUBMITTED = 7, + LOAN_APPLICATION_RECEIVED = 8, + LOAN_APPLICATION_APPROVED = 9, + LOAN_APPLICATION_REJECTED = 10, + LOAN_APPLICATION_REQUEST_CHANGES = 11, + LOAN_APPLICATION_INTERVIEW = 12, + TRANSACTION_RECEIVED = 13 +} + +export type NotificationParams = { + path?: string; +}; + +export enum NotificationParamsPath { + STORY = 'stories?id=', + COMMUNITY = 'communities/', + LOAN_APPLICATION = 'microcredit/form/', + LOAN_APPROVED = 'microcredit/' } export interface AppNotification { diff --git a/packages/core/src/services/microcredit/create.ts b/packages/core/src/services/microcredit/create.ts index 56db01f10..ed5940e13 100644 --- a/packages/core/src/services/microcredit/create.ts +++ b/packages/core/src/services/microcredit/create.ts @@ -1,4 +1,5 @@ import * as prismicH from '@prismicio/helpers'; +import { AppUser } from '../../interfaces/app/appUser'; import { AppUserModel } from '../../database/models/app/appUser'; import { BaseError, prismic as basePrimisc, locales } from '../../utils'; import { MailDataRequired } from '@sendgrid/mail'; @@ -8,7 +9,7 @@ import { MicroCreditApplicationStatus } from '../../interfaces/microCredit/applications'; import { MicroCreditContentStorage } from '../../services/storage'; -import { NotificationType } from '../../interfaces/app/appNotification'; +import { NotificationParamsPath, NotificationType } from '../../interfaces/app/appNotification'; import { NullishPropertiesOf } from 'sequelize/types/utils'; import { Op, Optional, Transaction, WhereOptions } from 'sequelize'; import { config } from '../../..'; @@ -150,7 +151,7 @@ export default class MicroCreditCreate { { model: models.appUser, as: 'user', - attributes: ['id', 'walletPNT', 'appPNT', 'language'] + attributes: ['id', 'email', 'walletPNT', 'appPNT', 'language'] } ] }); @@ -160,7 +161,31 @@ export default class MicroCreditCreate { .map(application => application.user) .filter(user => user !== undefined) as AppUserModel[]; - await sendNotification(users, NotificationType.LOAN_STATUS_CHANGED, true, true, undefined, transaction); + // we are assuming that all applications have the same status + let notificationType: NotificationType = NotificationType.LOAN_APPLICATION_INTERVIEW; + switch (status[0]) { + case MicroCreditApplicationStatus.APPROVED: + notificationType = NotificationType.LOAN_APPLICATION_APPROVED; + break; + case MicroCreditApplicationStatus.REJECTED: + notificationType = NotificationType.LOAN_APPLICATION_REJECTED; + break; + case MicroCreditApplicationStatus.REQUEST_CHANGES: + notificationType = NotificationType.LOAN_APPLICATION_REQUEST_CHANGES; + break; + } + + users.forEach((user, i) => { + this._notifyApplicationChangeStatusByEmail(user, applicationIds[i], notificationType); + }); + await sendNotification( + users, + notificationType, + true, + true, + applicationIds.map(a => ({ path: `${NotificationParamsPath.LOAN_APPLICATION}${a}` })), + transaction + ); } public saveForm = async ( @@ -203,10 +228,12 @@ export default class MicroCreditCreate { id: userForm.selectedLoanManagerId } }) - .then(user => user && sendNotification([user], NotificationType.NEW_LOAN_SUBMITTED, true, true)); + .then( + user => + user && sendNotification([user], NotificationType.LOAN_APPLICATION_RECEIVED, false, true) + ); // below we will send email to user with form // get user email and language to get text for template and send email - const urlToApplicationForm = `https://app.impactmarket.com/microcredit/form/${userForm.id}`; const user = await models.appUser.findOne({ attributes: ['email', 'language', 'walletPNT', 'appPNT'], where: { @@ -217,73 +244,13 @@ export default class MicroCreditCreate { throw new BaseError('USER_NOT_FOUND', 'User not found'); } - // get text for email template on user language defaulting to english - const locale = locales.find(({ shortCode }) => user?.language.toLowerCase() === shortCode.toLowerCase()) - ?.code; - const response = await prismic.getAllByType('push_notifications_data', { - lang: locale || 'en-US' - }); - let submittedFormEmailNotificationSubject: string | undefined; - let submittedFormEmailNotificationTitle: string | undefined; - let submittedFormEmailNotificationSubtitle: string | undefined; - let submittedFormEmailNotificationViewApplication: string | undefined; - let submittedFormEmailNotificationViewNextSteps: string | undefined; - if (response.length > 0) { - const data = response[0].data; - submittedFormEmailNotificationSubject = data['submitted-form-email-notification-subject']; - submittedFormEmailNotificationTitle = data['submitted-form-email-notification-title']; - submittedFormEmailNotificationSubtitle = data['submitted-form-email-notification-subtitle']; - submittedFormEmailNotificationViewApplication = - data['submitted-form-email-notification-view-application']; - submittedFormEmailNotificationViewNextSteps = - prismicH.asHTML(data['submitted-form-email-notification-next-steps']) || ''; - } - if ( - !submittedFormEmailNotificationSubject || - !submittedFormEmailNotificationTitle || - !submittedFormEmailNotificationSubtitle || - !submittedFormEmailNotificationViewApplication || - !submittedFormEmailNotificationViewNextSteps - ) { - const response = await prismic.getAllByType('push_notifications_data', { - lang: 'en-US' - }); - const data = response[0].data; - submittedFormEmailNotificationSubject = data['submitted-form-email-notification-subject']; - submittedFormEmailNotificationTitle = data['submitted-form-email-notification-title']; - submittedFormEmailNotificationSubtitle = data['submitted-form-email-notification-subtitle']; - submittedFormEmailNotificationViewApplication = - data['submitted-form-email-notification-view-application']; - submittedFormEmailNotificationViewNextSteps = - prismicH.asHTML(data['submitted-form-email-notification-next-steps']) || ''; - } - - // build the email structure and send - const dynamicTemplateData = { - submittedFormEmailNotificationTitle, - submittedFormEmailNotificationSubtitle, - urlToApplicationForm, - submittedFormEmailNotificationViewApplication, - submittedFormEmailNotificationViewNextSteps, - submittedFormEmailNotificationSubject - }; - const personalizations = [ - { - to: [{ email: user.email }], - dynamicTemplateData - } - ]; - const sendgridData: MailDataRequired = { - from: { - name: 'impactMarket', - email: 'hello@impactmarket.com' - }, - personalizations, - templateId: 'd-b257690897ff41028d7ad8cabe88f8cb' - }; - sendEmail(sendgridData); + this._notifyApplicationChangeStatusByEmail( + user, + userForm.id, + NotificationType.LOAN_APPLICATION_SUBMITTED + ); sendNotification([user.toJSON()], NotificationType.LOAN_APPLICATION_SUBMITTED, true, true, { - url: urlToApplicationForm + path: `${NotificationParamsPath.LOAN_APPLICATION}${userForm.id}` }); } @@ -319,4 +286,110 @@ export default class MicroCreditCreate { note }); }; + + private async _notifyApplicationChangeStatusByEmail( + user: AppUser, + formId: number, + notificationType: NotificationType + ) { + let baseKey = 'submitted'; + let emailType = MicroCreditApplicationStatus.PENDING; + let urlPath = NotificationParamsPath.LOAN_APPLICATION; + switch (notificationType) { + // case NotificationType.LOAN_APPLICATION_SUBMITTED: + case NotificationType.LOAN_APPLICATION_REQUEST_CHANGES: + baseKey = 'requested-changes'; + emailType = MicroCreditApplicationStatus.REQUEST_CHANGES; + break; + case NotificationType.LOAN_APPLICATION_APPROVED: + baseKey = 'approved'; + emailType = MicroCreditApplicationStatus.APPROVED; + urlPath = NotificationParamsPath.LOAN_APPROVED; + break; + case NotificationType.LOAN_APPLICATION_INTERVIEW: + baseKey = 'interview'; + emailType = MicroCreditApplicationStatus.INTERVIEW; + urlPath = NotificationParamsPath.LOAN_APPROVED; + break; + case NotificationType.LOAN_APPLICATION_REJECTED: + baseKey = 'rejected'; + emailType = MicroCreditApplicationStatus.REJECTED; + break; + } + let buttonURL = `https://app.impactmarket.com/${urlPath}${formId}`; + if (urlPath === NotificationParamsPath.LOAN_APPROVED) { + buttonURL = `https://app.impactmarket.com/${urlPath}`; + } + // get text for email template on user language defaulting to english + const locale = locales.find(({ shortCode }) => user.language.toLowerCase() === shortCode.toLowerCase())?.code; + const response = await prismic.getAllByType('push_notifications_data', { + lang: locale || 'en-US' + }); + let subject: string | undefined; + let title: string | undefined; + let subtitle: string | undefined; + let buttonText: string | undefined; + let body: string | undefined; + if (response.length > 0) { + const data = response[0].data; + subject = data[`${baseKey}-form-email-notification-subject`]; + title = data[`${baseKey}-form-email-notification-title`]; + subtitle = data[`${baseKey}-form-email-notification-subtitle`]; + buttonText = data[`${baseKey}-form-email-notification-button-text`]; + body = data[`${baseKey}-form-email-notification-body`] + ? prismicH.asHTML(data[`${baseKey}-form-email-notification-body`]) || undefined + : undefined; + } + if (!subject || !title || !subtitle || !buttonText || !body) { + const response = await prismic.getAllByType('push_notifications_data', { + lang: 'en-US' + }); + const data = response[0].data; + subject = data[`${baseKey}-form-email-notification-subject`]; + title = data[`${baseKey}-form-email-notification-title`]; + subtitle = data[`${baseKey}-form-email-notification-subtitle`]; + buttonText = data[`${baseKey}-form-email-notification-button-text`]; + body = data[`${baseKey}-form-email-notification-body`] + ? prismicH.asHTML(data[`${baseKey}-form-email-notification-body`]) || undefined + : undefined; + } + + if (notificationType === NotificationType.LOAN_APPLICATION_APPROVED) { + const formData = await models.microCreditApplications.findOne({ + attributes: ['amount'], + where: { + id: formId + } + }); + subtitle = subtitle! + .replace('{{amount}}', formData!.amount?.toString() || '0') + .replace('{{currency}}', 'cUSD'); + } + + // build the email structure and send + const dynamicTemplateData = { + title, + subtitle, + buttonURL, + buttonText, + body, + subject, + emailType + }; + const personalizations = [ + { + to: [{ email: user.email }], + dynamicTemplateData + } + ]; + const sendgridData: MailDataRequired = { + from: { + name: 'impactMarket', + email: 'hello@impactmarket.com' + }, + personalizations, + templateId: 'd-b257690897ff41028d7ad8cabe88f8cb' + }; + sendEmail(sendgridData); + } } diff --git a/packages/core/src/services/story/index.ts b/packages/core/src/services/story/index.ts index 7e7e83f91..3d7cb513f 100644 --- a/packages/core/src/services/story/index.ts +++ b/packages/core/src/services/story/index.ts @@ -518,7 +518,6 @@ export default class StoryServiceV2 { where: { contentId, address: userAddress } }); } else { - this.addNotification(userAddress, contentId); const user = await models.appUser.findOne({ attributes: ['id', 'language', 'walletPNT', 'appPNT'], where: { @@ -527,7 +526,7 @@ export default class StoryServiceV2 { }); if (user) { - await sendNotification([user.toJSON()], NotificationType.STORY_LIKED); + await sendNotification([user.toJSON()], NotificationType.STORY_LIKED, false); } await models.storyUserEngagement.create({ @@ -660,26 +659,4 @@ export default class StoryServiceV2 { throw new BaseError('ERROR', 'comment was not deleted'); } } - - private async addNotification(userAddress: string, contentId: number) { - const story = (await models.storyContent.findOne({ - attributes: [], - where: { id: contentId }, - include: [ - { - model: models.appUser, - as: 'user', - attributes: ['id'] - } - ] - }))! as StoryContent; - - await models.appNotification.findOrCreate({ - where: { - userId: story.user!.id, - type: NotificationType.STORY_LIKED, - params: { userAddress, contentId } - } - }); - } } diff --git a/packages/core/src/subscriber/chainSubscribers.ts b/packages/core/src/subscriber/chainSubscribers.ts index a19aee08e..20bd4ec14 100644 --- a/packages/core/src/subscriber/chainSubscribers.ts +++ b/packages/core/src/subscriber/chainSubscribers.ts @@ -1,7 +1,7 @@ import { Logger } from '../utils/logger'; import { MicroCreditApplicationStatus } from '../interfaces/microCredit/applications'; import { Create as MicroCreditCreate } from '../services/microcredit'; -import { NotificationType } from '../interfaces/app/appNotification'; +import { NotificationParamsPath, NotificationType } from '../interfaces/app/appNotification'; import { Transaction } from 'sequelize'; import { config, contracts, database, services, utils } from '../../'; import { ethers } from 'ethers'; @@ -248,7 +248,7 @@ class ChainSubscribers { true, true, { - communityId: affectedRows[0].id + path: `${NotificationParamsPath.COMMUNITY}${affectedRows[0].id}` }, transaction ); @@ -287,7 +287,7 @@ class ChainSubscribers { true, true, { - communityId: community + path: `${NotificationParamsPath.COMMUNITY}${community}` }, transaction ); @@ -341,14 +341,6 @@ class ChainSubscribers { [userAddress], [MicroCreditApplicationStatus.APPROVED], transaction - ), - sendNotification( - [user.toJSON()], - NotificationType.LOAN_ADDED, - true, - true, - undefined, - transaction ) ]); if (!created) { diff --git a/packages/core/src/utils/pushNotification.ts b/packages/core/src/utils/pushNotification.ts index 4718cdc0f..f90d121c4 100644 --- a/packages/core/src/utils/pushNotification.ts +++ b/packages/core/src/utils/pushNotification.ts @@ -1,5 +1,5 @@ import { AppUserModel } from '../database/models/app/appUser'; -import { NotificationType } from '../interfaces/app/appNotification'; +import { NotificationParams, NotificationType } from '../interfaces/app/appNotification'; import { models } from '../database'; import { client as prismic } from '../utils/prismic'; import localesConfig from '../utils/locale.json'; @@ -16,18 +16,18 @@ export async function sendNotification( type: NotificationType, isWallet: boolean = true, isWebApp: boolean = true, - params: object | undefined = undefined, + params: NotificationParams | NotificationParams[] | undefined = undefined, transaction: Transaction | undefined = undefined ) { try { // registry notification await models.appNotification.bulkCreate( - users.map(el => ({ + users.map((el, i) => ({ userId: el.id, type, isWallet, isWebApp, - params + params: params instanceof Array ? params[i] : params })), { transaction } ); @@ -68,12 +68,13 @@ export async function sendNotification( await Promise.all(languages.map(fetchNotificationsFromPrismic)); // send notification by group of languages - Object.keys(prismicNotifications).forEach(async key => { + Object.keys(prismicNotifications).forEach(async (key, i) => { const prismicData = prismicNotifications[key]; sendFirebasePushNotification( prismicData.users.map(el => el.walletPNT!), prismicData.title, - prismicData.description + prismicData.description, + params && (params instanceof Array ? params[i] : params) ).catch(error => utils.Logger.error('sendFirebasePushNotification' + error)); }); } catch (error) { diff --git a/packages/core/tests/integration/subscriber/microcredit.test.ts b/packages/core/tests/integration/subscriber/microcredit.test.ts index 04b375c9f..1b328a0d7 100644 --- a/packages/core/tests/integration/subscriber/microcredit.test.ts +++ b/packages/core/tests/integration/subscriber/microcredit.test.ts @@ -111,7 +111,7 @@ describe.skip('Microcredit', () => { assert.calledWith(notificationUpdated.getCall(0), [ { userId: user.id, - type: NotificationType.LOAN_ADDED, + type: NotificationType.LOAN_APPLICATION_APPROVED, isWallet: true, isWebApp: true, params: undefined