Merged PR 352: API実装(TypistGroup追加API)
## 概要 [Task2428: API実装(TypistGroup追加API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2428) - IFを修正 - typistIdsの要素が数値であることをOpenAPIに明記する - typistIdsのチェックバリデータを追加・修正 - entity修正 - typistGuroup作成処理を実装 - テスト作成 ## レビューポイント - テストケースは足りているか - entityの修正に問題はないか - typistIdsのチェック処理で漏れているものはないか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
e1693a7323
commit
d5c756184b
36
dictation_server/src/common/validators/IsUnique.validator.ts
Normal file
36
dictation_server/src/common/validators/IsUnique.validator.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
ValidatorConstraint,
|
||||||
|
ValidatorConstraintInterface,
|
||||||
|
ValidationArguments,
|
||||||
|
ValidationOptions,
|
||||||
|
registerDecorator,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
@ValidatorConstraint()
|
||||||
|
export class IsUniqueArray implements ValidatorConstraintInterface {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
validate(arr: any[], args: ValidationArguments) {
|
||||||
|
return arr.length === new Set(arr).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(args: ValidationArguments) {
|
||||||
|
return `${args.property} should be an array of unique values`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 配列内の値がすべてユニークであるかをチェックする
|
||||||
|
* @param [validationOptions]
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function IsUnique(validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: object, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'isUnique',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
constraints: [],
|
||||||
|
options: validationOptions,
|
||||||
|
validator: IsUniqueArray,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { registerDecorator, ValidationOptions } from 'class-validator';
|
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||||
|
|
||||||
|
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||||
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||||
return (object: any, propertyName: string) => {
|
return (object: any, propertyName: string) => {
|
||||||
registerDecorator({
|
registerDecorator({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
ValidationArguments,
|
ValidationArguments,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Assignee } from '../../features/tasks/types/types';
|
import { Assignee } from '../../features/tasks/types/types';
|
||||||
|
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||||
/**
|
/**
|
||||||
* Validations options
|
* Validations options
|
||||||
* @param [validationOptions]
|
* @param [validationOptions]
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
ValidationOptions,
|
ValidationOptions,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { SignupRequest } from '../../features/users/types/types';
|
import { SignupRequest } from '../../features/users/types/types';
|
||||||
|
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||||
export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||||
return (object: any, propertyName: string) => {
|
return (object: any, propertyName: string) => {
|
||||||
registerDecorator({
|
registerDecorator({
|
||||||
@ -35,7 +35,7 @@ export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||||
export const IsEncryptionPasswordPresent = (
|
export const IsEncryptionPasswordPresent = (
|
||||||
validationOptions?: ValidationOptions,
|
validationOptions?: ValidationOptions,
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
} from '../../features/users/types/types';
|
} from '../../features/users/types/types';
|
||||||
import { USER_ROLES } from '../../constants';
|
import { USER_ROLES } from '../../constants';
|
||||||
|
|
||||||
|
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||||
export const IsRoleAuthorDataValid = <
|
export const IsRoleAuthorDataValid = <
|
||||||
T extends SignupRequest | PostUpdateUserRequest,
|
T extends SignupRequest | PostUpdateUserRequest,
|
||||||
>(
|
>(
|
||||||
|
|||||||
@ -326,10 +326,17 @@ export class AccountsController {
|
|||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Body() body: CreateTypistGroupRequest,
|
@Body() body: CreateTypistGroupRequest,
|
||||||
): Promise<CreateTypistGroupResponse> {
|
): Promise<CreateTypistGroupResponse> {
|
||||||
|
const { typistGroupName, typistIds } = body;
|
||||||
// アクセストークン取得
|
// アクセストークン取得
|
||||||
const accessToken = retrieveAuthorizationToken(req);
|
const accessToken = retrieveAuthorizationToken(req);
|
||||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||||
|
const context = makeContext(userId);
|
||||||
|
await this.accountService.createTypistGroup(
|
||||||
|
context,
|
||||||
|
userId,
|
||||||
|
typistGroupName,
|
||||||
|
typistIds,
|
||||||
|
);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,9 @@ import {
|
|||||||
getUsers,
|
getUsers,
|
||||||
getSortCriteria,
|
getSortCriteria,
|
||||||
createAccountAndAdminUser,
|
createAccountAndAdminUser,
|
||||||
|
createAccountAndAdminUserForTier5,
|
||||||
|
getTypistGroup,
|
||||||
|
getTypistGroupMember,
|
||||||
} from './test/utility';
|
} from './test/utility';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { makeTestingModule } from '../../common/test/modules';
|
import { makeTestingModule } from '../../common/test/modules';
|
||||||
@ -38,6 +41,7 @@ import {
|
|||||||
} from '../../common/test/overrides';
|
} from '../../common/test/overrides';
|
||||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||||
|
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||||
|
|
||||||
describe('createAccount', () => {
|
describe('createAccount', () => {
|
||||||
let source: DataSource = null;
|
let source: DataSource = null;
|
||||||
@ -2288,3 +2292,232 @@ describe('getDealers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('createTypistGroup', () => {
|
||||||
|
let source: DataSource = null;
|
||||||
|
beforeEach(async () => {
|
||||||
|
source = new DataSource({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: ':memory:',
|
||||||
|
logging: false,
|
||||||
|
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||||
|
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||||
|
});
|
||||||
|
return source.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await source.destroy();
|
||||||
|
source = null;
|
||||||
|
});
|
||||||
|
it('TypistGroupを作成できる', async () => {
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
const adminExternalId = 'admin-external-id';
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { accountId } = await createAccountAndAdminUserForTier5(
|
||||||
|
source,
|
||||||
|
adminExternalId,
|
||||||
|
);
|
||||||
|
// 作成したアカウントにユーザーを3名追加する
|
||||||
|
const typiptUserExternalIds = [
|
||||||
|
'typist-user-external-id1',
|
||||||
|
'typist-user-external-id2',
|
||||||
|
'typist-user-external-id3',
|
||||||
|
];
|
||||||
|
const userIds: number[] = [];
|
||||||
|
for (const typiptUserExternalId of typiptUserExternalIds) {
|
||||||
|
const { userId } = await createUser(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
typiptUserExternalId,
|
||||||
|
'typist',
|
||||||
|
);
|
||||||
|
userIds.push(userId);
|
||||||
|
}
|
||||||
|
//作成したデータを確認
|
||||||
|
{
|
||||||
|
const accounts = await getAccounts(source);
|
||||||
|
expect(accounts.length).toBe(1);
|
||||||
|
expect(accounts[0].id).toBe(accountId);
|
||||||
|
const users = await getUsers(source);
|
||||||
|
expect(users.length).toBe(4);
|
||||||
|
}
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
const typistGroupName = 'typist-group-name';
|
||||||
|
const typistUserIds = userIds;
|
||||||
|
const context = makeContext(adminExternalId);
|
||||||
|
await service.createTypistGroup(
|
||||||
|
context,
|
||||||
|
adminExternalId,
|
||||||
|
typistGroupName,
|
||||||
|
typistUserIds,
|
||||||
|
);
|
||||||
|
//実行結果を確認
|
||||||
|
{
|
||||||
|
const typistGroups = await getTypistGroup(source, accountId);
|
||||||
|
expect(typistGroups.length).toBe(1);
|
||||||
|
expect(typistGroups[0].name).toBe(typistGroupName);
|
||||||
|
|
||||||
|
const typistGroupUsers = await getTypistGroupMember(
|
||||||
|
source,
|
||||||
|
typistGroups[0].id,
|
||||||
|
);
|
||||||
|
expect(typistGroupUsers.length).toBe(3);
|
||||||
|
expect(typistGroupUsers.map((user) => user.user_id)).toEqual(userIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => {
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
const adminExternalId = 'admin-external-id';
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { accountId } = await createAccountAndAdminUserForTier5(
|
||||||
|
source,
|
||||||
|
adminExternalId,
|
||||||
|
);
|
||||||
|
// 作成したアカウントにユーザーを3名追加する
|
||||||
|
const typiptUserExternalIds = [
|
||||||
|
'typist-user-external-id1',
|
||||||
|
'typist-user-external-id2',
|
||||||
|
'typist-user-external-id3',
|
||||||
|
];
|
||||||
|
const userIds: number[] = [];
|
||||||
|
for (const typiptUserExternalId of typiptUserExternalIds) {
|
||||||
|
const { userId } = await createUser(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
typiptUserExternalId,
|
||||||
|
typiptUserExternalId === 'typist-user-external-id3' ? 'none' : 'typist', //typist-user-external-id3のみRole:none
|
||||||
|
);
|
||||||
|
userIds.push(userId);
|
||||||
|
}
|
||||||
|
//作成したデータを確認
|
||||||
|
{
|
||||||
|
const accounts = await getAccounts(source);
|
||||||
|
expect(accounts.length).toBe(1);
|
||||||
|
expect(accounts[0].id).toBe(accountId);
|
||||||
|
const users = await getUsers(source);
|
||||||
|
expect(users.length).toBe(4);
|
||||||
|
expect(users.filter((user) => user.role === 'typist').length).toBe(2);
|
||||||
|
}
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
const typistGroupName = 'typist-group-name';
|
||||||
|
const typistUserIds = userIds;
|
||||||
|
const context = makeContext(adminExternalId);
|
||||||
|
await expect(
|
||||||
|
service.createTypistGroup(
|
||||||
|
context,
|
||||||
|
adminExternalId,
|
||||||
|
typistGroupName,
|
||||||
|
typistUserIds,
|
||||||
|
),
|
||||||
|
).rejects.toEqual(
|
||||||
|
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('typistIdsに存在しないユーザーが含まれていた場合、400エラーを返却する', async () => {
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
const adminExternalId = 'admin-external-id';
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { accountId } = await createAccountAndAdminUserForTier5(
|
||||||
|
source,
|
||||||
|
adminExternalId,
|
||||||
|
);
|
||||||
|
// 作成したアカウントにユーザーを3名追加する
|
||||||
|
const typiptUserExternalIds = [
|
||||||
|
'typist-user-external-id1',
|
||||||
|
'typist-user-external-id2',
|
||||||
|
'typist-user-external-id3',
|
||||||
|
];
|
||||||
|
const userIds: number[] = [];
|
||||||
|
for (const typiptUserExternalId of typiptUserExternalIds) {
|
||||||
|
const { userId } = await createUser(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
typiptUserExternalId,
|
||||||
|
'typist',
|
||||||
|
);
|
||||||
|
userIds.push(userId);
|
||||||
|
}
|
||||||
|
//作成したデータを確認
|
||||||
|
{
|
||||||
|
const accounts = await getAccounts(source);
|
||||||
|
expect(accounts.length).toBe(1);
|
||||||
|
expect(accounts[0].id).toBe(accountId);
|
||||||
|
const users = await getUsers(source);
|
||||||
|
expect(users.length).toBe(4);
|
||||||
|
}
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
const typistGroupName = 'typist-group-name';
|
||||||
|
const typistUserIds = [...userIds, 9999]; //存在しないユーザーIDを追加
|
||||||
|
const context = makeContext(adminExternalId);
|
||||||
|
await expect(
|
||||||
|
service.createTypistGroup(
|
||||||
|
context,
|
||||||
|
adminExternalId,
|
||||||
|
typistGroupName,
|
||||||
|
typistUserIds,
|
||||||
|
),
|
||||||
|
).rejects.toEqual(
|
||||||
|
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
const adminExternalId = 'admin-external-id';
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { accountId } = await createAccountAndAdminUserForTier5(
|
||||||
|
source,
|
||||||
|
adminExternalId,
|
||||||
|
);
|
||||||
|
// 作成したアカウントにユーザーを3名追加する
|
||||||
|
const typiptUserExternalIds = [
|
||||||
|
'typist-user-external-id1',
|
||||||
|
'typist-user-external-id2',
|
||||||
|
'typist-user-external-id3',
|
||||||
|
];
|
||||||
|
const userIds: number[] = [];
|
||||||
|
for (const typiptUserExternalId of typiptUserExternalIds) {
|
||||||
|
const { userId } = await createUser(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
typiptUserExternalId,
|
||||||
|
'typist',
|
||||||
|
);
|
||||||
|
userIds.push(userId);
|
||||||
|
}
|
||||||
|
//作成したデータを確認
|
||||||
|
{
|
||||||
|
const accounts = await getAccounts(source);
|
||||||
|
expect(accounts.length).toBe(1);
|
||||||
|
expect(accounts[0].id).toBe(accountId);
|
||||||
|
const users = await getUsers(source);
|
||||||
|
expect(users.length).toBe(4);
|
||||||
|
}
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
const typistGroupName = 'typist-group-name';
|
||||||
|
const typistUserIds = userIds;
|
||||||
|
const context = makeContext(adminExternalId);
|
||||||
|
//DBアクセスに失敗するようにする
|
||||||
|
const typistGroupService = module.get<UserGroupsRepositoryService>(
|
||||||
|
UserGroupsRepositoryService,
|
||||||
|
);
|
||||||
|
typistGroupService.createTypistGroup = jest
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue('DB failed');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.createTypistGroup(
|
||||||
|
context,
|
||||||
|
adminExternalId,
|
||||||
|
typistGroupName,
|
||||||
|
typistUserIds,
|
||||||
|
),
|
||||||
|
).rejects.toEqual(
|
||||||
|
new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import {
|
|||||||
OrderNotFoundError,
|
OrderNotFoundError,
|
||||||
} from '../../repositories/licenses/errors/types';
|
} from '../../repositories/licenses/errors/types';
|
||||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||||
|
import { TypistIdInvalidError } from '../../repositories/user_groups/errors/types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountsService {
|
export class AccountsService {
|
||||||
@ -870,4 +871,57 @@ export class AccountsService {
|
|||||||
this.logger.log(`[OUT] ${this.getDealers.name}`);
|
this.logger.log(`[OUT] ${this.getDealers.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* タイピストグループを作成する
|
||||||
|
* @param context
|
||||||
|
* @param externalId
|
||||||
|
* @param typistGroupName
|
||||||
|
* @param typistIds
|
||||||
|
* @returns createTypistGroupResponse
|
||||||
|
**/
|
||||||
|
async createTypistGroup(
|
||||||
|
context: Context,
|
||||||
|
externalId: string,
|
||||||
|
typistGroupName: string,
|
||||||
|
typistIds: number[],
|
||||||
|
): Promise<void> {
|
||||||
|
this.logger.log(
|
||||||
|
`[IN] [${context.trackingId}] ${this.createTypistGroup.name} | params: { ` +
|
||||||
|
`externalId: ${externalId}, ` +
|
||||||
|
`typistGroupName: ${typistGroupName}, ` +
|
||||||
|
`typistIds: ${typistIds} };`,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
// 外部IDをもとにユーザー情報を取得する
|
||||||
|
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||||
|
externalId,
|
||||||
|
);
|
||||||
|
// API実行ユーザーのアカウントIDでタイピストグループを作成し、タイピストグループとtypistIdsのユーザーを紐付ける
|
||||||
|
await this.userGroupsRepository.createTypistGroup(
|
||||||
|
typistGroupName,
|
||||||
|
typistIds,
|
||||||
|
account_id,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`error=${e}`);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
switch (e.constructor) {
|
||||||
|
case TypistIdInvalidError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E010204'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import {
|
|||||||
LicenseOrder,
|
LicenseOrder,
|
||||||
} from '../../../repositories/licenses/entity/license.entity';
|
} from '../../../repositories/licenses/entity/license.entity';
|
||||||
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
|
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
|
||||||
|
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||||
|
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||||
|
|
||||||
// TODO: [PBI 2379] 他のUtilityからコピペしてきたもの。後日整理される前提。
|
// TODO: [PBI 2379] 他のUtilityからコピペしてきたもの。後日整理される前提。
|
||||||
export const createAccountAndAdminUser = async (
|
export const createAccountAndAdminUser = async (
|
||||||
@ -269,3 +271,93 @@ export const createUser = async (
|
|||||||
const user = identifiers.pop() as User;
|
const user = identifiers.pop() as User;
|
||||||
return { userId: user.id, externalId: external_id, authorId: author_id };
|
return { userId: user.id, externalId: external_id, authorId: author_id };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: [PBI 2379] 第五階層のアカウント・管理者を作成する。後日整理される前提。
|
||||||
|
export const createAccountAndAdminUserForTier5 = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
adminExternalId: string,
|
||||||
|
): Promise<{
|
||||||
|
accountId: number;
|
||||||
|
adminId: number;
|
||||||
|
role: string;
|
||||||
|
tier: number;
|
||||||
|
}> => {
|
||||||
|
const { identifiers: account_idf } = await datasource
|
||||||
|
.getRepository(Account)
|
||||||
|
.insert({
|
||||||
|
tier: 5,
|
||||||
|
country: 'JP',
|
||||||
|
delegation_permission: false,
|
||||||
|
locked: false,
|
||||||
|
company_name: 'test inc.',
|
||||||
|
verified: true,
|
||||||
|
deleted_at: '',
|
||||||
|
created_by: 'test_runner',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
const account = account_idf.pop() as Account;
|
||||||
|
|
||||||
|
const { identifiers: user_idf } = await datasource
|
||||||
|
.getRepository(User)
|
||||||
|
.insert({
|
||||||
|
account_id: account.id,
|
||||||
|
external_id: adminExternalId,
|
||||||
|
role: 'none',
|
||||||
|
accepted_terms_version: '1.0',
|
||||||
|
email_verified: true,
|
||||||
|
auto_renew: true,
|
||||||
|
license_alert: true,
|
||||||
|
notification: true,
|
||||||
|
encryption: true,
|
||||||
|
encryption_password: 'password',
|
||||||
|
prompt: true,
|
||||||
|
created_by: 'test_runner',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
const user = user_idf.pop() as User;
|
||||||
|
|
||||||
|
// Accountの管理者を設定する
|
||||||
|
await datasource.getRepository(Account).update(
|
||||||
|
{ id: user.account_id },
|
||||||
|
{
|
||||||
|
primary_admin_user_id: user.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const accountResult = await getAccount(datasource, account.id);
|
||||||
|
const userResult = await getUser(datasource, user.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountId: account.id,
|
||||||
|
adminId: user.id,
|
||||||
|
role: userResult.role,
|
||||||
|
tier: accountResult.tier,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// タイピストグループを取得する
|
||||||
|
export const getTypistGroup = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
accountId: number,
|
||||||
|
): Promise<UserGroup[]> => {
|
||||||
|
return await datasource.getRepository(UserGroup).find({
|
||||||
|
where: {
|
||||||
|
account_id: accountId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// タイピストグループメンバーを取得する
|
||||||
|
export const getTypistGroupMember = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
userGroupId: number,
|
||||||
|
): Promise<UserGroupMember[]> => {
|
||||||
|
return await datasource.getRepository(UserGroupMember).find({
|
||||||
|
where: {
|
||||||
|
user_group_id: userGroupId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
IsArray,
|
IsArray,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator';
|
import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator';
|
||||||
|
import { IsUnique } from '../../../common/validators/IsUnique.validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
export class CreateAccountRequest {
|
export class CreateAccountRequest {
|
||||||
@ -155,6 +156,9 @@ export class CreateTypistGroupRequest {
|
|||||||
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
|
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
|
||||||
@ArrayMinSize(1)
|
@ArrayMinSize(1)
|
||||||
@IsArray()
|
@IsArray()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
@Min(0, { each: true })
|
||||||
|
@IsUnique()
|
||||||
typistIds: number[];
|
typistIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
import {
|
||||||
|
Entity,
|
||||||
|
Column,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
OneToMany,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
import { UserGroupMember } from './user_group_member.entity';
|
import { UserGroupMember } from './user_group_member.entity';
|
||||||
|
|
||||||
@Entity({ name: 'user_group' })
|
@Entity({ name: 'user_group' })
|
||||||
@ -15,16 +22,16 @@ export class UserGroup {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
deleted_at?: Date;
|
deleted_at?: Date;
|
||||||
|
|
||||||
@Column()
|
|
||||||
created_by: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
created_by?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
|
|
||||||
@Column()
|
|
||||||
updated_by: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
updated_by?: string;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
updated_at?: Date;
|
updated_at?: Date;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { UserGroup } from './user_group.entity';
|
import { UserGroup } from './user_group.entity';
|
||||||
|
|
||||||
@ -22,16 +24,16 @@ export class UserGroupMember {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
deleted_at?: Date;
|
deleted_at?: Date;
|
||||||
|
|
||||||
@Column()
|
|
||||||
created_by: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
created_by?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
|
|
||||||
@Column()
|
|
||||||
updated_by: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
updated_by?: string;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
updated_at?: Date;
|
updated_at?: Date;
|
||||||
|
|
||||||
@ManyToOne(() => User, (user) => user.id)
|
@ManyToOne(() => User, (user) => user.id)
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
// typistIdが不正な場合のエラー
|
||||||
|
export class TypistIdInvalidError extends Error {}
|
||||||
@ -2,6 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { DataSource, In, IsNull } from 'typeorm';
|
import { DataSource, In, IsNull } from 'typeorm';
|
||||||
import { UserGroup } from './entity/user_group.entity';
|
import { UserGroup } from './entity/user_group.entity';
|
||||||
import { UserGroupMember } from './entity/user_group_member.entity';
|
import { UserGroupMember } from './entity/user_group_member.entity';
|
||||||
|
import { User } from '../users/entity/user.entity';
|
||||||
|
import { TypistIdInvalidError } from './errors/types';
|
||||||
|
import { USER_ROLES } from '../../constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserGroupsRepositoryService {
|
export class UserGroupsRepositoryService {
|
||||||
@ -43,4 +46,53 @@ export class UserGroupsRepositoryService {
|
|||||||
return groupMembers;
|
return groupMembers;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 指定したアカウントIDでタイピストグループを作成し、そのタイピストグループとtypistIdsのユーザーを紐付ける
|
||||||
|
* @param accountId
|
||||||
|
* @param name
|
||||||
|
* @param typistIds
|
||||||
|
* @returns createdTypistGroup
|
||||||
|
*/
|
||||||
|
async createTypistGroup(
|
||||||
|
name: string,
|
||||||
|
typistIds: number[],
|
||||||
|
accountId: number,
|
||||||
|
): Promise<UserGroup> {
|
||||||
|
return await this.dataSource.transaction(async (entityManager) => {
|
||||||
|
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||||
|
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
|
||||||
|
// typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
|
||||||
|
const userRepo = entityManager.getRepository(User);
|
||||||
|
const userRecords = await userRepo.find({
|
||||||
|
where: {
|
||||||
|
id: In(typistIds),
|
||||||
|
account_id: accountId,
|
||||||
|
role: USER_ROLES.TYPIST,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (userRecords.length !== typistIds.length) {
|
||||||
|
throw new TypistIdInvalidError(
|
||||||
|
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
|
||||||
|
(x) => x.id,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// userGroupをDBに保存する
|
||||||
|
const userGroup = await userGroupRepo.save({
|
||||||
|
account_id: accountId,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const userGroupMembers = userRecords.map((user) => {
|
||||||
|
return {
|
||||||
|
user_group_id: userGroup.id,
|
||||||
|
user_id: user.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// userGroupMembersをDBに保存する
|
||||||
|
await userGroupMemberRepo.save(userGroupMembers);
|
||||||
|
|
||||||
|
return userGroup;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -317,14 +317,7 @@ export class UsersRepositoryService {
|
|||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
.into(License)
|
.into(License)
|
||||||
.values(
|
.values(licenses)
|
||||||
licenses.map((value) => ({
|
|
||||||
expiry_date: value.expiry_date,
|
|
||||||
account_id: value.account_id,
|
|
||||||
type: value.type,
|
|
||||||
status: value.status,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.execute();
|
.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user