This commit is contained in:
Yassine 2025-06-24 05:23:47 +00:00 committed by GitHub
commit b7bc84c09b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 183 additions and 126 deletions

View File

@ -9,14 +9,11 @@
import path from 'path'; import path from 'path';
import { CacheModule } from '@nestjs/cache-manager'; import { CacheModule } from '@nestjs/cache-manager';
// eslint-disable-next-line import/order
import { MailerModule } from '@nestjs-modules/mailer';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core'; import { APP_GUARD } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { MongooseModule } from '@nestjs/mongoose'; import { MongooseModule } from '@nestjs/mongoose';
// eslint-disable-next-line import/order // eslint-disable-next-line import/order
import { MjmlAdapter } from '@nestjs-modules/mailer/dist/adapters/mjml.adapter';
import { CsrfGuard, CsrfModule } from '@tekuconcept/nestjs-csrf'; import { CsrfGuard, CsrfModule } from '@tekuconcept/nestjs-csrf';
import { redisStore } from 'cache-manager-redis-yet'; import { redisStore } from 'cache-manager-redis-yet';
import { import {
@ -24,7 +21,6 @@ import {
I18nOptions, I18nOptions,
QueryResolver, QueryResolver,
} from 'nestjs-i18n'; } from 'nestjs-i18n';
import SMTPTransport from 'nodemailer/lib/smtp-transport';
import { RedisClientOptions } from 'redis'; import { RedisClientOptions } from 'redis';
import { AnalyticsModule } from './analytics/analytics.module'; import { AnalyticsModule } from './analytics/analytics.module';
@ -40,6 +36,7 @@ import extraModules from './extra';
import { HelperModule } from './helper/helper.module'; import { HelperModule } from './helper/helper.module';
import { I18nModule } from './i18n/i18n.module'; import { I18nModule } from './i18n/i18n.module';
import { LoggerModule } from './logger/logger.module'; import { LoggerModule } from './logger/logger.module';
import { MailerModule } from './mailer/mailer.module';
import { MigrationModule } from './migration/migration.module'; import { MigrationModule } from './migration/migration.module';
import { NlpModule } from './nlp/nlp.module'; import { NlpModule } from './nlp/nlp.module';
import { PluginsModule } from './plugins/plugins.module'; import { PluginsModule } from './plugins/plugins.module';
@ -63,36 +60,7 @@ const i18nOptions: I18nOptions = {
@Module({ @Module({
imports: [ imports: [
...(config.emails.isEnabled MailerModule,
? [
MailerModule.forRoot({
transport: new SMTPTransport({
...config.emails.smtp,
logger: true,
debug: false,
}),
template: {
adapter: new MjmlAdapter(
'handlebars',
{
inlineCssEnabled: false,
},
{
handlebar: {},
},
),
dir: path.join(process.cwd(), 'dist', 'templates'),
options: {
context: {
appName: config.parameters.appName,
appUrl: config.uiBaseUrl,
},
},
},
defaults: { from: config.emails.from },
}),
]
: []),
MongooseModule.forRoot(config.mongo.uri, { MongooseModule.forRoot(config.mongo.uri, {
dbName: config.mongo.dbName, dbName: config.mongo.dbName,
connectionFactory: (connection) => { connectionFactory: (connection) => {

View File

@ -0,0 +1,78 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import path from 'path';
import { Global, Module } from '@nestjs/common';
import {
ISendMailOptions,
MAILER_OPTIONS,
MailerModule as NestjsMailerModule,
} from '@nestjs-modules/mailer';
import { MjmlAdapter } from '@nestjs-modules/mailer/dist/adapters/mjml.adapter';
import SMTPTransport from 'nodemailer/lib/smtp-transport';
import { config } from '@/config';
import { MailerService } from './mailer.service';
const mailerOptions = {
transport: new SMTPTransport({
...config.emails.smtp,
logger: true,
debug: false,
}),
template: {
adapter: new MjmlAdapter(
'handlebars',
{
inlineCssEnabled: false,
},
{
handlebar: {},
},
),
dir: path.join(process.cwd(), 'dist', 'templates'),
options: {
context: {
appName: config.parameters.appName,
appUrl: config.uiBaseUrl,
},
},
},
defaults: { from: config.emails.from },
};
@Global()
@Module({
imports: [
...(config.emails.isEnabled
? [NestjsMailerModule.forRoot(mailerOptions)]
: []),
],
providers: [
{
provide: MAILER_OPTIONS,
useValue: mailerOptions,
},
...(config.emails.isEnabled
? [MailerService]
: [
{
provide: MailerService,
useValue: {
sendMail(_options: ISendMailOptions) {
throw new Error('Email Service is not enabled');
},
},
},
]),
],
exports: [MailerService],
})
export class MailerModule {}

View File

@ -0,0 +1,19 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { MailerService as NestjsMailerService } from '@nestjs-modules/mailer';
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
export class MailerService extends NestjsMailerService {}

View File

@ -12,10 +12,11 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { BadRequestException } from '@nestjs/common/exceptions/bad-request.exception'; import { BadRequestException } from '@nestjs/common/exceptions/bad-request.exception';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ISendMailOptions, MailerService } from '@nestjs-modules/mailer'; import { ISendMailOptions } from '@nestjs-modules/mailer';
import { SentMessageInfo } from 'nodemailer'; import { SentMessageInfo } from 'nodemailer';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { MailerService } from '@/mailer/mailer.service';
import { getRandom } from '@/utils/helpers/safeRandom'; import { getRandom } from '@/utils/helpers/safeRandom';
import { installLanguageFixtures } from '@/utils/test/fixtures/language'; import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures } from '@/utils/test/fixtures/user'; import { installUserFixtures } from '@/utils/test/fixtures/user';

View File

@ -8,11 +8,12 @@
import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { ForbiddenException, NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ISendMailOptions, MailerService } from '@nestjs-modules/mailer'; import { ISendMailOptions } from '@nestjs-modules/mailer';
import { Session as ExpressSession } from 'express-session'; import { Session as ExpressSession } from 'express-session';
import { SentMessageInfo } from 'nodemailer'; import { SentMessageInfo } from 'nodemailer';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { MailerService } from '@/mailer/mailer.service';
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants'; import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
import { installLanguageFixtures } from '@/utils/test/fixtures/language'; import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installPermissionFixtures } from '@/utils/test/fixtures/permission'; import { installPermissionFixtures } from '@/utils/test/fixtures/permission';

View File

@ -7,10 +7,11 @@
*/ */
import { JwtModule, JwtService } from '@nestjs/jwt'; import { JwtModule, JwtService } from '@nestjs/jwt';
import { ISendMailOptions, MailerService } from '@nestjs-modules/mailer'; import { ISendMailOptions } from '@nestjs-modules/mailer';
import { SentMessageInfo } from 'nodemailer'; import { SentMessageInfo } from 'nodemailer';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { MailerService } from '@/mailer/mailer.service';
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants'; import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
import { import {
installInvitationFixtures, installInvitationFixtures,

View File

@ -6,19 +6,17 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
Inject, Inject,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
Optional,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
import { config } from '@/config'; import { config } from '@/config';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { MailerService } from '@/mailer/mailer.service';
import { BaseService } from '@/utils/generics/base-service'; import { BaseService } from '@/utils/generics/base-service';
import { InvitationCreateDto } from '../dto/invitation.dto'; import { InvitationCreateDto } from '../dto/invitation.dto';
@ -41,7 +39,7 @@ export class InvitationService extends BaseService<
@Inject(JwtService) private readonly jwtService: JwtService, @Inject(JwtService) private readonly jwtService: JwtService,
protected readonly i18n: I18nService, protected readonly i18n: I18nService,
public readonly languageService: LanguageService, public readonly languageService: LanguageService,
@Optional() private readonly mailerService?: MailerService, private readonly mailerService: MailerService,
) { ) {
super(repository); super(repository);
} }
@ -61,31 +59,28 @@ export class InvitationService extends BaseService<
*/ */
async create(dto: InvitationCreateDto): Promise<Invitation> { async create(dto: InvitationCreateDto): Promise<Invitation> {
const jwt = await this.sign({ ...dto }); const jwt = await this.sign({ ...dto });
if (this.mailerService) { try {
try { const defaultLanguage = await this.languageService.getDefaultLanguage();
const defaultLanguage = await this.languageService.getDefaultLanguage(); await this.mailerService.sendMail({
await this.mailerService.sendMail({ to: dto.email,
to: dto.email, template: 'invitation.mjml',
template: 'invitation.mjml', context: {
context: { appName: config.parameters.appName,
appName: config.parameters.appName, appUrl: config.uiBaseUrl,
appUrl: config.uiBaseUrl, token: jwt,
token: jwt, // TODO: Which language should we use?
// TODO: Which language should we use? t: (key: string) => this.i18n.t(key, { lang: defaultLanguage.code }),
t: (key: string) => },
this.i18n.t(key, { lang: defaultLanguage.code }), subject: this.i18n.t('invitation_subject'),
}, });
subject: this.i18n.t('invitation_subject'), } catch (e) {
}); this.logger.error(
} catch (e) { 'Could not send email',
this.logger.error( e.message,
'Could not send email', e.stack,
e.message, 'InvitationService',
e.stack, );
'InvitationService', throw new InternalServerErrorException('Could not send email');
);
throw new InternalServerErrorException('Could not send email');
}
} }
const newInvitation = await super.create({ ...dto, token: jwt }); const newInvitation = await super.create({ ...dto, token: jwt });
return { ...newInvitation, token: jwt }; return { ...newInvitation, token: jwt };

View File

@ -9,12 +9,13 @@
import { NotFoundException } from '@nestjs/common'; import { NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { getModelToken } from '@nestjs/mongoose'; import { getModelToken } from '@nestjs/mongoose';
import { ISendMailOptions, MailerService } from '@nestjs-modules/mailer'; import { ISendMailOptions } from '@nestjs-modules/mailer';
import { compareSync } from 'bcryptjs'; import { compareSync } from 'bcryptjs';
import { Model } from 'mongoose'; import { Model } from 'mongoose';
import { SentMessageInfo } from 'nodemailer'; import { SentMessageInfo } from 'nodemailer';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { MailerService } from '@/mailer/mailer.service';
import { installLanguageFixtures } from '@/utils/test/fixtures/language'; import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures, users } from '@/utils/test/fixtures/user'; import { installUserFixtures, users } from '@/utils/test/fixtures/user';
import { import {

View File

@ -6,15 +6,12 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
BadRequestException, BadRequestException,
Inject, Inject,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
NotFoundException, NotFoundException,
Optional,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
@ -24,6 +21,7 @@ import { config } from '@/config';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { MailerService } from '@/mailer/mailer.service';
import { UserRequestResetDto, UserResetPasswordDto } from '../dto/user.dto'; import { UserRequestResetDto, UserResetPasswordDto } from '../dto/user.dto';
@ -37,7 +35,7 @@ export class PasswordResetService {
private readonly userService: UserService, private readonly userService: UserService,
public readonly i18n: I18nService, public readonly i18n: I18nService,
public readonly languageService: LanguageService, public readonly languageService: LanguageService,
@Optional() private readonly mailerService?: MailerService, private readonly mailerService: MailerService,
) {} ) {}
public readonly jwtSignOptions: JwtSignOptions = { public readonly jwtSignOptions: JwtSignOptions = {
@ -60,31 +58,28 @@ export class PasswordResetService {
} }
const jwt = await this.sign({ ...dto }); const jwt = await this.sign({ ...dto });
if (this.mailerService) { try {
try { const defaultLanguage = await this.languageService.getDefaultLanguage();
const defaultLanguage = await this.languageService.getDefaultLanguage(); await this.mailerService.sendMail({
await this.mailerService.sendMail({ to: dto.email,
to: dto.email, template: 'password_reset.mjml',
template: 'password_reset.mjml', context: {
context: { appName: config.parameters.appName,
appName: config.parameters.appName, appUrl: config.uiBaseUrl,
appUrl: config.uiBaseUrl, token: jwt,
token: jwt, first_name: user.first_name,
first_name: user.first_name, t: (key: string) => this.i18n.t(key, { lang: defaultLanguage.code }),
t: (key: string) => },
this.i18n.t(key, { lang: defaultLanguage.code }), subject: this.i18n.t('password_reset_subject'),
}, });
subject: this.i18n.t('password_reset_subject'), } catch (e) {
}); this.logger.error(
} catch (e) { 'Could not send email',
this.logger.error( e.message,
'Could not send email', e.stack,
e.message, 'PasswordResetService',
e.stack, );
'PasswordResetService', throw new InternalServerErrorException('Could not send email');
);
throw new InternalServerErrorException('Could not send email');
}
} }
// TODO: hash the token before saving it // TODO: hash the token before saving it

View File

@ -6,10 +6,11 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import { ISendMailOptions, MailerService } from '@nestjs-modules/mailer'; import { ISendMailOptions } from '@nestjs-modules/mailer';
import { SentMessageInfo } from 'nodemailer'; import { SentMessageInfo } from 'nodemailer';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { MailerService } from '@/mailer/mailer.service';
import { installLanguageFixtures } from '@/utils/test/fixtures/language'; import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures, users } from '@/utils/test/fixtures/user'; import { installUserFixtures, users } from '@/utils/test/fixtures/user';
import { import {

View File

@ -6,13 +6,10 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
Inject, Inject,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
Optional,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
@ -21,6 +18,7 @@ import { config } from '@/config';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { MailerService } from '@/mailer/mailer.service';
import { UserCreateDto } from '../dto/user.dto'; import { UserCreateDto } from '../dto/user.dto';
@ -40,7 +38,7 @@ export class ValidateAccountService {
private logger: LoggerService, private logger: LoggerService,
private readonly i18n: I18nService, private readonly i18n: I18nService,
private readonly languageService: LanguageService, private readonly languageService: LanguageService,
@Optional() private readonly mailerService?: MailerService, private readonly mailerService: MailerService,
) {} ) {}
/** /**
@ -77,31 +75,28 @@ export class ValidateAccountService {
) { ) {
const confirmationToken = await this.sign({ email: dto.email }); const confirmationToken = await this.sign({ email: dto.email });
if (this.mailerService) { try {
try { const defaultLanguage = await this.languageService.getDefaultLanguage();
const defaultLanguage = await this.languageService.getDefaultLanguage(); await this.mailerService.sendMail({
await this.mailerService.sendMail({ to: dto.email,
to: dto.email, template: 'account_confirmation.mjml',
template: 'account_confirmation.mjml', context: {
context: { appName: config.parameters.appName,
appName: config.parameters.appName, appUrl: config.uiBaseUrl,
appUrl: config.uiBaseUrl, token: confirmationToken,
token: confirmationToken, first_name: dto.first_name,
first_name: dto.first_name, t: (key: string) => this.i18n.t(key, { lang: defaultLanguage.code }),
t: (key: string) => },
this.i18n.t(key, { lang: defaultLanguage.code }), subject: this.i18n.t('account_confirmation_subject'),
}, });
subject: this.i18n.t('account_confirmation_subject'), } catch (e) {
}); this.logger.error(
} catch (e) { 'Could not send email',
this.logger.error( e.message,
'Could not send email', e.stack,
e.message, 'ValidateAccount',
e.stack, );
'ValidateAccount', throw new InternalServerErrorException('Could not send email');
);
throw new InternalServerErrorException('Could not send email');
}
} }
} }

View File

@ -12,6 +12,7 @@ import { MongooseModule } from '@nestjs/mongoose';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { AttachmentModule } from '@/attachment/attachment.module'; import { AttachmentModule } from '@/attachment/attachment.module';
import { MailerModule } from '@/mailer/mailer.module';
import { LocalAuthController } from './controllers/auth.controller'; import { LocalAuthController } from './controllers/auth.controller';
import { ModelController } from './controllers/model.controller'; import { ModelController } from './controllers/model.controller';
@ -46,6 +47,7 @@ import { ValidateAccountService } from './services/validate-account.service';
@Module({ @Module({
imports: [ imports: [
MailerModule,
MongooseModule.forFeature([ MongooseModule.forFeature([
UserModel, UserModel,
ModelModel, ModelModel,