Merge branch 'develop' into ccb

This commit is contained in:
makabe 2024-01-29 15:52:55 +09:00
commit f00861702a
27 changed files with 361 additions and 545 deletions

View File

@ -0,0 +1,4 @@
-- [OMDS_IS-231] アカウントIDの開始番号調整 | 課題の表示 | Backlog 対応
-- IDからアカウント数が推測されるため、ユーザ指定の任意値を最初の番号とする
-- 一度しか実行しないため、migrate fileではなくDBの初期値として扱う。移行時の実行を想定
ALTER TABLE accounts AUTO_INCREMENT = 853211;

View File

@ -62,6 +62,12 @@ export const orderLicenseAsync = createAsyncThunk<
);
}
if (error.code === "E010501") {
errorMessage = getTranslationID(
"licenseOrderPage.message.dealerNotFoundError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",

View File

@ -85,7 +85,7 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
<input
type="text"
size={40}
maxLength={255}
maxLength={16}
value={worktypeId ?? ""}
className={styles.formInput}
onChange={(e) => {

View File

@ -84,7 +84,7 @@ export const EditWorktypeIdPopup: React.FC<EditWorktypeIdPopupProps> = (
<input
type="text"
size={40}
maxLength={255}
maxLength={16}
value={worktypeId ?? ""}
className={styles.formInput}
onChange={(e) => {

View File

@ -189,7 +189,8 @@
"poNumberIncorrectError": "Das Format der Bestellnummer ist ungültig. Für die Bestellnummer können nur alphanumerische Zeichen eingegeben werden.",
"newOrderIncorrectError": "Bitte geben Sie für die neue Bestellung eine Zahl größer oder gleich 1 ein.",
"confirmOrder": "Möchten Sie eine Bestellung aufgeben?",
"poNumberConflictError": "Die eingegebene Bestellnummer existiert bereits. Bitte geben Sie eine andere Bestellnummer ein."
"poNumberConflictError": "Die eingegebene Bestellnummer existiert bereits. Bitte geben Sie eine andere Bestellnummer ein.",
"dealerNotFoundError": "(de)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
},
"label": {
"title": "Lizenz bestellen",

View File

@ -190,7 +190,8 @@
"poNumberIncorrectError": "PO Number format is not valid. Only alphanumeric characters can be entered for the PO Number.",
"newOrderIncorrectError": "Please enter a number greater than or equal to 1 for the New Order.",
"confirmOrder": "Would you like to place an order?",
"poNumberConflictError": "PO Number entered already exists. Please enter a different PO Number."
"poNumberConflictError": "PO Number entered already exists. Please enter a different PO Number.",
"dealerNotFoundError": "ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
},
"label": {
"title": "Order License",

View File

@ -190,7 +190,8 @@
"poNumberIncorrectError": "El formato del número de orden de compra no es válido. Sólo se pueden ingresar caracteres alfanuméricos para el número de orden de compra.",
"newOrderIncorrectError": "Ingrese un número mayor o igual a 1 para el Nuevo Pedido.",
"confirmOrder": "¿Quieres hacer un pedido?",
"poNumberConflictError": "El número de orden de compra ingresado ya existe. Ingrese un número de orden de compra diferente."
"poNumberConflictError": "El número de orden de compra ingresado ya existe. Ingrese un número de orden de compra diferente.",
"dealerNotFoundError": "(es)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
},
"label": {
"title": "Licencia de pedido",

View File

@ -190,7 +190,8 @@
"poNumberIncorrectError": "Le format du numéro de bon de commande n'est pas valide. Seuls des caractères alphanumériques peuvent être saisis pour le numéro de bon de commande.",
"newOrderIncorrectError": "Veuillez saisir un nombre supérieur ou égal à 1 pour la nouvelle commande.",
"confirmOrder": "Voulez-vous passer commande?",
"poNumberConflictError": "Le numéro de bon de commande saisi existe déjà. Veuillez saisir un autre numéro de bon de commande."
"poNumberConflictError": "Le numéro de bon de commande saisi existe déjà. Veuillez saisir un autre numéro de bon de commande.",
"dealerNotFoundError": "(fr)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
},
"label": {
"title": "Commander licence",

View File

@ -16,7 +16,7 @@ MAIL_FROM=xxxxx@xxxxx.xxxx
NOTIFICATION_HUB_NAME=ntf-odms-dev
NOTIFICATION_HUB_CONNECT_STRING=XXXXXXXXXXXXXXXXXX
APP_DOMAIN=http://localhost:8081/
STORAGE_TOKEN_EXPIRE_TIME=30
STORAGE_TOKEN_EXPIRE_TIME=2
STORAGE_ACCOUNT_NAME_US=saodmsusdev
STORAGE_ACCOUNT_NAME_AU=saodmsaudev
STORAGE_ACCOUNT_NAME_EU=saodmseudev
@ -26,10 +26,10 @@ STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX
STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA
STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
ACCESS_TOKEN_LIFETIME_WEB=7200000
REFRESH_TOKEN_LIFETIME_WEB=86400000
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000000
EMAIL_CONFIRM_LIFETIME=86400000
ACCESS_TOKEN_LIFETIME_WEB=7200
REFRESH_TOKEN_LIFETIME_WEB=86400
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000
EMAIL_CONFIRM_LIFETIME=86400
REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass

View File

@ -0,0 +1,19 @@
-- +migrate Up
ALTER TABLE `accounts` ADD INDEX `idx_accounts_tier` (tier);
ALTER TABLE `accounts` ADD INDEX `idx_accounts_parent_account_id` (parent_account_id);
ALTER TABLE `users` ADD INDEX `idx_users_external_id` (external_id);
ALTER TABLE `users` ADD INDEX `idx_users_email_verified` (email_verified);
ALTER TABLE `licenses` ADD INDEX `idx_licenses_order_id` (order_id);
ALTER TABLE `licenses` ADD INDEX `idx_licenses_status` (status);
ALTER TABLE `template_files` ADD INDEX `idx_template_files_account_id` (account_id);
ALTER TABLE `template_files` ADD INDEX `idx_template_files_file_name` (file_name(500));
-- +migrate Down
ALTER TABLE `accounts` DROP INDEX `idx_accounts_tier`;
ALTER TABLE `accounts` DROP INDEX `idx_accounts_parent_account_id`;
ALTER TABLE `users` DROP INDEX `idx_users_external_id`;
ALTER TABLE `users` DROP INDEX `idx_users_email_verified`;
ALTER TABLE `licenses` DROP INDEX `idx_licenses_order_id`;
ALTER TABLE `licenses` DROP INDEX `idx_licenses_status`;
ALTER TABLE `template_files` DROP INDEX `idx_template_files_account_id`;
ALTER TABLE `template_files` DROP INDEX `idx_template_files_file_name`;

View File

@ -219,9 +219,9 @@ export const PNS = {
};
/**
*
*
*/
export const USER_LICENSE_STATUS = {
export const USER_LICENSE_EXPIRY_STATUS = {
NORMAL: 'Normal',
NO_LICENSE: 'NoLicense',
ALERT: 'Alert',
@ -311,3 +311,13 @@ export const USER_AUDIO_FORMAT = 'DS2(QP)';
* @const {string[]}
*/
export const NODE_ENV_TEST = 'test';
/**
*
* @const {string[]}
*/
export const USER_LICENSE_STATUS = {
UNALLOCATED: 'unallocated',
ALLOCATED: 'allocated',
EXPIRED: 'expired',
} as const;

View File

@ -198,7 +198,7 @@ export class CancelIssueRequest {
export class CreateWorktypesRequest {
@ApiProperty({ minLength: 1, maxLength: 255, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@MaxLength(16)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })
@ -210,7 +210,7 @@ export class CreateWorktypesRequest {
export class UpdateWorktypesRequest {
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@MaxLength(16)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })

View File

@ -235,67 +235,6 @@ describe('publishUploadSas', () => {
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('アップロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishUploadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishUploadSas(
makeContext('trackingId', 'requestId'),
externalId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('タスク作成から自動ルーティング(DB使用)', () => {
@ -1097,76 +1036,6 @@ describe('音声ファイルダウンロードURL取得', () => {
),
).toEqual(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 本日の日付を作成
let today = new Date();
today.setDate(today.getDate());
today = new DateWithZeroTime(today);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
today,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
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);
expect(
await service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1396,133 +1265,6 @@ describe('音声ファイルダウンロードURL取得', () => {
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('テンプレートファイルダウンロードURL取得', () => {
@ -1596,70 +1338,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 本日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate());
yesterday = new DateWithZeroTime(yesterday);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
userId,
'AUTHOR_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);
const resultUrl = await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1849,135 +1528,6 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}
}
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
undefined,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
);
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();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
undefined,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
});
});
describe('publishTemplateFileUploadSas', () => {

View File

@ -8,6 +8,7 @@ import {
OPTION_ITEM_NUM,
TASK_STATUS,
TIERS,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants/index';
import { User } from '../../repositories/users/entity/user.entity';
@ -308,10 +309,10 @@ export class FilesService {
context,
user.id,
);
if (state === 'expired') {
if (state === USER_LICENSE_STATUS.EXPIRED) {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
@ -392,20 +393,6 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
user.id,
);
if (state === 'expired') {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
accountId = user.account.id;
userId = user.id;
country = user.account.country;
@ -422,16 +409,6 @@ export class FilesService {
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
@ -571,20 +548,6 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
user.id,
);
if (state === 'expired') {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
accountId = user.account_id;
userId = user.id;
country = user.account.country;
@ -596,16 +559,6 @@ export class FilesService {
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),

View File

@ -192,8 +192,8 @@ describe('ライセンス注文', () => {
await service.licenseOrders(context, externalId, poNumber, orderCount);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
} else {
fail();
}

View File

@ -80,7 +80,9 @@ export class LicensesService {
.parent_account_id ?? undefined;
// 親アカウントIDが取得できない場合はエラー
if (parentAccountId === undefined) {
throw new Error('parent account id is undefined');
throw new AccountNotFoundError(
`parent account id is not found. myAccountId: ${myAccountId}`,
);
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -147,6 +149,7 @@ export class LicensesService {
);
}
}
async issueCardLicenseKeys(
context: Context,
externalId: string,

View File

@ -9,6 +9,7 @@ import { NotificationhubModule } from '../../gateways/notificationhub/notificati
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
@Module({
imports: [
@ -20,6 +21,7 @@ import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module
NotificationhubModule,
SendGridModule,
BlobstorageModule,
LicensesRepositoryModule,
],
providers: [TasksService],
controllers: [TasksController],

View File

@ -27,7 +27,13 @@ import {
makeTestSimpleAccount,
makeTestUser,
} from '../../common/test/utility';
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
import {
ADMIN_ROLES,
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
TASK_STATUS,
USER_ROLES,
} from '../../constants';
import { makeTestingModule } from '../../common/test/modules';
import { createSortCriteria } from '../users/test/utility';
import { createWorktype } from '../accounts/test/utility';
@ -42,6 +48,9 @@ import { TasksRepositoryService } from '../../repositories/tasks/tasks.repositor
import { overrideBlobstorageService } from '../../common/test/overrides';
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
import { truncateAllTable } from '../../common/test/init';
import { makeDefaultLicensesRepositoryMockValue } from '../accounts/test/accounts.service.mock';
import { DateWithZeroTime } from '../licenses/types/types';
import { createLicense } from '../licenses/test/utility';
describe('TasksService', () => {
it('タスク一覧を取得できるadmin', async () => {
@ -52,12 +61,15 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -126,6 +138,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
usersRepositoryMockValue.findUserByExternalId = new Error('DB failed');
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -133,6 +147,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -168,6 +183,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
tasksRepositoryMockValue.getTasksFromAccountId = new Error('DB failed');
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -175,6 +192,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -256,12 +274,15 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
const offset = 0;
@ -296,6 +317,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
return;
}
@ -306,6 +329,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -380,6 +404,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
tasksRepositoryMockValue.getTasksFromAuthorIdAndAccountId = new Error(
'DB failed',
);
@ -389,6 +415,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -424,6 +451,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
return;
}
@ -435,6 +464,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -512,12 +542,15 @@ describe('TasksService', () => {
tasksRepositoryMockValue.getTasksFromTypistRelations = new Error(
'DB failed',
);
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -553,6 +586,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
adb2cServiceMockValue.getUsers = new Adb2cTooManyRequestsError();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -560,6 +595,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -1636,7 +1672,75 @@ describe('checkout', () => {
user_group_id: null,
});
});
it('第五階層のアカウントの場合、有効なライセンスが割当されている場合チェックアウトできる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
// 本日の日付を作成
const today = new Date();
// 有効なライセンスを作成して紐づける
await createLicense(
source,
1,
today,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
typistUserId,
null,
null,
null,
);
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Pending',
);
await createCheckoutPermissions(source, taskId, typistUserId);
const service = module.get<TasksService>(TasksService);
const initTask = await getTask(source, taskId);
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
const resultTask = await getTask(source, taskId);
const permisions = await getCheckoutPermissions(source, taskId);
expect(resultTask?.status).toEqual('InProgress');
expect(resultTask?.typist_user_id).toEqual(typistUserId);
//タスクの元々のステータスがPending,Inprogressの場合、文字起こし開始時刻は更新されない
expect(resultTask?.started_at).toEqual(initTask?.started_at);
expect(permisions.length).toEqual(1);
expect(permisions[0]).toEqual({
id: 2,
task_id: 1,
user_id: 1,
user_group_id: null,
});
});
it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -1682,7 +1786,116 @@ describe('checkout', () => {
}
}
});
it('第五階層のアカウントの場合、ライセンスが未割当の場合チェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Backup',
);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
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();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
typistUserId,
null,
null,
null,
);
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Backup',
);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);

View File

@ -13,6 +13,8 @@ import {
ADMIN_ROLES,
MANUAL_RECOVERY_REQUIRED,
TASK_STATUS,
TIERS,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants';
import {
@ -42,6 +44,12 @@ import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import {
LicenseExpiredError,
LicenseNotAllocatedError,
} from '../../repositories/licenses/errors/types';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
@Injectable()
export class TasksService {
@ -55,6 +63,7 @@ export class TasksService {
private readonly sendgridService: SendGridService,
private readonly notificationhubService: NotificationhubService,
private readonly blobStorageService: BlobstorageService,
private readonly licensesRepository: LicensesRepositoryService,
) {}
async getTasks(
@ -283,9 +292,26 @@ export class TasksService {
} | params: { audioFileId: ${audioFileId}, roles: ${roles}, externalId: ${externalId} };`,
);
const { id, account_id, author_id } =
const { id, account_id, author_id, account } =
await this.usersRepository.findUserByExternalId(context, externalId);
if (!account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
id,
);
if (state === USER_LICENSE_STATUS.EXPIRED) {
throw new LicenseExpiredError('license is expired.');
}
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
@ -315,6 +341,16 @@ export class TasksService {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
case CheckoutPermissionNotFoundError:
case TaskAuthorIdNotMatchError:
case InvalidRoleError:

View File

@ -18,6 +18,11 @@ import { UserGroupsRepositoryService } from '../../../repositories/user_groups/u
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
import { BlobstorageService } from '../../../gateways/blobstorage/blobstorage.service';
import {
LicensesRepositoryMockValue,
makeLicensesRepositoryMock,
} from '../../accounts/test/accounts.service.mock';
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
export type TasksRepositoryMockValue = {
getTasksFromAccountId:
@ -66,6 +71,7 @@ export const makeTasksServiceMock = async (
userGroupsRepositoryMockValue: UserGroupsRepositoryMockValue,
adB2CServiceMockValue: AdB2CServiceMockValue,
notificationhubServiceMockValue: NotificationhubServiceMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue,
): Promise<{
tasksService: TasksService;
taskRepoService: TasksRepositoryService;
@ -95,6 +101,8 @@ export const makeTasksServiceMock = async (
return {};
case BlobstorageService:
return {};
case LicensesRepositoryService:
return makeLicensesRepositoryMock(licensesRepositoryMockValue);
}
})
.compile();

View File

@ -9,7 +9,7 @@ import {
} from 'class-validator';
import {
TASK_LIST_SORTABLE_ATTRIBUTES,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
} from '../../../constants';
import { USER_ROLES } from '../../../constants';
import {
@ -67,9 +67,9 @@ export class User {
remaining?: number;
@ApiProperty({
description: `${Object.values(USER_LICENSE_STATUS).join('/')}`,
description: `${Object.values(USER_LICENSE_EXPIRY_STATUS).join('/')}`,
})
@IsIn(Object.values(USER_LICENSE_STATUS), {
@IsIn(Object.values(USER_LICENSE_EXPIRY_STATUS), {
message: 'invalid license status',
})
licenseStatus: string;

View File

@ -22,7 +22,7 @@ import {
LICENSE_EXPIRATION_THRESHOLD_DAYS,
LICENSE_TYPE,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
USER_ROLES,
} from '../../constants';
import { makeTestingModule } from '../../common/test/modules';
@ -1479,7 +1479,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
{
id: typistUserId,
@ -1495,7 +1495,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
{
id: noneUserId,
@ -1511,7 +1511,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
];
@ -1591,7 +1591,7 @@ describe('UsersService.getUsers', () => {
date1.getMonth() + 1
}/${date1.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS + 1,
licenseStatus: USER_LICENSE_STATUS.NORMAL,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NORMAL,
},
{
id: user2,
@ -1609,7 +1609,7 @@ describe('UsersService.getUsers', () => {
date2.getMonth() + 1
}/${date2.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS,
licenseStatus: USER_LICENSE_STATUS.RENEW,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.RENEW,
},
{
id: user3,
@ -1627,7 +1627,7 @@ describe('UsersService.getUsers', () => {
date3.getMonth() + 1
}/${date3.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS - 1,
licenseStatus: USER_LICENSE_STATUS.ALERT,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.ALERT,
},
];

View File

@ -37,7 +37,7 @@ import {
MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
USER_ROLES,
} from '../../constants';
import { DateWithZeroTime } from '../licenses/types/types';
@ -617,7 +617,7 @@ export class UsersService {
throw new Error('mail not found.');
}
let status = USER_LICENSE_STATUS.NORMAL;
let status = USER_LICENSE_EXPIRY_STATUS.NORMAL;
// ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する
// ライセンスが存在しない場合は、undefinedのままとする
@ -648,11 +648,11 @@ export class UsersService {
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
) {
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT;
? USER_LICENSE_EXPIRY_STATUS.RENEW
: USER_LICENSE_EXPIRY_STATUS.ALERT;
}
} else {
status = USER_LICENSE_STATUS.NO_LICENSE;
status = USER_LICENSE_EXPIRY_STATUS.NO_LICENSE;
}
return {

View File

@ -821,6 +821,7 @@ export class AccountsRepositoryService {
status: Not(LICENSE_ALLOCATED_STATUS.UNALLOCATED),
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// 存在した場合エラー
@ -1023,6 +1024,7 @@ export class AccountsRepositoryService {
email_verified: true,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!primaryAdminUser) {
throw new AdminUserNotFoundError(
@ -1040,6 +1042,7 @@ export class AccountsRepositoryService {
email_verified: true,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!secondryAdminUser) {
throw new AdminUserNotFoundError(

View File

@ -12,9 +12,9 @@ import {
LICENSE_ALLOCATED_STATUS,
LICENSE_ISSUE_STATUS,
LICENSE_TYPE,
NODE_ENV_TEST,
SWITCH_FROM_TYPE,
TIERS,
USER_LICENSE_STATUS,
} from '../../constants';
import {
PoNumberAlreadyExistError,
@ -422,10 +422,7 @@ export class LicensesRepositoryService {
po_number: poNumber,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
// テスト環境の場合はロックを行わない(sqliteがlockに対応していないため)
...(process.env.NODE_ENV !== NODE_ENV_TEST
? { lock: { mode: 'pessimistic_write' } }
: {}),
lock: { mode: 'pessimistic_write' },
});
if (!issuingOrder) {
// 注文が存在しない場合、エラー
@ -569,6 +566,7 @@ export class LicensesRepositoryService {
id: newLicenseId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// ライセンスが存在しない場合はエラー
@ -806,12 +804,17 @@ export class LicensesRepositoryService {
*
* @param userId ID
* @error { Error } DBアクセス失敗時の例外
* @returns Promise<{ state: 'allocated' | 'inallocated' | 'expired' }>
* @returns Promise<{ state: 'allocated' | 'unallocated' | 'expired' }>
*/
async getLicenseState(
context: Context,
userId: number,
): Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> {
): Promise<{
state:
| typeof USER_LICENSE_STATUS.ALLOCATED
| typeof USER_LICENSE_STATUS.UNALLOCATED
| typeof USER_LICENSE_STATUS.EXPIRED;
}> {
const allocatedLicense = await this.dataSource
.getRepository(License)
.findOne({
@ -824,7 +827,7 @@ export class LicensesRepositoryService {
// ライセンスが割り当てられていない場合は未割当状態
if (allocatedLicense == null) {
return { state: 'inallocated' };
return { state: USER_LICENSE_STATUS.UNALLOCATED };
}
// ライセンスの有効期限が過ぎている場合は期限切れ状態
@ -833,9 +836,9 @@ export class LicensesRepositoryService {
allocatedLicense.expiry_date &&
allocatedLicense.expiry_date < currentDate
) {
return { state: 'expired' };
return { state: USER_LICENSE_STATUS.EXPIRED };
}
return { state: 'allocated' };
return { state: USER_LICENSE_STATUS.ALLOCATED };
}
}

View File

@ -52,6 +52,7 @@ export class TemplateFilesRepositoryService {
const template = await templateFilesRepo.findOne({
where: { account_id: accountId, file_name: fileName },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// 同名ファイルは同じものとして扱うため、すでにファイルがあれば更新(更新日時の履歴を残しておきたい)

View File

@ -289,6 +289,7 @@ export class UsersRepositoryService {
const targetUser = await repo.findOne({
where: { id: id, account_id: accountId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理