From c45bd0ca4aa24f70e5322c2afa0449d04d62bd16 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 19 Dec 2022 10:36:38 +0330 Subject: [PATCH 01/23] Change some title of notification setting --- migrations/1660716115917-seedNotificationType.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 605b069..2c0251b 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1379,8 +1379,8 @@ export const GivethNotificationTypes = { PROJECT_UPDATED_OWNER: { name: 'Project updated - owner', - title: 'Project updates', - description: 'Your project update', + title: 'Your project update', + description: 'You Posted an update on your project', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, category: NOTIFICATION_CATEGORY.PROJECT_RELATED, @@ -1483,8 +1483,8 @@ export const GivethNotificationTypes = { }, NEW_PROJECT_UPDATE_FOR_USERS_WHO_LIKED: { name: 'Project updates', - title: 'Project updates', - description: 'Your liked project update', + title: 'Your liked project update', + description: 'You Posted an update on your project', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, category: NOTIFICATION_CATEGORY.PROJECT_RELATED, @@ -1756,8 +1756,8 @@ export const GivethNotificationTypes = { }, PROJECT_STATUS_GROUP: { name: 'Project status', - title: 'Project status', - description: 'Your project status', + title: 'Your project status', + description: 'When your own project has been listed, unlisted, cancelled, activated or deactivated', showOnSettingPage: true, isEmailEditable: true, isWebEditable: false, @@ -1853,8 +1853,8 @@ export const GivethNotificationTypes = { }, LIKED_BY_YOU_PROJECT_GROUP: { name: 'Project status - Users Who Liked', - title: 'Project status', - description: 'Your liked project status', + title: 'Your liked project status', + description: 'When your liked Project has been listed, unlisted, cancelled, activated or deactivated', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, schemaValidator: null, From 1e95c39d3c9b4d56279b7ddc1e5ab09f6f1658a8 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 19 Dec 2022 10:41:32 +0330 Subject: [PATCH 02/23] Make Unstake/Stake notifications disabled --- migrations/1660716115917-seedNotificationType.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 2c0251b..614598f 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1,4 +1,4 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import {Column, MigrationInterface, QueryRunner} from 'typeorm'; import { NotificationType, SCHEMA_VALIDATORS_NAMES, @@ -873,6 +873,10 @@ export const GivethNotificationTypes = { pushNotifierService: null, categoryGroup: NOTIFICATION_CATEGORY_GROUPS.STAKING, title: 'Stake', + isEmailEditable: false, + isWebEditable: false, + emailDefaultValue: false, + webDefaultValue: false, htmlTemplate: [ { type: 'p', @@ -909,6 +913,10 @@ export const GivethNotificationTypes = { emailNotificationId: null, pushNotifierService: null, categoryGroup: NOTIFICATION_CATEGORY_GROUPS.STAKING, + isEmailEditable: false, + isWebEditable: false, + emailDefaultValue: false, + webDefaultValue: false, title: 'UnStake', htmlTemplate: [ { @@ -1809,7 +1817,7 @@ export const GivethNotificationTypes = { microService: MICRO_SERVICES.givethio, title: 'Stakes', description: 'Shows when you stake or unstake on the GIVfarm', - showOnSettingPage: true, + showOnSettingPage: false, schemaValidator: null, emailNotifierService: null, emailNotificationId: null, From 9d37a50f0c9b96d480d48731ba7c499af1a8ebe5 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 19 Dec 2022 10:53:02 +0330 Subject: [PATCH 03/23] Change notification setting copies --- migrations/1660716115917-seedNotificationType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 614598f..db87234 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1492,7 +1492,7 @@ export const GivethNotificationTypes = { NEW_PROJECT_UPDATE_FOR_USERS_WHO_LIKED: { name: 'Project updates', title: 'Your liked project update', - description: 'You Posted an update on your project', + description: 'When your liked project has an update', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, category: NOTIFICATION_CATEGORY.PROJECT_RELATED, From e5c40e535e5cc7ba75db7c247b3e4d1ffe3cfa20 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Wed, 21 Dec 2022 13:18:04 +0330 Subject: [PATCH 04/23] Create notificationType for givPower unlocked --- .../1660716115917-seedNotificationType.ts | 5 +- src/routes/v1/notificationRouter.test.ts | 50 +++++++++++++++++++ .../segmentAndMetadataValidators.ts | 6 ++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index db87234..04d98c5 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1642,9 +1642,9 @@ export const GivethNotificationTypes = { content: 'You locked {amount} & recieved {amount} GIVpower', }, YOU_UNLOCKED_GIVPOWER: { - name: '{amount} unlocked.', + name: 'givPower unlocked', description: 'User unlocked GIVpower', - microService: MICRO_SERVICES.givethio, + microService: MICRO_SERVICES.givEconomyNotificationMicroService, category: NOTIFICATION_CATEGORY.GIV_POWER, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_UNLOCKED_GIVPOWER, @@ -1662,6 +1662,7 @@ export const GivethNotificationTypes = { type: 'p', content: ' unlocked.', }, + ], content: '{amount} unlocked.', }, diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 8c9ea71..6daf4e4 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1320,6 +1320,56 @@ function sendNotificationTestCases() { } }); + it('should create *Unlock* notification, success, segment is off', async () => { + const data = { + eventName: 'givPower unlocked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + metadata: { + amount: 10, + round: 13, + }, + }; + + const result = await axios.post(sendNotificationUrl, data, { + headers: { + authorization: getGivEconomyBasicAuth(), + }, + }); + assert.equal(result.status, 200); + assert.isOk(result.data); + assert.isTrue(result.data.success); + }); + it('should create *Unlock* notification, failed invalid metadata, segment is off', async () => { + try { + const data = { + eventName: 'givPower unlocked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + metadata: { + round: 11 + }, + }; + + await axios.post(sendNotificationUrl, data, { + headers: { + authorization: getGivEconomyBasicAuth(), + }, + }); + // If request doesn't fail, it means this test failed + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, + ); + assert.equal(e.response.data.description, '"amount" is required'); + } + }); + + it('should create *UnStake* notification, success, segment is off', async () => { const data = { eventName: 'UnStake', diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index f82e325..72d5d8d 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -45,6 +45,10 @@ const projectBoostedSchema = Joi.object({ const givPowerLockedSchema = Joi.object({ amount: Joi.number()?.greater(0).required(), }); +const givPowerUnLockedSchema = Joi.object({ + amount: Joi.number()?.greater(0).required(), + round: Joi.number()?.greater(0).required(), +}); const donationTrackerSchema = Joi.object({ email: Joi.string().allow(null, ''), // if anonymous @@ -241,7 +245,7 @@ export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { }, givPowerUserLockedGivPower: { metadata: givPowerLockedSchema, segment: null }, givPowerUserUnlockedGivPower: { - metadata: givPowerLockedSchema, + metadata: givPowerUnLockedSchema, segment: null, }, givPowerUserGivPowerRelockedAutoMatically: { From ac06b13a757b54d0722e365aa6e56d271c99b1f5 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Thu, 22 Dec 2022 11:24:46 +0300 Subject: [PATCH 05/23] Changed givpower unlock method type --- src/utils/validators/segmentAndMetadataValidators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index 72d5d8d..5590da3 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -46,7 +46,7 @@ const givPowerLockedSchema = Joi.object({ amount: Joi.number()?.greater(0).required(), }); const givPowerUnLockedSchema = Joi.object({ - amount: Joi.number()?.greater(0).required(), + amount: Joi.string().required(), round: Joi.number()?.greater(0).required(), }); From d77473da9f27f393ca207bf0938fa0caa6f4b098 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Thu, 22 Dec 2022 11:37:50 +0300 Subject: [PATCH 06/23] Added transactionHash, networkId and poolName to givpower token unlock event Updated a test accordingly to pass --- src/routes/v1/notificationRouter.test.ts | 5 ++++- src/utils/validators/segmentAndMetadataValidators.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 6daf4e4..049fe96 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1327,8 +1327,11 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - amount: 10, + poolName: 'GIVpower', + amount: '10', round: 13, + transactionHash: generateRandomTxHash(), + network: 100, }, }; diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index 5590da3..debb45d 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -43,11 +43,18 @@ const projectBoostedSchema = Joi.object({ }); const givPowerLockedSchema = Joi.object({ - amount: Joi.number()?.greater(0).required(), + poolName: Joi.string().required(), + amount: Joi.string().required(), + round: Joi.number()?.greater(0).required(), + transactionHash: Joi.string().required(), + network: Joi.number().required(), }); const givPowerUnLockedSchema = Joi.object({ + poolName: Joi.string().required(), amount: Joi.string().required(), round: Joi.number()?.greater(0).required(), + transactionHash: Joi.string().required(), + network: Joi.number().required(), }); const donationTrackerSchema = Joi.object({ From 1b1e6b03f9cf8b4ef0475f33e595d6cb8237eead Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Thu, 22 Dec 2022 11:46:03 +0300 Subject: [PATCH 07/23] Updated test to pass --- src/routes/v1/notificationRouter.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 049fe96..54bb295 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1352,7 +1352,10 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - round: 11 + round: 11, + poolName: 'GIVpower', + transactionHash: generateRandomTxHash(), + network: 100, }, }; From 6626850b003dc287d713f82b74ccf75d5fdfbc1e Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Sun, 25 Dec 2022 10:57:37 +0330 Subject: [PATCH 08/23] Change category og givPower notifications to givEconomy https://github.com/Giveth/giveth-dapps-v2/issues/477#issuecomment-1364552778 --- migrations/1660716115917-seedNotificationType.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 04d98c5..5ea6109 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1519,7 +1519,7 @@ export const GivethNotificationTypes = { name: 'You boosted', description: 'User boosted a project', microService: MICRO_SERVICES.givethio, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_BOOSTED, emailNotifierService: null, @@ -1556,7 +1556,7 @@ export const GivethNotificationTypes = { name: 'You changed the allocation', description: 'User changed boosted allocation', microService: MICRO_SERVICES.givethio, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_CHANGED_BOOSTED_ALLOCATION, emailNotifierService: null, @@ -1576,7 +1576,7 @@ export const GivethNotificationTypes = { name: 'Your project has been boosted.', description: 'Project has received a boosting', microService: MICRO_SERVICES.givethio, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.PROJECT_HAS_BEEN_BOOSTED, emailNotifierService: null, @@ -1609,7 +1609,7 @@ export const GivethNotificationTypes = { name: 'You locked {amount} & recieved {amount} GIVpower', description: 'User locked GIVpower', microService: MICRO_SERVICES.givethio, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_LOCKED_GIVPOWER, emailNotifierService: null, @@ -1645,7 +1645,7 @@ export const GivethNotificationTypes = { name: 'givPower unlocked', description: 'User unlocked GIVpower', microService: MICRO_SERVICES.givEconomyNotificationMicroService, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_UNLOCKED_GIVPOWER, emailNotifierService: null, @@ -1670,7 +1670,7 @@ export const GivethNotificationTypes = { name: '{amount} re-locked automatically', description: 'User unlocked GIVpower', microService: MICRO_SERVICES.givethio, - category: NOTIFICATION_CATEGORY.GIV_POWER, + category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: '', schemaValidator: SCHEMA_VALIDATORS_NAMES.USER_GIVPOWER_RELOCKED_AUTOMATICALLY, From bf4b840eab44c56f31a2e9b8131cf362e559479c Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 26 Dec 2022 17:21:49 +0330 Subject: [PATCH 09/23] Add projectBoosted notificationType related to https://github.com/Giveth/impact-graph/issues/775 --- .../1660716115917-seedNotificationType.ts | 24 +++++++++++++++++++ src/entities/notificationType.ts | 1 + .../segmentAndMetadataValidators.ts | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 5ea6109..da9fd33 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -320,6 +320,30 @@ export const GivethNotificationTypes = { content: 'The {project name} that you donated before is no longer visible.\n{reason}', }, + PROJECT_BOOSTED: { + name: 'Project boosted', + description: 'Project has been boosted', + microService: MICRO_SERVICES.givethio, + category: NOTIFICATION_CATEGORY.PROJECT_RELATED, + icon: '', + schemaValidator: SCHEMA_VALIDATORS_NAMES.PROJECT_BOOSTED, + emailNotifierService: THIRD_PARTY_EMAIL_SERVICES.SEGMENT, + emailNotificationId: null, + pushNotifierService: null, + htmlTemplate: [ + { + type: 'p', + content: 'Someone boosted your project ', + }, + { + type: 'a', + content: '$projectTitle', + href: '$projectLink', + } + ], + content: + 'Someone boosted your project {projectName}!', + }, PROJECT_CANCELLED: { name: 'Project cancelled', description: 'Project has been cancelled', diff --git a/src/entities/notificationType.ts b/src/entities/notificationType.ts index fce53ab..c56db81 100644 --- a/src/entities/notificationType.ts +++ b/src/entities/notificationType.ts @@ -33,6 +33,7 @@ export const SCHEMA_VALIDATORS_NAMES = { PROJECT_ACTIVATED: 'projectActivated', PROJECT_DEACTIVATED: 'projectDeactivated', PROJECT_CANCELLED: 'projectCancelled', + PROJECT_BOOSTED: 'projectBoosted', VERIFICATION_FORM_SENT: 'verificationFormSent', VERIFICATION_FORM_Reapply_Reminder: 'verificationFormReapplyReminder', VERIFICATION_FORM_REJECTED: 'verificationFormRejected', diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index debb45d..2dcba9e 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -186,6 +186,10 @@ export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { metadata: projectTitleProjectLinkSchema, segment: projectRelatedTrackerSchema, }, + projectBoosted: { + metadata: projectTitleProjectLinkSchema, + segment: null, + }, sendEmailConfirmation: { metadata: null, segment: projectRelatedTrackerSchema, From 9cab21aa28ba67055f82d9965daf9fe7b7f33a4b Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 14:00:04 +0300 Subject: [PATCH 10/23] Added GIVBACK IS READY event --- .../1660716115917-seedNotificationType.ts | 30 +++++++++---------- src/entities/notificationType.ts | 1 + src/routes/v1/notificationRouter.test.ts | 12 ++++---- .../segment/segmentAnalyticsSingleton.ts | 3 +- .../segmentAndMetadataValidators.ts | 16 ++++++++-- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 5ea6109..a438a5f 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -892,7 +892,7 @@ export const GivethNotificationTypes = { }, { type: 'a', - content: '$poolName', + content: '$contractName', href: '/givfarm', }, { @@ -900,7 +900,7 @@ export const GivethNotificationTypes = { content: ' was successful.', }, ], - content: 'Staking {amount} of {poolName} was successful.', + content: 'Staking {amount} of {contractName} was successful.', }, UN_STAKE: { name: 'UnStake', @@ -933,7 +933,7 @@ export const GivethNotificationTypes = { }, { type: 'a', - content: '$poolName', + content: '$contractName', href: '/givfarm', }, { @@ -941,32 +941,32 @@ export const GivethNotificationTypes = { content: ' was successful.', }, ], - content: 'Unstaking {amount} of {poolname} was successful.', + content: 'Unstaking {amount} of {contractName} was successful.', }, - GIV_BACKS_ARE_READY_TO_CLAIM: { - name: 'GIVbacks are ready to claim', - title: 'GIVbacks', - description: 'When GIVbacks are ready to be claimed after each round', + GIV_BACK_IS_READY_TO_CLAIM: { + name: 'GIVback is ready to claim', + title: 'GIVback', + description: 'When GIVback is ready to be claimed after each round', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, category: NOTIFICATION_CATEGORY.GIV_ECONOMY, - icon: 'IconUnstake', - schemaValidator: SCHEMA_VALIDATORS_NAMES.GIV_FARM_READY_TO_CLAIM, + icon: 'IconGIVBack', + schemaValidator: SCHEMA_VALIDATORS_NAMES.GIV_BACK_IS_READY_TO_CLAIM, emailNotifierService: THIRD_PARTY_EMAIL_SERVICES.SEGMENT, - emailNotificationId: SegmentEvents.PROJECT_REJECTED, + emailNotificationId: SegmentEvents.GIVBACK_IS_READY_TO_CLAIM, pushNotifierService: null, htmlTemplate: [ { type: 'p', - content: 'Your GIVbacks for round ', + content: 'Your GIVback ', }, { type: 'b', - content: '$round', + content: '$amount', }, { type: 'p', - content: ' are ready to claim! ', + content: ' GIV is ready to claim! ', }, { type: 'br' }, { @@ -980,7 +980,7 @@ export const GivethNotificationTypes = { }, ], content: - 'Your GIVbacks for round {round number} are ready to claim! \n{Click here} to take a shortcut.', + 'Your GIVback {amount} GIV is ready to claim! \n{Click here} to take a shortcut.', }, PROJECT_EDITED: { name: 'Project edited', diff --git a/src/entities/notificationType.ts b/src/entities/notificationType.ts index fce53ab..7116d69 100644 --- a/src/entities/notificationType.ts +++ b/src/entities/notificationType.ts @@ -59,6 +59,7 @@ export const SCHEMA_VALIDATORS_NAMES = { USER_UNLOCKED_GIVPOWER: 'givPowerUserUnlockedGivPower', USER_GIVPOWER_RELOCKED_AUTOMATICALLY: 'givPowerUserGivPowerRelockedAutoMatically', + GIV_BACK_IS_READY_TO_CLAIM: 'givBackReadyToClaim', }; export type HtmlTemplate = { type: string; content: string; href?: string }[]; diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 54bb295..2b273f1 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1274,7 +1274,7 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - poolName: 'GIV farm', + contractName: 'GIV farm', amount: '10', network: 100, transactionHash: generateRandomTxHash(), @@ -1298,7 +1298,7 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - poolName: 'GIV farm', + contractName: 'GIV farm', network: 100, transactionHash: generateRandomTxHash(), }, @@ -1327,7 +1327,7 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - poolName: 'GIVpower', + contractName: 'GIVpower', amount: '10', round: 13, transactionHash: generateRandomTxHash(), @@ -1353,7 +1353,7 @@ function sendNotificationTestCases() { userWalletAddress: generateRandomEthereumAddress(), metadata: { round: 11, - poolName: 'GIVpower', + contractName: 'GIVpower', transactionHash: generateRandomTxHash(), network: 100, }, @@ -1383,7 +1383,7 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - poolName: 'GIV farm', + contractName: 'GIV farm', amount: '10', network: 100, transactionHash: generateRandomTxHash(), @@ -1407,7 +1407,7 @@ function sendNotificationTestCases() { sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - poolName: 'GIV farm', + contractName: 'GIV farm', network: 100, transactionHash: generateRandomTxHash(), }, diff --git a/src/services/segment/segmentAnalyticsSingleton.ts b/src/services/segment/segmentAnalyticsSingleton.ts index 00cdcaf..71f34f5 100644 --- a/src/services/segment/segmentAnalyticsSingleton.ts +++ b/src/services/segment/segmentAnalyticsSingleton.ts @@ -23,7 +23,8 @@ export enum SegmentEvents { PROJECT_BADGE_UP_FOR_REVOKING = 'Project badge up for revoking', PROJECT_VERIFIED = 'Project verified', PROJECT_REJECTED = 'Project rejected', - GIVBACK_ARE_READY_TO_CLAIM = 'We dont have event yet', + GIVBACKS_ARE_READY_TO_CLAIM = 'We dont have event yet', + GIVBACK_IS_READY_TO_CLAIM = 'We dont have event yet', PROJECT_UNVERIFIED = 'Project unverified', PROJECT_UNVERIFIED_USERS_WHO_BOOSTED = 'Project unverified - Users Who Boosted', PROJECT_ACTIVATED = 'Project activated', diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index debb45d..25f7630 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -43,14 +43,14 @@ const projectBoostedSchema = Joi.object({ }); const givPowerLockedSchema = Joi.object({ - poolName: Joi.string().required(), + contractName: Joi.string().required(), amount: Joi.string().required(), round: Joi.number()?.greater(0).required(), transactionHash: Joi.string().required(), network: Joi.number().required(), }); const givPowerUnLockedSchema = Joi.object({ - poolName: Joi.string().required(), + contractName: Joi.string().required(), amount: Joi.string().required(), round: Joi.number()?.greater(0).required(), transactionHash: Joi.string().required(), @@ -100,7 +100,7 @@ const getDonationPriceFailedMetadataSchema = Joi.object({ txLink: Joi.string().required(), }); const stakeUnStakeSchema = Joi.object({ - poolName: Joi.string().required(), + contractName: Joi.string().required(), transactionHash: Joi.string().required(), network: Joi.number().required(), amount: Joi.string().required(), @@ -115,6 +115,12 @@ const adminMessageSchema = Joi.object({ instruction: Joi.string().required(), href: Joi.string().required(), }); +const givBackReadyClaimSchema = Joi.object({ + contractName: Joi.string().required(), + transactionHash: Joi.string().required(), + network: Joi.number().required(), + amount: Joi.string().required(), +}); export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { [key: string]: { @@ -259,6 +265,10 @@ export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { metadata: givPowerLockedSchema, segment: null, }, + givBackReadyToClaim: { + metadata: givBackReadyClaimSchema, + segment: null, + } }; function throwHttpErrorIfJoiValidatorFails( From b0f051229dc6127b85772ca86c0783f414f16b7f Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 14:04:24 +0300 Subject: [PATCH 11/23] Disabled givback is ready email --- migrations/1660716115917-seedNotificationType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index a438a5f..8d1d29c 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -952,8 +952,8 @@ export const GivethNotificationTypes = { category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: 'IconGIVBack', schemaValidator: SCHEMA_VALIDATORS_NAMES.GIV_BACK_IS_READY_TO_CLAIM, - emailNotifierService: THIRD_PARTY_EMAIL_SERVICES.SEGMENT, - emailNotificationId: SegmentEvents.GIVBACK_IS_READY_TO_CLAIM, + emailNotifierService: null, + emailNotificationId: null, pushNotifierService: null, htmlTemplate: [ { From 5fdb0733938f64988b7114422292579d5cbb96a7 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 27 Dec 2022 14:39:12 +0330 Subject: [PATCH 12/23] Implement sending bulk notifications related to #24 --- .../1660716115917-seedNotificationType.ts | 9 +- .../jwtAuthentication/mockJwtAdapter.ts | 6 +- .../v1/notificationSettingsController.ts | 6 +- src/controllers/v1/notificationsController.ts | 171 +++++----------- .../notificationSettingRepository.ts | 10 +- src/routes/v1/notificationRouter.test.ts | 189 +++++++++++++++++- src/routes/v1/notificationRouter.ts | 19 ++ src/services/notificationService.ts | 115 +++++++++++ src/types/requestResponses.ts | 55 ++--- src/utils/errorMessages.ts | 2 +- src/validators/schemaValidators.ts | 18 +- 11 files changed, 427 insertions(+), 173 deletions(-) create mode 100644 src/services/notificationService.ts diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 5ea6109..4237866 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1,4 +1,4 @@ -import {Column, MigrationInterface, QueryRunner} from 'typeorm'; +import { Column, MigrationInterface, QueryRunner } from 'typeorm'; import { NotificationType, SCHEMA_VALIDATORS_NAMES, @@ -1662,7 +1662,6 @@ export const GivethNotificationTypes = { type: 'p', content: ' unlocked.', }, - ], content: '{amount} unlocked.', }, @@ -1766,7 +1765,8 @@ export const GivethNotificationTypes = { PROJECT_STATUS_GROUP: { name: 'Project status', title: 'Your project status', - description: 'When your own project has been listed, unlisted, cancelled, activated or deactivated', + description: + 'When your own project has been listed, unlisted, cancelled, activated or deactivated', showOnSettingPage: true, isEmailEditable: true, isWebEditable: false, @@ -1863,7 +1863,8 @@ export const GivethNotificationTypes = { LIKED_BY_YOU_PROJECT_GROUP: { name: 'Project status - Users Who Liked', title: 'Your liked project status', - description: 'When your liked Project has been listed, unlisted, cancelled, activated or deactivated', + description: + 'When your liked Project has been listed, unlisted, cancelled, activated or deactivated', showOnSettingPage: true, microService: MICRO_SERVICES.givethio, schemaValidator: null, diff --git a/src/adapters/jwtAuthentication/mockJwtAdapter.ts b/src/adapters/jwtAuthentication/mockJwtAdapter.ts index ea71c82..8fe38f4 100644 --- a/src/adapters/jwtAuthentication/mockJwtAdapter.ts +++ b/src/adapters/jwtAuthentication/mockJwtAdapter.ts @@ -2,13 +2,17 @@ import { JwtAuthenticationInterface } from './JwtAuthenticationInterface'; import { decode, JwtPayload } from 'jsonwebtoken'; import { errorMessages } from '../../utils/errorMessages'; import { logger } from '../../utils/logger'; +import { StandardError } from '../../types/StandardError'; export class MockJwtAdapter implements JwtAuthenticationInterface { verifyJwt(token: string): Promise { try { const decodedJwt = decode(token) as JwtPayload; if (!decodedJwt.publicAddress) { - throw new Error(errorMessages.UN_AUTHORIZED); + throw new StandardError({ + message: errorMessages.UN_AUTHORIZED, + httpStatusCode: 401, + }); } return decodedJwt.publicAddress.toLowerCase(); } catch (e: any) { diff --git a/src/controllers/v1/notificationSettingsController.ts b/src/controllers/v1/notificationSettingsController.ts index 5c35320..656937c 100644 --- a/src/controllers/v1/notificationSettingsController.ts +++ b/src/controllers/v1/notificationSettingsController.ts @@ -13,6 +13,7 @@ import { updateOneNotificationSetting, validateWithJoiSchema, } from '../../validators/schemaValidators'; +import { StandardError } from '../../types/StandardError'; interface SettingParams { id: number; @@ -86,7 +87,10 @@ export class NotificationSettingsController { const notificationSetting = await findNotificationSettingById(id); if (!notificationSetting) { - throw new Error(errorMessages.NOTIFICATION_SETTING_NOT_FOUND); + throw new StandardError({ + message: errorMessages.NOTIFICATION_SETTING_NOT_FOUND, + httpStatusCode: 400, + }); } const newSettingData = { notificationSettingId: id, diff --git a/src/controllers/v1/notificationsController.ts b/src/controllers/v1/notificationsController.ts index c877a49..59e2ff0 100644 --- a/src/controllers/v1/notificationsController.ts +++ b/src/controllers/v1/notificationsController.ts @@ -9,12 +9,12 @@ import { Query, Put, Path, - Example, } from 'tsoa'; import { logger } from '../../utils/logger'; import { countUnreadValidator, + sendBulkNotificationValidator, sendNotificationValidator, validateWithJoiSchema, } from '../../validators/schemaValidators'; @@ -22,33 +22,23 @@ import { CountUnreadNotificationsResponse, GetNotificationsResponse, ReadAllNotificationsRequestType, - ReadAllNotificationsResponse, ReadSingleNotificationResponse, - SendNotificationRequest, SendNotificationResponse, - SendNotificationTypeRequest, + SendNotificationRequest, + sendBulkNotificationRequest, } from '../../types/requestResponses'; -import { errorMessages, errorMessagesEnum } from '../../utils/errorMessages'; -import { StandardError } from '../../types/StandardError'; -import { User } from '../../types/general'; +import { errorMessages } from '../../utils/errorMessages'; import { countUnreadNotifications, - createNotification, - findNotificationByTrackId, getNotifications, markNotificationGroupAsRead, markNotificationsAsRead, } from '../../repositories/notificationRepository'; import { UserAddress } from '../../entities/userAddress'; -import { - getNotificationTypeByEventName, - getNotificationTypeByEventNameAndMicroservice, -} from '../../repositories/notificationTypeRepository'; -import { EMAIL_STATUSES, Notification } from '../../entities/notification'; +import { Notification } from '../../entities/notification'; import { createNewUserAddressIfNotExists } from '../../repositories/userAddressRepository'; -import { SEGMENT_METADATA_SCHEMA_VALIDATOR } from '../../utils/validators/segmentAndMetadataValidators'; -import { findNotificationSettingByNotificationTypeAndUserAddress } from '../../repositories/notificationSettingRepository'; -import { SegmentAnalyticsSingleton } from '../../services/segment/segmentAnalyticsSingleton'; +import { sendNotification } from '../../services/notificationService'; +import { StandardError } from '../../types/StandardError'; @Route('/v1') @Tags('Notification') @@ -57,105 +47,16 @@ export class NotificationsController { @Security('basicAuth') public async sendNotification( @Body() - body: SendNotificationTypeRequest, + body: SendNotificationRequest, @Inject() params: { microService: string; }, ): Promise { const { microService } = params; - const { userWalletAddress, projectId } = body; try { validateWithJoiSchema(body, sendNotificationValidator); - if (body.trackId && (await findNotificationByTrackId(body.trackId))) { - // We dont throw error in this case but dont create new notification neither - return { - success: true, - message: errorMessages.DUPLICATED_TRACK_ID, - }; - } - const userAddress = await createNewUserAddressIfNotExists( - userWalletAddress as string, - ); - const notificationType = - await getNotificationTypeByEventNameAndMicroservice({ - eventName: body.eventName, - microService, - }); - - if (!notificationType) { - throw new Error(errorMessages.INVALID_NOTIFICATION_TYPE); - } - const notificationSetting = - await findNotificationSettingByNotificationTypeAndUserAddress({ - notificationTypeId: notificationType.id, - userAddressId: userAddress.id, - }); - - logger.debug('notificationController.sendNotification()', { - notificationSetting, - notificationType, - }); - - const shouldSendEmail = - body.sendEmail && notificationSetting?.allowEmailNotification; - let emailStatus = shouldSendEmail - ? EMAIL_STATUSES.WAITING_TO_BE_SEND - : EMAIL_STATUSES.NO_NEED_TO_SEND; - - const segmentValidator = - SEGMENT_METADATA_SCHEMA_VALIDATOR[ - notificationType?.schemaValidator as string - ]?.segment; - - if (shouldSendEmail && body.sendSegment && segmentValidator) { - //TODO Currently sending email and segment event are tightly coupled, we can't send segment event without sending email - // And it's not good, we should find another solution to separate sending segment and email - const segmentData = body.segment?.payload; - validateWithJoiSchema(segmentData, segmentValidator); - await SegmentAnalyticsSingleton.getInstance().track({ - eventName: notificationType.emailNotificationId as string, - anonymousId: body?.segment?.anonymousId, - properties: segmentData, - analyticsUserId: body?.segment?.analyticsUserId, - }); - emailStatus = EMAIL_STATUSES.SENT; - } - - const metadataValidator = - SEGMENT_METADATA_SCHEMA_VALIDATOR[ - notificationType?.schemaValidator as string - ]?.metadata; - - if (metadataValidator) { - validateWithJoiSchema(body.metadata, metadataValidator); - } - - if (!notificationSetting?.allowDappPushNotification) { - //TODO In future we can add a create notification but with disabledNotification:true - // So we can exclude them in list of notifications - return { - success: true, - message: errorMessages.USER_TURNED_OF_THIS_NOTIFICATION_TYPE, - }; - } - const notificationData: Partial = { - notificationType, - userAddress, - email: body.email, - emailStatus, - trackId: body?.trackId, - metadata: body?.metadata, - segmentData: body.segment, - projectId, - }; - if (body.creationTime) { - // creationTime is optional and it's timestamp in milliseconds format - notificationData.createdAt = new Date(body.creationTime); - } - await createNotification(notificationData); - - return { success: true }; + return sendNotification(body, microService); // add if and logic for push notification (not in mvp) } catch (e) { logger.error('sendNotification() error', { @@ -167,20 +68,45 @@ export class NotificationsController { } } + @Post('/thirdParty/notificationsBulk') + @Security('basicAuth') + public async sendBulkNotification( + @Body() + body: sendBulkNotificationRequest, + @Inject() + params: { + microService: string; + }, + ): Promise { + const { microService } = params; + try { + validateWithJoiSchema(body, sendBulkNotificationValidator); + await Promise.all( + body.notifications.map(item => sendNotification(item, microService)), + ); + return { success: true }; + } catch (e) { + logger.error('sendBulkNotification() error', { + error: e, + }); + throw e; + } + } + // https://tsoa-community.github.io/docs/examples.html#parameter-examples /** - * @example limit "20" - * @example offset "0" - * @example category "" - * @example category "projectRelated" - * @example category "givEconomyRelated" - * @example category "general" - * @example isRead "" - * @example isRead "false" - * @example isRead "true" - * @example startTime "1659356987" - - */ + * @example limit "20" + * @example offset "0" + * @example category "" + * @example category "projectRelated" + * @example category "givEconomyRelated" + * @example category "general" + * @example isRead "" + * @example isRead "false" + * @example isRead "true" + * @example startTime "1659356987" + + */ @Get('/notifications/') @Security('JWT') public async getNotifications( @@ -258,7 +184,10 @@ export class NotificationsController { userAddressId: user.id, }); if (!notification) { - throw new Error(errorMessages.NOTIFICATION_NOT_FOUND); + throw new StandardError({ + message: errorMessages.NOTIFICATION_NOT_FOUND, + httpStatusCode: 404, + }); } return { notification, diff --git a/src/repositories/notificationSettingRepository.ts b/src/repositories/notificationSettingRepository.ts index 98feb94..16d083b 100644 --- a/src/repositories/notificationSettingRepository.ts +++ b/src/repositories/notificationSettingRepository.ts @@ -4,6 +4,7 @@ import { NotificationSetting } from '../entities/notificationSetting'; import { errorMessages } from '../utils/errorMessages'; import { createQueryBuilder } from 'typeorm'; import { logger } from '../utils/logger'; +import { StandardError } from '../types/StandardError'; export const createNotificationSettingsForNewUser = async ( user: UserAddress, @@ -120,9 +121,12 @@ export const updateUserNotificationSetting = async (params: { ) .getOne(); - if (!notificationSetting) - throw new Error(errorMessages.NOTIFICATION_SETTING_NOT_FOUND); - + if (!notificationSetting) { + throw new StandardError({ + message: errorMessages.NOTIFICATION_SETTING_NOT_FOUND, + httpStatusCode: 400, + }); + } if (notificationSetting.notificationType?.isEmailEditable) { notificationSetting.allowEmailNotification = params.allowEmailNotification; } diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 54bb295..0ef0b6b 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -11,11 +11,14 @@ import axios from 'axios'; import { assert } from 'chai'; import { errorMessages, errorMessagesEnum } from '../../utils/errorMessages'; import { findNotificationByTrackId } from '../../repositories/notificationRepository'; +import { generateRandomString } from '../../utils/utils'; describe('/notifications POST test cases', sendNotificationTestCases); +describe('/notificationsBulk POST test cases', sendBulkNotificationsTestCases); describe('/notifications GET test cases', getNotificationTestCases); const sendNotificationUrl = `${serverUrl}/v1/thirdParty/notifications`; +const sendBulkNotificationsUrl = `${serverUrl}/v1/thirdParty/notificationsBulk`; const getNotificationUrl = `${serverUrl}/v1/notifications`; const projectTitle = 'project title'; const projectLink = 'https://giveth.io/project/project-title'; @@ -1368,14 +1371,13 @@ function sendNotificationTestCases() { assert.isTrue(false); } catch (e: any) { assert.equal( - e.response.data.message, - errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, + e.response.data.message, + errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, ); assert.equal(e.response.data.description, '"amount" is required'); } }); - it('should create *UnStake* notification, success, segment is off', async () => { const data = { eventName: 'UnStake', @@ -2296,3 +2298,184 @@ function sendNotificationTestCases() { assert.equal(createdNotification?.createdAt.getTime(), creationTime); }); } + +function sendBulkNotificationsTestCases() { + it('should create two *project liked* notifications, success, segment is off', async () => { + const data = { + notifications: [ + { + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + metadata: { + projectTitle, + projectLink, + }, + }, + { + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + metadata: { + projectTitle, + projectLink, + }, + }, + ], + }; + const result = await axios.post(sendBulkNotificationsUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.equal(result.status, 200); + assert.isOk(result.data); + assert.isTrue(result.data.success); + assert.isOk( + await findNotificationByTrackId(data.notifications[0].trackId as string), + ); + assert.isOk( + await findNotificationByTrackId(data.notifications[1].trackId as string), + ); + }); + it('should create two *project liked* notifications, success 100 notification', async () => { + const notifications = []; + for (let i = 0; i < 100; i++) { + notifications.push({ + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + metadata: { + projectTitle, + projectLink, + }, + }); + } + const data = { + notifications, + }; + const result = await axios.post(sendBulkNotificationsUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.equal(result.status, 200); + assert.isOk(result.data); + assert.isTrue(result.data.success); + for (const notification of data.notifications) { + assert.isOk( + await findNotificationByTrackId(notification.trackId as string), + ); + } + }); + it('should create two *project liked* notifications, failed for more than 100', async () => { + const notifications = []; + for (let i = 0; i < 101; i++) { + notifications.push({ + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + metadata: { + projectTitle, + projectLink, + }, + }); + } + const data = { + notifications, + }; + try { + await axios.post(sendBulkNotificationsUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, + ); + assert.equal( + e.response.data.description, + '"notifications" must contain less than or equal to 100 items', + ); + } + }); + it('should create two *project liked* notifications, failed for empty array', async () => { + const data = { + notifications: [], + }; + try { + await axios.post(sendBulkNotificationsUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, + ); + assert.equal( + e.response.data.description, + '"notifications" must contain at least 1 items', + ); + } + }); + it('should create two *project liked* notifications, failed if one notification had problem (but correct notification would be saved in DB)', async () => { + const data = { + notifications: [ + { + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + metadata: { + projectTitle, + projectLink, + }, + }, + { + eventName: 'project liked2', + trackId: `${new Date().getTime()}-${generateRandomString(30)}`, + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + metadata: { + projectTitle, + projectLink, + }, + }, + ], + }; + try { + await axios.post(sendBulkNotificationsUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessages.INVALID_NOTIFICATION_TYPE, + ); + assert.isOk( + await findNotificationByTrackId( + data.notifications[0].trackId as string, + ), + ); + } + }); +} diff --git a/src/routes/v1/notificationRouter.ts b/src/routes/v1/notificationRouter.ts index 6b2cd69..d34263e 100644 --- a/src/routes/v1/notificationRouter.ts +++ b/src/routes/v1/notificationRouter.ts @@ -27,6 +27,25 @@ notificationRouter.post( }, ); +notificationRouter.post( + '/thirdParty/notificationsBulk', + authenticateThirdPartyServiceToken, + async (req: Request, res: Response, next) => { + const { microService } = res.locals; + try { + const result = await notificationsController.sendBulkNotification( + req.body, + { + microService, + }, + ); + return sendStandardResponse({ res, result }); + } catch (e) { + next(e); + } + }, +); + notificationRouter.get( '/notifications', validateAuthMicroserviceJwt, diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts new file mode 100644 index 0000000..87e6c2b --- /dev/null +++ b/src/services/notificationService.ts @@ -0,0 +1,115 @@ +import { + createNotification, + findNotificationByTrackId, +} from '../repositories/notificationRepository'; +import { errorMessages } from '../utils/errorMessages'; +import { createNewUserAddressIfNotExists } from '../repositories/userAddressRepository'; +import { getNotificationTypeByEventNameAndMicroservice } from '../repositories/notificationTypeRepository'; +import { findNotificationSettingByNotificationTypeAndUserAddress } from '../repositories/notificationSettingRepository'; +import { logger } from '../utils/logger'; +import { EMAIL_STATUSES, Notification } from '../entities/notification'; +import { SEGMENT_METADATA_SCHEMA_VALIDATOR } from '../utils/validators/segmentAndMetadataValidators'; +import { validateWithJoiSchema } from '../validators/schemaValidators'; +import { SegmentAnalyticsSingleton } from './segment/segmentAnalyticsSingleton'; +import { SendNotificationRequest } from '../types/requestResponses'; +import { StandardError } from '../types/StandardError'; + +export const sendNotification = async ( + body: SendNotificationRequest, + microService: string, +): Promise<{ + success: boolean; + message?: string; +}> => { + const { userWalletAddress, projectId } = body; + if (body.trackId && (await findNotificationByTrackId(body.trackId))) { + // We dont throw error in this case but dont create new notification neither + return { + success: true, + message: errorMessages.DUPLICATED_TRACK_ID, + }; + } + const userAddress = await createNewUserAddressIfNotExists( + userWalletAddress as string, + ); + const notificationType = await getNotificationTypeByEventNameAndMicroservice({ + eventName: body.eventName, + microService, + }); + + if (!notificationType) { + throw new StandardError({ + message: errorMessages.INVALID_NOTIFICATION_TYPE, + httpStatusCode: 400, + }); + } + const notificationSetting = + await findNotificationSettingByNotificationTypeAndUserAddress({ + notificationTypeId: notificationType.id, + userAddressId: userAddress.id, + }); + + logger.debug('notificationController.sendNotification()', { + notificationSetting, + notificationType, + }); + + const shouldSendEmail = + body.sendEmail && notificationSetting?.allowEmailNotification; + let emailStatus = shouldSendEmail + ? EMAIL_STATUSES.WAITING_TO_BE_SEND + : EMAIL_STATUSES.NO_NEED_TO_SEND; + + const segmentValidator = + SEGMENT_METADATA_SCHEMA_VALIDATOR[ + notificationType?.schemaValidator as string + ]?.segment; + + if (shouldSendEmail && body.sendSegment && segmentValidator) { + //TODO Currently sending email and segment event are tightly coupled, we can't send segment event without sending email + // And it's not good, we should find another solution to separate sending segment and email + const segmentData = body.segment?.payload; + validateWithJoiSchema(segmentData, segmentValidator); + await SegmentAnalyticsSingleton.getInstance().track({ + eventName: notificationType.emailNotificationId as string, + anonymousId: body?.segment?.anonymousId, + properties: segmentData, + analyticsUserId: body?.segment?.analyticsUserId, + }); + emailStatus = EMAIL_STATUSES.SENT; + } + + const metadataValidator = + SEGMENT_METADATA_SCHEMA_VALIDATOR[ + notificationType?.schemaValidator as string + ]?.metadata; + + if (metadataValidator) { + validateWithJoiSchema(body.metadata, metadataValidator); + } + + if (!notificationSetting?.allowDappPushNotification) { + //TODO In future we can add a create notification but with disabledNotification:true + // So we can exclude them in list of notifications + return { + success: true, + message: errorMessages.USER_TURNED_OF_THIS_NOTIFICATION_TYPE, + }; + } + const notificationData: Partial = { + notificationType, + userAddress, + email: body.email, + emailStatus, + trackId: body?.trackId, + metadata: body?.metadata, + segmentData: body.segment, + projectId, + }; + if (body.creationTime) { + // creationTime is optional and it's timestamp in milliseconds format + notificationData.createdAt = new Date(body.creationTime); + } + await createNotification(notificationData); + return { success: true }; +}; diff --git a/src/types/requestResponses.ts b/src/types/requestResponses.ts index bd9a879..33ac26e 100644 --- a/src/types/requestResponses.ts +++ b/src/types/requestResponses.ts @@ -4,15 +4,12 @@ interface BaseNotificationResponse { trackId?: string; } -export type SendNotificationTypeRequest = { +interface BaseSendNotificationTypeRequest { /** * @example "Made donation" */ eventName: string; - /** - * @example "Should be unique" - */ - trackId?: string; + /** * @example false */ @@ -57,51 +54,33 @@ export type SendNotificationTypeRequest = { anonymousId?: string; }; metadata?: NotificationMetadata; -}; - -export type SendNotificationRequest = { - /** - * @example "1" - */ - userId: string; - - /** - * @example "1" - */ - projectId: string; - - metadata: { - /** - * @example "https://giveth.io/project/test-project - */ - projectLink?: string; - - /** - * @example "Test project" - */ - projectTitle?: string; - }; +} +export interface SendNotificationRequest + extends BaseSendNotificationTypeRequest { /** - * @example "y@giveth.io" + * @example "Should be unique" */ - email: string; + trackId?: string; +} +interface SendNotificationTypeRequestBulkItem + extends BaseSendNotificationTypeRequest { /** - * @example "projectListed" + * @example "Should be unique" */ - notificationTemplate: number; + trackId: string; +} - /** - * @example true - */ - sendEmail: boolean; -}; +export interface sendBulkNotificationRequest { + notifications: SendNotificationTypeRequestBulkItem[]; +} export interface SendNotificationResponse extends BaseNotificationResponse { success: boolean; message?: string; } + export interface GetNotificationsResponse extends BaseNotificationResponse { notifications: Notification[]; count: number; diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index fa35f07..ce7424b 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -92,7 +92,7 @@ export const errorMessages = { JUST_ACTIVE_PROJECTS_ACCEPT_DONATION: 'Just active projects accept donation', CATEGORIES_LENGTH_SHOULD_NOT_BE_MORE_THAN_FIVE: 'Please select no more than 5 categories', - INVALID_NOTIFICATION_TYPE: 'Notification type invalid', + INVALID_NOTIFICATION_TYPE: 'Notification type is invalid', DUPLICATED_TRACK_ID: 'Duplicated trackId', CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION: 'This category is not valid', diff --git a/src/validators/schemaValidators.ts b/src/validators/schemaValidators.ts index 2f58f69..55fa091 100644 --- a/src/validators/schemaValidators.ts +++ b/src/validators/schemaValidators.ts @@ -24,7 +24,9 @@ const throwHttpErrorIfJoiValidatorFails = ( export const countUnreadValidator = Joi.object({ walletAddress: Joi.string().pattern(ethereumWalletAddressRegex).required(), }); + export const sendNotificationValidator = Joi.object({ + trackId: Joi.string(), projectId: Joi.string(), analyticsUserId: Joi.string(), anonymousId: Joi.string(), @@ -33,7 +35,6 @@ export const sendNotificationValidator = Joi.object({ sendEmail: Joi.boolean(), sendSegment: Joi.boolean(), email: Joi.string(), - trackId: Joi.string(), creationTime: Joi.number(), userWalletAddress: Joi.string() .pattern(ethereumWalletAddressRegex) @@ -75,6 +76,20 @@ export const sendNotificationValidator = Joi.object({ }), }); +export const sendBulkNotificationValidator = Joi.object({ + notifications: Joi.array() + .required() + .min(1) + .max(100) + .items( + sendNotificationValidator.concat( + Joi.object({ + trackId: Joi.string().required(), + }), + ), + ), +}); + export const updateNotificationSettings = Joi.object({ settings: Joi.array() .required() @@ -86,6 +101,7 @@ export const updateNotificationSettings = Joi.object({ }), ), }); + export const updateOneNotificationSetting = Joi.object({ id: Joi.number().required(), allowEmailNotification: Joi.boolean().required(), From 0d1cb936e28366f73dd519ed615519ec6e6c4b5e Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 14:12:00 +0300 Subject: [PATCH 13/23] Made test compatible with new givback is paid event --- src/routes/v1/notificationRouter.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 2b273f1..300654b 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1431,12 +1431,15 @@ function sendNotificationTestCases() { it('should create *GIVbacks are ready to claim* notification, success, segment is off', async () => { const data = { - eventName: 'GIVbacks are ready to claim', + eventName: 'GIVback is ready to claim', sendEmail: false, sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), metadata: { - round: 5, + contractName: 'Gnosis Chain Token Distro', + amount: '10', + network: 100, + transactionHash: generateRandomTxHash(), }, }; @@ -1452,7 +1455,7 @@ function sendNotificationTestCases() { it('should create *GIVbacks are ready to claim* notification, failed invalid metadata, segment is off', async () => { try { const data = { - eventName: 'GIVbacks are ready to claim', + eventName: 'GIVback is ready to claim', sendEmail: false, sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), From 833518d9b8ea93e8f92caf54a30589dc50a560a1 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 14:16:16 +0300 Subject: [PATCH 14/23] Updated tests to be compatible with givback is paid / 2 --- src/routes/v1/notificationRouter.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 300654b..70411db 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1459,7 +1459,11 @@ function sendNotificationTestCases() { sendEmail: false, sendSegment: false, userWalletAddress: generateRandomEthereumAddress(), - metadata: {}, + metadata: { + contractName: 'Gnosis Chain Token Distro', + network: 100, + transactionHash: generateRandomTxHash(), + }, }; await axios.post(sendNotificationUrl, data, { @@ -1474,7 +1478,7 @@ function sendNotificationTestCases() { e.response.data.message, errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, ); - assert.equal(e.response.data.description, '"round" is required'); + assert.equal(e.response.data.description, '"amount" is required'); } }); From 4aa5451bf04e3769c7f46c100a77e16a260f137e Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 15:51:55 +0300 Subject: [PATCH 15/23] Updated givback is ready event template / 3 --- migrations/1660716115917-seedNotificationType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 8d1d29c..6522d75 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -948,7 +948,7 @@ export const GivethNotificationTypes = { title: 'GIVback', description: 'When GIVback is ready to be claimed after each round', showOnSettingPage: true, - microService: MICRO_SERVICES.givethio, + microService: MICRO_SERVICES.givEconomyNotificationMicroService, category: NOTIFICATION_CATEGORY.GIV_ECONOMY, icon: 'IconGIVBack', schemaValidator: SCHEMA_VALIDATORS_NAMES.GIV_BACK_IS_READY_TO_CLAIM, @@ -980,7 +980,7 @@ export const GivethNotificationTypes = { }, ], content: - 'Your GIVback {amount} GIV is ready to claim! \n{Click here} to take a shortcut.', + 'Your GIVback {amount} GIV is ready to claim! \n[Click here] to take a shortcut.', }, PROJECT_EDITED: { name: 'Project edited', From 94e0e7a79ea5fa81051f8435c2ba0d45450abdc0 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 27 Dec 2022 15:56:27 +0300 Subject: [PATCH 16/23] Updated giback claim is ready tests to match new microservice type --- src/routes/v1/notificationRouter.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 70411db..6b09e42 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -1445,7 +1445,7 @@ function sendNotificationTestCases() { const result = await axios.post(sendNotificationUrl, data, { headers: { - authorization: getGivethIoBasicAuth(), + authorization: getGivEconomyBasicAuth(), }, }); assert.equal(result.status, 200); @@ -1468,7 +1468,7 @@ function sendNotificationTestCases() { await axios.post(sendNotificationUrl, data, { headers: { - authorization: getGivethIoBasicAuth(), + authorization: getGivEconomyBasicAuth(), }, }); // If request doesn't fail, it means this test failed From d7a58cf000d1e34e6debd64e79e39655559e2598 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 27 Dec 2022 16:42:34 +0330 Subject: [PATCH 17/23] Fix sendBulkNotification test cases --- src/routes/v1/notificationRouter.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 0ef0b6b..b253c27 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -2432,7 +2432,7 @@ function sendBulkNotificationsTestCases() { ); } }); - it('should create two *project liked* notifications, failed if one notification had problem (but correct notification would be saved in DB)', async () => { + it('should create two *project liked* notifications, failed if one notification had problem', async () => { const data = { notifications: [ { @@ -2471,11 +2471,6 @@ function sendBulkNotificationsTestCases() { e.response.data.message, errorMessages.INVALID_NOTIFICATION_TYPE, ); - assert.isOk( - await findNotificationByTrackId( - data.notifications[0].trackId as string, - ), - ); } }); } From 398f385b54165a3b7259a4a625a77696b2c328c9 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Wed, 28 Dec 2022 14:47:02 +0330 Subject: [PATCH 18/23] Add validation for uniqueness of trackIds in input data for sending bulk notifications related to #24 --- src/controllers/v1/notificationsController.ts | 10 +++++ src/routes/v1/notificationRouter.test.ts | 44 +++++++++++++++++++ src/types/requestResponses.ts | 18 +++----- src/utils/errorMessages.ts | 1 + 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/controllers/v1/notificationsController.ts b/src/controllers/v1/notificationsController.ts index 59e2ff0..4a868ce 100644 --- a/src/controllers/v1/notificationsController.ts +++ b/src/controllers/v1/notificationsController.ts @@ -39,6 +39,7 @@ import { Notification } from '../../entities/notification'; import { createNewUserAddressIfNotExists } from '../../repositories/userAddressRepository'; import { sendNotification } from '../../services/notificationService'; import { StandardError } from '../../types/StandardError'; +import { not } from 'joi'; @Route('/v1') @Tags('Notification') @@ -81,6 +82,15 @@ export class NotificationsController { const { microService } = params; try { validateWithJoiSchema(body, sendBulkNotificationValidator); + const trackIdsSet = new Set( + body.notifications.map(notification => notification.trackId), + ); + if (trackIdsSet.size !== body.notifications.length) { + throw new StandardError({ + message: errorMessages.THERE_IS_SOME_ITEMS_WITH_SAME_TRACK_ID, + httpStatusCode: 400, + }); + } await Promise.all( body.notifications.map(item => sendNotification(item, microService)), ); diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index b253c27..ce02b10 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -2410,6 +2410,50 @@ function sendBulkNotificationsTestCases() { ); } }); + it('should create two *project liked* notifications, failed because two notifications have same trackIds', async () => { + const trackId = `${new Date().getTime()}-${generateRandomString(30)}`; + const notifications = [ + { + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId, + metadata: { + projectTitle, + projectLink, + }, + }, + { + eventName: 'project liked', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + trackId, + metadata: { + projectTitle, + projectLink, + }, + }, + ]; + try { + await axios.post( + sendBulkNotificationsUrl, + { notifications }, + { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }, + ); + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessages.THERE_IS_SOME_ITEMS_WITH_SAME_TRACK_ID, + ); + } + }); it('should create two *project liked* notifications, failed for empty array', async () => { const data = { notifications: [], diff --git a/src/types/requestResponses.ts b/src/types/requestResponses.ts index 33ac26e..c63a123 100644 --- a/src/types/requestResponses.ts +++ b/src/types/requestResponses.ts @@ -4,7 +4,12 @@ interface BaseNotificationResponse { trackId?: string; } -interface BaseSendNotificationTypeRequest { +export interface SendNotificationRequest { + /** + * @example "Should be unique" + */ + trackId?: string; + /** * @example "Made donation" */ @@ -56,16 +61,7 @@ interface BaseSendNotificationTypeRequest { metadata?: NotificationMetadata; } -export interface SendNotificationRequest - extends BaseSendNotificationTypeRequest { - /** - * @example "Should be unique" - */ - trackId?: string; -} - -interface SendNotificationTypeRequestBulkItem - extends BaseSendNotificationTypeRequest { +interface SendNotificationTypeRequestBulkItem extends SendNotificationRequest { /** * @example "Should be unique" */ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index ce7424b..b1ad8a7 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -93,6 +93,7 @@ export const errorMessages = { CATEGORIES_LENGTH_SHOULD_NOT_BE_MORE_THAN_FIVE: 'Please select no more than 5 categories', INVALID_NOTIFICATION_TYPE: 'Notification type is invalid', + THERE_IS_SOME_ITEMS_WITH_SAME_TRACK_ID: 'There is some ids with same trackId', DUPLICATED_TRACK_ID: 'Duplicated trackId', CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION: 'This category is not valid', From fbd389e129574f16f23da7744a57b9888d458c9b Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 28 Dec 2022 17:05:22 +0300 Subject: [PATCH 19/23] Removed unused imports --- migrations/1660539566228-createUserAddress.ts | 1 - migrations/1660540253547-createNotificationType.ts | 1 - migrations/1660716115917-seedNotificationType.ts | 2 +- src/controllers/v1/notificationSettingsController.ts | 1 - src/controllers/v1/notificationsController.ts | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/migrations/1660539566228-createUserAddress.ts b/migrations/1660539566228-createUserAddress.ts index 464d949..c790df0 100644 --- a/migrations/1660539566228-createUserAddress.ts +++ b/migrations/1660539566228-createUserAddress.ts @@ -2,7 +2,6 @@ import { MigrationInterface, QueryRunner, Table, - TableForeignKey, } from 'typeorm'; export class createUserAddress1660539566228 implements MigrationInterface { diff --git a/migrations/1660540253547-createNotificationType.ts b/migrations/1660540253547-createNotificationType.ts index 54a2ff0..42f1134 100644 --- a/migrations/1660540253547-createNotificationType.ts +++ b/migrations/1660540253547-createNotificationType.ts @@ -2,7 +2,6 @@ import { MigrationInterface, QueryRunner, Table, - TableForeignKey, TableIndex, } from 'typeorm'; diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 4237866..281edb9 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1,4 +1,4 @@ -import { Column, MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from 'typeorm'; import { NotificationType, SCHEMA_VALIDATORS_NAMES, diff --git a/src/controllers/v1/notificationSettingsController.ts b/src/controllers/v1/notificationSettingsController.ts index 656937c..7d7ab3a 100644 --- a/src/controllers/v1/notificationSettingsController.ts +++ b/src/controllers/v1/notificationSettingsController.ts @@ -8,7 +8,6 @@ import { import { UserAddress } from '../../entities/userAddress'; import { errorMessages } from '../../utils/errorMessages'; import { - sendNotificationValidator, updateNotificationSettings, updateOneNotificationSetting, validateWithJoiSchema, diff --git a/src/controllers/v1/notificationsController.ts b/src/controllers/v1/notificationsController.ts index 4a868ce..cd4936a 100644 --- a/src/controllers/v1/notificationsController.ts +++ b/src/controllers/v1/notificationsController.ts @@ -39,7 +39,6 @@ import { Notification } from '../../entities/notification'; import { createNewUserAddressIfNotExists } from '../../repositories/userAddressRepository'; import { sendNotification } from '../../services/notificationService'; import { StandardError } from '../../types/StandardError'; -import { not } from 'joi'; @Route('/v1') @Tags('Notification') From f78076a9d617697f77d9e9e2181da5c4268a695c Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 2 Jan 2023 15:37:40 +0330 Subject: [PATCH 20/23] Change metadata validator for notifications related to https://github.com/Giveth/impact-graph/issues/780 --- migrations/1660539566228-createUserAddress.ts | 6 +----- migrations/1660540253547-createNotificationType.ts | 7 +------ src/utils/validators/segmentAndMetadataValidators.ts | 6 +++--- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/migrations/1660539566228-createUserAddress.ts b/migrations/1660539566228-createUserAddress.ts index c790df0..1c11c49 100644 --- a/migrations/1660539566228-createUserAddress.ts +++ b/migrations/1660539566228-createUserAddress.ts @@ -1,8 +1,4 @@ -import { - MigrationInterface, - QueryRunner, - Table, -} from 'typeorm'; +import { MigrationInterface, QueryRunner, Table } from 'typeorm'; export class createUserAddress1660539566228 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { diff --git a/migrations/1660540253547-createNotificationType.ts b/migrations/1660540253547-createNotificationType.ts index 42f1134..b4d1fb6 100644 --- a/migrations/1660540253547-createNotificationType.ts +++ b/migrations/1660540253547-createNotificationType.ts @@ -1,9 +1,4 @@ -import { - MigrationInterface, - QueryRunner, - Table, - TableIndex, -} from 'typeorm'; +import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'; export class createNotificationType1660540253547 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index 25f7630..c3a34ac 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -112,8 +112,8 @@ const claimSchema = Joi.object({ const adminMessageSchema = Joi.object({ linkTitle: Joi.string().required(), content: Joi.string().required(), - instruction: Joi.string().required(), - href: Joi.string().required(), + instruction: Joi.string(), + href: Joi.string(), }); const givBackReadyClaimSchema = Joi.object({ contractName: Joi.string().required(), @@ -268,7 +268,7 @@ export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { givBackReadyToClaim: { metadata: givBackReadyClaimSchema, segment: null, - } + }, }; function throwHttpErrorIfJoiValidatorFails( From a90a054cc429143b9b8a7ef44bef41a30185b5eb Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Sun, 8 Jan 2023 10:17:07 +0330 Subject: [PATCH 21/23] Add Raw HTML Broadcast notificationType related to https://github.com/Giveth/impact-graph/issues/780 --- .../1660716115917-seedNotificationType.ts | 24 +++++++++- src/entities/notificationType.ts | 1 + src/routes/v1/notificationRouter.test.ts | 46 +++++++++++++++++++ .../segmentAndMetadataValidators.ts | 5 ++ 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index 90c8c0f..0275314 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -1720,7 +1720,7 @@ export const GivethNotificationTypes = { title: 'Your boost status', description: 'Shows when you boost a project, change the allocation of GIVpower.', - showOnSettingPage: true, + showOnSettingPage: false, microService: MICRO_SERVICES.givethio, schemaValidator: null, emailNotifierService: null, @@ -1883,6 +1883,28 @@ export const GivethNotificationTypes = { icon: '', categoryGroup: NOTIFICATION_CATEGORY_GROUPS.LIKED_BY_YOU_PROJECT_GROUP, }, + + RAW_HTML: { + name: 'Raw HTML Broadcast', + description: 'Raw HTML Broadcast', + microService: MICRO_SERVICES.givethio, + category: 'general', + icon: 'IconAdminNotif', + schemaValidator: SCHEMA_VALIDATORS_NAMES.RAW_HTML_BROADCAST, + emailNotifierService: null, + emailNotificationId: null, + pushNotifierService: null, + title: 'Raw HTML Broadcast', + htmlTemplate: [ + { + type: 'html', + content: '$html', + }, + ], + Content: { + html: '', + }, + }, }; export class seedNotificationType1660716115917 implements MigrationInterface { diff --git a/src/entities/notificationType.ts b/src/entities/notificationType.ts index 7116d69..62a24e2 100644 --- a/src/entities/notificationType.ts +++ b/src/entities/notificationType.ts @@ -17,6 +17,7 @@ import { NotificationSetting } from './notificationSetting'; // Export Object with Schemas to N1 lookup export const SCHEMA_VALIDATORS_NAMES = { ADMIN_MESSAGE: 'adminMessage', + RAW_HTML_BROADCAST: 'rawHtmlBroadcast', DRAFTED_PROJECT_SAVED: 'draftedProjectSavedValidator', DRAFTED_PROJECT_ACTIVATED: 'draftedProjectPublishedValidator', PROJECT_LISTED: 'projectListed', diff --git a/src/routes/v1/notificationRouter.test.ts b/src/routes/v1/notificationRouter.test.ts index 188eac0..fb8bfe5 100644 --- a/src/routes/v1/notificationRouter.test.ts +++ b/src/routes/v1/notificationRouter.test.ts @@ -165,6 +165,52 @@ function sendNotificationTestCases() { } }); + it('should create *Raw HTML Broadcast* notification, success, segment is off', async () => { + const data = { + eventName: 'Raw HTML Broadcast', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + metadata: { + html: '

dasl;dksa;lkd;laskd;askd;alsdas hi

', + }, + }; + + const result = await axios.post(sendNotificationUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + assert.equal(result.status, 200); + assert.isOk(result.data); + assert.isTrue(result.data.success); + }); + it('should create *Raw HTML Broadcast* notification, failed invalid metadata, segment is off', async () => { + try { + const data = { + eventName: 'Raw HTML Broadcast', + sendEmail: false, + sendSegment: false, + userWalletAddress: generateRandomEthereumAddress(), + metadata: {}, + }; + + await axios.post(sendNotificationUrl, data, { + headers: { + authorization: getGivethIoBasicAuth(), + }, + }); + // If request doesn't fail, it means this test failed + assert.isTrue(false); + } catch (e: any) { + assert.equal( + e.response.data.message, + errorMessagesEnum.IMPACT_GRAPH_VALIDATION_ERROR.message, + ); + assert.equal(e.response.data.description, '"html" is required'); + } + }); + it('should create *draft project saved* notification, success, segment is off', async () => { const data = { eventName: 'The project saved as draft', diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index c3a34ac..07b9d3b 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -115,6 +115,10 @@ const adminMessageSchema = Joi.object({ instruction: Joi.string(), href: Joi.string(), }); + +const rawHtmlBroadcastSchema = Joi.object({ + html: Joi.string().required(), +}); const givBackReadyClaimSchema = Joi.object({ contractName: Joi.string().required(), transactionHash: Joi.string().required(), @@ -250,6 +254,7 @@ export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { givFarmUnStake: { metadata: stakeUnStakeSchema, segment: null }, givFarmReadyToClaim: { metadata: claimSchema, segment: null }, adminMessage: { metadata: adminMessageSchema, segment: null }, + rawHtmlBroadcast: { metadata: rawHtmlBroadcastSchema, segment: null }, givPowerUserBoosted: { metadata: boostedSchema, segment: null }, givPowerUserChangedBoostedAllocation: { metadata: null, segment: null }, givPowerProjectHasBeenBoosted: { From b5ce8aca45c1b12addb7baefa289bc39d5965c43 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Sun, 8 Jan 2023 17:18:22 +0330 Subject: [PATCH 22/23] Sort notificationSettings by notificationTypeId related to https://github.com/Giveth/giveth-dapps-v2/issues/477#issuecomment-1374580571 --- ...1660541108096-createNotificationSetting.ts | 2 +- .../1660716115917-seedNotificationType.ts | 5 ++-- src/entities/notificationSetting.ts | 6 ++--- .../notificationSettingRepository.test.ts | 24 +++++++++++++++++++ .../notificationSettingRepository.ts | 2 +- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/migrations/1660541108096-createNotificationSetting.ts b/migrations/1660541108096-createNotificationSetting.ts index 0825dd5..5c0a909 100644 --- a/migrations/1660541108096-createNotificationSetting.ts +++ b/migrations/1660541108096-createNotificationSetting.ts @@ -42,7 +42,7 @@ export class createNotificationSetting1660541108096 { name: 'notificationTypeId', type: 'int', - isNullable: true, + isNullable: false, }, { name: 'userAddressId', diff --git a/migrations/1660716115917-seedNotificationType.ts b/migrations/1660716115917-seedNotificationType.ts index a07ad54..fd80bf3 100644 --- a/migrations/1660716115917-seedNotificationType.ts +++ b/migrations/1660716115917-seedNotificationType.ts @@ -339,10 +339,9 @@ export const GivethNotificationTypes = { type: 'a', content: '$projectTitle', href: '$projectLink', - } + }, ], - content: - 'Someone boosted your project {projectName}!', + content: 'Someone boosted your project {projectName}!', }, PROJECT_CANCELLED: { name: 'Project cancelled', diff --git a/src/entities/notificationSetting.ts b/src/entities/notificationSetting.ts index b57ddbd..db92441 100644 --- a/src/entities/notificationSetting.ts +++ b/src/entities/notificationSetting.ts @@ -41,13 +41,13 @@ export class NotificationSetting extends BaseEntity { allowDappPushNotification: boolean; @Index() - @ManyToOne(type => NotificationType, { eager: true, nullable: true }) - notificationType?: NotificationType; + @ManyToOne(type => NotificationType, { eager: true }) + notificationType: NotificationType; @RelationId( (notificationSetting: NotificationSetting) => notificationSetting.notificationType, ) - notificationTypeId?: number | null; + notificationTypeId: number; @Index() @ManyToOne(type => UserAddress, { eager: true, nullable: false }) diff --git a/src/repositories/notificationSettingRepository.test.ts b/src/repositories/notificationSettingRepository.test.ts index 3647fcd..97aa7eb 100644 --- a/src/repositories/notificationSettingRepository.test.ts +++ b/src/repositories/notificationSettingRepository.test.ts @@ -45,6 +45,30 @@ function getUserNotificationSettingsTestCases() { assert.isTrue(setting?.notificationType?.showOnSettingPage); }); }); + it('return notification settings order by notificationTypeId', async () => { + const userAddress = await createNewUserAddressIfNotExists( + generateRandomEthereumAddress(), + ); + + const [notificationSettings] = await getUserNotificationSettings( + 100, + 0, + userAddress.id, + ); + + assert.isOk(notificationSettings); + notificationSettings.forEach(setting => { + assert.isTrue(setting!.userAddressId === userAddress.id); + assert.isTrue(setting?.notificationType?.showOnSettingPage); + }); + + for (let i = 1; i < notificationSettings.length; i++) { + assert.isTrue( + notificationSettings[i]?.notificationTypeId > + notificationSettings[i - 1]?.notificationTypeId, + ); + } + }); } function findNotificationSettingByNotificationTypeAndUserAddressTestCases() { diff --git a/src/repositories/notificationSettingRepository.ts b/src/repositories/notificationSettingRepository.ts index 16d083b..1277c23 100644 --- a/src/repositories/notificationSettingRepository.ts +++ b/src/repositories/notificationSettingRepository.ts @@ -46,7 +46,7 @@ export const getUserNotificationSettings = async ( category: category, }); } - + query.orderBy('notificationTypeId', 'ASC'); return query.take(take).skip(skip).getManyAndCount(); }; From ed474b6d2aff58e07572706adb98fb21511cd429 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Sun, 8 Jan 2023 17:25:50 +0330 Subject: [PATCH 23/23] Fix getUserNotificationSettings sort query --- src/repositories/notificationSettingRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repositories/notificationSettingRepository.ts b/src/repositories/notificationSettingRepository.ts index 1277c23..7835128 100644 --- a/src/repositories/notificationSettingRepository.ts +++ b/src/repositories/notificationSettingRepository.ts @@ -46,7 +46,7 @@ export const getUserNotificationSettings = async ( category: category, }); } - query.orderBy('notificationTypeId', 'ASC'); + query.orderBy('notificationType.id', 'ASC'); return query.take(take).skip(skip).getManyAndCount(); };