## 概要 [Task3023: [ライセンスアラート改善]AzureAdB2Cアクセスの効率化](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3023) ADB2Cからユーザーを取得する際に、Redisによるキャッシュ保存・キャッシュからの取得を行う処理を実装しました。 ## レビューポイント 処理の妥当性などを全体的にお願いします。 ## UIの変更 なし ## 動作確認状況 ローカルで動作確認済み ## 補足 なし
370 lines
13 KiB
TypeScript
370 lines
13 KiB
TypeScript
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;
|
||
}
|