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

Feat/ auth sending emails using nodemailer #37

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions apps/api/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ REDIS_URL=redis://localhost:6379
# Auth
SESSION_SECRET=secret
COOKIE_MAX_AGE=604800000

GMAIL_USER=example@spacemail.mail
GMAIL_PASSWORD=spacemailPassword
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"graphql": "^16.9.0",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"nodemailer": "^6.9.15",
"pg": "^8.13.0",
"redis": "^4.7.0",
"reflect-metadata": "^0.2.0",
Expand Down
34 changes: 34 additions & 0 deletions apps/api/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UseGuards } from "@nestjs/common";
import { Args, Context, Mutation, Resolver } from "@nestjs/graphql";
import { Request, Response } from "express";
import { Public } from "../../common/custom-decorators/public-endpoint";
import { LocalAuthGuard } from "../../common/guards/local-auth.guard";
import { User } from "../users/entities/user.entity";
import { AuthService } from "./auth.service";
import { LoginDto } from "./dto/login.input";
import { SignupDto } from "./dto/signup.input";

@Resolver("auth")
export class AuthResolver {
constructor(private readonly authService: AuthService) {}

@Public()
@Mutation(() => User, { name: "signup" })
async signup(@Args() signupDto: SignupDto) {
const user = await this.authService.signup(signupDto);

return user;
}

@Public()
@UseGuards(LocalAuthGuard)
@Mutation(() => User, { name: "login" })
async login(@Context() ctx: any, @Args() loginDto: LoginDto) {
return ctx.req.user;
}

@Mutation(() => Boolean, { name: "logout" })
async logout(@Context() context: { req: Request; res: Response }) {
return await this.authService.logout(context.req, context.res);
}
}

Check warning on line 34 in apps/api/src/modules/auth/auth.controller.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/auth/auth.controller.ts#L2-L34

Added lines #L2 - L34 were not covered by tests
18 changes: 18 additions & 0 deletions apps/api/src/modules/mail/Providers/mail-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mailOptions } from "./mailOptions.interface";

// this class is where you select the strategy you want work with e.g. nodemailer , brevo...
export class MailContext {
private strategy: mailOptions;

constructor(strategy: mailOptions) {
this.strategy = strategy;
}

setStrategy(strategy: mailOptions) {
this.strategy = strategy;
}

async WelcomeUser(email: string) {
this.strategy.welcomeUser(email);
}
}

Check warning on line 18 in apps/api/src/modules/mail/Providers/mail-context.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/mail/Providers/mail-context.ts#L2-L18

Added lines #L2 - L18 were not covered by tests
4 changes: 4 additions & 0 deletions apps/api/src/modules/mail/Providers/mailOptions.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// this interface used for all messages that server can send via email.
export interface mailOptions {
welcomeUser(email: string): Promise<void>;
}

Check warning on line 4 in apps/api/src/modules/mail/Providers/mailOptions.interface.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/mail/Providers/mailOptions.interface.ts#L2-L4

Added lines #L2 - L4 were not covered by tests
37 changes: 37 additions & 0 deletions apps/api/src/modules/mail/Providers/nodemailer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as nodemailer from "nodemailer";
import { mailOptions } from "./mailOptions.interface";

// nodemailer strategy where all logic for sending email via nodemailer is implemented.
export class NodemailerStrategy implements mailOptions {
private transporter: nodemailer.Transporter;

constructor() {
this.transporter = nodemailer.createTransport({
host: "mail.spacemail.com",
port: 465, // Usually 587 for TLS, 465 for SSL
secure: true, // true for 465, false for other ports
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASSWORD,
},
});
}

async welcomeUser(email: string): Promise<void> {
const mailOptions: nodemailer.SendMailOptions = {
from: process.env.GMAIL_USER,
to: email,
subject: "Welcome!",
html: `
<h1>Welcome!</h1>
`,
};

try {
await this.transporter.sendMail(mailOptions);
} catch (error) {
console.error("Error sending welcome email:", error);
throw new Error("Failed to send welcome email");
}
}
}

Check warning on line 37 in apps/api/src/modules/mail/Providers/nodemailer.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/mail/Providers/nodemailer.ts#L2-L37

Added lines #L2 - L37 were not covered by tests
15 changes: 15 additions & 0 deletions apps/api/src/modules/mail/mail.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Module } from "@nestjs/common";
import { NodemailerStrategy } from "./Providers/nodemailer";
import { MailService } from "./mail.service";

@Module({
providers: [
MailService,
{
provide: "MAIL_STRATEGY",
useClass: NodemailerStrategy,
},
],
exports: [MailService],
})
export class MailModule {}

Check warning on line 15 in apps/api/src/modules/mail/mail.module.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/mail/mail.module.ts#L2-L15

Added lines #L2 - L15 were not covered by tests
24 changes: 24 additions & 0 deletions apps/api/src/modules/mail/mail.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// src/mail/mail.service.ts
import { Inject, Injectable } from "@nestjs/common";
import { MailContext } from "./Providers/mail-context";
import { mailOptions } from "./Providers/mailOptions.interface";
import { NodemailerStrategy } from "./Providers/nodemailer";

/* this is the mail service class where it used in other services or controllers */
@Injectable()
export class MailService {
private mailContext: MailContext;

// make sure to select used strategy
constructor(@Inject("MAIL_STRATEGY") defaultStrategy: mailOptions) {
this.mailContext = new MailContext(defaultStrategy);
}

changeStrategyToNodemailer() {
this.mailContext.setStrategy(new NodemailerStrategy());
}

async welcomeUser(email: string) {
await this.mailContext.WelcomeUser(email);
}
}

Check warning on line 24 in apps/api/src/modules/mail/mail.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/modules/mail/mail.service.ts#L2-L24

Added lines #L2 - L24 were not covered by tests
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
"engines": {
"node": ">=20"
},
"packageManager": "pnpm@9.11.0"
"packageManager": "pnpm@9.11.0",
"dependencies": {
"@types/nodemailer": "^6.4.16"
}
}
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.