masaaki fee99a0974 Merged PR 583: [ライセンスアラート改善]AzureAdB2Cアクセスの効率化
## 概要
[Task3023: [ライセンスアラート改善]AzureAdB2Cアクセスの効率化](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3023)

ADB2Cからユーザーを取得する際に、Redisによるキャッシュ保存・キャッシュからの取得を行う処理を実装しました。

## レビューポイント
処理の妥当性などを全体的にお願いします。

## UIの変更
なし

## 動作確認状況
ローカルで動作確認済み

## 補足
なし
2023-12-01 01:39:18 +00:00

370 lines
13 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 { createMailContentOfLicenseShortage } from "../sendgrid/mailContents/U103ShortageAlert";
import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents/U104ExpiringSoonAlert";
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
import { getMailFrom } from "../common/getEnv/getEnv";
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(context, externalIds);
if (!adb2cUsers) {
context.log("Target user not found");
context.log("[OUT]licenseAlertProcessing");
return;
}
// 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 createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
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 createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
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 createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
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 createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
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 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;
}