Skip to content

Commit

Permalink
[FEATURE] Envoi de l'email de creation de compte en asynchrone (PIX-1…
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Nov 14, 2024
2 parents bd43f7a + 295efe3 commit 1854e7f
Show file tree
Hide file tree
Showing 23 changed files with 756 additions and 412 deletions.
72 changes: 5 additions & 67 deletions api/lib/domain/services/mail-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import dayjs from 'dayjs';
import { config } from '../../../src/shared/config.js';
import { LOCALE } from '../../../src/shared/domain/constants.js';
import { tokenService } from '../../../src/shared/domain/services/token-service.js';
import { urlBuilder } from '../../../src/shared/infrastructure/utils/url-builder.js';
import { getEmailDefaultVariables } from '../../../src/shared/mail/domain/emails-default-variables.js';
import { mailer } from '../../../src/shared/mail/infrastructure/services/mailer.js';
import * as translations from '../../../translations/index.js';

Expand All @@ -17,10 +17,6 @@ const SCO_ACCOUNT_RECOVERY_TAG = 'SCO_ACCOUNT_RECOVERY';
const PIX_HOME_NAME_FRENCH_FRANCE = `pix${config.domain.tldFr}`;
const PIX_HOME_URL_FRENCH_FRANCE = `${config.domain.pix + config.domain.tldFr}`;
const PIX_APP_URL_FRENCH_FRANCE = `${config.domain.pixApp + config.domain.tldFr}`;
const PIX_APP_CONNECTION_URL_FRENCH_FRANCE = `${PIX_APP_URL_FRENCH_FRANCE}/connexion`;
const PIX_ORGA_HOME_URL_FRENCH_FRANCE = `${config.domain.pixOrga + config.domain.tldFr}`;
const PIX_CERTIF_HOME_URL_FRENCH_FRANCE = `${config.domain.pixCertif + config.domain.tldFr}`;
const HELPDESK_FRENCH_FRANCE = 'https://pix.fr/support';

// INTERNATIONAL
const PIX_HOME_NAME_INTERNATIONAL = `pix${config.domain.tldOrg}`;
Expand All @@ -29,52 +25,7 @@ const PIX_HOME_URL_INTERNATIONAL = {
fr: `${config.domain.pix + config.domain.tldOrg}/fr/`,
nl: `${config.domain.pix + config.domain.tldOrg}/nl-be/`,
};
const PIX_ORGA_HOME_URL_INTERNATIONAL = `${config.domain.pixOrga + config.domain.tldOrg}`;
const PIX_CERTIF_HOME_URL_INTERNATIONAL = `${config.domain.pixCertif + config.domain.tldOrg}`;
const PIX_APP_URL_INTERNATIONAL = `${config.domain.pixApp + config.domain.tldOrg}`;
const PIX_APP_CONNECTION_URL_INTERNATIONAL = {
en: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=en`,
es: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=es`,
fr: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=fr`,
nl: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=nl`,
};
const PIX_HELPDESK_URL_INTERNATIONAL = {
en: 'https://pix.org/en/support',
fr: 'https://pix.org/fr/support',
nl: 'https://pix.org/nl-be/support',
};

/**
* @param email
* @param locale
* @param redirectionUrl
* @returns {Promise<EmailingAttempt>}
*/
function sendAccountCreationEmail({ email, firstName, locale = FRENCH_FRANCE, token, redirectionUrl, i18n }) {
const mailerConfig = _getMailerConfig(locale);
const redirectUrl = redirectionUrl || mailerConfig.pixAppConnectionUrl;

const templateVariables = {
homeName: mailerConfig.homeName,
homeUrl: mailerConfig.homeUrl,
redirectionUrl: urlBuilder.getEmailValidationUrl({ locale, redirectUrl, token }),
helpdeskUrl: mailerConfig.helpdeskUrl,
displayNationalLogo: mailerConfig.displayNationalLogo,
...mailerConfig.translation['pix-account-creation-email'].params,
title: i18n.__({ phrase: 'pix-account-creation-email.params.title', locale }, { firstName }),
};
const pixName = mailerConfig.translation['email-sender-name']['pix-app'];
const accountCreationEmailSubject = mailerConfig.translation['pix-account-creation-email'].subject;

return mailer.sendEmail({
from: EMAIL_ADDRESS_NO_RESPONSE,
fromName: pixName,
to: email,
subject: accountCreationEmailSubject,
template: mailer.accountCreationTemplateId,
variables: templateVariables,
});
}

function sendCertificationResultEmail({
email,
Expand Down Expand Up @@ -410,30 +361,20 @@ function sendNotificationToOrganizationMembersForTargetProfileDetached({ email,
* @private
*/
function _getMailerConfig(locale) {
const defaultVariables = getEmailDefaultVariables(locale);

switch (locale) {
case FRENCH_SPOKEN:
case SPANISH_SPOKEN:
case ENGLISH_SPOKEN:
case DUTCH_SPOKEN:
return {
homeName: PIX_HOME_NAME_INTERNATIONAL,
homeUrl: PIX_HOME_URL_INTERNATIONAL[locale] ?? PIX_HOME_URL_INTERNATIONAL.en,
pixOrgaHomeUrl: PIX_ORGA_HOME_URL_INTERNATIONAL,
pixCertifHomeUrl: PIX_CERTIF_HOME_URL_INTERNATIONAL,
pixAppConnectionUrl: PIX_APP_CONNECTION_URL_INTERNATIONAL[locale] ?? PIX_APP_CONNECTION_URL_INTERNATIONAL.en,
helpdeskUrl: PIX_HELPDESK_URL_INTERNATIONAL[locale] ?? PIX_HELPDESK_URL_INTERNATIONAL.en,
displayNationalLogo: false,
...defaultVariables,
translation: translations[locale],
};
default:
return {
homeName: PIX_HOME_NAME_FRENCH_FRANCE,
homeUrl: PIX_HOME_URL_FRENCH_FRANCE,
pixOrgaHomeUrl: PIX_ORGA_HOME_URL_FRENCH_FRANCE,
pixCertifHomeUrl: PIX_CERTIF_HOME_URL_FRENCH_FRANCE,
pixAppConnectionUrl: PIX_APP_CONNECTION_URL_FRENCH_FRANCE,
helpdeskUrl: HELPDESK_FRENCH_FRANCE,
displayNationalLogo: true,
...defaultVariables,
translation: translations.fr,
};
}
Expand All @@ -456,7 +397,6 @@ function _formatUrlWithLocale(url, locale) {
}

const mailService = {
sendAccountCreationEmail,
sendAccountRecoveryEmail,
sendCertificationResultEmail,
sendOrganizationInvitationEmail,
Expand All @@ -471,7 +411,6 @@ const mailService = {

/**
* @typedef {Object} MailService
* @property {function} sendAccountCreationEmail
* @property {function} sendAccountRecoveryEmail
* @property {function} sendCertificationCenterInvitationEmail
* @property {function} sendCertificationResultEmail
Expand All @@ -485,7 +424,6 @@ const mailService = {
*/
export {
mailService,
sendAccountCreationEmail,
sendAccountRecoveryEmail,
sendCertificationCenterInvitationEmail,
sendCertificationResultEmail,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import lodash from 'lodash';
const { isNil } = lodash;

import { createAccountCreationEmail } from '../../../src/identity-access-management/domain/emails/create-account-creation.email.js';
import { User } from '../../../src/identity-access-management/domain/models/User.js';
import { STUDENT_RECONCILIATION_ERRORS } from '../../../src/shared/domain/constants.js';
import { EntityValidationError } from '../../../src/shared/domain/errors.js';
Expand All @@ -19,18 +20,17 @@ const createAndReconcileUserToOrganizationLearner = async function ({
userAttributes,
authenticationMethodRepository,
campaignRepository,
emailRepository,
emailValidationDemandRepository,
organizationLearnerRepository,
userRepository,
userToCreateRepository,
cryptoService,
mailService,
obfuscationService,
userReconciliationService,
userService,
passwordValidator,
userValidator,
i18n,
}) {
const campaign = await campaignRepository.getByCode(campaignCode);
if (!campaign) {
Expand Down Expand Up @@ -84,14 +84,16 @@ const createAndReconcileUserToOrganizationLearner = async function ({
if (!isUsernameMode) {
const redirectionUrl = urlBuilder.getCampaignUrl(locale, campaignCode);
const token = await emailValidationDemandRepository.save(createdUser.id);
await mailService.sendAccountCreationEmail({
email: createdUser.email,
firstName: createdUser.firstName,
locale,
token,
redirectionUrl,
i18n,
});

await emailRepository.sendEmailAsync(
createAccountCreationEmail({
locale,
email: createdUser.email,
firstName: createdUser.firstName,
token,
redirectionUrl,
}),
);
}
return createdUser;
};
Expand Down
2 changes: 2 additions & 0 deletions api/lib/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import * as writeCsvUtils from '../../../src/shared/infrastructure/utils/csv/wri
import * as dateUtils from '../../../src/shared/infrastructure/utils/date-utils.js';
import { injectDependencies } from '../../../src/shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../src/shared/infrastructure/utils/import-named-exports-from-directory.js';
import * as emailRepository from '../../../src/shared/mail/infrastructure/repositories/email.repository.js';
import * as certificationCenterInvitationService from '../../../src/team/domain/services/certification-center-invitation-service.js';
import { organizationInvitationService } from '../../../src/team/domain/services/organization-invitation.service.js';
import * as certificationCenterInvitationRepository from '../../../src/team/infrastructure/repositories/certification-center-invitation-repository.js';
Expand Down Expand Up @@ -254,6 +255,7 @@ const dependencies = {
dataProtectionOfficerRepository,
dateUtils,
divisionRepository,
emailRepository,
emailValidationDemandRepository,
finalizedSessionRepository,
flashAlgorithmConfigurationRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { urlBuilder } from '../../../shared/infrastructure/utils/url-builder.js';
import { EmailFactory } from '../../../shared/mail/domain/models/EmailFactory.js';
import { mailer } from '../../../shared/mail/infrastructure/services/mailer.js';

export function createAccountCreationEmail({ locale, email, firstName, token, redirectionUrl }) {
const factory = new EmailFactory({ app: 'pix-app', locale });

const { i18n, defaultVariables } = factory;

const redirectUrl = redirectionUrl || defaultVariables.pixAppConnectionUrl;

return factory.buildEmail({
template: mailer.accountCreationTemplateId,
subject: i18n.__('pix-account-creation-email.subject'),
to: email,
variables: {
homeName: defaultVariables.homeName,
homeUrl: defaultVariables.homeUrl,
helpdeskUrl: defaultVariables.helpdeskUrl,
displayNationalLogo: defaultVariables.displayNationalLogo,
askForHelp: i18n.__('pix-account-creation-email.params.askForHelp'),
disclaimer: i18n.__('pix-account-creation-email.params.disclaimer'),
doNotAnswer: i18n.__('pix-account-creation-email.params.doNotAnswer'),
goToPix: i18n.__('pix-account-creation-email.params.goToPix'),
helpdeskLinkLabel: i18n.__('pix-account-creation-email.params.helpdeskLinkLabel'),
moreOn: i18n.__('pix-account-creation-email.params.moreOn'),
pixPresentation: i18n.__('pix-account-creation-email.params.pixPresentation'),
subtitle: i18n.__('pix-account-creation-email.params.subtitle'),
subtitleDescription: i18n.__('pix-account-creation-email.params.subtitleDescription'),
title: i18n.__('pix-account-creation-email.params.title', { firstName }),
redirectionUrl: urlBuilder.getEmailValidationUrl({ locale, redirectUrl, token }),
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { withTransaction } from '../../../shared/domain/DomainTransaction.js';
import { AlreadyRegisteredEmailError } from '../../../shared/domain/errors.js';
import { EntityValidationError } from '../../../shared/domain/errors.js';
import { urlBuilder } from '../../../shared/infrastructure/utils/url-builder.js';
import { createAccountCreationEmail } from '../emails/create-account-creation.email.js';

/**
* @param {Object} params
Expand All @@ -27,15 +28,14 @@ const createUser = withTransaction(async function ({
user,
authenticationMethodRepository,
campaignRepository,
emailRepository,
emailValidationDemandRepository,
userRepository,
userToCreateRepository,
cryptoService,
mailService,
userService,
userValidator,
passwordValidator,
i18n,
}) {
await _assertValidData({
password,
Expand Down Expand Up @@ -68,14 +68,16 @@ const createUser = withTransaction(async function ({
}

const token = await emailValidationDemandRepository.save(savedUser.id);
await mailService.sendAccountCreationEmail({
email: savedUser.email,
firstName: savedUser.firstName,
locale: localeFromHeader,
token,
redirectionUrl,
i18n,
});

await emailRepository.sendEmailAsync(
createAccountCreationEmail({
locale: localeFromHeader,
email: savedUser.email,
firstName: savedUser.firstName,
token,
redirectionUrl,
}),
);

return savedUser;
});
Expand Down
2 changes: 2 additions & 0 deletions api/src/identity-access-management/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as userLoginRepository from '../../../shared/infrastructure/repositorie
import * as codeUtils from '../../../shared/infrastructure/utils/code-utils.js';
import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
import * as emailRepository from '../../../shared/mail/infrastructure/repositories/email.repository.js';
import { accountRecoveryDemandRepository } from '../../infrastructure/repositories/account-recovery-demand.repository.js';
import * as authenticationMethodRepository from '../../infrastructure/repositories/authentication-method.repository.js';
import { emailValidationDemandRepository } from '../../infrastructure/repositories/email-validation-demand.repository.js';
Expand Down Expand Up @@ -48,6 +49,7 @@ const repositories = {
campaignRepository,
campaignToJoinRepository: campaignRepositories.campaignToJoinRepository,
emailValidationDemandRepository,
emailRepository,
eventLoggingJobRepository,
oidcProviderRepository,
organizationLearnerRepository,
Expand Down
14 changes: 14 additions & 0 deletions api/src/shared/mail/application/jobs/send-email.job-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { JobController } from '../../../application/jobs/job-controller.js';
import { Email } from '../../domain/models/Email.js';
import * as emailRepository from '../../infrastructure/repositories/email.repository.js';

export class SendEmailJobController extends JobController {
constructor() {
super('SendEmailJob');
}

async handle({ data, dependencies = { emailRepository } }) {
const email = new Email(data);
await dependencies.emailRepository.sendEmail(email);
}
}
64 changes: 64 additions & 0 deletions api/src/shared/mail/domain/emails-default-variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { config } from '../../config.js';
import { LOCALE } from '../../domain/constants.js';

// FRENCH_FRANCE
const PIX_HOME_URL_FRENCH_FRANCE = `${config.domain.pix + config.domain.tldFr}`;
const PIX_APP_URL_FRENCH_FRANCE = `${config.domain.pixApp + config.domain.tldFr}`;
const PIX_ORGA_HOME_URL_FRENCH_FRANCE = `${config.domain.pixOrga + config.domain.tldFr}`;
const PIX_CERTIF_HOME_URL_FRENCH_FRANCE = `${config.domain.pixCertif + config.domain.tldFr}`;

const PIX_APP_CONNECTION_URL_FRENCH_FRANCE = `${PIX_APP_URL_FRENCH_FRANCE}/connexion`;
const HELPDESK_FRENCH_FRANCE = 'https://pix.fr/support';

// INTERNATIONAL
const PIX_HOME_URL_INTERNATIONAL_BASE = `${config.domain.pix + config.domain.tldOrg}`;
const PIX_APP_URL_INTERNATIONAL = `${config.domain.pixApp + config.domain.tldOrg}`;
const PIX_ORGA_HOME_URL_INTERNATIONAL = `${config.domain.pixOrga + config.domain.tldOrg}`;
const PIX_CERTIF_HOME_URL_INTERNATIONAL = `${config.domain.pixCertif + config.domain.tldOrg}`;

const PIX_HOME_URL_INTERNATIONAL = {
en: `${PIX_HOME_URL_INTERNATIONAL_BASE}/en/`,
es: `${PIX_HOME_URL_INTERNATIONAL_BASE}/en/`,
fr: `${PIX_HOME_URL_INTERNATIONAL_BASE}/fr/`,
nl: `${PIX_HOME_URL_INTERNATIONAL_BASE}/nl-be/`,
};
const PIX_APP_CONNECTION_URL_INTERNATIONAL = {
en: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=en`,
es: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=es`,
fr: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=fr`,
nl: `${PIX_APP_URL_INTERNATIONAL}/connexion/?lang=nl`,
};
const PIX_HELPDESK_URL_INTERNATIONAL = {
en: `${PIX_HOME_URL_INTERNATIONAL['en']}support`,
es: `${PIX_HOME_URL_INTERNATIONAL['es']}support`,
fr: `${PIX_HOME_URL_INTERNATIONAL['fr']}support`,
nl: `${PIX_HOME_URL_INTERNATIONAL['nl']}support`,
};

export function getEmailDefaultVariables(locale) {
switch (locale) {
case LOCALE.FRENCH_SPOKEN:
case LOCALE.SPANISH_SPOKEN:
case LOCALE.ENGLISH_SPOKEN:
case LOCALE.DUTCH_SPOKEN:
return {
homeName: `pix${config.domain.tldOrg}`,
homeUrl: PIX_HOME_URL_INTERNATIONAL[locale] ?? PIX_HOME_URL_INTERNATIONAL.en,
pixOrgaHomeUrl: PIX_ORGA_HOME_URL_INTERNATIONAL,
pixCertifHomeUrl: PIX_CERTIF_HOME_URL_INTERNATIONAL,
pixAppConnectionUrl: PIX_APP_CONNECTION_URL_INTERNATIONAL[locale] ?? PIX_APP_CONNECTION_URL_INTERNATIONAL.en,
helpdeskUrl: PIX_HELPDESK_URL_INTERNATIONAL[locale] ?? PIX_HELPDESK_URL_INTERNATIONAL.en,
displayNationalLogo: false,
};
default:
return {
homeName: `pix${config.domain.tldFr}`,
homeUrl: PIX_HOME_URL_FRENCH_FRANCE,
pixOrgaHomeUrl: PIX_ORGA_HOME_URL_FRENCH_FRANCE,
pixCertifHomeUrl: PIX_CERTIF_HOME_URL_FRENCH_FRANCE,
pixAppConnectionUrl: PIX_APP_CONNECTION_URL_FRENCH_FRANCE,
helpdeskUrl: HELPDESK_FRENCH_FRANCE,
displayNationalLogo: true,
};
}
}
Loading

0 comments on commit 1854e7f

Please sign in to comment.