From 2c935c8b52a62765c05c368c212bc4ff9394f663 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Tue, 30 May 2023 07:17:43 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20101:=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=E6=B3=A8?= =?UTF-8?q?=E6=96=87=E7=99=BB=E9=8C=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1685: API実装(ライセンス注文登録)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1685) タスク 1685: API実装(ライセンス注文登録) ライセンス注文APIを追加 ## レビューポイント 登録時のDB処理方法に問題がないか。 処理、エラーハンドリングに過不足がないか。 ## UIの変更 なし ## 動作確認状況 ローカルでユニットテストを実施。 ローカルでAPIを実行し、DBに登録できること・poNumberの重複チェックが想定通りに動作していることを確認。 ## 補足 なし --- dictation_server/src/app.module.ts | 4 +- dictation_server/src/common/error/code.ts | 3 + dictation_server/src/common/error/message.ts | 3 + dictation_server/src/constants/index.ts | 12 ++ .../licenses/licenses.controller.spec.ts | 3 +- .../features/licenses/licenses.controller.ts | 49 ++++++- .../src/features/licenses/licenses.module.ts | 10 ++ .../licenses/licenses.service.spec.ts | 121 ++++++++++++++++-- .../src/features/licenses/licenses.service.ts | 108 +++++++++++++++- .../licenses/test/liscense.service.mock.ts | 119 +++++++++++++++++ .../accounts/accounts.repository.service.ts | 20 +++ .../licenses/entity/license.entity.ts | 36 ++++++ .../licenses/licenses.repository.module.ts | 11 ++ .../licenses/licenses.repository.service.ts | 61 +++++++++ 14 files changed, 542 insertions(+), 18 deletions(-) create mode 100644 dictation_server/src/features/licenses/test/liscense.service.mock.ts create mode 100644 dictation_server/src/repositories/licenses/entity/license.entity.ts create mode 100644 dictation_server/src/repositories/licenses/licenses.repository.module.ts create mode 100644 dictation_server/src/repositories/licenses/licenses.repository.service.ts diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index 5c7821f..3d5b563 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -19,6 +19,7 @@ import { AccountsRepositoryModule } from './repositories/accounts/accounts.repos import { TypeOrmModule } from '@nestjs/typeorm'; import { SendGridModule } from './gateways/sendgrid/sendgrid.module'; import { UsersRepositoryModule } from './repositories/users/users.repository.module'; +import { LicensesRepositoryModule } from './repositories/licenses/licenses.repository.module'; import { AudioFilesRepositoryModule } from './repositories/audio_files/audio_files.repository.module'; import { AudioOptionItemsRepositoryModule } from './repositories/audio_option_items/audio_option_items.repository.module'; import { TasksRepositoryModule } from './repositories/tasks/tasks.repository.module'; @@ -55,8 +56,10 @@ import { LicensesController } from './features/licenses/licenses.controller'; TasksModule, UsersModule, SendGridModule, + LicensesModule, AccountsRepositoryModule, UsersRepositoryModule, + LicensesRepositoryModule, AudioFilesRepositoryModule, AudioOptionItemsRepositoryModule, TasksRepositoryModule, @@ -77,7 +80,6 @@ import { LicensesController } from './features/licenses/licenses.controller'; NotificationModule, NotificationhubModule, BlobstorageModule, - LicensesModule, AuthGuardsModule, ], controllers: [ diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index c8d799c..8d68836 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -26,6 +26,9 @@ export const ErrorCodes = [ 'E010201', // 未認証ユーザエラー 'E010202', // 認証済ユーザエラー 'E010203', // 管理ユーザ権限エラー + 'E010204', // ユーザ不在エラー 'E010301', // メールアドレス登録済みエラー 'E010302', // authorId重複エラー + 'E010401', // PONumber重複エラー + 'E010501', // アカウント不在エラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 0f8121f..96e8c25 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -15,6 +15,9 @@ export const errors: Errors = { E010201: 'Email not verified user Error.', E010202: 'Email already verified user Error.', E010203: 'Administrator Permissions Error.', + E010204: 'User not Found Error.', E010301: 'This email user already created Error', E010302: 'This AuthorId already used Error', + E010401: 'This PoNumber already used Error', + E010501: 'Account not Found Error.', }; diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index d3fc87e..134d4fb 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -88,6 +88,18 @@ export const BLOB_STORAGE_REGION_EU = [ */ export const ROLE_NONE = 'None'; +/** + * ライセンス注文ステータス(発行待ち) + * @const {string} + */ +export const LICENSE_STATUS_ISSUE_REQUESTING = 'Issue Requesting'; + +/** + * ライセンス注文ステータス(発行済み) + * @const {string} + */ +export const LICENSE_STATUS_ISSUED = 'Issued'; + /** * 音声ファイルに紐づくオプションアイテムの数 * @const {string} diff --git a/dictation_server/src/features/licenses/licenses.controller.spec.ts b/dictation_server/src/features/licenses/licenses.controller.spec.ts index 70daf6c..94cfefd 100644 --- a/dictation_server/src/features/licenses/licenses.controller.spec.ts +++ b/dictation_server/src/features/licenses/licenses.controller.spec.ts @@ -1,6 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LicensesController } from './licenses.controller'; import { LicensesService } from './licenses.service'; +import { CryptoService } from '../../gateways/crypto/crypto.service'; import { ConfigModule } from '@nestjs/config'; describe('LicensesController', () => { @@ -16,7 +17,7 @@ describe('LicensesController', () => { }), ], controllers: [LicensesController], - providers: [LicensesService], + providers: [LicensesService, CryptoService], }) .overrideProvider(LicensesService) .useValue(mockLicensesService) diff --git a/dictation_server/src/features/licenses/licenses.controller.ts b/dictation_server/src/features/licenses/licenses.controller.ts index b64dced..5e78c15 100644 --- a/dictation_server/src/features/licenses/licenses.controller.ts +++ b/dictation_server/src/features/licenses/licenses.controller.ts @@ -5,6 +5,7 @@ import { Post, Req, UseGuards, + HttpException, } from '@nestjs/common'; import { ApiResponse, @@ -16,12 +17,21 @@ import { ErrorResponse } from '../../common/error/types/types'; import { LicensesService } from './licenses.service'; import { CreateOrdersResponse, CreateOrdersRequest } from './types/types'; import { Request } from 'express'; +import { CryptoService } from '../../gateways/crypto/crypto.service'; +import { retrieveAuthorizationToken } from '../../common/http/helper'; +import { confirmPermission } from '../../common/auth/auth'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { isVerifyError, verify } from '../../common/jwt'; +import { AccessToken } from '../../common/token'; import { AuthGuard } from '../../common/guards/auth/authguards'; @ApiTags('licenses') @Controller('licenses') export class LicensesController { - constructor(private readonly licensesService: LicensesService) {} + constructor( + private readonly licensesService: LicensesService, + private readonly cryptoService: CryptoService, + ) {} @ApiResponse({ status: HttpStatus.OK, @@ -45,6 +55,8 @@ export class LicensesController { }) @ApiOperation({ operationId: 'createOrders' }) @ApiBearerAuth() + // @UseGuards(AuthGuard) + // @UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] })) @Post('/orders') async createOrders( @Req() req: Request, @@ -52,6 +64,41 @@ export class LicensesController { ): Promise { console.log(req.header('Authorization')); console.log(body); + + // アクセストークンにより権限を確認する + const pubKey = await this.cryptoService.getPublicKey(); + const accessToken = retrieveAuthorizationToken(req); + + //アクセストークンが存在しない場合のエラー + if (accessToken == undefined) { + throw new HttpException( + makeErrorResponse('E000107'), + HttpStatus.UNAUTHORIZED, + ); + } + const payload = verify(accessToken, pubKey); + + //アクセストークン形式エラー + if (isVerifyError(payload)) { + throw new HttpException( + makeErrorResponse('E000101'), + HttpStatus.UNAUTHORIZED, + ); + } + //アクセストークンの権限不足エラー + if (!confirmPermission(payload.role)) { + throw new HttpException( + makeErrorResponse('E000108'), + HttpStatus.UNAUTHORIZED, + ); + } + + // ライセンス注文処理 + await this.licensesService.licenseOrders( + payload, + body.poNumber, + body.orderCount, + ); return {}; } } diff --git a/dictation_server/src/features/licenses/licenses.module.ts b/dictation_server/src/features/licenses/licenses.module.ts index a223cf9..e1d7c8b 100644 --- a/dictation_server/src/features/licenses/licenses.module.ts +++ b/dictation_server/src/features/licenses/licenses.module.ts @@ -1,8 +1,18 @@ import { Module } from '@nestjs/common'; import { LicensesController } from './licenses.controller'; import { LicensesService } from './licenses.service'; +import { CryptoModule } from '../../gateways/crypto/crypto.module'; +import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; +import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module'; +import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module'; @Module({ + imports: [ + CryptoModule, + UsersRepositoryModule, + AccountsRepositoryModule, + LicensesRepositoryModule, + ], controllers: [LicensesController], providers: [LicensesService], }) diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 7288d3d..df0cc52 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -1,18 +1,113 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { LicensesService } from './licenses.service'; +import { AccessToken } from 'src/common/token'; +import { CreateOrdersRequest } from './types/types'; +import { + makeDefaultAccountsRepositoryMockValue, + makeDefaultLicensesRepositoryMockValue, + makeDefaultUsersRepositoryMockValue, + makeLicensesServiceMock, +} from './test/liscense.service.mock'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { HttpException, HttpStatus } from '@nestjs/common'; describe('LicensesService', () => { - let service: LicensesService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [LicensesService], - }).compile(); - - service = module.get(LicensesService); + it('ライセンス注文が完了する', async () => { + const lisencesRepositoryMockValue = + makeDefaultLicensesRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const accountsRepositoryMockValue = + makeDefaultAccountsRepositoryMockValue(); + const service = await makeLicensesServiceMock( + lisencesRepositoryMockValue, + usersRepositoryMockValue, + accountsRepositoryMockValue, + ); + const body = new CreateOrdersRequest(); + const token: AccessToken = { userId: '0001', role: '' }; + body.orderCount = 1000; + body.poNumber = '1'; + expect( + await service.licenseOrders(token, body.poNumber, body.orderCount), + ).toEqual(undefined); }); - - it('should be defined', () => { - expect(service).toBeDefined(); + it('ユーザID取得できなかった場合、エラーとなる', async () => { + const lisencesRepositoryMockValue = + makeDefaultLicensesRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + usersRepositoryMockValue.findUserByExternalId = new Error( + 'User not Found Error.', + ); + const accountsRepositoryMockValue = + makeDefaultAccountsRepositoryMockValue(); + const service = await makeLicensesServiceMock( + lisencesRepositoryMockValue, + usersRepositoryMockValue, + accountsRepositoryMockValue, + ); + const body = new CreateOrdersRequest(); + const token: AccessToken = { userId: '', role: '' }; + body.orderCount = 1000; + body.poNumber = '1'; + await expect( + service.licenseOrders(token, body.poNumber, body.orderCount), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('親ユーザID取得できなかった場合、エラーとなる', async () => { + const lisencesRepositoryMockValue = + makeDefaultLicensesRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + usersRepositoryMockValue.findUserByExternalId = new Error( + 'Account not Found Error.', + ); + const accountsRepositoryMockValue = + makeDefaultAccountsRepositoryMockValue(); + const service = await makeLicensesServiceMock( + lisencesRepositoryMockValue, + usersRepositoryMockValue, + accountsRepositoryMockValue, + ); + const body = new CreateOrdersRequest(); + const token: AccessToken = { userId: '0001', role: '' }; + body.orderCount = 1000; + body.poNumber = '1'; + await expect( + service.licenseOrders(token, body.poNumber, body.orderCount), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('POナンバー重複時、エラーとなる', async () => { + const lisencesRepositoryMockValue = + makeDefaultLicensesRepositoryMockValue(); + lisencesRepositoryMockValue.order = new Error( + 'Email already verified user Error.', + ); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const accountsRepositoryMockValue = + makeDefaultAccountsRepositoryMockValue(); + const service = await makeLicensesServiceMock( + lisencesRepositoryMockValue, + usersRepositoryMockValue, + accountsRepositoryMockValue, + ); + const body = new CreateOrdersRequest(); + const token: AccessToken = { userId: '0001', role: '' }; + body.orderCount = 1000; + body.poNumber = '1'; + await expect( + service.licenseOrders(token, body.poNumber, body.orderCount), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); }); }); diff --git a/dictation_server/src/features/licenses/licenses.service.ts b/dictation_server/src/features/licenses/licenses.service.ts index 4677a8f..94d24b2 100644 --- a/dictation_server/src/features/licenses/licenses.service.ts +++ b/dictation_server/src/features/licenses/licenses.service.ts @@ -1,4 +1,108 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; +import { request } from 'http'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { CryptoService } from '../../gateways/crypto/crypto.service'; +import { AccessToken } from 'src/common/token'; +import { User as EntityUser } from '../../repositories/users/entity/user.entity'; +import { + UsersRepositoryService, + UserNotFoundError, +} from '../../repositories/users/users.repository.service'; +import { + AccountsRepositoryService, + AccountNotFoundError, +} from '../../repositories/accounts/accounts.repository.service'; +import { + LicensesRepositoryService, + PoNumberAlreadyExistError, +} from '../../repositories/licenses/licenses.repository.service'; +import { CreateOrdersRequest } from './types/types'; +import { DataSource } from 'typeorm'; @Injectable() -export class LicensesService {} +export class LicensesService { + constructor( + private readonly usersRepository: UsersRepositoryService, + private readonly accountsRepository: AccountsRepositoryService, + private readonly licensesRepository: LicensesRepositoryService, + ) {} + private readonly logger = new Logger(LicensesService.name); + + /** + * license Orders + * @param token + * @param body + */ + async licenseOrders( + accessToken: AccessToken, + poNumber: string, + orderCount: number, + ): Promise { + //アクセストークンからユーザーIDを取得する + this.logger.log(`[IN] ${this.licenseOrders.name}`); + const userId = accessToken.userId; + let myAccountId: number; + let parentAccountId: number; + + // ユーザIDからアカウントIDを取得する + try { + myAccountId = (await this.usersRepository.findUserByExternalId(userId)) + .account_id; + } catch (e) { + switch (e.constructor) { + case UserNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + // 親アカウントIDを取得 + try { + parentAccountId = ( + await this.accountsRepository.findAccountById(myAccountId) + ).parent_account_id; + } catch (e) { + switch (e.constructor) { + case AccountNotFoundError: + throw new HttpException( + makeErrorResponse('E010501'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + try { + await this.licensesRepository.order( + poNumber, + myAccountId, + parentAccountId, + orderCount, + ); + } catch (e) { + switch (e.constructor) { + case PoNumberAlreadyExistError: + throw new HttpException( + makeErrorResponse('E010401'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/dictation_server/src/features/licenses/test/liscense.service.mock.ts b/dictation_server/src/features/licenses/test/liscense.service.mock.ts new file mode 100644 index 0000000..bf9c4e5 --- /dev/null +++ b/dictation_server/src/features/licenses/test/liscense.service.mock.ts @@ -0,0 +1,119 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LicensesService } from '../licenses.service'; +import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service'; +import { User } from '../../../repositories/users/entity/user.entity'; +import { Account } from '../../../repositories/accounts/entity/account.entity'; +import { UsersRepositoryService } from '../../../repositories/users/users.repository.service'; +import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service'; + +export type LicensesRepositoryMockValue = { + order: undefined | Error; +}; + +export type AccountsRepositoryMockValue = { + findAccountById: Account | Error; +}; + +export type UsersRepositoryMockValue = { + findUserByExternalId: User | Error; +}; + +export const makeLicensesServiceMock = async ( + licensesRepositoryMockValue: LicensesRepositoryMockValue, + usersRepositoryMockValue: UsersRepositoryMockValue, + accountsRepositoryMockValue: AccountsRepositoryMockValue, +): Promise => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LicensesService], + }) + .useMocker((token) => { + switch (token) { + case LicensesRepositoryService: + return makeLicensesRepositoryMock(licensesRepositoryMockValue); + case UsersRepositoryService: + return makeUsersRepositoryMock(usersRepositoryMockValue); + case AccountsRepositoryService: + return makeAccountsRepositoryMock(accountsRepositoryMockValue); + } + }) + .compile(); + + return module.get(LicensesService); +}; + +export const makeLicensesRepositoryMock = ( + value: LicensesRepositoryMockValue, +) => { + const { order } = value; + return { + order: + order instanceof Error + ? jest.fn, []>().mockRejectedValue(order) + : jest.fn, []>().mockResolvedValue(order), + }; +}; + +export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { + const { findUserByExternalId } = value; + + return { + findUserByExternalId: + findUserByExternalId instanceof Error + ? jest.fn, []>().mockRejectedValue(findUserByExternalId) + : jest.fn, []>().mockResolvedValue(findUserByExternalId), + }; +}; + +export const makeAccountsRepositoryMock = ( + value: AccountsRepositoryMockValue, +) => { + const { findAccountById } = value; + + return { + findAccountById: + findAccountById instanceof Error + ? jest.fn, []>().mockRejectedValue(findAccountById) + : jest.fn, []>().mockResolvedValue(findAccountById), + }; +}; + +export const makeDefaultLicensesRepositoryMockValue = + (): LicensesRepositoryMockValue => { + return { + order: undefined, + }; + }; +export const makeDefaultUsersRepositoryMockValue = + (): UsersRepositoryMockValue => { + const user1 = new User(); + user1.id = 2; + user1.external_id = 'ede66c43-9b9d-4222-93ed-5f11c96e08e2'; + user1.account_id = 1234567890123456; + user1.role = 'none'; + user1.author_id = '6cce347f-0cf1-a15e-19ab-d00988b643f9'; + user1.accepted_terms_version = '1.0'; + user1.email_verified = true; + user1.auto_renew = false; + user1.license_alert = false; + user1.notification = false; + user1.deleted_at = undefined; + user1.created_by = 'test'; + user1.created_at = new Date(); + user1.updated_by = undefined; + user1.updated_at = undefined; + + return { + findUserByExternalId: user1, + }; + }; + +export const makeDefaultAccountsRepositoryMockValue = + (): AccountsRepositoryMockValue => { + const account1 = new Account(); + account1.id = 2; + account1.parent_account_id = 987654321098765; + + return { + findAccountById: account1, + }; + }; diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 56f4b85..d63a532 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -3,6 +3,8 @@ import { DataSource, UpdateResult } from 'typeorm'; import { User } from '../users/entity/user.entity'; import { Account } from './entity/account.entity'; +export class AccountNotFoundError extends Error {} + @Injectable() export class AccountsRepositoryService { constructor(private dataSource: DataSource) {} @@ -112,4 +114,22 @@ export class AccountsRepositoryService { return { newAccount: persistedAccount, adminUser: persistedUser }; }); } + + /** + * アカウントIDからアカウント情報を取得する + * @param id + * @returns account + */ + async findAccountById(id: number): Promise { + const account = await this.dataSource.getRepository(Account).findOne({ + where: { + id: id, + }, + }); + + if (!account) { + throw new AccountNotFoundError(); + } + return account; + } } diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts new file mode 100644 index 0000000..ad22ec9 --- /dev/null +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -0,0 +1,36 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, +} from 'typeorm'; + +@Entity({ name: 'license_orders' }) +export class LicenseOrder { + @PrimaryGeneratedColumn() + id: number; + + @Column() + po_number: string; + + @Column() + from_account_id: number; + + @Column() + to_account_id: number; + + @CreateDateColumn() + ordered_at: Date; + + @Column('timestamp', { nullable: true }) + issued_at?: Date; + + @Column() + quantity: number; + + @Column() + status: string; + + @Column('timestamp', { nullable: true }) + canceled_at?: Date; +} diff --git a/dictation_server/src/repositories/licenses/licenses.repository.module.ts b/dictation_server/src/repositories/licenses/licenses.repository.module.ts new file mode 100644 index 0000000..1f1e9da --- /dev/null +++ b/dictation_server/src/repositories/licenses/licenses.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { LicenseOrder } from './entity/license.entity'; +import { LicensesRepositoryService } from './licenses.repository.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([LicenseOrder])], + providers: [LicensesRepositoryService], + exports: [LicensesRepositoryService], +}) +export class LicensesRepositoryModule {} diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts new file mode 100644 index 0000000..5e140a2 --- /dev/null +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { LicenseOrder } from './entity/license.entity'; +import { + LICENSE_STATUS_ISSUE_REQUESTING, + LICENSE_STATUS_ISSUED, +} from '../../constants'; + +export class PoNumberAlreadyExistError extends Error {} + +@Injectable() +export class LicensesRepositoryService { + constructor(private dataSource: DataSource) {} + + async order( + poNumber: string, + fromAccountId: number, + toAccountId: number, + quantity: number, + ): Promise { + const licenseOrder = new LicenseOrder(); + licenseOrder.po_number = poNumber; + licenseOrder.from_account_id = fromAccountId; + licenseOrder.to_account_id = toAccountId; + licenseOrder.quantity = quantity; + licenseOrder.status = LICENSE_STATUS_ISSUE_REQUESTING; + + // ライセンス注文テーブルに登録する + const createdEntity = await this.dataSource.transaction( + async (entityManager) => { + //poNumberの重複チェックを行う + const isPoNumberDuplicated = await entityManager + .getRepository(LicenseOrder) + .findOne({ + where: [ + { + po_number: poNumber, + from_account_id: fromAccountId, + status: LICENSE_STATUS_ISSUED, + }, + { + po_number: poNumber, + from_account_id: fromAccountId, + status: LICENSE_STATUS_ISSUE_REQUESTING, + }, + ], + }); + // 重複があった場合はエラーを返却する + if (isPoNumberDuplicated) { + throw new PoNumberAlreadyExistError(); + } + + const repo = entityManager.getRepository(LicenseOrder); + const newLicenseOrder = repo.create(licenseOrder); + const persisted = await repo.save(newLicenseOrder); + return persisted; + }, + ); + return createdEntity; + } +}