diff --git a/migrations/1711607882826-addSuperfluidNotifications.ts b/migrations/1711607882826-addSuperfluidNotifications.ts new file mode 100644 index 0000000..327f341 --- /dev/null +++ b/migrations/1711607882826-addSuperfluidNotifications.ts @@ -0,0 +1,149 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { NOTIFICATION_CATEGORY_GROUPS } from '../src/entities/notificationSetting'; +import { NOTIFICATION_CATEGORY } from '../src/types/general'; +import { MICRO_SERVICES } from '../src/utils/utils'; +import { + NotificationType, + SCHEMA_VALIDATORS_NAMES, +} from '../src/entities/notificationType'; + +export const superFluidNotificationTypes = [ + { + name: 'One month left in stream balance', + description: 'Stream balance of underlying token will run out in 1 month', + microService: MICRO_SERVICES.givethio, + category: NOTIFICATION_CATEGORY.GENERAL, + icon: '', + schemaValidator: SCHEMA_VALIDATORS_NAMES.SUPERFLUID, + emailNotifierService: null, + emailNotificationId: null, + pushNotifierService: null, + categoryGroup: NOTIFICATION_CATEGORY_GROUPS.SUPERFLUID, + title: 'One Month Left in Stream Balance', + htmlTemplate: [ + { + type: 'p', + content: 'Your Stream Balance of ', + }, + { + type: 'p', + content: '$tokenSymbol', + }, + { + type: 'p', + content: ' on ', + }, + { + type: 'p', + content: '$networkName', + }, + { + type: 'p', + content: ' will run out in 1 month, ', + }, + { + type: 'a', + content: 'top-up here.', + href: '$recurringDonationTab', // Actual link goes here + }, + ], + }, + { + name: 'One week left in stream balance', + description: 'Stream balance of underlying token will run out in 1 week', + microService: MICRO_SERVICES.givethio, + category: NOTIFICATION_CATEGORY.GENERAL, + icon: '', + schemaValidator: SCHEMA_VALIDATORS_NAMES.SUPERFLUID, + emailNotifierService: null, + emailNotificationId: null, + pushNotifierService: null, + categoryGroup: NOTIFICATION_CATEGORY_GROUPS.SUPERFLUID, + title: 'One Week Left in Stream Balance', + htmlTemplate: [ + { + type: 'p', + content: 'Your Stream Balance of ', + }, + { + type: 'p', + content: '$tokenSymbol', + }, + { + type: 'p', + content: ' on ', + }, + { + type: 'p', + content: '$networkName', + }, + { + type: 'p', + content: ' will run out in 1 week, ', + }, + { + type: 'a', + content: 'top-up here.', + href: '$recurringDonationTab', // Actual link goes here + }, + ], + }, + { + name: 'Stream balance depleted', + description: 'Stream balance in token has run out of funds', + microService: MICRO_SERVICES.givethio, + category: NOTIFICATION_CATEGORY.GENERAL, + icon: '', + schemaValidator: SCHEMA_VALIDATORS_NAMES.SUPERFLUID, + emailNotifierService: null, + emailNotificationId: null, + pushNotifierService: null, + categoryGroup: NOTIFICATION_CATEGORY_GROUPS.SUPERFLUID, + title: 'Stream Balance Depleted', + htmlTemplate: [ + { + type: 'p', + content: 'Your Stream Balance in ', + }, + { + type: 'p', + content: '$tokenSymbol', + }, + { + type: 'p', + content: ' on ', + }, + { + type: 'p', + content: '$networkName', + }, + { + type: 'p', + content: + ' has run out of funds, subsequently some of your recurring donations have ended. ', + }, + { + type: 'a', + content: 'Manage your Recurring Donations', + href: '$recurringDonationTab', // Actual link goes here + }, + ], + }, +]; + +export class addSuperfluidNotifications1711607882826 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.manager.save( + NotificationType, + superFluidNotificationTypes, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM notification_type WHERE "categoryGroup" = 'superfluid';`, + ); + } +} diff --git a/migrations/1712683625687-addSuperFluidNotificationForAllUsers.ts b/migrations/1712683625687-addSuperFluidNotificationForAllUsers.ts new file mode 100644 index 0000000..7c24869 --- /dev/null +++ b/migrations/1712683625687-addSuperFluidNotificationForAllUsers.ts @@ -0,0 +1,45 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class addSuperFluidNotificationForAllUsers1712683625687 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Fetch the notificationTypeIds for the "superfluid" categoryGroup + const notificationTypeIds = await queryRunner.query(` + SELECT "id" FROM "notification_type" WHERE "categoryGroup" = 'superfluid'; + `); + + // Fetch all unique userAddressIds + const userAddressIds = await queryRunner.query(` + SELECT DISTINCT "userAddressId" FROM "notification_setting"; + `); + + // For each userAddressId, insert a new row for each notificationTypeId + for (const { userAddressId } of userAddressIds) { + for (const { id: notificationTypeId } of notificationTypeIds) { + await queryRunner.query(` + INSERT INTO "notification_setting" ( + "allowNotifications", + "allowEmailNotification", + "allowDappPushNotification", + "notificationTypeId", + "userAddressId" + ) VALUES (true, true, true, ${notificationTypeId}, ${userAddressId}); + `); + } + } + } + + public async down(queryRunner: QueryRunner): Promise { + // Fetch the notificationTypeIds for the "superfluid" categoryGroup + const notificationTypeIds = await queryRunner.query(` + SELECT "id" FROM "notification_type" WHERE "categoryGroup" = 'superfluid'; + `); + + // Convert fetched rows to a list of IDs for the IN clause + const ids = notificationTypeIds.map((nt: { id: any; }) => nt.id).join(', '); + + // Delete the rows with the fetched notificationTypeIds for all userAddressIds + await queryRunner.query(` + DELETE FROM "notification_setting" WHERE "notificationTypeId" IN (${ids}); + `); + } +} diff --git a/src/entities/notificationSetting.ts b/src/entities/notificationSetting.ts index dacea8e..fbc62d4 100644 --- a/src/entities/notificationSetting.ts +++ b/src/entities/notificationSetting.ts @@ -13,6 +13,7 @@ import { NotificationType } from './notificationType'; import { UserAddress } from './userAddress'; export const NOTIFICATION_CATEGORY_GROUPS = { + SUPERFLUID: 'superfluid', GIVPOWER_ALLOCATIONS: 'givPowerAllocations', PROJECT_BOOSTING_STATUS: 'projectBoostStatus', SELF_BOOSTING_STATUS: 'yourBoostStatus', diff --git a/src/entities/notificationType.ts b/src/entities/notificationType.ts index d5c6b8d..a4077b9 100644 --- a/src/entities/notificationType.ts +++ b/src/entities/notificationType.ts @@ -16,6 +16,7 @@ import { NotificationSetting } from './notificationSetting'; // Export Object with Schemas to N1 lookup export const SCHEMA_VALIDATORS_NAMES = { + SUPERFLUID: 'userSuperTokensCritical', ADMIN_MESSAGE: 'adminMessage', RAW_HTML_BROADCAST: 'rawHtmlBroadcast', DRAFTED_PROJECT_SAVED: 'draftedProjectSavedValidator', diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts index d1e84b4..1c2cded 100644 --- a/src/services/notificationService.ts +++ b/src/services/notificationService.ts @@ -24,8 +24,35 @@ const activityCreator = (payload: any, orttoEventName: NOTIFICATIONS_EVENT_NAMES } let attributes; switch (orttoEventName) { + case NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_DEPLETED: + attributes = { + "str:cm:tokensymbol": payload.tokenSymbol, + "str:cm:email": payload.email, + "str:cm:userId": payload.userId?.toString(), + "bol:cm:isended": payload.isEnded, + } + break; + case NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_WEEK: + attributes = { + "str:cm:tokensymbol": payload.tokenSymbol, + "str:cm:email": payload.email, + "str:cm:userId": payload.userId?.toString(), + "str:cm:criticalDate": 'week', + "bol:cm:isended": payload.isEnded, + } + break; + case NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_MONTH: + attributes = { + "str:cm:tokensymbol": payload.tokenSymbol, + "str:cm:email": payload.email, + "str:cm:userId": payload.userId?.toString(), + "str:cm:criticalDate": 'month', + "bol:cm:isended": payload.isEnded, + } + break; case NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED: attributes = { + "bol:cm:isrecurringdonation": !!payload.isRecurringDonation, "str:cm:projecttitle": payload.title, "str:cm:donationamount": payload.amount.toString(), "str:cm:donationtoken": payload.token, @@ -104,6 +131,22 @@ const activityCreator = (payload: any, orttoEventName: NOTIFICATIONS_EVENT_NAMES "str:cm:verified-status": 'revoked', } break + case NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING: + attributes = { + "str:cm:projecttitle": payload.title, + "str:cm:email": payload.email, + "str:cm:projectupdatelink": payload.projectLink + '?tab=updates', + "str:cm:user-id": payload.userId?.toString(), + } + break + case NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING: + attributes = { + "str:cm:projecttitle": payload.title, + "str:cm:email": payload.email, + "str:cm:projectupdatelink": payload.projectLink + '?tab=updates', + "str:cm:user-id": payload.userId?.toString(), + } + break default: logger.debug('activityCreator() invalid event name', orttoEventName) return; @@ -158,6 +201,17 @@ export const sendNotification = async ( userAddressId: userAddress.id, }); + 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; + logger.debug('notificationController.sendNotification()', { notificationSetting, notificationTypeId: notificationType.id, @@ -174,19 +228,10 @@ export const sendNotification = async ( payload: body.segment?.payload, sendEmail: body.sendEmail, sendSegment: body.sendSegment, + segmentValidator: !!segmentValidator, + eventName: body.eventName, }); - 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) { const emailData = body.segment?.payload; validateWithJoiSchema(emailData, segmentValidator); diff --git a/src/types/notifications.ts b/src/types/notifications.ts index 19627e6..81c253f 100644 --- a/src/types/notifications.ts +++ b/src/types/notifications.ts @@ -44,9 +44,15 @@ export enum NOTIFICATIONS_EVENT_NAMES { PROJECT_HAS_RISEN_IN_THE_RANK = 'Your Project has risen in the rank', PROJECT_HAS_A_NEW_RANK = 'Your project has a new rank', YOUR_PROJECT_GOT_A_RANK = 'Your project got a rank', + SUPER_TOKENS_BALANCE_WEEK = 'One week left in stream balance', + SUPER_TOKENS_BALANCE_MONTH = 'One month left in stream balance', + SUPER_TOKENS_BALANCE_DEPLETED = 'Stream balance depleted', } export const ORTTO_EVENT_NAMES = { + [NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_WEEK]: 'superfluid-balance-warning', + [NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_MONTH]: 'superfluid-balance-warning', + [NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_DEPLETED]: 'superfluid-balance-warning', [NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED]: 'testing-donation-received', [NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED]: 'project-created', [NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED]: 'project-listed', @@ -57,4 +63,6 @@ export const ORTTO_EVENT_NAMES = { [NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED]: 'project-verification', [NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED]: 'project-verification', [NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_REJECTED]: 'project-verification', + [NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING]: 'first-update-warning', + [NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING]: 'second-update-warning', } \ No newline at end of file diff --git a/src/utils/validators/segmentAndMetadataValidators.ts b/src/utils/validators/segmentAndMetadataValidators.ts index cc3e8ab..d943134 100644 --- a/src/utils/validators/segmentAndMetadataValidators.ts +++ b/src/utils/validators/segmentAndMetadataValidators.ts @@ -92,6 +92,7 @@ const donationTrackerSchema = Joi.object({ donationValueEth: Joi.number().greater(0).allow(null), verified: Joi.boolean().allow(null), transakStatus: Joi.string().allow(null), + isRecurringDonation: Joi.boolean().allow(null), }); const projectTitleProjectLinkSchema = Joi.object({ @@ -141,12 +142,34 @@ const givBackReadyClaimSchema = Joi.object({ amount: Joi.string().required(), }); +const superFluidTokenMetadataSchema = Joi.object({ + tokenSymbol: Joi.string().required(), + email: Joi.string().required(), + userId: Joi.number().required(), + isEnded: Joi.boolean(), + projectTitle: Joi.string(), + projectLink: Joi.string(), + networkName: Joi.string().required(), + recurringDonationTab: Joi.string().required(), +}); + +const superFluidTokenSegmentSchema = Joi.object({ + tokenSymbol: Joi.string().required(), + email: Joi.string().required(), + userId: Joi.number().required(), + isEnded: Joi.boolean(), +}); + export const SEGMENT_METADATA_SCHEMA_VALIDATOR: { [key: string]: { segment: ObjectSchema | null; metadata: ObjectSchema | null; }; } = { + userSuperTokensCritical: { + metadata: superFluidTokenMetadataSchema, + segment: superFluidTokenSegmentSchema, + }, draftedProjectSavedValidator: { metadata: projectTitleProjectLinkSchema, segment: projectRelatedTrackerSchema, diff --git a/src/validators/schemaValidators.ts b/src/validators/schemaValidators.ts index 3ba478e..1eb9aab 100644 --- a/src/validators/schemaValidators.ts +++ b/src/validators/schemaValidators.ts @@ -75,6 +75,11 @@ export const sendNotificationValidator = Joi.object({ donationValueEth: Joi.number().allow(null), verified: Joi.boolean(), transakStatus: Joi.string().allow(null).allow(''), + isRecurringDonation: Joi.boolean().allow(null), + + //Super token critical attributes + isEnded: Joi.boolean(), + tokenSymbol: Joi.string(), //Project related attributes lastName: Joi.string().allow(null).allow(''),