Skip to content

Commit

Permalink
Merge pull request #15 from Giveth/f_14_add_trackId_to_notification
Browse files Browse the repository at this point in the history
F 14 add track id to notification
  • Loading branch information
mohammadranjbarz authored Nov 23, 2022
2 parents 04c7717 + fb9b636 commit 1b82e33
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 64 deletions.
6 changes: 6 additions & 0 deletions migrations/1660546929601-createNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export class createNotification1660546929601 implements MigrationInterface {
type: 'varchar',
isNullable: true,
},
{
name: 'trackId',
type: 'varchar',
isNullable: true,
isUnique: true,
},
{
name: 'isRead',
type: 'boolean',
Expand Down
3 changes: 2 additions & 1 deletion migrations/1660716115917-seedNotificationType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,8 @@ export const GivethNotificationTypes = {
content: ' is not verified anymore',
},
],
content: 'The project that you boosted before {project name} is not verified anymore',
content:
'The project that you boosted before {project name} is not verified anymore',
},
MADE_DONATION: {
name: 'Made donation',
Expand Down
22 changes: 18 additions & 4 deletions src/controllers/v1/notificationsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { User } from '../../types/general';
import {
countUnreadNotifications,
createNotification,
findNotificationByTrackId,
getNotifications,
markNotificationGroupAsRead,
markNotificationsAsRead,
Expand All @@ -42,7 +43,7 @@ import {
getNotificationTypeByEventName,
getNotificationTypeByEventNameAndMicroservice,
} from '../../repositories/notificationTypeRepository';
import { EMAIL_STATUSES } from '../../entities/notification';
import { EMAIL_STATUSES, Notification } from '../../entities/notification';
import { createNewUserAddressIfNotExists } from '../../repositories/userAddressRepository';
import { SEGMENT_METADATA_SCHEMA_VALIDATOR } from '../../utils/validators/segmentAndMetadataValidators';
import { findNotificationSettingByNotificationTypeAndUserAddress } from '../../repositories/notificationSettingRepository';
Expand All @@ -65,6 +66,13 @@ export class NotificationsController {
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,
);
Expand Down Expand Up @@ -130,15 +138,21 @@ export class NotificationsController {
message: errorMessages.USER_TURNED_OF_THIS_NOTIFICATION_TYPE,
};
}
await createNotification({
const notificationData: Partial<Notification> = {
notificationType,
user: userAddress,
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 };
// add if and logic for push notification (not in mvp)
Expand Down
16 changes: 11 additions & 5 deletions src/entities/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import { NotificationType } from './notificationType';
import { UserAddress } from './userAddress';

export class NotificationMetadata {
// name of recipient
name?: string;
/**
* @example "title"
*/
projectTitle?: string;

// For donation emails it's needed
amount?: string;
currency?: string;
/**
* @example "project link"
*/
projectLink?: string;
}

export const EMAIL_STATUSES = {
Expand All @@ -45,6 +48,9 @@ export class Notification extends BaseEntity {
@Column('text', { nullable: true })
email?: string;

@Column('text', { nullable: true, unique: true })
trackId?: string;

@Column('text', { nullable: true })
emailContent?: string;

Expand Down
9 changes: 4 additions & 5 deletions src/repositories/notificationRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,18 @@ function createNotificationTestCases() {

const result = await createNotification({
notificationType: notificationType!,
user: userAddress,
userAddress,
email: '[email protected]',
emailStatus: EMAIL_STATUSES.NO_NEED_TO_SEND,
projectId: '1',
metadata: {
name: 'Carlos',
amount: 10,
currency: 'DAI',
projectTitle: 'Carlos',
projectLink: 'https://givet.io/Carlos',
},
});

assert.isOk(result);
assert.equal(result.userAddressId, userAddress.id);
assert.equal(result.userAddress.id, userAddress.id);
assert.equal(result.notificationTypeId, notificationType!.id);
});
}
Expand Down
50 changes: 20 additions & 30 deletions src/repositories/notificationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ export const countUnreadNotifications = async (
};
};

export const findNotificationByTrackId = async (
trackId: string,
): Promise<Notification | null> => {
return Notification.createQueryBuilder('notification')
.where('notification."trackId" = :trackId', {
trackId,
})
.getOne();
};

export const baseNotificationQuery = (user: UserAddress) => {
return Notification.createQueryBuilder('notification')
.innerJoinAndSelect('notification.notificationType', 'notificationType')
Expand Down Expand Up @@ -147,34 +157,14 @@ export const getNotifications = async (params: {
return query.take(limit).skip(skip).getManyAndCount();
};

export const createNotification = async (params: {
notificationType: NotificationType;
user: UserAddress;
email?: string;
emailStatus?: string;
metadata?: any;
segmentData?: any;
projectId?: string;
}) => {
const {
notificationType,
user,
projectId,
email,
emailStatus,
metadata,
segmentData,
} = params;

const notification = Notification.create({
userAddress: user,
email,
metadata,
segmentData,
emailStatus,
projectId,
notificationType,
});

return notification.save();
export const createNotification = async (
params: Partial<Notification>,
): Promise<Notification> => {
try {
const notification = Notification.create(params);
return (await notification.save()) as Notification;
} catch (e) {
logger.error('createNotification error', e);
throw e;
}
};
3 changes: 0 additions & 3 deletions src/repositories/notificationSettingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ export const updateUserNotificationSetting = async (params: {
)
.getOne();

console.log('user id ' + params.userAddressId);
console.log('notif id ' + params.notificationSettingId);

if (!notificationSetting)
throw new Error(errorMessages.NOTIFICATION_SETTING_NOT_FOUND);

Expand Down
94 changes: 89 additions & 5 deletions src/routes/v1/notificationRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import axios from 'axios';
import { assert } from 'chai';
import { errorMessages, errorMessagesEnum } from '../../utils/errorMessages';
import { findNotificationByTrackId } from '../../repositories/notificationRepository';

describe('/notifications POST test cases', sendNotificationTestCases);
describe('/notifications GET test cases', getNotificationTestCases);
Expand Down Expand Up @@ -54,7 +55,6 @@ function getNotificationTestCases() {
authorization: getAccessTokenForMockAuthMicroService(walletAddress),
},
});
console.log('**result**', notificationResult.data);
assert.equal(notificationResult.status, 200);
assert.isOk(notificationResult.data);
assert.equal(notificationResult.data.count, 3);
Expand Down Expand Up @@ -84,7 +84,6 @@ function sendNotificationTestCases() {
authorization: getGivethIoBasicAuth(),
},
});
console.log('**result**', result.data);
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
Expand All @@ -104,7 +103,6 @@ function sendNotificationTestCases() {
authorization: getGivethIoBasicAuth(),
},
});
console.log('**result**', result.data);
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
Expand Down Expand Up @@ -1244,7 +1242,6 @@ function sendNotificationTestCases() {
authorization: getGivethIoBasicAuth(),
},
});
console.log('**result**', result.data);
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
Expand All @@ -1264,7 +1261,6 @@ function sendNotificationTestCases() {
authorization: getGivethIoBasicAuth(),
},
});
console.log('**result**', result.data);
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
Expand Down Expand Up @@ -2146,4 +2142,92 @@ function sendNotificationTestCases() {
assert.equal(e.response.data.description, '"projectTitle" is required');
}
});

it('should create notification successfully with passing trackId', async () => {
const trackId = generateRandomTxHash();
const data = {
eventName: 'Draft published',
sendEmail: false,
sendSegment: false,
userWalletAddress: generateRandomEthereumAddress(),
trackId,
metadata: {
projectTitle,
projectLink,
},
};

const result = await axios.post(sendNotificationUrl, data, {
headers: {
authorization: getGivethIoBasicAuth(),
},
});
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
assert.isNotNull(await findNotificationByTrackId(trackId));
});

it('should throw error for repetitive trackId', async () => {
const trackId = generateRandomTxHash();
const data = {
eventName: 'Draft published',
sendEmail: false,
sendSegment: false,
userWalletAddress: generateRandomEthereumAddress(),
trackId,
metadata: {
projectTitle,
projectLink,
},
};

const result = await axios.post(sendNotificationUrl, data, {
headers: {
authorization: getGivethIoBasicAuth(),
},
});

assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
assert.isNotNull(await findNotificationByTrackId(trackId));
const duplicateResult = await axios.post(sendNotificationUrl, data, {
headers: {
authorization: getGivethIoBasicAuth(),
},
});
assert.equal(duplicateResult.status, 200);
assert.equal(
duplicateResult.data.message,
errorMessages.DUPLICATED_TRACK_ID,
);
});

it('should create notification successfully with passing creationTime', async () => {
const creationTime = new Date().getTime();
const trackId = generateRandomTxHash();
const data = {
eventName: 'Draft published',
sendEmail: false,
sendSegment: false,
userWalletAddress: generateRandomEthereumAddress(),
trackId,
creationTime,
metadata: {
projectTitle,
projectLink,
},
};
const result = await axios.post(sendNotificationUrl, data, {
headers: {
authorization: getGivethIoBasicAuth(),
},
});
assert.equal(result.status, 200);
assert.isOk(result.data);
assert.isTrue(result.data.success);
const createdNotification = await findNotificationByTrackId(trackId);
assert.equal(createdNotification?.createdAt.getTime(), creationTime);
});
}
21 changes: 10 additions & 11 deletions src/types/requestResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export type SendNotificationTypeRequest = {
* @example "Made donation"
*/
eventName: string;
/**
* @example "Should be unique"
*/
trackId?: string;
/**
* @example false
*/
Expand All @@ -17,6 +21,11 @@ export type SendNotificationTypeRequest = {
* @example false
*/
sendSegment: boolean;
/**
* @example 1667992708000
* @description milliseconds
*/
creationTime: number;
/**
* @example false
*/
Expand Down Expand Up @@ -47,17 +56,7 @@ export type SendNotificationTypeRequest = {
*/
anonymousId?: string;
};
metadata?: {
/**
* @example "title"
*/
projectTitle?: string;

/**
* @example "project link"
*/
projectLink?: string;
};
metadata?: NotificationMetadata;
};

export type SendNotificationRequest = {
Expand Down
1 change: 1 addition & 0 deletions src/utils/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 invalid',
DUPLICATED_TRACK_ID: 'Duplicated trackId',
CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION:
'This category is not valid',
INVALID_TX_HASH: 'Invalid txHash',
Expand Down
Loading

0 comments on commit 1b82e33

Please sign in to comment.