Merged PR 635: アカウント登録完了通知 [U-101] の実装

## 概要
[Task3301: アカウント登録完了通知 [U-101] の実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3301)

- アカウント登録完了(認証完了)後にメール送信をする機能を追加しました。
  - 合わせてテスト修正をしています。

## レビューポイント
- テンプレートの適用は適切でしょうか。
- テスト修正で対象Sendgridメソッドを上書きしていますが対応として不自然な点はないでしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-12-19 02:00:35 +00:00
parent 11aa73f190
commit b2fef69ea9
11 changed files with 308 additions and 4 deletions

View File

@ -27,8 +27,8 @@
"debug.javascript.usePreview": false,
"editor.copyWithSyntaxHighlighting": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,

View File

@ -1,7 +1,7 @@
{
"terminal.integrated.shell.linux": "/bin/bash",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"source.fixAll.eslint": true
},
"eslint.format.enable": false,
"[javascript]": {

View File

@ -92,6 +92,11 @@ export const overrideSendgridService = <TService>(
text: string,
html: string,
) => Promise<void>;
sendMailWithU101?: (
context: Context,
customerMail: string,
customerAccountName: string,
) => Promise<void>;
},
): void => {
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
@ -113,6 +118,20 @@ export const overrideSendgridService = <TService>(
});
}
if (overrides.sendMailWithU101) {
Object.defineProperty(obj, obj.sendMailWithU101.name, {
value: overrides.sendMailWithU101,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU101.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.createMailContentFromEmailConfirm) {
Object.defineProperty(obj, obj.createMailContentFromEmailConfirm.name, {
value: overrides.createMailContentFromEmailConfirm,

View File

@ -18,6 +18,7 @@ import {
} from '../../../common/types/sort';
import { AdB2cUser } from '../../../gateways/adb2c/types/types';
import { ADB2C_SIGN_IN_TYPE } from '../../../constants';
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
export type SortCriteriaRepositoryMockValue = {
updateSortCriteria: SortCriteria | Error;
@ -80,6 +81,8 @@ export const makeUsersServiceMock = async (
})
.useMocker((token) => {
switch (token) {
case AccountsRepositoryService:
return {};
case UsersRepositoryService:
return makeUsersRepositoryMock(usersRepositoryMockValue);
case LicensesRepositoryService:

View File

@ -8,9 +8,11 @@ import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.r
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { AuthService } from '../auth/auth.service';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
@Module({
imports: [
AccountsRepositoryModule,
UsersRepositoryModule,
LicensesRepositoryModule,
SortCriteriaRepositoryModule,

View File

@ -94,6 +94,12 @@ describe('UsersService.confirmUser', () => {
});
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
// account id:1, user id: 2のトークン
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -141,6 +147,11 @@ describe('UsersService.confirmUser', () => {
if (!module) fail();
const token = 'invalid.id.token';
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
const context = makeContext(`uuidv4`, 'requestId');
await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST),
@ -175,6 +186,11 @@ describe('UsersService.confirmUser', () => {
email_verified: false,
});
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`, 'requestId');
@ -187,6 +203,11 @@ describe('UsersService.confirmUser', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`, 'requestId');

View File

@ -33,7 +33,6 @@ import {
UserNotFoundError,
} from '../../repositories/users/errors/types';
import {
ADB2C_SIGN_IN_TYPE,
LICENSE_EXPIRATION_THRESHOLD_DAYS,
MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER,
@ -51,6 +50,7 @@ import {
} from '../../repositories/licenses/errors/types';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
@Injectable()
export class UsersService {
@ -58,6 +58,7 @@ export class UsersService {
private readonly mailFrom: string;
private readonly appDomain: string;
constructor(
private readonly accountsRepository: AccountsRepositoryService,
private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
@ -98,6 +99,24 @@ export class UsersService {
context,
userId,
);
try {
const { company_name: companyName } =
await this.accountsRepository.findAccountById(
context,
decodedToken.accountId,
);
// アカウント認証が完了した旨をメール送信する
await this.sendgridService.sendMailWithU101(
context,
decodedToken.email,
companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {

View File

@ -11,6 +11,7 @@ import {
DEALER_NAME,
LICENSE_QUANTITY,
PO_NUMBER,
TOP_URL,
} from '../../templates/constants';
@Injectable()
@ -21,6 +22,8 @@ export class SendGridService {
private readonly mailFrom: string;
private readonly templateEmailVerifyHtml: string;
private readonly templateEmailVerifyText: string;
private readonly templateU101Html: string;
private readonly templateU101Text: string;
private readonly templateU105Html: string;
private readonly templateU105Text: string;
private readonly templateU106Html: string;
@ -48,6 +51,15 @@ export class SendGridService {
'utf-8',
);
this.templateU101Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_101.html`),
'utf-8',
);
this.templateU101Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_101.txt`),
'utf-8',
);
this.templateU105Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_105.html`),
'utf-8',
@ -178,6 +190,46 @@ export class SendGridService {
};
}
/**
* U-101使
* @param context
* @param customerMail
* @param customerAccountName
* @returns mail with u101
*/
async sendMailWithU101(
context: Context,
customerMail: string,
customerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
);
try {
const subject = 'Account Registered Notification [U-101]';
const html = this.templateU101Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(TOP_URL, this.appDomain);
const text = this.templateU101Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(TOP_URL, this.appDomain);
await this.sendMail(
context,
[customerMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
);
}
}
/**
* U-105使
* @param context

View File

@ -2,3 +2,4 @@ export const CUSTOMER_NAME = '$CUSTOMER_NAME$';
export const DEALER_NAME = '$DEALER_NAME$';
export const LICENSE_QUANTITY = '$LICENSE_QUANTITY$';
export const PO_NUMBER = '$PO_NUMBER$';
export const TOP_URL = '$TOP_URL$';

View File

@ -0,0 +1,128 @@
<html>
<head>
<title>Account Registered Notification [U-101]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
Thank you for choosing ODMS Cloud. Your account has been successfully
registered.
</p>
<p>
We have granted [100] trial licenses to your account which is valid for
30 days. During the trial, you can try all the features of ODMS Cloud.
</p>
<p>
If you wish to continue using ODMS Cloud after the trial period has
expired, please contact an authorized OM SYSTEM audio dealer to purchase
annual licenses. Various settings including dealer selection can be
configured within ODMS Cloud under the Account tab.
</p>
<p>
Please log in to ODMS Cloud to configure your user setting and and
verify the license expiration date.<br />
URL: $TOP_URL$
</p>
<p>
After you have selected a dealer, to request the number of licenses
please select Subscription tab. Licenses issued by dealers will be
stored in your license Inventory.
</p>
<p>
If you need assistance with ODMS Cloud, please contact your selected
approved OM SYSTEM audio dealer directly.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Vielen Dank, dass Sie sich für ODMS Cloud entschieden haben. Ihr Konto
wurde erfolgreich registriert.
</p>
<p>
Wir haben Ihrem Konto [100] Testlizenzen gewährt, die 30 Tage gültig
sind. Während der Testversion können Sie alle Funktionen von ODMS Cloud
ausprobieren.
</p>
<p>
Wenn Sie ODMS Cloud nach Ablauf des Testzeitraums weiterhin nutzen
möchten, wenden Sie sich bitte an einen autorisierten OM
SYSTEM-Audiohändler, um Jahreslizenzen zu erwerben. Verschiedene
Einstellungen, einschließlich der Händlerauswahl, können in der ODMS
Cloud auf der Registerkarte „Konto“ konfiguriert werden.
</p>
<p>
Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen
zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.<br />
URL: $TOP_URL$
</p>
<p>
Nachdem Sie einen Händler ausgewählt haben, wählen Sie bitte die
Registerkarte „Abonnement“ aus, um die Anzahl der Lizenzen anzufordern.
Von Händlern ausgestellte Lizenzen werden in Ihrem Lizenzbestand
gespeichert.
</p>
<p>
Wenn Sie Hilfe mit ODMS Cloud benötigen, wenden Sie sich bitte direkt an
Ihren ausgewählten zugelassenen OM SYSTEM-Audiohändler.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
Merci d'avoir choisi ODMS Cloud. Votre compte a été enregistré avec
succès.
</p>
<p>
Nous avons accordé [100] licences d'essai à votre compte, valables 30
jours. Pendant la période d'essai, vous pouvez essayer toutes les
fonctionnalités d'ODMS Cloud.
</p>
<p>
Si vous souhaitez continuer à utiliser ODMS Cloud après l'expiration de
la période d'essai, veuillez contacter un concessionnaire audio agréé OM
SYSTEM pour acheter des licences annuelles. Divers paramètres, y compris
la sélection du concessionnaire, peuvent être configurés dans ODMS Cloud
sous l'onglet Compte.
</p>
<p>
Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres
utilisateur et vérifier la date d'expiration de la licence.<br />
URL: $TOP_URL$
</p>
<p>
Après avoir sélectionné un concessionnaire, pour demander le nombre de
licences, veuillez sélectionner l'onglet Abonnement. Les licences
délivrées par les concessionnaires seront stockées dans votre inventaire
de licences.
</p>
<p>
Si vous avez besoin d'aide avec ODMS Cloud, veuillez contacter
directement votre concessionnaire audio OM SYSTEM agréé sélectionné.
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,59 @@
<English>
Dear $CUSTOMER_NAME$,
Thank you for choosing ODMS Cloud. Your account has been successfully registered.
We have granted [100] trial licenses to your account which is valid for 30 days. During the trial, you can try all the features of ODMS Cloud.
If you wish to continue using ODMS Cloud after the trial period has expired, please contact an authorized OM SYSTEM audio dealer to purchase annual licenses. Various settings including dealer selection can be configured within ODMS Cloud under the Account tab.
Please log in to ODMS Cloud to configure your user setting and and verify the license expiration date.
URL: $TOP_URL$
After you have selected a dealer, to request the number of licenses please select Subscription tab. Licenses issued by dealers will be stored in your license Inventory.
If you need assistance with ODMS Cloud, please contact your selected approved OM SYSTEM audio dealer directly.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Vielen Dank, dass Sie sich für ODMS Cloud entschieden haben. Ihr Konto wurde erfolgreich registriert.
Wir haben Ihrem Konto [100] Testlizenzen gewährt, die 30 Tage gültig sind. Während der Testversion können Sie alle Funktionen von ODMS Cloud ausprobieren.
Wenn Sie ODMS Cloud nach Ablauf des Testzeitraums weiterhin nutzen möchten, wenden Sie sich bitte an einen autorisierten OM SYSTEM-Audiohändler, um Jahreslizenzen zu erwerben. Verschiedene Einstellungen, einschließlich der Händlerauswahl, können in der ODMS Cloud auf der Registerkarte „Konto“ konfiguriert werden.
Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.
URL: $TOP_URL$
Nachdem Sie einen Händler ausgewählt haben, wählen Sie bitte die Registerkarte „Abonnement“ aus, um die Anzahl der Lizenzen anzufordern. Von Händlern ausgestellte Lizenzen werden in Ihrem Lizenzbestand gespeichert.
Wenn Sie Hilfe mit ODMS Cloud benötigen, wenden Sie sich bitte direkt an Ihren ausgewählten zugelassenen OM SYSTEM-Audiohändler.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Chère/Cher $CUSTOMER_NAME$,
Merci d'avoir choisi ODMS Cloud. Votre compte a été enregistré avec succès.
Nous avons accordé [100] licences d'essai à votre compte, valables 30 jours. Pendant la période d'essai, vous pouvez essayer toutes les fonctionnalités d'ODMS Cloud.
Si vous souhaitez continuer à utiliser ODMS Cloud après l'expiration de la période d'essai, veuillez contacter un concessionnaire audio agréé OM SYSTEM pour acheter des licences annuelles. Divers paramètres, y compris la sélection du concessionnaire, peuvent être configurés dans ODMS Cloud sous l'onglet Compte.
Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence.
URL: $TOP_URL$
Après avoir sélectionné un concessionnaire, pour demander le nombre de licences, veuillez sélectionner l'onglet Abonnement. Les licences délivrées par les concessionnaires seront stockées dans votre inventaire de licences.
Si vous avez besoin d'aide avec ODMS Cloud, veuillez contacter directement votre concessionnaire audio OM SYSTEM agréé sélectionné.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.