Merged PR 497: strictNullChecks修正③(task,users ,Repositoriesのusers)

## 概要
[Task2837: 修正③(task,users ,Repositoriesのusers)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2837)

- feature
  - tasks
  - users
- Repositories
  - users

## レビューポイント
- 該当箇所にコメントで記載

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

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-10-16 10:25:45 +00:00
parent c9bc6393c6
commit d2c2223acf
22 changed files with 827 additions and 462 deletions

View File

@ -132,7 +132,7 @@ export const getPrivateKey = (configService: ConfigService): string => {
return (
// 開発環境用に改行コードを置換する
// 本番環境では\\nが含まれないため、置換が行われない想定
configService.get<string>('JWT_PRIVATE_KEY')?.replace(/\\n/g, '\n') ?? ''
configService.getOrThrow<string>('JWT_PRIVATE_KEY').replace(/\\n/g, '\n')
);
};
@ -140,6 +140,6 @@ export const getPublicKey = (configService: ConfigService): string => {
return (
// 開発環境用に改行コードを置換する
// 本番環境では\\nが含まれないため、置換が行われない想定
configService.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n') ?? ''
configService.getOrThrow<string>('JWT_PUBLIC_KEY').replace(/\\n/g, '\n')
);
};

View File

@ -140,6 +140,9 @@ export const makeDefaultUsersRepositoryMockValue =
notification: true,
encryption: false,
prompt: false,
encryption_password: null,
license: null,
userGroupMembers: null,
account: {
id: 2,
parent_account_id: 2,
@ -172,6 +175,14 @@ export const makeDefaultTasksRepositoryMockValue =
status: 'Uploaded',
priority: '01',
created_at: new Date(),
finished_at: null,
started_at: null,
template_file_id: null,
typist_user_id: null,
file: null,
option_items: null,
template_file: null,
typist_user: null,
},
getTasksFromAccountId: {
tasks: [],

View File

@ -3,6 +3,7 @@ import {
Controller,
Get,
Headers,
HttpException,
HttpStatus,
Param,
ParseIntPipe,
@ -32,6 +33,8 @@ import {
TasksResponse,
} from './types/types';
import {
SortDirection,
TaskListSortableAttribute,
isSortDirection,
isTaskListSortableAttribute,
} from '../../common/types/sort';
@ -43,6 +46,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { Roles } from '../../common/types/role';
import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('tasks')
@Controller('tasks')
@ -80,22 +84,39 @@ export class TasksController {
@Req() req,
@Query() body: TasksRequest,
): Promise<TasksResponse> {
const accessToken = retrieveAuthorizationToken(req);
const decodedToken = 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, role } = decodedAccessToken as AccessToken;
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[];
const context = makeContext(decodedToken.userId);
const context = makeContext(userId);
const { limit, offset, status } = body;
const paramName = isTaskListSortableAttribute(body.paramName)
? body.paramName
const paramName = isTaskListSortableAttribute(body.paramName ?? '')
? (body.paramName as TaskListSortableAttribute)
: undefined;
const direction = isSortDirection(body.direction)
? body.direction
const direction = isSortDirection(body.direction ?? '')
? (body.direction as SortDirection)
: undefined;
const { tasks, total } = await this.taskService.getTasks(
context,
decodedToken,
userId,
roles,
offset,
limit,
// statusが指定されていない場合は全てのステータスを取得する
@ -183,10 +204,22 @@ export class TasksController {
@Param() param: ChangeStatusRequest,
): Promise<ChangeStatusResponse> {
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { role, userId } = 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, role } = decodedAccessToken as AccessToken;
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[];
@ -241,10 +274,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> {
const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId } = 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, role } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
@ -296,10 +341,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> {
const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId, role } = 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, role } = decodedAccessToken as AccessToken;
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[];
@ -353,10 +410,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> {
const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId } = 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 context = makeContext(userId);
@ -491,11 +560,22 @@ export class TasksController {
@Body() body: PostCheckoutPermissionRequest,
): Promise<PostCheckoutPermissionResponse> {
const { assignees } = body;
const accessToken = retrieveAuthorizationToken(req);
const { role, userId } = 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, role } = decodedAccessToken as AccessToken;
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[];

File diff suppressed because it is too large Load Diff

View File

@ -45,10 +45,10 @@ export class TasksService {
private readonly notificationhubService: NotificationhubService,
) {}
// TODO [Task2244] 引数にAccessTokenがあるのは不適切なのでController側で分解したい
async getTasks(
context: Context,
accessToken: AccessToken,
userId: string,
roles: Roles[],
offset: number,
limit: number,
status?: string[],
@ -59,10 +59,6 @@ export class TasksService {
`[IN] [${context.trackingId}] ${this.getTasks.name} | params: { offset: ${offset}, limit: ${limit}, status: ${status}, paramName: ${paramName}, direction: ${direction} };`,
);
const { role, userId } = accessToken;
// TODO [Task2244] Roleに型で定義されている値が入っているかをチェックして異常値を弾く実装に修正する
const roles = role.split(' ');
// パラメータが省略された場合のデフォルト値: 保存するソート条件の値の初期値と揃える
const defaultParamName: TaskListSortableAttribute = 'JOB_NUMBER';
const defaultDirection: SortDirection = 'ASC';
@ -95,6 +91,10 @@ export class TasksService {
return { tasks: tasks, total: result.count };
}
if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
throw new Error('AuthorID not found');
}
const result =
await this.taskRepository.getTasksFromAuthorIdAndAccountId(
author_id,
@ -179,6 +179,10 @@ export class TasksService {
await this.usersRepository.findUserByExternalId(externalId);
if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
throw new Error('AuthorID not found');
}
await this.taskRepository.getTaskFromAudioFileId(
audioFileId,
account_id,
@ -407,30 +411,21 @@ export class TasksService {
permissions: CheckoutPermission[],
): Promise<AdB2cUser[]> {
// 割り当て候補の外部IDを列挙
const assigneesExternalIds = permissions.map((x) => {
if (x.user) {
return x.user.external_id;
}
});
const assigneesExternalIds = permissions.flatMap((permission) =>
permission.user ? [permission.user.external_id] : [],
);
// 割り当てられているタイピストの外部IDを列挙
const typistExternalIds = tasks.flatMap((x) => {
if (x.typist_user) {
return x.typist_user.external_id;
}
});
const typistExternalIds = tasks.flatMap((task) =>
task.typist_user ? [task.typist_user.external_id] : [],
);
//重複をなくす
const distinctedExternalIds = [
...new Set(assigneesExternalIds.concat(typistExternalIds)),
];
// undefinedがあった場合、取り除く
const filteredExternalIds: string[] = distinctedExternalIds.filter(
(x): x is string => x !== undefined,
);
// B2Cからユーザー名を取得する
return await this.adB2cService.getUsers(context, filteredExternalIds);
return await this.adB2cService.getUsers(context, distinctedExternalIds);
}
/**
*
@ -451,10 +446,14 @@ export class TasksService {
);
const { author_id, account_id } =
await this.usersRepository.findUserByExternalId(externalId);
// RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (role.includes(USER_ROLES.AUTHOR) && !author_id) {
throw new Error('AuthorID not found');
}
await this.taskRepository.changeCheckoutPermission(
audioFileId,
author_id,
author_id ?? undefined,
account_id,
role,
assignees,
@ -462,11 +461,16 @@ export class TasksService {
// すべての割り当て候補ユーザーを取得する
const assigneesGroupIds = assignees
.filter((x) => x.typistGroupId)
.map((x) => x.typistGroupId);
.filter((assignee) => assignee.typistGroupId)
.flatMap((assignee) =>
assignee.typistGroupId ? [assignee.typistGroupId] : [],
);
const assigneesUserIds = assignees
.filter((x) => x.typistUserId)
.map((x) => x.typistUserId);
.filter((assignee) => assignee.typistUserId)
.flatMap((assignee) =>
assignee.typistUserId ? [assignee.typistUserId] : [],
);
const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(

View File

@ -307,7 +307,7 @@ export const makeDefaultUsersRepositoryMockValue =
user1.auto_renew = false;
user1.license_alert = false;
user1.notification = false;
user1.deleted_at = undefined;
user1.deleted_at = null;
user1.created_by = 'test';
user1.created_at = new Date();
user1.author_id = 'abcdef';
@ -331,6 +331,12 @@ const defaultTasksRepositoryMockValue: {
status: 'Uploaded',
priority: '00',
created_at: new Date('2023-01-01T01:01:01.000Z'),
finished_at: null,
started_at: null,
typist_user_id: null,
template_file_id: null,
typist_user: null,
template_file: null,
option_items: [
{
id: 1,
@ -435,6 +441,12 @@ const defaultTasksRepositoryMockValue: {
created_at: new Date(),
updated_by: 'test',
updated_at: new Date(),
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
},
},
],

View File

@ -39,10 +39,10 @@ import {
makeNotificationhubServiceMock,
} from './tasks.service.mock';
export const makeTaskTestingModule = async (
export const makeTaskTestingModuleWithNotificaiton = async (
datasource: DataSource,
notificationhubServiceMockValue: NotificationhubServiceMockValue,
): Promise<TestingModule> => {
): Promise<TestingModule | undefined> => {
try {
const module: TestingModule = await Test.createTestingModule({
imports: [
@ -205,7 +205,7 @@ export const createUserGroup = async (
export const getTask = async (
datasource: DataSource,
task_id: number,
): Promise<Task> => {
): Promise<Task | null> => {
const task = await datasource.getRepository(Task).findOne({
where: {
id: task_id,

View File

@ -45,7 +45,7 @@ const createTask = (
const assignees = createAssignees(permissions, b2cUserInfo);
// RepositoryDTO => ControllerDTOに変換
const typist: Typist =
const typist: Typist | undefined =
typist_user != null
? convertUserToTypist(typist_user, b2cUserInfo)
: undefined;
@ -113,7 +113,10 @@ const convertUserToAssignee = (
): Assignee => {
const typistName = b2cUserInfo.find(
(x) => x.id === user.external_id,
).displayName;
)?.displayName;
if (!typistName) {
throw new Error('typistName not found.');
}
return {
typistUserId: user.id,
typistName,
@ -135,7 +138,10 @@ const convertUserToTypist = (
): Typist => {
const typistName = b2cUserInfo.find(
(x) => x.id === user.external_id,
).displayName;
)?.displayName;
if (!typistName) {
throw new Error('typistName not found.');
}
return {
id: user.id,
name: typistName,

View File

@ -64,7 +64,7 @@ export type ConfigMockValue = {
export const makeUsersServiceMock = async (
usersRepositoryMockValue: UsersRepositoryMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue | null,
adB2cMockValue: AdB2cMockValue,
sendGridMockValue: SendGridMockValue,
configMockValue: ConfigMockValue,
@ -368,7 +368,7 @@ export const makeDefaultUsersRepositoryMockValue =
user1.created_by = 'test';
user1.created_at = new Date();
user1.updated_by = null;
user1.updated_at = null;
user1.updated_at = new Date();
const user2 = new User();
user2.id = 3;
@ -388,7 +388,7 @@ export const makeDefaultUsersRepositoryMockValue =
user2.created_by = 'test';
user2.created_at = new Date();
user2.updated_by = null;
user2.updated_at = null;
user2.updated_at = new Date();
return {
updateUserVerified: undefined,

View File

@ -107,7 +107,7 @@ export const createLicense = async (
export const makeTestingModuleWithAdb2c = async (
datasource: DataSource,
adB2cMockValue: AdB2cMockValue,
): Promise<TestingModule> => {
): Promise<TestingModule | undefined> => {
try {
const module: TestingModule = await Test.createTestingModule({
imports: [

View File

@ -130,10 +130,24 @@ export class UsersController {
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get()
async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
const accessToken = retrieveAuthorizationToken(req);
const decodedToken = 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 users = await this.usersService.getUsers(decodedToken.userId);
const users = await this.usersService.getUsers(userId);
return { users };
}
@ -179,15 +193,29 @@ export class UsersController {
prompt,
} = body;
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 context = makeContext(payload.userId);
const context = makeContext(userId);
//ユーザ作成処理
await this.usersService.createUser(
context,
payload,
userId,
name,
role as UserRoles,
email,
@ -225,8 +253,22 @@ export class UsersController {
@UseGuards(AuthGuard)
@Get('relations')
async getRelations(@Req() req: Request): Promise<GetRelationsResponse> {
const token = retrieveAuthorizationToken(req);
const { userId } = 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(userId);
@ -265,8 +307,22 @@ export class UsersController {
@Req() req: Request,
): Promise<PostSortCriteriaResponse> {
const { direction, paramName } = body;
const accessToken = retrieveAuthorizationToken(req);
const decodedToken = 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;
//型チェック
if (
@ -278,11 +334,7 @@ export class UsersController {
HttpStatus.BAD_REQUEST,
);
}
await this.usersService.updateSortCriteria(
paramName,
direction,
decodedToken,
);
await this.usersService.updateSortCriteria(paramName, direction, userId);
return {};
}
@ -313,11 +365,25 @@ export class UsersController {
@Req() req: Request,
): Promise<GetSortCriteriaResponse> {
const {} = query;
const accessToken = retrieveAuthorizationToken(req);
const decodedToken = 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 { direction, paramName } = await this.usersService.getSortCriteria(
decodedToken,
userId,
);
return { direction, paramName };
}
@ -366,8 +432,22 @@ export class UsersController {
prompt,
} = body;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
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);
@ -421,8 +501,22 @@ export class UsersController {
@Body() body: AllocateLicenseRequest,
@Req() req: Request,
): Promise<AllocateLicenseResponse> {
const accessToken = retrieveAuthorizationToken(req);
const { userId } = 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 context = makeContext(userId);
await this.usersService.allocateLicense(
@ -467,8 +561,22 @@ export class UsersController {
@Body() body: DeallocateLicenseRequest,
@Req() req: Request,
): Promise<DeallocateLicenseResponse> {
const accessToken = retrieveAuthorizationToken(req);
const { userId } = 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 context = makeContext(userId);

View File

@ -46,7 +46,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
describe('UsersService.confirmUser', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -57,12 +57,15 @@ describe('UsersService.confirmUser', () => {
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになり、トライアルライセンスが100件作成される', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = (await makeTestAccount(source)).account;
const { id: userId } = await makeTestUser(source, {
account_id: accountId,
@ -127,7 +130,9 @@ describe('UsersService.confirmUser', () => {
});
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const token = 'invalid.id.token';
const service = module.get<UsersService>(UsersService);
await expect(service.confirmUser(token)).rejects.toEqual(
@ -136,7 +141,9 @@ describe('UsersService.confirmUser', () => {
});
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = (await makeTestAccount(source)).account;
await makeTestUser(source, {
account_id: accountId,
@ -168,7 +175,9 @@ describe('UsersService.confirmUser', () => {
);
});
it('ユーザーが存在しない場合は、想定外のエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -201,6 +210,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true,
encryption: false,
prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
@ -246,6 +261,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true,
encryption: false,
prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
@ -287,6 +308,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true,
encryption: false,
prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
@ -332,6 +359,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true,
encryption: false,
prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
@ -362,7 +395,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
});
describe('UsersService.createUser', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -375,12 +408,15 @@ describe('UsersService.createUser', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
@ -433,7 +469,7 @@ describe('UsersService.createUser', () => {
expect(
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -446,16 +482,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId);
expect(user.role).toEqual(role);
expect(user.author_id).toEqual(null);
expect(user.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification);
expect(user.encryption).toEqual(false);
expect(user.encryption_password).toEqual(null);
expect(user.prompt).toEqual(false);
expect(user?.account_id).toEqual(accountId);
expect(user?.role).toEqual(role);
expect(user?.author_id).toEqual(null);
expect(user?.email_verified).toEqual(false);
expect(user?.auto_renew).toEqual(autoRenew);
expect(user?.license_alert).toEqual(licenseAlert);
expect(user?.notification).toEqual(notification);
expect(user?.encryption).toEqual(false);
expect(user?.encryption_password).toEqual(null);
expect(user?.prompt).toEqual(false);
// 他にユーザーが登録されていないことを確認
const users = await getUsers(source);
@ -463,7 +499,9 @@ describe('UsersService.createUser', () => {
});
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -519,7 +557,7 @@ describe('UsersService.createUser', () => {
expect(
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -536,16 +574,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId);
expect(user.role).toEqual(role);
expect(user.author_id).toEqual(authorId);
expect(user.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification);
expect(user.encryption).toEqual(encryption);
expect(user.encryption_password).toEqual(encryptionPassword);
expect(user.prompt).toEqual(prompt);
expect(user?.account_id).toEqual(accountId);
expect(user?.role).toEqual(role);
expect(user?.author_id).toEqual(authorId);
expect(user?.email_verified).toEqual(false);
expect(user?.auto_renew).toEqual(autoRenew);
expect(user?.license_alert).toEqual(licenseAlert);
expect(user?.notification).toEqual(notification);
expect(user?.encryption).toEqual(encryption);
expect(user?.encryption_password).toEqual(encryptionPassword);
expect(user?.prompt).toEqual(prompt);
// 他にユーザーが登録されていないことを確認
const users = await getUsers(source);
@ -553,7 +591,9 @@ describe('UsersService.createUser', () => {
});
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -608,7 +648,7 @@ describe('UsersService.createUser', () => {
expect(
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -625,16 +665,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId);
expect(user.role).toEqual(role);
expect(user.author_id).toEqual(authorId);
expect(user.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification);
expect(user.encryption).toEqual(encryption);
expect(user.encryption_password).toBeNull();
expect(user.prompt).toEqual(prompt);
expect(user?.account_id).toEqual(accountId);
expect(user?.role).toEqual(role);
expect(user?.author_id).toEqual(authorId);
expect(user?.email_verified).toEqual(false);
expect(user?.auto_renew).toEqual(autoRenew);
expect(user?.license_alert).toEqual(licenseAlert);
expect(user?.notification).toEqual(notification);
expect(user?.encryption).toEqual(encryption);
expect(user?.encryption_password).toBeNull();
expect(user?.prompt).toEqual(prompt);
// 他にユーザーが登録されていないことを確認
const users = await getUsers(source);
@ -642,7 +682,9 @@ describe('UsersService.createUser', () => {
});
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -694,7 +736,7 @@ describe('UsersService.createUser', () => {
expect(
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -707,16 +749,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId);
expect(user.role).toEqual(role);
expect(user.author_id).toBeNull();
expect(user.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification);
expect(user.encryption).toEqual(false);
expect(user.encryption_password).toBeNull();
expect(user.prompt).toEqual(false);
expect(user?.account_id).toEqual(accountId);
expect(user?.role).toEqual(role);
expect(user?.author_id).toBeNull();
expect(user?.email_verified).toEqual(false);
expect(user?.auto_renew).toEqual(autoRenew);
expect(user?.license_alert).toEqual(licenseAlert);
expect(user?.notification).toEqual(notification);
expect(user?.encryption).toEqual(false);
expect(user?.encryption_password).toBeNull();
expect(user?.prompt).toEqual(false);
// 他にユーザーが登録されていないことを確認
const users = await getUsers(source);
@ -724,7 +766,9 @@ describe('UsersService.createUser', () => {
});
it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001';
@ -785,7 +829,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -809,7 +853,9 @@ describe('UsersService.createUser', () => {
});
it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001';
@ -870,7 +916,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -899,7 +945,9 @@ describe('UsersService.createUser', () => {
});
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -949,7 +997,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -972,7 +1020,9 @@ describe('UsersService.createUser', () => {
});
it('Azure AD B2C内でメールアドレスが重複している場合、エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -1026,7 +1076,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -1049,7 +1099,9 @@ describe('UsersService.createUser', () => {
});
it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount(
@ -1105,7 +1157,7 @@ describe('UsersService.createUser', () => {
expect(
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email_1,
@ -1146,7 +1198,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email_2,
@ -1175,7 +1227,9 @@ describe('UsersService.createUser', () => {
});
it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001';
@ -1240,7 +1294,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -1272,7 +1326,9 @@ describe('UsersService.createUser', () => {
});
it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService);
@ -1327,7 +1383,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -1357,7 +1413,9 @@ describe('UsersService.createUser', () => {
});
it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService);
@ -1417,7 +1475,7 @@ describe('UsersService.createUser', () => {
try {
await service.createUser(
makeContext('trackingId'),
token,
adminExternalId,
name,
role,
email,
@ -1446,7 +1504,7 @@ describe('UsersService.createUser', () => {
});
describe('UsersService.getUsers', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -1459,13 +1517,16 @@ describe('UsersService.getUsers', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId_author, id: authorUserId } =
@ -1565,7 +1626,9 @@ describe('UsersService.getUsers', () => {
it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: user1, external_id: external_id1 } = await makeTestUser(
@ -1679,7 +1742,9 @@ describe('UsersService.getUsers', () => {
it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1697,12 +1762,14 @@ describe('UsersService.getUsers', () => {
await expect(service.getUsers('externalId_failed')).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
);
});
},60000000);
it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
adb2cParam.getUsers = new Error('ADB2C error');
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId_author } = await makeTestUser(source, {
@ -1742,11 +1809,7 @@ describe('UsersService.updateSortCriteria', () => {
);
expect(
await service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
await service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
).toEqual(undefined);
});
@ -1771,11 +1834,7 @@ describe('UsersService.updateSortCriteria', () => {
);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -1806,11 +1865,7 @@ describe('UsersService.updateSortCriteria', () => {
);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -1838,13 +1893,10 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue,
);
expect(
await service.getSortCriteria({
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' });
expect(await service.getSortCriteria('external_id')).toEqual({
direction: 'ASC',
paramName: 'JOB_NUMBER',
});
});
it('ソート条件が存在せず、ソート条件を取得できない', async () => {
@ -1869,13 +1921,7 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue,
);
await expect(
service.getSortCriteria({
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual(
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1907,13 +1953,7 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue,
);
await expect(
service.getSortCriteria({
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual(
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1923,7 +1963,7 @@ describe('UsersService.getSortCriteria', () => {
});
describe('UsersService.updateUser', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -1936,12 +1976,15 @@ describe('UsersService.updateUser', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ユーザー情報を更新できるNone', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -1997,7 +2040,9 @@ describe('UsersService.updateUser', () => {
});
it('ユーザー情報を更新できるTypist', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2053,7 +2098,9 @@ describe('UsersService.updateUser', () => {
});
it('ユーザー情報を更新できるAuthor', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2109,7 +2156,9 @@ describe('UsersService.updateUser', () => {
});
it('ユーザーのRoleを更新できるNone⇒Typist', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2165,7 +2214,9 @@ describe('UsersService.updateUser', () => {
});
it('ユーザーのRoleを更新できるNone⇒Author', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2221,7 +2272,9 @@ describe('UsersService.updateUser', () => {
});
it('None以外からRoleを変更した場合、エラーとなるTypist⇒None', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2267,7 +2320,9 @@ describe('UsersService.updateUser', () => {
});
it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持するEncryptionがtrue', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2323,7 +2378,9 @@ describe('UsersService.updateUser', () => {
});
it('Authorが暗号化なしで更新した場合、パスワードをNULLにするEncryptionがfalse', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2379,7 +2436,9 @@ describe('UsersService.updateUser', () => {
});
it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなるEncryptionがtrue', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2425,7 +2484,9 @@ describe('UsersService.updateUser', () => {
});
it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, {
@ -2483,7 +2544,7 @@ describe('UsersService.updateUser', () => {
});
describe('UsersService.updateAcceptedVersion', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -2496,12 +2557,15 @@ describe('UsersService.updateAcceptedVersion', () => {
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('同意済み利用規約バージョンを更新できる(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, {
tier: 5,
});
@ -2515,7 +2579,9 @@ describe('UsersService.updateAcceptedVersion', () => {
});
it('同意済み利用規約バージョンを更新できる(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, {
tier: 4,
});
@ -2534,23 +2600,10 @@ describe('UsersService.updateAcceptedVersion', () => {
expect(user.accepted_dpa_version).toBe('v3.0');
});
it('パラメータが不在のときエラーとなる(第五)', async () => {
const module = await makeTestingModule(source);
const { admin } = await makeTestAccount(source, {
tier: 5,
});
const context = makeContext(uuidv4());
const service = module.get<UsersService>(UsersService);
await expect(
service.updateAcceptedVersion(context, admin.external_id, undefined),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
);
});
it('パラメータが不在のときエラーとなる(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, {
tier: 4,
});

View File

@ -48,9 +48,13 @@ import {
LicenseExpiredError,
LicenseUnavailableError,
} from '../../repositories/licenses/errors/types';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@Injectable()
export class UsersService {
private readonly logger = new Logger(UsersService.name);
private readonly mailFrom: string;
private readonly appDomain: string;
constructor(
private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
@ -58,8 +62,10 @@ export class UsersService {
private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService,
private readonly sendgridService: SendGridService,
) {}
private readonly logger = new Logger(UsersService.name);
) {
this.mailFrom = this.configService.getOrThrow<string>('MAIL_FROM');
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
}
/**
* Confirms user
@ -128,7 +134,7 @@ export class UsersService {
*/
async createUser(
context: Context,
accessToken: AccessToken,
externalId: string,
name: string,
role: UserRoles,
email: string,
@ -145,9 +151,7 @@ export class UsersService {
//DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser;
try {
adminUser = await this.usersRepository.findUserByExternalId(
accessToken.userId,
);
adminUser = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
throw new HttpException(
@ -254,9 +258,6 @@ export class UsersService {
//Email送信用のコンテンツを作成する
try {
// メールの送信元を取得
const from = this.configService.get<string>('MAIL_FROM') ?? '';
// メールの内容を構成
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
@ -269,7 +270,7 @@ export class UsersService {
await this.sendgridService.sendMail(
context,
email,
from,
this.mailFrom,
subject,
text,
html,
@ -343,6 +344,12 @@ export class UsersService {
license_alert: licenseAlert,
notification,
role,
accepted_dpa_version: null,
accepted_eula_version: null,
encryption: false,
encryption_password: null,
prompt: false,
author_id: null,
};
case USER_ROLES.AUTHOR:
return {
@ -352,10 +359,12 @@ export class UsersService {
license_alert: licenseAlert,
notification,
role,
author_id: authorId,
encryption,
encryption_password: encryptionPassword,
prompt,
author_id: authorId ?? null,
encryption: encryption ?? false,
encryption_password: encryptionPassword ?? null,
prompt: prompt ?? false,
accepted_dpa_version: null,
accepted_eula_version: null,
};
default:
//不正なroleが指定された場合はログを出力してエラーを返す
@ -405,19 +414,16 @@ export class UsersService {
await this.adB2cService.changePassword(extarnalId, ramdomPassword);
// ユーザを認証済みにする
await this.usersRepository.updateUserVerified(userId);
// メールの送信元を取得
const from = this.configService.get<string>('MAIL_FROM') ?? '';
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
const subject = 'A temporary password has been issued.';
const text = 'temporary password: ' + ramdomPassword;
const domains = this.configService.get<string>('APP_DOMAIN');
const html = `<p>OMDS TOP PAGE URL.<p><a href="${domains}">${domains}"</a><br>temporary password: ${ramdomPassword}`;
const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}"</a><br>temporary password: ${ramdomPassword}`;
// メールを送信
await this.sendgridService.sendMail(
context,
email,
from,
this.mailFrom,
subject,
text,
html,
@ -464,17 +470,29 @@ export class UsersService {
);
// DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出
const users = dbUsers.map((x) => {
const users = dbUsers.map((dbUser): User => {
// ユーザーの所属グループ名を取得する
const groupNames =
x.userGroupMembers?.map((group) => group.userGroup?.name) ?? [];
const userGroupMembers =
dbUser.userGroupMembers !== null ? dbUser.userGroupMembers : [];
const adb2cUser = adb2cUsers.find((user) => user.id === x.external_id);
//所属グループ名の配列にする
const groupNames = userGroupMembers.flatMap((userGroupMember) =>
userGroupMember.userGroup ? [userGroupMember.userGroup.name] : [],
);
const adb2cUser = adb2cUsers.find(
(user) => user.id === dbUser.external_id,
);
// メールアドレスを取得する
const mail = adb2cUser.identities.find(
const mail = adb2cUser?.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
).issuerAssignedId;
)?.issuerAssignedId;
//メールアドレスが取得できない場合はエラー
if (!mail) {
throw new Error('mail not found.');
}
let status = USER_LICENSE_STATUS.NORMAL;
@ -483,9 +501,9 @@ export class UsersService {
let expiration: string | undefined = undefined;
let remaining: number | undefined = undefined;
if (x.license) {
if (dbUser.license) {
// 有効期限日付 YYYY/MM/DD
const expiry_date = x.license.expiry_date;
const expiry_date = dbUser.license.expiry_date;
expiration = `${expiry_date.getFullYear()}/${
expiry_date.getMonth() + 1
}/${expiry_date.getDate()}`;
@ -497,7 +515,7 @@ export class UsersService {
(1000 * 60 * 60 * 24),
);
if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) {
status = x.auto_renew
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT;
}
@ -506,18 +524,18 @@ export class UsersService {
}
return {
id: x.id,
id: dbUser.id,
name: adb2cUser.displayName,
role: x.role,
authorId: x.author_id ?? undefined,
role: dbUser.role,
authorId: dbUser.author_id ?? undefined,
typistGroupName: groupNames,
email: mail,
emailVerified: x.email_verified,
autoRenew: x.auto_renew,
licenseAlert: x.license_alert,
notification: x.notification,
encryption: x.encryption,
prompt: x.prompt,
emailVerified: dbUser.email_verified,
autoRenew: dbUser.auto_renew,
licenseAlert: dbUser.license_alert,
notification: dbUser.notification,
encryption: dbUser.encryption,
prompt: dbUser.prompt,
expiration: expiration,
remaining: remaining,
licenseStatus: status,
@ -545,14 +563,13 @@ export class UsersService {
async updateSortCriteria(
paramName: TaskListSortableAttribute,
direction: SortDirection,
token: AccessToken,
externalId: string,
): Promise<void> {
this.logger.log(`[IN] ${this.updateSortCriteria.name}`);
let user: EntityUser;
try {
// ユーザー情報を取得
const sub = token.userId;
user = await this.usersRepository.findUserByExternalId(sub);
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
@ -584,7 +601,7 @@ export class UsersService {
* @param token
* @returns sort criteria
*/
async getSortCriteria(token: AccessToken): Promise<{
async getSortCriteria(externalId: string): Promise<{
paramName: TaskListSortableAttribute;
direction: SortDirection;
}> {
@ -592,8 +609,7 @@ export class UsersService {
let user: EntityUser;
try {
// ユーザー情報を取得
const sub = token.userId;
user = await this.usersRepository.findUserByExternalId(sub);
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
@ -643,8 +659,8 @@ export class UsersService {
// TODO: PBI2105 本実装時に修正すること
return {
authorId: user.author_id,
authorIdList: [user.author_id, 'XXX'],
authorId: user.author_id ?? '',
authorIdList: [user.author_id ?? '', 'XXX'],
isEncrypted: true,
encryptionPassword: 'abcd@123?dcba',
audioFormat: 'DS2(QP)',
@ -1004,6 +1020,11 @@ export class UsersService {
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
case UpdateTermsVersionNotSetError:
throw new HttpException(
makeErrorResponse('E010001'),

View File

@ -2062,7 +2062,7 @@ describe('updateWorkflow', () => {
});
describe('deleteWorkflows', () => {
let source: DataSource = null;
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
@ -2074,12 +2074,15 @@ describe('deleteWorkflows', () => {
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('アカウント内のWorkflowを削除できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {
@ -2126,7 +2129,9 @@ describe('deleteWorkflows', () => {
});
it('アカウント内のWorkflowを削除できる複数ワークフローがある場合', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId1 } = await makeTestUser(source, {
@ -2177,7 +2182,9 @@ describe('deleteWorkflows', () => {
});
it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {
@ -2226,7 +2233,9 @@ describe('deleteWorkflows', () => {
});
it('指定されたワークフローが存在しない場合、400エラーを返却するログインユーザーのアカウント外', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {
@ -2303,7 +2312,9 @@ describe('deleteWorkflows', () => {
});
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {

View File

@ -57,8 +57,8 @@ export class WorkflowsService {
return workflowTypists;
});
// externalIdsからundefinedを除外
const filteredExternalIds = externalIds.filter(
(externalId): externalId is string => externalId !== undefined,
const filteredExternalIds = externalIds.flatMap((externalId) =>
externalId ? [externalId] : [],
);
// externalIdsから重複を除外
const distinctedExternalIds = [...new Set(filteredExternalIds)];

View File

@ -8,7 +8,13 @@ import { Context } from '../../common/log';
@Injectable()
export class SendGridService {
private readonly logger = new Logger(SendGridService.name);
private readonly emailConfirmLifetime: number;
readonly appDomain: string;
constructor(private readonly configService: ConfigService) {
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
this.emailConfirmLifetime = this.configService.getOrThrow<number>(
'EMAIL_CONFIRM_LIFETIME',
);
const key = this.configService.getOrThrow<string>('SENDGRID_API_KEY');
sendgrid.setApiKey(key);
}
@ -30,8 +36,6 @@ export class SendGridService {
`[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`,
);
const lifetime =
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
{
@ -39,10 +43,9 @@ export class SendGridService {
userId,
email,
},
lifetime,
this.emailConfirmLifetime,
privateKey,
);
const domains = this.configService.get<string>('APP_DOMAIN');
const path = 'mail-confirm/';
this.logger.log(
@ -50,8 +53,8 @@ export class SendGridService {
);
return {
subject: 'Verify your new account',
text: `The verification URL. ${domains}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`,
text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}"</a>`,
};
}
@ -68,9 +71,6 @@ export class SendGridService {
userId: number,
email: string,
): Promise<{ subject: string; text: string; html: string }> {
const lifetime =
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
@ -79,16 +79,15 @@ export class SendGridService {
userId,
email,
},
lifetime,
this.emailConfirmLifetime,
privateKey,
);
const domains = this.configService.get<string>('APP_DOMAIN');
const path = 'mail-confirm/user/';
return {
subject: 'Verify your new account',
text: `The verification URL. ${domains}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`,
text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}"</a>`,
};
}

View File

@ -21,32 +21,32 @@ export class Task {
@Column()
account_id: number;
@Column({ nullable: true })
is_job_number_enabled?: boolean;
is_job_number_enabled: boolean | null;
@Column()
audio_file_id: number;
@Column()
status: string;
@Column({ nullable: true })
typist_user_id?: number;
typist_user_id: number | null;
@Column()
priority: string;
@Column({ nullable: true })
template_file_id?: number;
template_file_id: number | null;
@Column({ nullable: true })
started_at?: Date;
started_at: Date | null;
@Column({ nullable: true })
finished_at?: Date;
finished_at: Date | null;
@Column({})
created_at: Date;
@OneToOne(() => AudioFile, (audiofile) => audiofile.task)
@JoinColumn({ name: 'audio_file_id' })
file?: AudioFile;
file: AudioFile | null;
@OneToMany(() => AudioOptionItem, (option) => option.task)
option_items?: AudioOptionItem[];
option_items: AudioOptionItem[] | null;
@OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'typist_user_id' })
typist_user?: User;
typist_user: User | null;
@ManyToOne(() => TemplateFile, (templateFile) => templateFile.id)
@JoinColumn({ name: 'template_file_id' })
template_file?: TemplateFile;
template_file: TemplateFile | null;
}

View File

@ -757,7 +757,7 @@ export class TasksRepositoryService {
*/
async changeCheckoutPermission(
audio_file_id: number,
author_id: string,
author_id: string | undefined,
account_id: number,
roles: Roles[],
assignees: Assignee[],

View File

@ -29,13 +29,13 @@ export class User {
role: string;
@Column({ nullable: true })
author_id?: string;
author_id: string | null;
@Column({ nullable: true })
accepted_eula_version?: string;
accepted_eula_version: string | null;
@Column({ nullable: true })
accepted_dpa_version?: string;
accepted_dpa_version: string | null;
@Column({ default: false })
email_verified: boolean;
@ -50,16 +50,16 @@ export class User {
notification: boolean;
@Column({ default: false })
encryption?: boolean;
encryption: boolean;
@Column({ nullable: true })
encryption_password?: string;
encryption_password: string | null;
@Column({ default: false })
prompt?: boolean;
prompt: boolean;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by: string;
@ -68,7 +68,7 @@ export class User {
created_at: Date;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date;
@ -77,13 +77,13 @@ export class User {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: 'account_id' })
account?: Account;
account: Account | null;
@OneToOne(() => License, (license) => license.user)
license?: License;
license: License | null;
@OneToMany(() => UserGroupMember, (userGroupMember) => userGroupMember.user)
userGroupMembers?: UserGroupMember[];
userGroupMembers: UserGroupMember[] | null;
}
@Entity({ name: 'users_archive' })
@ -101,13 +101,13 @@ export class UserArchive {
role: string;
@Column({ nullable: true })
author_id?: string;
author_id: string | null;
@Column({ nullable: true })
accepted_eula_version?: string;
accepted_eula_version: string | null;
@Column({ nullable: true })
accepted_dpa_version?: string;
accepted_dpa_version: string | null;
@Column()
email_verified: boolean;
@ -128,7 +128,7 @@ export class UserArchive {
prompt: boolean;
@Column({ nullable: true })
deleted_at?: Date;
deleted_at: Date | null;
@Column({ nullable: true })
created_by: string;
@ -137,7 +137,7 @@ export class UserArchive {
created_at: Date;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@Column()
updated_at: Date;

View File

@ -27,6 +27,7 @@ import { License } from '../licenses/entity/license.entity';
import { NewTrialLicenseExpirationDate } from '../../features/licenses/types/types';
import { Term } from '../terms/entity/term.entity';
import { TermsCheckInfo } from '../../features/auth/types/types';
import { AccountNotFoundError } from '../accounts/errors/types';
@Injectable()
export class UsersRepositoryService {
@ -120,7 +121,7 @@ export class UsersRepositoryService {
return user;
}
async findUserById(id: number): Promise<User | undefined> {
async findUserById(id: number): Promise<User> {
const user = await this.dataSource.getRepository(User).findOne({
where: {
id: id,
@ -128,7 +129,7 @@ export class UsersRepositoryService {
});
if (!user) {
return undefined;
throw new UserNotFoundError();
}
return user;
}
@ -138,10 +139,7 @@ export class UsersRepositoryService {
* @param user
* @returns true false
*/
async existsAuthorId(
accountId: number,
authorId: string,
): Promise<boolean | undefined> {
async existsAuthorId(accountId: number, authorId: string): Promise<boolean> {
const user = await this.dataSource.getRepository(User).findOne({
where: [
{
@ -219,9 +217,9 @@ export class UsersRepositoryService {
}
// Author用項目を更新
targetUser.author_id = authorId;
targetUser.encryption = encryption;
targetUser.prompt = prompt;
targetUser.author_id = authorId ?? null;
targetUser.encryption = encryption ?? false;
targetUser.prompt = prompt ?? false;
} else {
// ユーザーのロールがAuthor以外の場合はAuthor用項目はundefinedにする
targetUser.author_id = null;
@ -315,7 +313,7 @@ export class UsersRepositoryService {
for (let i = 0; i < TRIAL_LICENSE_ISSUE_NUM; i++) {
const license = new License();
license.expiry_date = expiryDate;
license.account_id = targetUser.account.id;
license.account_id = targetUser.account_id;
license.type = LICENSE_TYPE.TRIAL;
license.status = LICENSE_ALLOCATED_STATUS.UNALLOCATED;
licenses.push(license);
@ -339,8 +337,11 @@ export class UsersRepositoryService {
return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User);
const accountId = (await repo.findOne({ where: { external_id } }))
.account_id;
const accountId = (await repo.findOne({ where: { external_id } }))?.account_id;
if (!accountId) {
throw new AccountNotFoundError('Account is Not Found.');
}
const dbUsers = await this.dataSource.getRepository(User).find({
relations: {
@ -371,6 +372,11 @@ export class UsersRepositoryService {
},
});
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!user) {
throw new UserNotFoundError();
}
const typists = await repo.find({
where: {
account_id: user.account_id,
@ -442,6 +448,9 @@ export class UsersRepositoryService {
if (!user) {
throw new UserNotFoundError();
}
if (!user.account) {
throw new AccountNotFoundError('Account is Not Found.');
}
const termRepo = entityManager.getRepository(Term);
const latestEulaInfo = await termRepo.findOne({
@ -506,6 +515,10 @@ export class UsersRepositoryService {
);
}
if (!user.account) {
throw new AccountNotFoundError('Account is Not Found.');
}
// パラメータが不在の場合はエラーを返却
if (!eulaVersion) {
throw new UpdateTermsVersionNotSetError(`EULA version param not set.`);

View File

@ -20,10 +20,10 @@ export class WorkflowTypist {
workflow_id: number;
@Column({ nullable: true })
typist_id?: number;
typist_id: number | null;
@Column({ nullable: true })
typist_group_id?: number;
typist_group_id: number | null;
@Column({ nullable: true })
created_by: string;
@ -32,20 +32,20 @@ export class WorkflowTypist {
created_at: Date;
@Column({ nullable: true })
updated_by?: string;
updated_by: string | null;
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date;
@ManyToOne(() => Workflow, (workflow) => workflow.id)
@JoinColumn({ name: 'workflow_id' })
workflow?: Workflow;
workflow: Workflow | null;
@ManyToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'typist_id' })
typist?: User;
typist: User | null;
@ManyToOne(() => UserGroup, (userGroup) => userGroup.id)
@JoinColumn({ name: 'typist_group_id' })
typistGroup?: UserGroup;
typistGroup: UserGroup | null;
}

View File

@ -337,8 +337,8 @@ export class WorkflowsRepositoryService {
private makeWorkflow(
accountId: number,
authorId: number,
worktypeId?: number | undefined,
templateId?: number | undefined,
worktypeId?: number,
templateId?: number,
): Workflow {
const workflow = new Workflow();
workflow.account_id = accountId;
@ -363,8 +363,8 @@ export class WorkflowsRepositoryService {
): DbWorkflowTypist {
const workflowTypist = new DbWorkflowTypist();
workflowTypist.workflow_id = workflowId;
workflowTypist.typist_id = typistId;
workflowTypist.typist_group_id = typistGroupId;
workflowTypist.typist_id = typistId ?? null;
workflowTypist.typist_group_id = typistGroupId ?? null;
return workflowTypist;
}