oura.a 7c16e7c358 Merged PR 559: ライセンスアラート処理実装(メール内容固定)
## 概要
[Task3021: ライセンスアラート処理実装(メール内容固定)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3021)

ライセンスアラート処理を実装しました。

## レビューポイント
取得している情報に過不足はないか。
処理の構成に問題がないか。
※redis対応は別タスクとなりますので、adb2cへのアクセス効率はレビュー対象外でお願いします
※メールの内容は別タスクで作成しますので、レビュー対象外でお願いします。

## UIの変更
なし

## 動作確認状況
ローカルで動作確認済み、UT実施済み

## 補足
UTでメールを送信した、していないを判断する方法が分からず、ひとまずconsoleログの出力の有無で判断しています。
2023-11-10 07:57:18 +00:00

348 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { app, InvocationContext, Timer } from "@azure/functions";
import { Between, DataSource, In, IsNull, MoreThan, Not } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import {
ADB2C_SIGN_IN_TYPE,
LICENSE_ALLOCATED_STATUS,
TIERS,
} from "../constants";
import * as dotenv from "dotenv";
import { License } from "../entity/license.entity";
import {
DateWithDayEndTime,
DateWithZeroTime,
ExpirationThresholdDate,
} from "../common/types/types";
import { getMailFrom } from "../common/getEnv/getEnv";
import { AdB2cService } from "../adb2c/adb2c.service";
import { SendGridService } from "../sendgrid/sendgrid.service";
export async function licenseAlertProcessing(
context: InvocationContext,
datasource: DataSource,
sendgrid: SendGridService,
adb2c: AdB2cService
) {
context.log("[IN]licenseAlertProcessing");
const mailFrom = getMailFrom();
const accountRepository = datasource.getRepository(Account);
// 第五のアカウントを取得
const accounts = await accountRepository.find({
where: {
tier: TIERS.TIER5,
},
relations: {
primaryAdminUser: true,
secondaryAdminUser: true,
},
});
const licenseRepository = datasource.getRepository(License);
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
const currentDateWithZeroTime = new DateWithZeroTime();
const currentDateWithDayEndTime = new DateWithDayEndTime();
const sendTargetAccounts = [] as accountInfo[];
const counts = async () => {
for (const account of accounts) {
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
const allocatableLicenseWithMargin = await licenseRepository.count({
where: [
{
account_id: account.id,
status: In([
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
LICENSE_ALLOCATED_STATUS.REUSABLE,
]),
expiry_date: MoreThan(expiringSoonDate),
},
{
account_id: account.id,
status: In([
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
LICENSE_ALLOCATED_STATUS.REUSABLE,
]),
expiry_date: IsNull(),
},
],
});
// 有効期限が現在日付からしきい値以内のライセンス数を取得する
const expiringSoonLicense = await licenseRepository.count({
where: {
account_id: account.id,
expiry_date: Between(currentDate, expiringSoonDate),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED),
},
});
// shortage算出
let shortage = allocatableLicenseWithMargin - expiringSoonLicense;
shortage = shortage >= 0 ? 0 : Math.abs(shortage);
// AutoRenewが未チェックかつ、有効期限当日のライセンスが割り当てられているユーザー数を取得
const userCount = await licenseRepository.count({
where: [
{
account_id: account.id,
expiry_date: Between(
currentDateWithZeroTime,
currentDateWithDayEndTime
),
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
user: {
auto_renew: false,
},
},
],
relations: {
user: true,
},
});
// 上で取得したshortageとユーザー数のどちらかが1以上ならプライマリ、セカンダリ管理者、親企業名を保持する
// shortageとユーザー数のどちらかが1以上 = アラートメールを送る必要がある)
let primaryAdminExternalId: string | undefined;
let secondaryAdminExternalId: string | undefined;
let parentCompanyName: string | undefined;
if (shortage !== 0 || userCount !== 0) {
primaryAdminExternalId = account.primaryAdminUser
? account.primaryAdminUser.external_id
: undefined;
secondaryAdminExternalId = account.secondaryAdminUser
? account.secondaryAdminUser.external_id
: undefined;
// 第五のアカウントを取得
// strictNullChecks対応
if (account.parent_account_id) {
const parent = await accountRepository.findOne({
where: {
id: account.parent_account_id,
},
});
parentCompanyName = parent?.company_name;
}
} else {
primaryAdminExternalId = undefined;
secondaryAdminExternalId = undefined;
parentCompanyName = undefined;
}
sendTargetAccounts.push({
accountId: account.id,
companyName: account.company_name,
parentCompanyName: parentCompanyName,
shortage: shortage,
userCountOfLicenseExpiringSoon: userCount,
primaryAdminExternalId: primaryAdminExternalId,
secondaryAdminExternalId: secondaryAdminExternalId,
primaryAdminEmail: undefined,
secondaryAdminEmail: undefined,
});
}
};
await counts();
// ADB2Cからユーザーを取得する用の外部ID配列を作成
const externalIds = [] as string[];
sendTargetAccounts.map((x) => {
if (x.primaryAdminExternalId) {
externalIds.push(x.primaryAdminExternalId);
}
if (x.secondaryAdminExternalId) {
externalIds.push(x.secondaryAdminExternalId);
}
});
const adb2cUsers = await adb2c.getUsers(externalIds);
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
sendTargetAccounts.map((info) => {
const primaryAdminUser = adb2cUsers.find(
(adb2c) => info.primaryAdminExternalId === adb2c.id
);
if (primaryAdminUser) {
const primaryAdminMail = primaryAdminUser.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
)?.issuerAssignedId;
if (primaryAdminMail) {
info.primaryAdminEmail = primaryAdminMail;
}
const secondaryAdminUser = adb2cUsers.find(
(adb2c) => info.secondaryAdminExternalId === adb2c.id
);
if (secondaryAdminUser) {
const secondaryAdminMail = secondaryAdminUser.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
)?.issuerAssignedId;
if (secondaryAdminMail) {
info.secondaryAdminEmail = secondaryAdminMail;
}
}
}
});
const sendMail = async () => {
for (const targetAccount of sendTargetAccounts) {
// プライマリ管理者が入っているかチェック
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
if (targetAccount.primaryAdminExternalId) {
// メール送信
// strictNullChecks対応
if (targetAccount.primaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseShortage();
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
} catch {
context.log(
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
}
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (targetAccount.secondaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseShortage();
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
);
} catch {
context.log(
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
);
}
}
}
}
// ライセンス失効警告メール
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseExpiringSoon();
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
} catch {
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
}
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (targetAccount.secondaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseExpiringSoon();
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
);
} catch {
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
);
}
}
}
}
}
}
}
};
await sendMail();
context.log("[OUT]licenseAlertProcessing");
}
export async function licenseAlert(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
context.log("[IN]licenseAlert");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License],
});
await datasource.initialize();
const adb2c = new AdB2cService();
const sendgrid = new SendGridService();
try {
await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
} catch (e) {
context.log("licenseAlertProcessing failed");
context.error(e);
} finally {
await datasource.destroy();
context.log("[OUT]licenseAlert");
}
}
app.timer("licenseAlert", {
schedule: "0 */1 * * * *",
handler: licenseAlert,
});
class accountInfo {
accountId: number;
companyName: string;
parentCompanyName: string | undefined;
shortage: number;
userCountOfLicenseExpiringSoon: number;
primaryAdminExternalId: string | undefined;
secondaryAdminExternalId: string | undefined;
primaryAdminEmail: string | undefined;
secondaryAdminEmail: string | undefined;
}