Merged PR 299: ユーザー編集API実装
## 概要 [Task2317: ユーザー編集API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2317) - ユーザー編集APIとテストを実装しました。 ## レビューポイント - リポジトリでのチェックは適切か - バリデータの適用は適切か - テストケースは十分か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
34ad2e489d
commit
338d6b88a9
@ -30,6 +30,8 @@ export const ErrorCodes = [
|
||||
'E010204', // ユーザ不在エラー
|
||||
'E010205', // DBのRoleが想定外の値エラー
|
||||
'E010206', // DBのTierが想定外の値エラー
|
||||
'E010207', // ユーザーのRole変更不可エラー
|
||||
'E010208', // ユーザーの暗号化パスワード不足エラー
|
||||
'E010301', // メールアドレス登録済みエラー
|
||||
'E010302', // authorId重複エラー
|
||||
'E010401', // PONumber重複エラー
|
||||
|
||||
@ -19,6 +19,8 @@ export const errors: Errors = {
|
||||
E010204: 'User not Found Error.',
|
||||
E010205: 'Role from DB is unexpected value Error.',
|
||||
E010206: 'Tier from DB is unexpected value Error.',
|
||||
E010207: 'User role change not allowed Error.',
|
||||
E010208: 'User encryption password not found Error.',
|
||||
E010301: 'This email user already created Error',
|
||||
E010302: 'This AuthorId already used Error',
|
||||
E010401: 'This PoNumber already used Error',
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||
|
||||
export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
name: 'IsPasswordvalid',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: string | undefined) => {
|
||||
// passwordが設定されていない場合はチェックしない
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
// 正規表現でパスワードのチェックを行う
|
||||
// 4~16文字の半角英数字と記号のみ
|
||||
const regex = /^[!-~]{4,16}$/;
|
||||
if (!regex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return 'EncryptionPassword rule not satisfied';
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
import {
|
||||
SignupRequest,
|
||||
PostUpdateUserRequest,
|
||||
} from '../../features/users/types/types';
|
||||
|
||||
export const IsRoleAuthorDataValid = <
|
||||
T extends SignupRequest | PostUpdateUserRequest,
|
||||
>(
|
||||
validationOptions?: ValidationOptions,
|
||||
) => {
|
||||
return (object: T, propertyName: string) => {
|
||||
registerDecorator({
|
||||
name: 'IsRoleAuthorDataValid',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: boolean | undefined, args: ValidationArguments) => {
|
||||
const request = args.object as T;
|
||||
const { role } = request;
|
||||
if (role === USER_ROLES.AUTHOR && value === undefined) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return `When role is author, ${propertyName} cannot be undefined`;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -65,6 +65,9 @@ export const createUser = async (
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
auto_renew?: boolean,
|
||||
encryption?: boolean | undefined,
|
||||
encryption_password?: string | undefined,
|
||||
prompt?: boolean | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
@ -76,8 +79,9 @@ export const createUser = async (
|
||||
auto_renew: auto_renew,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
encryption: encryption ?? false,
|
||||
encryption_password: encryption_password,
|
||||
prompt: prompt ?? false,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
@ -87,6 +91,18 @@ export const createUser = async (
|
||||
return { userId: user.id, externalId: external_id };
|
||||
};
|
||||
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param datasource
|
||||
|
||||
@ -5,6 +5,8 @@ import {
|
||||
USER_LICENSE_STATUS,
|
||||
} from '../../../constants';
|
||||
import { USER_ROLES } from '../../../constants';
|
||||
import { IsRoleAuthorDataValid } from '../../../common/validators/roleAuthor.validator';
|
||||
import { IsPasswordvalid } from '../../../common/validators/encryptionPassword.validator';
|
||||
|
||||
export class ConfirmRequest {
|
||||
@ApiProperty()
|
||||
@ -197,6 +199,7 @@ export class PostUpdateUserRequest {
|
||||
role: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsRoleAuthorDataValid()
|
||||
authorId?: string | undefined;
|
||||
|
||||
@ApiProperty()
|
||||
@ -209,12 +212,15 @@ export class PostUpdateUserRequest {
|
||||
notification: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsRoleAuthorDataValid()
|
||||
encryption?: boolean | undefined;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsPasswordvalid()
|
||||
encryptionPassword?: string | undefined;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsRoleAuthorDataValid()
|
||||
prompt?: boolean | undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -335,11 +335,36 @@ export class UsersController {
|
||||
@Body() body: PostUpdateUserRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<PostUpdateUserResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const {
|
||||
id,
|
||||
role,
|
||||
authorId,
|
||||
autoRenew,
|
||||
licenseAlart,
|
||||
notification,
|
||||
encryption,
|
||||
encryptionPassword,
|
||||
prompt,
|
||||
} = body;
|
||||
|
||||
console.log(body);
|
||||
console.log(decodedToken);
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.usersService.updateUser(
|
||||
context,
|
||||
userId,
|
||||
id,
|
||||
role,
|
||||
authorId,
|
||||
autoRenew,
|
||||
licenseAlart,
|
||||
notification,
|
||||
encryption,
|
||||
encryptionPassword,
|
||||
prompt,
|
||||
);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
createLicense,
|
||||
createUser,
|
||||
createUserGroup,
|
||||
getUser,
|
||||
makeTestingModuleWithAdb2c,
|
||||
} from './test/utility';
|
||||
import { DataSource } from 'typeorm';
|
||||
@ -22,7 +23,9 @@ import { UsersService } from './users.service';
|
||||
import {
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
|
||||
describe('UsersService.confirmUser', () => {
|
||||
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになる', async () => {
|
||||
@ -1016,3 +1019,539 @@ describe('UsersService.getSortCriteria', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UsersService.updateUser', () => {
|
||||
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('ユーザー情報を更新できる(None)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.NONE);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザー情報を更新できる(Typist)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.TYPIST,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.TYPIST,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザー情報を更新できる(Author)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.AUTHOR,
|
||||
undefined,
|
||||
true,
|
||||
true,
|
||||
'password',
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
'new_password',
|
||||
true,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(true);
|
||||
expect(createdUser.encryption_password).toBe('new_password');
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('ユーザーのRoleを更新できる(None⇒Typist)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.TYPIST,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザーのRoleを更新できる(None⇒Author)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('None以外からRoleを変更した場合、エラーとなる(Typist⇒None)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.TYPIST,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
await expect(
|
||||
service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010207'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持する(Encryptionがtrue)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
true,
|
||||
true,
|
||||
'password',
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(true);
|
||||
expect(createdUser.encryption_password).toBe('password');
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('Authorが暗号化なしで更新した場合、パスワードをNULLにする(Encryptionがfalse)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
true,
|
||||
false,
|
||||
'password',
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
expect(
|
||||
await service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
'password',
|
||||
true,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなる(Encryptionがtrue)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
true,
|
||||
true,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
await expect(
|
||||
service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010208'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId: external_id } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id',
|
||||
USER_ROLES.NONE,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const { userId: user1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id1',
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID1',
|
||||
true,
|
||||
true,
|
||||
'password',
|
||||
true,
|
||||
);
|
||||
|
||||
const { userId: user2 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'external_id2',
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID2',
|
||||
true,
|
||||
true,
|
||||
'password',
|
||||
true,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
|
||||
await expect(
|
||||
service.updateUser(
|
||||
{ trackingId: 'trackingId' },
|
||||
external_id,
|
||||
user1,
|
||||
USER_ROLES.AUTHOR,
|
||||
'AUTHOR_ID2',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -21,7 +21,13 @@ import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/
|
||||
import { User as EntityUser } from '../../repositories/users/entity/user.entity';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
import { GetRelationsResponse, User } from './types/types';
|
||||
import { EmailAlreadyVerifiedError } from '../../repositories/users/errors/types';
|
||||
import {
|
||||
AuthorIdAlreadyExistsError,
|
||||
EmailAlreadyVerifiedError,
|
||||
EncryptionPasswordNeedError,
|
||||
InvalidRoleChangeError,
|
||||
UserNotFoundError,
|
||||
} from '../../repositories/users/errors/types';
|
||||
import {
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
USER_LICENSE_STATUS,
|
||||
@ -628,4 +634,103 @@ export class UsersService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したユーザーの情報を更新します
|
||||
* @param context
|
||||
* @param extarnalId
|
||||
* @param id
|
||||
* @param role
|
||||
* @param authorId
|
||||
* @param autoRenew
|
||||
* @param licenseAlart
|
||||
* @param notification
|
||||
* @param encryption
|
||||
* @param encryptionPassword
|
||||
* @param prompt
|
||||
* @returns user
|
||||
*/
|
||||
async updateUser(
|
||||
context: Context,
|
||||
extarnalId: string,
|
||||
id: number,
|
||||
role: string,
|
||||
authorId: string | undefined,
|
||||
autoRenew: boolean,
|
||||
licenseAlart: boolean,
|
||||
notification: boolean,
|
||||
encryption: boolean | undefined,
|
||||
encryptionPassword: string | undefined,
|
||||
prompt: boolean | undefined,
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateUser.name} | params: { ` +
|
||||
`role: ${role}, ` +
|
||||
`authorId: ${authorId}, ` +
|
||||
`autoRenew: ${autoRenew}, ` +
|
||||
`licenseAlart: ${licenseAlart}, ` +
|
||||
`notification: ${notification}, ` +
|
||||
`encryption: ${encryption}, ` +
|
||||
`encryptionPassword: ********, ` +
|
||||
`prompt: ${prompt} }`,
|
||||
);
|
||||
|
||||
// 実行ユーザーのアカウントIDを取得
|
||||
const accountId = (
|
||||
await this.usersRepository.findUserByExternalId(extarnalId)
|
||||
).account_id;
|
||||
|
||||
// ユーザー情報を更新
|
||||
await this.usersRepository.update(
|
||||
accountId,
|
||||
id,
|
||||
role,
|
||||
authorId,
|
||||
autoRenew,
|
||||
licenseAlart,
|
||||
notification,
|
||||
encryption,
|
||||
encryptionPassword,
|
||||
prompt,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case UserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AuthorIdAlreadyExistsError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010302'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case InvalidRoleChangeError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010207'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case EncryptionPasswordNeedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010208'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.updateUser.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +48,9 @@ export class User {
|
||||
@Column()
|
||||
encryption: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
encryption_password?: string;
|
||||
|
||||
@Column()
|
||||
prompt: boolean;
|
||||
|
||||
|
||||
@ -2,3 +2,9 @@
|
||||
export class EmailAlreadyVerifiedError extends Error {}
|
||||
// ユーザー未発見エラー
|
||||
export class UserNotFoundError extends Error {}
|
||||
// AuthorID重複エラー
|
||||
export class AuthorIdAlreadyExistsError extends Error {}
|
||||
// 不正なRole変更エラー
|
||||
export class InvalidRoleChangeError extends Error {}
|
||||
// 暗号化パスワード不足エラー
|
||||
export class EncryptionPasswordNeedError extends Error {}
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, IsNull, UpdateResult } from 'typeorm';
|
||||
import { DataSource, IsNull, Not, UpdateResult } from 'typeorm';
|
||||
import { User } from './entity/user.entity';
|
||||
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
|
||||
import {
|
||||
getDirection,
|
||||
getTaskListSortableAttribute,
|
||||
} from '../../common/types/sort/util';
|
||||
import { UserNotFoundError, EmailAlreadyVerifiedError } from './errors/types';
|
||||
import {
|
||||
UserNotFoundError,
|
||||
EmailAlreadyVerifiedError,
|
||||
AuthorIdAlreadyExistsError,
|
||||
InvalidRoleChangeError,
|
||||
EncryptionPasswordNeedError,
|
||||
} from './errors/types';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
|
||||
@Injectable()
|
||||
@ -165,10 +171,87 @@ export class UsersRepositoryService {
|
||||
* @param user
|
||||
* @returns update
|
||||
*/
|
||||
async update(user: User): Promise<UpdateResult> {
|
||||
async update(
|
||||
accountId: number,
|
||||
id: number,
|
||||
role: string,
|
||||
authorId: string | undefined,
|
||||
autoRenew: boolean,
|
||||
licenseAlart: boolean,
|
||||
notification: boolean,
|
||||
encryption: boolean | undefined,
|
||||
encryptionPassword: string | undefined,
|
||||
prompt: boolean | undefined,
|
||||
): Promise<UpdateResult> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const repo = entityManager.getRepository(User);
|
||||
return await repo.update({ id: user.id }, user);
|
||||
|
||||
// 変更対象のユーザーを取得
|
||||
const targetUser = await repo.findOne({
|
||||
where: { id: id, account_id: accountId },
|
||||
});
|
||||
|
||||
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
|
||||
if (!targetUser) {
|
||||
throw new UserNotFoundError();
|
||||
}
|
||||
|
||||
// ユーザーのロールがNoneの場合以外はロールを変更できない
|
||||
if (targetUser.role !== USER_ROLES.NONE && targetUser.role !== role) {
|
||||
throw new InvalidRoleChangeError('Not None user cannot change role.');
|
||||
}
|
||||
|
||||
if (role === USER_ROLES.AUTHOR) {
|
||||
// ユーザーのロールがAuthorの場合はAuthorIDの重複チェックを行う
|
||||
const authorIdDuplicatedUser = await repo.findOne({
|
||||
where: { account_id: accountId, id: Not(id), author_id: authorId },
|
||||
});
|
||||
|
||||
// 重複したAuthorIDがあった場合はエラー
|
||||
if (authorIdDuplicatedUser) {
|
||||
throw new AuthorIdAlreadyExistsError('authorId already exists.');
|
||||
}
|
||||
|
||||
// 暗号化を有効にする場合はパスワードを設定する
|
||||
if (encryption) {
|
||||
// 暗号化パスワードが設定されている場合は更新する(undefinedの場合は変更なしとして元のパスワードを維持)
|
||||
if (encryptionPassword) {
|
||||
targetUser.encryption_password = encryptionPassword;
|
||||
} else if (!targetUser.encryption_password) {
|
||||
// DBにパスワードが設定されていない場合にはパスワードを設定しないとエラー
|
||||
throw new EncryptionPasswordNeedError(
|
||||
'encryption_password need to set value.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
targetUser.encryption_password = null;
|
||||
}
|
||||
|
||||
// Author用項目を更新
|
||||
targetUser.author_id = authorId;
|
||||
targetUser.encryption = encryption;
|
||||
targetUser.prompt = prompt;
|
||||
} else {
|
||||
// ユーザーのロールがAuthor以外の場合はAuthor用項目はundefinedにする
|
||||
targetUser.author_id = null;
|
||||
targetUser.encryption = false;
|
||||
targetUser.encryption_password = null;
|
||||
targetUser.prompt = false;
|
||||
}
|
||||
|
||||
// 共通項目を更新
|
||||
targetUser.role = role;
|
||||
targetUser.auto_renew = autoRenew;
|
||||
targetUser.license_alert = licenseAlart;
|
||||
targetUser.notification = notification;
|
||||
|
||||
const result = await repo.update({ id: id }, targetUser);
|
||||
|
||||
// 想定外の更新が行われた場合はロールバックを行った上でエラー送出
|
||||
if (result.affected !== 1) {
|
||||
throw new Error(`invalid update. result.affected=${result.affected}`);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user