feat: choosable email

This commit is contained in:
Nevo David 2024-09-27 13:36:42 +07:00
parent 129b21b46b
commit 940fc7e583
12 changed files with 144 additions and 20 deletions

View File

@ -8,11 +8,15 @@ import { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/for
import { ForgotPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot.password.dto';
import { ApiTags } from '@nestjs/swagger';
import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
@ApiTags('Auth')
@Controller('/auth')
export class AuthController {
constructor(private _authService: AuthService) {}
constructor(
private _authService: AuthService,
private _emailService: EmailService
) {}
@Post('/register')
async register(
@Req() req: Request,
@ -30,7 +34,7 @@ export class AuthController {
getOrgFromCookie
);
const activationRequired = body.provider === 'LOCAL' && !!process.env.RESEND_API_KEY;
const activationRequired = body.provider === 'LOCAL' && this._emailService.hasProvider();
if (activationRequired) {
response.header('activate', 'true');

View File

@ -30,7 +30,6 @@ export class ConfigurationChecker {
this.checkIsValidUrl('FRONTEND_URL')
this.checkIsValidUrl('NEXT_PUBLIC_BACKEND_URL')
this.checkIsValidUrl('BACKEND_INTERNAL_URL')
this.checkNonEmpty('RESEND_API_KEY', 'Needed to send user activation emails.')
this.checkNonEmpty('CLOUDFLARE_ACCOUNT_ID', 'Needed to setup providers.')
this.checkNonEmpty('CLOUDFLARE_ACCESS_KEY', 'Needed to setup providers.')
this.checkNonEmpty('CLOUDFLARE_SECRET_ACCESS_KEY', 'Needed to setup providers.')

View File

@ -40,4 +40,8 @@ export class NotificationService {
async sendEmail(to: string, subject: string, html: string) {
await this._emailService.sendEmail(to, subject, html);
}
hasEmailProvider() {
return this._emailService.hasProvider();
}
}

View File

@ -177,7 +177,8 @@ export class OrganizationRepository {
}
async createOrgAndUser(
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string }
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },
hasEmail: boolean
) {
return this._organization.model.organization.create({
data: {
@ -187,7 +188,7 @@ export class OrganizationRepository {
role: Role.SUPERADMIN,
user: {
create: {
activated: body.provider !== 'LOCAL' || !process.env.RESEND_API_KEY,
activated: body.provider !== 'LOCAL' || !hasEmail,
email: body.email,
password: body.password
? AuthService.hashPassword(body.password)

View File

@ -17,7 +17,7 @@ export class OrganizationService {
async createOrgAndUser(
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string }
) {
return this._organizationRepository.createOrgAndUser(body);
return this._organizationRepository.createOrgAndUser(body, this._notificationsService.hasEmailProvider());
}
addUserToOrg(

View File

@ -0,0 +1,5 @@
export interface EmailInterface {
name: string;
validateEnvKeys: string[];
sendEmail(to: string, subject: string, html: string, emailFromName: string, emailFromAddress: string): Promise<any>;
}

View File

@ -0,0 +1,9 @@
import { EmailInterface } from "./email.interface";
export class EmptyProvider implements EmailInterface {
name = 'no provider';
validateEnvKeys = [];
async sendEmail(to: string, subject: string, html: string) {
return `No email provider found, email was supposed to be sent to ${to} with subject: ${subject} and ${html}, html`;
}
}

View File

@ -0,0 +1,40 @@
import nodemailer from 'nodemailer';
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: +process.env.EMAIL_PORT!,
secure: process.env.EMAIL_SECURE === 'true',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
export class NodeMailerProvider implements EmailInterface {
name = 'nodemailer';
validateEnvKeys = [
'EMAIL_HOST',
'EMAIL_PORT',
'EMAIL_SECURE',
'EMAIL_USER',
'EMAIL_PASS',
];
async sendEmail(
to: string,
subject: string,
html: string,
emailFromName: string,
emailFromAddress: string
) {
const sends = await transporter.sendMail({
from:`${emailFromName} <${emailFromAddress}>`, // sender address
to: to, // list of receivers
subject: subject, // Subject line
text: html, // plain text body
html: html, // html body
});
return sends;
}
}

View File

@ -0,0 +1,19 @@
import { Resend } from 'resend';
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
const resend = new Resend(process.env.RESEND_API_KEY || 're_132');
export class ResendProvider implements EmailInterface {
name = 'resend';
validateEnvKeys = ['RESEND_API_KEY'];
async sendEmail(to: string, subject: string, html: string, emailFromName: string, emailFromAddress: string) {
const sends = await resend.emails.send({
from: `${emailFromName} <${emailFromAddress}>`,
to,
subject,
html,
});
return sends;
}
}

View File

@ -1,29 +1,52 @@
import { Injectable } from '@nestjs/common';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY || 're_132');
import { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';
import { ResendProvider } from '@gitroom/nestjs-libraries/emails/resend.provider';
import { EmptyProvider } from '@gitroom/nestjs-libraries/emails/empty.provider';
import { NodeMailerProvider } from '@gitroom/nestjs-libraries/emails/node.mailer.provider';
@Injectable()
export class EmailService {
emailService: EmailInterface;
constructor() {
this.emailService = this.selectProvider(process.env.EMAIL_PROVIDER!);
console.log('Email service provider:', this.emailService.name);
for (const key of this.emailService.validateEnvKeys) {
if (!process.env[key]) {
console.error(`Missing environment variable: ${key}`);
}
}
}
hasProvider() {
return !(this.emailService instanceof EmptyProvider);
}
selectProvider(provider: string) {
switch (provider) {
case 'resend':
return new ResendProvider();
case 'nodemailer':
return new NodeMailerProvider();
default:
return new EmptyProvider();
}
}
async sendEmail(to: string, subject: string, html: string) {
if (!process.env.RESEND_API_KEY) {
console.log('No Resend API Key found, skipping email sending');
return;
}
if (!process.env.EMAIL_FROM_ADDRESS || !process.env.EMAIL_FROM_NAME) {
console.log('Email sender information not found in environment variables');
console.log(
'Email sender information not found in environment variables'
);
return;
}
console.log('Sending email to', to);
const sends = await resend.emails.send({
from: `${process.env.EMAIL_FROM_NAME} <${process.env.EMAIL_FROM_ADDRESS}>`,
const sends = await this.emailService.sendEmail(
to,
subject,
html,
});
process.env.EMAIL_FROM_NAME,
process.env.EMAIL_FROM_ADDRESS
);
console.log(sends);
}
}

18
package-lock.json generated
View File

@ -50,6 +50,7 @@
"@types/md5": "^2.3.5",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/nodemailer": "^6.4.16",
"@types/remove-markdown": "^0.3.4",
"@types/sha256": "^0.2.2",
"@types/stripe": "^8.0.417",
@ -90,6 +91,7 @@
"nestjs-command": "^3.1.4",
"next": "14.2.3",
"next-plausible": "^3.12.0",
"nodemailer": "^6.9.15",
"nx": "19.7.2",
"openai": "^4.47.1",
"polotno": "^2.10.5",
@ -12973,6 +12975,14 @@
"@types/node": "*"
}
},
"node_modules/@types/nodemailer": {
"version": "6.4.16",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz",
"integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@ -29267,6 +29277,14 @@
"node": ">=6"
}
},
"node_modules/nodemailer": {
"version": "6.9.15",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz",
"integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",

View File

@ -70,6 +70,7 @@
"@types/md5": "^2.3.5",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/nodemailer": "^6.4.16",
"@types/remove-markdown": "^0.3.4",
"@types/sha256": "^0.2.2",
"@types/stripe": "^8.0.417",
@ -110,6 +111,7 @@
"nestjs-command": "^3.1.4",
"next": "14.2.3",
"next-plausible": "^3.12.0",
"nodemailer": "^6.9.15",
"nx": "19.7.2",
"openai": "^4.47.1",
"polotno": "^2.10.5",