Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Job Launcher][Reputation Oracle] Add Sendgrid email templates #1062

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/apps/job-launcher/server/src/common/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChainId } from '@human-protocol/sdk';
import { JobRequestType, JobStatus } from '../enums/job';

export const SERVICE_NAME = 'Job Launcher';
export const NS = 'hmt';
export const COINGECKO_API_URL =
'https://api.coingecko.com/api/v3/simple/price';
Expand All @@ -24,6 +25,19 @@ export const SENDGRID_API_KEY_REGEX =

export const HEADER_SIGNATURE_KEY = 'human-signature';

export const CVAT_JOB_TYPES = [JobRequestType.IMAGE_BOXES, JobRequestType.IMAGE_POINTS]
export const CVAT_JOB_TYPES = [
JobRequestType.IMAGE_BOXES,
JobRequestType.IMAGE_POINTS,
];

export const CANCEL_JOB_STATUSES = [
JobStatus.PENDING,
JobStatus.PAID,
JobStatus.LAUNCHED,
];

export const CANCEL_JOB_STATUSES = [JobStatus.PENDING, JobStatus.PAID, JobStatus.LAUNCHED]
export const SENDGRID_TEMPLATES = {
signup: 'd-ca99cc7410aa4e6dab3e6042d5ecb9a3',
resetPassword: 'd-3ac74546352a4e1abdd1689947632c22',
passwordChanged: 'd-ca0ac7e6fff845829cd0167af09f25cf',
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { PaymentService } from '../payment/payment.service';
import { UserStatus } from '../../common/enums/user';
import { SendGridService } from '../sendgrid/sendgrid.service';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants';

jest.mock('@human-protocol/sdk');

Expand Down Expand Up @@ -394,31 +395,43 @@ describe('AuthService', () => {
} as UserEntity;

userService.getByEmail = jest.fn().mockResolvedValue(userEntity);

const existingToken = {
id: 2,
userId: userEntity.id,
tokenType: TokenType.PASSWORD,
remove: jest.fn(),
};
tokenRepository.findOne = jest.fn().mockResolvedValue(existingToken);

await authService.forgotPassword({ email: '[email protected]' });

expect(existingToken.remove).toHaveBeenCalled();
});

it('should create a new token and send email', async () => {
userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity);

sendGridService.sendEmail = jest.fn();
const email = '[email protected]';

await authService.forgotPassword({ email: '[email protected]' });
await authService.forgotPassword({ email });

expect(createTokenMock).toHaveBeenCalled();
expect(sendGridService.sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining(tokenEntity.uuid),
personalizations: [
{
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: expect.stringContaining(
'undefined/reset-password?token=mocked-uuid',
),
},
to: email,
},
],
templateId: SENDGRID_TEMPLATES.resetPassword,
}),
);
});
Expand Down Expand Up @@ -530,12 +543,6 @@ describe('AuthService', () => {
status: UserStatus.PENDING,
};

const tokenEntity = {
uuid: v4(),
tokenType: TokenType.EMAIL,
user: userEntity,
};

let createTokenMock: any;

beforeEach(() => {
Expand All @@ -558,13 +565,23 @@ describe('AuthService', () => {
userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity);

sendGridService.sendEmail = jest.fn();
const email = '[email protected]';

await authService.resendEmailVerification({ email: '[email protected]' });
await authService.resendEmailVerification({ email });

expect(createTokenMock).toHaveBeenCalled();
expect(sendGridService.sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining(tokenEntity.uuid),
personalizations: [
{
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: expect.stringContaining('/verify?token=mocked-uuid'),
},
to: email,
},
],
templateId: SENDGRID_TEMPLATES.signup,
}),
);
});
Expand Down
71 changes: 45 additions & 26 deletions packages/apps/job-launcher/server/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ConflictException,
Injectable,
Logger,
NotFoundException,
Expand Down Expand Up @@ -27,6 +26,7 @@ import { ConfigNames } from '../../common/config';
import { ConfigService } from '@nestjs/config';
import { createHash } from 'crypto';
import { SendGridService } from '../sendgrid/sendgrid.service';
import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants';

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -84,12 +84,16 @@ export class AuthService {
});

await this.sendgridService.sendEmail({
to: data.email,
subject: 'Verify your email',
html: `Welcome to the Job Launcher Service.<br />
Click <a href="${this.feURL}/verify?token=${tokenEntity.uuid}">here</a> to complete sign up.`,
text: `Welcome to the Job Launcher Service.
Click ${this.feURL}/verify?token=${tokenEntity.uuid} to complete sign up.`,
personalizations: [
{
to: data.email,
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: `${this.feURL}/verify?token=${tokenEntity.uuid}`,
},
},
],
templateId: SENDGRID_TEMPLATES.signup,
});

return userEntity;
Expand Down Expand Up @@ -147,21 +151,27 @@ Click ${this.feURL}/verify?token=${tokenEntity.uuid} to complete sign up.`,
userId: userEntity.id,
tokenType: TokenType.PASSWORD,
});

if (existingToken) {
await existingToken.remove();
}

const newTokenEntity = await this.tokenRepository.create({
tokenType: TokenType.PASSWORD,
user: userEntity,
});

this.sendgridService.sendEmail({
to: data.email,
subject: 'Reset password',
html: `Click <a href="${this.feURL}/reset-password?token=${newTokenEntity.uuid}">here</a> to reset the password.`,
text: `Click ${this.feURL}/reset-password?token=${newTokenEntity.uuid} to reset the password.`,
await this.sendgridService.sendEmail({
personalizations: [
{
to: data.email,
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: `${this.feURL}/reset-password?token=${newTokenEntity.uuid}`,
},
},
],
templateId: SENDGRID_TEMPLATES.resetPassword,
});
}

Expand All @@ -176,11 +186,16 @@ Click ${this.feURL}/verify?token=${tokenEntity.uuid} to complete sign up.`,
}

await this.userService.updatePassword(tokenEntity.user, data);

this.sendgridService.sendEmail({
to: tokenEntity.user.email,
subject: 'Password changed',
text: 'Password has been changed successfully!',
await this.sendgridService.sendEmail({
personalizations: [
{
to: tokenEntity.user.email,
dynamicTemplateData: {
service_name: SERVICE_NAME,
},
},
],
templateId: SENDGRID_TEMPLATES.passwordChanged,
});

await tokenEntity.remove();
Expand Down Expand Up @@ -216,13 +231,17 @@ Click ${this.feURL}/verify?token=${tokenEntity.uuid} to complete sign up.`,
user: userEntity,
});

this.sendgridService.sendEmail({
to: data.email,
subject: 'Verify your email',
html: `Welcome to the Job Launcher Service.<br />
Click <a href="${this.feURL}/verify?token=${tokenEntity.uuid}">here</a> to complete sign up.`,
text: `Welcome to the Job Launcher Service.
Click ${this.feURL}/verify?token=${tokenEntity.uuid} to complete sign up.`,
await this.sendgridService.sendEmail({
personalizations: [
{
to: data.email,
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: `${this.feURL}/verify?token=${tokenEntity.uuid}`,
},
},
],
templateId: SENDGRID_TEMPLATES.signup,
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
BadRequestException,
Injectable,
Logger,
UnauthorizedException,
} from '@nestjs/common';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MailDataRequired, MailService } from '@sendgrid/mail';
import { ConfigNames } from '../../common/config';
Expand Down Expand Up @@ -40,19 +35,22 @@ export class SendGridService {
}

async sendEmail({
text = '',
from = {
email: this.defaultFromEmail,
name: this.defaultFromName,
},
templateId = '',
personalizations,
...emailData
}: Partial<MailDataRequired>): Promise<void> {
try {
await this.mailService.send({
from,
text,
templateId,
personalizations,
...emailData,
});

this.logger.log('Email sent successfully');
return;
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { JobRequestType } from '../enums';

export const SERVICE_NAME = 'Reputation Oracle';
export const NS = 'hmt';
export const RETRIES_COUNT_THRESHOLD = 3;
export const INITIAL_REPUTATION = 0;
export const JWT_PREFIX = 'bearer ';
export const SENDGRID_API_KEY_REGEX =
/^SG\.[A-Za-z0-9-_]{22}\.[A-Za-z0-9-_]{43}$/;

export const SENDGRID_TEMPLATES = {
signup: 'd-ca99cc7410aa4e6dab3e6042d5ecb9a3',
resetPassword: 'd-3ac74546352a4e1abdd1689947632c22',
passwordChanged: 'd-ca0ac7e6fff845829cd0167af09f25cf',
};

export const CVAT_RESULTS_ANNOTATIONS_FILENAME = 'resulting_annotations.zip';
export const CVAT_VALIDATION_META_FILENAME = 'validation_meta.json';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { v4 } from 'uuid';
import { UserStatus, UserType } from '../../common/enums/user';
import { SendGridService } from '../sendgrid/sendgrid.service';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants';

jest.mock('@human-protocol/sdk');

Expand Down Expand Up @@ -390,13 +391,25 @@ describe('AuthService', () => {
userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity);

sendGridService.sendEmail = jest.fn();
const email = '[email protected]';

await authService.forgotPassword({ email: '[email protected]' });
await authService.forgotPassword({ email });

expect(createTokenMock).toHaveBeenCalled();
expect(sendGridService.sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining(tokenEntity.uuid),
personalizations: [
{
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: expect.stringContaining(
'undefined/reset-password?token=mocked-uuid',
),
},
to: email,
},
],
templateId: SENDGRID_TEMPLATES.resetPassword,
}),
);
});
Expand Down Expand Up @@ -508,12 +521,6 @@ describe('AuthService', () => {
status: UserStatus.PENDING,
};

const tokenEntity = {
uuid: v4(),
tokenType: TokenType.EMAIL,
user: userEntity,
};

let createTokenMock: any;

beforeEach(() => {
Expand All @@ -536,13 +543,23 @@ describe('AuthService', () => {
userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity);

sendGridService.sendEmail = jest.fn();
const email = '[email protected]';

await authService.resendEmailVerification({ email: '[email protected]' });
await authService.resendEmailVerification({ email });

expect(createTokenMock).toHaveBeenCalled();
expect(sendGridService.sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining(tokenEntity.uuid),
personalizations: [
{
dynamicTemplateData: {
service_name: SERVICE_NAME,
url: expect.stringContaining('/verify?token=mocked-uuid'),
},
to: email,
},
],
templateId: SENDGRID_TEMPLATES.signup,
}),
);
});
Expand Down
Loading