From f6de63f2d66ea9c8a22259aa6ec7c948a197cdd4 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Fri, 10 Nov 2023 08:21:15 +0100 Subject: [PATCH 01/20] N21-1319 externaltool metadata --- .../external-tool-metadata.response.ts | 15 +++ .../controller/tool.controller.ts | 24 ++++ .../domain/external-tool-metadata.ts | 12 ++ .../mapper/external-tool-metadata.mapper.ts | 33 +++++ .../service/external-tool-metadata.service.ts | 126 ++++++++++++++++++ .../tool/external-tool/uc/external-tool.uc.ts | 12 +- .../context-external-tool.repo.ts | 12 ++ 7 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts create mode 100644 apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts create mode 100644 apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts create mode 100644 apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts new file mode 100644 index 00000000000..16b4e1d60b4 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ToolContextType } from '../../../../common/enum'; + +export class ExternalToolMetadataResponse { + @ApiProperty() + schoolExternalToolCount: number; + + @ApiProperty() + contextExternalToolCountPerContext: Map; + + constructor(externalToolMetadataResponse: ExternalToolMetadataResponse) { + this.schoolExternalToolCount = externalToolMetadataResponse.schoolExternalToolCount; + this.contextExternalToolCountPerContext = externalToolMetadataResponse.contextExternalToolCountPerContext; + } +} 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 80139a586cb..8bac75bf769 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 @@ -19,7 +19,9 @@ import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool } from '../domain'; import { ExternalToolLogo } from '../domain/external-tool-logo'; +import { ExternalToolMetadata } from '../domain/external-tool-metadata'; import { ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; +import { ExternalToolMetadataMapper } from '../mapper/external-tool-metadata.mapper'; import { ExternalToolLogoService } from '../service'; import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; import { @@ -31,6 +33,7 @@ import { ExternalToolUpdateParams, SortExternalToolParams, } from './dto'; +import { ExternalToolMetadataResponse } from './dto/response/external-tool-metadata.response'; @ApiTags('Tool') @Authenticate('jwt') @@ -165,4 +168,25 @@ export class ToolController { res.setHeader('Cache-Control', 'must-revalidate'); res.send(externalToolLogo.logo); } + + @Get('/:externalToolId/metadata') + @ApiOperation({ summary: 'Gets the metadata of an external tool.' }) + @ApiOkResponse({ + description: 'Metadata of external tool fetched successfully.', + }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getMetaDataForExternalTool( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: ExternalToolIdParams + ): Promise { + const externalToolMetadata: ExternalToolMetadata = await this.externalToolUc.getMetadataForExternalTool( + currentUser.userId, + params.externalToolId + ); + + const mapped: ExternalToolMetadataResponse = + ExternalToolMetadataMapper.mapToExternalToolMetadataResponse(externalToolMetadata); + + return mapped; + } } diff --git a/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts new file mode 100644 index 00000000000..7a4b98d8356 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts @@ -0,0 +1,12 @@ +import { ToolContextType } from '../../common/enum'; + +export class ExternalToolMetadata { + schoolExternalToolCount: number; + + contextExternalToolCountPerContext: Map; + + constructor(externalToolMetadata: ExternalToolMetadata) { + this.schoolExternalToolCount = externalToolMetadata.schoolExternalToolCount; + this.contextExternalToolCountPerContext = externalToolMetadata.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts new file mode 100644 index 00000000000..cfe506500fa --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts @@ -0,0 +1,33 @@ +import { ToolContextType } from '../../common/enum'; +import { ExternalToolMetadataResponse } from '../controller/dto/response/external-tool-metadata.response'; +import { ExternalToolMetadata } from '../domain/external-tool-metadata'; + +export class ExternalToolMetadataMapper { + static mapExternalToolMetadata( + schoolExternalToolCount: number, + toolCountPerContext: { ToolContextType; number }[] + ): ExternalToolMetadata { + const contextExternalToolMetadata: Map = new Map( + toolCountPerContext.map((contextExternalToolCountPerContext: { ToolContextType; number }) => [ + contextExternalToolCountPerContext.ToolContextType, + contextExternalToolCountPerContext.number, + ]) + ); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount, + contextExternalToolCountPerContext: contextExternalToolMetadata, + }); + + return externalToolMetadata; + } + + static mapToExternalToolMetadataResponse(externalToolMetadata: ExternalToolMetadata): ExternalToolMetadataResponse { + const externalToolMetadataResponse: ExternalToolMetadataResponse = new ExternalToolMetadataResponse({ + schoolExternalToolCount: externalToolMetadata.schoolExternalToolCount, + contextExternalToolCountPerContext: externalToolMetadata.contextExternalToolCountPerContext, + }); + + return externalToolMetadataResponse; + } +} diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts new file mode 100644 index 00000000000..593467da966 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -0,0 +1,126 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { Logger } from '@src/core/logger'; +import { ToolContextType } from '../../common/enum'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ExternalToolMetadata } from '../domain/external-tool-metadata'; + +@Injectable() +export class ExternalToolMetadataService { + constructor( + private readonly logger: Logger, + private readonly externalToolRepo: ExternalToolRepo, + private readonly schoolToolRepo: SchoolExternalToolRepo, + private readonly contextToolRepo: ContextExternalToolRepo + ) {} + + async getMetadataComplicated(toolId: EntityId) { + const schoolExternalToolCount: number = await this.schoolToolRepo.countSchoolExternalToolsByExternalToolId(toolId); + + const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); + let contextExternalToolCountPerContext: number; + + const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( + // Context Course + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise<{ ToolContextType; number }> => { + const contextExternalToolCountPerSchool = await Promise.all( + // all schoolExternalTools with externalToolId + schoolExternalTools.map(async (schoolExternalTool: SchoolExternalTool) => { + if (schoolExternalTool.id !== undefined) { + // contexttools in course context + const countPerContext: number = + await this.contextToolRepo.countContextExternalToolsBySchoolToolIdAndContextType( + contextType, + schoolExternalTool.id + ); + return countPerContext; + } + throw new Error('SchoolExternalTool id is undefined'); + }) + ); + + contextExternalToolCountPerSchool.forEach((countPerContext: number) => { + contextExternalToolCountPerContext += countPerContext; + }); + + return { ToolContextType: contextType, number: contextExternalToolCountPerContext }; + }) + ); + + const externaltoolMetadata = this.createExternalToolMetadata(schoolExternalToolCount, toolCountPerContext); + + /* const contextExternalToolMetadata: Map = new Map( + toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ + contextExternalToolCountPerSchool.ToolContextType, + contextExternalToolCountPerSchool.number, + ]) + ); + + const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount, + contextExternalToolCountPerContext: contextExternalToolMetadata, + }); + + */ + + return externaltoolMetadata; + } + + private createContextExternalToolMetaData( + toolCountPerContext: { ToolContextType; number }[] + ): Map { + const contextExternalToolMetadata: Map = new Map( + toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ + contextExternalToolCountPerSchool.ToolContextType, + contextExternalToolCountPerSchool.number, + ]) + ); + + return contextExternalToolMetadata; + } + + private createExternalToolMetadata( + schoolExternalToolCount: number, + contextExternalToolCountPerContext: { ToolContextType; number }[] + ): ExternalToolMetadata { + const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount, + contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), + }); + + return externaltoolMetadata; + } + + /* async getMetadata(toolId: EntityId) { + const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); + let contextExternalToolsCourseContext: number | undefined; + let contextExternalToolsBoardContext: number | undefined; + + schoolExternalTools.map(async (schoolExternalTool: SchoolExternalTool) => { + if (schoolExternalTool.id !== undefined) { + // tools in course context + const contextExternalTools = await this.contextToolRepo.find({ + schoolToolRef: schoolExternalTool, + context: { type: ToolContextType.COURSE }, + }); + + + } else { + throw new Error('SchoolExternalTool id is undefined'); + } + }); + }); + + if (contextExternalToolsCourseContext === undefined || contextExternalToolsBoardContext === undefined) { + throw new Error('contextExternalToolsCourseContext or contextExternalToolsBoardContext is undefined'); + } + const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: schoolExternalTools.length, + contextExternalToolCountPerContext: new Map([ + [ToolContextType.COURSE, contextExternalToolsCourseContext], + [ToolContextType.BOARD_ELEMENT, contextExternalToolsBoardContext], + ]), + }); + } */ +} 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 2cf49867103..d14aec171c3 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 @@ -2,7 +2,9 @@ import { Injectable } from '@nestjs/common'; import { EntityId, IFindOptions, Page, Permission, User } from '@shared/domain'; import { AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; +import { ExternalToolMetadataService } from '../service/external-tool-metadata.service'; import { ExternalTool, ExternalToolConfig } from '../domain'; +import { ExternalToolMetadata } from '../domain/external-tool-metadata'; import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; import { ExternalToolCreate, ExternalToolUpdate } from './dto'; @@ -12,7 +14,8 @@ export class ExternalToolUc { private readonly externalToolService: ExternalToolService, private readonly authorizationService: AuthorizationService, private readonly toolValidationService: ExternalToolValidationService, - private readonly externalToolLogoService: ExternalToolLogoService + private readonly externalToolLogoService: ExternalToolLogoService, + private readonly externaltoolMetadataService: ExternalToolMetadataService ) {} async createExternalTool(userId: EntityId, externalToolCreate: ExternalToolCreate): Promise { @@ -74,6 +77,13 @@ export class ExternalToolUc { return promise; } + async getMetadataForExternalTool(userId: EntityId, toolId: EntityId): Promise { + await this.ensurePermission(userId, Permission.TOOL_ADMIN); + + const metadata: ExternalToolMetadata = await this.externaltoolMetadataService.getMetadataComplicated(toolId); + return metadata; + } + private async ensurePermission(userId: EntityId, permission: Permission) { const user: User = await this.authorizationService.getUserWithPermissions(userId); this.authorizationService.checkAllPermissions(user, [permission]); diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts index 5ad1629f0c2..3b8397379c1 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts @@ -53,6 +53,18 @@ export class ContextExternalToolRepo extends BaseDORepo< return dos; } + async countContextExternalToolsBySchoolToolIdAndContextType( + toolContextType: ToolContextType, + schoolToolId: EntityId + ) { + const contextExternalToolCount = await this._em.count(this.entityName, { + schoolTool: schoolToolId, + contextType: toolContextType, + }); + + return contextExternalToolCount; + } + public override async findById(id: EntityId): Promise { const entity: ContextExternalToolEntity = await this._em.findOneOrFail( this.entityName, From f1e1044bb21a203b5f89a8555a4696e5c91edf62 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Fri, 10 Nov 2023 08:21:36 +0100 Subject: [PATCH 02/20] N21-1319 schoolexternaltool metadata --- .../school-external-tool-metadata.response.ts | 11 +++ .../controller/tool-school.controller.ts | 22 ++++++ .../domain/school-external-tool-metadata.ts | 9 +++ .../school-external-tool-metadata.mapper.ts | 14 ++++ .../school-external-tool-metadata.service.ts | 78 +++++++++++++++++++ .../uc/school-external-tool.uc.ts | 19 +++++ .../school-external-tool.repo.ts | 5 ++ 7 files changed, 158 insertions(+) create mode 100644 apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts new file mode 100644 index 00000000000..59cbdc91152 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ToolContextType } from '../../../common/enum'; + +export class SchoolExternalToolMetadataResponse { + @ApiProperty() + contextExternalToolCountPerContext: Map; + + constructor(schoolExternalToolMetadataResponse: SchoolExternalToolMetadataResponse) { + this.contextExternalToolCountPerContext = schoolExternalToolMetadataResponse.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts index 79d6f789443..9a4cdac32b9 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts @@ -14,8 +14,10 @@ import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpS import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from '../mapper'; import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; +import { SchoolExternalToolMetadataMapper } from '../mapper/school-external-tool-metadata.mapper'; import { SchoolExternalToolIdParams, SchoolExternalToolPostParams, @@ -26,6 +28,7 @@ import { import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolUc } from '../uc'; import { SchoolExternalTool } from '../domain'; +import { SchoolExternalToolMetadataResponse } from './dto/school-external-tool-metadata.response'; @ApiTags('Tool') @Authenticate('jwt') @@ -136,4 +139,23 @@ export class ToolSchoolController { return response; } + + @Get('/:schoolExternalToolId/metadata') + @ApiOperation({ summary: 'Gets the metadata of an school external tool.' }) + @ApiOkResponse({ + description: 'Metadata of school external tool fetched successfully.', + }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getMetaDataForExternalTool( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: SchoolExternalToolIdParams + ): Promise { + const schoolExternalToolMetadata: SchoolExternalToolMetadata = + await this.schoolExternalToolUc.getMetadataForSchoolExternalTool(currentUser.userId, params.schoolExternalToolId); + + const mapped: SchoolExternalToolMetadataResponse = + SchoolExternalToolMetadataMapper.mapToSchoolExternalToolMetadataResponse(schoolExternalToolMetadata); + + return mapped; + } } diff --git a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts new file mode 100644 index 00000000000..ec03f944360 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts @@ -0,0 +1,9 @@ +import { ToolContextType } from '../../common/enum'; + +export class SchoolExternalToolMetadata { + contextExternalToolCountPerContext: Map; + + constructor(schoolExternalToolMetadata: SchoolExternalToolMetadata) { + this.contextExternalToolCountPerContext = schoolExternalToolMetadata.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts new file mode 100644 index 00000000000..ef397dbe0f5 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts @@ -0,0 +1,14 @@ +import { SchoolExternalToolMetadataResponse } from '../controller/dto/school-external-tool-metadata.response'; +import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; + +export class SchoolExternalToolMetadataMapper { + static mapToSchoolExternalToolMetadataResponse( + schoolExternalToolMetadata: SchoolExternalToolMetadata + ): SchoolExternalToolMetadataResponse { + const externalToolMetadataResponse: SchoolExternalToolMetadataResponse = new SchoolExternalToolMetadataResponse({ + contextExternalToolCountPerContext: schoolExternalToolMetadata.contextExternalToolCountPerContext, + }); + + return externalToolMetadataResponse; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts new file mode 100644 index 00000000000..d4de0f42ed2 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { Logger } from '../../../../core/logger'; +import { ToolContextType } from '../../common/enum'; +import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; + +@Injectable() +export class SchoolExternalToolMetadataService { + constructor( + private readonly logger: Logger, + private readonly externalToolRepo: ExternalToolRepo, + private readonly schoolToolRepo: SchoolExternalToolRepo, + private readonly contextToolRepo: ContextExternalToolRepo + ) {} + + async getMetadata(schoolExternalToolId: EntityId) { + const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( + // Context Course + Object.values(ToolContextType).map( + async ( + contextType: ToolContextType + ): Promise<{ + ToolContextType; + number; + }> => { + const countPerContext: number = + await this.contextToolRepo.countContextExternalToolsBySchoolToolIdAndContextType( + contextType, + schoolExternalToolId + ); + return { ToolContextType: contextType, number: countPerContext }; + } + ) + ); + + const schoolExternaltoolMetadata = this.createSchoolExternalToolMetadata(toolCountPerContext); + + /* const contextExternalToolMetadata: Map = new Map( + toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ + contextExternalToolCountPerSchool.ToolContextType, + contextExternalToolCountPerSchool.number, + ]) + ); + + const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount, + contextExternalToolCountPerContext: contextExternalToolMetadata, + }); + + */ + + return schoolExternaltoolMetadata; + } + + private createContextExternalToolMetaData( + toolCountPerContext: { ToolContextType; number }[] + ): Map { + const contextExternalToolMetadata: Map = new Map( + toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ + contextExternalToolCountPerSchool.ToolContextType, + contextExternalToolCountPerSchool.number, + ]) + ); + + return contextExternalToolMetadata; + } + + private createSchoolExternalToolMetadata( + contextExternalToolCountPerContext: { ToolContextType; number }[] + ): SchoolExternalToolMetadata { + const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), + }); + + return schoolExternaltoolMetadata; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts index d7adf3f4937..e5d05409d3b 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts @@ -4,7 +4,9 @@ import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/auth import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../domain'; +import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { SchoolExternalToolMetadataService } from '../service/school-external-tool-metadata.service'; import { SchoolExternalToolDto, SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; @Injectable() @@ -13,6 +15,7 @@ export class SchoolExternalToolUc { private readonly schoolExternalToolService: SchoolExternalToolService, private readonly contextExternalToolService: ContextExternalToolService, private readonly schoolExternalToolValidationService: SchoolExternalToolValidationService, + private readonly schoolexternaltoolMetadataService: SchoolExternalToolMetadataService, private readonly toolPermissionHelper: ToolPermissionHelper ) {} @@ -95,4 +98,20 @@ export class SchoolExternalToolUc { const saved = await this.schoolExternalToolService.saveSchoolExternalTool(updated); return saved; } + + async getMetadataForSchoolExternalTool( + userId: EntityId, + schoolExternalToolId: EntityId + ): Promise { + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById(schoolExternalToolId); + + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); + await this.toolPermissionHelper.ensureSchoolPermissions(userId, schoolExternalTool, context); + + const metadata: SchoolExternalToolMetadata = await this.schoolexternaltoolMetadataService.getMetadata( + schoolExternalToolId + ); + + return metadata; + } } diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts index 64f55c715e8..978193b54c0 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts @@ -38,6 +38,11 @@ export class SchoolExternalToolRepo extends BaseDORepo< return domainObjects; } + async countSchoolExternalToolsByExternalToolId(toolId: string): Promise { + const schoolExternalToolCount: number = await this._em.count(this.entityName, { tool: toolId }); + return schoolExternalToolCount; + } + async findBySchoolId(schoolId: string): Promise { const entities: SchoolExternalToolEntity[] = await this._em.find(this.entityName, { school: schoolId }); const domainObjects: SchoolExternalTool[] = entities.map((entity: SchoolExternalToolEntity): SchoolExternalTool => { From 0abb730d4b2cd53770169a263b8a73703f7dbbc1 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Fri, 10 Nov 2023 12:10:31 +0100 Subject: [PATCH 03/20] N21-1319 clean up --- .../mapper/external-tool-metadata.mapper.ts | 20 -------- .../service/external-tool-metadata.service.ts | 49 ------------------- .../school-external-tool-metadata.service.ts | 15 ------ 3 files changed, 84 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts index cfe506500fa..543acd46f87 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts @@ -1,27 +1,7 @@ -import { ToolContextType } from '../../common/enum'; import { ExternalToolMetadataResponse } from '../controller/dto/response/external-tool-metadata.response'; import { ExternalToolMetadata } from '../domain/external-tool-metadata'; export class ExternalToolMetadataMapper { - static mapExternalToolMetadata( - schoolExternalToolCount: number, - toolCountPerContext: { ToolContextType; number }[] - ): ExternalToolMetadata { - const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerContext: { ToolContextType; number }) => [ - contextExternalToolCountPerContext.ToolContextType, - contextExternalToolCountPerContext.number, - ]) - ); - - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount, - contextExternalToolCountPerContext: contextExternalToolMetadata, - }); - - return externalToolMetadata; - } - static mapToExternalToolMetadataResponse(externalToolMetadata: ExternalToolMetadata): ExternalToolMetadataResponse { const externalToolMetadataResponse: ExternalToolMetadataResponse = new ExternalToolMetadataResponse({ schoolExternalToolCount: externalToolMetadata.schoolExternalToolCount, diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index 593467da966..841905bf405 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -22,13 +22,10 @@ export class ExternalToolMetadataService { let contextExternalToolCountPerContext: number; const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( - // Context Course Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise<{ ToolContextType; number }> => { const contextExternalToolCountPerSchool = await Promise.all( - // all schoolExternalTools with externalToolId schoolExternalTools.map(async (schoolExternalTool: SchoolExternalTool) => { if (schoolExternalTool.id !== undefined) { - // contexttools in course context const countPerContext: number = await this.contextToolRepo.countContextExternalToolsBySchoolToolIdAndContextType( contextType, @@ -50,20 +47,6 @@ export class ExternalToolMetadataService { const externaltoolMetadata = this.createExternalToolMetadata(schoolExternalToolCount, toolCountPerContext); - /* const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ - contextExternalToolCountPerSchool.ToolContextType, - contextExternalToolCountPerSchool.number, - ]) - ); - - const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount, - contextExternalToolCountPerContext: contextExternalToolMetadata, - }); - - */ - return externaltoolMetadata; } @@ -91,36 +74,4 @@ export class ExternalToolMetadataService { return externaltoolMetadata; } - - /* async getMetadata(toolId: EntityId) { - const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); - let contextExternalToolsCourseContext: number | undefined; - let contextExternalToolsBoardContext: number | undefined; - - schoolExternalTools.map(async (schoolExternalTool: SchoolExternalTool) => { - if (schoolExternalTool.id !== undefined) { - // tools in course context - const contextExternalTools = await this.contextToolRepo.find({ - schoolToolRef: schoolExternalTool, - context: { type: ToolContextType.COURSE }, - }); - - - } else { - throw new Error('SchoolExternalTool id is undefined'); - } - }); - }); - - if (contextExternalToolsCourseContext === undefined || contextExternalToolsBoardContext === undefined) { - throw new Error('contextExternalToolsCourseContext or contextExternalToolsBoardContext is undefined'); - } - const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount: schoolExternalTools.length, - contextExternalToolCountPerContext: new Map([ - [ToolContextType.COURSE, contextExternalToolsCourseContext], - [ToolContextType.BOARD_ELEMENT, contextExternalToolsBoardContext], - ]), - }); - } */ } diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index d4de0f42ed2..5c90cacf62b 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -16,7 +16,6 @@ export class SchoolExternalToolMetadataService { async getMetadata(schoolExternalToolId: EntityId) { const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( - // Context Course Object.values(ToolContextType).map( async ( contextType: ToolContextType @@ -36,20 +35,6 @@ export class SchoolExternalToolMetadataService { const schoolExternaltoolMetadata = this.createSchoolExternalToolMetadata(toolCountPerContext); - /* const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ - contextExternalToolCountPerSchool.ToolContextType, - contextExternalToolCountPerSchool.number, - ]) - ); - - const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount, - contextExternalToolCountPerContext: contextExternalToolMetadata, - }); - - */ - return schoolExternaltoolMetadata; } From a6838661250073faf71017b09b1a316b53f9bc5c Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Mon, 13 Nov 2023 08:25:26 +0100 Subject: [PATCH 04/20] N21-1319 add ExternalToolMetadataService --- .../common/enum/tool-context-type.enum.ts | 2 +- .../controller/tool.controller.ts | 7 ++- .../tool/external-tool/domain/index.ts | 1 + .../external-tool/external-tool.module.ts | 3 ++ .../tool/external-tool/mapper/index.ts | 1 + .../service/external-tool-metadata.service.ts | 53 ++++++++----------- .../tool/external-tool/service/index.ts | 1 + .../tool/external-tool/uc/external-tool.uc.ts | 13 +++-- .../context-external-tool.repo.ts | 9 ++-- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts b/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts index 4c930b57397..04b1299fc8c 100644 --- a/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts +++ b/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts @@ -1,4 +1,4 @@ export enum ToolContextType { COURSE = 'course', - BOARD_ELEMENT = 'board-element', + BOARD_ELEMENT = 'boardElement', } 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 8bac75bf769..6c2921f6870 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 @@ -17,11 +17,10 @@ import { LegacyLogger } from '@src/core/logger'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalTool } from '../domain'; +import { ExternalTool, ExternalToolMetadata } from '../domain'; import { ExternalToolLogo } from '../domain/external-tool-logo'; -import { ExternalToolMetadata } from '../domain/external-tool-metadata'; -import { ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; -import { ExternalToolMetadataMapper } from '../mapper/external-tool-metadata.mapper'; + +import { ExternalToolRequestMapper, ExternalToolResponseMapper, ExternalToolMetadataMapper } from '../mapper'; import { ExternalToolLogoService } from '../service'; import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; import { diff --git a/apps/server/src/modules/tool/external-tool/domain/index.ts b/apps/server/src/modules/tool/external-tool/domain/index.ts index e5a1dab735d..61fca0d2bfe 100644 --- a/apps/server/src/modules/tool/external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/external-tool/domain/index.ts @@ -1,2 +1,3 @@ export * from './external-tool.do'; export * from './config'; +export * from './external-tool-metadata'; diff --git a/apps/server/src/modules/tool/external-tool/external-tool.module.ts b/apps/server/src/modules/tool/external-tool/external-tool.module.ts index 2fbd2f28edd..6b86bba7a7b 100644 --- a/apps/server/src/modules/tool/external-tool/external-tool.module.ts +++ b/apps/server/src/modules/tool/external-tool/external-tool.module.ts @@ -13,6 +13,7 @@ import { ExternalToolServiceMapper, ExternalToolValidationService, ExternalToolVersionService, + ExternalToolMetadataService, } from './service'; import { CommonToolModule } from '../common'; @@ -27,6 +28,7 @@ import { CommonToolModule } from '../common'; ExternalToolConfigurationService, ExternalToolLogoService, ExternalToolRepo, + ExternalToolMetadataService, ], exports: [ ExternalToolService, @@ -34,6 +36,7 @@ import { CommonToolModule } from '../common'; ExternalToolVersionService, ExternalToolConfigurationService, ExternalToolLogoService, + ExternalToolMetadataService, ], }) export class ExternalToolModule {} diff --git a/apps/server/src/modules/tool/external-tool/mapper/index.ts b/apps/server/src/modules/tool/external-tool/mapper/index.ts index 4149a17a519..73fdb05cf5a 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/index.ts @@ -1,2 +1,3 @@ export * from './external-tool-request.mapper'; export * from './external-tool-response.mapper'; +export * from './external-tool-metadata.mapper'; diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index 841905bf405..a5c6f6a65cb 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -4,7 +4,7 @@ import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } fro import { Logger } from '@src/core/logger'; import { ToolContextType } from '../../common/enum'; import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { ExternalToolMetadata } from '../domain/external-tool-metadata'; +import { ExternalToolMetadata } from '../domain'; @Injectable() export class ExternalToolMetadataService { @@ -15,47 +15,40 @@ export class ExternalToolMetadataService { private readonly contextToolRepo: ContextExternalToolRepo ) {} - async getMetadataComplicated(toolId: EntityId) { - const schoolExternalToolCount: number = await this.schoolToolRepo.countSchoolExternalToolsByExternalToolId(toolId); - + async getMetaData(toolId: EntityId) { const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); - let contextExternalToolCountPerContext: number; - - const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( - Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise<{ ToolContextType; number }> => { - const contextExternalToolCountPerSchool = await Promise.all( - schoolExternalTools.map(async (schoolExternalTool: SchoolExternalTool) => { - if (schoolExternalTool.id !== undefined) { - const countPerContext: number = - await this.contextToolRepo.countContextExternalToolsBySchoolToolIdAndContextType( - contextType, - schoolExternalTool.id - ); - return countPerContext; - } - throw new Error('SchoolExternalTool id is undefined'); - }) - ); + if (schoolExternalTools.length < 0) { + throw Error('Metadata of tool could not be loaded because no SchoolExternalTool was found'); + } + const schoolExternalToolIds: string[] = schoolExternalTools.map( + (schoolExternalTool: SchoolExternalTool): string => + // We can be sure that the repo returns the id + schoolExternalTool.id as string + ); - contextExternalToolCountPerSchool.forEach((countPerContext: number) => { - contextExternalToolCountPerContext += countPerContext; - }); + const contextTools = await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType) => { + const countPerContext: number = + await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType( + contextType, + schoolExternalToolIds + ); - return { ToolContextType: contextType, number: contextExternalToolCountPerContext }; + return { contextType, number: countPerContext }; }) ); - const externaltoolMetadata = this.createExternalToolMetadata(schoolExternalToolCount, toolCountPerContext); + const externaltoolMetadata = this.createExternalToolMetadata(schoolExternalTools.length, contextTools); return externaltoolMetadata; } private createContextExternalToolMetaData( - toolCountPerContext: { ToolContextType; number }[] + toolCountPerContext: { contextType: ToolContextType; number: number }[] ): Map { const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ - contextExternalToolCountPerSchool.ToolContextType, + toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; number: number }) => [ + contextExternalToolCountPerSchool.contextType, contextExternalToolCountPerSchool.number, ]) ); @@ -65,7 +58,7 @@ export class ExternalToolMetadataService { private createExternalToolMetadata( schoolExternalToolCount: number, - contextExternalToolCountPerContext: { ToolContextType; number }[] + contextExternalToolCountPerContext: { contextType; number }[] ): ExternalToolMetadata { const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount, diff --git a/apps/server/src/modules/tool/external-tool/service/index.ts b/apps/server/src/modules/tool/external-tool/service/index.ts index 8cb1df69bc1..9baf7c8a56a 100644 --- a/apps/server/src/modules/tool/external-tool/service/index.ts +++ b/apps/server/src/modules/tool/external-tool/service/index.ts @@ -5,3 +5,4 @@ export * from './external-tool-validation.service'; export * from './external-tool-parameter-validation.service'; export * from './external-tool-configuration.service'; export * from './external-tool-logo.service'; +export * from './external-tool-metadata.service'; 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 d14aec171c3..e30ec56bb34 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 @@ -2,10 +2,13 @@ import { Injectable } from '@nestjs/common'; import { EntityId, IFindOptions, Page, Permission, User } from '@shared/domain'; import { AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalToolMetadataService } from '../service/external-tool-metadata.service'; -import { ExternalTool, ExternalToolConfig } from '../domain'; -import { ExternalToolMetadata } from '../domain/external-tool-metadata'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; +import { ExternalTool, ExternalToolConfig, ExternalToolMetadata } from '../domain'; +import { + ExternalToolLogoService, + ExternalToolService, + ExternalToolValidationService, + ExternalToolMetadataService, +} from '../service'; import { ExternalToolCreate, ExternalToolUpdate } from './dto'; @Injectable() @@ -80,7 +83,7 @@ export class ExternalToolUc { async getMetadataForExternalTool(userId: EntityId, toolId: EntityId): Promise { await this.ensurePermission(userId, Permission.TOOL_ADMIN); - const metadata: ExternalToolMetadata = await this.externaltoolMetadataService.getMetadataComplicated(toolId); + const metadata: ExternalToolMetadata = await this.externaltoolMetadataService.getMetaData(toolId); return metadata; } diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts index 3b8397379c1..2bbcd88f0a1 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts @@ -53,13 +53,12 @@ export class ContextExternalToolRepo extends BaseDORepo< return dos; } - async countContextExternalToolsBySchoolToolIdAndContextType( - toolContextType: ToolContextType, - schoolToolId: EntityId + async countContextExternalToolsBySchoolToolIdsAndContextType( + contextType: ToolContextType, + schoolExternalToolIds: string[] ) { const contextExternalToolCount = await this._em.count(this.entityName, { - schoolTool: schoolToolId, - contextType: toolContextType, + $and: [{ schoolTool: { $in: schoolExternalToolIds }, contextType }], }); return contextExternalToolCount; From 9824bdc69c16f0639749fb14716675b88533bd9e Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Mon, 13 Nov 2023 08:25:47 +0100 Subject: [PATCH 05/20] N21-1319 add SchoolExternalToolMetadataService --- .../controller/tool-school.controller.ts | 10 +++-- .../tool/school-external-tool/domain/index.ts | 1 + .../tool/school-external-tool/mapper/index.ts | 1 + .../school-external-tool.module.ts | 10 +++-- .../school-external-tool/service/index.ts | 1 + .../school-external-tool-metadata.service.ts | 41 +++++++------------ .../uc/school-external-tool.uc.ts | 10 +++-- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts index 9a4cdac32b9..34e1eb6396d 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts @@ -14,10 +14,12 @@ import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpS import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; -import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; -import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from '../mapper'; +import { + SchoolExternalToolRequestMapper, + SchoolExternalToolResponseMapper, + SchoolExternalToolMetadataMapper, +} from '../mapper'; import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; -import { SchoolExternalToolMetadataMapper } from '../mapper/school-external-tool-metadata.mapper'; import { SchoolExternalToolIdParams, SchoolExternalToolPostParams, @@ -27,7 +29,7 @@ import { } from './dto'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolUc } from '../uc'; -import { SchoolExternalTool } from '../domain'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; import { SchoolExternalToolMetadataResponse } from './dto/school-external-tool-metadata.response'; @ApiTags('Tool') diff --git a/apps/server/src/modules/tool/school-external-tool/domain/index.ts b/apps/server/src/modules/tool/school-external-tool/domain/index.ts index 1d734ed8376..d089fb2e908 100644 --- a/apps/server/src/modules/tool/school-external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/domain/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool.do'; export * from './school-external-tool-ref.do'; +export * from './school-external-tool-metadata'; diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts index 961644805f1..48cd0b13a20 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool-request.mapper'; export * from './school-external-tool-response.mapper'; +export * from './school-external-tool-metadata.mapper'; diff --git a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts index 790f4e716c2..4b338cfdbc0 100644 --- a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts +++ b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts @@ -1,11 +1,15 @@ import { Module } from '@nestjs/common'; import { CommonToolModule } from '../common'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from './service'; +import { + SchoolExternalToolService, + SchoolExternalToolValidationService, + SchoolExternalToolMetadataService, +} from './service'; import { ExternalToolModule } from '../external-tool'; @Module({ imports: [CommonToolModule, ExternalToolModule], - providers: [SchoolExternalToolService, SchoolExternalToolValidationService], - exports: [SchoolExternalToolService, SchoolExternalToolValidationService], + providers: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], + exports: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], }) export class SchoolExternalToolModule {} diff --git a/apps/server/src/modules/tool/school-external-tool/service/index.ts b/apps/server/src/modules/tool/school-external-tool/service/index.ts index 1ceab5f3da5..ea949d8b70a 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool.service'; export * from './school-external-tool-validation.service'; +export * from './school-external-tool-metadata.service'; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index 5c90cacf62b..140fe2a9677 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -1,49 +1,38 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; -import { Logger } from '../../../../core/logger'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { ToolContextType } from '../../common/enum'; -import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; +import { SchoolExternalToolMetadata } from '../domain'; @Injectable() export class SchoolExternalToolMetadataService { constructor( - private readonly logger: Logger, - private readonly externalToolRepo: ExternalToolRepo, private readonly schoolToolRepo: SchoolExternalToolRepo, private readonly contextToolRepo: ContextExternalToolRepo ) {} async getMetadata(schoolExternalToolId: EntityId) { - const toolCountPerContext: { ToolContextType; number }[] = await Promise.all( - Object.values(ToolContextType).map( - async ( - contextType: ToolContextType - ): Promise<{ - ToolContextType; - number; - }> => { - const countPerContext: number = - await this.contextToolRepo.countContextExternalToolsBySchoolToolIdAndContextType( - contextType, - schoolExternalToolId - ); - return { ToolContextType: contextType, number: countPerContext }; - } - ) + const contextTools = await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType) => { + const countPerContext: number = + await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType(contextType, + [schoolExternalToolId.toString()]); + + return { contextType, number: countPerContext }; + }) ); - const schoolExternaltoolMetadata = this.createSchoolExternalToolMetadata(toolCountPerContext); + const schoolExternaltoolMetadata = this.createSchoolExternalToolMetadata(contextTools); return schoolExternaltoolMetadata; } private createContextExternalToolMetaData( - toolCountPerContext: { ToolContextType; number }[] + toolCountPerContext: { contextType: ToolContextType; number: number }[] ): Map { const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { ToolContextType; number }) => [ - contextExternalToolCountPerSchool.ToolContextType, + toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; number: number }) => [ + contextExternalToolCountPerSchool.contextType, contextExternalToolCountPerSchool.number, ]) ); @@ -52,7 +41,7 @@ export class SchoolExternalToolMetadataService { } private createSchoolExternalToolMetadata( - contextExternalToolCountPerContext: { ToolContextType; number }[] + contextExternalToolCountPerContext: { contextType; number }[] ): SchoolExternalToolMetadata { const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts index e5d05409d3b..c0f6495ebf6 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts @@ -3,10 +3,12 @@ import { EntityId, Permission } from '@shared/domain'; import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalTool } from '../domain'; -import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; -import { SchoolExternalToolMetadataService } from '../service/school-external-tool-metadata.service'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; +import { + SchoolExternalToolService, + SchoolExternalToolValidationService, + SchoolExternalToolMetadataService, +} from '../service'; import { SchoolExternalToolDto, SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; @Injectable() From 4475b87b5dbe1ac2a12b81d0f312657cca4ec732 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Tue, 14 Nov 2023 08:51:25 +0100 Subject: [PATCH 06/20] N21-1319 loggable --- .../external-tool-metadata-loggable.spec.ts | 42 +++++++++++++++++++ .../external-tool-metadata-loggable.ts | 15 +++++++ .../service/external-tool-metadata.service.ts | 29 +++++++++---- 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts create mode 100644 apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts new file mode 100644 index 00000000000..a0630d599b5 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts @@ -0,0 +1,42 @@ +import { ExternalToolMetadataLoggable } from './external-tool-metadata-loggable'; + +describe('ExternalToolMetadataLoggable', () => { + describe('constructor', () => { + const setup = () => { + const msg = 'message'; + + return { msg }; + }; + + it('should create an instance of ExternalToolLogoFetchedLoggable', () => { + const { msg } = setup(); + + const loggable = new ExternalToolMetadataLoggable(msg); + + expect(loggable).toBeInstanceOf(ExternalToolMetadataLoggable); + }); + }); + + describe('getLogMessage', () => { + const setup = () => { + const msg = 'message'; + const loggable = new ExternalToolMetadataLoggable(msg); + + return { loggable, msg }; + }; + + it('should return a loggable message', () => { + const { loggable, msg } = setup(); + + const message = loggable.getLogMessage(); + + expect(message).toEqual({ + type: 'EXTERNAL_TOOL_METADATA', + message: 'No related tools found, return empty external tool metadata', + data: { + msg, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts new file mode 100644 index 00000000000..163523b3494 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts @@ -0,0 +1,15 @@ +import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; + +export class ExternalToolMetadataLoggable implements Loggable { + constructor(private readonly msg: string) {} + + getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { + return { + type: 'EXTERNAL_TOOL_METADATA', + message: 'No related tools found, return empty external tool metadata', + data: { + msg: this.msg, + }, + }; + } +} diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index a5c6f6a65cb..fefa51e7851 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -5,6 +5,7 @@ import { Logger } from '@src/core/logger'; import { ToolContextType } from '../../common/enum'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ExternalToolMetadata } from '../domain'; +import { ExternalToolMetadataLoggable } from '../loggable/external-tool-metadata-loggable'; @Injectable() export class ExternalToolMetadataService { @@ -15,11 +16,23 @@ export class ExternalToolMetadataService { private readonly contextToolRepo: ContextExternalToolRepo ) {} - async getMetaData(toolId: EntityId) { + async getMetaData(toolId: EntityId): Promise { const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); - if (schoolExternalTools.length < 0) { - throw Error('Metadata of tool could not be loaded because no SchoolExternalTool was found'); + if (schoolExternalTools.length < 1) { + this.logger.info( + new ExternalToolMetadataLoggable( + `There are no such schoolExternalTools for toolId: ${toolId}, returning empty metadata.` + ) + ); + + const externalToolMetadata = this.createExternalToolMetadata(0, [ + { contextType: ToolContextType.COURSE, count: 0 }, + { contextType: ToolContextType.BOARD_ELEMENT, count: 0 }, + ]); + + return externalToolMetadata; } + const schoolExternalToolIds: string[] = schoolExternalTools.map( (schoolExternalTool: SchoolExternalTool): string => // We can be sure that the repo returns the id @@ -34,7 +47,7 @@ export class ExternalToolMetadataService { schoolExternalToolIds ); - return { contextType, number: countPerContext }; + return { contextType, count: countPerContext }; }) ); @@ -44,12 +57,12 @@ export class ExternalToolMetadataService { } private createContextExternalToolMetaData( - toolCountPerContext: { contextType: ToolContextType; number: number }[] + toolCountPerContext: { contextType: ToolContextType; count: number }[] ): Map { const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; number: number }) => [ + toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; count: number }) => [ contextExternalToolCountPerSchool.contextType, - contextExternalToolCountPerSchool.number, + contextExternalToolCountPerSchool.count, ]) ); @@ -58,7 +71,7 @@ export class ExternalToolMetadataService { private createExternalToolMetadata( schoolExternalToolCount: number, - contextExternalToolCountPerContext: { contextType; number }[] + contextExternalToolCountPerContext: { contextType: ToolContextType; count: number }[] ): ExternalToolMetadata { const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount, From da6298243fd2e7ccf81d92cc776b37b89b0a36bb Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Tue, 14 Nov 2023 08:52:53 +0100 Subject: [PATCH 07/20] N21-1319 unit tests --- .../controller/api-test/tool.api.spec.ts | 129 ++++++++++++- .../external-tool-metadata.service.spec.ts | 175 ++++++++++++++++++ .../external-tool/uc/external-tool.uc.spec.ts | 90 ++++++++- .../api-test/tool-school.api.spec.ts | 123 ++++++++++++ ...ool-external-tool-metadata.service.spec.ts | 137 ++++++++++++++ .../school-external-tool-metadata.service.ts | 15 +- .../uc/school-external-tool.uc.spec.ts | 63 ++++++- .../uc/school-external-tool.uc.ts | 4 +- ...ext-external-tool.repo.integration.spec.ts | 126 ++++++++++++- ...ool-external-tool.repo.integration.spec.ts | 19 ++ 10 files changed, 861 insertions(+), 20 deletions(-) create mode 100644 apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts 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 ebff659529d..6bb0a21e6fc 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 @@ -1,16 +1,18 @@ import { Loaded } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { cleanupCollections, + contextExternalToolEntityFactory, externalToolEntityFactory, externalToolFactory, + schoolExternalToolEntityFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { Response } from 'supertest'; @@ -19,9 +21,14 @@ import { CustomParameterScopeTypeParams, CustomParameterTypeParams, ToolConfigType, + ToolContextType, } from '../../../common/enum'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; +import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ExternalToolMetadata } from '../../domain'; import { ExternalToolEntity } from '../../entity'; import { ExternalToolCreateParams, ExternalToolResponse, ExternalToolSearchListResponse } from '../dto'; +import { ExternalToolMetadataResponse } from '../dto/response/external-tool-metadata.response'; describe('ToolController (API)', () => { let app: INestApplication; @@ -617,4 +624,124 @@ describe('ToolController (API)', () => { }); }); }); + + describe('[GET] tools/external-tools/:externalToolId/metadata', () => { + describe('when user is not authenticated', () => { + const setup = async () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); + await em.persistAndFlush([adminAccount, adminUser, externalToolEntity]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + }; + + it('should return unauthorized', async () => { + const { externalToolEntity } = await setup(); + + const response: Response = await testApiClient.get(`${externalToolEntity.id}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when externalToolId is given ', () => { + const setup = async () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); + + const schoolToolId: string = new ObjectId().toHexString(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( + { tool: externalToolEntity }, + schoolToolId + ); + const schoolToolId1: string = new ObjectId().toHexString(); + const schoolExternalToolEntity1: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( + { tool: externalToolEntity }, + schoolToolId1 + ); + + const courseExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + const courseExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity1, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + const courseExternalToolEntity2: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + + const boardExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + const boardExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity1, + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); + await em.persistAndFlush([ + adminAccount, + adminUser, + externalToolEntity, + schoolExternalToolEntity, + schoolExternalToolEntity1, + courseExternalToolEntity, + courseExternalToolEntity1, + courseExternalToolEntity2, + boardExternalToolEntity, + boardExternalToolEntity1, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + }; + + it('should return the metadata of externaltool', async () => { + const { loggedInClient, externalToolEntity, externalToolMetadata } = await setup(); + + const response: Response = await loggedInClient.get(`${externalToolEntity.id}/metadata`); + + const body: ExternalToolMetadataResponse = response.body as ExternalToolMetadataResponse; + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(body).toBeDefined(); + expect(body).toMatchObject(externalToolMetadata); + expect(body.schoolExternalToolCount).toEqual(2); + expect(body.contextExternalToolCountPerContext).toHaveProperty('course', 3); + expect(body.contextExternalToolCountPerContext).toHaveProperty('boardElement', 2); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts new file mode 100644 index 00000000000..0590b08ce3e --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts @@ -0,0 +1,175 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { externalToolFactory, legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; +import { Logger } from '@src/core/logger'; +import { ToolContextType } from '../../common/enum'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ExternalTool, ExternalToolMetadata } from '../domain'; +import { ExternalToolMetadataLoggable } from '../loggable/external-tool-metadata-loggable'; +import { ExternalToolMetadataService } from './external-tool-metadata.service'; + +describe('ExternalToolMetadataService', () => { + let module: TestingModule; + let service: ExternalToolMetadataService; + + let externalToolRepo: DeepMocked; + let schoolExternalToolRepo: DeepMocked; + let contextExternalToolRepo: DeepMocked; + let logger: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ExternalToolMetadataService, + { + provide: ExternalToolRepo, + useValue: createMock(), + }, + { + provide: SchoolExternalToolRepo, + useValue: createMock(), + }, + { + provide: ContextExternalToolRepo, + useValue: createMock(), + }, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(ExternalToolMetadataService); + externalToolRepo = module.get(ExternalToolRepo); + schoolExternalToolRepo = module.get(SchoolExternalToolRepo); + contextExternalToolRepo = module.get(ContextExternalToolRepo); + logger = module.get(Logger); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getMetadata', () => { + describe('when externalToolId is given', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + const externalTool: ExternalTool = externalToolFactory.buildWithId(undefined, toolId); + + const school = legacySchoolDoFactory.buildWithId(); + const school1 = legacySchoolDoFactory.buildWithId(); + + const schoolToolId: string = new ObjectId().toHexString(); + const schoolToolId1: string = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId, + schoolId: school.id, + id: schoolToolId, + }); + const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( + { toolId, schoolId: school1.id, id: schoolToolId1 }, + schoolToolId1 + ); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + externalToolRepo.findById.mockResolvedValue(externalTool); + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); + contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(3); + + return { + toolId, + externalToolMetadata, + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should call the repo to get schoolExternalTools by externalToolId', async () => { + const { toolId } = setup(); + + await service.getMetaData(toolId); + + expect(schoolExternalToolRepo.findByExternalToolId).toHaveBeenCalledWith(toolId); + }); + + it('should call the repo to count contextExternalTools by schoolExternalToolId and context', async () => { + const { toolId, schoolExternalTool, schoolExternalTool1 } = setup(); + + await service.getMetaData(toolId); + + expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ToolContextType.COURSE, + [schoolExternalTool.id, schoolExternalTool1.id] + ); + expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ToolContextType.BOARD_ELEMENT, + [schoolExternalTool.id, schoolExternalTool1.id] + ); + expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledTimes(2); + }); + + it('should return externalToolMetadata', async () => { + const { toolId, externalToolMetadata } = setup(); + + const result: ExternalToolMetadata = await service.getMetaData(toolId); + + expect(result).toEqual(externalToolMetadata); + }); + }); + + describe('when no related school external tool was found', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalTool = externalToolFactory.buildWithId(undefined, toolId); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 0); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 0); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 0, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + externalToolRepo.findById.mockResolvedValue(externalToolEntity); + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([]); + contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(0); + + return { + toolId, + externalToolEntity, + externalToolMetadata, + }; + }; + + it('should return empty externalToolMetadata', async () => { + const { toolId, externalToolMetadata } = setup(); + + const result: ExternalToolMetadata = await service.getMetaData(toolId); + + expect(result).toEqual(externalToolMetadata); + expect(logger.info).toHaveBeenCalledWith( + new ExternalToolMetadataLoggable( + `There are no such schoolExternalTools for toolId: ${toolId}, returning empty metadata.` + ) + ); + }); + }); + }); +}); 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 2d371f47c9e..f613e322305 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 @@ -1,4 +1,5 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { IFindOptions, Permission, SortOrder, User } from '@shared/domain'; @@ -10,9 +11,15 @@ import { } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { ICurrentUser } from '@modules/authentication'; import { AuthorizationService } from '@modules/authorization'; +import { ToolContextType } from '../../common/enum'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalTool, Oauth2ToolConfig } from '../domain'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; +import { ExternalTool, ExternalToolMetadata, Oauth2ToolConfig } from '../domain'; +import { + ExternalToolLogoService, + ExternalToolMetadataService, + ExternalToolService, + ExternalToolValidationService, +} from '../service'; import { ExternalToolUpdate } from './dto'; import { ExternalToolUc } from './external-tool.uc'; @@ -25,6 +32,7 @@ describe('ExternalToolUc', () => { let authorizationService: DeepMocked; let toolValidationService: DeepMocked; let logoService: DeepMocked; + let externalToolMetadataService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -48,6 +56,10 @@ describe('ExternalToolUc', () => { provide: ExternalToolLogoService, useValue: createMock(), }, + { + provide: ExternalToolMetadataService, + useValue: createMock(), + }, ], }).compile(); @@ -56,6 +68,7 @@ describe('ExternalToolUc', () => { authorizationService = module.get(AuthorizationService); toolValidationService = module.get(ExternalToolValidationService); logoService = module.get(ExternalToolLogoService); + externalToolMetadataService = module.get(ExternalToolMetadataService); }); afterAll(async () => { @@ -468,4 +481,77 @@ describe('ExternalToolUc', () => { expect(externalToolService.deleteExternalTool).toHaveBeenCalledWith(toolId); }); }); + + describe('getMetadataForExternalTool', () => { + describe('Authorization', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + return { + toolId, + }; + }; + + it('should call getUserWithPermissions', async () => { + const { currentUser } = setupAuthorization(); + const { toolId } = setupMetadata(); + + await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(currentUser.userId); + }); + + it('should successfully check the user permission with the authorization service', async () => { + const { currentUser, user } = setupAuthorization(); + const { toolId } = setupMetadata(); + + await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.TOOL_ADMIN]); + }); + + it('should throw if the user has insufficient permission to get an external tool', async () => { + const { currentUser } = setupAuthorization(); + const { toolId } = setupMetadata(); + authorizationService.checkAllPermissions.mockImplementation(() => { + throw new UnauthorizedException(); + }); + + const result: Promise = uc.getMetadataForExternalTool(currentUser.userId, toolId); + + await expect(result).rejects.toThrow(UnauthorizedException); + }); + }); + + describe('when externalToolId is given', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + externalToolMetadataService.getMetaData.mockResolvedValue(externalToolMetadata); + + return { + toolId, + externalToolMetadata, + }; + }; + + it('should call the service to get metadata', async () => { + const { currentUser } = setupAuthorization(); + + const { toolId } = setupMetadata(); + + await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(externalToolMetadataService.getMetaData).toHaveBeenCalledWith(toolId); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 3512e66038e..f5a25e8841e 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -1,9 +1,11 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, SchoolEntity, User } from '@shared/domain'; import { accountFactory, + contextExternalToolEntityFactory, externalToolEntityFactory, schoolExternalToolEntityFactory, schoolFactory, @@ -12,7 +14,12 @@ import { userFactory, } from '@shared/testing'; import { ServerTestModule } from '@modules/server'; +import { Response } from 'supertest'; +import { ToolContextType } from '../../../common/enum'; import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; +import { ExternalToolMetadataResponse } from '../../../external-tool/controller/dto/response/external-tool-metadata.response'; +import { ExternalToolMetadata } from '../../../external-tool/domain'; import { ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; import { @@ -22,6 +29,7 @@ import { SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, } from '../dto'; +import { SchoolExternalToolMetadataResponse } from '../dto/school-external-tool-metadata.response'; describe('ToolSchoolController (API)', () => { let app: INestApplication; @@ -487,4 +495,119 @@ describe('ToolSchoolController (API)', () => { expect(updatedSchoolExternalTool).toBeDefined(); }); }); + + describe('[GET] tools/school-external-tools/:schoolExternalToolId/metadata', () => { + describe('when user is not authenticated', () => { + const setup = async () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); + await em.persistAndFlush([adminAccount, adminUser, externalToolEntity]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + }; + + it('should return unauthorized', async () => { + const { externalToolEntity } = await setup(); + + const response: Response = await testApiClient.get(`${externalToolEntity.id}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when schoolExternalToolId is given ', () => { + const setup = async () => { + const school = schoolFactory.buildWithId(); + const schoolToolId: string = new ObjectId().toHexString(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( + { school }, + schoolToolId + ); + + const courseExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + + const courseExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + + const courseExternalToolEntity2: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + + const boardExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + + const boardExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); + const schoolExternalToolMetadata: SchoolExternalToolMetadataResponse = new SchoolExternalToolMetadataResponse({ + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ + Permission.SCHOOL_TOOL_ADMIN, + ]); + + await em.persistAndFlush([ + adminAccount, + adminUser, + schoolExternalToolEntity, + courseExternalToolEntity, + courseExternalToolEntity1, + courseExternalToolEntity2, + boardExternalToolEntity, + boardExternalToolEntity1, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, schoolExternalToolEntity, schoolExternalToolMetadata }; + }; + + it('should return the metadata of schoolExternalTool', async () => { + const { loggedInClient, schoolExternalToolEntity, schoolExternalToolMetadata } = await setup(); + + const response: Response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); + + const body: SchoolExternalToolMetadataResponse = response.body as SchoolExternalToolMetadataResponse; + + expect(body).toBeDefined(); + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(body).toMatchObject(schoolExternalToolMetadata); + expect(body.contextExternalToolCountPerContext).toHaveProperty('course', 3); + expect(body.contextExternalToolCountPerContext).toHaveProperty('boardElement', 2); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts new file mode 100644 index 00000000000..28cc5fee004 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -0,0 +1,137 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; +import { Logger } from '@src/core/logger'; +import { ToolContextType } from '../../common/enum'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; +import { SchoolExternalToolMetadataService } from './school-external-tool-metadata.service'; + +describe('SchoolExternalToolMetadataService', () => { + let module: TestingModule; + let service: SchoolExternalToolMetadataService; + + let schoolExternalToolRepo: DeepMocked; + let contextExternalToolRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + SchoolExternalToolMetadataService, + { + provide: SchoolExternalToolRepo, + useValue: createMock(), + }, + { + provide: ContextExternalToolRepo, + useValue: createMock(), + }, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(SchoolExternalToolMetadataService); + schoolExternalToolRepo = module.get(SchoolExternalToolRepo); + contextExternalToolRepo = module.get(ContextExternalToolRepo); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getMetadata', () => { + describe('when schoolExternalToolId is given', () => { + const setup = () => { + const school = legacySchoolDoFactory.buildWithId(); + const school1 = legacySchoolDoFactory.buildWithId(); + + const schoolToolId: string = new ObjectId().toHexString(); + const schoolToolId1: string = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId: school.id, + id: schoolToolId, + }); + const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( + { schoolId: school1.id, id: schoolToolId1 }, + schoolToolId1 + ); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 3); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); + + const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); + contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(3); + + return { + schoolToolId, + schoolExternalToolMetadata, + }; + }; + + it('should return externalToolMetadata', async () => { + const { schoolToolId, schoolExternalToolMetadata } = setup(); + + const result: SchoolExternalToolMetadata = await service.getMetadata(schoolToolId); + + expect(result).toEqual(schoolExternalToolMetadata); + }); + }); + + describe('when no related context external tool was found', () => { + const setup = () => { + const school = legacySchoolDoFactory.buildWithId(); + const school1 = legacySchoolDoFactory.buildWithId(); + + const schoolToolId: string = new ObjectId().toHexString(); + const schoolToolId1: string = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId: school.id, + id: schoolToolId, + }); + const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( + { schoolId: school1.id, id: schoolToolId1 }, + schoolToolId1 + ); + + const contextExternalToolCount = new Map(); + contextExternalToolCount.set(ToolContextType.COURSE, 0); + contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 0); + + const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); + contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(0); + + return { + schoolToolId, + schoolExternalToolMetadata, + }; + }; + + it('should return empty schoolExternalToolMetadata', async () => { + const { schoolToolId, schoolExternalToolMetadata } = setup(); + + const result: SchoolExternalToolMetadata = await service.getMetadata(schoolToolId); + + expect(result).toEqual(schoolExternalToolMetadata); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index 140fe2a9677..438076a2c55 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -15,10 +15,11 @@ export class SchoolExternalToolMetadataService { const contextTools = await Promise.all( Object.values(ToolContextType).map(async (contextType: ToolContextType) => { const countPerContext: number = - await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType(contextType, - [schoolExternalToolId.toString()]); + await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType(contextType, [ + schoolExternalToolId.toString(), + ]); - return { contextType, number: countPerContext }; + return { contextType, count: countPerContext }; }) ); @@ -28,12 +29,12 @@ export class SchoolExternalToolMetadataService { } private createContextExternalToolMetaData( - toolCountPerContext: { contextType: ToolContextType; number: number }[] + toolCountPerContext: { contextType: ToolContextType; count: number }[] ): Map { const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; number: number }) => [ + toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; count: number }) => [ contextExternalToolCountPerSchool.contextType, - contextExternalToolCountPerSchool.number, + contextExternalToolCountPerSchool.count, ]) ); @@ -41,7 +42,7 @@ export class SchoolExternalToolMetadataService { } private createSchoolExternalToolMetadata( - contextExternalToolCountPerContext: { contextType; number }[] + contextExternalToolCountPerContext: { contextType: ToolContextType; count: number }[] ): SchoolExternalToolMetadata { const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts index 377a5d24a38..60df089c683 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts @@ -1,4 +1,5 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, Permission, User } from '@shared/domain'; import { schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; @@ -6,7 +7,11 @@ import { AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../domain'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { + SchoolExternalToolMetadataService, + SchoolExternalToolService, + SchoolExternalToolValidationService, +} from '../service'; import { SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; import { SchoolExternalToolUc } from './school-external-tool.uc'; @@ -18,6 +23,7 @@ describe('SchoolExternalToolUc', () => { let contextExternalToolService: DeepMocked; let schoolExternalToolValidationService: DeepMocked; let toolPermissionHelper: DeepMocked; + let schoolExternalToolMetadataService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -40,6 +46,10 @@ describe('SchoolExternalToolUc', () => { provide: ToolPermissionHelper, useValue: createMock(), }, + { + provide: SchoolExternalToolMetadataService, + useValue: createMock(), + }, ], }).compile(); @@ -48,6 +58,7 @@ describe('SchoolExternalToolUc', () => { contextExternalToolService = module.get(ContextExternalToolService); schoolExternalToolValidationService = module.get(SchoolExternalToolValidationService); toolPermissionHelper = module.get(ToolPermissionHelper); + schoolExternalToolMetadataService = module.get(SchoolExternalToolMetadataService); }); afterAll(async () => { @@ -56,6 +67,7 @@ describe('SchoolExternalToolUc', () => { afterEach(() => { jest.resetAllMocks(); + jest.clearAllMocks(); }); describe('findSchoolExternalTools', () => { @@ -358,4 +370,53 @@ describe('SchoolExternalToolUc', () => { expect(result).toEqual(updatedTool); }); }); + + describe('getMetadataForSchoolExternalTool', () => { + describe('Authorization', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ toolId }); + const userId: string = new ObjectId().toHexString(); + const user: User = userFactory.buildWithId({}, userId); + + return { + user, + tool, + }; + }; + + it('should check the permissions of the user', async () => { + const { user, tool } = setupMetadata(); + + await uc.getMetadataForSchoolExternalTool(user.id, tool.id!); + + expect(toolPermissionHelper.ensureSchoolPermissions).toHaveBeenCalledWith( + user.id, + tool, + AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]) + ); + }); + }); + + describe('when externalToolId is given', () => { + const setupMetadata = () => { + const user: User = userFactory.buildWithId(); + + const toolId: string = new ObjectId().toHexString(); + + return { + toolId, + user, + }; + }; + + it('should call the service to get metadata', async () => { + const { toolId, user } = setupMetadata(); + + await uc.getMetadataForSchoolExternalTool(user.id, toolId); + + expect(schoolExternalToolMetadataService.getMetadata).toHaveBeenCalledWith(toolId); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts index c0f6495ebf6..0d098c8c657 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts @@ -17,7 +17,7 @@ export class SchoolExternalToolUc { private readonly schoolExternalToolService: SchoolExternalToolService, private readonly contextExternalToolService: ContextExternalToolService, private readonly schoolExternalToolValidationService: SchoolExternalToolValidationService, - private readonly schoolexternaltoolMetadataService: SchoolExternalToolMetadataService, + private readonly schoolExternalToolMetadataService: SchoolExternalToolMetadataService, private readonly toolPermissionHelper: ToolPermissionHelper ) {} @@ -110,7 +110,7 @@ export class SchoolExternalToolUc { const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); await this.toolPermissionHelper.ensureSchoolPermissions(userId, schoolExternalTool, context); - const metadata: SchoolExternalToolMetadata = await this.schoolexternaltoolMetadataService.getMetadata( + const metadata: SchoolExternalToolMetadata = await this.schoolExternalToolMetadataService.getMetadata( schoolExternalToolId ); diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts index 854c3958135..8c4076fe9b3 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts @@ -1,8 +1,14 @@ import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { CustomParameterEntry } from '@modules/tool/common/domain'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { ContextExternalTool, ContextExternalToolProps } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolEntity, ContextExternalToolType } from '@modules/tool/context-external-tool/entity'; +import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { Test, TestingModule } from '@nestjs/testing'; import { SchoolEntity } from '@shared/domain'; -import { MongoMemoryDatabaseModule } from '@infra/database'; import { ExternalToolRepoMapper } from '@shared/repo/externaltool/external-tool.repo.mapper'; import { cleanupCollections, @@ -12,12 +18,6 @@ import { schoolFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { CustomParameterEntry } from '@modules/tool/common/domain'; -import { ToolContextType } from '@modules/tool/common/enum'; -import { ContextExternalTool, ContextExternalToolProps } from '@modules/tool/context-external-tool/domain'; -import { ContextExternalToolEntity, ContextExternalToolType } from '@modules/tool/context-external-tool/entity'; -import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; -import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { ContextExternalToolRepo } from './context-external-tool.repo'; describe('ContextExternalToolRepo', () => { @@ -394,4 +394,116 @@ describe('ContextExternalToolRepo', () => { }); }); }); + + describe('countContextExternalToolsBySchoolToolIdsAndContextType', () => { + describe('when a ContextExternalTool is found for course context', () => { + const setup = async () => { + const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); + const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); + + const contextExternalTool = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool, + }); + const contextExternalTool1 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool, + }); + const contextExternalTool2 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool, + }); + + const contextExternalTool3 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool1, + }); + + const contextExternalTool4 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool1, + }); + + await em.persistAndFlush([ + schoolExternalTool, + schoolExternalTool1, + contextExternalTool, + contextExternalTool1, + contextExternalTool2, + contextExternalTool3, + contextExternalTool4, + ]); + + return { + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should return correct results', async () => { + const { schoolExternalTool, schoolExternalTool1 } = await setup(); + + const result = await repo.countContextExternalToolsBySchoolToolIdsAndContextType(ToolContextType.COURSE, [ + schoolExternalTool.id, + schoolExternalTool1.id, + ]); + + expect(result).toEqual(3); + }); + }); + + describe('when a ContextExternalTool is found for board context', () => { + const setup = async () => { + const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); + const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); + + const contextExternalTool = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool, + }); + const contextExternalTool1 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool, + }); + const contextExternalTool2 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool, + }); + const contextExternalTool3 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool1, + }); + const contextExternalTool4 = contextExternalToolEntityFactory.buildWithId({ + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool1, + }); + + await em.persistAndFlush([ + schoolExternalTool, + schoolExternalTool1, + contextExternalTool, + contextExternalTool1, + contextExternalTool2, + contextExternalTool3, + contextExternalTool4, + ]); + + return { + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should return correct results', async () => { + const { schoolExternalTool, schoolExternalTool1 } = await setup(); + + const result = await repo.countContextExternalToolsBySchoolToolIdsAndContextType( + ToolContextType.BOARD_ELEMENT, + [schoolExternalTool.id, schoolExternalTool1.id] + ); + + expect(result).toEqual(2); + }); + }); + }); }); diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts index 47196013b63..0721e8ad08c 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts @@ -112,6 +112,25 @@ describe('SchoolExternalToolRepo', () => { }); }); + describe('countSchoolExternalToolsByExternalToolId', () => { + const setup = async () => { + const { externalToolEntity, school, schoolExternalTool1, schoolExternalTool3 } = createTools(); + + await em.persistAndFlush([school, externalToolEntity, schoolExternalTool1, schoolExternalTool3]); + em.clear(); + + return { externalToolEntity, school, schoolExternalTool1, schoolExternalTool3 }; + }; + + it('should find all SchoolExternalTools with reference to a given ExternalTool', async () => { + const { externalToolEntity } = await setup(); + + const result: number = await repo.countSchoolExternalToolsByExternalToolId(externalToolEntity.id); + + expect(result).toEqual(2); + }); + }); + describe('findBySchoolId', () => { describe('when searching for SchoolExternalTools by school id', () => { const setup = async () => { From 06954aa5438a097819ab34790a082451e3121b80 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Tue, 14 Nov 2023 10:24:35 +0100 Subject: [PATCH 08/20] N21-1319 fix unit tests --- .../src/modules/tool/external-tool/loggable/index.ts | 1 + .../service/external-tool-metadata.service.ts | 2 +- .../uc/school-external-tool.uc.spec.ts | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/loggable/index.ts b/apps/server/src/modules/tool/external-tool/loggable/index.ts index 67d65be236d..50eb1adbd8a 100644 --- a/apps/server/src/modules/tool/external-tool/loggable/index.ts +++ b/apps/server/src/modules/tool/external-tool/loggable/index.ts @@ -3,3 +3,4 @@ export * from './external-tool-logo-size-exceeded-loggable-exception'; export * from './external-tool-logo-fetched-loggable'; export * from './external-tool-logo-fetch-failed-loggable-exception'; export * from './external-tool-logo-wrong-file-type-loggable-exception'; +export * from './external-tool-metadata-loggable'; diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index fefa51e7851..ed494756e49 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -5,7 +5,7 @@ import { Logger } from '@src/core/logger'; import { ToolContextType } from '../../common/enum'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ExternalToolMetadata } from '../domain'; -import { ExternalToolMetadataLoggable } from '../loggable/external-tool-metadata-loggable'; +import { ExternalToolMetadataLoggable } from '../loggable'; @Injectable() export class ExternalToolMetadataService { diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts index 60df089c683..a8a96e04624 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts @@ -67,7 +67,6 @@ describe('SchoolExternalToolUc', () => { afterEach(() => { jest.resetAllMocks(); - jest.clearAllMocks(); }); describe('findSchoolExternalTools', () => { @@ -374,11 +373,13 @@ describe('SchoolExternalToolUc', () => { describe('getMetadataForSchoolExternalTool', () => { describe('Authorization', () => { const setupMetadata = () => { - const toolId: string = new ObjectId().toHexString(); - const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ toolId }); + const toolId = new ObjectId().toHexString(); + const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ id: toolId }, toolId); const userId: string = new ObjectId().toHexString(); const user: User = userFactory.buildWithId({}, userId); + schoolExternalToolService.findById.mockResolvedValue(tool); + return { user, tool, From ac9886aa5d23b3f7a3f1e3ce81e4776b7adb39ed Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Tue, 14 Nov 2023 10:34:27 +0100 Subject: [PATCH 09/20] N21-1319 fix lint --- .../controller/api-test/tool-school.api.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index f5a25e8841e..89784b09750 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -16,9 +16,8 @@ import { import { ServerTestModule } from '@modules/server'; import { Response } from 'supertest'; import { ToolContextType } from '../../../common/enum'; -import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; -import { ExternalToolMetadataResponse } from '../../../external-tool/controller/dto/response/external-tool-metadata.response'; import { ExternalToolMetadata } from '../../../external-tool/domain'; import { ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; From 280f3c9d38a81c5647f142faf365d1b21427a65a Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 15 Nov 2023 16:10:17 +0100 Subject: [PATCH 10/20] N21-1319 review changes --- .../common/enum/tool-context-type.enum.ts | 2 +- .../tool/common/mapper/tool-context.mapper.ts | 9 ++ .../controller/api-test/tool.api.spec.ts | 106 ++++++------------ .../external-tool-metadata.response.ts | 10 +- .../controller/dto/response/index.ts | 1 + .../controller/tool.controller.ts | 3 +- .../domain/external-tool-metadata.ts | 4 +- .../external-tool/external-tool.module.ts | 4 + .../external-tool-metadata-loggable.spec.ts | 33 +----- .../external-tool-metadata-loggable.ts | 5 - .../mapper/external-tool-metadata.mapper.ts | 6 +- .../external-tool-metadata.service.spec.ts | 49 +++----- .../service/external-tool-metadata.service.ts | 72 +++++------- .../external-tool/uc/external-tool.uc.spec.ts | 95 ++++++++++------ .../tool/external-tool/uc/external-tool.uc.ts | 6 +- .../api-test/tool-school.api.spec.ts | 71 +++--------- .../controller/dto/index.ts | 1 + .../school-external-tool-metadata.response.ts | 4 +- .../controller/tool-school.controller.ts | 3 +- .../domain/school-external-tool-metadata.ts | 4 +- .../school-external-tool-metadata.mapper.ts | 4 +- ...ool-external-tool-metadata.service.spec.ts | 16 +-- .../school-external-tool-metadata.service.ts | 51 ++++----- .../uc/school-external-tool.uc.spec.ts | 2 +- ...ext-external-tool.repo.integration.spec.ts | 20 ++-- .../context-external-tool.repo.ts | 5 +- .../school-external-tool.repo.ts | 6 +- 27 files changed, 238 insertions(+), 354 deletions(-) create mode 100644 apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts diff --git a/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts b/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts index 04b1299fc8c..4c930b57397 100644 --- a/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts +++ b/apps/server/src/modules/tool/common/enum/tool-context-type.enum.ts @@ -1,4 +1,4 @@ export enum ToolContextType { COURSE = 'course', - BOARD_ELEMENT = 'boardElement', + BOARD_ELEMENT = 'board-element', } diff --git a/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts new file mode 100644 index 00000000000..183da9cdbbd --- /dev/null +++ b/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts @@ -0,0 +1,9 @@ +import { ToolContextType } from '../enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; + +export class ToolContextMapper { + static contextMapping: Record = { + [ToolContextType.COURSE]: ContextExternalToolType.COURSE, + [ToolContextType.BOARD_ELEMENT]: ContextExternalToolType.BOARD_ELEMENT, + }; +} 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 6bb0a21e6fc..211f8d83cd9 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 @@ -3,13 +3,14 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '@shared/domain'; +import { Permission, SchoolEntity } from '@shared/domain'; import { cleanupCollections, contextExternalToolEntityFactory, externalToolEntityFactory, externalToolFactory, schoolExternalToolEntityFactory, + schoolFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; @@ -21,14 +22,17 @@ import { CustomParameterScopeTypeParams, CustomParameterTypeParams, ToolConfigType, - ToolContextType, } from '../../../common/enum'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; import { ExternalToolMetadata } from '../../domain'; import { ExternalToolEntity } from '../../entity'; -import { ExternalToolCreateParams, ExternalToolResponse, ExternalToolSearchListResponse } from '../dto'; -import { ExternalToolMetadataResponse } from '../dto/response/external-tool-metadata.response'; +import { + ExternalToolCreateParams, + ExternalToolResponse, + ExternalToolSearchListResponse, + ExternalToolMetadataResponse, +} from '../dto'; describe('ToolController (API)', () => { let app: INestApplication; @@ -627,31 +631,16 @@ describe('ToolController (API)', () => { describe('[GET] tools/external-tools/:externalToolId/metadata', () => { describe('when user is not authenticated', () => { - const setup = async () => { + const setup = () => { const toolId: string = new ObjectId().toHexString(); - const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount: 2, - contextExternalToolCountPerContext: contextExternalToolCount, - }); - - const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); - await em.persistAndFlush([adminAccount, adminUser, externalToolEntity]); - em.clear(); - - const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); - - return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + return { toolId }; }; it('should return unauthorized', async () => { - const { externalToolEntity } = await setup(); + const { toolId } = setup(); - const response: Response = await testApiClient.get(`${externalToolEntity.id}/metadata`); + const response: Response = await testApiClient.get(`${toolId}/metadata`); expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); }); @@ -662,64 +651,37 @@ describe('ToolController (API)', () => { const toolId: string = new ObjectId().toHexString(); const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); - const schoolToolId: string = new ObjectId().toHexString(); - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( - { tool: externalToolEntity }, - schoolToolId - ); - const schoolToolId1: string = new ObjectId().toHexString(); - const schoolExternalToolEntity1: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( - { tool: externalToolEntity }, - schoolToolId1 - ); - - const courseExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextType: ContextExternalToolType.COURSE, - contextId: new ObjectId().toHexString(), + const school: SchoolEntity = schoolFactory.buildWithId(); + const schoolExternalToolEntitys: SchoolExternalToolEntity[] = schoolExternalToolEntityFactory.buildList(2, { + tool: externalToolEntity, + school, }); - const courseExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity1, - contextType: ContextExternalToolType.COURSE, - contextId: new ObjectId().toHexString(), - }); - const courseExternalToolEntity2: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, + + const courseTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(3, { + schoolTool: schoolExternalToolEntitys[0], contextType: ContextExternalToolType.COURSE, - contextId: new ObjectId().toHexString(), }); - const boardExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextType: ContextExternalToolType.BOARD_ELEMENT, - contextId: new ObjectId().toHexString(), - }); - const boardExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity1, + const boardTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { + schoolTool: schoolExternalToolEntitys[1], contextType: ContextExternalToolType.BOARD_ELEMENT, contextId: new ObjectId().toHexString(), }); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount: 2, - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 3, boardElement: 2 }, }); const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); await em.persistAndFlush([ adminAccount, adminUser, + school, externalToolEntity, - schoolExternalToolEntity, - schoolExternalToolEntity1, - courseExternalToolEntity, - courseExternalToolEntity1, - courseExternalToolEntity2, - boardExternalToolEntity, - boardExternalToolEntity1, + ...schoolExternalToolEntitys, + ...courseTools, + ...boardTools, ]); em.clear(); @@ -728,19 +690,21 @@ describe('ToolController (API)', () => { return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; }; - it('should return the metadata of externaltool', async () => { - const { loggedInClient, externalToolEntity, externalToolMetadata } = await setup(); + it('should return the metadata of externalTool', async () => { + const { loggedInClient, externalToolEntity } = await setup(); const response: Response = await loggedInClient.get(`${externalToolEntity.id}/metadata`); const body: ExternalToolMetadataResponse = response.body as ExternalToolMetadataResponse; expect(response.statusCode).toEqual(HttpStatus.OK); - expect(body).toBeDefined(); - expect(body).toMatchObject(externalToolMetadata); - expect(body.schoolExternalToolCount).toEqual(2); - expect(body.contextExternalToolCountPerContext).toHaveProperty('course', 3); - expect(body.contextExternalToolCountPerContext).toHaveProperty('boardElement', 2); + expect(body).toEqual({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: { + course: 3, + boardElement: 2, + }, + }); }); }); }); diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts index 16b4e1d60b4..8b8537e2f1d 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts @@ -1,12 +1,18 @@ import { ApiProperty } from '@nestjs/swagger'; import { ToolContextType } from '../../../../common/enum'; +import { ContextExternalToolType } from '../../../../context-external-tool/entity'; export class ExternalToolMetadataResponse { @ApiProperty() schoolExternalToolCount: number; - @ApiProperty() - contextExternalToolCountPerContext: Map; + @ApiProperty({ + type: 'object', + properties: Object.fromEntries( + Object.values(ContextExternalToolType).map((key: ContextExternalToolType) => [key, { type: 'number' }]) + ), + }) + contextExternalToolCountPerContext: Record; constructor(externalToolMetadataResponse: ExternalToolMetadataResponse) { this.schoolExternalToolCount = externalToolMetadataResponse.schoolExternalToolCount; 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 e9e5fafa376..4f532238863 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 @@ -6,3 +6,4 @@ export * from './context-external-tool-configuration-template.response'; export * from './context-external-tool-configuration-template-list.response'; export * from './school-external-tool-configuration-template.response'; export * from './school-external-tool-configuration-template-list.response'; +export * from './external-tool-metadata.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 6c2921f6870..448be593367 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 @@ -31,8 +31,8 @@ import { ExternalToolSearchParams, ExternalToolUpdateParams, SortExternalToolParams, + ExternalToolMetadataResponse, } from './dto'; -import { ExternalToolMetadataResponse } from './dto/response/external-tool-metadata.response'; @ApiTags('Tool') @Authenticate('jwt') @@ -172,6 +172,7 @@ export class ToolController { @ApiOperation({ summary: 'Gets the metadata of an external tool.' }) @ApiOkResponse({ description: 'Metadata of external tool fetched successfully.', + type: ExternalToolMetadataResponse, }) @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) async getMetaDataForExternalTool( diff --git a/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts index 7a4b98d8356..04492680ff7 100644 --- a/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts +++ b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts @@ -1,9 +1,7 @@ -import { ToolContextType } from '../../common/enum'; - export class ExternalToolMetadata { schoolExternalToolCount: number; - contextExternalToolCountPerContext: Map; + contextExternalToolCountPerContext: Record; constructor(externalToolMetadata: ExternalToolMetadata) { this.schoolExternalToolCount = externalToolMetadata.schoolExternalToolCount; diff --git a/apps/server/src/modules/tool/external-tool/external-tool.module.ts b/apps/server/src/modules/tool/external-tool/external-tool.module.ts index 6b86bba7a7b..1f9010516f7 100644 --- a/apps/server/src/modules/tool/external-tool/external-tool.module.ts +++ b/apps/server/src/modules/tool/external-tool/external-tool.module.ts @@ -5,6 +5,8 @@ import { OauthProviderServiceModule } from '@infra/oauth-provider'; import { EncryptionModule } from '@infra/encryption'; import { ExternalToolRepo } from '@shared/repo'; import { ToolConfigModule } from '../tool-config.module'; +import { ExternalToolMetadataMapper } from './mapper'; +import { ToolContextMapper } from '../common/mapper/tool-context.mapper'; import { ExternalToolConfigurationService, ExternalToolLogoService, @@ -29,6 +31,8 @@ import { CommonToolModule } from '../common'; ExternalToolLogoService, ExternalToolRepo, ExternalToolMetadataService, + ExternalToolMetadataMapper, + ToolContextMapper, ], exports: [ ExternalToolService, diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts index a0630d599b5..8d280ea6944 100644 --- a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts +++ b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts @@ -2,41 +2,10 @@ import { ExternalToolMetadataLoggable } from './external-tool-metadata-loggable' describe('ExternalToolMetadataLoggable', () => { describe('constructor', () => { - const setup = () => { - const msg = 'message'; - - return { msg }; - }; - it('should create an instance of ExternalToolLogoFetchedLoggable', () => { - const { msg } = setup(); - - const loggable = new ExternalToolMetadataLoggable(msg); + const loggable = new ExternalToolMetadataLoggable(); expect(loggable).toBeInstanceOf(ExternalToolMetadataLoggable); }); }); - - describe('getLogMessage', () => { - const setup = () => { - const msg = 'message'; - const loggable = new ExternalToolMetadataLoggable(msg); - - return { loggable, msg }; - }; - - it('should return a loggable message', () => { - const { loggable, msg } = setup(); - - const message = loggable.getLogMessage(); - - expect(message).toEqual({ - type: 'EXTERNAL_TOOL_METADATA', - message: 'No related tools found, return empty external tool metadata', - data: { - msg, - }, - }); - }); - }); }); diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts index 163523b3494..cf6b0132ece 100644 --- a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts +++ b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts @@ -1,15 +1,10 @@ import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; export class ExternalToolMetadataLoggable implements Loggable { - constructor(private readonly msg: string) {} - getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { return { type: 'EXTERNAL_TOOL_METADATA', message: 'No related tools found, return empty external tool metadata', - data: { - msg: this.msg, - }, }; } } diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts index 543acd46f87..a8b4c6cf256 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts @@ -1,5 +1,7 @@ -import { ExternalToolMetadataResponse } from '../controller/dto/response/external-tool-metadata.response'; -import { ExternalToolMetadata } from '../domain/external-tool-metadata'; +import { ToolContextType } from '../../common/enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; +import { ExternalToolMetadataResponse } from '../controller/dto'; +import { ExternalToolMetadata } from '../domain'; export class ExternalToolMetadataMapper { static mapToExternalToolMetadataResponse(externalToolMetadata: ExternalToolMetadata): ExternalToolMetadataResponse { diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts index 0590b08ce3e..c2caf123815 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts @@ -3,11 +3,9 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { externalToolFactory, legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; -import { Logger } from '@src/core/logger'; -import { ToolContextType } from '../../common/enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ExternalTool, ExternalToolMetadata } from '../domain'; -import { ExternalToolMetadataLoggable } from '../loggable/external-tool-metadata-loggable'; import { ExternalToolMetadataService } from './external-tool-metadata.service'; describe('ExternalToolMetadataService', () => { @@ -17,7 +15,6 @@ describe('ExternalToolMetadataService', () => { let externalToolRepo: DeepMocked; let schoolExternalToolRepo: DeepMocked; let contextExternalToolRepo: DeepMocked; - let logger: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -35,10 +32,6 @@ describe('ExternalToolMetadataService', () => { provide: ContextExternalToolRepo, useValue: createMock(), }, - { - provide: Logger, - useValue: createMock(), - }, ], }).compile(); @@ -46,7 +39,6 @@ describe('ExternalToolMetadataService', () => { externalToolRepo = module.get(ExternalToolRepo); schoolExternalToolRepo = module.get(SchoolExternalToolRepo); contextExternalToolRepo = module.get(ContextExternalToolRepo); - logger = module.get(Logger); }); afterAll(async () => { @@ -79,18 +71,14 @@ describe('ExternalToolMetadataService', () => { schoolToolId1 ); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount: 2, - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, }); externalToolRepo.findById.mockResolvedValue(externalTool); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); - contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(3); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); return { toolId, @@ -103,7 +91,7 @@ describe('ExternalToolMetadataService', () => { it('should call the repo to get schoolExternalTools by externalToolId', async () => { const { toolId } = setup(); - await service.getMetaData(toolId); + await service.getMetadata(toolId); expect(schoolExternalToolRepo.findByExternalToolId).toHaveBeenCalledWith(toolId); }); @@ -111,23 +99,23 @@ describe('ExternalToolMetadataService', () => { it('should call the repo to count contextExternalTools by schoolExternalToolId and context', async () => { const { toolId, schoolExternalTool, schoolExternalTool1 } = setup(); - await service.getMetaData(toolId); + await service.getMetadata(toolId); - expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledWith( - ToolContextType.COURSE, + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ContextExternalToolType.COURSE, [schoolExternalTool.id, schoolExternalTool1.id] ); - expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledWith( - ToolContextType.BOARD_ELEMENT, + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ContextExternalToolType.BOARD_ELEMENT, [schoolExternalTool.id, schoolExternalTool1.id] ); - expect(contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType).toHaveBeenCalledTimes(2); + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledTimes(2); }); it('should return externalToolMetadata', async () => { const { toolId, externalToolMetadata } = setup(); - const result: ExternalToolMetadata = await service.getMetaData(toolId); + const result: ExternalToolMetadata = await service.getMetadata(toolId); expect(result).toEqual(externalToolMetadata); }); @@ -138,18 +126,14 @@ describe('ExternalToolMetadataService', () => { const toolId: string = new ObjectId().toHexString(); const externalToolEntity: ExternalTool = externalToolFactory.buildWithId(undefined, toolId); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 0); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 0); - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount: 0, - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, }); externalToolRepo.findById.mockResolvedValue(externalToolEntity); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([]); - contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(0); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); return { toolId, @@ -161,14 +145,9 @@ describe('ExternalToolMetadataService', () => { it('should return empty externalToolMetadata', async () => { const { toolId, externalToolMetadata } = setup(); - const result: ExternalToolMetadata = await service.getMetaData(toolId); + const result: ExternalToolMetadata = await service.getMetadata(toolId); expect(result).toEqual(externalToolMetadata); - expect(logger.info).toHaveBeenCalledWith( - new ExternalToolMetadataLoggable( - `There are no such schoolExternalTools for toolId: ${toolId}, returning empty metadata.` - ) - ); }); }); }); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index ed494756e49..e47da7ec630 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -1,34 +1,30 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; -import { Logger } from '@src/core/logger'; import { ToolContextType } from '../../common/enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ExternalToolMetadata } from '../domain'; -import { ExternalToolMetadataLoggable } from '../loggable'; +import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; @Injectable() export class ExternalToolMetadataService { constructor( - private readonly logger: Logger, private readonly externalToolRepo: ExternalToolRepo, private readonly schoolToolRepo: SchoolExternalToolRepo, private readonly contextToolRepo: ContextExternalToolRepo ) {} - async getMetaData(toolId: EntityId): Promise { - const schoolExternalTools = await this.schoolToolRepo.findByExternalToolId(toolId); + async getMetadata(toolId: EntityId): Promise { + const schoolExternalTools: SchoolExternalTool[] = await this.schoolToolRepo.findByExternalToolId(toolId); if (schoolExternalTools.length < 1) { - this.logger.info( - new ExternalToolMetadataLoggable( - `There are no such schoolExternalTools for toolId: ${toolId}, returning empty metadata.` - ) - ); - - const externalToolMetadata = this.createExternalToolMetadata(0, [ - { contextType: ToolContextType.COURSE, count: 0 }, - { contextType: ToolContextType.BOARD_ELEMENT, count: 0 }, - ]); + const externalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 0, + contextExternalToolCountPerContext: { + [ToolContextMapper.contextMapping[ToolContextType.COURSE]]: 0, + [ToolContextMapper.contextMapping[ToolContextType.BOARD_ELEMENT]]: 0, + }, + }); return externalToolMetadata; } @@ -39,43 +35,33 @@ export class ExternalToolMetadataService { schoolExternalTool.id as string ); - const contextTools = await Promise.all( - Object.values(ToolContextType).map(async (contextType: ToolContextType) => { - const countPerContext: number = - await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType( - contextType, + const contextTools: { contextType: ContextExternalToolType; countPerContext: number }[] = await Promise.all( + Object.values(ToolContextType).map( + async ( + contextType: ToolContextType + ): Promise<{ contextType: ContextExternalToolType; countPerContext: number }> => { + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( + ToolContextMapper.contextMapping[contextType], schoolExternalToolIds ); - return { contextType, count: countPerContext }; - }) - ); - - const externaltoolMetadata = this.createExternalToolMetadata(schoolExternalTools.length, contextTools); + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; - return externaltoolMetadata; - } + return { contextType: type, countPerContext }; + } + ) + ); - private createContextExternalToolMetaData( - toolCountPerContext: { contextType: ToolContextType; count: number }[] - ): Map { - const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; count: number }) => [ - contextExternalToolCountPerSchool.contextType, - contextExternalToolCountPerSchool.count, + const tools = Object.fromEntries( + contextTools.map((contextTool): [ContextExternalToolType, number] => [ + contextTool.contextType, + contextTool.countPerContext, ]) ); - return contextExternalToolMetadata; - } - - private createExternalToolMetadata( - schoolExternalToolCount: number, - contextExternalToolCountPerContext: { contextType: ToolContextType; count: number }[] - ): ExternalToolMetadata { const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount, - contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), + schoolExternalToolCount: schoolExternalTools.length, + contextExternalToolCountPerContext: tools, }); return externaltoolMetadata; 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 f613e322305..501eef61de9 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 @@ -2,16 +2,15 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { IFindOptions, Permission, SortOrder, User } from '@shared/domain'; +import { IFindOptions, Permission, Role, SortOrder, User } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; -import { setupEntities, userFactory } from '@shared/testing'; +import { roleFactory, setupEntities, userFactory } from '@shared/testing'; import { externalToolFactory, oauth2ToolConfigFactory, } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { ICurrentUser } from '@modules/authentication'; -import { AuthorizationService } from '@modules/authorization'; -import { ToolContextType } from '../../common/enum'; +import { Action, AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool, ExternalToolMetadata, Oauth2ToolConfig } from '../domain'; import { @@ -483,40 +482,63 @@ describe('ExternalToolUc', () => { }); describe('getMetadataForExternalTool', () => { - describe('Authorization', () => { + describe('when authorize user', () => { const setupMetadata = () => { const toolId: string = new ObjectId().toHexString(); + const tool: ExternalTool = externalToolFactory.buildWithId({ id: toolId }, toolId); + + const role: Role = roleFactory.buildWithId({ permissions: [Permission.TOOL_ADMIN] }); + const user: User = userFactory.buildWithId({ roles: [role] }); + const currentUser: ICurrentUser = { userId: user.id } as ICurrentUser; + const context = { action: Action.read, requiredPermissions: [Permission.TOOL_ADMIN] }; + + externalToolService.findById.mockResolvedValue(tool); + authorizationService.getUserWithPermissions.mockResolvedValue(user); + return { + user, + currentUser, toolId, + tool, + context, }; }; - it('should call getUserWithPermissions', async () => { - const { currentUser } = setupAuthorization(); - const { toolId } = setupMetadata(); + it('get user with permissions', async () => { + const { toolId, user } = setupMetadata(); - await uc.getMetadataForExternalTool(currentUser.userId, toolId); + await uc.getMetadataForExternalTool(user.id, toolId); - expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(currentUser.userId); + expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(user.id); }); - it('should successfully check the user permission with the authorization service', async () => { - const { currentUser, user } = setupAuthorization(); - const { toolId } = setupMetadata(); + it('should check that the user has TOOL_ADMIN permission', async () => { + const { user, tool } = setupMetadata(); - await uc.getMetadataForExternalTool(currentUser.userId, toolId); + await uc.getMetadataForExternalTool(user.id, tool.id!); expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.TOOL_ADMIN]); }); + }); - it('should throw if the user has insufficient permission to get an external tool', async () => { - const { currentUser } = setupAuthorization(); - const { toolId } = setupMetadata(); - authorizationService.checkAllPermissions.mockImplementation(() => { - throw new UnauthorizedException(); - }); + describe('when user has insufficient permission to get an metadata for external tool ', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); - const result: Promise = uc.getMetadataForExternalTool(currentUser.userId, toolId); + const user: User = userFactory.buildWithId(); + + authorizationService.getUserWithPermissions.mockRejectedValue(new UnauthorizedException()); + + return { + user, + toolId, + }; + }; + + it('should throw UnauthorizedException ', async () => { + const { toolId, user } = setupMetadata(); + + const result: Promise = uc.getMetadataForExternalTool(user.id, toolId); await expect(result).rejects.toThrow(UnauthorizedException); }); @@ -526,31 +548,40 @@ describe('ExternalToolUc', () => { const setupMetadata = () => { const toolId: string = new ObjectId().toHexString(); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount: 2, - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 3, 'board-element': 3 }, }); - externalToolMetadataService.getMetaData.mockResolvedValue(externalToolMetadata); + externalToolMetadataService.getMetadata.mockResolvedValue(externalToolMetadata); + + const user: User = userFactory.buildWithId(); + const currentUser: ICurrentUser = { userId: user.id } as ICurrentUser; + + authorizationService.getUserWithPermissions.mockResolvedValue(user); return { + user, + currentUser, toolId, externalToolMetadata, }; }; - it('should call the service to get metadata', async () => { - const { currentUser } = setupAuthorization(); - - const { toolId } = setupMetadata(); + it('get metadata for external tool', async () => { + const { toolId, currentUser } = setupMetadata(); await uc.getMetadataForExternalTool(currentUser.userId, toolId); - expect(externalToolMetadataService.getMetaData).toHaveBeenCalledWith(toolId); + expect(externalToolMetadataService.getMetadata).toHaveBeenCalledWith(toolId); + }); + + it('return metadata of external tool', async () => { + const { toolId, currentUser, externalToolMetadata } = setupMetadata(); + + const result = await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(result).toEqual(externalToolMetadata); }); }); }); 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 e30ec56bb34..c8cbae04af5 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,7 +18,7 @@ export class ExternalToolUc { private readonly authorizationService: AuthorizationService, private readonly toolValidationService: ExternalToolValidationService, private readonly externalToolLogoService: ExternalToolLogoService, - private readonly externaltoolMetadataService: ExternalToolMetadataService + private readonly externalToolMetadataService: ExternalToolMetadataService ) {} async createExternalTool(userId: EntityId, externalToolCreate: ExternalToolCreate): Promise { @@ -81,9 +81,11 @@ export class ExternalToolUc { } async getMetadataForExternalTool(userId: EntityId, toolId: EntityId): Promise { + // TODO ticket: https://ticketsystem.dbildungscloud.de/browse/N21-1496 await this.ensurePermission(userId, Permission.TOOL_ADMIN); - const metadata: ExternalToolMetadata = await this.externaltoolMetadataService.getMetaData(toolId); + const metadata: ExternalToolMetadata = await this.externalToolMetadataService.getMetadata(toolId); + return metadata; } diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 89784b09750..ac265658487 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -15,10 +15,9 @@ import { } from '@shared/testing'; import { ServerTestModule } from '@modules/server'; import { Response } from 'supertest'; -import { ToolContextType } from '../../../common/enum'; import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; -import { ExternalToolMetadata } from '../../../external-tool/domain'; +import { ExternalToolMetadataResponse } from '../../../external-tool/controller/dto'; import { ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; import { @@ -27,8 +26,8 @@ import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, + SchoolExternalToolMetadataResponse, } from '../dto'; -import { SchoolExternalToolMetadataResponse } from '../dto/school-external-tool-metadata.response'; describe('ToolSchoolController (API)', () => { let app: INestApplication; @@ -497,29 +496,15 @@ describe('ToolSchoolController (API)', () => { describe('[GET] tools/school-external-tools/:schoolExternalToolId/metadata', () => { describe('when user is not authenticated', () => { - const setup = async () => { + const setup = () => { const toolId: string = new ObjectId().toHexString(); const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); - const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount: 2, - contextExternalToolCountPerContext: contextExternalToolCount, - }); - - const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); - await em.persistAndFlush([adminAccount, adminUser, externalToolEntity]); - em.clear(); - - const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); - - return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + return { toolId, externalToolEntity }; }; it('should return unauthorized', async () => { - const { externalToolEntity } = await setup(); + const { externalToolEntity } = setup(); const response: Response = await testApiClient.get(`${externalToolEntity.id}/metadata`); @@ -536,41 +521,20 @@ describe('ToolSchoolController (API)', () => { schoolToolId ); - const courseExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextType: ContextExternalToolType.COURSE, - contextId: new ObjectId().toHexString(), - }); - - const courseExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + const courseExternalToolEntitys: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(3, { schoolTool: schoolExternalToolEntity, contextType: ContextExternalToolType.COURSE, contextId: new ObjectId().toHexString(), }); - const courseExternalToolEntity2: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextType: ContextExternalToolType.COURSE, - contextId: new ObjectId().toHexString(), - }); - - const boardExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextType: ContextExternalToolType.BOARD_ELEMENT, - contextId: new ObjectId().toHexString(), - }); - - const boardExternalToolEntity1: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + const boardExternalToolEntitys: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { schoolTool: schoolExternalToolEntity, contextType: ContextExternalToolType.BOARD_ELEMENT, contextId: new ObjectId().toHexString(), }); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 2); const schoolExternalToolMetadata: SchoolExternalToolMetadataResponse = new SchoolExternalToolMetadataResponse({ - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 3, boardElement: 2 }, }); const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ @@ -581,11 +545,8 @@ describe('ToolSchoolController (API)', () => { adminAccount, adminUser, schoolExternalToolEntity, - courseExternalToolEntity, - courseExternalToolEntity1, - courseExternalToolEntity2, - boardExternalToolEntity, - boardExternalToolEntity1, + ...courseExternalToolEntitys, + ...boardExternalToolEntitys, ]); em.clear(); @@ -595,17 +556,19 @@ describe('ToolSchoolController (API)', () => { }; it('should return the metadata of schoolExternalTool', async () => { - const { loggedInClient, schoolExternalToolEntity, schoolExternalToolMetadata } = await setup(); + const { loggedInClient, schoolExternalToolEntity } = await setup(); const response: Response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); const body: SchoolExternalToolMetadataResponse = response.body as SchoolExternalToolMetadataResponse; - expect(body).toBeDefined(); expect(response.statusCode).toEqual(HttpStatus.OK); - expect(body).toMatchObject(schoolExternalToolMetadata); - expect(body.contextExternalToolCountPerContext).toHaveProperty('course', 3); - expect(body.contextExternalToolCountPerContext).toHaveProperty('boardElement', 2); + expect(body).toEqual({ + contextExternalToolCountPerContext: { + course: 3, + boardElement: 2, + }, + }); }); }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts index 5c3075e1cd2..595b16d7f9c 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts @@ -5,3 +5,4 @@ export * from './custom-parameter-entry.response'; export * from './school-external-tool-post.params'; export * from './school-external-tool-search.params'; export * from './school-external-tool-search-list.response'; +export * from './school-external-tool-metadata.response'; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts index 59cbdc91152..4715ceccd98 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts @@ -1,9 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { ToolContextType } from '../../../common/enum'; +import { ContextExternalToolType } from '../../../context-external-tool/entity'; export class SchoolExternalToolMetadataResponse { @ApiProperty() - contextExternalToolCountPerContext: Map; + contextExternalToolCountPerContext: Record; constructor(schoolExternalToolMetadataResponse: SchoolExternalToolMetadataResponse) { this.contextExternalToolCountPerContext = schoolExternalToolMetadataResponse.contextExternalToolCountPerContext; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts index 34e1eb6396d..ccfef902bee 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts @@ -26,11 +26,11 @@ import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, + SchoolExternalToolMetadataResponse, } from './dto'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolUc } from '../uc'; import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; -import { SchoolExternalToolMetadataResponse } from './dto/school-external-tool-metadata.response'; @ApiTags('Tool') @Authenticate('jwt') @@ -146,6 +146,7 @@ export class ToolSchoolController { @ApiOperation({ summary: 'Gets the metadata of an school external tool.' }) @ApiOkResponse({ description: 'Metadata of school external tool fetched successfully.', + type: SchoolExternalToolMetadataResponse, }) @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) async getMetaDataForExternalTool( diff --git a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts index ec03f944360..4cccdfe11a1 100644 --- a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts +++ b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts @@ -1,7 +1,5 @@ -import { ToolContextType } from '../../common/enum'; - export class SchoolExternalToolMetadata { - contextExternalToolCountPerContext: Map; + contextExternalToolCountPerContext: Record; constructor(schoolExternalToolMetadata: SchoolExternalToolMetadata) { this.contextExternalToolCountPerContext = schoolExternalToolMetadata.contextExternalToolCountPerContext; diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts index ef397dbe0f5..1e42f22ebf2 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts @@ -1,5 +1,5 @@ -import { SchoolExternalToolMetadataResponse } from '../controller/dto/school-external-tool-metadata.response'; -import { SchoolExternalToolMetadata } from '../domain/school-external-tool-metadata'; +import { SchoolExternalToolMetadataResponse } from '../controller/dto'; +import { SchoolExternalToolMetadata } from '../domain'; export class SchoolExternalToolMetadataMapper { static mapToSchoolExternalToolMetadataResponse( diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts index 28cc5fee004..a9a9a8de343 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -65,16 +65,12 @@ describe('SchoolExternalToolMetadataService', () => { schoolToolId1 ); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 3); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 3); - const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 3, 'board-element': 3 }, }); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); - contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(3); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); return { schoolToolId, @@ -108,16 +104,12 @@ describe('SchoolExternalToolMetadataService', () => { schoolToolId1 ); - const contextExternalToolCount = new Map(); - contextExternalToolCount.set(ToolContextType.COURSE, 0); - contextExternalToolCount.set(ToolContextType.BOARD_ELEMENT, 0); - const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: contextExternalToolCount, + contextExternalToolCountPerContext: { course: 0, 'board-element': 0 }, }); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); - contextExternalToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType.mockResolvedValue(0); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); return { schoolToolId, diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index 438076a2c55..a27e1420d1e 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { ToolContextType } from '../../common/enum'; +import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; import { SchoolExternalToolMetadata } from '../domain'; @Injectable() @@ -12,40 +14,31 @@ export class SchoolExternalToolMetadataService { ) {} async getMetadata(schoolExternalToolId: EntityId) { - const contextTools = await Promise.all( - Object.values(ToolContextType).map(async (contextType: ToolContextType) => { - const countPerContext: number = - await this.contextToolRepo.countContextExternalToolsBySchoolToolIdsAndContextType(contextType, [ - schoolExternalToolId.toString(), - ]); - - return { contextType, count: countPerContext }; - }) + const contextTools: { contextType: ContextExternalToolType; countPerContext: number }[] = await Promise.all( + Object.values(ToolContextType).map( + async ( + contextType: ToolContextType + ): Promise<{ contextType: ContextExternalToolType; countPerContext: number }> => { + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( + ToolContextMapper.contextMapping[contextType], + [schoolExternalToolId.toString()] + ); + + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; + + return { contextType: type, countPerContext }; + } + ) ); - - const schoolExternaltoolMetadata = this.createSchoolExternalToolMetadata(contextTools); - - return schoolExternaltoolMetadata; - } - - private createContextExternalToolMetaData( - toolCountPerContext: { contextType: ToolContextType; count: number }[] - ): Map { - const contextExternalToolMetadata: Map = new Map( - toolCountPerContext.map((contextExternalToolCountPerSchool: { contextType: ToolContextType; count: number }) => [ - contextExternalToolCountPerSchool.contextType, - contextExternalToolCountPerSchool.count, + const tools = Object.fromEntries( + contextTools.map((contextTool): [ContextExternalToolType, number] => [ + contextTool.contextType, + contextTool.countPerContext, ]) ); - return contextExternalToolMetadata; - } - - private createSchoolExternalToolMetadata( - contextExternalToolCountPerContext: { contextType: ToolContextType; count: number }[] - ): SchoolExternalToolMetadata { const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: this.createContextExternalToolMetaData(contextExternalToolCountPerContext), + contextExternalToolCountPerContext: tools, }); return schoolExternaltoolMetadata; diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts index a8a96e04624..6f3512f6a0d 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts @@ -371,7 +371,7 @@ describe('SchoolExternalToolUc', () => { }); describe('getMetadataForSchoolExternalTool', () => { - describe('Authorization', () => { + describe('when authorize user', () => { const setupMetadata = () => { const toolId = new ObjectId().toHexString(); const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ id: toolId }, toolId); diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts index 8c4076fe9b3..32faf630263 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts @@ -443,7 +443,7 @@ describe('ContextExternalToolRepo', () => { it('should return correct results', async () => { const { schoolExternalTool, schoolExternalTool1 } = await setup(); - const result = await repo.countContextExternalToolsBySchoolToolIdsAndContextType(ToolContextType.COURSE, [ + const result = await repo.countBySchoolToolIdsAndContextType(ContextExternalToolType.COURSE, [ schoolExternalTool.id, schoolExternalTool1.id, ]); @@ -457,14 +457,11 @@ describe('ContextExternalToolRepo', () => { const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); - const contextExternalTool = contextExternalToolEntityFactory.buildWithId({ - contextType: ContextExternalToolType.COURSE, - schoolTool: schoolExternalTool, - }); - const contextExternalTool1 = contextExternalToolEntityFactory.buildWithId({ + const contextExternalTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { contextType: ContextExternalToolType.COURSE, schoolTool: schoolExternalTool, }); + const contextExternalTool2 = contextExternalToolEntityFactory.buildWithId({ contextType: ContextExternalToolType.BOARD_ELEMENT, schoolTool: schoolExternalTool, @@ -481,8 +478,7 @@ describe('ContextExternalToolRepo', () => { await em.persistAndFlush([ schoolExternalTool, schoolExternalTool1, - contextExternalTool, - contextExternalTool1, + ...contextExternalTools, contextExternalTool2, contextExternalTool3, contextExternalTool4, @@ -497,10 +493,10 @@ describe('ContextExternalToolRepo', () => { it('should return correct results', async () => { const { schoolExternalTool, schoolExternalTool1 } = await setup(); - const result = await repo.countContextExternalToolsBySchoolToolIdsAndContextType( - ToolContextType.BOARD_ELEMENT, - [schoolExternalTool.id, schoolExternalTool1.id] - ); + const result = await repo.countBySchoolToolIdsAndContextType(ContextExternalToolType.BOARD_ELEMENT, [ + schoolExternalTool.id, + schoolExternalTool1.id, + ]); expect(result).toEqual(2); }); diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts index 2bbcd88f0a1..31053cd48b5 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts @@ -53,10 +53,7 @@ export class ContextExternalToolRepo extends BaseDORepo< return dos; } - async countContextExternalToolsBySchoolToolIdsAndContextType( - contextType: ToolContextType, - schoolExternalToolIds: string[] - ) { + async countBySchoolToolIdsAndContextType(contextType: ContextExternalToolType, schoolExternalToolIds: string[]) { const contextExternalToolCount = await this._em.count(this.entityName, { $and: [{ schoolTool: { $in: schoolExternalToolIds }, contextType }], }); diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts index 978193b54c0..c17e4d2ac17 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts @@ -38,17 +38,13 @@ export class SchoolExternalToolRepo extends BaseDORepo< return domainObjects; } - async countSchoolExternalToolsByExternalToolId(toolId: string): Promise { - const schoolExternalToolCount: number = await this._em.count(this.entityName, { tool: toolId }); - return schoolExternalToolCount; - } - async findBySchoolId(schoolId: string): Promise { const entities: SchoolExternalToolEntity[] = await this._em.find(this.entityName, { school: schoolId }); const domainObjects: SchoolExternalTool[] = entities.map((entity: SchoolExternalToolEntity): SchoolExternalTool => { const domainObject: SchoolExternalTool = this.mapEntityToDO(entity); return domainObject; }); + return domainObjects; } From 44327fd4a62041df256819ae579901cdf38dc5c5 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 06:55:03 +0100 Subject: [PATCH 11/20] N21-1319 clean up --- .../external-tool-metadata-loggable.spec.ts | 11 ---- .../external-tool-metadata-loggable.ts | 10 ---- ...ext-external-tool.repo.integration.spec.ts | 50 +++++-------------- 3 files changed, 12 insertions(+), 59 deletions(-) delete mode 100644 apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts delete mode 100644 apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts deleted file mode 100644 index 8d280ea6944..00000000000 --- a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ExternalToolMetadataLoggable } from './external-tool-metadata-loggable'; - -describe('ExternalToolMetadataLoggable', () => { - describe('constructor', () => { - it('should create an instance of ExternalToolLogoFetchedLoggable', () => { - const loggable = new ExternalToolMetadataLoggable(); - - expect(loggable).toBeInstanceOf(ExternalToolMetadataLoggable); - }); - }); -}); diff --git a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts b/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts deleted file mode 100644 index cf6b0132ece..00000000000 --- a/apps/server/src/modules/tool/external-tool/loggable/external-tool-metadata-loggable.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; - -export class ExternalToolMetadataLoggable implements Loggable { - getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { - return { - type: 'EXTERNAL_TOOL_METADATA', - message: 'No related tools found, return empty external tool metadata', - }; - } -} diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts index 32faf630263..c6a684a377d 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts @@ -395,31 +395,18 @@ describe('ContextExternalToolRepo', () => { }); }); - describe('countContextExternalToolsBySchoolToolIdsAndContextType', () => { + describe('countBySchoolToolIdsAndContextType', () => { describe('when a ContextExternalTool is found for course context', () => { const setup = async () => { const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); - const contextExternalTool = contextExternalToolEntityFactory.buildWithId({ - contextType: ContextExternalToolType.COURSE, - schoolTool: schoolExternalTool, - }); - const contextExternalTool1 = contextExternalToolEntityFactory.buildWithId({ + const contextExternalTool = contextExternalToolEntityFactory.buildList(4, { contextType: ContextExternalToolType.COURSE, schoolTool: schoolExternalTool, }); - const contextExternalTool2 = contextExternalToolEntityFactory.buildWithId({ - contextType: ContextExternalToolType.BOARD_ELEMENT, - schoolTool: schoolExternalTool, - }); - - const contextExternalTool3 = contextExternalToolEntityFactory.buildWithId({ - contextType: ContextExternalToolType.BOARD_ELEMENT, - schoolTool: schoolExternalTool1, - }); - const contextExternalTool4 = contextExternalToolEntityFactory.buildWithId({ + const contextExternalTool3 = contextExternalToolEntityFactory.buildList(2, { contextType: ContextExternalToolType.COURSE, schoolTool: schoolExternalTool1, }); @@ -427,11 +414,8 @@ describe('ContextExternalToolRepo', () => { await em.persistAndFlush([ schoolExternalTool, schoolExternalTool1, - contextExternalTool, - contextExternalTool1, - contextExternalTool2, - contextExternalTool3, - contextExternalTool4, + ...contextExternalTool, + ...contextExternalTool3, ]); return { @@ -448,7 +432,7 @@ describe('ContextExternalToolRepo', () => { schoolExternalTool1.id, ]); - expect(result).toEqual(3); + expect(result).toEqual(6); }); }); @@ -457,31 +441,21 @@ describe('ContextExternalToolRepo', () => { const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); - const contextExternalTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { - contextType: ContextExternalToolType.COURSE, - schoolTool: schoolExternalTool, - }); - - const contextExternalTool2 = contextExternalToolEntityFactory.buildWithId({ + const contextExternalTool1 = contextExternalToolEntityFactory.buildList(3, { contextType: ContextExternalToolType.BOARD_ELEMENT, schoolTool: schoolExternalTool, }); - const contextExternalTool3 = contextExternalToolEntityFactory.buildWithId({ + + const contextExternalTool2 = contextExternalToolEntityFactory.buildList(2, { contextType: ContextExternalToolType.BOARD_ELEMENT, schoolTool: schoolExternalTool1, }); - const contextExternalTool4 = contextExternalToolEntityFactory.buildWithId({ - contextType: ContextExternalToolType.COURSE, - schoolTool: schoolExternalTool1, - }); await em.persistAndFlush([ schoolExternalTool, schoolExternalTool1, - ...contextExternalTools, - contextExternalTool2, - contextExternalTool3, - contextExternalTool4, + ...contextExternalTool1, + ...contextExternalTool2, ]); return { @@ -498,7 +472,7 @@ describe('ContextExternalToolRepo', () => { schoolExternalTool1.id, ]); - expect(result).toEqual(2); + expect(result).toEqual(5); }); }); }); From 8a6be9ad3d67454cdfa3a5e704268e8585009916 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 06:56:56 +0100 Subject: [PATCH 12/20] N21-1319 clean barrelfile --- apps/server/src/modules/tool/external-tool/loggable/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/server/src/modules/tool/external-tool/loggable/index.ts b/apps/server/src/modules/tool/external-tool/loggable/index.ts index 50eb1adbd8a..67d65be236d 100644 --- a/apps/server/src/modules/tool/external-tool/loggable/index.ts +++ b/apps/server/src/modules/tool/external-tool/loggable/index.ts @@ -3,4 +3,3 @@ export * from './external-tool-logo-size-exceeded-loggable-exception'; export * from './external-tool-logo-fetched-loggable'; export * from './external-tool-logo-fetch-failed-loggable-exception'; export * from './external-tool-logo-wrong-file-type-loggable-exception'; -export * from './external-tool-metadata-loggable'; From 65542673c0577491ae97441545db75b0b56bf94b Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 08:59:16 +0100 Subject: [PATCH 13/20] N21-1319 clean up --- .../api-test/tool-school.api.spec.ts | 9 ++++++--- .../school-external-tool-metadata.response.ts | 7 ++++++- ...ool-external-tool.repo.integration.spec.ts | 19 ------------------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 9ea75160d75..7327dc94a6f 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -1,10 +1,12 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, SchoolEntity, User } from '@shared/domain'; import { accountFactory, + contextExternalToolEntityFactory, customParameterEntityFactory, externalToolEntityFactory, schoolExternalToolEntityFactory, @@ -13,7 +15,8 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; import { CustomParameterScope, CustomParameterType, ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; import { @@ -520,7 +523,7 @@ describe('ToolSchoolController (API)', () => { it('should return unauthorized', async () => { const { externalToolEntity } = setup(); - const response: Response = await testApiClient.get(`${externalToolEntity.id}/metadata`); + const response = await testApiClient.get(`${externalToolEntity.id}/metadata`); expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); }); @@ -572,7 +575,7 @@ describe('ToolSchoolController (API)', () => { it('should return the metadata of schoolExternalTool', async () => { const { loggedInClient, schoolExternalToolEntity } = await setup(); - const response: Response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); + const response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); const body: SchoolExternalToolMetadataResponse = response.body as SchoolExternalToolMetadataResponse; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts index 4715ceccd98..db4806d23ec 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts @@ -2,7 +2,12 @@ import { ApiProperty } from '@nestjs/swagger'; import { ContextExternalToolType } from '../../../context-external-tool/entity'; export class SchoolExternalToolMetadataResponse { - @ApiProperty() + @ApiProperty({ + type: 'object', + properties: Object.fromEntries( + Object.values(ContextExternalToolType).map((key: ContextExternalToolType) => [key, { type: 'number' }]) + ), + }) contextExternalToolCountPerContext: Record; constructor(schoolExternalToolMetadataResponse: SchoolExternalToolMetadataResponse) { diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts index 0721e8ad08c..47196013b63 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.integration.spec.ts @@ -112,25 +112,6 @@ describe('SchoolExternalToolRepo', () => { }); }); - describe('countSchoolExternalToolsByExternalToolId', () => { - const setup = async () => { - const { externalToolEntity, school, schoolExternalTool1, schoolExternalTool3 } = createTools(); - - await em.persistAndFlush([school, externalToolEntity, schoolExternalTool1, schoolExternalTool3]); - em.clear(); - - return { externalToolEntity, school, schoolExternalTool1, schoolExternalTool3 }; - }; - - it('should find all SchoolExternalTools with reference to a given ExternalTool', async () => { - const { externalToolEntity } = await setup(); - - const result: number = await repo.countSchoolExternalToolsByExternalToolId(externalToolEntity.id); - - expect(result).toEqual(2); - }); - }); - describe('findBySchoolId', () => { describe('when searching for SchoolExternalTools by school id', () => { const setup = async () => { From c7c80678be38f739b775af8ea06d2d99c30468f2 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 09:52:31 +0100 Subject: [PATCH 14/20] N21-1319 clean up --- .../dto/response/external-tool-metadata.response.ts | 1 - .../mapper/external-tool-metadata.mapper.ts | 2 -- .../school-external-tool.module.ts | 10 +++++++--- .../school-external-tool-metadata.service.spec.ts | 3 +-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts index 8b8537e2f1d..d38b48cc503 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts @@ -1,5 +1,4 @@ import { ApiProperty } from '@nestjs/swagger'; -import { ToolContextType } from '../../../../common/enum'; import { ContextExternalToolType } from '../../../../context-external-tool/entity'; export class ExternalToolMetadataResponse { diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts index a8b4c6cf256..b3d6555f898 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts @@ -1,5 +1,3 @@ -import { ToolContextType } from '../../common/enum'; -import { ContextExternalToolType } from '../../context-external-tool/entity'; import { ExternalToolMetadataResponse } from '../controller/dto'; import { ExternalToolMetadata } from '../domain'; diff --git a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts index b9fe973f770..93d4c4f6705 100644 --- a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts +++ b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts @@ -1,12 +1,16 @@ import { Module } from '@nestjs/common'; import { CommonToolModule } from '../common'; -import { SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService } from './service'; +import { + SchoolExternalToolService, + SchoolExternalToolValidationService, + SchoolExternalToolMetadataService, +} from './service'; import { ExternalToolModule } from '../external-tool'; import { ToolConfigModule } from '../tool-config.module'; @Module({ imports: [CommonToolModule, ExternalToolModule, ToolConfigModule], - providers: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], - exports: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], + providers: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], + exports: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], }) export class SchoolExternalToolModule {} diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts index a9a9a8de343..082e3188f3b 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -4,7 +4,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { ToolContextType } from '../../common/enum'; import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; import { SchoolExternalToolMetadataService } from './school-external-tool-metadata.service'; @@ -66,7 +65,7 @@ describe('SchoolExternalToolMetadataService', () => { ); const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: { course: 3, 'board-element': 3 }, + contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, }); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); From 68869cdb71a4de9960a189b98917c5101e149048 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 09:53:46 +0100 Subject: [PATCH 15/20] N21-1319 clean up --- .../service/school-external-tool-metadata.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts index 082e3188f3b..fbc46b76cc4 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -104,7 +104,7 @@ describe('SchoolExternalToolMetadataService', () => { ); const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: { course: 0, 'board-element': 0 }, + contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, }); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); From 5e1fb2c1dc4bde713740bed9756ea08725b63e61 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 10:51:35 +0100 Subject: [PATCH 16/20] N21-1319 Review Changes2 --- .../tool/external-tool/controller/api-test/tool.api.spec.ts | 4 +--- .../src/modules/tool/external-tool/uc/external-tool.uc.ts | 2 +- .../controller/api-test/tool-school.api.spec.ts | 4 +--- 3 files changed, 3 insertions(+), 7 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 211f8d83cd9..75dc1229231 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 @@ -695,10 +695,8 @@ describe('ToolController (API)', () => { const response: Response = await loggedInClient.get(`${externalToolEntity.id}/metadata`); - const body: ExternalToolMetadataResponse = response.body as ExternalToolMetadataResponse; - expect(response.statusCode).toEqual(HttpStatus.OK); - expect(body).toEqual({ + expect(response.body).toEqual({ schoolExternalToolCount: 2, contextExternalToolCountPerContext: { course: 3, 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 c8cbae04af5..9880994272a 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 @@ -81,7 +81,7 @@ export class ExternalToolUc { } async getMetadataForExternalTool(userId: EntityId, toolId: EntityId): Promise { - // TODO ticket: https://ticketsystem.dbildungscloud.de/browse/N21-1496 + // TODO N21-1496: Change External Tools to use authorizationService.checkPermission await this.ensurePermission(userId, Permission.TOOL_ADMIN); const metadata: ExternalToolMetadata = await this.externalToolMetadataService.getMetadata(toolId); diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 7327dc94a6f..274bd289edf 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -577,10 +577,8 @@ describe('ToolSchoolController (API)', () => { const response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); - const body: SchoolExternalToolMetadataResponse = response.body as SchoolExternalToolMetadataResponse; - expect(response.statusCode).toEqual(HttpStatus.OK); - expect(body).toEqual({ + expect(response.body).toEqual({ contextExternalToolCountPerContext: { course: 3, boardElement: 2, From 9d1f3b66dab11e08e707a3e4233815923af63b89 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 10:51:54 +0100 Subject: [PATCH 17/20] N21-1319 Review Changes2 --- ...ool-external-tool-metadata.service.spec.ts | 39 +------------------ .../school-external-tool-metadata.service.ts | 9 ++--- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts index fbc46b76cc4..8aa29737550 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -1,27 +1,21 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; -import { legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; +import { ContextExternalToolRepo } from '@shared/repo'; import { Logger } from '@src/core/logger'; -import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; +import { SchoolExternalToolMetadata } from '../domain'; import { SchoolExternalToolMetadataService } from './school-external-tool-metadata.service'; describe('SchoolExternalToolMetadataService', () => { let module: TestingModule; let service: SchoolExternalToolMetadataService; - let schoolExternalToolRepo: DeepMocked; let contextExternalToolRepo: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ providers: [ SchoolExternalToolMetadataService, - { - provide: SchoolExternalToolRepo, - useValue: createMock(), - }, { provide: ContextExternalToolRepo, useValue: createMock(), @@ -34,7 +28,6 @@ describe('SchoolExternalToolMetadataService', () => { }).compile(); service = module.get(SchoolExternalToolMetadataService); - schoolExternalToolRepo = module.get(SchoolExternalToolRepo); contextExternalToolRepo = module.get(ContextExternalToolRepo); }); @@ -49,26 +42,12 @@ describe('SchoolExternalToolMetadataService', () => { describe('getMetadata', () => { describe('when schoolExternalToolId is given', () => { const setup = () => { - const school = legacySchoolDoFactory.buildWithId(); - const school1 = legacySchoolDoFactory.buildWithId(); - const schoolToolId: string = new ObjectId().toHexString(); - const schoolToolId1: string = new ObjectId().toHexString(); - - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ - schoolId: school.id, - id: schoolToolId, - }); - const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( - { schoolId: school1.id, id: schoolToolId1 }, - schoolToolId1 - ); const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, }); - schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); return { @@ -88,26 +67,12 @@ describe('SchoolExternalToolMetadataService', () => { describe('when no related context external tool was found', () => { const setup = () => { - const school = legacySchoolDoFactory.buildWithId(); - const school1 = legacySchoolDoFactory.buildWithId(); - const schoolToolId: string = new ObjectId().toHexString(); - const schoolToolId1: string = new ObjectId().toHexString(); - - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ - schoolId: school.id, - id: schoolToolId, - }); - const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( - { schoolId: school1.id, id: schoolToolId1 }, - schoolToolId1 - ); const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, }); - schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); return { diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index a27e1420d1e..bc25a4a3d44 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { ContextExternalToolRepo } from '@shared/repo'; import { ToolContextType } from '../../common/enum'; import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; import { ContextExternalToolType } from '../../context-external-tool/entity'; @@ -8,10 +8,7 @@ import { SchoolExternalToolMetadata } from '../domain'; @Injectable() export class SchoolExternalToolMetadataService { - constructor( - private readonly schoolToolRepo: SchoolExternalToolRepo, - private readonly contextToolRepo: ContextExternalToolRepo - ) {} + constructor(private readonly contextToolRepo: ContextExternalToolRepo) {} async getMetadata(schoolExternalToolId: EntityId) { const contextTools: { contextType: ContextExternalToolType; countPerContext: number }[] = await Promise.all( @@ -21,7 +18,7 @@ export class SchoolExternalToolMetadataService { ): Promise<{ contextType: ContextExternalToolType; countPerContext: number }> => { const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( ToolContextMapper.contextMapping[contextType], - [schoolExternalToolId.toString()] + [schoolExternalToolId] ); const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; From d63cb92356d6a18fc1e8b74d62e5a4346aac12b1 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Thu, 16 Nov 2023 14:40:14 +0100 Subject: [PATCH 18/20] N21-1319 service changes --- .../service/external-tool-metadata.service.ts | 46 ++++++------------- .../school-external-tool-metadata.service.ts | 35 ++++++-------- 2 files changed, 29 insertions(+), 52 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index e47da7ec630..c2b15cec1a7 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -17,51 +17,33 @@ export class ExternalToolMetadataService { async getMetadata(toolId: EntityId): Promise { const schoolExternalTools: SchoolExternalTool[] = await this.schoolToolRepo.findByExternalToolId(toolId); - if (schoolExternalTools.length < 1) { - const externalToolMetadata = new ExternalToolMetadata({ - schoolExternalToolCount: 0, - contextExternalToolCountPerContext: { - [ToolContextMapper.contextMapping[ToolContextType.COURSE]]: 0, - [ToolContextMapper.contextMapping[ToolContextType.BOARD_ELEMENT]]: 0, - }, - }); - - return externalToolMetadata; - } const schoolExternalToolIds: string[] = schoolExternalTools.map( (schoolExternalTool: SchoolExternalTool): string => // We can be sure that the repo returns the id schoolExternalTool.id as string ); + const contextExternalToolCount: Record = { + [ContextExternalToolType.BOARD_ELEMENT]: 0, + [ContextExternalToolType.COURSE]: 0, + }; + if (schoolExternalTools.length >= 1) { + await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; - const contextTools: { contextType: ContextExternalToolType; countPerContext: number }[] = await Promise.all( - Object.values(ToolContextType).map( - async ( - contextType: ToolContextType - ): Promise<{ contextType: ContextExternalToolType; countPerContext: number }> => { const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( - ToolContextMapper.contextMapping[contextType], + type, schoolExternalToolIds ); - - const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; - - return { contextType: type, countPerContext }; - } - ) - ); - - const tools = Object.fromEntries( - contextTools.map((contextTool): [ContextExternalToolType, number] => [ - contextTool.contextType, - contextTool.countPerContext, - ]) - ); + contextExternalToolCount[type] = countPerContext; + }) + ); + } const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ schoolExternalToolCount: schoolExternalTools.length, - contextExternalToolCountPerContext: tools, + contextExternalToolCountPerContext: contextExternalToolCount, }); return externaltoolMetadata; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index bc25a4a3d44..a2e8480bae6 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -11,31 +11,26 @@ export class SchoolExternalToolMetadataService { constructor(private readonly contextToolRepo: ContextExternalToolRepo) {} async getMetadata(schoolExternalToolId: EntityId) { - const contextTools: { contextType: ContextExternalToolType; countPerContext: number }[] = await Promise.all( - Object.values(ToolContextType).map( - async ( - contextType: ToolContextType - ): Promise<{ contextType: ContextExternalToolType; countPerContext: number }> => { - const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( - ToolContextMapper.contextMapping[contextType], - [schoolExternalToolId] - ); + const contextExternalToolCount: Record = { + [ContextExternalToolType.BOARD_ELEMENT]: 0, + [ContextExternalToolType.COURSE]: 0, + }; + if (schoolExternalToolId) { + await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; - return { contextType: type, countPerContext }; - } - ) - ); - const tools = Object.fromEntries( - contextTools.map((contextTool): [ContextExternalToolType, number] => [ - contextTool.contextType, - contextTool.countPerContext, - ]) - ); + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType(type, [ + schoolExternalToolId, + ]); + contextExternalToolCount[type] = countPerContext; + }) + ); + } const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ - contextExternalToolCountPerContext: tools, + contextExternalToolCountPerContext: contextExternalToolCount, }); return schoolExternaltoolMetadata; From f25c3fbcf3dd7b3cc5a1fb1b21fda35d74b3d4c3 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Fri, 17 Nov 2023 09:38:54 +0100 Subject: [PATCH 19/20] N21-1319 changes --- .../external-tool-metadata.service.spec.ts | 11 +--------- .../service/external-tool-metadata.service.ts | 3 +-- .../school-external-tool-metadata.service.ts | 21 +++++++++---------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts index c2caf123815..4753cc805f4 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { externalToolFactory, legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; import { ContextExternalToolType } from '../../context-external-tool/entity'; import { SchoolExternalTool } from '../../school-external-tool/domain'; @@ -12,7 +12,6 @@ describe('ExternalToolMetadataService', () => { let module: TestingModule; let service: ExternalToolMetadataService; - let externalToolRepo: DeepMocked; let schoolExternalToolRepo: DeepMocked; let contextExternalToolRepo: DeepMocked; @@ -20,10 +19,6 @@ describe('ExternalToolMetadataService', () => { module = await Test.createTestingModule({ providers: [ ExternalToolMetadataService, - { - provide: ExternalToolRepo, - useValue: createMock(), - }, { provide: SchoolExternalToolRepo, useValue: createMock(), @@ -36,7 +31,6 @@ describe('ExternalToolMetadataService', () => { }).compile(); service = module.get(ExternalToolMetadataService); - externalToolRepo = module.get(ExternalToolRepo); schoolExternalToolRepo = module.get(SchoolExternalToolRepo); contextExternalToolRepo = module.get(ContextExternalToolRepo); }); @@ -53,7 +47,6 @@ describe('ExternalToolMetadataService', () => { describe('when externalToolId is given', () => { const setup = () => { const toolId: string = new ObjectId().toHexString(); - const externalTool: ExternalTool = externalToolFactory.buildWithId(undefined, toolId); const school = legacySchoolDoFactory.buildWithId(); const school1 = legacySchoolDoFactory.buildWithId(); @@ -76,7 +69,6 @@ describe('ExternalToolMetadataService', () => { contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, }); - externalToolRepo.findById.mockResolvedValue(externalTool); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); @@ -131,7 +123,6 @@ describe('ExternalToolMetadataService', () => { contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, }); - externalToolRepo.findById.mockResolvedValue(externalToolEntity); schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([]); contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts index c2b15cec1a7..9476e738a87 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { ToolContextType } from '../../common/enum'; import { ContextExternalToolType } from '../../context-external-tool/entity'; import { SchoolExternalTool } from '../../school-external-tool/domain'; @@ -10,7 +10,6 @@ import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; @Injectable() export class ExternalToolMetadataService { constructor( - private readonly externalToolRepo: ExternalToolRepo, private readonly schoolToolRepo: SchoolExternalToolRepo, private readonly contextToolRepo: ContextExternalToolRepo ) {} diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts index a2e8480bae6..fa8fd4fc926 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -16,18 +16,17 @@ export class SchoolExternalToolMetadataService { [ContextExternalToolType.COURSE]: 0, }; - if (schoolExternalToolId) { - await Promise.all( - Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { - const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; + await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; - const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType(type, [ - schoolExternalToolId, - ]); - contextExternalToolCount[type] = countPerContext; - }) - ); - } + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType(type, [ + schoolExternalToolId, + ]); + + contextExternalToolCount[type] = countPerContext; + }) + ); const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ contextExternalToolCountPerContext: contextExternalToolCount, From 9e29367b3b596b593c7127e380f8fd13cbb1ac4c Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Mon, 20 Nov 2023 12:19:27 +0100 Subject: [PATCH 20/20] N21-1319 adjust seed data --- backup/setup/school-external-tools.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backup/setup/school-external-tools.json b/backup/setup/school-external-tools.json index 884c8ffe373..59befc464af 100644 --- a/backup/setup/school-external-tools.json +++ b/backup/setup/school-external-tools.json @@ -45,5 +45,28 @@ }, "schoolParameters": [], "toolVersion": 2 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e5bb" + }, + "createdAt": { + "$date": { + "$numberLong": "1685971828284" + } + }, + "updatedAt": { + "$date": { + "$numberLong": "1685971828284" + } + }, + "tool": { + "$oid": "647de247cf6a427b9d39e5b9" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [], + "toolVersion": 2 } ]