diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index caf1c85..4e50b27 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -279,6 +279,42 @@ "security": [{ "bearer": [] }] } }, + "/accounts/authors": { + "get": { + "operationId": "getAuthors", + "summary": "", + "description": "ログインしているユーザーのアカウント配下のAuthor一覧を取得します", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetAuthorsResponse" } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["accounts"], + "security": [{ "bearer": [] }] + } + }, "/accounts/typists": { "get": { "operationId": "getTypists", @@ -2848,6 +2884,98 @@ "security": [{ "bearer": [] }] } }, + "/workflows": { + "get": { + "operationId": "getWorkflows", + "summary": "", + "description": "アカウント内のワークフローの一覧を取得します", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetWorkflowsResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["workflows"], + "security": [{ "bearer": [] }] + }, + "post": { + "operationId": "createWorkflows", + "summary": "", + "description": "アカウント内にワークフローを新規作成します", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWorkflowsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWorkflowsResponse" + } + } + } + }, + "400": { + "description": "パラメータ不正エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["workflows"], + "security": [{ "bearer": [] }] + } + }, "/notification/register": { "post": { "operationId": "register", @@ -3036,6 +3164,24 @@ "properties": { "account": { "$ref": "#/components/schemas/Account" } }, "required": ["account"] }, + "Author": { + "type": "object", + "properties": { + "id": { "type": "number", "description": "Authorユーザーの内部ID" }, + "authorId": { "type": "string", "description": "AuthorID" } + }, + "required": ["id", "authorId"] + }, + "GetAuthorsResponse": { + "type": "object", + "properties": { + "authors": { + "type": "array", + "items": { "$ref": "#/components/schemas/Author" } + } + }, + "required": ["authors"] + }, "Typist": { "type": "object", "properties": { @@ -4035,6 +4181,92 @@ }, "required": ["templates"] }, + "WorkflowWorktype": { + "type": "object", + "properties": { + "id": { "type": "number", "description": "Worktypeの内部ID" }, + "worktypeId": { "type": "string", "description": "WorktypeID" } + }, + "required": ["id", "worktypeId"] + }, + "WorkflowTemplate": { + "type": "object", + "properties": { + "id": { "type": "number", "description": "テンプレートの内部ID" }, + "fileName": { + "type": "string", + "description": "テンプレートのファイル名" + } + }, + "required": ["id", "fileName"] + }, + "Workflow": { + "type": "object", + "properties": { + "id": { "type": "number", "description": "ワークフローの内部ID" }, + "author": { + "description": "Author情報", + "allOf": [{ "$ref": "#/components/schemas/Author" }] + }, + "worktype": { + "description": "Worktype情報", + "allOf": [{ "$ref": "#/components/schemas/WorkflowWorktype" }] + }, + "template": { + "description": "テンプレート情報", + "allOf": [{ "$ref": "#/components/schemas/WorkflowTemplate" }] + }, + "typists": { + "description": "ルーティング候補のタイピストユーザー/タイピストグループ", + "type": "array", + "items": { "$ref": "#/components/schemas/Assignee" } + } + }, + "required": ["id", "author", "typists"] + }, + "GetWorkflowsResponse": { + "type": "object", + "properties": { + "workflows": { + "description": "ワークフローの一覧", + "type": "array", + "items": { "$ref": "#/components/schemas/Workflow" } + } + }, + "required": ["workflows"] + }, + "WorkflowTypist": { + "type": "object", + "properties": { + "typistId": { + "type": "number", + "description": "タイピストユーザーの内部ID" + }, + "typistGroupId": { + "type": "number", + "description": "タイピストグループの内部ID" + } + } + }, + "CreateWorkflowsRequest": { + "type": "object", + "properties": { + "authorId": { "type": "number", "description": "Authornの内部ID" }, + "worktypeId": { "type": "number", "description": "Worktypeの内部ID" }, + "templateId": { + "type": "number", + "description": "テンプレートの内部ID" + }, + "typists": { + "description": "ルーティング候補のタイピストユーザー/タイピストグループ", + "minItems": 1, + "type": "array", + "items": { "$ref": "#/components/schemas/WorkflowTypist" } + } + }, + "required": ["authorId", "typists"] + }, + "CreateWorkflowsResponse": { "type": "object", "properties": {} }, "RegisterRequest": { "type": "object", "properties": { diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index 49b1d97..c99f882 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -44,6 +44,9 @@ import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_ import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module'; import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module'; import { TemplatesService } from './features/templates/templates.service'; +import { WorkflowsModule } from './features/workflows/workflows.module'; +import { WorkflowsController } from './features/workflows/workflows.controller'; +import { WorkflowsService } from './features/workflows/workflows.service'; import { validate } from './common/validators/env.validator'; @Module({ @@ -73,6 +76,7 @@ import { validate } from './common/validators/env.validator'; TemplatesModule, SendGridModule, LicensesModule, + WorkflowsModule, AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, @@ -113,6 +117,7 @@ import { validate } from './common/validators/env.validator'; UsersController, LicensesController, TemplatesController, + WorkflowsController, ], providers: [ AuthService, @@ -123,6 +128,7 @@ import { validate } from './common/validators/env.validator'; TasksService, LicensesService, TemplatesService, + WorkflowsService, ], }) export class AppModule { diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index b3a2eef..05f8e77 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -32,6 +32,8 @@ import { LicensesService } from '../../features/licenses/licenses.service'; import { TasksService } from '../../features/tasks/tasks.service'; import { TemplatesService } from '../../features/templates/templates.service'; import { TemplatesModule } from '../../features/templates/templates.module'; +import { WorkflowsService } from '../../features/workflows/workflows.service'; +import { WorkflowsModule } from '../../features/workflows/workflows.module'; export const makeTestingModule = async ( datasource: DataSource, @@ -53,6 +55,7 @@ export const makeTestingModule = async ( SendGridModule, LicensesModule, TemplatesModule, + WorkflowsModule, AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, @@ -78,6 +81,7 @@ export const makeTestingModule = async ( TasksService, LicensesService, TemplatesService, + WorkflowsService, ], }) .useMocker(async (token) => { diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index c33085d..03e4cd9 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -62,6 +62,7 @@ import { UpdateAccountInfoResponse, DeleteAccountRequest, DeleteAccountResponse, + GetAuthorsResponse, } from './types/types'; import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants'; import { AuthGuard } from '../../common/guards/auth/authguards'; @@ -201,6 +202,40 @@ export class AccountsController { return accountInfo; } + @ApiResponse({ + status: HttpStatus.OK, + type: GetAuthorsResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'getAuthors', + description: + 'ログインしているユーザーのアカウント配下のAuthor一覧を取得します', + }) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) + @Get('authors') + async getAuthors(@Req() req: Request): Promise { + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; + const context = makeContext(userId); + + console.log(context.trackingId); + + return { authors: [] }; + } + @ApiResponse({ status: HttpStatus.OK, type: GetTypistsResponse, diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 62b45a7..8a8afb6 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -134,6 +134,18 @@ export class GetMyAccountResponse { account: Account; } +export class Author { + @ApiProperty({ description: 'Authorユーザーの内部ID' }) + id: number; + @ApiProperty({ description: 'AuthorID' }) + authorId: string; +} + +export class GetAuthorsResponse { + @ApiProperty({ type: [Author] }) + authors: Author[]; +} + export class Typist { @ApiProperty({ description: 'TypistのユーザーID', diff --git a/dictation_server/src/features/workflows/types/types.ts b/dictation_server/src/features/workflows/types/types.ts new file mode 100644 index 0000000..3bd82f9 --- /dev/null +++ b/dictation_server/src/features/workflows/types/types.ts @@ -0,0 +1,81 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Assignee } from '../../tasks/types/types'; +import { ArrayMinSize, IsArray, IsInt, IsOptional, Min } from 'class-validator'; +import { Type } from 'class-transformer'; +import { Author } from '../../accounts/types/types'; + +export class WorkflowWorktype { + @ApiProperty({ description: 'Worktypeの内部ID' }) + id: number; + @ApiProperty({ description: 'WorktypeID' }) + worktypeId: string; +} + +export class WorkflowTemplate { + @ApiProperty({ description: 'テンプレートの内部ID' }) + id: number; + @ApiProperty({ description: 'テンプレートのファイル名' }) + fileName: string; +} + +export class Workflow { + @ApiProperty({ description: 'ワークフローの内部ID' }) + id: number; + @ApiProperty({ description: 'Author情報' }) + author: Author; + @ApiProperty({ description: 'Worktype情報', required: false }) + worktype?: WorkflowWorktype; + @ApiProperty({ description: 'テンプレート情報', required: false }) + template?: WorkflowTemplate; + @ApiProperty({ + description: 'ルーティング候補のタイピストユーザー/タイピストグループ', + type: [Assignee], + }) + typists: Assignee[]; +} + +export class GetWorkflowsResponse { + @ApiProperty({ + description: 'ワークフローの一覧', + type: [Workflow], + }) + workflows: Workflow[]; +} + +export class WorkflowTypist { + @ApiProperty({ description: 'タイピストユーザーの内部ID', required: false }) + typistId?: number | undefined; + @ApiProperty({ description: 'タイピストグループの内部ID', required: false }) + typistGroupId?: number | undefined; +} + +export class CreateWorkflowsRequest { + @ApiProperty({ description: 'Authornの内部ID' }) + @Type(() => Number) + @IsInt() + @Min(0) + authorId: number; + @ApiProperty({ description: 'Worktypeの内部ID', required: false }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(0) + worktypeId?: number | undefined; + @ApiProperty({ description: 'テンプレートの内部ID', required: false }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(0) + templateId?: number | undefined; + @ApiProperty({ + description: 'ルーティング候補のタイピストユーザー/タイピストグループ', + type: [WorkflowTypist], + minItems: 1, + }) + @Type(() => WorkflowTypist) + @IsArray() + @ArrayMinSize(1) + typists: WorkflowTypist[]; +} + +export class CreateWorkflowsResponse {} diff --git a/dictation_server/src/features/workflows/workflows.controller.spec.ts b/dictation_server/src/features/workflows/workflows.controller.spec.ts new file mode 100644 index 0000000..afab754 --- /dev/null +++ b/dictation_server/src/features/workflows/workflows.controller.spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigModule } from '@nestjs/config'; +import { WorkflowsService } from './workflows.service'; +import { WorkflowsController } from './workflows.controller'; + +describe('WorkflowsController', () => { + let controller: WorkflowsController; + const mockTemplatesService = {}; + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + envFilePath: ['.env.local', '.env'], + isGlobal: true, + }), + ], + controllers: [WorkflowsController], + providers: [WorkflowsService], + }) + .overrideProvider(WorkflowsService) + .useValue(mockTemplatesService) + .compile(); + + controller = module.get(WorkflowsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/dictation_server/src/features/workflows/workflows.controller.ts b/dictation_server/src/features/workflows/workflows.controller.ts new file mode 100644 index 0000000..9b256a7 --- /dev/null +++ b/dictation_server/src/features/workflows/workflows.controller.ts @@ -0,0 +1,112 @@ +import { + Body, + Controller, + Get, + HttpStatus, + Post, + Req, + UseGuards, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import jwt from 'jsonwebtoken'; +import { AccessToken } from '../../common/token'; +import { ErrorResponse } from '../../common/error/types/types'; +import { + GetWorkflowsResponse, + CreateWorkflowsRequest, + CreateWorkflowsResponse, +} from './types/types'; +import { AuthGuard } from '../../common/guards/auth/authguards'; +import { RoleGuard } from '../../common/guards/role/roleguards'; +import { ADMIN_ROLES } from '../../constants'; +import { retrieveAuthorizationToken } from '../../common/http/helper'; +import { Request } from 'express'; +import { makeContext } from '../../common/log'; +import { WorkflowsService } from './workflows.service'; + +@ApiTags('workflows') +@Controller('workflows') +export class WorkflowsController { + constructor(private readonly workflowsService: WorkflowsService) {} + + @ApiResponse({ + status: HttpStatus.OK, + type: GetWorkflowsResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'getWorkflows', + description: 'アカウント内のワークフローの一覧を取得します', + }) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) + @Get() + async getWorkflows(@Req() req: Request): Promise { + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; + + const context = makeContext(userId); + console.log(context.trackingId); + + return { workflows: [] }; + } + + @ApiResponse({ + status: HttpStatus.OK, + type: CreateWorkflowsResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'パラメータ不正エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'createWorkflows', + description: 'アカウント内にワークフローを新規作成します', + }) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) + @Post() + async createWorkflows( + @Req() req: Request, + @Body() body: CreateWorkflowsRequest, + ): Promise { + const { authorId } = body; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; + + const context = makeContext(userId); + console.log(context.trackingId); + console.log(authorId); + + return {}; + } +} diff --git a/dictation_server/src/features/workflows/workflows.module.ts b/dictation_server/src/features/workflows/workflows.module.ts new file mode 100644 index 0000000..f0547ff --- /dev/null +++ b/dictation_server/src/features/workflows/workflows.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; +import { WorkflowsController } from './workflows.controller'; +import { WorkflowsService } from './workflows.service'; + +@Module({ + imports: [UsersRepositoryModule], + providers: [WorkflowsService], + controllers: [WorkflowsController], +}) +export class WorkflowsModule {} diff --git a/dictation_server/src/features/workflows/workflows.service.ts b/dictation_server/src/features/workflows/workflows.service.ts new file mode 100644 index 0000000..9b32526 --- /dev/null +++ b/dictation_server/src/features/workflows/workflows.service.ts @@ -0,0 +1,7 @@ +import { Injectable, Logger } from '@nestjs/common'; + +@Injectable() +export class WorkflowsService { + private readonly logger = new Logger(WorkflowsService.name); + constructor() {} +}