Merged PR 620: テンプレートファイルダウンロードをTypistのみが実行可能にする

## 概要
[Task3291: テンプレートファイルダウンロードをTypistのみが実行可能にする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3291)

- テンプレートファイルダウンロード先要求APIを実行できるユーザーをTypistのみに修正しました。
  - Authorが実行できないようにしました。

## レビューポイント
- ガードでTypistのみにしたので内部のロールでの分岐処理を削除しましたが問題ないでしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-12-13 08:08:58 +00:00
parent 0f35789b91
commit 63892bad83
3 changed files with 137 additions and 198 deletions

View File

@ -338,9 +338,7 @@ export class FilesController {
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR, USER_ROLES.TYPIST] }),
)
@UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.TYPIST] }))
async downloadTemplateLocation(
@Req() req: Request,
@Query() body: TemplateDownloadLocationRequest,

View File

@ -34,7 +34,12 @@ import { TasksRepositoryService } from '../../repositories/tasks/tasks.repositor
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
import { DateWithZeroTime } from '../licenses/types/types';
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
import {
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
TASK_STATUS,
USER_ROLES,
} from '../../constants';
describe('publishUploadSas', () => {
let source: DataSource | null = null;
@ -1505,15 +1510,11 @@ describe('テンプレートファイルダウンロードURL取得', () => {
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser(
source,
{
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
},
);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
const { audioFileId } = await createTask(
@ -1521,9 +1522,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
TASK_STATUS.IN_PROGRESS,
userId,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -1539,13 +1540,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
const resultUrl = await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
@ -1557,15 +1557,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 本日の日付を作成
let yesterday = new Date();
@ -1591,9 +1586,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
TASK_STATUS.IN_PROGRESS,
userId,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -1609,22 +1604,20 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
const resultUrl = await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
it('タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
author_id: undefined,
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
@ -1633,7 +1626,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId,
url,
'test.zip',
'Finished',
TASK_STATUS.FINISHED,
userId,
);
@ -1650,31 +1643,35 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
});
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
it('自身が担当するタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
author_id: undefined,
role: USER_ROLES.TYPIST,
});
const { id: otherId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'other-typist-user-external-id',
role: 'typist',
author_id: undefined,
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
@ -1683,7 +1680,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId,
url,
'test.zip',
'InProgress',
TASK_STATUS.IN_PROGRESS,
otherId,
);
@ -1700,60 +1697,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
);
});
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: '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 notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
});
it('Taskが存在しない場合はエラーとなる', async () => {
@ -1761,9 +1719,8 @@ describe('テンプレートファイルダウンロードURL取得', () => {
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const blobParam = makeBlobstorageServiceMockValue();
@ -1777,29 +1734,31 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
1,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
});
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser(
source,
{
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
},
);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
const { audioFileId } = await createTask(
@ -1807,9 +1766,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
TASK_STATUS.IN_PROGRESS,
userId,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -1825,15 +1784,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('tracking', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
} else {
fail();
}
}
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
@ -1845,15 +1810,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
@ -1862,9 +1822,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
TASK_STATUS.IN_PROGRESS,
undefined,
authorId ?? '',
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -1880,15 +1840,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
} else {
fail();
}
}
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
@ -1900,15 +1866,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 昨日の日付を作成
let yesterday = new Date();
@ -1934,9 +1895,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
TASK_STATUS.IN_PROGRESS,
undefined,
authorId ?? '',
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -1952,15 +1913,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
});
});

View File

@ -474,7 +474,7 @@ export class FilesService {
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
if (isTypist && task.typist_user_id !== userId) {
throw new AuthorUserNotMatchError(
throw new TypistUserNotFoundError(
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
);
}
@ -563,8 +563,6 @@ export class FilesService {
let accountId: number;
let userId: number;
let country: string;
let isTypist: boolean;
let authorId: string | undefined;
try {
const user = await this.usersRepository.findUserByExternalId(
context,
@ -590,8 +588,6 @@ export class FilesService {
accountId = user.account_id;
userId = user.id;
country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id ?? undefined;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
@ -619,27 +615,13 @@ export class FilesService {
}
try {
const status = isTypist
? [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING]
: Object.values(TASK_STATUS);
const task = await this.tasksRepository.getTaskAndAudioFile(
context,
audioFileId,
accountId,
status,
[TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING],
);
const { file } = task;
// タスクに紐づく音声ファイルだけが消される場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う
if (!file) {
throw new AudioFileNotFoundError(
`Audio file is not exists in DB. audio_file_id:${audioFileId}`,
);
}
const template_file = task.template_file;
const { template_file } = task;
// タスクに紐づくテンプレートファイルがない場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う
@ -649,16 +631,9 @@ export class FilesService {
);
}
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
if (!isTypist && file.author_id !== authorId) {
throw new AuthorUserNotMatchError(
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
);
}
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
if (isTypist && task.typist_user_id !== userId) {
throw new AuthorUserNotMatchError(
// ユーザー自身が担当したタスクでない場合はエラー
if (task.typist_user_id !== userId) {
throw new TypistUserNotFoundError(
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
);
}
@ -693,7 +668,6 @@ export class FilesService {
case TasksNotFoundError:
case AccountNotMatchError:
case StatusNotMatchError:
case AuthorUserNotMatchError:
case TypistUserNotFoundError:
throw new HttpException(
makeErrorResponse('E010603'),