Merged PR 499: 修正②(files,licenses , Repositoiesのlicenses)

## 概要
[Task2836: 修正②(files,licenses , Repositoiesのlicenses)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2836)

- feature
  - files
  - licenses
- repositories
  - licenses
  - users
  - worktypes
- entity
  - licenses
  - audio_files
  - audio_option_item
  - checkout_permission
- アクセストークンをそのままService層に渡している箇所を修正し、必要なパラメータのみ渡すように修正
- クライアントの型生成
  - 割り当て可能ライセンス取得APIのIFが変わったため

## レビューポイント
-

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルでテストが通ることを確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-10-19 01:04:14 +00:00
parent c46d2bad61
commit 96848f5e54
30 changed files with 601 additions and 291 deletions

View File

@ -127,7 +127,7 @@ export interface AllocatableLicenseInfo {
* @type {string}
* @memberof AllocatableLicenseInfo
*/
'expiryDate': string;
'expiryDate'?: string;
}
/**
*

View File

@ -4465,7 +4465,7 @@
"licenseId": { "type": "number" },
"expiryDate": { "format": "date-time", "type": "string" }
},
"required": ["licenseId", "expiryDate"]
"required": ["licenseId"]
},
"GetAllocatableLicensesResponse": {
"type": "object",

View File

@ -444,6 +444,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
name: 'GroupA',
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
userGroupMembers: null,
},
{
id: 2,
@ -451,6 +455,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
name: 'GroupB',
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
userGroupMembers: null,
},
],
};

View File

@ -2,6 +2,7 @@ import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Post,
Query,
@ -37,6 +38,7 @@ import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { retrieveAuthorizationToken } from '../../common/http/helper';
import { Request } from 'express';
import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('files')
@Controller('files')
@ -75,10 +77,23 @@ export class FilesController {
@Req() req: Request,
@Body() body: AudioUploadFinishedRequest,
): Promise<AudioUploadFinishedResponse> {
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(accessToken.userId);
const context = makeContext(userId);
const {
url,
@ -99,7 +114,7 @@ export class FilesController {
const res = await this.filesService.uploadFinished(
context,
accessToken.userId,
userId,
url,
authorId,
fileName,
@ -149,10 +164,23 @@ export class FilesController {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query() _query: AudioUploadLocationRequest,
): Promise<AudioUploadLocationResponse> {
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(accessToken.userId);
const context = makeContext(userId);
const url = await this.filesService.publishUploadSas(context, accessToken);
return { url };
@ -195,14 +223,27 @@ export class FilesController {
): Promise<AudioDownloadLocationResponse> {
const { audioFileId } = body;
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(accessToken.userId);
const context = makeContext(userId);
const url = await this.filesService.publishAudioFileDownloadSas(
context,
accessToken.userId,
userId,
audioFileId,
);
@ -246,14 +287,27 @@ export class FilesController {
): Promise<TemplateDownloadLocationResponse> {
const { audioFileId } = body;
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(accessToken.userId);
const context = makeContext(userId);
const url = await this.filesService.publishTemplateFileDownloadSas(
context,
accessToken.userId,
userId,
audioFileId,
);
@ -287,8 +341,21 @@ export class FilesController {
async uploadTemplateLocation(
@Req() req: Request,
): Promise<TemplateUploadLocationResponse> {
const token = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
@ -333,8 +400,21 @@ export class FilesController {
@Body() body: TemplateUploadFinishedRequest,
): Promise<TemplateUploadFinishedReqponse> {
const { name, url } = body;
const token = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
await this.filesService.templateUploadFinished(context, userId, url, name);

View File

@ -35,11 +35,10 @@ describe('音声ファイルアップロードURL取得', () => {
);
expect(
await service.publishUploadSas(makeContext('trackingId'), {
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
role: 'Author',
tier: 5,
}),
await service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).toEqual('https://blob-storage?sas-token');
});
@ -57,11 +56,10 @@ describe('音声ファイルアップロードURL取得', () => {
);
expect(
await service.publishUploadSas(makeContext('trackingId'), {
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
role: 'Author',
tier: 5,
}),
await service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).toEqual('https://blob-storage?sas-token');
});
@ -78,11 +76,10 @@ describe('音声ファイルアップロードURL取得', () => {
);
await expect(
service.publishUploadSas(makeContext('trackingId'), {
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
role: 'Author',
tier: 5,
}),
service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
);
@ -102,11 +99,10 @@ describe('音声ファイルアップロードURL取得', () => {
blobParam.publishUploadSas = new Error('Azure service down');
await expect(
service.publishUploadSas(makeContext('trackingId'), {
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
role: 'Author',
tier: 5,
}),
service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
);
@ -295,7 +291,7 @@ describe('タスク作成', () => {
});
describe('音声ファイルダウンロードURL取得', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -308,11 +304,13 @@ describe('音声ファイルダウンロードURL取得', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const {
external_id: externalId,
@ -333,7 +331,7 @@ describe('音声ファイルダウンロードURL取得', () => {
'test.zip',
'InProgress',
undefined,
authorId,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -341,6 +339,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
@ -353,6 +352,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
@ -382,6 +382,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -396,6 +397,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
@ -429,6 +431,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -443,6 +446,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
@ -467,6 +471,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -481,6 +486,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
it('Taskが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
@ -492,6 +498,7 @@ describe('音声ファイルダウンロードURL取得', () => {
const blobParam = makeBlobstorageServiceMockValue();
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -506,6 +513,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const {
external_id: externalId,
@ -526,7 +534,7 @@ describe('音声ファイルダウンロードURL取得', () => {
'test.zip',
'InProgress',
undefined,
authorId,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -534,6 +542,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = false;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -549,7 +558,7 @@ describe('音声ファイルダウンロードURL取得', () => {
});
describe('テンプレートファイルダウンロードURL取得', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -562,11 +571,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser(
source,
@ -586,7 +597,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
'test.zip',
'InProgress',
undefined,
authorId,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -594,6 +605,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
@ -606,6 +618,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId,
@ -629,6 +642,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -643,6 +657,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
@ -672,6 +687,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -686,6 +702,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
@ -710,6 +727,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -724,6 +742,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
it('Taskが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
@ -735,6 +754,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
const blobParam = makeBlobstorageServiceMockValue();
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -749,6 +769,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser(
source,
@ -768,7 +789,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
'test.zip',
'InProgress',
undefined,
authorId,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
@ -776,6 +797,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = false;
const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
@ -791,7 +813,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
});
describe('publishTemplateFileUploadSas', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -804,12 +826,15 @@ describe('publishTemplateFileUploadSas', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -832,7 +857,9 @@ describe('publishTemplateFileUploadSas', () => {
});
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
@ -858,7 +885,9 @@ describe('publishTemplateFileUploadSas', () => {
});
it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
@ -887,7 +916,7 @@ describe('publishTemplateFileUploadSas', () => {
});
describe('templateUploadFinished', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -900,12 +929,15 @@ describe('templateUploadFinished', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('アップロード完了後のテンプレートファイル情報をDBに保存できる新規追加', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -937,7 +969,9 @@ describe('templateUploadFinished', () => {
});
it('アップロード完了後のテンプレートファイル情報をDBに保存できる更新', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -975,7 +1009,9 @@ describe('templateUploadFinished', () => {
});
it('DBへの保存に失敗した場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });

View File

@ -24,6 +24,7 @@ import {
} from '../../repositories/tasks/errors/types';
import { Context } from '../../common/log';
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@Injectable()
export class FilesService {
@ -206,7 +207,7 @@ export class FilesService {
*/
async publishUploadSas(
context: Context,
token: AccessToken,
externalId: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
@ -216,10 +217,11 @@ export class FilesService {
let accountId: number;
let country: string;
try {
const user = await this.usersRepository.findUserByExternalId(
token.userId,
);
accountId = user.account.id;
const user = await this.usersRepository.findUserByExternalId(externalId);
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
accountId = user.account_id;
country = user.account.country;
} catch (e) {
this.logger.error(`error=${e}`);
@ -291,14 +293,17 @@ export class FilesService {
let userId: number;
let country: string;
let isTypist: boolean;
let authorId: string;
let authorId: string | undefined;
try {
const user = await this.usersRepository.findUserByExternalId(externalId);
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
accountId = user.account.id;
userId = user.id;
country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id;
authorId = user.author_id ?? undefined;
} catch (e) {
this.logger.error(`error=${e}`);
@ -321,7 +326,7 @@ export class FilesService {
accountId,
status,
);
const file = task.file;
const { file } = task;
// タスクに紐づく音声ファイルだけが消される場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う
@ -332,9 +337,9 @@ export class FilesService {
}
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
if (!isTypist && task.file.author_id !== authorId) {
if (!isTypist && file.author_id !== authorId) {
throw new AuthorUserNotMatchError(
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
);
}
@ -425,14 +430,17 @@ export class FilesService {
let userId: number;
let country: string;
let isTypist: boolean;
let authorId: string;
let authorId: string | undefined;
try {
const user = await this.usersRepository.findUserByExternalId(externalId);
accountId = user.account.id;
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
accountId = user.account_id;
userId = user.id;
country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id;
authorId = user.author_id ?? undefined;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.log(
@ -454,6 +462,15 @@ export class FilesService {
accountId,
status,
);
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;
@ -466,9 +483,9 @@ export class FilesService {
}
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
if (!isTypist && task.file.author_id !== authorId) {
if (!isTypist && file.author_id !== authorId) {
throw new AuthorUserNotMatchError(
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
);
}
@ -515,6 +532,7 @@ export class FilesService {
makeErrorResponse('E010603'),
HttpStatus.BAD_REQUEST,
);
case AudioFileNotFoundError:
case TemplateFileNotFoundError:
throw new HttpException(
makeErrorResponse('E010701'),
@ -552,15 +570,18 @@ export class FilesService {
`[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`,
);
try {
const {
account: { id: accountId, country },
} = await this.usersRepository.findUserByExternalId(externalId);
const { account } = await this.usersRepository.findUserByExternalId(
externalId,
);
if (!account) {
throw new AccountNotFoundError('account not found.');
}
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
const isContainerExists = await this.blobStorageService.containerExists(
context,
accountId,
country,
account.id,
account.country,
);
if (!isContainerExists) {
throw new Error('container not found.');
@ -569,8 +590,8 @@ export class FilesService {
// SASトークン発行
const url = await this.blobStorageService.publishTemplateUploadSas(
context,
accountId,
country,
account.id,
account.country,
);
return url;

View File

@ -134,7 +134,7 @@ export const makeDefaultUsersRepositoryMockValue =
created_by: 'test',
created_at: new Date(),
updated_by: null,
updated_at: null,
updated_at: new Date(),
auto_renew: true,
license_alert: true,
notification: true,
@ -157,7 +157,7 @@ export const makeDefaultUsersRepositoryMockValue =
created_by: '',
created_at: new Date(),
updated_by: '',
updated_at: null,
updated_at: new Date(),
},
},
};

View File

@ -98,7 +98,7 @@ export const createTask = async (
export const makeTestingModuleWithBlob = async (
datasource: DataSource,
blobStorageService: BlobstorageServiceMockValue,
): Promise<TestingModule> => {
): Promise<TestingModule | undefined> => {
try {
const module: TestingModule = await Test.createTestingModule({
imports: [

View File

@ -2,6 +2,7 @@ import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Post,
Req,
@ -34,6 +35,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, TIERS } from '../../constants';
import jwt from 'jsonwebtoken';
import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('licenses')
@Controller('licenses')
@ -73,12 +75,26 @@ export class LicensesController {
@Req() req: Request,
@Body() body: CreateOrdersRequest,
): Promise<CreateOrdersResponse> {
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req) as string;
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
// ライセンス注文処理
await this.licensesService.licenseOrders(
payload,
userId,
body.poNumber,
body.orderCount,
);
@ -111,11 +127,25 @@ export class LicensesController {
@Req() req: Request,
@Body() body: IssueCardLicensesRequest,
): Promise<IssueCardLicensesResponse> {
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req) as string;
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
payload.userId,
userId,
body.createCount,
);
@ -154,11 +184,25 @@ export class LicensesController {
@Req() req: Request,
@Body() body: ActivateCardLicensesRequest,
): Promise<ActivateCardLicensesResponse> {
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req) as string;
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
await this.licensesService.activateCardLicenseKey(
payload.userId,
userId,
body.cardLicenseKey,
);
@ -194,16 +238,27 @@ export class LicensesController {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Req() req: Request,
): Promise<GetAllocatableLicensesResponse> {
const token = retrieveAuthorizationToken(req);
const payload = jwt.decode(token, { json: true }) as AccessToken;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req) as string;
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(payload.userId);
const context = makeContext(userId);
const allocatableLicenses =
await this.licensesService.getAllocatableLicenses(
context,
payload.userId,
);
await this.licensesService.getAllocatableLicenses(context, userId);
return allocatableLicenses;
}
@ -245,16 +300,26 @@ export class LicensesController {
@Req() req: Request,
@Body() body: CancelOrderRequest,
): Promise<CancelOrderResponse> {
const token = retrieveAuthorizationToken(req);
const payload = jwt.decode(token, { json: true }) as AccessToken;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req) as string;
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(payload.userId);
const context = makeContext(userId);
await this.licensesService.cancelOrder(
context,
payload.userId,
body.poNumber,
);
await this.licensesService.cancelOrder(context, userId, body.poNumber);
return {};
}
}

View File

@ -56,11 +56,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
expect(
await service.licenseOrders(token, body.poNumber, body.orderCount),
await service.licenseOrders(userId, body.poNumber, body.orderCount),
).toEqual(undefined);
});
it('ユーザID取得できなかった場合、エラーとなる', async () => {
@ -78,11 +78,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '', role: '', tier: 5 };
const userId = '';
body.orderCount = 1000;
body.poNumber = '1';
await expect(
service.licenseOrders(token, body.poNumber, body.orderCount),
service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -105,11 +105,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
await expect(
service.licenseOrders(token, body.poNumber, body.orderCount),
service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -130,11 +130,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
await expect(
service.licenseOrders(token, body.poNumber, body.orderCount),
service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E010401'),
@ -154,7 +154,7 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new IssueCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.createCount = 10;
const issueCardLicensesResponse: IssueCardLicensesResponse = {
cardLicenseKeys: [
@ -171,7 +171,7 @@ describe('LicensesService', () => {
],
};
expect(
await service.issueCardLicenseKeys(token.userId, body.createCount),
await service.issueCardLicenseKeys(userId, body.createCount),
).toEqual(issueCardLicensesResponse);
});
it('カードライセンス発行に失敗した場合、エラーになる', async () => {
@ -187,10 +187,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new IssueCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.createCount = 1000;
await expect(
service.issueCardLicenseKeys(token.userId, body.createCount),
service.issueCardLicenseKeys(userId, body.createCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -210,10 +210,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
expect(
await service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
await service.activateCardLicenseKey(userId, body.cardLicenseKey),
).toEqual(undefined);
});
it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => {
@ -229,10 +229,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -254,10 +254,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
);
@ -276,10 +276,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
);
@ -287,7 +287,7 @@ describe('LicensesService', () => {
});
describe('DBテスト', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -300,12 +300,15 @@ describe('DBテスト', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
@ -323,7 +326,9 @@ describe('DBテスト', () => {
});
it('カードライセンス取り込みが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
@ -362,13 +367,15 @@ describe('DBテスト', () => {
);
const dbSelectResultFromLicense = await selectLicense(source, license_id);
expect(
dbSelectResultFromCardLicense.cardLicense.activated_at,
dbSelectResultFromCardLicense?.cardLicense?.activated_at,
).toBeDefined();
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId);
expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId);
});
it('取込可能なライセンスのみが取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const now = new Date();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -513,7 +520,7 @@ describe('DBテスト', () => {
});
describe('ライセンス割り当て', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -526,12 +533,15 @@ describe('ライセンス割り当て', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -567,11 +577,11 @@ describe('ライセンス割り当て', () => {
await service.allocateLicense(makeContext('trackingId'), userId, 1);
const resultLicense = await selectLicense(source, 1);
expect(resultLicense.license.allocated_user_id).toBe(userId);
expect(resultLicense.license.status).toBe(
expect(resultLicense.license?.allocated_user_id).toBe(userId);
expect(resultLicense.license?.status).toBe(
LICENSE_ALLOCATED_STATUS.ALLOCATED,
);
expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual(
expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
@ -579,22 +589,24 @@ describe('ライセンス割り当て', () => {
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
true,
);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId,
);
});
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -630,30 +642,32 @@ describe('ライセンス割り当て', () => {
await service.allocateLicense(makeContext('trackingId'), userId, 1);
const result = await selectLicense(source, 1);
expect(result.license.allocated_user_id).toBe(userId);
expect(result.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result.license.expiry_date).toEqual(date);
expect(result.license?.allocated_user_id).toBe(userId);
expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result.license?.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
true,
);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId,
);
});
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -705,32 +719,32 @@ describe('ライセンス割り当て', () => {
// もともと割り当てられていたライセンスの状態確認
const result1 = await selectLicense(source, 1);
expect(result1.license.allocated_user_id).toBe(null);
expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
expect(result1.license.expiry_date).toEqual(date);
expect(result1.license?.allocated_user_id).toBe(null);
expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
expect(result1.license?.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
false,
);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(false);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId,
);
// 新たに割り当てたライセンスの状態確認
const result2 = await selectLicense(source, 2);
expect(result2.license.allocated_user_id).toBe(userId);
expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual(
expect(result2.license?.allocated_user_id).toBe(userId);
expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
@ -738,22 +752,24 @@ describe('ライセンス割り当て', () => {
userId,
2,
);
expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId,
);
expect(
newlicenseAllocationHistory.licenseAllocationHistory.license_id,
newlicenseAllocationHistory.licenseAllocationHistory?.license_id,
).toBe(2);
expect(
newlicenseAllocationHistory.licenseAllocationHistory.is_allocated,
newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(true);
expect(
newlicenseAllocationHistory.licenseAllocationHistory.account_id,
newlicenseAllocationHistory.licenseAllocationHistory?.account_id,
).toBe(accountId);
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がNORMALのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -806,12 +822,14 @@ describe('ライセンス割り当て', () => {
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('NONE');
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がCARDのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -864,12 +882,14 @@ describe('ライセンス割り当て', () => {
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('CARD');
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がTRIALのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -922,12 +942,14 @@ describe('ライセンス割り当て', () => {
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('TRIAL');
});
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -961,7 +983,9 @@ describe('ライセンス割り当て', () => {
});
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -1013,7 +1037,7 @@ describe('ライセンス割り当て', () => {
});
describe('ライセンス割り当て解除', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -1026,12 +1050,15 @@ describe('ライセンス割り当て解除', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ライセンスの割り当て解除が完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -1068,11 +1095,11 @@ describe('ライセンス割り当て解除', () => {
// 割り当て解除したライセンスの状態確認
const deallocatedLicense = await selectLicense(source, 1);
expect(deallocatedLicense.license.allocated_user_id).toBe(null);
expect(deallocatedLicense.license.status).toBe(
expect(deallocatedLicense.license?.allocated_user_id).toBe(null);
expect(deallocatedLicense.license?.status).toBe(
LICENSE_ALLOCATED_STATUS.REUSABLE,
);
expect(deallocatedLicense.license.expiry_date).toEqual(date);
expect(deallocatedLicense.license?.expiry_date).toEqual(date);
// ライセンス履歴テーブルの状態確認
const licenseAllocationHistory = await selectLicenseAllocationHistory(
@ -1080,25 +1107,27 @@ describe('ライセンス割り当て解除', () => {
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
false,
);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(false);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('NONE');
});
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, {
@ -1158,7 +1187,7 @@ describe('ライセンス割り当て解除', () => {
});
describe('ライセンス注文キャンセル', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -1171,12 +1200,15 @@ describe('ライセンス注文キャンセル', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ライセンス注文のキャンセルが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source,
);
@ -1185,7 +1217,7 @@ describe('ライセンス注文キャンセル', () => {
source,
poNumber,
tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id,
tier2Accounts[0].account.parent_account_id ?? 0,
null,
10,
'Issue Requesting',
@ -1195,7 +1227,7 @@ describe('ライセンス注文キャンセル', () => {
source,
poNumber,
tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id,
tier2Accounts[0].account.parent_account_id ?? 0,
null,
10,
'Order Canceled',
@ -1214,12 +1246,14 @@ describe('ライセンス注文キャンセル', () => {
tier2Accounts[0].account.id,
poNumber,
);
expect(orderRecord.orderLicense.canceled_at).toBeDefined();
expect(orderRecord.orderLicense.status).toBe('Order Canceled');
expect(orderRecord.orderLicense?.canceled_at).toBeDefined();
expect(orderRecord.orderLicense?.status).toBe('Order Canceled');
});
it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source,
);
@ -1228,7 +1262,7 @@ describe('ライセンス注文キャンセル', () => {
source,
poNumber,
tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id,
tier2Accounts[0].account.parent_account_id ?? 0,
null,
10,
'Issued',
@ -1247,7 +1281,9 @@ describe('ライセンス注文キャンセル', () => {
});
it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source,
@ -1257,7 +1293,7 @@ describe('ライセンス注文キャンセル', () => {
source,
poNumber,
tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id,
tier2Accounts[0].account.parent_account_id ?? 0,
null,
10,
'Order Canceled',

View File

@ -33,20 +33,20 @@ export class LicensesService {
* @param body
*/
async licenseOrders(
accessToken: AccessToken,
externalId: string,
poNumber: string,
orderCount: number,
): Promise<void> {
//アクセストークンからユーザーIDを取得する
this.logger.log(`[IN] ${this.licenseOrders.name}`);
const userId = accessToken.userId;
let myAccountId: number;
let parentAccountId: number;
let parentAccountId: number | undefined;
// ユーザIDからアカウントIDを取得する
try {
myAccountId = (await this.usersRepository.findUserByExternalId(userId))
.account_id;
myAccountId = (
await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
switch (e.constructor) {
@ -65,9 +65,13 @@ export class LicensesService {
// 親アカウントIDを取得
try {
parentAccountId = (
await this.accountsRepository.findAccountById(myAccountId)
).parent_account_id;
parentAccountId =
(await this.accountsRepository.findAccountById(myAccountId))
.parent_account_id ?? undefined;
// 親アカウントIDが取得できない場合はエラー
if (parentAccountId === undefined) {
throw new Error('parent account id is undefined');
}
} catch (e) {
this.logger.error(`error=${e}`);
switch (e.constructor) {

View File

@ -124,11 +124,11 @@ export const makeDefaultUsersRepositoryMockValue =
user1.notification = false;
user1.encryption = false;
user1.prompt = false;
user1.deleted_at = undefined;
user1.deleted_at = null;
user1.created_by = 'test';
user1.created_at = new Date();
user1.updated_by = undefined;
user1.updated_at = undefined;
user1.updated_by = null;
user1.updated_at = new Date();
return {
findUserByExternalId: user1,

View File

@ -12,14 +12,14 @@ import {
export const createLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date,
expiry_date: Date | null,
accountId: number,
type: string,
status: string,
allocated_user_id: number,
order_id: number,
deleted_at: Date,
delete_order_id: number,
allocated_user_id: number | null,
order_id: number | null,
deleted_at: Date | null,
delete_order_id: number | null,
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
@ -107,7 +107,7 @@ export const createOrder = async (
poNumber: string,
fromId: number,
toId: number,
issuedAt: Date,
issuedAt: Date | null,
quantity: number,
status: string,
): Promise<void> => {
@ -138,7 +138,7 @@ export const selectCardLicensesCount = async (
export const selectCardLicense = async (
datasource: DataSource,
cardLicenseKey: string,
): Promise<{ cardLicense: CardLicense }> => {
): Promise<{ cardLicense: CardLicense | null }> => {
const cardLicense = await datasource.getRepository(CardLicense).findOne({
where: {
card_license_key: cardLicenseKey,
@ -150,7 +150,7 @@ export const selectCardLicense = async (
export const selectLicense = async (
datasource: DataSource,
id: number,
): Promise<{ license: License }> => {
): Promise<{ license: License | null }> => {
const license = await datasource.getRepository(License).findOne({
where: {
id: id,
@ -163,7 +163,7 @@ export const selectLicenseAllocationHistory = async (
datasource: DataSource,
userId: number,
licence_id: number,
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => {
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
const licenseAllocationHistory = await datasource
.getRepository(LicenseAllocationHistory)
.findOne({
@ -182,7 +182,7 @@ export const selectOrderLicense = async (
datasource: DataSource,
accountId: number,
poNumber: string,
): Promise<{ orderLicense: LicenseOrder }> => {
): Promise<{ orderLicense: LicenseOrder | null }> => {
const orderLicense = await datasource.getRepository(LicenseOrder).findOne({
where: {
from_account_id: accountId,

View File

@ -47,8 +47,8 @@ export class GetAllocatableLicensesRequest {}
export class AllocatableLicenseInfo {
@ApiProperty()
licenseId: number;
@ApiProperty()
expiryDate: Date;
@ApiProperty({ required: false })
expiryDate?: Date;
}
export class GetAllocatableLicensesResponse {
@ApiProperty({ type: [AllocatableLicenseInfo] })

View File

@ -219,6 +219,8 @@ describe('TasksService', () => {
audio_format: 'DS',
comment: 'comment',
is_encrypted: true,
deleted_at: null,
task: null,
},
},
],

View File

@ -263,6 +263,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1,
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
},
{
id: 2,
@ -270,6 +275,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 2,
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
},
{
id: 3,
@ -277,6 +287,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1,
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
},
{
id: 4,
@ -284,6 +299,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1,
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
},
{
id: 5,
@ -291,6 +311,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 3,
created_by: 'test',
updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
},
],
};
@ -343,60 +368,70 @@ const defaultTasksRepositoryMockValue: {
audio_file_id: 1,
label: 'label01',
value: 'value01',
task: null,
},
{
id: 2,
audio_file_id: 1,
label: 'label02',
value: 'value02',
task: null,
},
{
id: 3,
audio_file_id: 1,
label: 'label03',
value: 'value03',
task: null,
},
{
id: 4,
audio_file_id: 1,
label: 'label04',
value: 'value04',
task: null,
},
{
id: 5,
audio_file_id: 1,
label: 'label05',
value: 'value05',
task: null,
},
{
id: 6,
audio_file_id: 1,
label: 'label06',
value: 'value06',
task: null,
},
{
id: 7,
audio_file_id: 1,
label: 'label07',
value: 'value07',
task: null,
},
{
id: 8,
audio_file_id: 1,
label: 'label08',
value: 'value08',
task: null,
},
{
id: 9,
audio_file_id: 1,
label: 'label09',
value: 'value09',
task: null,
},
{
id: 10,
audio_file_id: 1,
label: 'label10',
value: 'value10',
task: null,
},
],
file: {
@ -416,6 +451,8 @@ const defaultTasksRepositoryMockValue: {
audio_format: 'DS',
comment: 'comment',
is_encrypted: true,
deleted_at: null,
task: null,
},
},
],
@ -448,6 +485,9 @@ const defaultTasksRepositoryMockValue: {
license: null,
userGroupMembers: null,
},
task: null,
user_group_id: null,
user_group: null,
},
],
count: 1,

View File

@ -113,6 +113,7 @@ describe('UsersService.confirmUser', () => {
allocated_user_id: resultLicenses[0].allocated_user_id,
order_id: resultLicenses[0].order_id,
delete_order_id: resultLicenses[0].delete_order_id,
user: resultLicenses[0].user ?? null,
};
expect(resultUser.email_verified).toBe(true);
@ -126,8 +127,9 @@ describe('UsersService.confirmUser', () => {
allocated_user_id: null,
order_id: null,
delete_order_id: null,
user: null,
});
});
}, 600000);
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
if (!source) fail();
@ -1762,7 +1764,7 @@ describe('UsersService.getUsers', () => {
await expect(service.getUsers('externalId_failed')).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
);
},60000000);
}, 60000000);
it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();

View File

@ -503,18 +503,27 @@ export class UsersService {
if (dbUser.license) {
// 有効期限日付 YYYY/MM/DD
const expiry_date = dbUser.license.expiry_date;
expiration = `${expiry_date.getFullYear()}/${
expiry_date.getMonth() + 1
}/${expiry_date.getDate()}`;
const expiry_date = dbUser.license.expiry_date ?? undefined;
expiration =
expiry_date !== undefined
? `${expiry_date.getFullYear()}/${
expiry_date.getMonth() + 1
}/${expiry_date.getDate()}`
: undefined;
const currentDate = new DateWithZeroTime();
// 有効期限までの日数
remaining = Math.floor(
(expiry_date.getTime() - currentDate.getTime()) /
(1000 * 60 * 60 * 24),
);
if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) {
remaining =
expiry_date !== undefined
? Math.floor(
(expiry_date.getTime() - currentDate.getTime()) /
(1000 * 60 * 60 * 24),
)
: undefined;
if (
remaining !== undefined &&
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
) {
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT;

View File

@ -14,7 +14,7 @@ export class Account {
id: number;
@Column({ nullable: true })
parent_account_id?: number;
parent_account_id: number | null;
@Column()
tier: number;
@ -44,7 +44,7 @@ export class Account {
active_worktype_id?: number;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by?: string;

View File

@ -33,11 +33,11 @@ export class AudioFile {
@Column()
audio_format: string;
@Column({ nullable: true })
comment?: string;
comment: string | null;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column()
is_encrypted: boolean;
@OneToOne(() => Task, (task) => task.file)
task?: Task;
task: Task | null;
}

View File

@ -19,5 +19,5 @@ export class AudioOptionItem {
value: string;
@ManyToOne(() => Task, (task) => task.audio_file_id)
@JoinColumn({ name: 'audio_file_id' })
task?: Task;
task: Task | null;
}

View File

@ -19,20 +19,20 @@ export class CheckoutPermission {
task_id: number;
@Column({ nullable: true })
user_id?: number;
user_id: number | null;
@Column({ nullable: true })
user_group_id?: number;
user_group_id: number | null;
@OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'user_id' })
user?: User;
user: User | null;
@OneToOne(() => UserGroup, (group) => group.id)
@JoinColumn({ name: 'user_group_id' })
user_group?: UserGroup;
user_group: UserGroup | null;
@ManyToOne(() => Task, (task) => task.id)
@JoinColumn({ name: 'task_id' })
task?: Task;
task: Task | null;
}

View File

@ -25,11 +25,11 @@ export class LicenseOrder {
@Column()
to_account_id: number;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
ordered_at: Date;
@Column({ nullable: true })
issued_at?: Date;
issued_at: Date | null;
@Column()
quantity: number;
@ -38,18 +38,18 @@ export class LicenseOrder {
status: string;
@Column({ nullable: true })
canceled_at?: Date;
canceled_at: Date | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@UpdateDateColumn()
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" })
updated_at: Date;
}
@ -59,7 +59,7 @@ export class License {
id: number;
@Column({ nullable: true })
expiry_date: Date;
expiry_date: Date | null;
@Column()
account_id: number;
@ -71,34 +71,34 @@ export class License {
status: string;
@Column({ nullable: true })
allocated_user_id: number;
allocated_user_id: number | null;
@Column({ nullable: true })
order_id: number;
order_id: number | null;
@Column({ nullable: true })
deleted_at: Date;
deleted_at: Date | null;
@Column({ nullable: true })
delete_order_id: number;
delete_order_id: number | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@UpdateDateColumn()
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" })
updated_at: Date;
@OneToOne(() => User, (user) => user.license, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: 'allocated_user_id' })
user?: User;
user: User | null;
}
@Entity({ name: 'card_license_issue' })
@ -110,15 +110,15 @@ export class CardLicenseIssue {
issued_at: Date;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@UpdateDateColumn()
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" })
updated_at: Date;
}
@ -134,18 +134,18 @@ export class CardLicense {
card_license_key: string;
@Column({ nullable: true })
activated_at: Date;
activated_at: Date | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@UpdateDateColumn({})
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" })
updated_at: Date;
}
@ -173,25 +173,25 @@ export class LicenseAllocationHistory {
switch_from_type: string;
@Column({ nullable: true })
deleted_at: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@UpdateDateColumn()
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" })
updated_at: Date;
@ManyToOne(() => License, (licenses) => licenses.id, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: 'license_id' })
license?: License;
license: License | null;
}
@Entity({ name: 'licenses_archive' })
@ -200,7 +200,7 @@ export class LicenseArchive {
id: number;
@Column({ nullable: true })
expiry_date: Date;
expiry_date: Date | null;
@Column()
account_id: number;
@ -212,30 +212,30 @@ export class LicenseArchive {
status: string;
@Column({ nullable: true })
allocated_user_id: number;
allocated_user_id: number | null;
@Column({ nullable: true })
order_id: number;
order_id: number | null;
@Column({ nullable: true })
deleted_at: Date;
deleted_at: Date | null;
@Column({ nullable: true })
delete_order_id: number;
delete_order_id: number | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@Column()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@Column()
updated_at: Date;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
archived_at: Date;
}
@ -263,20 +263,20 @@ export class LicenseAllocationHistoryArchive {
switch_from_type: string;
@Column({ nullable: true })
deleted_at: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by: string;
created_by: string | null;
@Column()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
updated_by: string | null;
@Column()
updated_at: Date;
@CreateDateColumn()
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" })
archived_at: Date;
}

View File

@ -444,7 +444,7 @@ export class LicensesRepositoryService {
const allocatableLicenses = await queryBuilder.getMany();
return allocatableLicenses.map((license) => ({
licenseId: license.id,
expiryDate: license.expiry_date,
expiryDate: license.expiry_date ?? undefined,
}));
}
/**
@ -469,6 +469,13 @@ export class LicensesRepositoryService {
},
});
// ライセンスが存在しない場合はエラー
if (!targetLicense) {
throw new LicenseNotExistError(
`License not exist. licenseId: ${newLicenseId}`,
);
}
// 期限切れの場合はエラー
if (targetLicense.expiry_date) {
const currentDay = new Date();
@ -533,7 +540,7 @@ export class LicensesRepositoryService {
});
let switchFromType = '';
if (oldLicenseType) {
if (oldLicenseType && oldLicenseType.license) {
switch (oldLicenseType.license.type) {
case LICENSE_TYPE.CARD:
switchFromType = SWITCH_FROM_TYPE.CARD;

View File

@ -844,8 +844,8 @@ export class TasksRepositoryService {
(assignee) => {
const checkoutPermission = new CheckoutPermission();
checkoutPermission.task_id = taskRecord.id;
checkoutPermission.user_id = assignee.typistUserId;
checkoutPermission.user_group_id = assignee.typistGroupId;
checkoutPermission.user_id = assignee.typistUserId ?? null;
checkoutPermission.user_group_id = assignee.typistGroupId ?? null;
return checkoutPermission;
},
);

View File

@ -20,23 +20,23 @@ export class UserGroup {
name: string;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by?: string;
created_by: string | null;
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
created_at?: Date;
created_at: Date | null;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at?: Date;
updated_at: Date | null;
@OneToMany(
() => UserGroupMember,
(userGroupMember) => userGroupMember.userGroup,
)
userGroupMembers?: UserGroupMember[];
userGroupMembers: UserGroupMember[] | null;
}

View File

@ -22,25 +22,25 @@ export class UserGroupMember {
user_id: number;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by?: string;
created_by: string | null;
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
created_at?: Date;
created_at: Date | null;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at?: Date;
updated_at: Date | null;
@ManyToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'user_id' })
user?: User;
user: User | null;
@ManyToOne(() => UserGroup, (userGroup) => userGroup.id)
@JoinColumn({ name: 'user_group_id' })
userGroup?: UserGroup;
userGroup: UserGroup | null;
}

View File

@ -19,11 +19,11 @@ export class OptionItem {
@Column()
initial_value: string;
@Column({ nullable: true })
created_by?: string;
created_by: string | null;
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
created_at?: Date;
created_at: Date | null;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at?: Date;
updated_at: Date | null;
}

View File

@ -19,10 +19,10 @@ export class Worktype {
custom_worktype_id: string;
@Column({ nullable: true })
description?: string;
description: string | null;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by: string;
@ -31,7 +31,7 @@ export class Worktype {
created_at: Date;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date;

View File

@ -153,7 +153,7 @@ export class WorktypesRepositoryService {
// ワークタイプを更新
worktype.custom_worktype_id = worktypeId;
worktype.description = description;
worktype.description = description ?? null;
await worktypeRepo.save(worktype);
});
}