Skip to content

Commit 940fc7e

Browse files
author
Nevo David
committed
feat: choosable email
1 parent 129b21b commit 940fc7e

File tree

12 files changed

+143
-19
lines changed

12 files changed

+143
-19
lines changed

apps/backend/src/api/routes/auth.controller.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/for
88
import { ForgotPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot.password.dto';
99
import { ApiTags } from '@nestjs/swagger';
1010
import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';
11+
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
1112

1213
@ApiTags('Auth')
1314
@Controller('/auth')
1415
export class AuthController {
15-
constructor(private _authService: AuthService) {}
16+
constructor(
17+
private _authService: AuthService,
18+
private _emailService: EmailService
19+
) {}
1620
@Post('/register')
1721
async register(
1822
@Req() req: Request,
@@ -30,7 +34,7 @@ export class AuthController {
3034
getOrgFromCookie
3135
);
3236

33-
const activationRequired = body.provider === 'LOCAL' && !!process.env.RESEND_API_KEY;
37+
const activationRequired = body.provider === 'LOCAL' && this._emailService.hasProvider();
3438

3539
if (activationRequired) {
3640
response.header('activate', 'true');

libraries/helpers/src/configuration/configuration.checker.ts

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export class ConfigurationChecker {
3030
this.checkIsValidUrl('FRONTEND_URL')
3131
this.checkIsValidUrl('NEXT_PUBLIC_BACKEND_URL')
3232
this.checkIsValidUrl('BACKEND_INTERNAL_URL')
33-
this.checkNonEmpty('RESEND_API_KEY', 'Needed to send user activation emails.')
3433
this.checkNonEmpty('CLOUDFLARE_ACCOUNT_ID', 'Needed to setup providers.')
3534
this.checkNonEmpty('CLOUDFLARE_ACCESS_KEY', 'Needed to setup providers.')
3635
this.checkNonEmpty('CLOUDFLARE_SECRET_ACCESS_KEY', 'Needed to setup providers.')

libraries/nestjs-libraries/src/database/prisma/notifications/notification.service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ export class NotificationService {
4040
async sendEmail(to: string, subject: string, html: string) {
4141
await this._emailService.sendEmail(to, subject, html);
4242
}
43+
44+
hasEmailProvider() {
45+
return this._emailService.hasProvider();
46+
}
4347
}

libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ export class OrganizationRepository {
177177
}
178178

179179
async createOrgAndUser(
180-
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string }
180+
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },
181+
hasEmail: boolean
181182
) {
182183
return this._organization.model.organization.create({
183184
data: {
@@ -187,7 +188,7 @@ export class OrganizationRepository {
187188
role: Role.SUPERADMIN,
188189
user: {
189190
create: {
190-
activated: body.provider !== 'LOCAL' || !process.env.RESEND_API_KEY,
191+
activated: body.provider !== 'LOCAL' || !hasEmail,
191192
email: body.email,
192193
password: body.password
193194
? AuthService.hashPassword(body.password)

libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class OrganizationService {
1717
async createOrgAndUser(
1818
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string }
1919
) {
20-
return this._organizationRepository.createOrgAndUser(body);
20+
return this._organizationRepository.createOrgAndUser(body, this._notificationsService.hasEmailProvider());
2121
}
2222

2323
addUserToOrg(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface EmailInterface {
2+
name: string;
3+
validateEnvKeys: string[];
4+
sendEmail(to: string, subject: string, html: string, emailFromName: string, emailFromAddress: string): Promise<any>;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { EmailInterface } from "./email.interface";
2+
3+
export class EmptyProvider implements EmailInterface {
4+
name = 'no provider';
5+
validateEnvKeys = [];
6+
async sendEmail(to: string, subject: string, html: string) {
7+
return `No email provider found, email was supposed to be sent to ${to} with subject: ${subject} and ${html}, html`;
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import nodemailer from 'nodemailer';
2+
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
3+
4+
const transporter = nodemailer.createTransport({
5+
host: process.env.EMAIL_HOST,
6+
port: +process.env.EMAIL_PORT!,
7+
secure: process.env.EMAIL_SECURE === 'true',
8+
auth: {
9+
user: process.env.EMAIL_USER,
10+
pass: process.env.EMAIL_PASS,
11+
},
12+
});
13+
14+
export class NodeMailerProvider implements EmailInterface {
15+
name = 'nodemailer';
16+
validateEnvKeys = [
17+
'EMAIL_HOST',
18+
'EMAIL_PORT',
19+
'EMAIL_SECURE',
20+
'EMAIL_USER',
21+
'EMAIL_PASS',
22+
];
23+
async sendEmail(
24+
to: string,
25+
subject: string,
26+
html: string,
27+
emailFromName: string,
28+
emailFromAddress: string
29+
) {
30+
const sends = await transporter.sendMail({
31+
from:`${emailFromName} <${emailFromAddress}>`, // sender address
32+
to: to, // list of receivers
33+
subject: subject, // Subject line
34+
text: html, // plain text body
35+
html: html, // html body
36+
});
37+
38+
return sends;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Resend } from 'resend';
2+
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
3+
4+
const resend = new Resend(process.env.RESEND_API_KEY || 're_132');
5+
6+
export class ResendProvider implements EmailInterface {
7+
name = 'resend';
8+
validateEnvKeys = ['RESEND_API_KEY'];
9+
async sendEmail(to: string, subject: string, html: string, emailFromName: string, emailFromAddress: string) {
10+
const sends = await resend.emails.send({
11+
from: `${emailFromName} <${emailFromAddress}>`,
12+
to,
13+
subject,
14+
html,
15+
});
16+
17+
return sends;
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,52 @@
11
import { Injectable } from '@nestjs/common';
2-
import { Resend } from 'resend';
3-
4-
const resend = new Resend(process.env.RESEND_API_KEY || 're_132');
2+
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
3+
import { ResendProvider } from '@gitroom/nestjs-libraries/emails/resend.provider';
4+
import { EmptyProvider } from '@gitroom/nestjs-libraries/emails/empty.provider';
5+
import { NodeMailerProvider } from '@gitroom/nestjs-libraries/emails/node.mailer.provider';
56

67
@Injectable()
78
export class EmailService {
8-
async sendEmail(to: string, subject: string, html: string) {
9-
if (!process.env.RESEND_API_KEY) {
10-
console.log('No Resend API Key found, skipping email sending');
11-
return;
9+
emailService: EmailInterface;
10+
constructor() {
11+
this.emailService = this.selectProvider(process.env.EMAIL_PROVIDER!);
12+
console.log('Email service provider:', this.emailService.name);
13+
for (const key of this.emailService.validateEnvKeys) {
14+
if (!process.env[key]) {
15+
console.error(`Missing environment variable: ${key}`);
16+
}
1217
}
18+
}
19+
20+
hasProvider() {
21+
return !(this.emailService instanceof EmptyProvider);
22+
}
1323

24+
selectProvider(provider: string) {
25+
switch (provider) {
26+
case 'resend':
27+
return new ResendProvider();
28+
case 'nodemailer':
29+
return new NodeMailerProvider();
30+
default:
31+
return new EmptyProvider();
32+
}
33+
}
34+
35+
async sendEmail(to: string, subject: string, html: string) {
1436
if (!process.env.EMAIL_FROM_ADDRESS || !process.env.EMAIL_FROM_NAME) {
15-
console.log('Email sender information not found in environment variables');
37+
console.log(
38+
'Email sender information not found in environment variables'
39+
);
1640
return;
1741
}
1842

19-
console.log('Sending email to', to);
20-
const sends = await resend.emails.send({
21-
from: `${process.env.EMAIL_FROM_NAME} <${process.env.EMAIL_FROM_ADDRESS}>`,
43+
const sends = await this.emailService.sendEmail(
2244
to,
2345
subject,
2446
html,
25-
});
26-
47+
process.env.EMAIL_FROM_NAME,
48+
process.env.EMAIL_FROM_ADDRESS
49+
);
2750
console.log(sends);
2851
}
2952
}

package-lock.json

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"@types/md5": "^2.3.5",
7171
"@types/mime-types": "^2.1.4",
7272
"@types/multer": "^1.4.11",
73+
"@types/nodemailer": "^6.4.16",
7374
"@types/remove-markdown": "^0.3.4",
7475
"@types/sha256": "^0.2.2",
7576
"@types/stripe": "^8.0.417",
@@ -110,6 +111,7 @@
110111
"nestjs-command": "^3.1.4",
111112
"next": "14.2.3",
112113
"next-plausible": "^3.12.0",
114+
"nodemailer": "^6.9.15",
113115
"nx": "19.7.2",
114116
"openai": "^4.47.1",
115117
"polotno": "^2.10.5",

0 commit comments

Comments
 (0)