Skip to content

Commit

Permalink
Merge pull request #74 from Giveth/staging
Browse files Browse the repository at this point in the history
Release 2024-02-25 Email migration
  • Loading branch information
mohammadranjbarz authored Feb 25, 2024
2 parents ba906ff + 4422ea9 commit acacac7
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 23 deletions.
6 changes: 6 additions & 0 deletions config/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ REDIS_PORT=6490
REDIS_PASSWORD=

SEGMENT_API_KEY=FAKE_API_KEY
ORTTO_API_KEY=FAKE_API_KEY
ORTTO_ACTIVITY_API=https://api-us.ortto.app/v1/activities/create

#JWT_AUTHORIZATION_ADAPTER=siweMicroservice
JWT_AUTHORIZATION_ADAPTER=mock
AUTH_MICROSERVICE_AUTHORIZATION_URL=

HOSTNAME_WHITELIST=next.giveth.io,www.next.giveth.io,staging.giveth.io,www.staging.giveth.io,localhost,giveth.io,vercel.app,www.giveth.io


EMAIL_ADAPTER=mock
#EMAIL_ADAPTER=ortto
6 changes: 5 additions & 1 deletion config/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ REDIS_HOST=127.0.0.1
REDIS_PORT=6490
REDIS_PASSWORD=


SEGMENT_API_KEY=FAKE_API_KEY

JWT_AUTHORIZATION_ADAPTER=mock
Expand All @@ -23,3 +22,8 @@ GIVETH_IO_THIRD_PARTY_MICRO_SERVICE=givethio

GIV_ECONOMY_THIRD_PARTY_SECRET=secret
GIV_ECONOMY_THIRD_PARTY_MICRO_SERVICE=giveconomy-notification-service

# OPTIONAL - force logging to stdout when the value is true
LOG_STDOUT=false

EMAIL_ADAPTER=mock
16 changes: 16 additions & 0 deletions src/adapters/adapterFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { SiweAuthenticationMicroserviceAdapter } from './jwtAuthentication/siweAuthenticationMicroserviceAdapter';
import { MockJwtAdapter } from './jwtAuthentication/mockJwtAdapter';
import { errorMessages } from '../utils/errorMessages';
import {OrttoAdapter} from "./emailAdapter/orttoAdapter";
import {OrttoMockAdapter} from "./emailAdapter/orttoMockAdapter";

const siweAuthenricationAdapter = new SiweAuthenticationMicroserviceAdapter();
const jwtMockAdapter = new MockJwtAdapter();
Expand All @@ -15,3 +17,17 @@ export const getJwtAuthenticationAdapter = () => {
throw new Error(errorMessages.SPECIFY_JWT_AUTHENTICATION_ADAPTER);
}
};


const orttoEmailAdapter = new OrttoAdapter()
const emailMockAdapter = new OrttoMockAdapter()
export const getEmailAdapter = () => {
switch (process.env.EMAIL_ADAPTER) {
case 'ortto':
return orttoEmailAdapter;
case 'mock':
return emailMockAdapter;
default:
throw new Error(errorMessages.SPECIFY_Email_ADAPTER);
}
};
30 changes: 30 additions & 0 deletions src/adapters/emailAdapter/orttoAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { logger } from '../../utils/logger';
import axios from 'axios';
import {OrttoAdapterInterface} from "./orttoAdapterInterface";

export class OrttoAdapter implements OrttoAdapterInterface{
async callOrttoActivity(data: any): Promise<void> {
try {
if (!data){
throw new Error('callOrttoActivity input data is empty')
}
const config = {
method: 'post',
maxBodyLength: Infinity,
url: process.env.ORTTO_ACTIVITY_API,
headers: {
'X-Api-Key': process.env.ORTTO_API_KEY as string,
'Content-Type': 'application/json'
},
data
};
data.activities.map((a: any) => logger.debug('orttoActivityCall', a));
await axios.request(config);
} catch (e) {
logger.error('orttoActivityCall error', {
error: e,
data
});
}
}
}
5 changes: 5 additions & 0 deletions src/adapters/emailAdapter/orttoAdapterInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import internal from "stream";

export interface OrttoAdapterInterface {
callOrttoActivity (data: any): Promise<void>
}
11 changes: 11 additions & 0 deletions src/adapters/emailAdapter/orttoMockAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { logger } from '../../utils/logger';
import axios from 'axios';
import {OrttoAdapterInterface} from "./orttoAdapterInterface";


export class OrttoMockAdapter implements OrttoAdapterInterface{
async callOrttoActivity(data: any): Promise<void> {
logger.debug('OrttoMockAdapter has been called', data)
}

}
1 change: 0 additions & 1 deletion src/controllers/v1/notificationsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
markNotificationsAsRead,
} from '../../repositories/notificationRepository';
import { UserAddress } from '../../entities/userAddress';
import { Notification } from '../../entities/notification';
import { createNewUserAddressIfNotExists } from '../../repositories/userAddressRepository';
import { sendNotification } from '../../services/notificationService';
import { StandardError } from '../../types/StandardError';
Expand Down
10 changes: 0 additions & 10 deletions src/routes/v1/notificationRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,6 @@ function sendNotificationTestCases() {
sendEmail: true,
sendSegment: true,
segment: {
analyticsUserId: 'givethId-255',
anonymousId: 'givethId-255',
payload: {
title: 'Test verify and reject form emails',
lastName: 'Ranjbar',
Expand Down Expand Up @@ -1385,8 +1383,6 @@ function sendNotificationTestCases() {
projectLink,
},
segment: {
analyticsUserId: 'givethId-255',
anonymousId: 'givethId-255',
payload: {
email: '[email protected]',
title: 'How many photos is too many photos?',
Expand Down Expand Up @@ -1428,8 +1424,6 @@ function sendNotificationTestCases() {
projectLink,
},
segment: {
analyticsUserId: 'givethId-255',
anonymousId: 'givethId-255',
payload: {
email: '[email protected]',
title: 'How many photos is too many photos?',
Expand Down Expand Up @@ -1499,8 +1493,6 @@ function sendNotificationTestCases() {
projectLink,
},
segment: {
analyticsUserId: 'givethId-255',
anonymousId: 'givethId-255',
payload: {
email: '[email protected]',
title: 'How many photos is too many photos?',
Expand Down Expand Up @@ -1542,8 +1534,6 @@ function sendNotificationTestCases() {
projectLink,
},
segment: {
analyticsUserId: 'givethId-255',
anonymousId: 'givethId-255',
payload: {
email: '[email protected]',
title: 'How many photos is too many photos?',
Expand Down
112 changes: 102 additions & 10 deletions src/services/notificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,105 @@ 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';
import { NOTIFICATIONS_EVENT_NAMES, ORTTO_EVENT_NAMES } from '../types/notifications';
import {getEmailAdapter} from "../adapters/adapterFactory";

const activityCreator = (payload: any, orttoEventName: NOTIFICATIONS_EVENT_NAMES) : any=> {
const fields = {
"str::email": payload.email,
}
if (process.env.ENVIRONMENT === 'production') {
fields['str:cm:user-id'] = payload.userId?.toString()
}
let attributes;
switch (orttoEventName) {
case NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:donationamount": payload.amount.toString(),
"str:cm:donationtoken": payload.token,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
"bol:cm:verified": payload.verified,
"str:cm:transactionlink": payload.transactionLink,
};
break
case NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
"str:cm:firstname": payload.firstName,
"str:cm:lastname": payload.lastName,
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_UPDATE_ADDED_OWNER:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectupdatelink": payload.projectLink + '?tab=updates',
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
"str:cm:verified-status": 'verified',
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
"str:cm:verified-status": 'rejected',
};
break
case NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED:
attributes = {
"str:cm:projecttitle": payload.title,
"str:cm:email": payload.email,
"str:cm:projectlink": payload.projectLink,
"str:cm:verified-status": 'revoked',
}
break
default:
logger.debug('activityCreator() invalid event name', orttoEventName)
}
return {
activities: [
{
activity_id: `act:cm:${ORTTO_EVENT_NAMES[orttoEventName]}`,
attributes,
fields,
}
]
};
}

export const sendNotification = async (
body: SendNotificationRequest,
Expand Down Expand Up @@ -78,14 +174,10 @@ export const sendNotification = async (
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,
});
const emailData = body.segment?.payload;
validateWithJoiSchema(emailData, segmentValidator);
const data = activityCreator(emailData, body.eventName as NOTIFICATIONS_EVENT_NAMES);
await getEmailAdapter().callOrttoActivity(data);
emailStatus = EMAIL_STATUSES.SENT;
}

Expand All @@ -99,7 +191,7 @@ export const sendNotification = async (
}

if (!notificationSetting?.allowDappPushNotification) {
//TODO In future we can add a create notification but with disabledNotification:true
//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,
Expand Down
59 changes: 59 additions & 0 deletions src/types/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export enum NOTIFICATIONS_EVENT_NAMES {
DRAFTED_PROJECT_ACTIVATED = 'Draft published',
PROJECT_LISTED = 'Project listed',
PROJECT_UNLISTED = 'Project unlisted',
PROJECT_UNLISTED_SUPPORTED = 'Project unlisted - Users who supported',
PROJECT_LISTED_SUPPORTED = 'Project listed - Users who supported',
PROJECT_EDITED = 'Project edited',
PROJECT_BADGE_REVOKED = 'Project badge revoked',
PROJECT_BADGE_REVOKE_REMINDER = 'Project badge revoke reminder',
PROJECT_BADGE_REVOKE_WARNING = 'Project badge revoke warning',
PROJECT_BADGE_REVOKE_LAST_WARNING = 'Project badge revoke last warning',
PROJECT_BADGE_UP_FOR_REVOKING = 'Project badge up for revoking',
PROJECT_BOOSTED = 'Project boosted',
PROJECT_BOOSTED_BY_PROJECT_OWNER = 'Project boosted by project owner',
PROJECT_VERIFIED = 'Project verified',
PROJECT_VERIFIED_USERS_WHO_SUPPORT = 'Project verified - Users who supported',

// https://github.com/Giveth/impact-graph/issues/624#issuecomment-1240364389
PROJECT_REJECTED = 'Project unverified',
PROJECT_NOT_REVIEWED = 'Project not reviewed',
PROJECT_UNVERIFIED = 'Project unverified',
VERIFICATION_FORM_REJECTED = 'Form rejected',
PROJECT_UNVERIFIED_USERS_WHO_SUPPORT = 'Project unverified - Users who supported',
PROJECT_ACTIVATED = 'Project activated',
PROJECT_ACTIVATED_USERS_WHO_SUPPORT = 'Project activated - Users who supported',
PROJECT_DEACTIVATED = 'Project deactivated',
PROJECT_DEACTIVATED_USERS_WHO_SUPPORT = 'Project deactivated - Users who supported',

PROJECT_CANCELLED = 'Project cancelled',
PROJECT_CANCELLED_USERS_WHO_SUPPORT = 'Project cancelled - Users who supported',
MADE_DONATION = 'Made donation',
DONATION_RECEIVED = 'Donation received',
DONATION_GET_PRICE_FAILED = 'Donation get price failed',
PROJECT_RECEIVED_HEART = 'project liked',
PROJECT_UPDATE_ADDED_OWNER = 'Project update added - owner',
PROJECT_CREATED = 'The project saved as draft',
UPDATED_PROFILE = 'Updated profile',
GET_DONATION_PRICE_FAILED = 'Get Donation Price Failed',
VERIFICATION_FORM_GOT_DRAFT_BY_ADMIN = 'Verification form got draft by admin',
RAW_HTML_BROADCAST = 'Raw HTML Broadcast',
PROJECT_ADD_AN_UPDATE_USERS_WHO_SUPPORT = 'Project update added - Users who supported',

// https://github.com/Giveth/impact-graph/issues/774#issuecomment-1542337083
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',
}

export const ORTTO_EVENT_NAMES = {
[NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED]: 'testing-donation-received',
[NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED]: 'project-created',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED]: 'project-listed',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED]: 'project-unlisted',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED]: 'project-deactivated',
[NOTIFICATIONS_EVENT_NAMES.MADE_DONATION]: 'donation-made',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED]: 'project-verification',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED]: 'project-verification',
[NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED]: 'project-verification',
}
1 change: 1 addition & 0 deletions src/utils/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const errorMessagesEnum = {

export const errorMessages = {
SPECIFY_JWT_AUTHENTICATION_ADAPTER: 'Specify authentication jwt adapter',
SPECIFY_Email_ADAPTER: 'Specify email adapter',
CHANGE_API_INVALID_TITLE_OR_EIN:
'ChangeAPI title or EIN not found or invalid',
INVALID_SOCIAL_NETWORK: 'Invalid social network',
Expand Down
3 changes: 2 additions & 1 deletion src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ function createBunyanLogger() {

if (
process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'test'
process.env.NODE_ENV === 'test' ||
process.env.LOG_STDOUT === 'true'
) {
// Adding logs to console in local machine and running tests
bunyanStreams.push({
Expand Down
Loading

0 comments on commit acacac7

Please sign in to comment.