From 60cafa136f8a3da77726cd56c390866a8ec2d761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Thu, 23 May 2024 15:48:34 +0200 Subject: [PATCH 1/5] impl --- .../external-tool-bulk-create.params.ts | 11 ++ .../controller/dto/request/index.ts | 1 + .../external-tool-import-result-response.ts | 30 ++++ .../controller/tool.controller.ts | 26 +++ .../mapper/external-tool-request.mapper.ts | 9 + .../mapper/external-tool-response.mapper.ts | 16 ++ .../uc/dto/external-tool-import-result.ts | 19 +++ .../external-tool/uc/external-tool.uc.spec.ts | 154 ++++++++++++++++++ .../tool/external-tool/uc/external-tool.uc.ts | 39 +++++ 9 files changed, 305 insertions(+) create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-bulk-create.params.ts create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts create mode 100644 apps/server/src/modules/tool/external-tool/uc/dto/external-tool-import-result.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-bulk-create.params.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-bulk-create.params.ts new file mode 100644 index 00000000000..d12b4f144ef --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-bulk-create.params.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { ValidateNested } from 'class-validator'; +import { ExternalToolCreateParams } from './external-tool-create.params'; + +export class ExternalToolBulkCreateParams { + @ValidateNested() + @Type(() => ExternalToolCreateParams) + @ApiProperty({ type: [ExternalToolCreateParams], description: 'List of external tools' }) + data!: ExternalToolCreateParams[]; +} diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts index 994c473e233..fc62b2b4cc4 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts @@ -10,3 +10,4 @@ export * from './context-ref.params'; export * from './school-external-tool-id.params'; export * from './context-external-tool-id.params'; export { ExternalToolMediumParams } from './external-tool-medium.params'; +export { ExternalToolBulkCreateParams } from './external-tool-bulk-create.params'; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts new file mode 100644 index 00000000000..435530b8d48 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ExternalToolImportResultResponse { + toolName: string; + + mediumId?: string; + + mediumSourceId?: string; + + toolId?: string; + + error?: string; + + constructor(props: ExternalToolImportResultResponse) { + this.toolName = props.toolName; + this.mediumId = props.mediumId; + this.mediumSourceId = props.mediumSourceId; + this.toolId = props.toolId; + this.error = props.error; + } +} + +export class ExternalToolImportResultListResponse { + @ApiProperty({ type: [ExternalToolImportResultResponse] }) + results: ExternalToolImportResultResponse[]; + + constructor(props: ExternalToolImportResultListResponse) { + this.results = props.results; + } +} diff --git a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts index febf1d459f3..1697176b095 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts @@ -13,6 +13,7 @@ import { StreamableFile, } from '@nestjs/common'; import { + ApiBadRequestResponse, ApiCreatedResponse, ApiForbiddenResponse, ApiFoundResponse, @@ -37,7 +38,9 @@ import { ExternalToolLogo } from '../domain/external-tool-logo'; import { ExternalToolMetadataMapper, ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; import { ExternalToolLogoService } from '../service'; import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; +import { ExternalToolImportResult } from '../uc/dto/external-tool-import-result'; import { + ExternalToolBulkCreateParams, ExternalToolCreateParams, ExternalToolIdParams, ExternalToolMetadataResponse, @@ -47,6 +50,7 @@ import { ExternalToolUpdateParams, SortExternalToolParams, } from './dto'; +import { ExternalToolImportResultListResponse } from './dto/response/external-tool-import-result-response'; @ApiTags('Tool') @Authenticate('jwt') @@ -81,6 +85,28 @@ export class ToolController { return mapped; } + @Post('/import') + @ApiCreatedResponse({ description: 'The Tool has been successfully created.', type: ExternalToolResponse }) + @ApiForbiddenResponse({ description: 'User is not allowed to access this resource.' }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + @ApiBadRequestResponse({ description: 'Request data has invalid format.' }) + @ApiOperation({ summary: 'Creates multiple ExternalTools at the same time.' }) + async importExternalTools( + @CurrentUser() currentUser: ICurrentUser, + @Body() externalToolBulkParams: ExternalToolBulkCreateParams + ): Promise { + const externalTools: ExternalToolCreate[] = this.externalToolDOMapper.mapBulkCreateRequest(externalToolBulkParams); + + const results: ExternalToolImportResult[] = await this.externalToolUc.importExternalTools( + currentUser.userId, + externalTools + ); + + const response: ExternalToolImportResultListResponse = ExternalToolResponseMapper.mapToImportResponse(results); + + return response; + } + @Get() @ApiFoundResponse({ description: 'Tools has been found.', type: ExternalToolSearchListResponse }) @ApiUnauthorizedResponse() diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts index 8d3cfe15f9a..16a3b1178ff 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts @@ -22,6 +22,7 @@ import { Oauth2ToolConfigUpdateParams, SortExternalToolParams, } from '../controller/dto'; +import { ExternalToolBulkCreateParams } from '../controller/dto/request/external-tool-bulk-create.params'; import { ExternalTool } from '../domain'; import { BasicToolConfigDto, @@ -119,6 +120,14 @@ export class ExternalToolRequestMapper { }; } + public mapBulkCreateRequest(externalToolCreateParams: ExternalToolBulkCreateParams): ExternalToolCreate[] { + const toolList: ExternalToolCreate[] = externalToolCreateParams.data.map( + (createParams: ExternalToolCreateParams): ExternalToolCreate => this.mapCreateRequest(createParams) + ); + + return toolList; + } + private mapRequestToExternalToolMedium( externalToolMediumParams: ExternalToolMediumParams | undefined ): ExternalToolMediumDto | undefined { diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts index 41e239c434d..3be76b9dcc4 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts @@ -16,7 +16,12 @@ import { Lti11ToolConfigResponse, Oauth2ToolConfigResponse, } from '../controller/dto'; +import { + ExternalToolImportResultListResponse, + ExternalToolImportResultResponse, +} from '../controller/dto/response/external-tool-import-result-response'; import { BasicToolConfig, ExternalTool, ExternalToolMedium, Lti11ToolConfig, Oauth2ToolConfig } from '../domain'; +import { ExternalToolImportResult } from '../uc/dto/external-tool-import-result'; const scopeMapping: Record = { [CustomParameterScope.GLOBAL]: CustomParameterScopeTypeParams.GLOBAL, @@ -110,4 +115,15 @@ export class ExternalToolResponseMapper { }; }); } + + static mapToImportResponse(results: ExternalToolImportResult[]): ExternalToolImportResultListResponse { + const response: ExternalToolImportResultListResponse = new ExternalToolImportResultListResponse({ + results: results.map( + (result: ExternalToolImportResult): ExternalToolImportResultResponse => + new ExternalToolImportResultResponse(result) + ), + }); + + return response; + } } diff --git a/apps/server/src/modules/tool/external-tool/uc/dto/external-tool-import-result.ts b/apps/server/src/modules/tool/external-tool/uc/dto/external-tool-import-result.ts new file mode 100644 index 00000000000..31aaf986d2e --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/uc/dto/external-tool-import-result.ts @@ -0,0 +1,19 @@ +export class ExternalToolImportResult { + toolName: string; + + mediumId?: string; + + mediumSourceId?: string; + + toolId?: string; + + error?: string; + + constructor(props: ExternalToolImportResult) { + this.toolName = props.toolName; + this.mediumId = props.mediumId; + this.mediumSourceId = props.mediumSourceId; + this.toolId = props.toolId; + this.error = props.error; + } +} diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts index 1bb76c22eb6..dce4668fd72 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts @@ -38,6 +38,7 @@ import { oauth2ToolConfigFactory, } from '../testing'; import { ExternalToolUpdate } from './dto'; +import { ExternalToolImportResult } from './dto/external-tool-import-result'; import { ExternalToolUc } from './external-tool.uc'; describe(ExternalToolUc.name, () => { @@ -273,6 +274,159 @@ describe(ExternalToolUc.name, () => { }); }); + describe('importExternalTools', () => { + describe('when importing multiple external tools', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const externalTool1 = externalToolFactory.build({ + name: 'tool1', + medium: { + mediumId: 'medium1', + mediaSourceId: 'mediumSource1', + }, + }); + const externalTool2 = externalToolFactory.build({ + name: 'tool2', + medium: { + mediumId: 'medium2', + mediaSourceId: 'mediumSource2', + }, + }); + + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + logoService.fetchLogo.mockResolvedValueOnce(undefined); + externalToolService.createExternalTool.mockResolvedValueOnce(externalTool1); + externalToolService.createExternalTool.mockResolvedValueOnce(externalTool2); + + return { + user, + externalTool1, + externalTool2, + }; + }; + + it('should check the users permission', async () => { + const { user, externalTool1, externalTool2 } = setup(); + + await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]); + + expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.TOOL_ADMIN]); + }); + + it('should validate each tool', async () => { + const { user, externalTool1, externalTool2 } = setup(); + + await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]); + + expect(toolValidationService.validateCreate).toHaveBeenNthCalledWith( + 1, + new ExternalTool({ + ...externalTool1.getProps(), + id: expect.any(String), + }) + ); + expect(toolValidationService.validateCreate).toHaveBeenNthCalledWith( + 2, + new ExternalTool({ + ...externalTool2.getProps(), + id: expect.any(String), + }) + ); + }); + + it('should save each tool', async () => { + const { user, externalTool1, externalTool2 } = setup(); + + await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]); + + expect(externalToolService.createExternalTool).toHaveBeenNthCalledWith( + 1, + new ExternalTool({ + ...externalTool1.getProps(), + id: expect.any(String), + }) + ); + expect(externalToolService.createExternalTool).toHaveBeenNthCalledWith( + 2, + new ExternalTool({ + ...externalTool2.getProps(), + id: expect.any(String), + }) + ); + }); + + it('should return a result report', async () => { + const { user, externalTool1, externalTool2 } = setup(); + + const result = await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]); + + expect(result).toEqual([ + { + toolName: externalTool1.name, + mediumId: externalTool1.medium?.mediumId, + mediumSourceId: externalTool1.medium?.mediaSourceId, + toolId: externalTool1.id, + }, + { + toolName: externalTool2.name, + mediumId: externalTool2.medium?.mediumId, + mediumSourceId: externalTool2.medium?.mediaSourceId, + toolId: externalTool2.id, + }, + ]); + }); + }); + + describe('when an external tools fails the validation', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const externalTool1 = externalToolFactory.build({ + name: 'tool1', + medium: { + mediumId: 'medium1', + mediaSourceId: 'mediumSource1', + }, + }); + const externalTool2 = externalToolFactory.build({ + name: 'tool1', + medium: { + mediumId: 'medium2', + mediaSourceId: 'mediumSource2', + }, + }); + const error = new Error('same tool name'); + + authorizationService.getUserWithPermissions.mockResolvedValue(user); + logoService.fetchLogo.mockResolvedValueOnce(undefined); + toolValidationService.validateCreate.mockResolvedValueOnce(); + externalToolService.createExternalTool.mockResolvedValueOnce(externalTool1); + toolValidationService.validateCreate.mockRejectedValueOnce(error); + + return { + user, + externalTool1, + externalTool2, + error, + }; + }; + + it('should check the users permission', async () => { + const { user, externalTool1, externalTool2, error } = setup(); + + const results = await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]); + + expect(results).toHaveLength(2); + expect(results[1]).toEqual({ + toolName: externalTool2.name, + mediumId: externalTool2.medium?.mediumId, + mediumSourceId: externalTool2.medium?.mediaSourceId, + toolId: undefined, + error: error.message, + }); + }); + }); + }); + describe('findExternalTool', () => { describe('Authorization', () => { it('should call getUserWithPermissions', async () => { diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts index c85bdce0c81..da7e9c8bfbd 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts @@ -19,6 +19,7 @@ import { ExternalToolValidationService, } from '../service'; import { ExternalToolCreate, ExternalToolUpdate } from './dto'; +import { ExternalToolImportResult } from './dto/external-tool-import-result'; @Injectable() export class ExternalToolUc { @@ -36,6 +37,44 @@ export class ExternalToolUc { public async createExternalTool(userId: EntityId, externalToolCreate: ExternalToolCreate): Promise { await this.ensurePermission(userId, Permission.TOOL_ADMIN); + const tool: ExternalTool = await this.validateAndSaveExternalTool(externalToolCreate); + + return tool; + } + + public async importExternalTools( + userId: EntityId, + externalTools: ExternalToolCreate[] + ): Promise { + await this.ensurePermission(userId, Permission.TOOL_ADMIN); + + const results: ExternalToolImportResult[] = []; + + for (const externalTool of externalTools) { + const result: ExternalToolImportResult = new ExternalToolImportResult({ + toolName: externalTool.name, + mediumId: externalTool.medium?.mediumId, + mediumSourceId: externalTool.medium?.mediaSourceId, + }); + + try { + // eslint-disable-next-line no-await-in-loop + const savedTool: ExternalTool = await this.validateAndSaveExternalTool(externalTool); + + result.toolId = savedTool.id; + } catch (error: unknown) { + if (error instanceof Error) { + result.error = error.message; + } + } + + results.push(result); + } + + return results; + } + + private async validateAndSaveExternalTool(externalToolCreate: ExternalToolCreate): Promise { const externalTool: ExternalTool = new ExternalTool({ ...externalToolCreate, id: new ObjectId().toHexString() }); externalTool.logo = await this.externalToolLogoService.fetchLogo(externalTool); From 1b713ac945798dbc6e073cd367f6a9b362f392fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Fri, 24 May 2024 09:18:51 +0200 Subject: [PATCH 2/5] api test --- .../controller/api-test/tool.api.spec.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts index abc11f2074a..68baf2d4add 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts @@ -29,11 +29,16 @@ import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; import { ExternalToolEntity } from '../../entity'; import { externalToolEntityFactory, externalToolFactory } from '../../testing'; import { + ExternalToolBulkCreateParams, ExternalToolCreateParams, ExternalToolMetadataResponse, ExternalToolResponse, ExternalToolSearchListResponse, } from '../dto'; +import { + ExternalToolImportResultListResponse, + ExternalToolImportResultResponse, +} from '../dto/response/external-tool-import-result-response'; describe('ToolController (API)', () => { let app: INestApplication; @@ -221,6 +226,108 @@ describe('ToolController (API)', () => { }); }); + describe('[POST] tools/external-tools', () => { + const logoUrl = 'https://link.to-my-logo.com'; + const postParams: ExternalToolCreateParams = { + name: 'Tool 1', + parameters: [ + { + name: 'key', + description: 'This is a parameter.', + displayName: 'User Friendly Name', + defaultValue: 'abc', + isOptional: false, + isProtected: false, + type: CustomParameterTypeParams.STRING, + regex: 'abc', + regexComment: 'Regex accepts "abc" as value.', + location: CustomParameterLocationParams.PATH, + scope: CustomParameterScopeTypeParams.GLOBAL, + }, + ], + config: { + type: ToolConfigType.BASIC, + baseUrl: 'https://link.to-my-tool.com/:key', + }, + isHidden: false, + isDeactivated: false, + logoUrl, + url: 'https://link.to-my-tool.com', + openNewTab: true, + medium: { + mediumId: 'medium:1', + mediaSourceId: 'source:1', + }, + }; + + describe('when valid data is given', () => { + const setup = async () => { + const params: ExternalToolBulkCreateParams = { data: [{ ...postParams }] }; + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); + await em.persistAndFlush([adminAccount, adminUser]); + em.clear(); + + const base64Logo: string = externalToolFactory.withBase64Logo().build().logo as string; + const logoBuffer: Buffer = Buffer.from(base64Logo, 'base64'); + axiosMock.onGet(logoUrl).reply(HttpStatus.OK, logoBuffer); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { + loggedInClient, + params, + }; + }; + + it('should create a tool', async () => { + const { loggedInClient, params } = await setup(); + + const response: Response = await loggedInClient.post('/import', params); + const body: ExternalToolImportResultListResponse = response.body as ExternalToolImportResultListResponse; + + expect(body.results[0]?.toolId).toBeDefined(); + const loaded: Loaded = await em.findOneOrFail(ExternalToolEntity, { + id: body.results[0].toolId, + }); + expect(loaded).toBeDefined(); + }); + + it('should return the created tool', async () => { + const { loggedInClient, params } = await setup(); + + const response: Response = await loggedInClient.post('/import', params); + const body: ExternalToolImportResultListResponse = response.body as ExternalToolImportResultListResponse; + + expect(response.statusCode).toEqual(HttpStatus.CREATED); + expect(body.results[0]).toBeDefined(); + expect(body.results[0]).toEqual({ + toolId: expect.any(String), + mediumId: postParams.medium?.mediumId, + mediumSourceId: postParams.medium?.mediaSourceId, + toolName: postParams.name, + error: undefined, + }); + }); + }); + + describe('when user is not authenticated', () => { + const setup = () => { + const params: ExternalToolBulkCreateParams = { data: [{ ...postParams }] }; + + return { params }; + }; + + it('should return unauthorized', async () => { + const { params } = setup(); + + const response: Response = await testApiClient.post('/import', params); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + }); + describe('[GET] tools/external-tools', () => { describe('when requesting tools', () => { const setup = async () => { From 8d1de182ae031d0ee26bcd891f5c7b62356b1603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Fri, 24 May 2024 09:26:13 +0200 Subject: [PATCH 3/5] import --- .../external-tool/controller/api-test/tool.api.spec.ts | 6 ++---- .../tool/external-tool/controller/dto/response/index.ts | 4 ++++ .../tool/external-tool/controller/tool.controller.ts | 5 ++--- .../external-tool/mapper/external-tool-response.mapper.ts | 8 +++----- .../server/src/modules/tool/external-tool/uc/dto/index.ts | 1 + .../src/modules/tool/external-tool/uc/external-tool.uc.ts | 3 +-- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts index 68baf2d4add..9a5780b037e 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts @@ -31,14 +31,12 @@ import { externalToolEntityFactory, externalToolFactory } from '../../testing'; import { ExternalToolBulkCreateParams, ExternalToolCreateParams, + ExternalToolImportResultListResponse, + ExternalToolImportResultResponse, ExternalToolMetadataResponse, ExternalToolResponse, ExternalToolSearchListResponse, } from '../dto'; -import { - ExternalToolImportResultListResponse, - ExternalToolImportResultResponse, -} from '../dto/response/external-tool-import-result-response'; describe('ToolController (API)', () => { let app: INestApplication; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts index 08fb3761317..a1f0b8fff5d 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts @@ -9,3 +9,7 @@ export * from './school-external-tool-configuration-template-list.response'; export * from './external-tool-metadata.response'; export * from './tool-context-types-list.response'; export { ExternalToolMediumResponse } from './external-tool-medium.response'; +export { + ExternalToolImportResultListResponse, + ExternalToolImportResultResponse, +} from './external-tool-import-result-response'; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts index 1697176b095..4209805a510 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts @@ -37,12 +37,12 @@ import { ExternalToolLogo } from '../domain/external-tool-logo'; import { ExternalToolMetadataMapper, ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; import { ExternalToolLogoService } from '../service'; -import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; -import { ExternalToolImportResult } from '../uc/dto/external-tool-import-result'; +import { ExternalToolCreate, ExternalToolImportResult, ExternalToolUc, ExternalToolUpdate } from '../uc'; import { ExternalToolBulkCreateParams, ExternalToolCreateParams, ExternalToolIdParams, + ExternalToolImportResultListResponse, ExternalToolMetadataResponse, ExternalToolResponse, ExternalToolSearchListResponse, @@ -50,7 +50,6 @@ import { ExternalToolUpdateParams, SortExternalToolParams, } from './dto'; -import { ExternalToolImportResultListResponse } from './dto/response/external-tool-import-result-response'; @ApiTags('Tool') @Authenticate('jwt') diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts index 3be76b9dcc4..feee7375b39 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts @@ -11,17 +11,15 @@ import { import { BasicToolConfigResponse, CustomParameterResponse, + ExternalToolImportResultListResponse, + ExternalToolImportResultResponse, ExternalToolMediumResponse, ExternalToolResponse, Lti11ToolConfigResponse, Oauth2ToolConfigResponse, } from '../controller/dto'; -import { - ExternalToolImportResultListResponse, - ExternalToolImportResultResponse, -} from '../controller/dto/response/external-tool-import-result-response'; import { BasicToolConfig, ExternalTool, ExternalToolMedium, Lti11ToolConfig, Oauth2ToolConfig } from '../domain'; -import { ExternalToolImportResult } from '../uc/dto/external-tool-import-result'; +import { ExternalToolImportResult } from '../uc'; const scopeMapping: Record = { [CustomParameterScope.GLOBAL]: CustomParameterScopeTypeParams.GLOBAL, diff --git a/apps/server/src/modules/tool/external-tool/uc/dto/index.ts b/apps/server/src/modules/tool/external-tool/uc/dto/index.ts index 4620f394779..42a6b6b8f4a 100644 --- a/apps/server/src/modules/tool/external-tool/uc/dto/index.ts +++ b/apps/server/src/modules/tool/external-tool/uc/dto/index.ts @@ -1,2 +1,3 @@ export * from './external-tool.types'; export * from './external-tool-configuration.types'; +export { ExternalToolImportResult } from './external-tool-import-result'; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts index da7e9c8bfbd..84dc40cc8e3 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts @@ -18,8 +18,7 @@ import { ExternalToolService, ExternalToolValidationService, } from '../service'; -import { ExternalToolCreate, ExternalToolUpdate } from './dto'; -import { ExternalToolImportResult } from './dto/external-tool-import-result'; +import { ExternalToolCreate, ExternalToolImportResult, ExternalToolUpdate } from './dto'; @Injectable() export class ExternalToolUc { From 9eca22f2e882125c968524d2ae604a18398c753f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Fri, 24 May 2024 09:49:48 +0200 Subject: [PATCH 4/5] api description --- .../response/external-tool-import-result-response.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts index 435530b8d48..547e3d48b69 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-import-result-response.ts @@ -1,14 +1,19 @@ import { ApiProperty } from '@nestjs/swagger'; export class ExternalToolImportResultResponse { + @ApiProperty({ description: 'Name of the external tool' }) toolName: string; + @ApiProperty({ description: 'Medium id of the external tool' }) mediumId?: string; + @ApiProperty({ description: 'Medium source of the external tool' }) mediumSourceId?: string; + @ApiProperty({ description: 'ObjectId of the created external tool' }) toolId?: string; + @ApiProperty({ description: 'Status message of the error that occurred' }) error?: string; constructor(props: ExternalToolImportResultResponse) { @@ -21,7 +26,10 @@ export class ExternalToolImportResultResponse { } export class ExternalToolImportResultListResponse { - @ApiProperty({ type: [ExternalToolImportResultResponse] }) + @ApiProperty({ + type: [ExternalToolImportResultResponse], + description: 'List of operation results for the provided external tools', + }) results: ExternalToolImportResultResponse[]; constructor(props: ExternalToolImportResultListResponse) { From 123df7e27b7591021309b69d0ca6bdb5404b7f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Mon, 27 May 2024 13:16:44 +0200 Subject: [PATCH 5/5] review changes --- .../external-tool/mapper/external-tool-request.mapper.ts | 2 +- .../external-tool/mapper/external-tool-response.mapper.ts | 2 +- .../modules/tool/external-tool/uc/external-tool.uc.spec.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts index 16a3b1178ff..9603103492e 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts @@ -12,6 +12,7 @@ import { ExternalToolSearchQuery } from '../../common/interface'; import { BasicToolConfigParams, CustomParameterPostParams, + ExternalToolBulkCreateParams, ExternalToolCreateParams, ExternalToolMediumParams, ExternalToolSearchParams, @@ -22,7 +23,6 @@ import { Oauth2ToolConfigUpdateParams, SortExternalToolParams, } from '../controller/dto'; -import { ExternalToolBulkCreateParams } from '../controller/dto/request/external-tool-bulk-create.params'; import { ExternalTool } from '../domain'; import { BasicToolConfigDto, diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts index feee7375b39..d54893ea48b 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts @@ -114,7 +114,7 @@ export class ExternalToolResponseMapper { }); } - static mapToImportResponse(results: ExternalToolImportResult[]): ExternalToolImportResultListResponse { + public static mapToImportResponse(results: ExternalToolImportResult[]): ExternalToolImportResultListResponse { const response: ExternalToolImportResultListResponse = new ExternalToolImportResultListResponse({ results: results.map( (result: ExternalToolImportResult): ExternalToolImportResultResponse => diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts index dce4668fd72..b8d5eff140d 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts @@ -37,8 +37,7 @@ import { externalToolFactory, oauth2ToolConfigFactory, } from '../testing'; -import { ExternalToolUpdate } from './dto'; -import { ExternalToolImportResult } from './dto/external-tool-import-result'; +import { ExternalToolImportResult, ExternalToolUpdate } from './dto'; import { ExternalToolUc } from './external-tool.uc'; describe(ExternalToolUc.name, () => { @@ -410,7 +409,7 @@ describe(ExternalToolUc.name, () => { }; }; - it('should check the users permission', async () => { + it('should return an error in the result report', async () => { const { user, externalTool1, externalTool2, error } = setup(); const results = await uc.importExternalTools(user.id, [externalTool1.getProps(), externalTool2.getProps()]);