水本 祐希 d7bb56af54 Merged PR 580: [Sp20]既存APIのログを強化(外部連携API以外)
## 概要
[Task2295: [Sp20]既存APIのログを強化(外部連携API以外)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2295)

- 何をどう変更したか、追加したライブラリなど
誰が操作したのかを追えるようにログを強化

## レビューポイント
- 特にレビューしてほしい箇所
特になし

## 動作確認状況
- ユニットテスト
2023-11-17 02:54:18 +00:00

1110 lines
33 KiB
TypeScript

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { isVerifyError, verify } from '../../common/jwt';
import { getPublicKey } from '../../common/jwt/jwt';
import { makePassword } from '../../common/password/password';
import {
SortDirection,
TaskListSortableAttribute,
isSortDirection,
isTaskListSortableAttribute,
} from '../../common/types/sort';
import {
AdB2cService,
ConflictError,
isConflictError,
} from '../../gateways/adb2c/adb2c.service';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/sort_criteria.repository.service';
import {
User as EntityUser,
newUser,
} from '../../repositories/users/entity/user.entity';
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { GetRelationsResponse, User } from './types/types';
import {
AuthorIdAlreadyExistsError,
EmailAlreadyVerifiedError,
EncryptionPasswordNeedError,
InvalidRoleChangeError,
UpdateTermsVersionNotSetError,
UserNotFoundError,
} from '../../repositories/users/errors/types';
import {
ADB2C_SIGN_IN_TYPE,
LICENSE_EXPIRATION_THRESHOLD_DAYS,
MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants';
import { DateWithZeroTime } from '../licenses/types/types';
import { Context } from '../../common/log';
import { UserRoles } from '../../common/types/role';
import {
LicenseAlreadyDeallocatedError,
LicenseExpiredError,
LicenseUnavailableError,
} from '../../repositories/licenses/errors/types';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@Injectable()
export class UsersService {
private readonly logger = new Logger(UsersService.name);
private readonly mailFrom: string;
private readonly appDomain: string;
constructor(
private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService,
private readonly sendgridService: SendGridService,
) {
this.mailFrom = this.configService.getOrThrow<string>('MAIL_FROM');
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
}
/**
* Confirms user
* @param token ユーザ仮登録時に払いだされるトークン
*/
async confirmUser(context: Context, token: string): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.confirmUser.name}`,
);
const pubKey = getPublicKey(this.configService);
const decodedToken = verify<{
accountId: number;
userId: number;
email: string;
}>(token, pubKey);
if (isVerifyError(decodedToken)) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.BAD_REQUEST,
);
}
try {
// トランザクションで取得と更新をまとめる
const userId = decodedToken.userId;
await this.usersRepository.updateUserVerifiedAndCreateTrialLicense(
userId,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case EmailAlreadyVerifiedError:
throw new HttpException(
makeErrorResponse('E010202'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
/**
* Creates user
* @param accessToken
* @param name
* @param role
* @param email
* @param autoRenew
* @param licenseAlert
* @param notification
* @param [authorId]
* @param [encryption]
* @param [encryptionPassword]
* @param [prompt]
* @returns void
*/
async createUser(
context: Context,
externalId: string,
name: string,
role: UserRoles,
email: string,
autoRenew: boolean,
licenseAlert: boolean,
notification: boolean,
authorId?: string | undefined,
encryption?: boolean | undefined,
encryptionPassword?: string | undefined,
prompt?: boolean | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` +
`externalId: ${externalId}, ` +
`role: ${role}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlert: ${licenseAlert}, ` +
`notification: ${notification}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`,
);
//DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser;
try {
adminUser = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const accountId = adminUser.account_id;
//authorIdが重複していないかチェックする
if (authorId) {
let isAuthorIdDuplicated = false;
try {
isAuthorIdDuplicated = await this.usersRepository.existsAuthorId(
accountId,
authorId,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
if (isAuthorIdDuplicated) {
throw new HttpException(
makeErrorResponse('E010302'),
HttpStatus.BAD_REQUEST,
);
}
}
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
//Azure AD B2Cにユーザーを新規登録する
let externalUser: { sub: string } | ConflictError;
try {
// idpにユーザーを作成
externalUser = await this.adB2cService.createUser(
context,
email,
ramdomPassword,
name,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
throw new HttpException(
makeErrorResponse('E010301'),
HttpStatus.BAD_REQUEST,
);
}
//Azure AD B2Cに登録したユーザー情報のID(sub)と受け取った情報を使ってDBにユーザーを登録する
let newUser: EntityUser;
try {
//roleに応じてユーザー情報を作成する
const newUserInfo = this.createNewUserInfo(
context,
role,
accountId,
externalUser.sub,
autoRenew,
licenseAlert,
notification,
authorId,
encryption,
encryptionPassword,
prompt,
);
// ユーザ作成
newUser = await this.usersRepository.createNormalUser(newUserInfo);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}]create user failed`);
//リカバリー処理
//Azure AD B2Cに登録したユーザー情報を削除する
await this.deleteB2cUser(externalUser.sub, context);
switch (e.code) {
case 'ER_DUP_ENTRY':
//AuthorID重複エラー
throw new HttpException(
makeErrorResponse('E010302'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
//Email送信用のコンテンツを作成する
try {
// メールの内容を構成
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
context,
accountId,
newUser.id,
email,
);
//SendGridAPIを呼び出してメールを送信する
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] create user failed`);
//リカバリー処理
//Azure AD B2Cに登録したユーザー情報を削除する
await this.deleteB2cUser(externalUser.sub, context);
// DBからユーザーを削除する
await this.deleteUser(newUser.id, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`,
);
return;
}
// Azure AD B2Cに登録したユーザー情報を削除する
// TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
private async deleteB2cUser(externalUserId: string, context: Context) {
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.getTrackingId()}] delete externalUser: ${externalUserId}`,
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`,
);
}
}
// DBに登録したユーザー情報を削除する
private async deleteUser(userId: number, context: Context) {
try {
await this.usersRepository.deleteNormalUser(userId);
this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}`,
);
}
}
// roleを受け取って、roleに応じたnewUserを作成して返却する
private createNewUserInfo(
context: Context,
role: UserRoles,
accountId: number,
externalId: string,
autoRenew: boolean,
licenseAlert: boolean,
notification: boolean,
authorId?: string | undefined,
encryption?: boolean | undefined,
encryptionPassword?: string | undefined,
prompt?: boolean | undefined,
): newUser {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createNewUserInfo.name
} | params: { ` +
`role: ${role}, ` +
`accountId: ${accountId}, ` +
`authorId: ${authorId}, ` +
`externalId: ${externalId}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlert: ${licenseAlert}, ` +
`notification: ${notification}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`,
);
switch (role) {
case USER_ROLES.NONE:
case USER_ROLES.TYPIST:
return {
account_id: accountId,
external_id: externalId,
auto_renew: autoRenew,
license_alert: licenseAlert,
notification,
role,
accepted_dpa_version: null,
accepted_eula_version: null,
encryption: false,
encryption_password: null,
prompt: false,
author_id: null,
};
case USER_ROLES.AUTHOR:
return {
account_id: accountId,
external_id: externalId,
auto_renew: autoRenew,
license_alert: licenseAlert,
notification,
role,
author_id: authorId ?? null,
encryption: encryption ?? false,
encryption_password: encryptionPassword ?? null,
prompt: prompt ?? false,
accepted_dpa_version: null,
accepted_eula_version: null,
};
default:
//不正なroleが指定された場合はログを出力してエラーを返す
this.logger.error(
`[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
/**
* confirm User And Init Password
* @param token ユーザ仮登録時に払いだされるトークン
*/
async confirmUserAndInitPassword(
context: Context,
token: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.confirmUserAndInitPassword.name
}`,
);
const pubKey = getPublicKey(this.configService);
const decodedToken = verify<{
accountId: number;
userId: number;
email: string;
}>(token, pubKey);
if (isVerifyError(decodedToken)) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.BAD_REQUEST,
);
}
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
const { userId, email } = decodedToken;
try {
// ユーザー情報からAzure AD B2CのIDを特定する
const user = await this.usersRepository.findUserById(userId);
const extarnalId = user.external_id;
// パスワードを変更する
await this.adB2cService.changePassword(
context,
extarnalId,
ramdomPassword,
);
// ユーザを認証済みにする
await this.usersRepository.updateUserVerified(userId);
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
const subject = 'A temporary password has been issued.';
const text = 'temporary password: ' + ramdomPassword;
const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}</a><br>temporary password: ${ramdomPassword}`;
// メールを送信
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case EmailAlreadyVerifiedError:
throw new HttpException(
makeErrorResponse('E010202'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
}
/**
* Get Users
* @param accessToken
* @returns users
*/
async getUsers(context: Context, externalId: string): Promise<User[]> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`);
try {
// DBから同一アカウントのユーザ一覧を取得する
const dbUsers = await this.usersRepository.findSameAccountUsers(
externalId,
);
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
const externalIds = dbUsers.map((x) => x.external_id);
const trackingId = new Context(context.trackingId);
const adb2cUsers = await this.adB2cService.getUsers(
// TODO: 外部連携以外のログ強化時に、ContollerからContextを取得するように修正する
trackingId,
externalIds,
);
// DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出
const users = dbUsers.map((dbUser): User => {
// ユーザーの所属グループ名を取得する
const userGroupMembers =
dbUser.userGroupMembers !== null ? dbUser.userGroupMembers : [];
//所属グループ名の配列にする
const groupNames = userGroupMembers.flatMap((userGroupMember) =>
userGroupMember.userGroup ? [userGroupMember.userGroup.name] : [],
);
const adb2cUser = adb2cUsers.find(
(user) => user.id === dbUser.external_id,
);
// メールアドレスを取得する
const mail = adb2cUser?.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
)?.issuerAssignedId;
//メールアドレスが取得できない場合はエラー
if (!mail) {
throw new Error('mail not found.');
}
let status = USER_LICENSE_STATUS.NORMAL;
// ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する
// ライセンスが存在しない場合は、undefinedのままとする
let expiration: string | undefined = undefined;
let remaining: number | undefined = undefined;
if (dbUser.license) {
// 有効期限日付 YYYY/MM/DD
const expiry_date = dbUser.license.expiry_date ?? undefined;
expiration =
expiry_date !== undefined
? `${expiry_date.getFullYear()}/${
expiry_date.getMonth() + 1
}/${expiry_date.getDate()}`
: undefined;
const currentDate = new DateWithZeroTime();
// 有効期限までの日数
remaining =
expiry_date !== undefined
? Math.floor(
(expiry_date.getTime() - currentDate.getTime()) /
(1000 * 60 * 60 * 24),
)
: undefined;
if (
remaining !== undefined &&
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
) {
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT;
}
} else {
status = USER_LICENSE_STATUS.NO_LICENSE;
}
return {
id: dbUser.id,
name: adb2cUser.displayName,
role: dbUser.role,
authorId: dbUser.author_id ?? undefined,
typistGroupName: groupNames,
email: mail,
emailVerified: dbUser.email_verified,
autoRenew: dbUser.auto_renew,
licenseAlert: dbUser.license_alert,
notification: dbUser.notification,
encryption: dbUser.encryption,
prompt: dbUser.prompt,
expiration: expiration,
remaining: remaining,
licenseStatus: status,
};
});
return users;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.NOT_FOUND,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUsers.name}`,
);
}
}
/**
* Updates sort criteria
* @param paramName
* @param direction
* @param token
* @returns sort criteria
*/
async updateSortCriteria(
context: Context,
paramName: TaskListSortableAttribute,
direction: SortDirection,
externalId: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateSortCriteria.name
} | params: { paramName: ${paramName}, direction: ${direction}, externalId: ${externalId} };`,
);
let user: EntityUser;
try {
// ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// ユーザーのソート条件を更新
await this.sortCriteriaRepository.updateSortCriteria(
user.id,
paramName,
direction,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateSortCriteria.name}`,
);
}
}
/**
* Gets sort criteria
* @param token
* @returns sort criteria
*/
async getSortCriteria(
context: Context,
externalId: string,
): Promise<{
paramName: TaskListSortableAttribute;
direction: SortDirection;
}> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getSortCriteria.name
} | params: { externalId: ${externalId} };`,
);
let user: EntityUser;
try {
// ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// ユーザーのソート条件を取得
const sortCriteria = await this.sortCriteriaRepository.getSortCriteria(
user.id,
);
const { direction, parameter } = sortCriteria;
//型チェック
if (
!isTaskListSortableAttribute(parameter) ||
!isSortDirection(direction)
) {
throw new Error('The value stored in the DB is invalid.');
}
return { direction, paramName: parameter };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getSortCriteria.name}`,
);
}
}
/**
* 指定したユーザーの文字起こし業務に関連する情報を取得します
* @param userId
* @returns relations
*/
async getRelations(
context: Context,
userId: string,
): Promise<GetRelationsResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getRelations.name
} | params: { userId: ${userId} };`,
);
try {
const { id } = await this.usersRepository.findUserByExternalId(userId);
// ユーザー関連情報を取得
const { user, authors, worktypes, activeWorktype } =
await this.usersRepository.getUserRelations(id);
// AuthorIDのリストを作成
const authorIds = authors.flatMap((author) =>
author.author_id ? [author.author_id] : [],
);
const workTypeList = worktypes?.map((worktype) => {
return {
workTypeId: worktype.custom_worktype_id,
optionItemList: worktype.option_items.map((optionItem) => {
const initialValueType = OPTION_ITEM_VALUE_TYPE_NUMBER.find(
(x) => x.type === optionItem.default_value_type,
)?.value;
if (!initialValueType) {
throw new Error(
`invalid default_value_type ${optionItem.default_value_type}`,
);
}
return {
label: optionItem.item_label,
initialValueType,
defaultValue: optionItem.initial_value,
};
}),
};
});
return {
authorId: user.author_id ?? undefined,
authorIdList: authorIds,
workTypeList,
isEncrypted: user.encryption,
encryptionPassword: user.encryption_password ?? undefined,
activeWorktype: activeWorktype?.custom_worktype_id ?? '',
audioFormat: USER_AUDIO_FORMAT,
prompt: user.prompt,
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getRelations.name}`,
);
}
}
/**
* 指定したユーザーの情報を更新します
* @param context
* @param extarnalId
* @param id
* @param role
* @param authorId
* @param autoRenew
* @param licenseAlart
* @param notification
* @param encryption
* @param encryptionPassword
* @param prompt
* @returns user
*/
async updateUser(
context: Context,
extarnalId: string,
id: number,
role: string,
authorId: string | undefined,
autoRenew: boolean,
licenseAlart: boolean,
notification: boolean,
encryption: boolean | undefined,
encryptionPassword: string | undefined,
prompt: boolean | undefined,
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateUser.name
} | params: { ` +
`extarnalId: ${extarnalId}, ` +
`id: ${id}, ` +
`role: ${role}, ` +
`authorId: ${authorId}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlart: ${licenseAlart}, ` +
`notification: ${notification}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} }`,
);
// 実行ユーザーのアカウントIDを取得
const accountId = (
await this.usersRepository.findUserByExternalId(extarnalId)
).account_id;
// ユーザー情報を更新
await this.usersRepository.update(
accountId,
id,
role,
authorId,
autoRenew,
licenseAlart,
notification,
encryption,
encryptionPassword,
prompt,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AuthorIdAlreadyExistsError:
throw new HttpException(
makeErrorResponse('E010302'),
HttpStatus.BAD_REQUEST,
);
case InvalidRoleChangeError:
throw new HttpException(
makeErrorResponse('E010207'),
HttpStatus.BAD_REQUEST,
);
case EncryptionPasswordNeedError:
throw new HttpException(
makeErrorResponse('E010208'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateUser.name}`,
);
}
}
/**
* ライセンスをユーザーに割り当てます
* @param context
* @param userId
* @param newLicenseId
*/
async allocateLicense(
context: Context,
userId: number,
newLicenseId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.allocateLicense.name
} | params: { ` +
`userId: ${userId}, ` +
`newLicenseId: ${newLicenseId}, };`,
);
try {
const accountId = (await this.usersRepository.findUserById(userId))
.account_id;
await this.licensesRepository.allocateLicense(
userId,
newLicenseId,
accountId,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseUnavailableError:
throw new HttpException(
makeErrorResponse('E010806'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.allocateLicense.name}`,
);
}
}
/**
* ユーザーに割り当てられているライセンスを解除します
* @param context
* @param userId
*/
async deallocateLicense(context: Context, userId: number): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deallocateLicense.name
} | params: { ` + `userId: ${userId}, };`,
);
try {
const accountId = (await this.usersRepository.findUserById(userId))
.account_id;
await this.licensesRepository.deallocateLicense(userId, accountId);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseAlreadyDeallocatedError:
throw new HttpException(
makeErrorResponse('E010807'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deallocateLicense.name}`,
);
}
}
/**
* 同意済み利用規約バージョンを更新する
* @param context
* @param idToken
* @param eulaVersion
* @param dpaVersion
*/
async updateAcceptedVersion(
context: Context,
externalId: string,
eulaVersion: string,
dpaVersion?: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateAcceptedVersion.name
} | params: { ` +
`externalId: ${externalId}, ` +
`eulaVersion: ${eulaVersion}, ` +
`dpaVersion: ${dpaVersion}, };`,
);
try {
await this.usersRepository.updateAcceptedTermsVersion(
externalId,
eulaVersion,
dpaVersion,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
case UpdateTermsVersionNotSetError:
throw new HttpException(
makeErrorResponse('E010001'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateAcceptedVersion.name}`,
);
}
}
/**
* Azure AD B2Cからユーザー名を取得する
* @param context
* @param externalId
*/
async getUserName(context: Context, externalId: string): Promise<string> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getUserName.name
} | params: { externalId: ${externalId} };`,
);
try {
// extarnalIdの存在チェックを行う
await this.usersRepository.findUserByExternalId(externalId);
// ADB2Cからユーザー名を取得する
const adb2cUser = await this.adB2cService.getUser(context, externalId);
return adb2cUser.displayName;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUserName.name}`,
);
}
}
}