diff --git a/dictation_server/.vscode/settings.json b/dictation_server/.vscode/settings.json index a3c8e93..f945bf2 100644 --- a/dictation_server/.vscode/settings.json +++ b/dictation_server/.vscode/settings.json @@ -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" + } \ No newline at end of file diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index b6d1b2a..29deb74 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -22,6 +22,7 @@ export const ErrorCodes = [ 'E000106', // トークンアルゴリズムエラー 'E000107', // トークン不足エラー 'E000108', // トークン権限エラー + 'E000301', // ADB2Cへのリクエスト上限超過エラー 'E010001', // パラメータ形式不正エラー 'E010201', // 未認証ユーザエラー 'E010202', // 認証済ユーザエラー diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index e75953b..0ac0827 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -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.', diff --git a/dictation_server/src/features/tasks/tasks.module.ts b/dictation_server/src/features/tasks/tasks.module.ts index e1d2803..f3d94ac 100644 --- a/dictation_server/src/features/tasks/tasks.module.ts +++ b/dictation_server/src/features/tasks/tasks.module.ts @@ -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], }) diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index f725a46..51d6e9c 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -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, ), ); diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index e6d979e..175965f 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -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 { + // 割り当て候補の外部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); + } } diff --git a/dictation_server/src/features/tasks/test/tasks.service.mock.ts b/dictation_server/src/features/tasks/test/tasks.service.mock.ts index 21c47b6..3566b91 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -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, []>().mockRejectedValue(getUsers) + : jest.fn, []>().mockResolvedValue(getUsers), + }; +}; + +export const makeDefaultAdb2cServiceMockValue = (): AdB2CServiceMockValue => { + return { + getUsers: [{ id: 'userId', displayName: 'XXXX XXX' }], + }; +}; + export const makeDefaultUsersRepositoryMockValue = (): UsersRepositoryMockValue => { const user1 = new User(); diff --git a/dictation_server/src/features/tasks/types/convert.ts b/dictation_server/src/features/tasks/types/convert.ts index 794f757..bdb96f6 100644 --- a/dictation_server/src/features/tasks/types/convert.ts +++ b/dictation_server/src/features/tasks/types/convert.ts @@ -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, }; }; diff --git a/dictation_server/src/gateways/adb2c/adb2c.service.ts b/dictation_server/src/gateways/adb2c/adb2c.service.ts index 8bd6fdc..6672a4f 100644 --- a/dictation_server/src/gateways/adb2c/adb2c.service.ts +++ b/dictation_server/src/gateways/adb2c/adb2c.service.ts @@ -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 { + 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; +}; diff --git a/dictation_server/src/gateways/adb2c/types/types.ts b/dictation_server/src/gateways/adb2c/types/types.ts new file mode 100644 index 0000000..1eb845a --- /dev/null +++ b/dictation_server/src/gateways/adb2c/types/types.ts @@ -0,0 +1,5 @@ +export type AdB2cResponse = { + '@odata.context': string; + value: AdB2cUser[]; +}; +export type AdB2cUser = { id: string; displayName: string };