From 6a1226c62e1fb3677614b05c5a89d64620789cfd Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Fri, 23 Jun 2023 04:04:01 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20165:=20=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=94=E3=82=B9=E3=83=88=E5=89=B2=E3=82=8A=E5=BD=93=E3=81=A6?= =?UTF-8?q?=E5=A4=89=E6=9B=B4API=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1932: タイピスト割り当て変更API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1932) - タイピスト割り当て変更APIを実装 - テスト実装 ## レビューポイント - IFのバリデーションを実装したがチェック内容はこれでよさそうか - DBのデータ取得・更新処理は問題ないか - DBへアクセスする回数は問題ない程度か - パスパラメータのバリデーションは問題ないか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認(swaggerUI,Postman) ## 補足 - 別途sqliteを用いたテストを実装する予定 --- dictation_server/.vscode/settings.json | 2 +- dictation_server/src/common/error/code.ts | 1 + dictation_server/src/common/error/message.ts | 1 + .../common/validators/assignees.validator.ts | 43 ++++++++ .../src/features/accounts/accounts.service.ts | 6 +- .../licenses/licenses.service.spec.ts | 2 +- .../src/features/licenses/licenses.service.ts | 18 ++-- .../src/features/tasks/tasks.controller.ts | 7 +- .../src/features/tasks/tasks.service.spec.ts | 45 ++++++++ .../src/features/tasks/tasks.service.ts | 50 ++++++++- .../features/tasks/test/tasks.service.mock.ts | 12 +++ .../src/features/tasks/types/types.ts | 20 +++- .../features/users/test/users.service.mock.ts | 23 +--- .../src/features/users/users.service.spec.ts | 2 +- .../src/features/users/users.service.ts | 6 +- .../accounts/accounts.repository.service.ts | 3 +- .../src/repositories/accounts/errors/types.ts | 2 + .../src/repositories/licenses/errors/types.ts | 2 + .../licenses/licenses.repository.service.ts | 3 +- .../sort_criteria.repository.service.ts | 1 - .../src/repositories/tasks/errors/types.ts | 6 ++ .../tasks/tasks.repository.service.ts | 100 ++++++++++++++++++ .../src/repositories/users/errors/types.ts | 4 + .../users/users.repository.service.ts | 6 +- 24 files changed, 307 insertions(+), 58 deletions(-) create mode 100644 dictation_server/src/common/validators/assignees.validator.ts create mode 100644 dictation_server/src/repositories/accounts/errors/types.ts create mode 100644 dictation_server/src/repositories/licenses/errors/types.ts create mode 100644 dictation_server/src/repositories/tasks/errors/types.ts create mode 100644 dictation_server/src/repositories/users/errors/types.ts diff --git a/dictation_server/.vscode/settings.json b/dictation_server/.vscode/settings.json index f945bf2..1f6f864 100644 --- a/dictation_server/.vscode/settings.json +++ b/dictation_server/.vscode/settings.json @@ -19,6 +19,6 @@ "editor.insertSpaces": false, "editor.renderLineHighlight": "all", "prettier.prettierPath": "./node_modules/prettier", - "typescript.preferences.importModuleSpecifier": "relative" + "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 29deb74..d885497 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -34,4 +34,5 @@ export const ErrorCodes = [ 'E010302', // authorId重複エラー 'E010401', // PONumber重複エラー 'E010501', // アカウント不在エラー + 'E010601', // タスク変更不可エラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 0ac0827..720ea35 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -23,4 +23,5 @@ export const errors: Errors = { E010302: 'This AuthorId already used Error', E010401: 'This PoNumber already used Error', E010501: 'Account not Found Error.', + E010601: 'Task is not Editable Error', }; diff --git a/dictation_server/src/common/validators/assignees.validator.ts b/dictation_server/src/common/validators/assignees.validator.ts new file mode 100644 index 0000000..fb760ec --- /dev/null +++ b/dictation_server/src/common/validators/assignees.validator.ts @@ -0,0 +1,43 @@ +import { + registerDecorator, + ValidationOptions, + ValidationArguments, +} from 'class-validator'; +import { Assignee } from '../../features/tasks/types/types'; +/** + * Validations options + * @param [validationOptions] + * @returns + */ +export const IsAssignees = (validationOptions?: ValidationOptions) => { + return (object: any, propertyName: string) => { + registerDecorator({ + name: 'IsAssignees', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + validate: (values: Assignee[], args: ValidationArguments) => { + return values.every((value) => { + const { typistUserId, typistGroupId, typistName } = value; + if (typistUserId === undefined && typistGroupId === undefined) { + return false; + } + if (typistUserId !== undefined && typistGroupId !== undefined) { + return false; + } + if (!typistName) { + return false; + } + return true; + }); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + defaultMessage: (args?: ValidationArguments): string => { + return 'Request body is invalid format'; + }, + }, + }); + }; +}; diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index f5c1ab0..4eeffd7 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -1,10 +1,7 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SendGridService } from '../../gateways/sendgrid/sendgrid.service'; -import { - UsersRepositoryService, - UserNotFoundError, -} from '../../repositories/users/users.repository.service'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; import { AdB2cService, @@ -18,6 +15,7 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { TypistGroup } from './types/types'; import { GetLicenseSummaryResponse, Typist } from './types/types'; import { AccessToken } from '../../common/token'; +import { UserNotFoundError } from '../../repositories/users/errors/types'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; @Injectable() diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index f2ccd25..599a289 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -8,7 +8,7 @@ import { } from './test/liscense.service.mock'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { HttpException, HttpStatus } from '@nestjs/common'; -import { PoNumberAlreadyExistError } from '../../repositories/licenses/licenses.repository.service'; +import { PoNumberAlreadyExistError } from '../../repositories/licenses/errors/types'; describe('LicensesService', () => { it('ライセンス注文が完了する', async () => { diff --git a/dictation_server/src/features/licenses/licenses.service.ts b/dictation_server/src/features/licenses/licenses.service.ts index 71cc532..2643563 100644 --- a/dictation_server/src/features/licenses/licenses.service.ts +++ b/dictation_server/src/features/licenses/licenses.service.ts @@ -1,18 +1,12 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { AccessToken } from '../../common/token'; -import { - UsersRepositoryService, - UserNotFoundError, -} from '../../repositories/users/users.repository.service'; -import { - AccountsRepositoryService, - AccountNotFoundError, -} from '../../repositories/accounts/accounts.repository.service'; -import { - LicensesRepositoryService, - PoNumberAlreadyExistError, -} from '../../repositories/licenses/licenses.repository.service'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; +import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; +import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; +import { PoNumberAlreadyExistError } from '../../repositories/licenses/errors/types'; +import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service'; +import { UserNotFoundError } from '../../repositories/users/errors/types'; @Injectable() export class LicensesService { diff --git a/dictation_server/src/features/tasks/tasks.controller.ts b/dictation_server/src/features/tasks/tasks.controller.ts index 364142c..858de11 100644 --- a/dictation_server/src/features/tasks/tasks.controller.ts +++ b/dictation_server/src/features/tasks/tasks.controller.ts @@ -5,6 +5,7 @@ import { Headers, HttpStatus, Param, + ParseIntPipe, Post, Query, Req, @@ -425,13 +426,11 @@ export class TasksController { ) async changeCheckoutPermission( @Req() req: Request, - @Param(`audioFileId`) audioFileId: number, + @Param(`audioFileId`, ParseIntPipe) audioFileId: number, @Body() body: PostCheckoutPermissionRequest, ): Promise { const { assignees } = body; - console.log(req.header('Authorization')); - console.log(audioFileId); - console.log(assignees); + await this.taskService.changeCheckoutPermission(audioFileId, assignees); return {}; } diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 51d6e9c..3602294 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -7,6 +7,8 @@ import { import { HttpException, HttpStatus } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { Adb2cTooManyRequestsError } from '../../gateways/adb2c/adb2c.service'; +import { UserNotFoundError } from '../../repositories/users/errors/types'; +import { TasksNotFoundError } from '../../repositories/tasks/errors/types'; describe('TasksService', () => { it('タスク一覧を取得できる(admin)', async () => { @@ -500,3 +502,46 @@ describe('TasksService', () => { ); }); }); + +describe('TasksService', () => { + // TODO sqliteを用いたテストを別途実装予定 + /* + 指定したユーザーグループがない場合 + 指定したユーザーがいない場合 + 指定したタスクがない場合 + タスクのステータスがUploadedでない場合 + */ + it('タスクのチェックアウト権限を変更できる', async () => { + const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue(); + const service = await makeTasksServiceMock( + tasksRepositoryMockValue, + usersRepositoryMockValue, + adb2cServiceMockValue, + ); + + expect(await service.tasksService.changeCheckoutPermission(1, [])).toEqual( + undefined, + ); + }); + + it('ユーザーが存在しない場合、タスクのチェックアウト権限を変更できない', async () => { + const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue(); + tasksRepositoryMockValue.changeCheckoutPermission = + new TasksNotFoundError(); + const service = await makeTasksServiceMock( + tasksRepositoryMockValue, + usersRepositoryMockValue, + adb2cServiceMockValue, + ); + + await expect( + service.tasksService.changeCheckoutPermission(1, []), + ).rejects.toEqual( + new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), + ); + }); +}); diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index 64e5799..a4765ba 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -1,7 +1,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 { Assignee, 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'; @@ -17,6 +17,11 @@ import { } from '../../gateways/adb2c/adb2c.service'; import { AdB2cUser } from '../../gateways/adb2c/types/types'; import { CheckoutPermission } from '../../repositories/checkout_permissions/entity/checkout_permission.entity'; +import { UserNotFoundError } from '../../repositories/users/errors/types'; +import { + TasksNotFoundError, + TypistUserGroupNotFoundError, +} from '../../repositories/tasks/errors/types'; @Injectable() export class TasksService { @@ -156,4 +161,47 @@ export class TasksService { // B2Cからユーザー名を取得する return await this.adB2cService.getUsers(filteredExternalIds); } + /** + * Changes checkout permission + * @param audioFileId + * @param assignees + * @returns checkout permission + */ + async changeCheckoutPermission( + audioFileId: number, + assignees: Assignee[], + ): Promise { + try { + await this.taskRepository.changeCheckoutPermission( + audioFileId, + assignees, + ); + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case UserNotFoundError: + case TypistUserGroupNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + case TasksNotFoundError: + throw new HttpException( + makeErrorResponse('E010601'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } 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 3566b91..9918b24 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -11,6 +11,7 @@ import { } from '../../../common/types/sort'; import { AdB2cService } from '../../../gateways/adb2c/adb2c.service'; import { AdB2cUser } from '../../../gateways/adb2c/types/types'; +import { Assignee } from '../types/types'; export type TasksRepositoryMockValue = { getTasksFromAccountId: @@ -34,6 +35,7 @@ export type TasksRepositoryMockValue = { count: number; } | Error; + changeCheckoutPermission: void | Error; }; export type AdB2CServiceMockValue = { @@ -78,6 +80,7 @@ export const makeTasksRepositoryMock = (value: TasksRepositoryMockValue) => { getTasksFromAccountId, getTasksFromAuthorIdAndAccountId, getTasksFromTypistRelations, + changeCheckoutPermission, } = value; return { getTasksFromAccountId: @@ -145,6 +148,14 @@ export const makeTasksRepositoryMock = (value: TasksRepositoryMockValue) => { [] >() .mockResolvedValue(getTasksFromTypistRelations), + changeCheckoutPermission: + changeCheckoutPermission instanceof Error + ? jest + .fn, []>() + .mockRejectedValue(changeCheckoutPermission) + : jest + .fn, [number, Assignee[], number]>() + .mockResolvedValue(changeCheckoutPermission), }; }; @@ -165,6 +176,7 @@ export const makeDefaultTasksRepositoryMockValue = getTasksFromAccountId: defaultTasksRepositoryMockValue, getTasksFromAuthorIdAndAccountId: defaultTasksRepositoryMockValue, getTasksFromTypistRelations: defaultTasksRepositoryMockValue, + changeCheckoutPermission: undefined, }; }; diff --git a/dictation_server/src/features/tasks/types/types.ts b/dictation_server/src/features/tasks/types/types.ts index 0bc2d09..c5aba6d 100644 --- a/dictation_server/src/features/tasks/types/types.ts +++ b/dictation_server/src/features/tasks/types/types.ts @@ -1,10 +1,18 @@ import { ApiProperty } from '@nestjs/swagger'; import { AudioOptionItem } from '../../../features/files/types/types'; import { Type } from 'class-transformer'; -import { IsIn, IsInt, IsOptional, Min } from 'class-validator'; +import { + IsArray, + IsIn, + IsInt, + IsOptional, + Min, + ValidateNested, +} from 'class-validator'; import { TASK_LIST_SORTABLE_ATTRIBUTES } from '../../../constants'; import { IsStatus } from '../../../common/validators/status.validator'; import { Typist } from '../../../features/accounts/types/types'; +import { IsAssignees } from '../../../common/validators/assignees.validator'; export class TasksRequest { @ApiProperty({ @@ -66,11 +74,17 @@ export class Assignee { required: false, description: 'TypistID(TypistIDかTypistGroupIDのどちらかに値が入る)', }) + @IsInt() + @Min(1) + @IsOptional() typistUserId?: number | undefined; @ApiProperty({ required: false, description: 'TypistGroupID(TypistGroupIDかTypistIDのどちらかに値が入る)', }) + @IsInt() + @Min(1) + @IsOptional() typistGroupId?: number | undefined; @ApiProperty({ description: 'Typist名 / TypistGroup名' }) typistName: string; @@ -200,6 +214,10 @@ export class PostCheckoutPermissionRequest { description: '文字起こしに着手可能(チェックアウト可能)にしたい、グループ個人の一覧', }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => Assignee) + @IsAssignees() assignees: Assignee[]; } diff --git a/dictation_server/src/features/users/test/users.service.mock.ts b/dictation_server/src/features/users/test/users.service.mock.ts index 3c34b68..eebac81 100644 --- a/dictation_server/src/features/users/test/users.service.mock.ts +++ b/dictation_server/src/features/users/test/users.service.mock.ts @@ -16,10 +16,6 @@ import { TaskListSortableAttribute, } from '../../../common/types/sort'; -export type CryptoMockValue = { - getPublicKey: string | Error; -}; - export type SortCriteriaRepositoryMockValue = { updateSortCriteria: SortCriteria | Error; getSortCriteria: SortCriteria | Error; @@ -54,6 +50,10 @@ export type SendGridMockValue = { sendMail: undefined | Error; }; +export type ConfigMockValue = { + get: string | Error; +}; + export const makeUsersServiceMock = async ( usersRepositoryMockValue: UsersRepositoryMockValue, adB2cMockValue: AdB2cMockValue, @@ -175,21 +175,6 @@ export const makeAdB2cServiceMock = (value: AdB2cMockValue) => { }; }; -export type ConfigMockValue = { - get: string | Error; -}; - -export const makeCryptoServiceMock = (value: CryptoMockValue) => { - const { getPublicKey } = value; - - return { - getPublicKey: - getPublicKey instanceof Error - ? jest.fn, []>().mockRejectedValue(getPublicKey) - : jest.fn, []>().mockResolvedValue(getPublicKey), - }; -}; - class authorIdError extends Error { constructor(public code: string, e?: string) { super(e); diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 4580ff5..17343f4 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -2,7 +2,6 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { AccessToken } from '../../common/token'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { User as EntityUser } from '../../repositories/users/entity/user.entity'; -import { EmailAlreadyVerifiedError } from '../../repositories/users/users.repository.service'; import { makeDefaultAdB2cMockValue, makeDefaultConfigValue, @@ -12,6 +11,7 @@ import { makeUsersServiceMock, } from './test/users.service.mock'; import { User } from './types/types'; +import { EmailAlreadyVerifiedError } from '../../repositories/users/errors/types'; describe('UsersService', () => { it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになる', async () => { diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 81a5f4c..604ea93 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -19,11 +19,9 @@ import { import { SendGridService } from '../../gateways/sendgrid/sendgrid.service'; import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/sort_criteria.repository.service'; import { User as EntityUser } from '../../repositories/users/entity/user.entity'; -import { - EmailAlreadyVerifiedError, - UsersRepositoryService, -} from '../../repositories/users/users.repository.service'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { User } from './types/types'; +import { EmailAlreadyVerifiedError } from '../../repositories/users/errors/types'; @Injectable() export class UsersService { diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index f61a367..5d26257 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -22,8 +22,7 @@ import { LICENSE_STATUS_ISSUE_REQUESTING, } from '../../constants'; import { LicenseSummaryInfo } from '../../features/accounts/types/types'; - -export class AccountNotFoundError extends Error {} +import { AccountNotFoundError } from './errors/types'; @Injectable() export class AccountsRepositoryService { diff --git a/dictation_server/src/repositories/accounts/errors/types.ts b/dictation_server/src/repositories/accounts/errors/types.ts new file mode 100644 index 0000000..9b5145a --- /dev/null +++ b/dictation_server/src/repositories/accounts/errors/types.ts @@ -0,0 +1,2 @@ +// アカウント未発見エラー +export class AccountNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/licenses/errors/types.ts b/dictation_server/src/repositories/licenses/errors/types.ts new file mode 100644 index 0000000..eca9967 --- /dev/null +++ b/dictation_server/src/repositories/licenses/errors/types.ts @@ -0,0 +1,2 @@ +// POナンバーがすでに存在するエラー +export class PoNumberAlreadyExistError extends Error {} diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 5e140a2..b6b6ce0 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -5,8 +5,7 @@ import { LICENSE_STATUS_ISSUE_REQUESTING, LICENSE_STATUS_ISSUED, } from '../../constants'; - -export class PoNumberAlreadyExistError extends Error {} +import { PoNumberAlreadyExistError } from './errors/types'; @Injectable() export class LicensesRepositoryService { diff --git a/dictation_server/src/repositories/sort_criteria/sort_criteria.repository.service.ts b/dictation_server/src/repositories/sort_criteria/sort_criteria.repository.service.ts index 1edb67b..c1dc4fb 100644 --- a/dictation_server/src/repositories/sort_criteria/sort_criteria.repository.service.ts +++ b/dictation_server/src/repositories/sort_criteria/sort_criteria.repository.service.ts @@ -6,7 +6,6 @@ import { SortDirection, } from '../../common/types/sort'; -export class SortCriteriaNotFoundError extends Error {} @Injectable() export class SortCriteriaRepositoryService { constructor(private dataSource: DataSource) {} diff --git a/dictation_server/src/repositories/tasks/errors/types.ts b/dictation_server/src/repositories/tasks/errors/types.ts new file mode 100644 index 0000000..8d828d4 --- /dev/null +++ b/dictation_server/src/repositories/tasks/errors/types.ts @@ -0,0 +1,6 @@ +// タイピストグループ未発見エラー +export class TypistUserGroupNotFoundError extends Error {} +// タイピストユーザー未発見エラー +export class TypistUserNotFoundError extends Error {} +// タスク未発見エラー +export class TasksNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 28f8226..d89e523 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -4,6 +4,7 @@ import { FindOptionsOrder, FindOptionsOrderValue, In, + IsNull, } from 'typeorm'; import { Task } from './entity/task.entity'; import { TASK_STATUS } from '../../constants'; @@ -16,6 +17,14 @@ import { TaskListSortableAttribute, } from '../../common/types/sort'; import { UserGroupMember } from '../user_groups/entity/user_group_member.entity'; +import { Assignee } from '../../features/tasks/types/types'; +import { UserGroup } from '../user_groups/entity/user_group.entity'; +import { User } from '../users/entity/user.entity'; +import { + TasksNotFoundError, + TypistUserGroupNotFoundError, + TypistUserNotFoundError, +} from './errors/types'; @Injectable() export class TasksRepositoryService { @@ -369,6 +378,97 @@ export class TasksRepositoryService { ); return createdEntity; } + + /** + * Changes checkout permission + * @param audioFileId + * @param assignees + * @param accountId + * @returns checkout permission + */ + async changeCheckoutPermission( + audioFileId: number, + assignees: Assignee[], + ): Promise { + await this.dataSource.transaction(async (entityManager) => { + // UserGroupの取得/存在確認 + const userGroupIds = assignees + .filter((x) => x.typistGroupId !== undefined) + .map((y) => { + return y.typistGroupId; + }); + const groupRepo = entityManager.getRepository(UserGroup); + const groupRecords = await groupRepo.find({ + where: { + id: In(userGroupIds), + deleted_at: IsNull(), + }, + }); + // idはユニークであるため取得件数の一致でグループの存在を確認 + if (userGroupIds.length !== groupRecords.length) { + throw new TypistUserGroupNotFoundError( + `Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map( + (x) => x.id, + )}`, + ); + } + + // Userの取得/存在確認 + const typistUserIds = assignees + .filter((x) => x.typistUserId !== undefined) + .map((y) => { + return y.typistUserId; + }); + const userRepo = entityManager.getRepository(User); + const userRecords = await userRepo.find({ + where: { + id: In(typistUserIds), + deleted_at: IsNull(), + }, + }); + // idはユニークであるため取得件数の一致でユーザーの存在を確認 + if (typistUserIds.length !== userRecords.length) { + throw new TypistUserNotFoundError( + `User not exists Error. reqUserId:${typistUserIds}; resUserId:${userRecords.map( + (x) => x.id, + )}`, + ); + } + + // 引数audioFileIdを使ってTaskレコードを特定し、そのステータスを取得/存在確認 + const taskRepo = entityManager.getRepository(Task); + + const taskRecord = await taskRepo.findOne({ + where: { audio_file_id: audioFileId, status: TASK_STATUS.UPLOADED }, + }); + //タスクが存在しない or ステータスがUploadedでなければエラー + if (!taskRecord) { + throw new TasksNotFoundError( + `Task not found Error. audio_file_id:${audioFileId}`, + ); + } + + // 当該タスクに紐づく既存checkoutPermissionをdelete + const checkoutPermissionRepo = + entityManager.getRepository(CheckoutPermission); + await checkoutPermissionRepo.delete({ + task_id: taskRecord.id, + }); + + // 当該タスクに紐づく新規checkoutPermissionをinsert + const checkoutPermissions: CheckoutPermission[] = assignees.map( + (assignee) => { + const checkoutPermission = new CheckoutPermission(); + checkoutPermission.task_id = taskRecord.id; + checkoutPermission.user_id = assignee.typistUserId; + checkoutPermission.user_group_id = assignee.typistGroupId; + return checkoutPermission; + }, + ); + + return await checkoutPermissionRepo.save(checkoutPermissions); + }); + } } // ソート用オブジェクトを生成する diff --git a/dictation_server/src/repositories/users/errors/types.ts b/dictation_server/src/repositories/users/errors/types.ts new file mode 100644 index 0000000..ddfdb91 --- /dev/null +++ b/dictation_server/src/repositories/users/errors/types.ts @@ -0,0 +1,4 @@ +// Email検証済みエラー +export class EmailAlreadyVerifiedError extends Error {} +// ユーザー未発見エラー +export class UserNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 333ca65..cc7cb61 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -6,13 +6,9 @@ import { getDirection, getTaskListSortableAttribute, } from '../../common/types/sort/util'; +import { UserNotFoundError, EmailAlreadyVerifiedError } from './errors/types'; import { USER_ROLES } from '../../constants'; -// UsersRepositoryServiceで発生するエラーを定義 -export class EmailAlreadyVerifiedError extends Error {} -export class UserNotFoundError extends Error {} -export class AuthorIdAlreadyExistError extends Error {} - @Injectable() export class UsersRepositoryService { constructor(private dataSource: DataSource) {}