diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index f156a85..2c10dd6 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -1934,16 +1934,6 @@ "summary": "", "description": "割り当て可能なライセンスを取得します", "parameters": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetAllocatableLicensesRequest" - } - } - } - }, "responses": { "200": { "description": "成功時のレスポンス", @@ -2850,7 +2840,6 @@ "required": ["cardLicenseKey"] }, "ActivateCardLicensesResponse": { "type": "object", "properties": {} }, - "GetAllocatableLicensesRequest": { "type": "object", "properties": {} }, "AllocatableLicenseInfo": { "type": "object", "properties": { diff --git a/dictation_server/src/features/licenses/licenses.controller.ts b/dictation_server/src/features/licenses/licenses.controller.ts index 1cf8e6d..f599211 100644 --- a/dictation_server/src/features/licenses/licenses.controller.ts +++ b/dictation_server/src/features/licenses/licenses.controller.ts @@ -32,6 +32,7 @@ import { AuthGuard } from '../../common/guards/auth/authguards'; import { RoleGuard } from '../../common/guards/role/roleguards'; import { ADMIN_ROLES, TIERS } from '../../constants'; import jwt from 'jsonwebtoken'; +import { makeContext } from '../../common/log'; @ApiTags('licenses') @Controller('licenses') @@ -201,17 +202,18 @@ export class LicensesController { async getAllocatableLicenses( // eslint-disable-next-line @typescript-eslint/no-unused-vars @Req() req: Request, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - @Body() body: GetAllocatableLicensesRequest, ): Promise { - // TODO 仮の戻り値 - return { - allocatableLicenses: [ - { licenseId: 1, expiryDate: null }, - { licenseId: 2, expiryDate: null }, - { licenseId: 3, expiryDate: new Date(2023, 12, 31, 23, 59, 59) }, - { licenseId: 4, expiryDate: new Date(2023, 10, 31, 23, 59, 59) }, - ], - }; + const token = retrieveAuthorizationToken(req); + const payload = jwt.decode(token, { json: true }) as AccessToken; + + const context = makeContext(payload.userId); + + const allocatableLicenses = + await this.licensesService.getAllocatableLicenses( + context, + payload.userId, + ); + + return allocatableLicenses; } } diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 2e3e46c..55e0e96 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -359,6 +359,123 @@ describe('DBテスト', () => { ).toBeDefined(); expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId); }); + + it('取込可能なライセンスのみが取得できる', async () => { + const module = await makeTestingModule(source); + + const now = new Date(); + const { accountId } = await createAccount(source); + const { externalId } = await createUser( + source, + accountId, + 'userId', + 'admin', + ); + + // ライセンスを作成する + // 1件目 + await createLicense( + source, + 1, + new Date(now.getTime() + 60 * 60 * 1000), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 2件目、expiry_dateがnull(OneYear) + await createLicense( + source, + 2, + null, + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 3件目、1件目と同じ有効期限 + await createLicense( + source, + 3, + new Date(now.getTime() + 60 * 60 * 1000), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 4件目、expiry_dateが一番遠いデータ + await createLicense( + source, + 4, + new Date(now.getTime() + 60 * 60 * 1000 * 2), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 5件目、expiry_dateがnull(OneYear) + await createLicense( + source, + 5, + null, + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 6件目、ライセンス状態が割当済 + await createLicense( + source, + 6, + new Date(now.getTime() + 60 * 60 * 1000 * 2), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + null, + ); + // 7件目、ライセンス状態が削除済 + await createLicense( + source, + 7, + new Date(now.getTime() + 60 * 60 * 1000 * 2), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.DELETED, + null, + ); + // 8件目、別アカウントの未割当のライセンス + await createLicense( + source, + 8, + new Date(now.getTime() + 60 * 60 * 1000), + accountId + 1, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + // 9件目、有効期限切れのライセンス + await createLicense( + source, + 9, + new Date(now.getTime() - 60 * 60 * 1000 * 24), + accountId, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + ); + const service = module.get(LicensesService); + const context = makeContext('userId'); + const response = await service.getAllocatableLicenses(context, externalId); + // 対象外のデータは取得していないことを確認する + expect(response.allocatableLicenses.length).toBe(5); + // ソートして取得されていることを確認する + // (expiry_dateがnullを最優先、次に有効期限が遠い順(同じ有効期限の場合はID昇順) + expect(response.allocatableLicenses[0].licenseId).toBe(2); + expect(response.allocatableLicenses[1].licenseId).toBe(5); + expect(response.allocatableLicenses[2].licenseId).toBe(4); + expect(response.allocatableLicenses[3].licenseId).toBe(1); + expect(response.allocatableLicenses[4].licenseId).toBe(3); + }); }); describe('ライセンス割り当て', () => { diff --git a/dictation_server/src/features/licenses/licenses.service.ts b/dictation_server/src/features/licenses/licenses.service.ts index 2c13742..a60e4fd 100644 --- a/dictation_server/src/features/licenses/licenses.service.ts +++ b/dictation_server/src/features/licenses/licenses.service.ts @@ -11,7 +11,11 @@ import { } from '../../repositories/licenses/errors/types'; import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service'; import { UserNotFoundError } from '../../repositories/users/errors/types'; -import { IssueCardLicensesResponse } from './types/types'; +import { + GetAllocatableLicensesResponse, + IssueCardLicensesResponse, +} from './types/types'; +import { Context } from '../../common/log'; @Injectable() export class LicensesService { @@ -211,4 +215,44 @@ export class LicensesService { this.logger.log(`[OUT] ${this.activateCardLicenseKey.name}`); return; } + + /** + * get allocatable lisences + * @param context + * @param userId + * @@returns AllocatableLicenseInfo[] + */ + async getAllocatableLicenses( + context: Context, + userId: string, + ): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.getAllocatableLicenses.name} | params: { ` + + `userId: ${userId}, `, + ); + // ユーザIDからアカウントIDを取得する + try { + const myAccountId = ( + await this.usersRepository.findUserByExternalId(userId) + ).account_id; + // 割り当て可能なライセンスを取得する + const allocatableLicenses = + await this.licensesRepository.getAllocatableLicenses(myAccountId); + + return { + allocatableLicenses, + }; + } catch (e) { + this.logger.error(`error=${e}`); + this.logger.error('get allocatable lisences failed'); + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.getAllocatableLicenses.name}`, + ); + } + } } diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 694d48a..258da24 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { DataSource, In } from 'typeorm'; +import { DataSource, In, IsNull, MoreThanOrEqual } from 'typeorm'; import { LicenseOrder, License, @@ -26,6 +26,10 @@ import { LicenseExpiredError, LicenseUnavailableError, } from './errors/types'; +import { + AllocatableLicenseInfo, + DateWithZeroTime, +} from '../../features/licenses/types/types'; import { NewAllocatedLicenseExpirationDate } from '../../features/licenses/types/types'; @Injectable() @@ -394,7 +398,54 @@ export class LicensesRepositoryService { } }); } + /** + * 対象のアカウントの割り当て可能なライセンスを取得する + * @context Context + * @param accountId + * @param tier + * @return AllocatableLicenseInfo[] + */ + async getAllocatableLicenses( + myAccountId: number, + ): Promise { + const nowDate = new DateWithZeroTime(); + return await this.dataSource.transaction(async (entityManager) => { + const licenseRepo = entityManager.getRepository(License); + const allocatableLicenses = await licenseRepo.find({ + where: [ + { + account_id: myAccountId, + status: In([ + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + LICENSE_ALLOCATED_STATUS.REUSABLE, + ]), + expiry_date: MoreThanOrEqual(nowDate), + }, + { + account_id: myAccountId, + status: In([ + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + LICENSE_ALLOCATED_STATUS.REUSABLE, + ]), + expiry_date: IsNull(), + }, + ], + order: { + expiry_date: { + direction: 'DESC', + nulls: 'FIRST', + }, + id: 'ASC', + }, + }); + + return allocatableLicenses.map((license) => ({ + licenseId: license.id, + expiryDate: license.expiry_date, + })); + }); + } /** * ライセンスをユーザーに割り当てる * @param userId