From 34cf80d636fd219c54cb79bc524a26935624a707 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Fri, 6 Oct 2023 05:17:43 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20469:=20API-IF=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2806: API-IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2806) 以下APIのIFを実装しました。 ・アカウント情報取得(未認証時最小アクセス)API ・利用規約情報取得API ・同意済バージョン更新API またトークン生成APIのIFにコメントを追加しました。 ## レビューポイント なし ## UIの変更 なし ## 動作確認状況 swaggerUIで動作確認 ## 補足 なし --- dictation_server/src/api/odms/openapi.json | 154 +++++++++++++++++- dictation_server/src/app.module.ts | 2 + .../features/accounts/accounts.controller.ts | 30 ++++ .../src/features/accounts/types/types.ts | 10 ++ .../src/features/auth/auth.controller.ts | 2 +- .../features/terms/terms.controller.spec.ts | 18 ++ .../src/features/terms/terms.controller.ts | 40 +++++ .../src/features/terms/terms.module.ts | 9 + .../src/features/terms/terms.service.spec.ts | 18 ++ .../src/features/terms/terms.service.ts | 4 + .../src/features/terms/types/types.ts | 12 ++ .../src/features/users/types/types.ts | 11 ++ .../src/features/users/users.controller.ts | 33 ++++ 13 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 dictation_server/src/features/terms/terms.controller.spec.ts create mode 100644 dictation_server/src/features/terms/terms.controller.ts create mode 100644 dictation_server/src/features/terms/terms.module.ts create mode 100644 dictation_server/src/features/terms/terms.service.spec.ts create mode 100644 dictation_server/src/features/terms/terms.service.ts create mode 100644 dictation_server/src/features/terms/types/types.ts diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index ace1893..3b567df 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -33,7 +33,7 @@ } }, "401": { - "description": "認証エラー", + "description": "認証エラー/同意済み利用規約が最新でない場合", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } @@ -1262,6 +1262,52 @@ "security": [{ "bearer": [] }] } }, + "/accounts/minimal-access": { + "post": { + "operationId": "getAccountInfoMinimalAccess", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAccountInfoMinimalAccessRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAccountInfoMinimalAccessResponse" + } + } + } + }, + "400": { + "description": "対象のユーザーIDが存在しない場合", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["accounts"] + } + }, "/users/confirm": { "post": { "operationId": "confirmUser", @@ -1728,6 +1774,53 @@ "security": [{ "bearer": [] }] } }, + "/users/accepted-version": { + "post": { + "operationId": "updateAcceptedVersion", + "summary": "", + "description": "利用規約同意バージョンを更新", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAcceptedVersionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAcceptedVersionResponse" + } + } + } + }, + "400": { + "description": "パラメータ不正/対象のユーザidが存在しない場合", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["users"] + } + }, "/files/audio/upload-finished": { "post": { "operationId": "uploadFinished", @@ -3134,6 +3227,34 @@ "tags": ["notification"], "security": [{ "bearer": [] }] } + }, + "/terms": { + "post": { + "operationId": "getTermsInfo", + "summary": "", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTermsInfoResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["terms"] + } } }, "info": { @@ -3705,6 +3826,18 @@ }, "required": ["accountId"] }, + "GetAccountInfoMinimalAccessRequest": { + "type": "object", + "properties": { + "idToken": { "type": "string", "description": "idトークン" } + }, + "required": ["idToken"] + }, + "GetAccountInfoMinimalAccessResponse": { + "type": "object", + "properties": { "tier": { "type": "number", "description": "階層" } }, + "required": ["tier"] + }, "ConfirmRequest": { "type": "object", "properties": { "token": { "type": "string" } }, @@ -3925,6 +4058,22 @@ "required": ["userId"] }, "DeallocateLicenseResponse": { "type": "object", "properties": {} }, + "UpdateAcceptedVersionRequest": { + "type": "object", + "properties": { + "idToken": { "type": "string", "description": "IDトークン" }, + "acceptedEULAVersion": { + "type": "string", + "description": "更新バージョン(EULA)" + }, + "acceptedDPAVersion": { + "type": "string", + "description": "更新バージョン(DPA)" + } + }, + "required": ["idToken", "acceptedEULAVersion"] + }, + "UpdateAcceptedVersionResponse": { "type": "object", "properties": {} }, "AudioOptionItem": { "type": "object", "properties": { @@ -4406,7 +4555,8 @@ }, "required": ["pns", "handler"] }, - "RegisterResponse": { "type": "object", "properties": {} } + "RegisterResponse": { "type": "object", "properties": {} }, + "GetTermsInfoResponse": { "type": "object", "properties": {} } } } } diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index ee977ec..f8c0664 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -49,6 +49,7 @@ import { WorkflowsController } from './features/workflows/workflows.controller'; import { WorkflowsService } from './features/workflows/workflows.service'; import { validate } from './common/validators/env.validator'; import { WorkflowsRepositoryModule } from './repositories/workflows/workflows.repository.module'; +import { TermsModule } from './features/terms/terms.module'; @Module({ imports: [ @@ -108,6 +109,7 @@ import { WorkflowsRepositoryModule } from './repositories/workflows/workflows.re AuthGuardsModule, SortCriteriaRepositoryModule, WorktypesRepositoryModule, + TermsModule, ], controllers: [ HealthController, diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 21becf3..e6039e8 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -63,6 +63,8 @@ import { DeleteAccountRequest, DeleteAccountResponse, GetAuthorsResponse, + GetAccountInfoMinimalAccessRequest, + GetAccountInfoMinimalAccessResponse, } from './types/types'; import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants'; import { AuthGuard } from '../../common/guards/auth/authguards'; @@ -1091,4 +1093,32 @@ export class AccountsController { await this.accountService.deleteAccountAndData(context, userId, accountId); return; } + + @Post('/minimal-access') + @ApiResponse({ + status: HttpStatus.OK, + type: GetAccountInfoMinimalAccessResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: '対象のユーザーIDが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'getAccountInfoMinimalAccess' }) + async getAccountInfoMinimalAccess( + @Body() body: GetAccountInfoMinimalAccessRequest, + ): Promise { + const context = makeContext(uuidv4()); + + // TODO 仮実装。API実装タスクで本実装する。 + // const idToken = await this.authService.getVerifiedIdToken(body.idToken); + // await this.accountService.getAccountInfoMinimalAccess(context, idToken); + return; + } } diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 8a8afb6..f5aae67 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -577,3 +577,13 @@ export class DeleteAccountRequest { } export class DeleteAccountResponse {} + +export class GetAccountInfoMinimalAccessRequest { + @ApiProperty({ description: 'idトークン' }) + idToken: string; +} + +export class GetAccountInfoMinimalAccessResponse { + @ApiProperty({ description: '階層' }) + tier: number; +} diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index 83b75de..029e60e 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -41,7 +41,7 @@ export class AuthController { }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, - description: '認証エラー', + description: '認証エラー/同意済み利用規約が最新でない場合', type: ErrorResponse, }) @ApiResponse({ diff --git a/dictation_server/src/features/terms/terms.controller.spec.ts b/dictation_server/src/features/terms/terms.controller.spec.ts new file mode 100644 index 0000000..d15564a --- /dev/null +++ b/dictation_server/src/features/terms/terms.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TermsController } from './terms.controller'; + +describe('TermsController', () => { + let controller: TermsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TermsController], + }).compile(); + + controller = module.get(TermsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/dictation_server/src/features/terms/terms.controller.ts b/dictation_server/src/features/terms/terms.controller.ts new file mode 100644 index 0000000..f28d5f8 --- /dev/null +++ b/dictation_server/src/features/terms/terms.controller.ts @@ -0,0 +1,40 @@ +import { Controller, HttpStatus, Post } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { TermsService } from '../terms/terms.service'; +import { ErrorResponse } from '../../common/error/types/types'; +import { makeContext } from '../../common/log'; +import { v4 as uuidv4 } from 'uuid'; +import { GetTermsInfoResponse, TermInfo } from './types/types'; + +@ApiTags('terms') +@Controller('terms') +export class TermsController { + constructor( + private readonly termsService: TermsService, //private readonly cryptoService: CryptoService, + ) {} + + @Post() + @ApiResponse({ + status: HttpStatus.OK, + type: GetTermsInfoResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'getTermsInfo' }) + async getTermsInfo(): Promise { + const context = makeContext(uuidv4()); + + // TODO 仮実装。API実装タスクで本実装する。 + // const termInfo = await this.termsService.getTermsInfo(context); + const termsInfo = [ + { documentType: 'EULA', version: '1.0' }, + { documentType: 'DPA', version: '1.1' }, + ] as TermInfo[]; + + return { termsInfo }; + } +} diff --git a/dictation_server/src/features/terms/terms.module.ts b/dictation_server/src/features/terms/terms.module.ts new file mode 100644 index 0000000..e314518 --- /dev/null +++ b/dictation_server/src/features/terms/terms.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TermsController } from './terms.controller'; +import { TermsService } from './terms.service'; + +@Module({ + controllers: [TermsController], + providers: [TermsService] +}) +export class TermsModule {} diff --git a/dictation_server/src/features/terms/terms.service.spec.ts b/dictation_server/src/features/terms/terms.service.spec.ts new file mode 100644 index 0000000..6e8839b --- /dev/null +++ b/dictation_server/src/features/terms/terms.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TermsService } from './terms.service'; + +describe('TermsService', () => { + let service: TermsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TermsService], + }).compile(); + + service = module.get(TermsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/dictation_server/src/features/terms/terms.service.ts b/dictation_server/src/features/terms/terms.service.ts new file mode 100644 index 0000000..51ba395 --- /dev/null +++ b/dictation_server/src/features/terms/terms.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TermsService {} diff --git a/dictation_server/src/features/terms/types/types.ts b/dictation_server/src/features/terms/types/types.ts new file mode 100644 index 0000000..e832cc6 --- /dev/null +++ b/dictation_server/src/features/terms/types/types.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetTermsInfoResponse { + termsInfo: TermInfo[]; +} + +export class TermInfo { + @ApiProperty({ description: '利用規約種別' }) + documentType: string; + @ApiProperty({ description: 'バージョン' }) + version: string; +} diff --git a/dictation_server/src/features/users/types/types.ts b/dictation_server/src/features/users/types/types.ts index c3df59c..991ec09 100644 --- a/dictation_server/src/features/users/types/types.ts +++ b/dictation_server/src/features/users/types/types.ts @@ -255,3 +255,14 @@ export class DeallocateLicenseRequest { } export class DeallocateLicenseResponse {} + +export class UpdateAcceptedVersionRequest { + @ApiProperty({ description: 'IDトークン' }) + idToken: string; + @ApiProperty({ description: '更新バージョン(EULA)' }) + acceptedEULAVersion: string; + @ApiProperty({ description: '更新バージョン(DPA)', required: false }) + acceptedDPAVersion?: string | undefined; +} + +export class UpdateAcceptedVersionResponse {} diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index 11ff39c..81306ec 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -37,6 +37,8 @@ import { AllocateLicenseRequest, DeallocateLicenseResponse, DeallocateLicenseRequest, + UpdateAcceptedVersionRequest, + UpdateAcceptedVersionResponse, } from './types/types'; import { UsersService } from './users.service'; import jwt from 'jsonwebtoken'; @@ -469,4 +471,35 @@ export class UsersController { await this.usersService.deallocateLicense(context, body.userId); return {}; } + + @ApiResponse({ + status: HttpStatus.OK, + type: UpdateAcceptedVersionResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'パラメータ不正/対象のユーザidが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'updateAcceptedVersion', + description: '利用規約同意バージョンを更新', + }) + @Post('/accepted-version') + async updateAcceptedVersion( + @Body() body: UpdateAcceptedVersionRequest, + ): Promise { + const context = makeContext(uuidv4()); + + // TODO 仮実装。API実装タスクで本実装する。 + // const idToken = await this.authService.getVerifiedIdToken(body.idToken); + // await this.usersService.updateAcceptedVersion(context, idToken); + return {}; + } }