## 概要 [Task3457: タスク削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3457) - タスク削除APIとUTを実装しました。 ## レビューポイント - テストケースは適切でしょうか? - リポジトリでの削除処理は適切でしょうか? - エラー時のコード使い分けは適切でしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
835 lines
23 KiB
TypeScript
835 lines
23 KiB
TypeScript
import {
|
||
Body,
|
||
Controller,
|
||
Get,
|
||
HttpException,
|
||
HttpStatus,
|
||
Logger,
|
||
Param,
|
||
ParseIntPipe,
|
||
Post,
|
||
Query,
|
||
Req,
|
||
UseGuards,
|
||
} from '@nestjs/common';
|
||
import {
|
||
ApiResponse,
|
||
ApiOperation,
|
||
ApiTags,
|
||
ApiBearerAuth,
|
||
ApiParam,
|
||
} from '@nestjs/swagger';
|
||
import { ErrorResponse } from '../../common/error/types/types';
|
||
import { Request } from 'express';
|
||
import { TasksService } from './tasks.service';
|
||
import {
|
||
AudioNextRequest,
|
||
AudioNextResponse,
|
||
ChangeStatusRequest,
|
||
ChangeStatusResponse,
|
||
PostCheckoutPermissionRequest,
|
||
PostCheckoutPermissionResponse,
|
||
PostDeleteTaskRequest,
|
||
PostDeleteTaskResponse,
|
||
TasksRequest,
|
||
TasksResponse,
|
||
} from './types/types';
|
||
import {
|
||
SortDirection,
|
||
TaskListSortableAttribute,
|
||
isSortDirection,
|
||
isTaskListSortableAttribute,
|
||
} from '../../common/types/sort';
|
||
import jwt from 'jsonwebtoken';
|
||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||
import { AccessToken } from '../../common/token';
|
||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||
import { Roles } from '../../common/types/role';
|
||
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
|
||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||
|
||
@ApiTags('tasks')
|
||
@Controller('tasks')
|
||
export class TasksController {
|
||
private readonly logger = new Logger(TasksController.name);
|
||
constructor(private readonly taskService: TasksService) {}
|
||
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: TasksResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'getTasks',
|
||
description: '音声ファイル・文字起こしタスク情報をページ指定して取得します',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@Get()
|
||
async getTasks(
|
||
@Req() req,
|
||
@Query() body: TasksRequest,
|
||
): Promise<TasksResponse> {
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId, role } = decodedAccessToken as AccessToken;
|
||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||
const roles = role.split(' ') as Roles[];
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
const { limit, offset, status } = body;
|
||
const paramName = isTaskListSortableAttribute(body.paramName ?? '')
|
||
? (body.paramName as TaskListSortableAttribute)
|
||
: undefined;
|
||
const direction = isSortDirection(body.direction ?? '')
|
||
? (body.direction as SortDirection)
|
||
: undefined;
|
||
|
||
const { tasks, total } = await this.taskService.getTasks(
|
||
context,
|
||
userId,
|
||
roles,
|
||
offset,
|
||
limit,
|
||
// statusが指定されていない場合は全てのステータスを取得する
|
||
status?.split(','),
|
||
paramName,
|
||
direction,
|
||
);
|
||
return { tasks, total, limit, offset };
|
||
}
|
||
|
||
@Get('next')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: AudioNextResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'getNextAudioFile',
|
||
description:
|
||
'指定した文字起こしタスクの次のタスクに紐づく音声ファイルIDを取得します',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [USER_ROLES.TYPIST],
|
||
}),
|
||
)
|
||
async getNextAudioFile(
|
||
@Req() req: Request,
|
||
@Query() param: AudioNextRequest,
|
||
): Promise<AudioNextResponse> {
|
||
const { endedFileId } = param;
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId } = decodedAccessToken as AccessToken;
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
const nextFileId = await this.taskService.getNextTask(
|
||
context,
|
||
userId,
|
||
endedFileId,
|
||
);
|
||
|
||
return { nextFileId };
|
||
}
|
||
|
||
@Post(':audioFileId/checkout')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: ChangeStatusResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない場合',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'checkout',
|
||
description:
|
||
'指定した文字起こしタスクをチェックアウトします(ステータスをInprogressにします)',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [USER_ROLES.AUTHOR, USER_ROLES.TYPIST],
|
||
}),
|
||
)
|
||
async checkout(
|
||
@Req() req: Request,
|
||
@Param() param: ChangeStatusRequest,
|
||
): Promise<ChangeStatusResponse> {
|
||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId, role } = decodedAccessToken as AccessToken;
|
||
|
||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||
const roles = role.split(' ') as Roles[];
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.checkout(context, param.audioFileId, roles, userId);
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/checkin')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: ChangeStatusResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない場合',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'checkin',
|
||
description:
|
||
'指定した文字起こしタスクをチェックインします(ステータスをFinishedにします)',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [USER_ROLES.TYPIST],
|
||
}),
|
||
)
|
||
async checkin(
|
||
@Req() req: Request,
|
||
@Param() params: ChangeStatusRequest,
|
||
): Promise<ChangeStatusResponse> {
|
||
const { audioFileId } = params;
|
||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId } = decodedAccessToken as AccessToken;
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.checkin(context, audioFileId, userId);
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/cancel')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: ChangeStatusResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない場合',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'cancel',
|
||
description:
|
||
'指定した文字起こしタスクをキャンセルします(ステータスをUploadedにします)',
|
||
})
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [ADMIN_ROLES.ADMIN, USER_ROLES.TYPIST],
|
||
}),
|
||
)
|
||
@ApiBearerAuth()
|
||
async cancel(
|
||
@Req() req: Request,
|
||
@Param() params: ChangeStatusRequest,
|
||
): Promise<ChangeStatusResponse> {
|
||
const { audioFileId } = params;
|
||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId, role } = decodedAccessToken as AccessToken;
|
||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||
const roles = role.split(' ') as Roles[];
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.cancel(context, audioFileId, userId, roles);
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/suspend')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: ChangeStatusResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない場合',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'suspend',
|
||
description:
|
||
'指定した文字起こしタスクを一時中断します(ステータスをPendingにします)',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [USER_ROLES.TYPIST],
|
||
}),
|
||
)
|
||
async suspend(
|
||
@Req() req: Request,
|
||
@Param() params: ChangeStatusRequest,
|
||
): Promise<ChangeStatusResponse> {
|
||
const { audioFileId } = params;
|
||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId } = decodedAccessToken as AccessToken;
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.suspend(context, audioFileId, userId);
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/backup')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: ChangeStatusResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない場合',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'backup',
|
||
description:
|
||
'指定した文字起こしタスクをバックアップします(ステータスをBackupにします)',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({
|
||
roles: [ADMIN_ROLES.ADMIN],
|
||
}),
|
||
)
|
||
async backup(
|
||
@Req() req: Request,
|
||
@Param() params: ChangeStatusRequest,
|
||
): Promise<ChangeStatusResponse> {
|
||
const { audioFileId } = params;
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId } = decodedAccessToken as AccessToken;
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.backup(context, audioFileId, userId);
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/checkout-permission')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: PostCheckoutPermissionResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description:
|
||
'不正なパラメータ(タスクのステータス不正、指定ユーザー不正など)',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.NOT_FOUND,
|
||
description: '指定したIDの音声ファイルが存在しない',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'changeCheckoutPermission',
|
||
description: '指定した文字起こしタスクのチェックアウト候補を変更します。',
|
||
})
|
||
@ApiParam({
|
||
name: 'audioFileId',
|
||
required: true,
|
||
description: 'ODMS Cloud上の音声ファイルID',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN, USER_ROLES.AUTHOR] }),
|
||
)
|
||
async changeCheckoutPermission(
|
||
@Req() req: Request,
|
||
//TODO [Task2243] checkoutやcheckinと同じパスパラメータなので記述方法を統一したい
|
||
@Param(`audioFileId`, ParseIntPipe)
|
||
audioFileId: number,
|
||
@Body() body: PostCheckoutPermissionRequest,
|
||
): Promise<PostCheckoutPermissionResponse> {
|
||
const { assignees } = body;
|
||
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId, role } = decodedAccessToken as AccessToken;
|
||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||
const roles = role.split(' ') as Roles[];
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.changeCheckoutPermission(
|
||
context,
|
||
audioFileId,
|
||
assignees,
|
||
userId,
|
||
roles,
|
||
);
|
||
|
||
return {};
|
||
}
|
||
|
||
@Post(':audioFileId/delete')
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: PostDeleteTaskResponse,
|
||
description: '成功時のレスポンス',
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.BAD_REQUEST,
|
||
description: '不正なパラメータ',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.UNAUTHORIZED,
|
||
description: '認証エラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: '想定外のサーバーエラー',
|
||
type: ErrorResponse,
|
||
})
|
||
@ApiOperation({
|
||
operationId: 'deleteTask',
|
||
description: '指定した文字起こしタスクを削除します。',
|
||
})
|
||
@ApiBearerAuth()
|
||
@UseGuards(AuthGuard)
|
||
@UseGuards(
|
||
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN, USER_ROLES.AUTHOR] }),
|
||
)
|
||
async deleteTask(
|
||
@Req() req: Request,
|
||
@Param() params: PostDeleteTaskRequest,
|
||
): Promise<PostDeleteTaskResponse> {
|
||
const { audioFileId } = params;
|
||
|
||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||
const accessToken = retrieveAuthorizationToken(req);
|
||
if (!accessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000107'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const ip = retrieveIp(req);
|
||
if (!ip) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000401'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
|
||
const requestId = retrieveRequestId(req);
|
||
if (!requestId) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000501'),
|
||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||
);
|
||
}
|
||
|
||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||
if (!decodedAccessToken) {
|
||
throw new HttpException(
|
||
makeErrorResponse('E000101'),
|
||
HttpStatus.UNAUTHORIZED,
|
||
);
|
||
}
|
||
const { userId } = decodedAccessToken as AccessToken;
|
||
|
||
const context = makeContext(userId, requestId);
|
||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||
|
||
await this.taskService.deleteTask(context, userId, audioFileId);
|
||
return {};
|
||
}
|
||
}
|