From 0fd1ff2b6a6bbfcfa02ec539eb0124e6ebdf16b2 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Thu, 24 Aug 2023 08:24:06 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20349:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E3=83=A9=E3=82=A4=E3=82=BB=E3=83=B3=E3=82=B9=E5=89=B2?= =?UTF-8?q?=E3=82=8A=E5=BD=93=E3=81=A6=E8=A7=A3=E9=99=A4API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2450: API実装(ライセンス割り当て解除API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2450) ライセンス割り当て解除APIを実装しました。 ## レビューポイント なし ## UIの変更 なし ## 動作確認状況 ローカルでUT、動作確認済み ## 補足 なし --- dictation_server/src/common/error/code.ts | 1 + dictation_server/src/common/error/message.ts | 1 + .../licenses/licenses.service.spec.ts | 104 ++++++++++++++++++ .../src/features/users/users.controller.ts | 9 +- .../src/features/users/users.service.ts | 39 ++++++- .../src/repositories/licenses/errors/types.ts | 3 + .../licenses/licenses.repository.service.ts | 42 +++++++ 7 files changed, 193 insertions(+), 6 deletions(-) diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 43aec9f..afbdeaf 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -46,4 +46,5 @@ export const ErrorCodes = [ 'E010804', // ライセンス不足エラー 'E010805', // ライセンス有効期限切れエラー 'E010806', // ライセンス割り当て不可エラー + 'E010807', // ライセンス割り当て解除済みエラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index e41ba72..86d41ba 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -35,4 +35,5 @@ export const errors: Errors = { E010804: 'License shortage Error', E010805: 'License is expired Error', E010806: 'License is unavailable Error', + E010807: 'License is already deallocated Error', }; diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 55e0e96..ee7ac87 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -844,3 +844,107 @@ describe('ライセンス割り当て', () => { ); }); }); + +describe('ライセンス割り当て解除', () => { + 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('ライセンスの割り当て解除が完了する', async () => { + const module = await makeTestingModule(source); + + const { accountId } = await createAccount(source); + const { userId } = await createUser(source, accountId, 'userId', 'admin'); + const date = new Date(); + date.setDate(date.getDate() + 30); + await createLicense( + source, + 1, + date, + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + userId, + ); + await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE'); + + const service = module.get(UsersService); + await service.deallocateLicense(makeContext('trackingId'), userId); + + // 割り当て解除したライセンスの状態確認 + const deallocatedLicense = await selectLicense(source, 1); + expect(deallocatedLicense.license.allocated_user_id).toBe(null); + expect(deallocatedLicense.license.status).toBe( + LICENSE_ALLOCATED_STATUS.REUSABLE, + ); + expect(deallocatedLicense.license.expiry_date).toEqual(date); + + // ライセンス履歴テーブルの状態確認 + const licenseAllocationHistory = await selectLicenseAllocationHistory( + source, + userId, + 1, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( + userId, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( + 1, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( + false, + ); + expect( + licenseAllocationHistory.licenseAllocationHistory.switch_from_type, + ).toBe('NONE'); + }); + + it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => { + const module = await makeTestingModule(source); + + const { accountId } = await createAccount(source); + const { userId } = await createUser(source, accountId, 'userId', 'admin'); + await createUser(source, accountId, 'userId2', 'admin'); + const date = new Date(); + date.setDate(date.getDate() + 30); + await createLicense( + source, + 1, + date, + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + 2, + ); + await createLicense( + source, + 2, + date, + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.REUSABLE, + userId, + ); + await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE'); + + const service = module.get(UsersService); + await expect( + service.deallocateLicense(makeContext('trackingId'), userId), + ).rejects.toEqual( + new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST), + ); + }); +}); diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index bb7b589..11ff39c 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -461,13 +461,12 @@ export class UsersController { @Body() body: DeallocateLicenseRequest, @Req() req: Request, ): Promise { - //API実装時に詳細をかいていく - //const accessToken = retrieveAuthorizationToken(req); - //const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; - //const context = makeContext(userId); + const context = makeContext(userId); - //await this.usersService.deallocateLicense(context, body.userId); + await this.usersService.deallocateLicense(context, body.userId); return {}; } } diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 6e9fa07..0fc460c 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -41,6 +41,7 @@ import { DateWithZeroTime } from '../licenses/types/types'; import { Context } from '../../common/log'; import { UserRoles } from '../../common/types/role'; import { + LicenseAlreadyDeallocatedError, LicenseExpiredError, LicenseUnavailableError, } from '../../repositories/licenses/errors/types'; @@ -886,7 +887,7 @@ export class UsersService { this.logger.log( `[IN] [${context.trackingId}] ${this.allocateLicense.name} | params: { ` + `userId: ${userId}, ` + - `newLicenseId: ${newLicenseId}, `, + `newLicenseId: ${newLicenseId}, };`, ); try { @@ -918,4 +919,40 @@ export class UsersService { ); } } + + /** + * ユーザーに割り当てられているライセンスを解除します + * @param context + * @param userId + */ + async deallocateLicense(context: Context, userId: number): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.deallocateLicense.name} | params: { ` + + `userId: ${userId}, };`, + ); + + try { + await this.licensesRepository.deallocateLicense(userId); + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case LicenseAlreadyDeallocatedError: + throw new HttpException( + makeErrorResponse('E010807'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.deallocateLicense.name}`, + ); + } + } } diff --git a/dictation_server/src/repositories/licenses/errors/types.ts b/dictation_server/src/repositories/licenses/errors/types.ts index e0f9fbe..9da7aad 100644 --- a/dictation_server/src/repositories/licenses/errors/types.ts +++ b/dictation_server/src/repositories/licenses/errors/types.ts @@ -18,3 +18,6 @@ export class LicensesShortageError extends Error {} export class LicenseExpiredError extends Error {} // ライセンス割り当て不可エラー export class LicenseUnavailableError extends Error {} + +// ライセンス割り当て解除済みエラー +export class LicenseAlreadyDeallocatedError extends Error {} diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 5bd59b9..38bfcee 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -25,6 +25,7 @@ import { OrderNotFoundError, LicenseExpiredError, LicenseUnavailableError, + LicenseAlreadyDeallocatedError, } from './errors/types'; import { AllocatableLicenseInfo, @@ -541,4 +542,45 @@ export class LicensesRepositoryService { await licenseAllocationHistoryRepo.save(allocationHistory); }); } + + /** + * ユーザーに割り当てられているライセンスを解除する + * @param userId + */ + async deallocateLicense(userId: number): Promise { + await this.dataSource.transaction(async (entityManager) => { + const licenseRepo = entityManager.getRepository(License); + const licenseAllocationHistoryRepo = entityManager.getRepository( + LicenseAllocationHistory, + ); + // 対象ユーザーのライセンス割り当て状態を取得 + const allocatedLicense = await licenseRepo.findOne({ + where: { + allocated_user_id: userId, + status: LICENSE_ALLOCATED_STATUS.ALLOCATED, + }, + }); + + // ライセンスが割り当てられていない場合はエラー + if (!allocatedLicense) { + throw new LicenseAlreadyDeallocatedError( + `License is already deallocated. userId: ${userId}`, + ); + } + + // ライセンスの割り当てを解除 + allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE; + allocatedLicense.allocated_user_id = null; + await licenseRepo.save(allocatedLicense); + + // ライセンス割り当て履歴テーブルへ登録 + const deallocationHistory = new LicenseAllocationHistory(); + deallocationHistory.user_id = userId; + deallocationHistory.license_id = allocatedLicense.id; + deallocationHistory.is_allocated = false; + deallocationHistory.executed_at = new Date(); + deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE; + await licenseAllocationHistoryRepo.save(deallocationHistory); + }); + } }