saito.k f1583cf783 Merged PR 611: 操作を特定できる文字列を追跡用のIDに追加する(IPアドレスもログに出力する)
## 概要
[Task3265: IPアドレスを追跡用のIDに追加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3265)

- MiddlewareでUUIDを発行しリクエストのヘッダに追加する
- 各コントローラーではヘッダからUUIDとIPアドレスを取得する
  - 取得したUUIDとADB2Cの外部IDでトラッキングIDを作成する
  - 作成したトラッキングIDとIPアドレスの繋がりをログに出力する。

## レビューポイント
- ADB2Cの外部IDがない場合にUnauthorized Userという文字列を入れているがほかの表現のほうが良いか
  - 外部IDもオプショナルにして入れなくてもよくする?
-

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2023-12-12 04:11:36 +00:00

539 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Logger,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import jwt from 'jsonwebtoken';
import { AccessToken } from '../../common/token';
import { ErrorResponse } from '../../common/error/types/types';
import { FilesService } from './files.service';
import {
AudioDownloadLocationRequest,
AudioDownloadLocationResponse,
AudioUploadFinishedRequest,
AudioUploadFinishedResponse,
AudioUploadLocationRequest,
AudioUploadLocationResponse,
TemplateDownloadLocationRequest,
TemplateDownloadLocationResponse,
TemplateUploadFinishedReqponse,
TemplateUploadFinishedRequest,
TemplateUploadLocationResponse,
} from './types/types';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { retrieveAuthorizationToken } from '../../common/http/helper';
import { Request } from 'express';
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('files')
@Controller('files')
export class FilesController {
private readonly logger = new Logger(FilesController.name);
constructor(private readonly filesService: FilesService) {}
@ApiResponse({
status: HttpStatus.OK,
type: AudioUploadFinishedResponse,
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: 'uploadFinished',
description:
'アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@Post('audio/upload-finished')
async uploadFinished(
@Req() req: Request,
@Body() body: AudioUploadFinishedRequest,
): Promise<AudioUploadFinishedResponse> {
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 {
url,
authorId,
fileName,
duration,
createdDate,
finishedDate,
uploadedDate,
fileSize,
priority,
audioFormat,
comment,
workType,
optionItemList,
isEncrypted,
} = body;
const res = await this.filesService.uploadFinished(
context,
userId,
url,
authorId,
fileName,
duration,
createdDate,
finishedDate,
uploadedDate,
fileSize,
priority,
audioFormat,
comment,
workType,
optionItemList,
isEncrypted,
);
return { jobNumber: res.jobNumber };
}
@Get('audio/upload-location')
@ApiResponse({
status: HttpStatus.OK,
type: AudioUploadLocationResponse,
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: 'uploadLocation',
description:
'ログイン中ユーザー用のBlob Storage上の音声ファイルのアップロード先アクセスURLを取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
async uploadLocation(
@Req() req: Request,
// クエリパラメータ AudioUploadLocationRequest は空であるため内部で使用しない。
// 使用しないことを宣言するために先頭にプレフィックス_アンダースコアをつけている
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query() _query: AudioUploadLocationRequest,
): Promise<AudioUploadLocationResponse> {
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 url = await this.filesService.publishUploadSas(context, userId);
return { url };
}
@Get('audio/download-location')
@ApiResponse({
status: HttpStatus.OK,
type: AudioDownloadLocationResponse,
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: 'downloadLocation',
description:
'指定した音声ファイルのBlob Storage上のダウンロード先アクセスURLを取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({
roles: [ADMIN_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.TYPIST],
}),
)
async downloadLocation(
@Req() req: Request,
@Query() body: AudioDownloadLocationRequest,
): Promise<AudioDownloadLocationResponse> {
const { audioFileId } = 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 } = decodedAccessToken as AccessToken;
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishAudioFileDownloadSas(
context,
userId,
audioFileId,
);
return { url };
}
@Get('template/download-location')
@ApiResponse({
status: HttpStatus.OK,
type: TemplateDownloadLocationResponse,
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: 'downloadTemplateLocation',
description:
'指定した音声ファイルに対応したテンプレートファイルのBlob Storage上のダウンロード先アクセスURLを取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR, USER_ROLES.TYPIST] }),
)
async downloadTemplateLocation(
@Req() req: Request,
@Query() body: TemplateDownloadLocationRequest,
): Promise<TemplateDownloadLocationResponse> {
const { audioFileId } = 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 } = decodedAccessToken as AccessToken;
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishTemplateFileDownloadSas(
context,
userId,
audioFileId,
);
return { url };
}
@Get('template/upload-location')
@ApiResponse({
status: HttpStatus.OK,
type: TemplateUploadLocationResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({
operationId: 'uploadTemplateLocation',
description:
'ログイン中ユーザー用のBlob Storage上のテンプレートファイルのアップロード先アクセスURLを取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
)
async uploadTemplateLocation(
@Req() req: Request,
): Promise<TemplateUploadLocationResponse> {
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 url = await this.filesService.publishTemplateFileUploadSas(
context,
userId,
);
return { url };
}
@ApiResponse({
status: HttpStatus.OK,
type: TemplateUploadFinishedReqponse,
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: 'uploadTemplateFinished',
description: 'アップロードが完了したテンプレートファイルの情報を登録します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
)
@Post('template/upload-finished')
async templateUploadFinished(
@Req() req: Request,
@Body() body: TemplateUploadFinishedRequest,
): Promise<TemplateUploadFinishedReqponse> {
const { name, url } = 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 } = decodedAccessToken as AccessToken;
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.filesService.templateUploadFinished(context, userId, url, name);
return {};
}
}