Skip to content

Commit

Permalink
Add email module and remove notify strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
carloszan committed Apr 28, 2024
1 parent 56d0c77 commit 2886d0c
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 71 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@czarpoliedros/email": "^0.2.7",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/microservices": "^9.1.6",
"@nestjs/platform-express": "^9.0.0",
"amqp-connection-manager": "^4.1.14",
"amqplib": "^0.10.3",
"nodemailer": "^6.9.13",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
Expand Down
3 changes: 2 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
import { NotifierModule } from './notifier/notifier.module';
import { EmailModule } from './email/email.module';

@Module({
imports: [NotifierModule],
imports: [NotifierModule, EmailModule],
controllers: [],
providers: [],
})
Expand Down
1 change: 1 addition & 0 deletions src/email/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SMTP_CONFIG_OPTIONS = 'SMTP_CONFIG_OPTIONS';
7 changes: 7 additions & 0 deletions src/email/dto/email-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class EmailRequest {
from: string;
to: string;
subject: string;
body: string;
body_html?: string;
}
38 changes: 38 additions & 0 deletions src/email/email.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { DynamicModule, Global, Module } from '@nestjs/common';
import { SMTP_CONFIG_OPTIONS } from './constants';
import { EmailService } from './email.service';
import { createTransport } from 'nodemailer';
import { SmtpOptions } from './interfaces/smtp-options.interface';

@Global()
@Module({})
export class EmailModule {
public static forRoot(smtpOptions: SmtpOptions): DynamicModule {
const transporter = createTransport({
name: smtpOptions.host,
host: smtpOptions.host,
port: smtpOptions.port,
secure: smtpOptions.secure,
auth: {
user: smtpOptions.email,
pass: smtpOptions.password,
},
tls: {
ciphers: 'SSLv3',
},
});

return {
module: EmailModule,
providers: [
{
provide: SMTP_CONFIG_OPTIONS,
useValue: transporter,
},
EmailService,
],
exports: [EmailService],
global: true,
};
}
}
38 changes: 38 additions & 0 deletions src/email/email.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';
import { SentMessageInfo, Transporter } from 'nodemailer';
import { SMTP_CONFIG_OPTIONS } from './constants';
import { EmailService } from './email.service';

describe('EmailService', () => {
let service: EmailService;
const transporterMock = mock<Transporter<SentMessageInfo>>();

beforeEach(async () => {
const smtpOptionsProvider = {
provide: SMTP_CONFIG_OPTIONS,
useValue: transporterMock,
};

const module: TestingModule = await Test.createTestingModule({
providers: [smtpOptionsProvider, EmailService],
}).compile();

service = module.get<EmailService>(EmailService);
});

it('should send email', async () => {
let sendMail = false;
transporterMock.verify.mockReturnValue(Promise.resolve(true));
transporterMock.sendMail.mockImplementation(async () => (sendMail = true));
await service.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Email sent',
body: 'body',
body_html: '<p>body</p>',
});

expect(sendMail).toBeTruthy();
});
});
46 changes: 46 additions & 0 deletions src/email/email.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Inject, Injectable } from '@nestjs/common';
import { SentMessageInfo, Transporter } from 'nodemailer';
import { EmailRequest } from './dto/email-request.dto';
import { SMTP_CONFIG_OPTIONS } from './constants';
import { ConnectionFailed } from './errors/connection-failed.error';

interface IEmailService {
sendMail(emailRequest: EmailRequest): Promise<void>;
}

@Injectable()
export class EmailService implements IEmailService {
constructor(
@Inject(SMTP_CONFIG_OPTIONS)
private transporter: Transporter<SentMessageInfo>,
) {}

async sendMail({
from,
to,
subject,
body,
body_html = '',
}: EmailRequest): Promise<void> {
const verifier = await this.transporter.verify();

if (!verifier)
throw new ConnectionFailed(
'Connection failed. Verify your credentials or connection.',
);

const mail = {
from: from,
to: to,
subject: subject,
text: body,
html: body_html,
};

try {
await this.transporter.sendMail(mail);
} catch (err) {
throw err;
}
}
}
5 changes: 5 additions & 0 deletions src/email/errors/connection-failed.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ConnectionFailed extends Error {
constructor(message?: string) {
super(message);
}
}
1 change: 1 addition & 0 deletions src/email/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './smtp-options.interface';
26 changes: 26 additions & 0 deletions src/email/interfaces/smtp-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface SmtpOptions {
/**
* host
*/
host: string;

/**
* port
*/
port: number;

/**
* email
*/
email: string;

/**
* password
*/
password: string;

/**
* secure
*/
secure: boolean;
}
10 changes: 2 additions & 8 deletions src/notifier/notifier.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { Controller, Logger } from '@nestjs/common';
import { Ctx, EventPattern, Payload, RmqContext } from '@nestjs/microservices';
import { SmsStrategy } from './strategies/sms.strategy';
import { EmailStrategy } from './strategies/email.strategy';
import { ConfirmChannel, Message } from 'amqplib';
import { ManagerCreatedRequest } from './dto/request/manager-created-request.dto';
import { EmailService } from '@czarpoliedros/email';
import { EmailService } from 'src/email/email.service';

@Controller()
export class NotifierController {
private readonly logger = new Logger(NotifierController.name);

constructor(
private readonly emailStrategy: EmailStrategy,
private readonly smsStrategy: SmsStrategy,
private readonly emailService: EmailService,
) {}
constructor(private readonly emailService: EmailService) {}

@EventPattern('send_email')
async handleSendEmail(
Expand Down
8 changes: 3 additions & 5 deletions src/notifier/notifier.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { EmailModule } from '@czarpoliedros/email';
import { ConfigModule } from '@nestjs/config';
import { SmsStrategy } from './strategies/sms.strategy';
import { EmailStrategy } from './strategies/email.strategy';
import { Module } from '@nestjs/common';
import { NotifierController } from './notifier.controller';
import { EmailModule } from '../email/email.module';

@Module({
imports: [
Expand All @@ -13,10 +11,10 @@ import { NotifierController } from './notifier.controller';
port: Number(process.env.SMTP_PORT),
email: process.env.SMTP_USER,
password: process.env.SMTP_PASSWORD,
secure: Boolean(process.env.SMTP_SECURE),
secure: false,
}),
],
controllers: [NotifierController],
providers: [EmailStrategy, SmsStrategy],
providers: [],
})
export class NotifierModule {}
29 changes: 0 additions & 29 deletions src/notifier/strategies/email.strategy.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/notifier/strategies/notify-strategy.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/notifier/strategies/sms.strategy.ts

This file was deleted.

17 changes: 2 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -346,19 +346,6 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"

"@czarpoliedros/email@^0.2.7":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@czarpoliedros/email/-/email-0.2.7.tgz#e4d5802ff92889d6f34ede8a4b9f1ca8b2f068e2"
integrity sha512-F4xd0b1cBiFHuFCk/PCqsJf3EvcBqHno8VJuZDTZVZhJYF9Vg4bGgL+tP9BQJa41MkXELm0av37gCj2yJeYJHQ==
dependencies:
"@nestjs/common" "^9.0.11"
"@nestjs/core" "^9.0.0"
"@nestjs/platform-express" "^9.0.11"
nodemailer "^6.9.13"
reflect-metadata "^0.1.13"
rimraf "^3.0.2"
rxjs "^7.2.0"

"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
Expand Down Expand Up @@ -700,7 +687,7 @@
webpack "5.82.1"
webpack-node-externals "3.0.0"

"@nestjs/common@^9.0.0", "@nestjs/common@^9.0.11":
"@nestjs/common@^9.0.0":
version "9.4.3"
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-9.4.3.tgz#f907c5315b4273f7675864a05c4dda7056632b87"
integrity sha512-Gd6D4IaYj01o14Bwv81ukidn4w3bPHCblMUq+SmUmWLyosK+XQmInCS09SbDDZyL8jy86PngtBLTdhJ2bXSUig==
Expand Down Expand Up @@ -739,7 +726,7 @@
iterare "1.2.1"
tslib "2.5.3"

"@nestjs/platform-express@^9.0.0", "@nestjs/platform-express@^9.0.11":
"@nestjs/platform-express@^9.0.0":
version "9.4.3"
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.4.3.tgz#f61b75686bdfce566be3b54fa7bb20a4d87ed619"
integrity sha512-FpdczWoRSC0zz2dNL9u2AQLXKXRVtq4HgHklAhbL59X0uy+mcxhlSThG7DHzDMkoSnuuHY8ojDVf7mDxk+GtCw==
Expand Down

0 comments on commit 2886d0c

Please sign in to comment.