Merged PR 210: API実装(テンプレートファイルDL元)
## 概要 [Task2039: API実装(テンプレートファイルDL元)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2039) - テンプレートファイルダウンロードURL取得API&テストを実装しました。 - 構成は音声ファイルDLと同様でBlobストレージアクセス部分は共通のメソッドを使用しています。 - テンプレートファイルは手動でBlob、DBに追加して確認しています。 ## レビューポイント - 音声ファイルとほどんど同じ処理だが内容に問題はないか - 共通部分の構成に問題はないか - テスト項目に問題はないか ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
4bbd9b371d
commit
869880c204
@ -39,6 +39,7 @@ import { LicensesController } from './features/licenses/licenses.controller';
|
||||
import { CheckoutPermissionsRepositoryModule } from './repositories/checkout_permissions/checkout_permissions.repository.module';
|
||||
import { UserGroupsRepositoryModule } from './repositories/user_groups/user_groups.repository.module';
|
||||
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -66,6 +67,7 @@ import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_
|
||||
TasksRepositoryModule,
|
||||
CheckoutPermissionsRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
TemplateFilesRepositoryModule,
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
|
||||
@ -1,2 +1,12 @@
|
||||
// 音声ファイル不在エラー
|
||||
export class AudioFileNotFoundError extends Error {}
|
||||
// テンプレートファイル不在エラー
|
||||
export class TemplateFileNotFoundError extends Error {}
|
||||
// Account不一致エラー
|
||||
export class AccountNotMatchError extends Error {}
|
||||
// Status不一致エラー
|
||||
export class StatusNotMatchError extends Error {}
|
||||
// Author不一致エラー
|
||||
export class AuthorUserNotMatchError extends Error {}
|
||||
// TypistUser不一致エラー
|
||||
export class TypistUserNotMatchError extends Error {}
|
||||
|
||||
@ -2,7 +2,6 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Query,
|
||||
@ -225,14 +224,23 @@ export class FilesController {
|
||||
'指定した音声ファイルに対応したテンプレートファイルのBlob Storage上のダウンロード先アクセスURLを取得します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR, USER_ROLES.TYPIST] }),
|
||||
)
|
||||
async downloadTemplateLocation(
|
||||
@Headers() headers,
|
||||
@Req() req: Request,
|
||||
@Query() body: TemplateDownloadLocationRequest,
|
||||
): Promise<TemplateDownloadLocationResponse> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { audioFileId } = body;
|
||||
// コンテナ作成処理の前にアクセストークンの認証を行う
|
||||
|
||||
return { url: '' };
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const url = await this.filesService.publishTemplateFileDownloadSas(
|
||||
accessToken.userId,
|
||||
audioFileId,
|
||||
);
|
||||
|
||||
return { url };
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
const { externalId, userId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
@ -315,6 +315,9 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -329,14 +332,13 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
).toEqual(`${url}?sas-token`);
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Uploaded,Inprogress,Pending]以外でエラー', async () => {
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
@ -345,6 +347,8 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'Finished',
|
||||
userId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -354,9 +358,85 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
await service.publishAudioFileDownloadSas(externalId, audioFileId),
|
||||
).toEqual(`${url}?sas-token`);
|
||||
await expect(
|
||||
service.publishAudioFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
);
|
||||
const { userId: otherId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'other-typist-user-external-id',
|
||||
'typist',
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
otherId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
'OTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishAudioFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
@ -383,7 +463,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
const { externalId, userId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
@ -397,6 +477,9 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -414,6 +497,225 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
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('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
await service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).toEqual(`${url}?sas-token`);
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'Finished',
|
||||
userId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const { userId: otherId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'other-typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
otherId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
'OTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, 1),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishTemplateFileDownloadSas(externalId, audioFileId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const optionItemList = [
|
||||
{
|
||||
optionItemLabel: 'label_01',
|
||||
|
||||
@ -5,10 +5,23 @@ import { UsersRepositoryService } from '../../repositories/users/users.repositor
|
||||
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import { AudioOptionItem, AudioUploadFinishedResponse } from './types/types';
|
||||
import { OPTION_ITEM_NUM, USER_ROLES } from '../../constants/index';
|
||||
import {
|
||||
OPTION_ITEM_NUM,
|
||||
TASK_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants/index';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { AudioFileNotFoundError } from './errors/types';
|
||||
import { TasksNotFoundError } from '../../repositories/tasks/errors/types';
|
||||
import {
|
||||
AccountNotMatchError,
|
||||
AudioFileNotFoundError,
|
||||
AuthorUserNotMatchError,
|
||||
StatusNotMatchError,
|
||||
TemplateFileNotFoundError,
|
||||
} from './errors/types';
|
||||
import {
|
||||
TasksNotFoundError,
|
||||
TypistUserNotFoundError,
|
||||
} from '../../repositories/tasks/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
@ -223,15 +236,18 @@ export class FilesService {
|
||||
): Promise<string> {
|
||||
//DBから国情報とアカウントID,ユーザーIDを取得する
|
||||
let accountId: number;
|
||||
let country: string;
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
accountId = user.account.id;
|
||||
userId = user.id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
console.log(e);
|
||||
@ -242,11 +258,16 @@ export class FilesService {
|
||||
}
|
||||
|
||||
try {
|
||||
const { file } = await this.tasksRepository.getTaskAndAudioFile(
|
||||
const status = isTypist
|
||||
? [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING]
|
||||
: Object.values(TASK_STATUS);
|
||||
|
||||
const task = await this.tasksRepository.getTaskAndAudioFile(
|
||||
audioFileId,
|
||||
accountId,
|
||||
isTypist,
|
||||
status,
|
||||
);
|
||||
const file = task.file;
|
||||
|
||||
// タスクに紐づく音声ファイルだけが消される場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
@ -256,12 +277,26 @@ export class FilesService {
|
||||
);
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
|
||||
if (isTypist && task.typist_user_id !== userId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = `${userId}/${file.file_name}`;
|
||||
|
||||
const isFileExist = await this.blobStorageService.fileExists(
|
||||
accountId,
|
||||
country,
|
||||
filePath
|
||||
filePath,
|
||||
);
|
||||
|
||||
if (!isFileExist) {
|
||||
@ -282,6 +317,10 @@ export class FilesService {
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TasksNotFoundError:
|
||||
case AccountNotMatchError:
|
||||
case StatusNotMatchError:
|
||||
case AuthorUserNotMatchError:
|
||||
case TypistUserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010603'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
@ -304,4 +343,124 @@ export class FilesService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したIDの音声ファイルに紐づいた文字起こしテンプレートファイルのダウンロードURLを取得する
|
||||
* @param externalId
|
||||
* @param audioFileId
|
||||
* @returns template file download sas
|
||||
*/
|
||||
async publishTemplateFileDownloadSas(
|
||||
externalId: string,
|
||||
audioFileId: number,
|
||||
): Promise<string> {
|
||||
//DBから国情報とアカウントID,ユーザーIDを取得する
|
||||
let accountId: number;
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
accountId = user.account.id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
console.log(e);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const status = isTypist
|
||||
? [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING]
|
||||
: Object.values(TASK_STATUS);
|
||||
|
||||
const task = await this.tasksRepository.getTaskAndAudioFile(
|
||||
audioFileId,
|
||||
accountId,
|
||||
status,
|
||||
);
|
||||
|
||||
const template_file = task.template_file;
|
||||
|
||||
// タスクに紐づくテンプレートファイルがない場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
if (!template_file) {
|
||||
throw new TemplateFileNotFoundError(
|
||||
`Template file is not exists in DB. audio_file_id:${audioFileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
|
||||
if (isTypist && task.typist_user_id !== userId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = `Templates/${template_file.file_name}`;
|
||||
|
||||
const isFileExist = await this.blobStorageService.fileExists(
|
||||
accountId,
|
||||
country,
|
||||
filePath,
|
||||
);
|
||||
|
||||
if (!isFileExist) {
|
||||
throw new TemplateFileNotFoundError(
|
||||
`Template file is not exists in blob storage. audio_file_id:${audioFileId}, url:${template_file.url}, fileName:${template_file.file_name}`,
|
||||
);
|
||||
}
|
||||
|
||||
// SASトークン発行
|
||||
const url = await this.blobStorageService.publishDownloadSas(
|
||||
accountId,
|
||||
country,
|
||||
filePath,
|
||||
);
|
||||
return url;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TasksNotFoundError:
|
||||
case AccountNotMatchError:
|
||||
case StatusNotMatchError:
|
||||
case AuthorUserNotMatchError:
|
||||
case TypistUserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010603'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case TemplateFileNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010701'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ import {
|
||||
} from './files.service.mock';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
@ -65,7 +66,7 @@ export const createUser = async (
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
): Promise<{ userId: number; externalId: string; authorId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
@ -82,14 +83,17 @@ export const createUser = async (
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id };
|
||||
return { userId: user.id, externalId: external_id, authorId: author_id };
|
||||
};
|
||||
|
||||
export const createTask = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
url: string,
|
||||
filename: string,
|
||||
fileName: string,
|
||||
status: string,
|
||||
typist_user_id?: number | undefined,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ audioFileId: number }> => {
|
||||
const { identifiers: audioFileIdentifiers } = await datasource
|
||||
.getRepository(AudioFile)
|
||||
@ -97,8 +101,8 @@ export const createTask = async (
|
||||
account_id: account_id,
|
||||
owner_user_id: 1,
|
||||
url: url,
|
||||
file_name: filename,
|
||||
author_id: 'AUTHOR_ID',
|
||||
file_name: fileName,
|
||||
author_id: author_id ?? 'DEFAULT_ID',
|
||||
work_type_id: 'work_type_id',
|
||||
started_at: new Date(),
|
||||
duration: '100000',
|
||||
@ -110,19 +114,32 @@ export const createTask = async (
|
||||
is_encrypted: true,
|
||||
});
|
||||
const audioFile = audioFileIdentifiers.pop() as AudioFile;
|
||||
const { identifiers: taskIdentifiers } = await datasource
|
||||
.getRepository(Task)
|
||||
const { identifiers: templateFileIdentifiers } = await datasource
|
||||
.getRepository(TemplateFile)
|
||||
.insert({
|
||||
job_number: '00000001',
|
||||
account_id: account_id,
|
||||
is_job_number_enabled: true,
|
||||
audio_file_id: audioFile.id,
|
||||
status: 'Uploaded',
|
||||
priority: '01',
|
||||
started_at: new Date().toISOString(),
|
||||
url: url,
|
||||
file_name: fileName,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
|
||||
const templateFile = templateFileIdentifiers.pop() as TemplateFile;
|
||||
await datasource.getRepository(Task).insert({
|
||||
job_number: '00000001',
|
||||
account_id: account_id,
|
||||
is_job_number_enabled: true,
|
||||
audio_file_id: audioFile.id,
|
||||
template_file_id: templateFile.id,
|
||||
typist_user_id: typist_user_id,
|
||||
status: status,
|
||||
priority: '01',
|
||||
started_at: new Date().toISOString(),
|
||||
created_at: new Date(),
|
||||
});
|
||||
|
||||
return { audioFileId: audioFile.id };
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AudioOptionItem } from '../../../repositories/audio_option_items/entity/audio_option_item.entity';
|
||||
import { AudioFile } from '../../../repositories/audio_files/entity/audio_file.entity';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { TemplateFile } from '../../template_files/entity/template_file.entity';
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
@ -8,8 +9,8 @@ import {
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
} from 'typeorm';
|
||||
import { TemplateFile } from '../../template_files/entity/template_file.entity';
|
||||
|
||||
@Entity({ name: 'tasks' })
|
||||
export class Task {
|
||||
@ -45,6 +46,7 @@ export class Task {
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: 'typist_user_id' })
|
||||
typist_user?: User;
|
||||
@ManyToOne(() => TemplateFile, (templateFile) => templateFile.id)
|
||||
@JoinColumn({ name: 'template_file_id' })
|
||||
template_file?: TemplateFile;
|
||||
}
|
||||
|
||||
@ -28,31 +28,34 @@ import {
|
||||
TypistUserNotFoundError,
|
||||
} from './errors/types';
|
||||
import { Roles } from '../../common/types/role';
|
||||
import {
|
||||
AccountNotMatchError,
|
||||
StatusNotMatchError,
|
||||
} from '../../features/files/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class TasksRepositoryService {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
|
||||
/**
|
||||
* 音声ファイルと紐づいたTaskを取得する
|
||||
* @param audioFileId
|
||||
* @param account_id
|
||||
* @param status 配列で設定したステータスのタスクのみ取得
|
||||
* @returns task and audio file
|
||||
*/
|
||||
async getTaskAndAudioFile(
|
||||
audioFileId: number,
|
||||
account_id: number,
|
||||
isTypist: boolean
|
||||
status: string[],
|
||||
): Promise<Task> {
|
||||
const status = isTypist
|
||||
? [TASK_STATUS.UPLOADED, TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING]
|
||||
: [TASK_STATUS.UPLOADED, TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING, TASK_STATUS.FINISHED, TASK_STATUS.BACKUP];
|
||||
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
// 指定した音声ファイルIDに紐づくTaskの中でAuthorIDが一致するものを取得
|
||||
// 指定した音声ファイルIDに紐づくTaskの中でステータスが一致するものを取得
|
||||
const task = await taskRepo.findOne({
|
||||
relations: {
|
||||
file: true,
|
||||
template_file: true,
|
||||
},
|
||||
where: {
|
||||
audio_file_id: audioFileId,
|
||||
@ -66,6 +69,19 @@ export class TasksRepositoryService {
|
||||
);
|
||||
}
|
||||
|
||||
// アカウントチェック
|
||||
if (task.account_id !== account_id) {
|
||||
throw new AccountNotMatchError(
|
||||
`task account_id not match. audio_file_id:${audioFileId}, task.account_id:${task.account_id}, account_id:${account_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ステータスチェック
|
||||
if (!status.includes(task.status)) {
|
||||
throw new StatusNotMatchError(
|
||||
`Unexpected task status. status:${task.status}`,
|
||||
);
|
||||
}
|
||||
return task;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { Task } from '../../tasks/entity/task.entity';
|
||||
|
||||
@Entity({ name: 'audio_files' })
|
||||
@Entity({ name: 'template_files' })
|
||||
export class TemplateFile {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@ -10,4 +18,16 @@ export class TemplateFile {
|
||||
url: string;
|
||||
@Column()
|
||||
file_name: string;
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
@Column()
|
||||
created_by: string;
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
@Column()
|
||||
updated_by: string;
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
@OneToMany(() => Task, (task) => task.template_file)
|
||||
tasks?: Task[];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user