diff --git a/myteamwallet_backend/src/auth/auth.service.ts b/myteamwallet_backend/src/auth/auth.service.ts index 4a7ccec..6884669 100644 --- a/myteamwallet_backend/src/auth/auth.service.ts +++ b/myteamwallet_backend/src/auth/auth.service.ts @@ -192,6 +192,7 @@ export class AuthService { to: user.email, data: { hash, + firstName: user.firstName, }, }); } @@ -247,6 +248,7 @@ export class AuthService { to: email, data: { hash, + firstName: user.firstName, }, }); } diff --git a/myteamwallet_backend/src/mail/mail.service.spec.ts b/myteamwallet_backend/src/mail/mail.service.spec.ts new file mode 100644 index 0000000..9e33ddc --- /dev/null +++ b/myteamwallet_backend/src/mail/mail.service.spec.ts @@ -0,0 +1,61 @@ +import { ConfigService } from '@nestjs/config'; +import { MailerService } from '@nestjs-modules/mailer'; +import { MailService } from './mail.service'; + +describe('MailService', () => { + let service: MailService; + let sendMail: jest.Mock; + let configGet: jest.Mock; + + beforeEach(() => { + sendMail = jest.fn().mockResolvedValue(undefined); + configGet = jest.fn().mockReturnValue('https://app.example.com'); + + service = new MailService( + { sendMail } as unknown as MailerService, + { get: configGet } as unknown as ConfigService, + ); + }); + + it('sends the activation mail with the confirm-email link', async () => { + await service.userSignUp({ + to: 'user@example.com', + data: { hash: 'abc123', firstName: 'Max' }, + }); + + expect(sendMail).toHaveBeenCalledTimes(1); + const call = sendMail.mock.calls[0][0]; + expect(call.to).toBe('user@example.com'); + expect(call.template).toBe('activation'); + expect(call.context.url).toBe( + 'https://app.example.com/confirm-email/abc123', + ); + expect(call.context.firstName).toBe('Max'); + }); + + it('sends the reset-password mail with the password-change link', async () => { + await service.forgotPassword({ + to: 'user@example.com', + data: { hash: 'xyz789', firstName: 'Erika' }, + }); + + expect(sendMail).toHaveBeenCalledTimes(1); + const call = sendMail.mock.calls[0][0]; + expect(call.to).toBe('user@example.com'); + expect(call.template).toBe('reset-password'); + expect(call.context.url).toBe( + 'https://app.example.com/password-change/xyz789', + ); + expect(call.context.firstName).toBe('Erika'); + }); + + it('works without a firstName (optional personalization)', async () => { + await service.userSignUp({ + to: 'user@example.com', + data: { hash: 'abc123' }, + }); + + const call = sendMail.mock.calls[0][0]; + expect(call.context.firstName).toBeUndefined(); + }); +}); diff --git a/myteamwallet_backend/src/mail/mail.service.ts b/myteamwallet_backend/src/mail/mail.service.ts index 94ce813..a825da8 100644 --- a/myteamwallet_backend/src/mail/mail.service.ts +++ b/myteamwallet_backend/src/mail/mail.service.ts @@ -1,61 +1,57 @@ import { MailerService } from '@nestjs-modules/mailer'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { I18n, I18nRequestScopeService } from 'nestjs-i18n'; import { MailData } from './interfaces/mail-data.interface'; @Injectable() export class MailService { constructor( - @I18n() - private i18n: I18nRequestScopeService, private mailerService: MailerService, private configService: ConfigService, ) {} - async userSignUp(mailData: MailData<{ hash: string }>) { - return; + async userSignUp( + mailData: MailData<{ hash: string; firstName?: string | null }>, + ) { + const actionTitle = 'E-Mail bestätigen'; + const url = `${this.configService.get('app.frontendDomain')}/confirm-email/${ + mailData.data.hash + }`; + await this.mailerService.sendMail({ to: mailData.to, - subject: await this.i18n.t('common.confirmEmail'), - text: `${this.configService.get('app.frontendDomain')}/confirm-email/${ - mailData.data.hash - } ${await this.i18n.t('common.confirmEmail')}`, + subject: 'Bestätige deine E-Mail-Adresse', + text: `${url} ${actionTitle}`, template: 'activation', context: { - title: await this.i18n.t('common.confirmEmail'), - url: `${this.configService.get('app.frontendDomain')}/confirm-email/${ - mailData.data.hash - }`, - actionTitle: await this.i18n.t('common.confirmEmail'), - app_name: this.configService.get('app.name'), - text1: await this.i18n.t('confirm-email.text1'), - text2: await this.i18n.t('confirm-email.text2'), - text3: await this.i18n.t('confirm-email.text3'), + title: 'Bestätige deine E-Mail-Adresse', + year: new Date().getFullYear(), + firstName: mailData.data.firstName, + url, + actionTitle, }, }); } - async forgotPassword(mailData: MailData<{ hash: string }>) { - return; + async forgotPassword( + mailData: MailData<{ hash: string; firstName?: string | null }>, + ) { + const actionTitle = 'Passwort zurücksetzen'; + const url = `${this.configService.get('app.frontendDomain')}/password-change/${ + mailData.data.hash + }`; + await this.mailerService.sendMail({ to: mailData.to, - subject: await this.i18n.t('common.resetPassword'), - text: `${this.configService.get('app.frontendDomain')}/password-change/${ - mailData.data.hash - } ${await this.i18n.t('common.resetPassword')}`, + subject: actionTitle, + text: `${url} ${actionTitle}`, template: 'reset-password', context: { - title: await this.i18n.t('common.resetPassword'), - url: `${this.configService.get('app.frontendDomain')}/password-change/${ - mailData.data.hash - }`, - actionTitle: await this.i18n.t('common.resetPassword'), - app_name: this.configService.get('app.name'), - text1: await this.i18n.t('reset-password.text1'), - text2: await this.i18n.t('reset-password.text2'), - text3: await this.i18n.t('reset-password.text3'), - text4: await this.i18n.t('reset-password.text4'), + title: actionTitle, + year: new Date().getFullYear(), + firstName: mailData.data.firstName, + url, + actionTitle, }, }); }