Merged PR 128: API実装(ソート条件変更)

## 概要
[Task1835: API実装(ソート条件変更)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1835)

- ソート条件変更APIを実装
  - トークンからB2CのIDを取得→userテーブルからユーザー情報を取得
  - ユーザーIDでソート条件テーブルを検索→レコードを更新
- ソート条件テーブルにレコードを作成する
  - アカウント作成時の処理にソート条件レコードを作成する処理を追加
  - ユーザー追加時にも処理を追加
- テスト修正

## レビューポイント
- APIの引数をチェックする関数をControllerに配置してもよいか
- ソート条件のレコードを作成するタイミングに漏れはないか
- 実装漏れはないか

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

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-06-06 08:20:21 +00:00
parent cfca98e53b
commit d5e5e59f8c
18 changed files with 497 additions and 67 deletions

View File

@ -14,7 +14,6 @@ async function bootstrap(): Promise<void> {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
in: 'header',
})
.build();
const document = SwaggerModule.createDocument(app, options);

View File

@ -37,6 +37,7 @@ import { BlobstorageModule } from './gateways/blobstorage/blobstorage.module';
import { LicensesModule } from './features/licenses/licenses.module';
import { LicensesService } from './features/licenses/licenses.service';
import { LicensesController } from './features/licenses/licenses.controller';
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
@Module({
imports: [
@ -81,6 +82,7 @@ import { LicensesController } from './features/licenses/licenses.controller';
NotificationhubModule,
BlobstorageModule,
AuthGuardsModule,
SortCriteriaRepositoryModule,
],
controllers: [
HealthController,

View File

@ -0,0 +1,27 @@
import {
TASK_LIST_SORTABLE_ATTRIBUTES,
SORT_DIRECTIONS,
} from '../../../constants';
export type TaskListSortableAttribute =
(typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number];
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
export const isTaskListSortableAttribute = (
arg: string,
): arg is TaskListSortableAttribute => {
const param = arg as TaskListSortableAttribute;
if (TASK_LIST_SORTABLE_ATTRIBUTES.includes(param)) {
return true;
}
return false;
};
export const isSortDirection = (arg: string): arg is SortDirection => {
const param = arg as SortDirection;
if (SORT_DIRECTIONS.includes(param)) {
return true;
}
return false;
};

View File

@ -0,0 +1,11 @@
import { SortDirection, TaskListSortableAttribute } from '.';
export const getDirection = (direction: SortDirection): SortDirection => {
return direction;
};
export const getTaskListSortableAttribute = (
TaskListSortableAttribute: TaskListSortableAttribute,
): TaskListSortableAttribute => {
return TaskListSortableAttribute;
};

View File

@ -149,5 +149,5 @@ export const TASK_LIST_SORTABLE_ATTRIBUTES = [
'TRANSCRIPTION_FINISHED_DATE',
] as const;
export type TaskListSortableAttribute =
(typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number];
// export const SORT_DIRECTIONS = { asc: 'ASC', desc: 'DESC' } as const;
export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const;

View File

@ -1,12 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { NotificationController } from './notification.controller';
import { NotificationService } from './notification.service';
import { ConfigModule } from '@nestjs/config';
describe('NotificationController', () => {
let controller: NotificationController;
const mockNotificationService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env.local', '.env'],
isGlobal: true,
}),
],
controllers: [NotificationController],
providers: [NotificationService],
})

View File

@ -8,7 +8,7 @@ import {
import { ErrorResponse } from '../../common/error/types/types';
import { RegisterRequest, RegisterResponse } from './types/types';
import { NotificationService } from './notification.service';
import { AuthGuard } from 'src/common/guards/auth/authguards';
import { AuthGuard } from '../../common/guards/auth/authguards';
@ApiTags('notification')
@Controller('notification')

View File

@ -10,11 +10,17 @@ import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
import { User } from '../../../repositories/users/entity/user.entity';
import { UsersRepositoryService } from '../../../repositories/users/users.repository.service';
import { UsersService } from '../users.service';
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
import { SortCriteriaRepositoryService } from '../../../repositories/sort_criteria/sort_criteria.repository.service';
export type CryptoMockValue = {
getPublicKey: string | Error;
};
export type SortCriteriaRepositoryMockValue = {
updateSortCriteria: SortCriteria | Error;
};
export type UsersRepositoryMockValue = {
updateUserVerified: undefined | Error;
findUserById: User | Error;
@ -44,29 +50,58 @@ export type SendGridMockValue = {
sendMail: undefined | Error;
};
export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => {
return {
getMetaData: {
issuer: 'issuer',
},
getSignKeySets: [
{
kid: 'kid',
nbf: 1111111111,
use: 'sig',
kty: 'RSA',
e: 'e',
n: 'n',
},
export const makeUsersServiceMock = async (
cryptoMockValue: CryptoMockValue,
usersRepositoryMockValue: UsersRepositoryMockValue,
adB2cMockValue: AdB2cMockValue,
sendGridMockValue: SendGridMockValue,
configMockValue: ConfigMockValue,
sortCriteriaRepositoryMockValue: SortCriteriaRepositoryMockValue,
): Promise<UsersService> => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
imports: [
ConfigModule.forRoot({
ignoreEnvFile: true,
ignoreEnvVars: true,
}),
],
changePassword: {
sub: 'TEST9999',
},
createUser: '001',
getUser: {
displayName: 'Hanako Sato',
mail: 'hanako@sample.com',
},
})
.useMocker((token) => {
switch (token) {
case CryptoService:
return makeCryptoServiceMock(cryptoMockValue);
case UsersRepositoryService:
return makeUsersRepositoryMock(usersRepositoryMockValue);
case AdB2cService:
return makeAdB2cServiceMock(adB2cMockValue);
case SendGridService:
return makeSendGridMock(sendGridMockValue);
case ConfigService:
return makeConfigMock(configMockValue);
case SortCriteriaRepositoryService:
return makeSortCriteriaRepositoryMock(
sortCriteriaRepositoryMockValue,
);
}
})
.compile();
return module.get<UsersService>(UsersService);
};
export const makeSortCriteriaRepositoryMock = (
value: SortCriteriaRepositoryMockValue,
) => {
const { updateSortCriteria } = value;
return {
updateSortCriteria:
updateSortCriteria instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(updateSortCriteria)
: jest
.fn<Promise<SortCriteria>, []>()
.mockResolvedValue(updateSortCriteria),
};
};
@ -130,41 +165,6 @@ export type ConfigMockValue = {
get: string | Error;
};
export const makeUsersServiceMock = async (
cryptoMockValue: CryptoMockValue,
usersRepositoryMockValue: UsersRepositoryMockValue,
adB2cMockValue: AdB2cMockValue,
sendGridMockValue: SendGridMockValue,
configMockValue: ConfigMockValue,
): Promise<UsersService> => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
imports: [
ConfigModule.forRoot({
ignoreEnvFile: true,
ignoreEnvVars: true,
}),
],
})
.useMocker((token) => {
switch (token) {
case CryptoService:
return makeCryptoServiceMock(cryptoMockValue);
case UsersRepositoryService:
return makeUsersRepositoryMock(usersRepositoryMockValue);
case AdB2cService:
return makeAdB2cServiceMock(adB2cMockValue);
case SendGridService:
return makeSendGridMock(sendGridMockValue);
case ConfigService:
return makeConfigMock(configMockValue);
}
})
.compile();
return module.get<UsersService>(UsersService);
};
export const makeCryptoServiceMock = (value: CryptoMockValue) => {
const { getPublicKey } = value;
@ -292,6 +292,46 @@ export const makeDefaultConfigValue = (): ConfigMockValue => {
};
};
export const makeDefaultSortCriteriaRepositoryMockValue =
(): SortCriteriaRepositoryMockValue => {
const sortCriteria = new SortCriteria();
{
sortCriteria.id = 1;
sortCriteria.direction = 'ASC';
sortCriteria.parameter = 'JOB_NUMBER';
sortCriteria.user_id = 1;
}
return {
updateSortCriteria: sortCriteria,
};
};
export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => {
return {
getMetaData: {
issuer: 'issuer',
},
getSignKeySets: [
{
kid: 'kid',
nbf: 1111111111,
use: 'sig',
kty: 'RSA',
e: 'e',
n: 'n',
},
],
changePassword: {
sub: 'TEST9999',
},
createUser: '001',
getUser: {
displayName: 'Hanako Sato',
mail: 'hanako@sample.com',
},
};
};
// 個別のテストケースに対応してそれぞれのMockを用意するのは無駄が多いのでテストケース内で個別の値を設定する
export const makeDefaultUsersRepositoryMockValue =
(): UsersRepositoryMockValue => {

View File

@ -139,8 +139,12 @@ export class SortCriteriaRequest {
@IsIn(['ASC', 'DESC'], { message: 'invalid direction' })
direction: string;
@ApiProperty({ description: `${TASK_LIST_SORTABLE_ATTRIBUTES.join('/')}` })
@IsIn(TASK_LIST_SORTABLE_ATTRIBUTES, { message: 'invalid attributes' })
@ApiProperty({
description: `${TASK_LIST_SORTABLE_ATTRIBUTES.join('/')}`,
})
@IsIn(TASK_LIST_SORTABLE_ATTRIBUTES, {
message: 'invalid attributes',
})
paramName: string;
}

View File

@ -5,8 +5,8 @@ import {
HttpStatus,
Post,
Req,
UseGuards,
HttpException,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
@ -33,9 +33,12 @@ import {
SortCriteriaResponse,
} from './types/types';
import { UsersService } from './users.service';
import jwt from 'jsonwebtoken';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import {
isSortDirection,
isTaskListSortableAttribute,
} from '../../common/types/sort';
@ApiTags('users')
@Controller('users')
export class UsersController {
@ -284,6 +287,11 @@ export class UsersController {
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '不正なパラメータ',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
@ -298,8 +306,27 @@ export class UsersController {
@Post('sort-criteria')
async updateSortCriteria(
@Body() body: SortCriteriaRequest,
@Req() req: Request,
): Promise<SortCriteriaResponse> {
console.log(body);
const { direction, paramName } = body;
const accessToken = retrieveAuthorizationToken(req);
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
//型チェック
if (
!isTaskListSortableAttribute(paramName) ||
!isSortDirection(direction)
) {
throw new HttpException(
makeErrorResponse('E010001'),
HttpStatus.BAD_REQUEST,
);
}
await this.usersService.updateSortCriteria(
paramName,
direction,
decodedToken,
);
return {};
}
}

View File

@ -6,11 +6,13 @@ import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { SortCriteriaRepositoryModule } from '../../repositories/sort_criteria/sort_criteria.repository.module';
@Module({
imports: [
CryptoModule,
UsersRepositoryModule,
SortCriteriaRepositoryModule,
AdB2cModule,
SendGridModule,
ConfigModule,

View File

@ -8,6 +8,7 @@ import {
makeDefaultConfigValue,
makeDefaultCryptoMockValue,
makeDefaultSendGridlValue,
makeDefaultSortCriteriaRepositoryMockValue,
makeDefaultUsersRepositoryMockValue,
makeUsersServiceMock,
} from './test/users.service.mock';
@ -20,12 +21,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -52,6 +56,8 @@ describe('UsersService', () => {
};
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
@ -59,6 +65,7 @@ describe('UsersService', () => {
adb2cParam,
sendGridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
@ -72,12 +79,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token = 'invalid.id.token';
await expect(service.confirmUser(token)).rejects.toEqual(
@ -106,12 +116,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token = 'invalid.id.token';
await expect(service.confirmUserAndInitPassword(token)).rejects.toEqual(
@ -124,6 +137,8 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.updateUserVerified =
new EmailAlreadyVerifiedError();
@ -134,6 +149,7 @@ describe('UsersService', () => {
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -162,6 +178,8 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.updateUserVerified =
new EmailAlreadyVerifiedError();
@ -171,6 +189,7 @@ describe('UsersService', () => {
adb2cParam,
sendGridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -184,6 +203,8 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.updateUserVerified = new Error('DB error');
const service = await makeUsersServiceMock(
@ -192,6 +213,7 @@ describe('UsersService', () => {
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -224,12 +246,15 @@ describe('UsersService', () => {
const sendGridMockValue = makeDefaultSendGridlValue();
usersRepositoryMockValue.updateUserVerified = new Error('DB error');
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -246,12 +271,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user1';
const role = 'None';
@ -281,12 +309,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user2';
const role = 'Author';
@ -318,12 +349,15 @@ describe('UsersService', () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user3';
const role = 'Transcriptioninst';
@ -355,6 +389,8 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.createNormalUser = new Error('DB error');
const service = await makeUsersServiceMock(
cryptoMockValue,
@ -362,6 +398,7 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user5';
const role = 'Transcriptioninst';
@ -394,12 +431,15 @@ it('Azure ADB2Cでネットワークエラーとなる場合、エラーとな
adb2cParam.createUser = new Error();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user6';
const role = 'Transcriptioninst';
@ -432,12 +472,15 @@ it('メールアドレスが重複している場合、エラーとなる。', a
adb2cParam.createUser = { reason: 'email', message: 'ObjectConflict' };
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user7';
const role = 'Transcriptioninst';
@ -466,6 +509,8 @@ it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.createNormalUser = new Error();
usersRepositoryMockValue.existsAuthorId = true;
@ -475,6 +520,7 @@ it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user8';
const role = 'Author';
@ -505,6 +551,8 @@ it('AuthorIDが重複している場合、エラーとなる。(insert失敗)',
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.createNormalUser = new Error();
usersRepositoryMockValue.createNormalUser.name = 'ER_DUP_ENTRY';
@ -514,6 +562,7 @@ it('AuthorIDが重複している場合、エラーとなる。(insert失敗)',
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const name = 'test_user9';
const role = 'Author';
@ -545,12 +594,15 @@ it('ユーザの一覧を取得する', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -589,6 +641,8 @@ it('ユーザの一覧を取得に失敗する', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
usersRepositoryMockValue.findSameAccountUsers = new Error(
'Failure to acquire',
);
@ -599,6 +653,7 @@ it('ユーザの一覧を取得に失敗する', async () => {
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
@ -615,6 +670,8 @@ it('ユーザの一覧を0件取得する', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
// モックでDBからのユーザ取得を空にする
const noDbUsers: EntityUser[] = [];
@ -626,6 +683,7 @@ it('ユーザの一覧を0件取得する', async () => {
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
const token =
@ -634,3 +692,98 @@ it('ユーザの一覧を0件取得する', async () => {
const emptyMergedUsers: User[] = [];
expect(await service.getUsers(token)).toEqual(emptyMergedUsers);
});
it('ソート条件を変更できる', async () => {
const cryptoMockValue = makeDefaultCryptoMockValue();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
expect(
await service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
}),
).toEqual(undefined);
});
it('ユーザー情報が存在せず、ソート条件を変更できない', async () => {
const cryptoMockValue = makeDefaultCryptoMockValue();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
// モックでDBからのユーザ取得がエラーとなる
usersRepositoryMockValue.findUserByExternalId = new Error('user not found');
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
}),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('ソート条件が存在せず、ソート条件を変更できない', async () => {
const cryptoMockValue = makeDefaultCryptoMockValue();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
makeDefaultSortCriteriaRepositoryMockValue();
sortCriteriaRepositoryMockValue.updateSortCriteria = new Error(
'sort criteria not found',
);
// モックでDBからのユーザ取得を空にする
const service = await makeUsersServiceMock(
cryptoMockValue,
usersRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
sortCriteriaRepositoryMockValue,
);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
role: 'none admin',
userId: 'xxxxxxxxxxxx',
}),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});

View File

@ -4,6 +4,10 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { isVerifyError, verify } from '../../common/jwt';
import { makePassword } from '../../common/password/password';
import { AccessToken } from '../../common/token';
import {
TaskListSortableAttribute,
SortDirection,
} from '../../common/types/sort';
import {
AdB2cService,
ConflictError,
@ -11,6 +15,7 @@ import {
} from '../../gateways/adb2c/adb2c.service';
import { CryptoService } from '../../gateways/crypto/crypto.service';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/sort_criteria.repository.service';
import { User as EntityUser } from '../../repositories/users/entity/user.entity';
import {
EmailAlreadyVerifiedError,
@ -23,6 +28,7 @@ export class UsersService {
constructor(
private readonly cryptoService: CryptoService,
private readonly usersRepository: UsersRepositoryService,
private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService,
private readonly sendgridService: SendGridService,
@ -336,4 +342,48 @@ export class UsersService {
this.logger.log(`[OUT] ${this.getUsers.name}`);
}
}
/**
* Updates sort criteria
* @param paramName
* @param direction
* @param token
* @returns sort criteria
*/
async updateSortCriteria(
paramName: TaskListSortableAttribute,
direction: SortDirection,
token: AccessToken,
): Promise<void> {
this.logger.log(`[IN] ${this.updateSortCriteria.name}`);
let user: EntityUser;
try {
// ユーザー情報を取得
const sub = token.userId;
user = await this.usersRepository.findUserByExternalId(sub);
} catch (e) {
this.logger.error(`error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// ユーザーのソート条件を更新
await this.sortCriteriaRepository.updateSortCriteria(
user.id,
paramName,
direction,
);
} catch (e) {
this.logger.error(`error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.updateSortCriteria.name}`);
}
}
}

View File

@ -2,6 +2,11 @@ import { Injectable } from '@nestjs/common';
import { DataSource, UpdateResult } from 'typeorm';
import { User } from '../users/entity/user.entity';
import { Account } from './entity/account.entity';
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
import {
getDirection,
getTaskListSortableAttribute,
} from '../../common/types/sort/util';
export class AccountNotFoundError extends Error {}
@ -111,6 +116,17 @@ export class AccountsRepositoryService {
throw new Error(`invalid update. result.affected=${result.affected}`);
}
// ユーザーのタスクソート条件を作成
const sortCriteria = new SortCriteria();
{
sortCriteria.parameter = getTaskListSortableAttribute('JOB_NUMBER');
sortCriteria.direction = getDirection('ASC');
sortCriteria.user_id = persistedUser.id;
}
const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
const newSortCriteria = sortCriteriaRepo.create(sortCriteria);
await sortCriteriaRepo.save(newSortCriteria);
return { newAccount: persistedAccount, adminUser: persistedUser };
});
}

View File

@ -0,0 +1,16 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ name: 'sort_criteria' })
export class SortCriteria {
@PrimaryGeneratedColumn()
id: number;
@Column()
user_id: number;
@Column()
parameter: string;
@Column()
direction: string;
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { SortCriteriaRepositoryService } from './sort_criteria.repository.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SortCriteria } from './entity/sort_criteria.entity';
@Module({
imports: [TypeOrmModule.forFeature([SortCriteria])],
providers: [SortCriteriaRepositoryService],
exports: [SortCriteriaRepositoryService],
})
export class SortCriteriaRepositoryModule {}

View File

@ -0,0 +1,48 @@
import { Injectable, Logger } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { SortCriteria } from './entity/sort_criteria.entity';
import {
TaskListSortableAttribute,
SortDirection,
} from '../../common/types/sort';
export class SortCriteriaNotFoundError extends Error {}
@Injectable()
export class SortCriteriaRepositoryService {
constructor(private dataSource: DataSource) {}
private readonly logger = new Logger(SortCriteriaRepositoryService.name);
/**
* Updates sort criteria
* @param userId
* @param parameter
* @param direction
* @returns sort criteria
*/
async updateSortCriteria(
userId: number,
parameter: TaskListSortableAttribute,
direction: SortDirection,
): Promise<SortCriteria> {
this.logger.log(
` ${this.updateSortCriteria.name}; parameter:${parameter}, direction:${direction}`,
);
return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(SortCriteria);
const targetSortCriteria = await repo.findOne({
where: {
user_id: userId,
},
});
// 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!targetSortCriteria) {
throw new Error('sort criteria not found ');
}
targetSortCriteria.parameter = parameter;
targetSortCriteria.direction = direction;
const persisted = await repo.save(targetSortCriteria);
return persisted;
});
}
}

View File

@ -1,6 +1,11 @@
import { Injectable } from '@nestjs/common';
import { DataSource, 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';
// UsersRepositoryServiceで発生するエラーを定義
export class EmailAlreadyVerifiedError extends Error {}
@ -73,6 +78,18 @@ export class UsersRepositoryService {
const repo = entityManager.getRepository(User);
const newUser = repo.create(user);
const persisted = await repo.save(newUser);
// ユーザーのタスクソート条件を作成
const sortCriteria = new SortCriteria();
{
sortCriteria.parameter = getTaskListSortableAttribute('JOB_NUMBER');
sortCriteria.direction = getDirection('ASC');
sortCriteria.user_id = persisted.id;
}
const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
const newSortCriteria = sortCriteriaRepo.create(sortCriteria);
await sortCriteriaRepo.save(newSortCriteria);
return persisted;
},
);