Merged PR 739: テンプレートファイル削除API実装
## 概要 [Task3599: テンプレートファイル削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3599) - テンプレートファイル削除APIとテストを実装しました。 ## レビューポイント - テンプレートファイル削除できないエラーの条件は適切でしょうか? - テストケースは適切でしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
447b0e280c
commit
83efd97bdf
@ -0,0 +1,5 @@
|
|||||||
|
-- +migrate Up
|
||||||
|
ALTER TABLE `tasks` ADD INDEX `idx_tasks_template_file_id` (template_file_id);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
ALTER TABLE `tasks` DROP INDEX `idx_tasks_template_file_id`;
|
||||||
@ -79,4 +79,7 @@ export const ErrorCodes = [
|
|||||||
'E015001', // タイピストグループ削除エラー(削除しようとしたタイピストグループがすでに削除済みだった)
|
'E015001', // タイピストグループ削除エラー(削除しようとしたタイピストグループがすでに削除済みだった)
|
||||||
'E015002', // タイピストグループ削除エラー(削除しようとしたタイピストグループがWorkflowのTypist候補として指定されていた)
|
'E015002', // タイピストグループ削除エラー(削除しようとしたタイピストグループがWorkflowのTypist候補として指定されていた)
|
||||||
'E015003', // タイピストグループ削除エラー(削除しようとしたタイピストグループがチェックアウト可能なタスクが存在した)
|
'E015003', // タイピストグループ削除エラー(削除しようとしたタイピストグループがチェックアウト可能なタスクが存在した)
|
||||||
|
'E016001', // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがすでに削除済みだった)
|
||||||
|
'E016002', // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがWorkflowに指定されていた)
|
||||||
|
'E016003', // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルが未完了のタスクに紐づいていた)
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@ -68,4 +68,8 @@ export const errors: Errors = {
|
|||||||
E015001: 'Typist Group delete failed Error: already deleted',
|
E015001: 'Typist Group delete failed Error: already deleted',
|
||||||
E015002: 'Typist Group delete failed Error: workflow assigned',
|
E015002: 'Typist Group delete failed Error: workflow assigned',
|
||||||
E015003: 'Typist Group delete failed Error: checkout permission existed',
|
E015003: 'Typist Group delete failed Error: checkout permission existed',
|
||||||
|
E016001: 'Template file delete failed Error: already deleted',
|
||||||
|
E016002: 'Template file delete failed Error: workflow assigned',
|
||||||
|
E016003:
|
||||||
|
'Template file delete failed Error: not finished task has template file',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -273,6 +273,16 @@ export const getTask = async (
|
|||||||
return task;
|
return task;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTasks = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
account_id: number,
|
||||||
|
): Promise<Task[]> => {
|
||||||
|
const tasks = await datasource.getRepository(Task).find({
|
||||||
|
where: { account_id: account_id },
|
||||||
|
});
|
||||||
|
return tasks;
|
||||||
|
};
|
||||||
|
|
||||||
export const getCheckoutPermissions = async (
|
export const getCheckoutPermissions = async (
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
task_id: number,
|
task_id: number,
|
||||||
|
|||||||
@ -18,7 +18,11 @@ import {
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { AccessToken } from '../../common/token';
|
import { AccessToken } from '../../common/token';
|
||||||
import { ErrorResponse } from '../../common/error/types/types';
|
import { ErrorResponse } from '../../common/error/types/types';
|
||||||
import { DeleteTemplateRequestParam, DeleteTemplateResponse, GetTemplatesResponse } from './types/types';
|
import {
|
||||||
|
DeleteTemplateRequestParam,
|
||||||
|
DeleteTemplateResponse,
|
||||||
|
GetTemplatesResponse,
|
||||||
|
} from './types/types';
|
||||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||||
import { ADMIN_ROLES } from '../../constants';
|
import { ADMIN_ROLES } from '../../constants';
|
||||||
@ -132,7 +136,7 @@ export class TemplatesController {
|
|||||||
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
|
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
|
||||||
)
|
)
|
||||||
@Post(':templateFileId/delete')
|
@Post(':templateFileId/delete')
|
||||||
async deleteTypistGroup(
|
async deleteTemplateFile(
|
||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Param() param: DeleteTemplateRequestParam,
|
@Param() param: DeleteTemplateRequestParam,
|
||||||
): Promise<DeleteTemplateResponse> {
|
): Promise<DeleteTemplateResponse> {
|
||||||
@ -174,7 +178,7 @@ export class TemplatesController {
|
|||||||
const context = makeContext(userId, requestId);
|
const context = makeContext(userId, requestId);
|
||||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||||
|
|
||||||
// TODO: service層呼び出し
|
await this.templatesService.deleteTemplate(context, userId, templateFileId);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,14 @@ import { TemplatesController } from './templates.controller';
|
|||||||
import { TemplatesService } from './templates.service';
|
import { TemplatesService } from './templates.service';
|
||||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||||
import { TemplateFilesRepositoryModule } from '../../repositories/template_files/template_files.repository.module';
|
import { TemplateFilesRepositoryModule } from '../../repositories/template_files/template_files.repository.module';
|
||||||
|
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersRepositoryModule, TemplateFilesRepositoryModule],
|
imports: [
|
||||||
|
UsersRepositoryModule,
|
||||||
|
TemplateFilesRepositoryModule,
|
||||||
|
BlobstorageModule,
|
||||||
|
],
|
||||||
providers: [TemplatesService],
|
providers: [TemplatesService],
|
||||||
controllers: [TemplatesController],
|
controllers: [TemplatesController],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,13 +1,26 @@
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { makeTestingModule } from '../../common/test/modules';
|
import { makeTestingModule } from '../../common/test/modules';
|
||||||
import { TemplatesService } from './templates.service';
|
import { TemplatesService } from './templates.service';
|
||||||
import { createTemplateFile } from './test/utility';
|
import {
|
||||||
import { makeTestAccount } from '../../common/test/utility';
|
createTemplateFile,
|
||||||
|
getTemplateFiles,
|
||||||
|
updateTaskTemplateFile,
|
||||||
|
} from './test/utility';
|
||||||
|
import { makeTestAccount, makeTestUser } from '../../common/test/utility';
|
||||||
import { makeContext } from '../../common/log';
|
import { makeContext } from '../../common/log';
|
||||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||||
import { truncateAllTable } from '../../common/test/init';
|
import { truncateAllTable } from '../../common/test/init';
|
||||||
|
import { overrideBlobstorageService } from '../../common/test/overrides';
|
||||||
|
import { TASK_STATUS, USER_ROLES } from '../../constants';
|
||||||
|
import { createTask, getTasks } from '../tasks/test/utility';
|
||||||
|
import {
|
||||||
|
createWorkflow,
|
||||||
|
createWorkflowTypist,
|
||||||
|
getWorkflow,
|
||||||
|
} from '../workflows/test/utility';
|
||||||
|
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||||
|
|
||||||
describe('getTemplates', () => {
|
describe('getTemplates', () => {
|
||||||
let source: DataSource | null = null;
|
let source: DataSource | null = null;
|
||||||
@ -129,3 +142,355 @@ describe('getTemplates', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('deleteTemplate', () => {
|
||||||
|
let source: DataSource | null = null;
|
||||||
|
beforeAll(async () => {
|
||||||
|
if (source == null) {
|
||||||
|
source = await (async () => {
|
||||||
|
const s = new DataSource({
|
||||||
|
type: 'mysql',
|
||||||
|
host: 'test_mysql_db',
|
||||||
|
port: 3306,
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
database: 'odms',
|
||||||
|
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||||
|
synchronize: false, // trueにすると自動的にmigrationが行われるため注意
|
||||||
|
});
|
||||||
|
return await s.initialize();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
if (source) {
|
||||||
|
await truncateAllTable(source);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await source?.destroy();
|
||||||
|
source = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('指定したテンプレートファイルを削除できる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
|
||||||
|
const service = module.get<TemplatesService>(TemplatesService);
|
||||||
|
const blobStorageService =
|
||||||
|
module.get<BlobstorageService>(BlobstorageService);
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
const { id: authorId } = await makeTestUser(source, {
|
||||||
|
account_id: account.id,
|
||||||
|
role: USER_ROLES.AUTHOR,
|
||||||
|
author_id: 'authorId',
|
||||||
|
});
|
||||||
|
const context = makeContext(admin.external_id, 'requestId');
|
||||||
|
|
||||||
|
const template1 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test1',
|
||||||
|
'https://url1/test1',
|
||||||
|
);
|
||||||
|
const template2 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test2',
|
||||||
|
'https://url2/test2',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { taskId: taskId1 } = await createTask(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
authorId,
|
||||||
|
'authorId',
|
||||||
|
'',
|
||||||
|
'01',
|
||||||
|
'00000001',
|
||||||
|
TASK_STATUS.FINISHED,
|
||||||
|
);
|
||||||
|
await updateTaskTemplateFile(source, taskId1, template1.id);
|
||||||
|
|
||||||
|
const { taskId: taskId2 } = await createTask(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
authorId,
|
||||||
|
'authorId',
|
||||||
|
'',
|
||||||
|
'01',
|
||||||
|
'00000002',
|
||||||
|
TASK_STATUS.BACKUP,
|
||||||
|
);
|
||||||
|
await updateTaskTemplateFile(source, taskId2, template1.id);
|
||||||
|
|
||||||
|
// 作成したデータを確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(2);
|
||||||
|
expect(templates[0].id).toBe(template1.id);
|
||||||
|
expect(templates[0].file_name).toBe(template1.file_name);
|
||||||
|
expect(templates[1].id).toBe(template2.id);
|
||||||
|
expect(templates[1].file_name).toBe(template2.file_name);
|
||||||
|
|
||||||
|
const tasks = await getTasks(source, account.id);
|
||||||
|
expect(tasks.length).toBe(2);
|
||||||
|
expect(tasks[0].template_file_id).toBe(template1.id);
|
||||||
|
expect(tasks[1].template_file_id).toBe(template1.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await service.deleteTemplate(context, admin.external_id, template1.id);
|
||||||
|
|
||||||
|
//実行結果を確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(1);
|
||||||
|
expect(templates[0].id).toBe(template2.id);
|
||||||
|
expect(templates[0].file_name).toBe(template2.file_name);
|
||||||
|
|
||||||
|
const tasks = await getTasks(source, account.id);
|
||||||
|
expect(tasks.length).toBe(2);
|
||||||
|
expect(tasks[0].template_file_id).toBe(null);
|
||||||
|
expect(tasks[1].template_file_id).toBe(null);
|
||||||
|
|
||||||
|
// Blob削除メソッドが呼ばれているか確認
|
||||||
|
expect(blobStorageService.deleteFile).toBeCalledWith(
|
||||||
|
context,
|
||||||
|
account.id,
|
||||||
|
account.country,
|
||||||
|
'Templates/test1',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('指定したテンプレートファイルが存在しない場合、400エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
|
||||||
|
const service = module.get<TemplatesService>(TemplatesService);
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
|
||||||
|
const context = makeContext(admin.external_id, 'requestId');
|
||||||
|
|
||||||
|
const template1 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test1',
|
||||||
|
'https://url1/test1',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 作成したデータを確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(1);
|
||||||
|
expect(templates[0].id).toBe(template1.id);
|
||||||
|
expect(templates[0].file_name).toBe(template1.file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//実行結果を確認
|
||||||
|
try {
|
||||||
|
await service.deleteTemplate(context, admin.external_id, 9999);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E016001'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('指定したテンプレートファイルがルーティングルールに紐づく場合、400エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
|
||||||
|
const service = module.get<TemplatesService>(TemplatesService);
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
const { id: authorId } = await makeTestUser(source, {
|
||||||
|
account_id: account.id,
|
||||||
|
role: USER_ROLES.AUTHOR,
|
||||||
|
author_id: 'authorId',
|
||||||
|
});
|
||||||
|
const { id: typistId } = await makeTestUser(source, {
|
||||||
|
account_id: account.id,
|
||||||
|
role: USER_ROLES.TYPIST,
|
||||||
|
});
|
||||||
|
const context = makeContext(admin.external_id, 'requestId');
|
||||||
|
|
||||||
|
const template1 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test1',
|
||||||
|
'https://url1/test1',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { id: workflowId } = await createWorkflow(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
authorId,
|
||||||
|
undefined,
|
||||||
|
template1.id,
|
||||||
|
);
|
||||||
|
await createWorkflowTypist(source, workflowId, typistId);
|
||||||
|
|
||||||
|
// 作成したデータを確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(1);
|
||||||
|
expect(templates[0].id).toBe(template1.id);
|
||||||
|
expect(templates[0].file_name).toBe(template1.file_name);
|
||||||
|
|
||||||
|
const workflow = await getWorkflow(source, account.id, workflowId);
|
||||||
|
expect(workflow?.template_id).toBe(template1.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//実行結果を確認
|
||||||
|
try {
|
||||||
|
await service.deleteTemplate(context, admin.external_id, template1.id);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E016002'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('指定したテンプレートファイルが未完了のタスクに紐づく場合、400エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
|
||||||
|
const service = module.get<TemplatesService>(TemplatesService);
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
const { id: authorId } = await makeTestUser(source, {
|
||||||
|
account_id: account.id,
|
||||||
|
role: USER_ROLES.AUTHOR,
|
||||||
|
author_id: 'authorId',
|
||||||
|
});
|
||||||
|
const context = makeContext(admin.external_id, 'requestId');
|
||||||
|
|
||||||
|
const template1 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test1',
|
||||||
|
'https://url1/test1',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { taskId: taskId1 } = await createTask(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
authorId,
|
||||||
|
'authorId',
|
||||||
|
'',
|
||||||
|
'01',
|
||||||
|
'00000001',
|
||||||
|
TASK_STATUS.UPLOADED,
|
||||||
|
);
|
||||||
|
await updateTaskTemplateFile(source, taskId1, template1.id);
|
||||||
|
|
||||||
|
// 作成したデータを確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(1);
|
||||||
|
expect(templates[0].id).toBe(template1.id);
|
||||||
|
expect(templates[0].file_name).toBe(template1.file_name);
|
||||||
|
|
||||||
|
const tasks = await getTasks(source, account.id);
|
||||||
|
expect(tasks.length).toBe(1);
|
||||||
|
expect(tasks[0].template_file_id).toBe(template1.id);
|
||||||
|
expect(tasks[0].status).toBe(TASK_STATUS.UPLOADED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//実行結果を確認
|
||||||
|
try {
|
||||||
|
await service.deleteTemplate(context, admin.external_id, template1.id);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E016003'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DBアクセスに失敗した場合、500エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
|
||||||
|
const service = module.get<TemplatesService>(TemplatesService);
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
const context = makeContext(admin.external_id, 'requestId');
|
||||||
|
|
||||||
|
const template1 = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
account.id,
|
||||||
|
'test1',
|
||||||
|
'https://url1/test1',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 作成したデータを確認
|
||||||
|
{
|
||||||
|
const templates = await getTemplateFiles(source, account.id);
|
||||||
|
expect(templates.length).toBe(1);
|
||||||
|
expect(templates[0].id).toBe(template1.id);
|
||||||
|
expect(templates[0].file_name).toBe(template1.file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//DBアクセスに失敗するようにする
|
||||||
|
const templateFilesRepositoryService =
|
||||||
|
module.get<TemplateFilesRepositoryService>(
|
||||||
|
TemplateFilesRepositoryService,
|
||||||
|
);
|
||||||
|
templateFilesRepositoryService.getTemplateFiles = jest
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue('DB failed');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.deleteTemplate(context, admin.external_id, template1.id);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -4,6 +4,13 @@ import { TemplateFile } from './types/types';
|
|||||||
import { Context } from '../../common/log';
|
import { Context } from '../../common/log';
|
||||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||||
|
import {
|
||||||
|
NotFinishedTaskHasTemplateDeleteFailedError,
|
||||||
|
TemplateFileNotExistError,
|
||||||
|
WorkflowHasTemplateDeleteFailedError,
|
||||||
|
} from '../../repositories/template_files/errors/types';
|
||||||
|
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||||
|
import { MANUAL_RECOVERY_REQUIRED } from '../../constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TemplatesService {
|
export class TemplatesService {
|
||||||
@ -11,6 +18,7 @@ export class TemplatesService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly usersRepository: UsersRepositoryService,
|
private readonly usersRepository: UsersRepositoryService,
|
||||||
private readonly templateFilesRepository: TemplateFilesRepositoryService,
|
private readonly templateFilesRepository: TemplateFilesRepositoryService,
|
||||||
|
private readonly blobStorageService: BlobstorageService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,4 +63,103 @@ export class TemplatesService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アカウント内の指定されたテンプレートファイルを削除する
|
||||||
|
* @param context
|
||||||
|
* @param externalId
|
||||||
|
* @param templateFileId
|
||||||
|
* @returns template
|
||||||
|
*/
|
||||||
|
async deleteTemplate(
|
||||||
|
context: Context,
|
||||||
|
externalId: string,
|
||||||
|
templateFileId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
this.logger.log(
|
||||||
|
`[IN] [${context.getTrackingId()}] ${
|
||||||
|
this.deleteTemplate.name
|
||||||
|
} | params: { externalId: ${externalId}, templateFileId: ${templateFileId} };`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account } = await this.usersRepository.findUserByExternalId(
|
||||||
|
context,
|
||||||
|
externalId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new Error(`account not found. externalId: ${externalId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// テンプレートファイルの取得
|
||||||
|
const templateFile = await this.templateFilesRepository.getTemplateFile(
|
||||||
|
context,
|
||||||
|
account.id,
|
||||||
|
templateFileId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// DBからのテンプレートファイルの削除
|
||||||
|
await this.templateFilesRepository.deleteTemplateFile(
|
||||||
|
context,
|
||||||
|
account.id,
|
||||||
|
templateFileId,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Blob Storageからのテンプレートファイルの削除
|
||||||
|
await this.blobStorageService.deleteFile(
|
||||||
|
context,
|
||||||
|
account.id,
|
||||||
|
account.country,
|
||||||
|
`Templates/${templateFile.file_name}`,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Blob削除失敗時は、MANUAL_RECOVERY_REQUIREDを出して処理続行
|
||||||
|
this.logger.log(`[${context.getTrackingId()}] ${e}`);
|
||||||
|
this.logger.log(
|
||||||
|
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete Blob: accountId: ${
|
||||||
|
account.id
|
||||||
|
}, fileName: ${templateFile.file_name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
switch (e.constructor) {
|
||||||
|
// 指定されたIDのテンプレートファイルが存在しない
|
||||||
|
case TemplateFileNotExistError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E016001'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
// 指定されたIDのテンプレートファイルがルーティングルールに設定されている
|
||||||
|
case WorkflowHasTemplateDeleteFailedError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E016002'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
// 指定されたIDのテンプレートファイルが未完了タスクに紐づいている
|
||||||
|
case NotFinishedTaskHasTemplateDeleteFailedError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E016003'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.logger.log(
|
||||||
|
`[OUT] [${context.getTrackingId()}] ${this.deleteTemplate.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
||||||
|
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
||||||
|
|
||||||
export const createTemplateFile = async (
|
export const createTemplateFile = async (
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
@ -41,3 +42,18 @@ export const getTemplateFiles = async (
|
|||||||
});
|
});
|
||||||
return templates;
|
return templates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateTaskTemplateFile = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
taskId: number,
|
||||||
|
templateFileId: number,
|
||||||
|
): Promise<void> => {
|
||||||
|
await datasource.getRepository(Task).update(
|
||||||
|
{ id: taskId },
|
||||||
|
{
|
||||||
|
template_file_id: templateFileId,
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -5,3 +5,17 @@ export class TemplateFileNotExistError extends Error {
|
|||||||
this.name = 'TemplateFileNotExistError';
|
this.name = 'TemplateFileNotExistError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WorkflowHasTemplateDeleteFailedError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'WorkflowHasTemplateDeleteFailedError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotFinishedTaskHasTemplateDeleteFailedError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'NotFinishedTaskHasTemplateDeleteFailedError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,20 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource, In } from 'typeorm';
|
||||||
import { TemplateFile } from './entity/template_file.entity';
|
import { TemplateFile } from './entity/template_file.entity';
|
||||||
import { insertEntity, updateEntity } from '../../common/repository';
|
import {
|
||||||
|
deleteEntity,
|
||||||
|
insertEntity,
|
||||||
|
updateEntity,
|
||||||
|
} from '../../common/repository';
|
||||||
import { Context } from '../../common/log';
|
import { Context } from '../../common/log';
|
||||||
|
import {
|
||||||
|
NotFinishedTaskHasTemplateDeleteFailedError,
|
||||||
|
TemplateFileNotExistError,
|
||||||
|
WorkflowHasTemplateDeleteFailedError,
|
||||||
|
} from './errors/types';
|
||||||
|
import { Workflow } from '../workflows/entity/workflow.entity';
|
||||||
|
import { Task } from '../tasks/entity/task.entity';
|
||||||
|
import { TASK_STATUS } from '../../constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TemplateFilesRepositoryService {
|
export class TemplateFilesRepositoryService {
|
||||||
@ -32,6 +44,36 @@ export class TemplateFilesRepositoryService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アカウント内のIDで指定されたテンプレートファイルを取得する
|
||||||
|
* @param context
|
||||||
|
* @param accountId
|
||||||
|
* @param templateFileId
|
||||||
|
* @returns template file
|
||||||
|
*/
|
||||||
|
async getTemplateFile(
|
||||||
|
context: Context,
|
||||||
|
accountId: number,
|
||||||
|
templateFileId: number,
|
||||||
|
): Promise<TemplateFile> {
|
||||||
|
return await this.dataSource.transaction(async (entityManager) => {
|
||||||
|
const templateFilesRepo = entityManager.getRepository(TemplateFile);
|
||||||
|
|
||||||
|
const template = await templateFilesRepo.findOne({
|
||||||
|
where: { account_id: accountId, id: templateFileId },
|
||||||
|
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new TemplateFileNotExistError(
|
||||||
|
`template file not found. accountId: ${accountId}, templateFileId: ${templateFileId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アカウント内にテンプレートファイルを追加(すでに同名ファイルがあれば更新)する
|
* アカウント内にテンプレートファイルを追加(すでに同名ファイルがあれば更新)する
|
||||||
* @param accountId
|
* @param accountId
|
||||||
@ -79,4 +121,92 @@ export class TemplateFilesRepositoryService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アカウント内にある指定されたテンプレートファイルを削除する
|
||||||
|
* @param accountId
|
||||||
|
* @param fileName
|
||||||
|
* @param url
|
||||||
|
* @returns template file
|
||||||
|
*/
|
||||||
|
async deleteTemplateFile(
|
||||||
|
context: Context,
|
||||||
|
accountId: number,
|
||||||
|
templateFileId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.dataSource.transaction(async (entityManager) => {
|
||||||
|
const workflowRepo = entityManager.getRepository(Workflow);
|
||||||
|
// テンプレートファイルがワークフローで使用されているか確認
|
||||||
|
const workflow = await workflowRepo.findOne({
|
||||||
|
where: {
|
||||||
|
account_id: accountId,
|
||||||
|
template_id: templateFileId,
|
||||||
|
},
|
||||||
|
lock: { mode: 'pessimistic_write' },
|
||||||
|
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ワークフローで使用されている場合はエラー
|
||||||
|
if (workflow) {
|
||||||
|
throw new WorkflowHasTemplateDeleteFailedError(
|
||||||
|
`workflow has template file. accountId: ${accountId}, templateFileId: ${templateFileId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateFilesRepo = entityManager.getRepository(TemplateFile);
|
||||||
|
// アカウント内に指定IDファイルがあるか確認
|
||||||
|
const template = await templateFilesRepo.findOne({
|
||||||
|
where: { account_id: accountId, id: templateFileId },
|
||||||
|
lock: { mode: 'pessimistic_write' },
|
||||||
|
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ファイルが存在しない場合はエラー
|
||||||
|
if (!template) {
|
||||||
|
throw new TemplateFileNotExistError(
|
||||||
|
`template file not found. accountId: ${accountId}, templateFileId: ${templateFileId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskRepo = entityManager.getRepository(Task);
|
||||||
|
// テンプレートファイルが未完了タスクで使用されているか確認
|
||||||
|
const templateUsedTasks = await taskRepo.findOne({
|
||||||
|
where: {
|
||||||
|
account_id: accountId,
|
||||||
|
template_file_id: templateFileId,
|
||||||
|
status: In([
|
||||||
|
TASK_STATUS.UPLOADED,
|
||||||
|
TASK_STATUS.IN_PROGRESS,
|
||||||
|
TASK_STATUS.PENDING,
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
lock: { mode: 'pessimistic_write' },
|
||||||
|
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未完了のタスクでテンプレートファイルが使用されている場合はエラー
|
||||||
|
if (templateUsedTasks) {
|
||||||
|
throw new NotFinishedTaskHasTemplateDeleteFailedError(
|
||||||
|
`not finished task has template file. accountId: ${accountId}, templateFileId: ${templateFileId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// テンプレートファイルの削除
|
||||||
|
await deleteEntity(
|
||||||
|
templateFilesRepo,
|
||||||
|
{ id: templateFileId },
|
||||||
|
this.isCommentOut,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 完了済みのタスクからテンプレートファイルの紐づけを解除
|
||||||
|
await updateEntity(
|
||||||
|
taskRepo,
|
||||||
|
{ template_file_id: templateFileId },
|
||||||
|
{ template_file_id: null },
|
||||||
|
this.isCommentOut,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user