Merged PR 145: typist名をAzure AD B2Cから取得し表示できるようにする
## 概要 [Task1950: typist名をAzure AD B2Cから取得し表示できるようにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1950) - チェックアウト権限にあるTypistのユーザー名をB2Cから取得する - チェックアウト権限に含まれているuser.externalIdを列挙 - ExternalIdでフィルターをかけてユーザー情報を取得 - B2Cへのリクエスト上限超過時のエラーを制御するために専用エラーを定義 - import文が常に絶対パスで指定されていて、それでテストがこけるので相対パスでインポートするようにvscodeを設定 ## レビューポイント - convert.tsの修正は問題ないか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 取得方法についてはMSに問い合わせ中
This commit is contained in:
parent
bb926f9feb
commit
d75c003b09
4
dictation_server/.vscode/settings.json
vendored
4
dictation_server/.vscode/settings.json
vendored
@ -18,5 +18,7 @@
|
||||
"editor.renderWhitespace": "all",
|
||||
"editor.insertSpaces": false,
|
||||
"editor.renderLineHighlight": "all",
|
||||
"prettier.prettierPath": "./node_modules/prettier"
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"typescript.preferences.importModuleSpecifier": "relative"
|
||||
|
||||
}
|
||||
@ -22,6 +22,7 @@ export const ErrorCodes = [
|
||||
'E000106', // トークンアルゴリズムエラー
|
||||
'E000107', // トークン不足エラー
|
||||
'E000108', // トークン権限エラー
|
||||
'E000301', // ADB2Cへのリクエスト上限超過エラー
|
||||
'E010001', // パラメータ形式不正エラー
|
||||
'E010201', // 未認証ユーザエラー
|
||||
'E010202', // 認証済ユーザエラー
|
||||
|
||||
@ -11,6 +11,7 @@ export const errors: Errors = {
|
||||
E000106: 'Token invalid algorithm Error.',
|
||||
E000107: 'Token is not exist Error.',
|
||||
E000108: 'Token authority failed Error.',
|
||||
E000301: 'ADB2C request limit exceeded Error',
|
||||
E010001: 'Param invalid format Error.',
|
||||
E010201: 'Email not verified user Error.',
|
||||
E010202: 'Email already verified user Error.',
|
||||
|
||||
@ -3,9 +3,10 @@ import { TasksService } from './tasks.service';
|
||||
import { TasksController } from './tasks.controller';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository.module';
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
|
||||
@Module({
|
||||
imports: [UsersRepositoryModule, TasksRepositoryModule],
|
||||
imports: [UsersRepositoryModule, TasksRepositoryModule, AdB2cModule],
|
||||
providers: [TasksService],
|
||||
controllers: [TasksController],
|
||||
})
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
import {
|
||||
makeDefaultAdb2cServiceMockValue,
|
||||
makeDefaultTasksRepositoryMockValue,
|
||||
makeDefaultUsersRepositoryMockValue,
|
||||
makeTasksServiceMock,
|
||||
} from './test/tasks.service.mock';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { Adb2cTooManyRequestsError } from '../../gateways/adb2c/adb2c.service';
|
||||
|
||||
describe('TasksService', () => {
|
||||
it('タスク一覧を取得できる(admin)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'admin', tier: 5 };
|
||||
@ -33,7 +37,7 @@ describe('TasksService', () => {
|
||||
).toEqual({
|
||||
tasks: [
|
||||
{
|
||||
assignees: [{ typistName: 'USER_userId', typistUserId: 1 }],
|
||||
assignees: [{ typistName: 'XXXX XXX', typistUserId: 1 }],
|
||||
audioCreatedDate: '2023-01-01T01:01:01.000Z',
|
||||
audioDuration: '123000',
|
||||
audioFileId: 1,
|
||||
@ -74,10 +78,12 @@ describe('TasksService', () => {
|
||||
it('アカウント情報の取得に失敗した場合、エラーを返却する', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
usersRepositoryMockValue.findUserByExternalId = new Error('DB failed');
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'admin', tier: 5 };
|
||||
@ -97,7 +103,7 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
@ -106,10 +112,12 @@ describe('TasksService', () => {
|
||||
it('タスク一覧の取得に失敗した場合、エラーを返却する(admin)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
tasksRepositoryMockValue.getTasksFromAccountId = new Error('DB failed');
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'admin', tier: 5 };
|
||||
@ -129,7 +137,7 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
@ -173,11 +181,12 @@ describe('TasksService', () => {
|
||||
count: 1,
|
||||
};
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'admin', tier: 5 };
|
||||
const offset = 0;
|
||||
const limit = 20;
|
||||
@ -195,7 +204,7 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
@ -204,14 +213,15 @@ describe('TasksService', () => {
|
||||
it('タスク一覧を取得できる(author)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
|
||||
return;
|
||||
}
|
||||
usersRepositoryMockValue.findUserByExternalId.role = 'author';
|
||||
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'author', tier: 5 };
|
||||
@ -231,7 +241,7 @@ describe('TasksService', () => {
|
||||
expect(result).toEqual({
|
||||
tasks: [
|
||||
{
|
||||
assignees: [{ typistName: 'USER_userId', typistUserId: 1 }],
|
||||
assignees: [{ typistName: 'XXXX XXX', typistUserId: 1 }],
|
||||
audioCreatedDate: '2023-01-01T01:01:01.000Z',
|
||||
audioDuration: '123000',
|
||||
audioFileId: 1,
|
||||
@ -278,12 +288,14 @@ describe('TasksService', () => {
|
||||
it('タスク一覧の取得に失敗した場合、エラーを返却する(author)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
tasksRepositoryMockValue.getTasksFromAuthorIdAndAccountId = new Error(
|
||||
'DB failed',
|
||||
);
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'author', tier: 5 };
|
||||
@ -303,7 +315,7 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
@ -312,6 +324,7 @@ describe('TasksService', () => {
|
||||
it('タスク一覧を取得できる(typist)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
|
||||
return;
|
||||
}
|
||||
@ -320,6 +333,7 @@ describe('TasksService', () => {
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'typist', tier: 5 };
|
||||
@ -339,7 +353,7 @@ describe('TasksService', () => {
|
||||
expect(result).toEqual({
|
||||
tasks: [
|
||||
{
|
||||
assignees: [{ typistName: 'USER_userId', typistUserId: 1 }],
|
||||
assignees: [{ typistName: 'XXXX XXX', typistUserId: 1 }],
|
||||
audioCreatedDate: '2023-01-01T01:01:01.000Z',
|
||||
audioDuration: '123000',
|
||||
audioFileId: 1,
|
||||
@ -386,12 +400,14 @@ describe('TasksService', () => {
|
||||
it('タスク一覧の取得に失敗した場合、エラーを返却する(typist)', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
tasksRepositoryMockValue.getTasksFromTypistRelations = new Error(
|
||||
'DB failed',
|
||||
);
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'typist', tier: 5 };
|
||||
@ -411,7 +427,7 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
@ -420,9 +436,11 @@ describe('TasksService', () => {
|
||||
it('想定外のRoleの場合、エラーを返却する', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'XXX', tier: 5 };
|
||||
@ -442,7 +460,41 @@ describe('TasksService', () => {
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('AdB2Cのリクエスト上限超過時、専用のエラーを返却する', async () => {
|
||||
const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
adb2cServiceMockValue.getUsers = new Adb2cTooManyRequestsError();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
);
|
||||
|
||||
const accessToken = { userId: 'userId', role: 'admin', tier: 5 };
|
||||
const offset = 0;
|
||||
const limit = 20;
|
||||
const status = ['Uploaded,Backup'];
|
||||
const paramName = 'JOB_NUMBER';
|
||||
const direction = 'ASC';
|
||||
await expect(
|
||||
service.tasksService.getTasks(
|
||||
accessToken,
|
||||
offset,
|
||||
limit,
|
||||
status,
|
||||
paramName,
|
||||
direction,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E000301'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
||||
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { Task } from './types/types';
|
||||
import { Task as TaskEntity } from '../../repositories/tasks/entity/task.entity';
|
||||
import { createTasks } from './types/convert';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
@ -10,6 +11,12 @@ import {
|
||||
TaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import {
|
||||
AdB2cService,
|
||||
Adb2cTooManyRequestsError,
|
||||
} from '../../gateways/adb2c/adb2c.service';
|
||||
import { AdB2cUser } from '../../gateways/adb2c/types/types';
|
||||
import { CheckoutPermission } from '../../repositories/checkout_permissions/entity/checkout_permission.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TasksService {
|
||||
@ -17,6 +24,7 @@ export class TasksService {
|
||||
constructor(
|
||||
private readonly taskRepository: TasksRepositoryService,
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
) {}
|
||||
|
||||
// TODO: 引数にAccessTokenがあるのは不適切なのでController側で分解したい
|
||||
@ -49,7 +57,13 @@ export class TasksService {
|
||||
status,
|
||||
);
|
||||
|
||||
const tasks = createTasks(result.tasks, result.permissions);
|
||||
// B2Cからユーザー名を取得する
|
||||
const b2cUsers = await this.getB2cUsers(
|
||||
result.tasks,
|
||||
result.permissions,
|
||||
);
|
||||
|
||||
const tasks = createTasks(result.tasks, result.permissions, b2cUsers);
|
||||
|
||||
return { tasks: tasks, total: result.count };
|
||||
}
|
||||
@ -64,7 +78,14 @@ export class TasksService {
|
||||
direction ?? defaultDirection,
|
||||
status,
|
||||
);
|
||||
const tasks = createTasks(result.tasks, result.permissions);
|
||||
|
||||
// B2Cからユーザー名を取得する
|
||||
const b2cUsers = await this.getB2cUsers(
|
||||
result.tasks,
|
||||
result.permissions,
|
||||
);
|
||||
|
||||
const tasks = createTasks(result.tasks, result.permissions, b2cUsers);
|
||||
return { tasks: tasks, total: result.count };
|
||||
}
|
||||
|
||||
@ -77,8 +98,13 @@ export class TasksService {
|
||||
direction ?? defaultDirection,
|
||||
status,
|
||||
);
|
||||
// B2Cからユーザー名を取得する
|
||||
const b2cUsers = await this.getB2cUsers(
|
||||
result.tasks,
|
||||
result.permissions,
|
||||
);
|
||||
|
||||
const tasks = createTasks(result.tasks, result.permissions);
|
||||
const tasks = createTasks(result.tasks, result.permissions, b2cUsers);
|
||||
|
||||
return { tasks: tasks, total: result.count };
|
||||
}
|
||||
@ -86,10 +112,49 @@ export class TasksService {
|
||||
throw new Error(`invalid roles: ${roles.join(',')}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
if (e.constructor === Adb2cTooManyRequestsError) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000301'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
private async getB2cUsers(
|
||||
tasks: TaskEntity[],
|
||||
permissions: CheckoutPermission[],
|
||||
): Promise<AdB2cUser[]> {
|
||||
// 割り当て候補の外部IDを列挙
|
||||
const assigneesExternalIds = permissions.map((x) => {
|
||||
if (x.user) {
|
||||
return x.user.external_id;
|
||||
}
|
||||
});
|
||||
// 割り当てられているタイピストの外部IDを列挙
|
||||
const typistExternalIds = tasks.flatMap((x) => {
|
||||
if (x.typist_user) {
|
||||
return x.typist_user.external_id;
|
||||
}
|
||||
});
|
||||
console.log(assigneesExternalIds.concat(undefined));
|
||||
|
||||
//重複をなくす
|
||||
const distinctedExternalIds = [
|
||||
...new Set(assigneesExternalIds.concat(typistExternalIds)),
|
||||
];
|
||||
|
||||
// undefinedがあった場合、取り除く
|
||||
const filteredExternalIds: string[] = distinctedExternalIds.filter(
|
||||
(x): x is string => x !== undefined,
|
||||
);
|
||||
|
||||
// B2Cからユーザー名を取得する
|
||||
return await this.adB2cService.getUsers(filteredExternalIds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
SortDirection,
|
||||
TaskListSortableAttribute,
|
||||
} from '../../../common/types/sort';
|
||||
import { AdB2cService } from '../../../gateways/adb2c/adb2c.service';
|
||||
import { AdB2cUser } from '../../../gateways/adb2c/types/types';
|
||||
|
||||
export type TasksRepositoryMockValue = {
|
||||
getTasksFromAccountId:
|
||||
@ -34,6 +36,10 @@ export type TasksRepositoryMockValue = {
|
||||
| Error;
|
||||
};
|
||||
|
||||
export type AdB2CServiceMockValue = {
|
||||
getUsers: AdB2cUser[] | Error;
|
||||
};
|
||||
|
||||
export type UsersRepositoryMockValue = {
|
||||
findUserByExternalId: User | Error;
|
||||
};
|
||||
@ -41,6 +47,7 @@ export type UsersRepositoryMockValue = {
|
||||
export const makeTasksServiceMock = async (
|
||||
tasksRepositoryMockValue: TasksRepositoryMockValue,
|
||||
usersRepositoryMockValue: UsersRepositoryMockValue,
|
||||
adB2CServiceMockValue: AdB2CServiceMockValue,
|
||||
): Promise<{
|
||||
tasksService: TasksService;
|
||||
taskRepoService: TasksRepositoryService;
|
||||
@ -54,6 +61,8 @@ export const makeTasksServiceMock = async (
|
||||
return makeTasksRepositoryMock(tasksRepositoryMockValue);
|
||||
case UsersRepositoryService:
|
||||
return makeUsersRepositoryMock(usersRepositoryMockValue);
|
||||
case AdB2cService:
|
||||
return makeAdb2cServiceMock(adB2CServiceMockValue);
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
@ -159,6 +168,23 @@ export const makeDefaultTasksRepositoryMockValue =
|
||||
};
|
||||
};
|
||||
|
||||
export const makeAdb2cServiceMock = (value: AdB2CServiceMockValue) => {
|
||||
const { getUsers } = value;
|
||||
|
||||
return {
|
||||
getUsers:
|
||||
getUsers instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(getUsers)
|
||||
: jest.fn<Promise<AdB2cUser[]>, []>().mockResolvedValue(getUsers),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultAdb2cServiceMockValue = (): AdB2CServiceMockValue => {
|
||||
return {
|
||||
getUsers: [{ id: 'userId', displayName: 'XXXX XXX' }],
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultUsersRepositoryMockValue =
|
||||
(): UsersRepositoryMockValue => {
|
||||
const user1 = new User();
|
||||
|
||||
@ -6,18 +6,20 @@ import { AudioOptionItem as AudioOptionItemEntity } from '../../../repositories/
|
||||
import { Task, Assignee } from './types';
|
||||
import { AudioOptionItem } from '../../files/types/types';
|
||||
import { Typist } from '../../../features/accounts/types/types';
|
||||
import { AdB2cUser } from '../../../gateways/adb2c/types/types';
|
||||
|
||||
// Repository側のDTOからTaskオブジェクトの一覧を構築する
|
||||
export const createTasks = (
|
||||
tasks: TaskEntity[],
|
||||
permissions: CheckoutPermissionEntity[],
|
||||
b2cUsers: AdB2cUser[],
|
||||
): Task[] => {
|
||||
// Taskオブジェクトを構築
|
||||
const convertedTasks = tasks.map((task) => {
|
||||
const targets = permissions.filter(
|
||||
(permission) => permission.task_id === task.id,
|
||||
);
|
||||
return createTask(task, targets);
|
||||
return createTask(task, targets, b2cUsers);
|
||||
});
|
||||
return convertedTasks;
|
||||
};
|
||||
@ -26,6 +28,7 @@ export const createTasks = (
|
||||
const createTask = (
|
||||
task: TaskEntity,
|
||||
permissions: CheckoutPermissionEntity[],
|
||||
b2cUserInfo: AdB2cUser[],
|
||||
): Task => {
|
||||
const { file, option_items, typist_user } = task;
|
||||
if (!file) {
|
||||
@ -39,11 +42,13 @@ const createTask = (
|
||||
const optionItems = createAudioOptionItems(option_items);
|
||||
|
||||
// RepositoryDTO => ControllerDTOに変換
|
||||
const assignees = createAssignees(permissions);
|
||||
const assignees = createAssignees(permissions, b2cUserInfo);
|
||||
|
||||
// RepositoryDTO => ControllerDTOに変換
|
||||
const typist: Typist =
|
||||
typist_user != null ? convertUserToTypist(typist_user) : undefined;
|
||||
typist_user != null
|
||||
? convertUserToTypist(typist_user, b2cUserInfo)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
audioFileId: task.audio_file_id,
|
||||
@ -85,10 +90,11 @@ const createAudioOptionItems = (
|
||||
// Repository側のDTOからAssigneeオブジェクトを構築する
|
||||
const createAssignees = (
|
||||
permissions: CheckoutPermissionEntity[],
|
||||
b2cUserInfo: AdB2cUser[],
|
||||
): Assignee[] => {
|
||||
return permissions.flatMap((x): Assignee[] => {
|
||||
if (x.user != null) {
|
||||
return [convertUserToAssignee(x.user)];
|
||||
return [convertUserToAssignee(x.user, b2cUserInfo)];
|
||||
}
|
||||
|
||||
if (x.user_group != null) {
|
||||
@ -100,11 +106,17 @@ const createAssignees = (
|
||||
});
|
||||
};
|
||||
|
||||
// RepositoryDTOのUserからAssigneeオブジェクトを生成します
|
||||
const convertUserToAssignee = (user: UserEntity): Assignee => {
|
||||
// RepositoryDTOのUserからTypistオブジェクトを生成します
|
||||
const convertUserToAssignee = (
|
||||
user: UserEntity,
|
||||
b2cUserInfo: AdB2cUser[],
|
||||
): Assignee => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
return {
|
||||
typistUserId: user.id,
|
||||
typistName: `USER_${user?.external_id}`, // XXX Azure AD B2Cから取得した名前を入れる
|
||||
typistName,
|
||||
};
|
||||
};
|
||||
|
||||
@ -117,9 +129,15 @@ const convertUserGroupToAssignee = (userGroup: UserGroupEntity): Assignee => {
|
||||
};
|
||||
|
||||
// RepositoryDTOのUserからTypistオブジェクトを生成します
|
||||
const convertUserToTypist = (user: UserEntity): Typist => {
|
||||
const convertUserToTypist = (
|
||||
user: UserEntity,
|
||||
b2cUserInfo: AdB2cUser[],
|
||||
): Typist => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
return {
|
||||
id: user.id,
|
||||
name: `USER_${user?.external_id}`, // XXX Azure AD B2Cから取得した名前を入れる
|
||||
name: typistName,
|
||||
};
|
||||
};
|
||||
|
||||
@ -5,12 +5,15 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios from 'axios';
|
||||
import { Aadb2cUser, B2cMetadata, JwkSignKey } from '../../common/token';
|
||||
import { AdB2cResponse, AdB2cUser } from './types/types';
|
||||
|
||||
export type ConflictError = {
|
||||
reason: 'email';
|
||||
message: string;
|
||||
};
|
||||
|
||||
export class Adb2cTooManyRequestsError extends Error {}
|
||||
|
||||
export const isConflictError = (arg: unknown): arg is ConflictError => {
|
||||
const value = arg as ConflictError;
|
||||
if (value.message === undefined) {
|
||||
@ -179,4 +182,61 @@ export class AdB2cService {
|
||||
this.logger.log(`[OUT] ${this.getUser.name}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets users
|
||||
* @param externalIds
|
||||
* @returns users
|
||||
*/
|
||||
async getUsers(externalIds: string[]): Promise<AdB2cUser[]> {
|
||||
this.logger.log(
|
||||
`[IN] ${this.getUsers.name}; externalIds:[${externalIds.join(',')}]`,
|
||||
);
|
||||
|
||||
/*
|
||||
TODO 現状の実装だと1リクエストで最大15パラメータまでしか設定できないため、
|
||||
別タスクでアカウント単位の検索用パラメータを用いて取得するように修正する。
|
||||
タスク 2002: B2Cからの名前取得をより低コストで行えるように修正する
|
||||
*/
|
||||
const chunkExternalIds = splitArrayInChunksOfFifteen(externalIds);
|
||||
|
||||
try {
|
||||
const resArr: AdB2cUser[] = [];
|
||||
for (let index = 0; index < chunkExternalIds.length; index++) {
|
||||
const element = chunkExternalIds[index];
|
||||
const res: AdB2cResponse = await this.graphClient
|
||||
.api(`users/`)
|
||||
.select(['id', 'displayName'])
|
||||
.filter(`id in (${element.map((y) => `'${y}'`).join(',')})`)
|
||||
.get();
|
||||
resArr.push(...res.value);
|
||||
}
|
||||
|
||||
const data: AdB2cResponse = await this.graphClient
|
||||
.api(`users/`)
|
||||
.select(['id', 'displayName'])
|
||||
.filter(`id in (${externalIds.map((x) => `'${x}'`).join(',')})`)
|
||||
.get();
|
||||
|
||||
return data.value;
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
const { statusCode } = e;
|
||||
if (statusCode === 429) {
|
||||
throw new Adb2cTooManyRequestsError();
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.getUsers.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO 文字列の配列を15要素ずつ区切る(この処理も別タスクで削除予定)
|
||||
const splitArrayInChunksOfFifteen = (arr: string[]): string[][] => {
|
||||
const result: string[][] = [];
|
||||
const chunkSize = 15; // SDKの制限数
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
result.push(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
5
dictation_server/src/gateways/adb2c/types/types.ts
Normal file
5
dictation_server/src/gateways/adb2c/types/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type AdB2cResponse = {
|
||||
'@odata.context': string;
|
||||
value: AdB2cUser[];
|
||||
};
|
||||
export type AdB2cUser = { id: string; displayName: string };
|
||||
Loading…
x
Reference in New Issue
Block a user