diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 2ce696b..53a4fdf 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -373,7 +373,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetTypistGroupsResponse" + "$ref": "#/components/schemas/GetTypistGroupResponse" } } } @@ -2449,6 +2449,14 @@ }, "required": ["typistGroups"] }, + "GetTypistGroupResponse": { + "type": "object", + "properties": { + "typistGroupName": { "type": "string" }, + "typistIds": { "type": "array", "items": { "type": "integer" } } + }, + "required": ["typistGroupName", "typistIds"] + }, "CreateTypistGroupRequest": { "type": "object", "properties": { diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index afbdeaf..4825b43 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -47,4 +47,5 @@ export const ErrorCodes = [ 'E010805', // ライセンス有効期限切れエラー 'E010806', // ライセンス割り当て不可エラー 'E010807', // ライセンス割り当て解除済みエラー + 'E010908', // タイピストグループ不在エラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 86d41ba..ceac9e4 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -36,4 +36,5 @@ export const errors: Errors = { E010805: 'License is expired Error', E010806: 'License is unavailable Error', E010807: 'License is already deallocated Error', + E010908: 'Typist Group not exist Error', }; diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index b74c9a3..ee2e6dd 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -245,7 +245,7 @@ export class AccountsController { @ApiResponse({ status: HttpStatus.OK, - type: GetTypistGroupsResponse, + type: GetTypistGroupResponse, description: '成功時のレスポンス', }) @ApiResponse({ @@ -276,14 +276,21 @@ export class AccountsController { @Req() req: Request, @Param() param: GetTypistGroupRequest, ): Promise { - console.log(req.header('Authorization')); + const { typistGroupId } = param; + // アクセストークン取得 const accessToken = retrieveAuthorizationToken(req); - const payload = jwt.decode(accessToken, { json: true }) as AccessToken; + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; - console.log(param.typistGroupId); + const context = makeContext(userId); - return { typistGroupName: '', typistIds: [] }; + const typistGroup = await this.accountService.getTypistGroup( + context, + userId, + typistGroupId, + ); + + return typistGroup; } @ApiResponse({ diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index a3f6154..ce53585 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -18,6 +18,7 @@ import { getSortCriteria, getTypistGroup, getTypistGroupMember, + createTypistGroup, } from './test/utility'; import { DataSource } from 'typeorm'; import { makeTestingModule } from '../../common/test/modules'; @@ -31,7 +32,7 @@ import { } from '../../common/test/utility'; import { AccountsService } from './accounts.service'; import { Context, makeContext } from '../../common/log'; -import { TIERS } from '../../constants'; +import { TIERS, USER_ROLES } from '../../constants'; import { License } from '../../repositories/licenses/entity/license.entity'; import { overrideAccountsRepositoryService, @@ -2590,3 +2591,188 @@ describe('createTypistGroup', () => { ); }); }); + +describe('getTypistGroup', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); + + afterEach(async () => { + await source.destroy(); + source = null; + }); + it('指定したIDのTypistGroupを取得できる', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + // 作成したアカウントにユーザーを3名追加する + const typiptUserExternalIds = [ + 'typist-user-external-id1', + 'typist-user-external-id2', + 'typist-user-external-id3', + ]; + const userIds: number[] = []; + for (const typiptUserExternalId of typiptUserExternalIds) { + const { id: userId } = await makeTestUser(source, { + account_id: account.id, + external_id: typiptUserExternalId, + role: USER_ROLES.TYPIST, + }); + userIds.push(userId); + } + + // アカウントにタイピストグループを作成する + const typistGroupName = 'typist-group-name'; + + const { id: typistGroupId } = await createTypistGroup( + source, + account.id, + typistGroupName, + userIds, + ); + + //作成したデータを確認 + { + const group = await getTypistGroup(source, account.id); + expect(group.length).toBe(1); + expect(group[0].name).toBe(typistGroupName); + const groupUsers = await getTypistGroupMember(source, group[0].id); + expect(groupUsers.length).toBe(3); + expect(groupUsers.map((user) => user.user_id)).toEqual(userIds); + } + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + const typistGroup = await service.getTypistGroup( + context, + admin.external_id, + typistGroupId, + ); + //実行結果を確認 + { + const typistGroups = await getTypistGroup(source, account.id); + expect(typistGroups.length).toBe(1); + expect(typistGroup.typistGroupName).toBe(typistGroupName); + expect(typistGroup.typistIds.length).toBe(3); + expect(typistGroup.typistIds).toEqual(userIds); + } + }); + + it('指定したタイピストグループIDのタイピストグループが存在しない場合、400エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + // 作成したアカウントにユーザーを3名追加する + const typiptUserExternalIds = [ + 'typist-user-external-id1', + 'typist-user-external-id2', + 'typist-user-external-id3', + ]; + const userIds: number[] = []; + for (const typiptUserExternalId of typiptUserExternalIds) { + const { id: userId } = await makeTestUser(source, { + account_id: account.id, + external_id: typiptUserExternalId, + role: USER_ROLES.TYPIST, + }); + userIds.push(userId); + } + + // アカウントにタイピストグループを作成する + const typistGroupName = 'typist-group-name'; + await createTypistGroup(source, account.id, typistGroupName, userIds); + + //作成したデータを確認 + { + const group = await getTypistGroup(source, account.id); + expect(group.length).toBe(1); + expect(group[0].name).toBe(typistGroupName); + const groupUsers = await getTypistGroupMember(source, group[0].id); + expect(groupUsers.length).toBe(3); + expect(groupUsers.map((user) => user.user_id)).toEqual(userIds); + } + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + try { + await service.getTypistGroup(context, admin.external_id, 999); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E010908')); + } else { + fail(); + } + } + }); + + it('DBアクセスに失敗した場合、500エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + // 作成したアカウントにユーザーを3名追加する + const typiptUserExternalIds = [ + 'typist-user-external-id1', + 'typist-user-external-id2', + 'typist-user-external-id3', + ]; + const userIds: number[] = []; + for (const typiptUserExternalId of typiptUserExternalIds) { + const { id: userId } = await makeTestUser(source, { + account_id: account.id, + external_id: typiptUserExternalId, + role: USER_ROLES.TYPIST, + }); + userIds.push(userId); + } + + // アカウントにタイピストグループを作成する + const typistGroupName = 'typist-group-name'; + + const { id: typistGroupId } = await createTypistGroup( + source, + account.id, + typistGroupName, + userIds, + ); + + //作成したデータを確認 + { + const group = await getTypistGroup(source, account.id); + expect(group.length).toBe(1); + expect(group[0].name).toBe(typistGroupName); + const groupUsers = await getTypistGroupMember(source, group[0].id); + expect(groupUsers.length).toBe(3); + expect(groupUsers.map((user) => user.user_id)).toEqual(userIds); + } + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + //DBアクセスに失敗するようにする + const typistGroupService = module.get( + UserGroupsRepositoryService, + ); + typistGroupService.getTypistGroup = jest + .fn() + .mockRejectedValue('DB failed'); + + try { + await service.getTypistGroup(context, admin.external_id, typistGroupId); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 7f68bea..f649b81 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -21,6 +21,7 @@ import { GetDealersResponse, Dealer, GetMyAccountResponse, + GetTypistGroupResponse, } from './types/types'; import { DateWithZeroTime, @@ -40,6 +41,7 @@ import { OrderNotFoundError, } from '../../repositories/licenses/errors/types'; import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service'; +import { TypistGroupNotExistError } from '../../repositories/user_groups/errors/types'; import { TypistIdInvalidError } from '../../repositories/user_groups/errors/types'; @Injectable() @@ -416,6 +418,61 @@ export class AccountsService { this.logger.log(`[OUT] ${this.getTypistGroups.name}`); } } + /** + * IDを指定してタイピストグループを取得する + * @param context + * @param externalId + * @param typistGroupId + * @returns typist group + */ + async getTypistGroup( + context: Context, + externalId: string, + typistGroupId: number, + ): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.getTypistGroup.name} | params: { externalId: ${externalId}, typistGroupId: ${typistGroupId} };`, + ); + + try { + const { account_id } = await this.usersRepository.findUserByExternalId( + externalId, + ); + const userGroup = await this.userGroupsRepository.getTypistGroup( + account_id, + typistGroupId, + ); + + return { + typistGroupName: userGroup.name, + typistIds: userGroup.userGroupMembers.map((x) => x.user_id), + }; + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case TypistGroupNotExistError: + throw new HttpException( + makeErrorResponse('E010908'), + 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.trackingId}] ${this.getTypistGroup.name}`, + ); + } + } /** * Gets typists diff --git a/dictation_server/src/features/accounts/test/utility.ts b/dictation_server/src/features/accounts/test/utility.ts index ef8cdb6..af9a47d 100644 --- a/dictation_server/src/features/accounts/test/utility.ts +++ b/dictation_server/src/features/accounts/test/utility.ts @@ -108,3 +108,34 @@ export const getTypistGroupMember = async ( }, }); }; + +// タイピストグループを作成する +export const createTypistGroup = async ( + datasource: DataSource, + accountId: number, + name: string, + memberIds: number[], +): Promise => { + const { identifiers } = await datasource.getRepository(UserGroup).insert({ + account_id: accountId, + name: name, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + const userGroup = identifiers.pop() as UserGroup; + + for (const memberId of memberIds) { + await datasource.getRepository(UserGroupMember).insert({ + user_group_id: userGroup.id, + user_id: memberId, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + } + + return userGroup; +}; diff --git a/dictation_server/src/repositories/user_groups/errors/types.ts b/dictation_server/src/repositories/user_groups/errors/types.ts index 18303df..a41ff78 100644 --- a/dictation_server/src/repositories/user_groups/errors/types.ts +++ b/dictation_server/src/repositories/user_groups/errors/types.ts @@ -1,2 +1,4 @@ +// タイピストグループが存在しないエラー +export class TypistGroupNotExistError extends Error {} // typistIdが不正な場合のエラー export class TypistIdInvalidError extends Error {} diff --git a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts index 12a66af..ffd9dbc 100644 --- a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts +++ b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; import { UserGroup } from './entity/user_group.entity'; import { UserGroupMember } from './entity/user_group_member.entity'; +import { TypistGroupNotExistError } from './errors/types'; import { User } from '../users/entity/user.entity'; import { TypistIdInvalidError } from './errors/types'; import { USER_ROLES } from '../../constants'; @@ -46,6 +47,40 @@ export class UserGroupsRepositoryService { return groupMembers; }); } + + /** + * タイピストグループと所属するメンバーのIDを取得します + * @param accountId + * @param typistGroupId + * @returns typist group + */ + async getTypistGroup( + accountId: number, + typistGroupId: number, + ): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const userGroupRepo = entityManager.getRepository(UserGroup); + + const userGroup = await userGroupRepo.findOne({ + where: { + id: typistGroupId, + account_id: accountId, + deleted_at: IsNull(), + }, + relations: { + userGroupMembers: true, + }, + }); + + if (!userGroup) { + throw new TypistGroupNotExistError( + `Typist Group is not exist. typistGroupId: ${typistGroupId}`, + ); + } + return userGroup; + }); + } + /** * 指定したアカウントIDでタイピストグループを作成し、そのタイピストグループとtypistIdsのユーザーを紐付ける * @param accountId