Skip to content

Commit

Permalink
Merge pull request #85 from Giveth/staging
Browse files Browse the repository at this point in the history
Superfluid emails
  • Loading branch information
CarlosQ96 authored Apr 22, 2024
2 parents 90734ad + a502872 commit f9beecd
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 11 deletions.
149 changes: 149 additions & 0 deletions migrations/1711607882826-addSuperfluidNotifications.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await queryRunner.manager.save(
NotificationType,
superFluidNotificationTypes,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM notification_type WHERE "categoryGroup" = 'superfluid';`,
);
}
}
45 changes: 45 additions & 0 deletions migrations/1712683625687-addSuperFluidNotificationForAllUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class addSuperFluidNotificationForAllUsers1712683625687 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// 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<void> {
// 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});
`);
}
}
1 change: 1 addition & 0 deletions src/entities/notificationSetting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/entities/notificationType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
67 changes: 56 additions & 11 deletions src/services/notificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/types/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
}
23 changes: 23 additions & 0 deletions src/utils/validators/segmentAndMetadataValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/validators/schemaValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(''),
Expand Down

0 comments on commit f9beecd

Please sign in to comment.