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 { 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 { 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 { 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 { 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 { 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 { 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 {}; } }