From f2f0b368e636b997aaea4aa63157bd745c56fc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= <103562092+MarvinOehlerkingCap@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:13:10 +0200 Subject: [PATCH 01/19] N21-1248 Configure External Tools in Boards (#4471) - move tool reference to context external tool module - new endpoint for tool reference from context external tool id - add new tool reference controller - add board-element as context for context external tools --- .../element/external-tool-element.response.ts | 6 +- .../external-tool-element-response.mapper.ts | 2 +- .../service/feathers-roster.service.spec.ts | 16 +- .../service/feathers-roster.service.ts | 6 +- .../common/enum/tool-context-type.enum.ts | 1 + .../tool/common/mapper/context-type.mapper.ts | 1 + .../mapper/tool-status-response.mapper.ts | 14 + .../context-external-tool.module.ts | 27 +- .../api-test/tool-reference.api.spec.ts | 287 ++++++++++++++++++ .../context-external-tool-context.params.ts | 8 +- .../controller/dto/index.ts | 3 + .../tool-configuration-status.response.ts | 0 .../dto}/tool-reference-list.response.ts | 0 .../dto}/tool-reference.response.ts | 1 + .../controller/tool-reference.controller.ts | 68 +++++ .../context-external-tool/domain/index.ts | 1 + .../domain/tool-reference.ts | 0 .../entity/context-external-tool-type.enum.ts | 1 + .../context-external-tool-response.mapper.ts | 25 +- .../context-external-tool/mapper/index.ts | 1 + .../mapper/tool-reference.mapper.ts | 4 +- ...t-external-tool-validation.service.spec.ts | 8 +- ...ontext-external-tool-validation.service.ts | 6 +- .../context-external-tool.service.spec.ts | 6 +- .../service/context-external-tool.service.ts | 4 +- .../context-external-tool/service/index.ts | 1 + .../service/tool-reference.service.spec.ts | 132 ++++++++ .../service/tool-reference.service.ts | 50 +++ .../uc/context-external-tool.uc.spec.ts | 10 +- .../uc/context-external-tool.uc.ts | 14 +- .../tool/context-external-tool/uc/index.ts | 1 + .../uc/tool-reference.uc.spec.ts | 215 +++++++++++++ .../uc/tool-reference.uc.ts | 82 +++++ .../controller/api-test/tool.api.spec.ts | 142 +-------- .../controller/dto/response/index.ts | 3 - .../controller/tool.controller.ts | 38 +-- .../tool/external-tool/domain/index.ts | 1 - .../mapper/external-tool-response.mapper.ts | 24 +- .../tool/external-tool/mapper/index.ts | 1 - .../external-tool-logo-service.spec.ts | 10 +- .../service/external-tool-logo.service.ts | 14 +- .../external-tool-validation.service.spec.ts | 12 +- .../external-tool-validation.service.ts | 4 +- .../service/external-tool.service.spec.ts | 8 +- .../service/external-tool.service.ts | 2 +- .../uc/external-tool-configuration.uc.spec.ts | 28 +- .../uc/external-tool-configuration.uc.ts | 16 +- .../external-tool/uc/external-tool.uc.spec.ts | 4 +- .../tool/external-tool/uc/external-tool.uc.ts | 4 +- .../modules/tool/external-tool/uc/index.ts | 1 - .../uc/tool-reference.uc.spec.ts | 234 -------------- .../external-tool/uc/tool-reference.uc.ts | 102 ------- .../api-test/tool-school.api.spec.ts | 6 +- .../dto/school-external-tool.response.ts | 2 +- ...hool-external-tool-response.mapper.spec.ts | 2 +- .../school-external-tool-response.mapper.ts | 12 +- ...l-external-tool-validation.service.spec.ts | 4 +- ...school-external-tool-validation.service.ts | 4 +- .../school-external-tool.service.spec.ts | 20 +- .../service/school-external-tool.service.ts | 12 +- .../uc/school-external-tool.uc.spec.ts | 12 +- .../uc/school-external-tool.uc.ts | 14 +- .../src/modules/tool/tool-api.module.ts | 10 +- .../strategy/abstract-launch.strategy.spec.ts | 151 +++++++-- .../strategy/abstract-launch.strategy.ts | 19 +- .../lti11-tool-launch.strategy.spec.ts | 4 +- .../strategy/lti11-tool-launch.strategy.ts | 2 +- .../service/tool-launch.service.spec.ts | 32 +- .../service/tool-launch.service.ts | 20 +- .../tool-launch/uc/tool-launch.uc.spec.ts | 10 +- .../tool/tool-launch/uc/tool-launch.uc.ts | 8 +- ...ext-external-tool.repo.integration.spec.ts | 34 ++- .../context-external-tool.repo.ts | 4 + 73 files changed, 1246 insertions(+), 785 deletions(-) create mode 100644 apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts create mode 100644 apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts rename apps/server/src/modules/tool/{external-tool/controller/dto/response => context-external-tool/controller/dto}/tool-configuration-status.response.ts (100%) rename apps/server/src/modules/tool/{external-tool/controller/dto/response => context-external-tool/controller/dto}/tool-reference-list.response.ts (100%) rename apps/server/src/modules/tool/{external-tool/controller/dto/response => context-external-tool/controller/dto}/tool-reference.response.ts (96%) create mode 100644 apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts rename apps/server/src/modules/tool/{external-tool => context-external-tool}/domain/tool-reference.ts (100%) rename apps/server/src/modules/tool/{external-tool => context-external-tool}/mapper/tool-reference.mapper.ts (80%) create mode 100644 apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts create mode 100644 apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts create mode 100644 apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts create mode 100644 apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts delete mode 100644 apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.spec.ts delete mode 100644 apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.ts diff --git a/apps/server/src/modules/board/controller/dto/element/external-tool-element.response.ts b/apps/server/src/modules/board/controller/dto/element/external-tool-element.response.ts index 5f51a1a26ec..fc67b7631b6 100644 --- a/apps/server/src/modules/board/controller/dto/element/external-tool-element.response.ts +++ b/apps/server/src/modules/board/controller/dto/element/external-tool-element.response.ts @@ -1,4 +1,4 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger'; import { ContentElementType } from '@shared/domain'; import { TimestampsResponse } from '../timestamps.response'; @@ -7,8 +7,8 @@ export class ExternalToolElementContent { this.contextExternalToolId = props.contextExternalToolId; } - @ApiPropertyOptional() - contextExternalToolId?: string; + @ApiProperty({ type: String, required: true, nullable: true }) + contextExternalToolId: string | null; } export class ExternalToolElementResponse { diff --git a/apps/server/src/modules/board/controller/mapper/external-tool-element-response.mapper.ts b/apps/server/src/modules/board/controller/mapper/external-tool-element-response.mapper.ts index a907f4eb157..a27cab41d63 100644 --- a/apps/server/src/modules/board/controller/mapper/external-tool-element-response.mapper.ts +++ b/apps/server/src/modules/board/controller/mapper/external-tool-element-response.mapper.ts @@ -18,7 +18,7 @@ export class ExternalToolElementResponseMapper implements BaseResponseMapper { id: element.id, timestamps: new TimestampsResponse({ lastUpdatedAt: element.updatedAt, createdAt: element.createdAt }), type: ContentElementType.EXTERNAL_TOOL, - content: new ExternalToolElementContent({ contextExternalToolId: element.contextExternalToolId }), + content: new ExternalToolElementContent({ contextExternalToolId: element.contextExternalToolId ?? null }), }); return result; diff --git a/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts b/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts index ce4d5144a38..e2a8484d630 100644 --- a/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts +++ b/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts @@ -2,13 +2,13 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { DatabaseObjectNotFoundException } from '@mikro-orm/core'; import { Test, TestingModule } from '@nestjs/testing'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; -import { Course, Pseudonym, RoleName, LegacySchoolDo, UserDO, SchoolEntity } from '@shared/domain'; +import { Course, LegacySchoolDo, Pseudonym, RoleName, SchoolEntity, UserDO } from '@shared/domain'; import { contextExternalToolFactory, courseFactory, externalToolFactory, - pseudonymFactory, legacySchoolDoFactory, + pseudonymFactory, schoolExternalToolFactory, schoolFactory, setupEntities, @@ -249,10 +249,10 @@ describe('FeathersRosterService', () => { ]); contextExternalToolService.findAllByContext.mockResolvedValueOnce([otherContextExternalTool]); contextExternalToolService.findAllByContext.mockResolvedValueOnce([]); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(otherSchoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(otherExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(otherSchoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); + externalToolService.findById.mockResolvedValueOnce(otherExternalTool); return { pseudonym, @@ -299,7 +299,7 @@ describe('FeathersRosterService', () => { await service.getUserGroups(pseudonym.pseudonym, clientId); - expect(schoolExternalToolService.getSchoolExternalToolById.mock.calls).toEqual([ + expect(schoolExternalToolService.findById.mock.calls).toEqual([ [schoolExternalTool.id], [otherSchoolExternalTool.id], ]); @@ -310,7 +310,7 @@ describe('FeathersRosterService', () => { await service.getUserGroups(pseudonym.pseudonym, clientId); - expect(externalToolService.findExternalToolById.mock.calls).toEqual([[externalToolId], [otherExternalTool.id]]); + expect(externalToolService.findById.mock.calls).toEqual([[externalToolId], [otherExternalTool.id]]); }); it('should return a group for each course where the tool of the users pseudonym is used', async () => { diff --git a/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts b/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts index a5fd359b6c1..e808a2fc59f 100644 --- a/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts +++ b/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts @@ -179,12 +179,10 @@ export class FeathersRosterService { ); for await (const contextExternalTool of contextExternalTools) { - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( contextExternalTool.schoolToolRef.schoolToolId ); - const externalTool: ExternalTool = await this.externalToolService.findExternalToolById( - schoolExternalTool.toolId - ); + const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); const isRequiredTool: boolean = externalTool.id === externalToolId; if (isRequiredTool) { 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 20e57d7bd60..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,3 +1,4 @@ export enum ToolContextType { COURSE = 'course', + BOARD_ELEMENT = 'board-element', } diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts index 00da0a8b36d..bf8fb537924 100644 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts @@ -3,6 +3,7 @@ import { ToolContextType } from '../enum'; const typeMapping: Record = { [ToolContextType.COURSE]: AuthorizableReferenceType.Course, + [ToolContextType.BOARD_ELEMENT]: AuthorizableReferenceType.BoardNode, }; export class ContextTypeMapper { diff --git a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts new file mode 100644 index 00000000000..c199fc6f307 --- /dev/null +++ b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts @@ -0,0 +1,14 @@ +import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatus } from '../enum'; + +export const statusMapping: Record = { + [ToolConfigurationStatus.LATEST]: ToolConfigurationStatusResponse.LATEST, + [ToolConfigurationStatus.OUTDATED]: ToolConfigurationStatusResponse.OUTDATED, + [ToolConfigurationStatus.UNKNOWN]: ToolConfigurationStatusResponse.UNKNOWN, +}; + +export class ToolStatusResponseMapper { + static mapToResponse(status: ToolConfigurationStatus): ToolConfigurationStatusResponse { + return statusMapping[status]; + } +} diff --git a/apps/server/src/modules/tool/context-external-tool/context-external-tool.module.ts b/apps/server/src/modules/tool/context-external-tool/context-external-tool.module.ts index e3319512fc5..1afd639f1e7 100644 --- a/apps/server/src/modules/tool/context-external-tool/context-external-tool.module.ts +++ b/apps/server/src/modules/tool/context-external-tool/context-external-tool.module.ts @@ -1,25 +1,28 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { CommonToolModule } from '../common'; import { ExternalToolModule } from '../external-tool'; import { SchoolExternalToolModule } from '../school-external-tool'; import { ContextExternalToolAuthorizableService, ContextExternalToolService, ContextExternalToolValidationService, + ToolReferenceService, } from './service'; -import { CommonToolModule } from '../common'; @Module({ - // TODO: remove authorization module here N21-1055 - imports: [ - CommonToolModule, - ExternalToolModule, - SchoolExternalToolModule, - LoggerModule, - forwardRef(() => AuthorizationModule), + imports: [CommonToolModule, ExternalToolModule, SchoolExternalToolModule, LoggerModule], + providers: [ + ContextExternalToolService, + ContextExternalToolValidationService, + ContextExternalToolAuthorizableService, + ToolReferenceService, + ], + exports: [ + ContextExternalToolService, + ContextExternalToolValidationService, + ContextExternalToolAuthorizableService, + ToolReferenceService, ], - providers: [ContextExternalToolService, ContextExternalToolValidationService, ContextExternalToolAuthorizableService], - exports: [ContextExternalToolService, ContextExternalToolValidationService, ContextExternalToolAuthorizableService], }) export class ContextExternalToolModule {} diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts new file mode 100644 index 00000000000..f3072a95131 --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts @@ -0,0 +1,287 @@ +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Course, Permission, SchoolEntity } from '@shared/domain'; +import { + cleanupCollections, + contextExternalToolEntityFactory, + courseFactory, + externalToolEntityFactory, + schoolExternalToolEntityFactory, + schoolFactory, + TestApiClient, + UserAndAccountTestFactory, +} from '@shared/testing'; +import { ServerTestModule } from '@src/modules/server'; +import { Response } from 'supertest'; +import { ToolContextType } from '../../../common/enum'; +import { ExternalToolEntity } from '../../../external-tool/entity'; +import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; +import { ContextExternalToolContextParams, ToolReferenceListResponse, ToolReferenceResponse } from '../dto'; +import { ToolConfigurationStatusResponse } from '../dto/tool-configuration-status.response'; + +describe('ToolReferenceController (API)', () => { + let app: INestApplication; + let em: EntityManager; + + let testApiClient: TestApiClient; + + beforeAll(async () => { + const moduleRef: TestingModule = await Test.createTestingModule({ + imports: [ServerTestModule], + }).compile(); + + app = moduleRef.createNestApplication(); + + await app.init(); + + em = app.get(EntityManager); + testApiClient = new TestApiClient(app, 'tools/tool-references'); + }); + + afterAll(async () => { + await app.close(); + }); + + afterEach(async () => { + await cleanupCollections(em); + }); + + describe('[GET] tools/tool-references/:contextType/:contextId', () => { + describe('when user is not authenticated', () => { + it('should return unauthorized', async () => { + const response: Response = await testApiClient.get(`contextType/${new ObjectId().toHexString()}`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when user has no access to a tool', () => { + const setup = async () => { + const schoolWithoutTool: SchoolEntity = schoolFactory.buildWithId(); + const school: SchoolEntity = schoolFactory.buildWithId(); + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school: schoolWithoutTool }); + const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalToolEntity, + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + }); + + await em.persistAndFlush([ + school, + adminAccount, + adminUser, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const params: ContextExternalToolContextParams = { + contextId: course.id, + contextType: ToolContextType.COURSE, + }; + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, params }; + }; + + it('should filter out the tool', async () => { + const { loggedInClient, params } = await setup(); + + const response: Response = await loggedInClient.get(`${params.contextType}/${params.contextId}`); + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ data: [] }); + }); + }); + + describe('when user has access for a tool', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ + Permission.CONTEXT_TOOL_USER, + ]); + const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + logoBase64: 'logoBase64', + }); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalToolEntity, + toolVersion: externalToolEntity.version, + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + displayName: 'This is a test tool', + toolVersion: schoolExternalToolEntity.toolVersion, + }); + + await em.persistAndFlush([ + school, + adminAccount, + adminUser, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const params: ContextExternalToolContextParams = { + contextId: course.id, + contextType: ToolContextType.COURSE, + }; + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, params, contextExternalToolEntity, externalToolEntity }; + }; + + it('should return an ToolReferenceListResponse with data', async () => { + const { loggedInClient, params, contextExternalToolEntity, externalToolEntity } = await setup(); + + const response: Response = await loggedInClient.get(`${params.contextType}/${params.contextId}`); + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ + data: [ + { + contextToolId: contextExternalToolEntity.id, + displayName: contextExternalToolEntity.displayName as string, + status: ToolConfigurationStatusResponse.LATEST, + logoUrl: `http://localhost:3030/api/v3/tools/external-tools/${externalToolEntity.id}/logo`, + openInNewTab: externalToolEntity.openNewTab, + }, + ], + }); + }); + }); + }); + + describe('[GET] tools/tool-references/context-external-tools/:contextExternalToolId', () => { + describe('when user is not authenticated', () => { + it('should return unauthorized', async () => { + const response: Response = await testApiClient.get(`context-external-tools/${new ObjectId().toHexString()}`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when user has no access to a tool', () => { + const setup = async () => { + const schoolWithoutTool: SchoolEntity = schoolFactory.buildWithId(); + const school: SchoolEntity = schoolFactory.buildWithId(); + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school: schoolWithoutTool }); + const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalToolEntity, + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + }); + + await em.persistAndFlush([ + school, + adminAccount, + adminUser, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { + loggedInClient, + contextExternalToolId: contextExternalToolEntity.id, + }; + }; + + it('should filter out the tool', async () => { + const { loggedInClient, contextExternalToolId } = await setup(); + + const response: Response = await loggedInClient.get(`context-external-tools/${contextExternalToolId}`); + + expect(response.statusCode).toEqual(HttpStatus.FORBIDDEN); + }); + }); + + describe('when user has access for a tool', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ + Permission.CONTEXT_TOOL_USER, + ]); + const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + logoBase64: 'logoBase64', + }); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalToolEntity, + toolVersion: externalToolEntity.version, + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + displayName: 'This is a test tool', + toolVersion: schoolExternalToolEntity.toolVersion, + }); + + await em.persistAndFlush([ + school, + adminAccount, + adminUser, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { + loggedInClient, + contextExternalToolId: contextExternalToolEntity.id, + contextExternalToolEntity, + externalToolEntity, + }; + }; + + it('should return an ToolReferenceListResponse with data', async () => { + const { loggedInClient, contextExternalToolId, contextExternalToolEntity, externalToolEntity } = await setup(); + + const response: Response = await loggedInClient.get(`context-external-tools/${contextExternalToolId}`); + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ + contextToolId: contextExternalToolEntity.id, + displayName: contextExternalToolEntity.displayName as string, + status: ToolConfigurationStatusResponse.LATEST, + logoUrl: `http://localhost:3030/api/v3/tools/external-tools/${externalToolEntity.id}/logo`, + openInNewTab: externalToolEntity.openNewTab, + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/dto/context-external-tool-context.params.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/context-external-tool-context.params.ts index 7d20deef026..63850daa22b 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/dto/context-external-tool-context.params.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/dto/context-external-tool-context.params.ts @@ -8,6 +8,12 @@ export class ContextExternalToolContextParams { contextId!: string; @IsEnum(ToolContextType) - @ApiProperty({ nullable: false, required: true, example: ToolContextType.COURSE }) + @ApiProperty({ + enum: ToolContextType, + enumName: 'ToolContextType', + nullable: false, + required: true, + example: ToolContextType.COURSE, + }) contextType!: ToolContextType; } diff --git a/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts index dfe16d84244..e6da4bb909f 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts @@ -3,3 +3,6 @@ export * from './context-external-tool-id.params'; export * from './context-external-tool-search-list.response'; export * from './context-external-tool-context.params'; export * from './context-external-tool.response'; +export * from './tool-reference-list.response'; +export * from './tool-reference.response'; +export * from './tool-configuration-status.response'; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/tool-configuration-status.response.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-configuration-status.response.ts similarity index 100% rename from apps/server/src/modules/tool/external-tool/controller/dto/response/tool-configuration-status.response.ts rename to apps/server/src/modules/tool/context-external-tool/controller/dto/tool-configuration-status.response.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/tool-reference-list.response.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference-list.response.ts similarity index 100% rename from apps/server/src/modules/tool/external-tool/controller/dto/response/tool-reference-list.response.ts rename to apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference-list.response.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/tool-reference.response.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts similarity index 96% rename from apps/server/src/modules/tool/external-tool/controller/dto/response/tool-reference.response.ts rename to apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts index 24844d8bd2a..0ccefffa6ae 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/tool-reference.response.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts @@ -20,6 +20,7 @@ export class ToolReferenceResponse { @ApiProperty({ enum: ToolConfigurationStatusResponse, + enumName: 'ToolConfigurationStatusResponse', nullable: false, required: true, description: 'The status of the tool', diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts new file mode 100644 index 00000000000..c414f4423de --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts @@ -0,0 +1,68 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiForbiddenResponse, ApiOkResponse, ApiOperation, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ToolReference } from '../domain'; +import { ContextExternalToolResponseMapper } from '../mapper'; +import { ToolReferenceUc } from '../uc'; +import { + ContextExternalToolContextParams, + ContextExternalToolIdParams, + ToolReferenceListResponse, + ToolReferenceResponse, +} from './dto'; + +@ApiTags('Tool') +@Authenticate('jwt') +@Controller('tools/tool-references') +export class ToolReferenceController { + constructor(private readonly toolReferenceUc: ToolReferenceUc) {} + + @Get('context-external-tools/:contextExternalToolId') + @ApiOperation({ summary: 'Get ExternalTool Reference for a given context external tool' }) + @ApiOkResponse({ + description: 'The Tool Reference has been successfully fetched.', + type: ToolReferenceResponse, + }) + @ApiForbiddenResponse({ description: 'User is not allowed to access this resource.' }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getToolReference( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: ContextExternalToolIdParams + ): Promise { + const toolReference: ToolReference = await this.toolReferenceUc.getToolReference( + currentUser.userId, + params.contextExternalToolId + ); + + const toolReferenceResponse: ToolReferenceResponse = + ContextExternalToolResponseMapper.mapToToolReferenceResponse(toolReference); + + return toolReferenceResponse; + } + + @Get('/:contextType/:contextId') + @ApiOperation({ summary: 'Get ExternalTool References for a given context' }) + @ApiOkResponse({ + description: 'The Tool References has been successfully fetched.', + type: ToolReferenceListResponse, + }) + @ApiForbiddenResponse({ description: 'User is not allowed to access this resource.' }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getToolReferencesForContext( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: ContextExternalToolContextParams + ): Promise { + const toolReferences: ToolReference[] = await this.toolReferenceUc.getToolReferencesForContext( + currentUser.userId, + params.contextType, + params.contextId + ); + + const toolReferenceResponses: ToolReferenceResponse[] = + ContextExternalToolResponseMapper.mapToToolReferenceResponses(toolReferences); + const toolReferenceListResponse = new ToolReferenceListResponse(toolReferenceResponses); + + return toolReferenceListResponse; + } +} diff --git a/apps/server/src/modules/tool/context-external-tool/domain/index.ts b/apps/server/src/modules/tool/context-external-tool/domain/index.ts index a012e1d4002..557bc04788c 100644 --- a/apps/server/src/modules/tool/context-external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/domain/index.ts @@ -1,2 +1,3 @@ export * from './context-external-tool.do'; export * from './context-ref'; +export * from './tool-reference'; diff --git a/apps/server/src/modules/tool/external-tool/domain/tool-reference.ts b/apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts similarity index 100% rename from apps/server/src/modules/tool/external-tool/domain/tool-reference.ts rename to apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts diff --git a/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool-type.enum.ts b/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool-type.enum.ts index 4d40ca6e84c..56753c354dc 100644 --- a/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool-type.enum.ts +++ b/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool-type.enum.ts @@ -1,3 +1,4 @@ export enum ContextExternalToolType { COURSE = 'course', + BOARD_ELEMENT = 'boardElement', } diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-response.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-response.mapper.ts index 601c960299d..07610a7a508 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-response.mapper.ts @@ -1,6 +1,7 @@ +import { ToolStatusResponseMapper } from '../../common/mapper/tool-status-response.mapper'; import { CustomParameterEntryParam, CustomParameterEntryResponse } from '../../school-external-tool/controller/dto'; -import { ContextExternalToolResponse } from '../controller/dto'; -import { ContextExternalTool } from '../domain'; +import { ContextExternalToolResponse, ToolReferenceResponse } from '../controller/dto'; +import { ContextExternalTool, ToolReference } from '../domain'; export class ContextExternalToolResponseMapper { static mapContextExternalToolResponse(contextExternalTool: ContextExternalTool): ContextExternalToolResponse { @@ -33,4 +34,24 @@ export class ContextExternalToolResponseMapper { return mapped; } + + static mapToToolReferenceResponses(toolReferences: ToolReference[]): ToolReferenceResponse[] { + const toolReferenceResponses: ToolReferenceResponse[] = toolReferences.map((toolReference: ToolReference) => + this.mapToToolReferenceResponse(toolReference) + ); + + return toolReferenceResponses; + } + + static mapToToolReferenceResponse(toolReference: ToolReference): ToolReferenceResponse { + const response = new ToolReferenceResponse({ + contextToolId: toolReference.contextToolId, + displayName: toolReference.displayName, + logoUrl: toolReference.logoUrl, + openInNewTab: toolReference.openInNewTab, + status: ToolStatusResponseMapper.mapToResponse(toolReference.status), + }); + + return response; + } } diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/index.ts b/apps/server/src/modules/tool/context-external-tool/mapper/index.ts index 1987491c4e0..427f02a713a 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/index.ts @@ -1,2 +1,3 @@ export * from './context-external-tool-request.mapper'; export * from './context-external-tool-response.mapper'; +export * from './tool-reference.mapper'; diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-reference.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts similarity index 80% rename from apps/server/src/modules/tool/external-tool/mapper/tool-reference.mapper.ts rename to apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts index ec982467578..be6e6b8ab12 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-reference.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts @@ -1,6 +1,6 @@ -import { ExternalTool, ToolReference } from '../domain'; -import { ContextExternalTool } from '../../context-external-tool/domain'; import { ToolConfigurationStatus } from '../../common/enum'; +import { ExternalTool } from '../../external-tool/domain'; +import { ContextExternalTool, ToolReference } from '../domain'; export class ToolReferenceMapper { static mapToToolReference( diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.spec.ts index c419154c020..41e849b3d79 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ValidationError } from '@mikro-orm/core'; import { UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { contextExternalToolFactory, externalToolFactory } from '@shared/testing'; -import { ValidationError } from '@mikro-orm/core'; import { CommonToolValidationService } from '../../common/service'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; @@ -62,7 +62,7 @@ describe('ContextExternalToolValidationService', () => { describe('when no tool with the name exists in the context', () => { const setup = () => { const externalTool: ExternalTool = externalToolFactory.buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Tool 1', @@ -93,9 +93,7 @@ describe('ContextExternalToolValidationService', () => { await service.validate(contextExternalTool); - expect(schoolExternalToolService.getSchoolExternalToolById).toBeCalledWith( - contextExternalTool.schoolToolRef.schoolToolId - ); + expect(schoolExternalToolService.findById).toBeCalledWith(contextExternalTool.schoolToolRef.schoolToolId); }); it('should call commonToolValidationService.checkCustomParameterEntries', async () => { diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts index af6d36840f7..3777273d18e 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts @@ -23,13 +23,11 @@ export class ContextExternalToolValidationService { await this.checkDuplicateInContext(contextExternalTool); - const loadedSchoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( + const loadedSchoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( contextExternalTool.schoolToolRef.schoolToolId ); - const loadedExternalTool: ExternalTool = await this.externalToolService.findExternalToolById( - loadedSchoolExternalTool.toolId - ); + const loadedExternalTool: ExternalTool = await this.externalToolService.findById(loadedSchoolExternalTool.toolId); this.commonToolValidationService.checkCustomParameterEntries(loadedExternalTool, contextExternalTool); } diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts index 28cb093ae2d..819e2895896 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts @@ -130,7 +130,7 @@ describe('ContextExternalToolService', () => { }); }); - describe('getContextExternalToolById', () => { + describe('findById', () => { describe('when contextExternalToolId is given', () => { const setup = () => { const schoolId: string = legacySchoolDoFactory.buildWithId().id as string; @@ -151,7 +151,7 @@ describe('ContextExternalToolService', () => { it('should return a contextExternalTool', async () => { const { contextExternalTool } = setup(); - const result: ContextExternalTool = await service.getContextExternalToolById(contextExternalTool.id as string); + const result: ContextExternalTool = await service.findById(contextExternalTool.id as string); expect(result).toEqual(contextExternalTool); }); @@ -165,7 +165,7 @@ describe('ContextExternalToolService', () => { it('should throw a not found exception', async () => { setup(); - const func = () => service.getContextExternalToolById('unknownContextExternalToolId'); + const func = () => service.findById('unknownContextExternalToolId'); await expect(func()).rejects.toThrow(NotFoundException); }); diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts index 011e6db2f7a..63618191810 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { ContextExternalToolRepo } from '@shared/repo'; -import { ContextExternalToolQuery } from '../uc/dto/context-external-tool.types'; import { ContextExternalTool, ContextRef } from '../domain'; +import { ContextExternalToolQuery } from '../uc/dto/context-external-tool.types'; @Injectable() export class ContextExternalToolService { @@ -14,7 +14,7 @@ export class ContextExternalToolService { return contextExternalTools; } - async getContextExternalToolById(contextExternalToolId: EntityId): Promise { + async findById(contextExternalToolId: EntityId): Promise { const tool: ContextExternalTool = await this.contextExternalToolRepo.findById(contextExternalToolId); return tool; diff --git a/apps/server/src/modules/tool/context-external-tool/service/index.ts b/apps/server/src/modules/tool/context-external-tool/service/index.ts index 887cfbe7d9d..31fedbe42af 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/index.ts @@ -1,3 +1,4 @@ export * from './context-external-tool.service'; export * from './context-external-tool-validation.service'; export * from './context-external-tool-authorizable.service'; +export * from './tool-reference.service'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts new file mode 100644 index 00000000000..e434ab49527 --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts @@ -0,0 +1,132 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; +import { ToolConfigurationStatus } from '../../common/enum'; +import { CommonToolService } from '../../common/service'; +import { ExternalToolLogoService, ExternalToolService } from '../../external-tool/service'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; +import { ToolReference } from '../domain'; +import { ContextExternalToolService } from './context-external-tool.service'; +import { ToolReferenceService } from './tool-reference.service'; + +describe('ToolReferenceService', () => { + let module: TestingModule; + let service: ToolReferenceService; + + let externalToolService: DeepMocked; + let schoolExternalToolService: DeepMocked; + let contextExternalToolService: DeepMocked; + let commonToolService: DeepMocked; + let externalToolLogoService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ToolReferenceService, + { + provide: ExternalToolService, + useValue: createMock(), + }, + { + provide: SchoolExternalToolService, + useValue: createMock(), + }, + { + provide: ContextExternalToolService, + useValue: createMock(), + }, + { + provide: CommonToolService, + useValue: createMock(), + }, + { + provide: ExternalToolLogoService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(ToolReferenceService); + externalToolService = module.get(ExternalToolService); + schoolExternalToolService = module.get(SchoolExternalToolService); + contextExternalToolService = module.get(ContextExternalToolService); + commonToolService = module.get(CommonToolService); + externalToolLogoService = module.get(ExternalToolLogoService); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('getToolReference', () => { + describe('when a context external tool id is provided', () => { + const setup = () => { + const contextExternalToolId = new ObjectId().toHexString(); + const externalTool = externalToolFactory.buildWithId(); + const schoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id as string, + }); + const contextExternalTool = contextExternalToolFactory + .withSchoolExternalToolRef(schoolExternalTool.id as string) + .buildWithId(undefined, contextExternalToolId); + const logoUrl = 'logoUrl'; + + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); + commonToolService.determineToolConfigurationStatus.mockReturnValue(ToolConfigurationStatus.OUTDATED); + externalToolLogoService.buildLogoUrl.mockReturnValue(logoUrl); + + return { + contextExternalToolId, + externalTool, + schoolExternalTool, + contextExternalTool, + logoUrl, + }; + }; + + it('should determine the tool status', async () => { + const { contextExternalToolId, externalTool, schoolExternalTool, contextExternalTool } = setup(); + + await service.getToolReference(contextExternalToolId); + + expect(commonToolService.determineToolConfigurationStatus).toHaveBeenCalledWith( + externalTool, + schoolExternalTool, + contextExternalTool + ); + }); + + it('should build the logo url', async () => { + const { contextExternalToolId, externalTool } = setup(); + + await service.getToolReference(contextExternalToolId); + + expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith( + '/v3/tools/external-tools/{id}/logo', + externalTool + ); + }); + + it('should return the tool reference', async () => { + const { contextExternalToolId, logoUrl, contextExternalTool, externalTool } = setup(); + + const result: ToolReference = await service.getToolReference(contextExternalToolId); + + expect(result).toEqual({ + logoUrl, + displayName: contextExternalTool.displayName as string, + openInNewTab: externalTool.openNewTab, + status: ToolConfigurationStatus.OUTDATED, + contextToolId: contextExternalToolId, + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts new file mode 100644 index 00000000000..02c6a08677e --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ToolConfigurationStatus } from '../../common/enum'; +import { CommonToolService } from '../../common/service'; +import { ExternalTool } from '../../external-tool/domain'; +import { ExternalToolLogoService, ExternalToolService } from '../../external-tool/service'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; +import { ContextExternalTool, ToolReference } from '../domain'; +import { ToolReferenceMapper } from '../mapper'; +import { ContextExternalToolService } from './context-external-tool.service'; + +@Injectable() +export class ToolReferenceService { + constructor( + private readonly externalToolService: ExternalToolService, + private readonly schoolExternalToolService: SchoolExternalToolService, + private readonly contextExternalToolService: ContextExternalToolService, + private readonly commonToolService: CommonToolService, + private readonly externalToolLogoService: ExternalToolLogoService + ) {} + + async getToolReference(contextExternalToolId: EntityId): Promise { + const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( + contextExternalToolId + ); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( + contextExternalTool.schoolToolRef.schoolToolId + ); + const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); + + const status: ToolConfigurationStatus = this.commonToolService.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + const toolReference: ToolReference = ToolReferenceMapper.mapToToolReference( + externalTool, + contextExternalTool, + status + ); + toolReference.logoUrl = this.externalToolLogoService.buildLogoUrl( + '/v3/tools/external-tools/{id}/logo', + externalTool + ); + + return toolReference; + } +} diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts index 7dcdea9d16b..801765f80e5 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts @@ -8,10 +8,10 @@ import { LegacyLogger } from '@src/core/logger'; import { Action, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { ForbiddenLoggableException } from '@src/modules/authorization/errors/forbidden.loggable-exception'; import { ToolContextType } from '../../common/enum'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; import { ContextExternalToolUc } from './context-external-tool.uc'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; describe('ContextExternalToolUc', () => { let module: TestingModule; @@ -339,7 +339,7 @@ describe('ContextExternalToolUc', () => { const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); toolPermissionHelper.ensureContextPermissions.mockResolvedValue(); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); + contextExternalToolService.findById.mockResolvedValue(contextExternalTool); return { contextExternalTool, @@ -496,7 +496,7 @@ describe('ContextExternalToolUc', () => { }, }); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); + contextExternalToolService.findById.mockResolvedValue(contextExternalTool); toolPermissionHelper.ensureContextPermissions.mockResolvedValue(Promise.resolve()); return { @@ -524,7 +524,7 @@ describe('ContextExternalToolUc', () => { await uc.getContextExternalTool(userId, contextExternalTool.id as string); - expect(contextExternalToolService.getContextExternalToolById).toHaveBeenCalledWith(contextExternalTool.id); + expect(contextExternalToolService.findById).toHaveBeenCalledWith(contextExternalTool.id); }); }); @@ -542,7 +542,7 @@ describe('ContextExternalToolUc', () => { }, }); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); + contextExternalToolService.findById.mockResolvedValue(contextExternalTool); toolPermissionHelper.ensureContextPermissions.mockRejectedValue( new ForbiddenLoggableException( userId, diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts index 903b8197251..04002cf9fc6 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts @@ -1,12 +1,12 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission, User } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { LegacyLogger } from '@src/core/logger'; -import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; -import { ContextExternalToolDto } from './dto/context-external-tool.types'; -import { ContextExternalTool, ContextRef } from '../domain'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { ContextExternalTool, ContextRef } from '../domain'; +import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; +import { ContextExternalToolDto } from './dto/context-external-tool.types'; @Injectable() export class ContextExternalToolUc { @@ -62,9 +62,7 @@ export class ContextExternalToolUc { } async deleteContextExternalTool(userId: EntityId, contextExternalToolId: EntityId): Promise { - const tool: ContextExternalTool = await this.contextExternalToolService.getContextExternalToolById( - contextExternalToolId - ); + const tool: ContextExternalTool = await this.contextExternalToolService.findById(contextExternalToolId); const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); await this.toolPermissionHelper.ensureContextPermissions(userId, tool, context); @@ -85,7 +83,7 @@ export class ContextExternalToolUc { } async getContextExternalTool(userId: EntityId, contextToolId: EntityId) { - const tool: ContextExternalTool = await this.contextExternalToolService.getContextExternalToolById(contextToolId); + const tool: ContextExternalTool = await this.contextExternalToolService.findById(contextToolId); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); await this.toolPermissionHelper.ensureContextPermissions(userId, tool, context); diff --git a/apps/server/src/modules/tool/context-external-tool/uc/index.ts b/apps/server/src/modules/tool/context-external-tool/uc/index.ts index cd34b162bad..12f2a82a9f1 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/index.ts @@ -1 +1,2 @@ export * from './context-external-tool.uc'; +export * from './tool-reference.uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts new file mode 100644 index 00000000000..e0fbdcf6a6a --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts @@ -0,0 +1,215 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Permission } from '@shared/domain'; +import { contextExternalToolFactory, externalToolFactory } from '@shared/testing'; +import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { ToolConfigurationStatus, ToolContextType } from '../../common/enum'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { ExternalTool } from '../../external-tool/domain'; +import { ContextExternalTool, ToolReference } from '../domain'; +import { ContextExternalToolService, ToolReferenceService } from '../service'; +import { ToolReferenceUc } from './tool-reference.uc'; + +describe('ToolReferenceUc', () => { + let module: TestingModule; + let uc: ToolReferenceUc; + + let contextExternalToolService: DeepMocked; + let toolReferenceService: DeepMocked; + let toolPermissionHelper: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ToolReferenceUc, + { + provide: ContextExternalToolService, + useValue: createMock(), + }, + { + provide: ToolReferenceService, + useValue: createMock(), + }, + { + provide: ToolPermissionHelper, + useValue: createMock(), + }, + ], + }).compile(); + + uc = module.get(ToolReferenceUc); + + contextExternalToolService = module.get(ContextExternalToolService); + toolReferenceService = module.get(ToolReferenceService); + toolPermissionHelper = module.get(ToolPermissionHelper); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('getToolReferencesForContext', () => { + describe('when called with a context type and id', () => { + const setup = () => { + const userId = 'userId'; + + const externalTool: ExternalTool = externalToolFactory.withBase64Logo().buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + const toolReference: ToolReference = new ToolReference({ + logoUrl: externalTool.logoUrl, + contextToolId: contextExternalTool.id as string, + displayName: contextExternalTool.displayName as string, + status: ToolConfigurationStatus.LATEST, + openInNewTab: externalTool.openNewTab, + }); + + const contextType: ToolContextType = ToolContextType.COURSE; + const contextId = 'contextId'; + + contextExternalToolService.findAllByContext.mockResolvedValueOnce([contextExternalTool]); + toolPermissionHelper.ensureContextPermissions.mockResolvedValueOnce(); + toolReferenceService.getToolReference.mockResolvedValue(toolReference); + + return { + userId, + contextType, + contextId, + contextExternalTool, + externalTool, + toolReference, + }; + }; + + it('should call toolPermissionHelper.ensureContextPermissions', async () => { + const { userId, contextType, contextId, contextExternalTool } = setup(); + + await uc.getToolReferencesForContext(userId, contextType, contextId); + + expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( + userId, + contextExternalTool, + AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]) + ); + }); + + it('should return a list of tool references', async () => { + const { userId, contextType, contextId, toolReference } = setup(); + + const result: ToolReference[] = await uc.getToolReferencesForContext(userId, contextType, contextId); + + expect(result).toEqual([toolReference]); + }); + }); + + describe('when user does not have permission to a tool', () => { + const setup = () => { + const userId = 'userId'; + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + + const contextType: ToolContextType = ToolContextType.COURSE; + const contextId = 'contextId'; + + contextExternalToolService.findAllByContext.mockResolvedValueOnce([contextExternalTool]); + toolPermissionHelper.ensureContextPermissions.mockRejectedValueOnce(new ForbiddenException()); + + return { + userId, + contextType, + contextId, + }; + }; + + it('should filter out tool references if a ForbiddenException is thrown', async () => { + const { userId, contextType, contextId } = setup(); + + const result: ToolReference[] = await uc.getToolReferencesForContext(userId, contextType, contextId); + + expect(result).toEqual([]); + }); + }); + }); + + describe('getToolReference', () => { + describe('when called with a context type and id', () => { + const setup = () => { + const userId = 'userId'; + const contextExternalToolId = 'contextExternalToolId'; + + const externalTool: ExternalTool = externalToolFactory.withBase64Logo().buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( + undefined, + contextExternalToolId + ); + const toolReference: ToolReference = new ToolReference({ + logoUrl: externalTool.logoUrl, + contextToolId: contextExternalTool.id as string, + displayName: contextExternalTool.displayName as string, + status: ToolConfigurationStatus.LATEST, + openInNewTab: externalTool.openNewTab, + }); + + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); + toolPermissionHelper.ensureContextPermissions.mockResolvedValueOnce(); + toolReferenceService.getToolReference.mockResolvedValue(toolReference); + + return { + userId, + contextExternalTool, + externalTool, + toolReference, + contextExternalToolId, + }; + }; + + it('should call toolPermissionHelper.ensureContextPermissions', async () => { + const { userId, contextExternalToolId, contextExternalTool } = setup(); + + await uc.getToolReference(userId, contextExternalToolId); + + expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( + userId, + contextExternalTool, + AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]) + ); + }); + + it('should return a list of tool references', async () => { + const { userId, contextExternalToolId, toolReference } = setup(); + + const result: ToolReference = await uc.getToolReference(userId, contextExternalToolId); + + expect(result).toEqual(toolReference); + }); + }); + + describe('when user does not have permission to a tool', () => { + const setup = () => { + const userId = 'userId'; + const contextExternalToolId = 'contextExternalToolId'; + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( + undefined, + contextExternalToolId + ); + const error = new ForbiddenException(); + + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); + toolPermissionHelper.ensureContextPermissions.mockRejectedValueOnce(error); + + return { + userId, + contextExternalToolId, + error, + }; + }; + + it('should filter out tool references if a ForbiddenException is thrown', async () => { + const { userId, contextExternalToolId, error } = setup(); + + await expect(uc.getToolReference(userId, contextExternalToolId)).rejects.toThrow(error); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts new file mode 100644 index 00000000000..c044e01dfeb --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId, Permission } from '@shared/domain'; +import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { ToolContextType } from '../../common/enum'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { ContextExternalTool, ContextRef, ToolReference } from '../domain'; +import { ContextExternalToolService, ToolReferenceService } from '../service'; + +@Injectable() +export class ToolReferenceUc { + constructor( + private readonly contextExternalToolService: ContextExternalToolService, + private readonly toolReferenceService: ToolReferenceService, + private readonly toolPermissionHelper: ToolPermissionHelper + ) {} + + async getToolReferencesForContext( + userId: EntityId, + contextType: ToolContextType, + contextId: EntityId + ): Promise { + const contextRef = new ContextRef({ type: contextType, id: contextId }); + + const contextExternalTools: ContextExternalTool[] = await this.contextExternalToolService.findAllByContext( + contextRef + ); + + const toolReferencesPromises: Promise[] = contextExternalTools.map( + async (contextExternalTool: ContextExternalTool) => this.tryBuildToolReference(userId, contextExternalTool) + ); + + const toolReferencesWithNull: (ToolReference | null)[] = await Promise.all(toolReferencesPromises); + const filteredToolReferences: ToolReference[] = toolReferencesWithNull.filter( + (toolReference: ToolReference | null): toolReference is ToolReference => toolReference !== null + ); + + return filteredToolReferences; + } + + private async tryBuildToolReference( + userId: EntityId, + contextExternalTool: ContextExternalTool + ): Promise { + try { + await this.ensureToolPermissions(userId, contextExternalTool); + + const toolReference: ToolReference = await this.toolReferenceService.getToolReference( + contextExternalTool.id as string + ); + + return toolReference; + } catch (e: unknown) { + return null; + } + } + + async getToolReference(userId: EntityId, contextExternalToolId: EntityId): Promise { + const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( + contextExternalToolId + ); + + await this.ensureToolPermissions(userId, contextExternalTool); + + const toolReference: ToolReference = await this.toolReferenceService.getToolReference( + contextExternalTool.id as string + ); + + return toolReference; + } + + private async ensureToolPermissions(userId: EntityId, contextExternalTool: ContextExternalTool): Promise { + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + + const promise: Promise = this.toolPermissionHelper.ensureContextPermissions( + userId, + contextExternalTool, + context + ); + + return promise; + } +} 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 6498f66fc1f..0d0c3fb08dc 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,41 +1,27 @@ +import { Loaded } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Course, Permission, SchoolEntity } from '@shared/domain'; +import { Permission } from '@shared/domain'; import { cleanupCollections, - contextExternalToolEntityFactory, - courseFactory, externalToolEntityFactory, externalToolFactory, - schoolExternalToolEntityFactory, - schoolFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; +import { ServerTestModule } from '@src/modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { Response } from 'supertest'; -import { Loaded } from '@mikro-orm/core'; -import { ServerTestModule } from '@src/modules/server'; import { CustomParameterLocationParams, CustomParameterScopeTypeParams, CustomParameterTypeParams, ToolConfigType, - ToolContextType, } from '../../../common/enum'; -import { - ExternalToolCreateParams, - ExternalToolResponse, - ExternalToolSearchListResponse, - ToolConfigurationStatusResponse, - ToolReferenceListResponse, -} from '../dto'; -import { ContextExternalToolContextParams } from '../../../context-external-tool/controller/dto'; import { ExternalToolEntity } from '../../entity'; -import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; -import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ExternalToolCreateParams, ExternalToolResponse, ExternalToolSearchListResponse } from '../dto'; describe('ToolController (API)', () => { let app: INestApplication; @@ -597,126 +583,6 @@ describe('ToolController (API)', () => { }); }); - describe('[GET] tools/external-tools/:contextType/:contextId/references', () => { - describe('when user is not authenticated', () => { - it('should return unauthorized', async () => { - const response: Response = await testApiClient.get(`contextType/${new ObjectId().toHexString()}/references`); - - expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); - }); - }); - - describe('when user has no access to a tool', () => { - const setup = async () => { - const schoolWithoutTool: SchoolEntity = schoolFactory.buildWithId(); - const school: SchoolEntity = schoolFactory.buildWithId(); - const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school: schoolWithoutTool }); - const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); - const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(); - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ - school, - tool: externalToolEntity, - }); - const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextId: course.id, - contextType: ContextExternalToolType.COURSE, - }); - - await em.persistAndFlush([ - school, - adminAccount, - adminUser, - course, - externalToolEntity, - schoolExternalToolEntity, - contextExternalToolEntity, - ]); - em.clear(); - - const params: ContextExternalToolContextParams = { - contextId: course.id, - contextType: ToolContextType.COURSE, - }; - - const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); - - return { loggedInClient, params }; - }; - - it('should filter out the tool', async () => { - const { loggedInClient, params } = await setup(); - - const response: Response = await loggedInClient.get(`${params.contextType}/${params.contextId}/references`); - - expect(response.statusCode).toEqual(HttpStatus.OK); - expect(response.body).toEqual({ data: [] }); - }); - }); - - describe('when user has access for a tool', () => { - const setup = async () => { - const school: SchoolEntity = schoolFactory.buildWithId(); - const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ - Permission.CONTEXT_TOOL_USER, - ]); - const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] }); - const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ logoUrl: undefined }); - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ - school, - tool: externalToolEntity, - toolVersion: externalToolEntity.version, - }); - const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ - schoolTool: schoolExternalToolEntity, - contextId: course.id, - contextType: ContextExternalToolType.COURSE, - displayName: 'This is a test tool', - toolVersion: schoolExternalToolEntity.toolVersion, - }); - - await em.persistAndFlush([ - school, - adminAccount, - adminUser, - course, - externalToolEntity, - schoolExternalToolEntity, - contextExternalToolEntity, - ]); - em.clear(); - - const params: ContextExternalToolContextParams = { - contextId: course.id, - contextType: ToolContextType.COURSE, - }; - - const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); - - return { loggedInClient, params, contextExternalToolEntity, externalToolEntity }; - }; - - it('should return an ToolReferenceListResponse with data', async () => { - const { loggedInClient, params, contextExternalToolEntity, externalToolEntity } = await setup(); - - const response: Response = await loggedInClient.get(`${params.contextType}/${params.contextId}/references`); - - expect(response.statusCode).toEqual(HttpStatus.OK); - expect(response.body).toEqual({ - data: [ - { - contextToolId: contextExternalToolEntity.id, - displayName: contextExternalToolEntity.displayName as string, - status: ToolConfigurationStatusResponse.LATEST, - logoUrl: externalToolEntity.logoUrl, - openInNewTab: externalToolEntity.openNewTab, - }, - ], - }); - }); - }); - }); - describe('[GET] tools/external-tools/:externalToolId/logo', () => { const setup = async () => { const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.withBase64Logo().buildWithId(); 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 fbae39a8b33..e9e5fafa376 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 @@ -1,10 +1,7 @@ export * from './config'; export * from './external-tool.response'; -export * from './tool-reference.response'; export * from './custom-parameter.response'; -export * from './tool-reference-list.response'; export * from './external-tool-search-list.response'; -export * from './tool-configuration-status.response'; 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'; 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 3e6ac38fedc..4c3658d8025 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 @@ -18,23 +18,20 @@ import { ICurrentUser } from '@src/modules/authentication'; import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ContextExternalToolContextParams } from '../../context-external-tool/controller/dto'; -import { ExternalTool, ToolReference } from '../domain'; +import { ExternalTool } from '../domain'; import { ExternalToolLogo } from '../domain/external-tool-logo'; import { ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; -import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate, ToolReferenceUc } from '../uc'; +import { ExternalToolLogoService } from '../service'; +import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; import { ExternalToolCreateParams, + ExternalToolIdParams, ExternalToolResponse, ExternalToolSearchListResponse, ExternalToolSearchParams, ExternalToolUpdateParams, SortExternalToolParams, - ExternalToolIdParams, - ToolReferenceListResponse, - ToolReferenceResponse, } from './dto'; -import { ExternalToolLogoService } from '../service'; @ApiTags('Tool') @Authenticate('jwt') @@ -43,7 +40,6 @@ export class ToolController { constructor( private readonly externalToolUc: ExternalToolUc, private readonly externalToolDOMapper: ExternalToolRequestMapper, - private readonly toolReferenceUc: ToolReferenceUc, private readonly logger: LegacyLogger, private readonly externalToolLogoService: ExternalToolLogoService ) {} @@ -156,32 +152,6 @@ export class ToolController { return promise; } - @Get('/:contextType/:contextId/references') - @ApiOperation({ summary: 'Get ExternalTool References for a given context' }) - @ApiOkResponse({ - description: 'The Tool References has been successfully fetched.', - type: ToolReferenceListResponse, - }) - @ApiForbiddenResponse({ description: 'User is not allowed to access this resource.' }) - @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) - async getToolReferences( - @CurrentUser() currentUser: ICurrentUser, - @Param() params: ContextExternalToolContextParams - ): Promise { - const toolReferences: ToolReference[] = await this.toolReferenceUc.getToolReferences( - currentUser.userId, - params.contextType, - params.contextId, - '/v3/tools/external-tools/{id}/logo' - ); - - const toolReferenceResponses: ToolReferenceResponse[] = - ExternalToolResponseMapper.mapToToolReferenceResponses(toolReferences); - const toolReferenceListResponse = new ToolReferenceListResponse(toolReferenceResponses); - - return toolReferenceListResponse; - } - @Get('/:externalToolId/logo') @ApiOperation({ summary: 'Gets the logo of an external tool.' }) @ApiOkResponse({ 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 9eaf1f03cbb..e5a1dab735d 100644 --- a/apps/server/src/modules/tool/external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/external-tool/domain/index.ts @@ -1,3 +1,2 @@ export * from './external-tool.do'; export * from './config'; -export * from './tool-reference'; diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts index 2885c2ea0c0..b2035e66477 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts @@ -8,16 +8,14 @@ import { CustomParameterType, CustomParameterTypeParams, } from '../../common/enum'; -import { statusMapping } from '../../school-external-tool/mapper'; import { BasicToolConfigResponse, CustomParameterResponse, ExternalToolResponse, Lti11ToolConfigResponse, Oauth2ToolConfigResponse, - ToolReferenceResponse, } from '../controller/dto'; -import { BasicToolConfig, ExternalTool, Lti11ToolConfig, Oauth2ToolConfig, ToolReference } from '../domain'; +import { BasicToolConfig, ExternalTool, Lti11ToolConfig, Oauth2ToolConfig } from '../domain'; const scopeMapping: Record = { [CustomParameterScope.GLOBAL]: CustomParameterScopeTypeParams.GLOBAL, @@ -98,24 +96,4 @@ export class ExternalToolResponseMapper { }; }); } - - static mapToToolReferenceResponses(toolReferences: ToolReference[]): ToolReferenceResponse[] { - const toolReferenceResponses: ToolReferenceResponse[] = toolReferences.map((toolReference: ToolReference) => - this.mapToToolReferenceResponse(toolReference) - ); - - return toolReferenceResponses; - } - - private static mapToToolReferenceResponse(toolReference: ToolReference): ToolReferenceResponse { - const response = new ToolReferenceResponse({ - contextToolId: toolReference.contextToolId, - displayName: toolReference.displayName, - logoUrl: toolReference.logoUrl, - openInNewTab: toolReference.openInNewTab, - status: statusMapping[toolReference.status], - }); - - return response; - } } 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 92aff5e73c9..4149a17a519 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/index.ts @@ -1,3 +1,2 @@ -export * from './tool-reference.mapper'; export * from './external-tool-request.mapper'; export * from './external-tool-response.mapper'; diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts index 57acd50122f..c53154098a5 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts @@ -1,4 +1,4 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { HttpService } from '@nestjs/axios'; import { HttpException, HttpStatus } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -9,8 +9,8 @@ import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ExternalTool } from '../domain'; import { ExternalToolLogo } from '../domain/external-tool-logo'; import { - ExternalToolLogoFetchFailedLoggableException, ExternalToolLogoFetchedLoggable, + ExternalToolLogoFetchFailedLoggableException, ExternalToolLogoNotFoundLoggableException, ExternalToolLogoSizeExceededLoggableException, ExternalToolLogoWrongFileTypeLoggableException, @@ -329,7 +329,7 @@ describe('ExternalToolLogoService', () => { const setup = () => { const externalTool: ExternalTool = externalToolFactory.withBase64Logo().buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); return { externalToolId: externalTool.id as string, @@ -355,7 +355,7 @@ describe('ExternalToolLogoService', () => { const setup = () => { const externalTool: ExternalTool = externalToolFactory.buildWithId({ logo: 'notAValidBase64File' }); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); return { externalToolId: externalTool.id as string, @@ -375,7 +375,7 @@ describe('ExternalToolLogoService', () => { const setup = () => { const externalTool: ExternalTool = externalToolFactory.buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); return { externalToolId: externalTool.id as string, diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts index f2518e65a3a..b39684fbd1b 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts @@ -1,19 +1,19 @@ +import { HttpService } from '@nestjs/axios'; import { HttpException, Inject } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { Logger } from '@src/core/logger'; import { AxiosResponse } from 'axios'; import { lastValueFrom } from 'rxjs'; -import { HttpService } from '@nestjs/axios'; -import { Logger } from '@src/core/logger'; -import { EntityId } from '@shared/domain'; +import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ExternalTool } from '../domain'; +import { ExternalToolLogo } from '../domain/external-tool-logo'; import { ExternalToolLogoFetchedLoggable, + ExternalToolLogoFetchFailedLoggableException, ExternalToolLogoNotFoundLoggableException, ExternalToolLogoSizeExceededLoggableException, ExternalToolLogoWrongFileTypeLoggableException, - ExternalToolLogoFetchFailedLoggableException, } from '../loggable'; -import { IToolFeatures, ToolFeatures } from '../../tool-config'; -import { ExternalToolLogo } from '../domain/external-tool-logo'; import { ExternalToolService } from './external-tool.service'; const contentTypeDetector: Record = { @@ -95,7 +95,7 @@ export class ExternalToolLogoService { } async getExternalToolBinaryLogo(toolId: EntityId): Promise { - const tool: ExternalTool = await this.externalToolService.findExternalToolById(toolId); + const tool: ExternalTool = await this.externalToolService.findById(toolId); if (!tool.logo) { throw new ExternalToolLogoNotFoundLoggableException(toolId); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.spec.ts index 8f5f1607df6..42efcd6559a 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.spec.ts @@ -4,10 +4,10 @@ import { ValidationError } from '@shared/common'; import { externalToolFactory } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ExternalTool } from '../domain'; +import { ExternalToolLogoService } from './external-tool-logo.service'; import { ExternalToolParameterValidationService } from './external-tool-parameter-validation.service'; import { ExternalToolValidationService } from './external-tool-validation.service'; import { ExternalToolService } from './external-tool.service'; -import { ExternalToolLogoService } from './external-tool-logo.service'; describe('ExternalToolValidationService', () => { let module: TestingModule; @@ -232,7 +232,7 @@ describe('ExternalToolValidationService', () => { .buildWithId(); externalOauthTool.id = 'toolId'; - externalToolService.findExternalToolById.mockResolvedValue(externalOauthTool); + externalToolService.findById.mockResolvedValue(externalOauthTool); return { externalOauthTool, @@ -266,7 +266,7 @@ describe('ExternalToolValidationService', () => { .withOauth2Config({ clientId: 'ClientId', clientSecret: 'secret' }) .buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(existingExternalOauthTool); + externalToolService.findById.mockResolvedValue(existingExternalOauthTool); const newExternalTool: ExternalTool = externalToolFactory.buildWithId(); @@ -296,7 +296,7 @@ describe('ExternalToolValidationService', () => { .withOauth2Config({ clientId: 'ClientId', clientSecret: 'secret' }) .buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(externalOauthTool); + externalToolService.findById.mockResolvedValue(externalOauthTool); return { externalOauthTool }; }; @@ -318,7 +318,7 @@ describe('ExternalToolValidationService', () => { const existingExternalOauthToolDOWithDifferentClientId: ExternalTool = externalToolFactory .withOauth2Config({ clientId: 'DifferentClientId', clientSecret: 'secret' }) .buildWithId(); - externalToolService.findExternalToolById.mockResolvedValue(existingExternalOauthToolDOWithDifferentClientId); + externalToolService.findById.mockResolvedValue(existingExternalOauthToolDOWithDifferentClientId); return { externalOauthTool, @@ -344,7 +344,7 @@ describe('ExternalToolValidationService', () => { const externalLtiToolDO: ExternalTool = externalToolFactory.withLti11Config().buildWithId(); externalLtiToolDO.id = 'toolId'; - externalToolService.findExternalToolById.mockResolvedValue(externalLtiToolDO); + externalToolService.findById.mockResolvedValue(externalLtiToolDO); return { externalLtiToolDO, diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.ts index 90b8307dc7e..434e7fac86e 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-validation.service.ts @@ -2,9 +2,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { ValidationError } from '@shared/common'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ExternalTool } from '../domain'; +import { ExternalToolLogoService } from './external-tool-logo.service'; import { ExternalToolParameterValidationService } from './external-tool-parameter-validation.service'; import { ExternalToolService } from './external-tool.service'; -import { ExternalToolLogoService } from './external-tool-logo.service'; @Injectable() export class ExternalToolValidationService { @@ -32,7 +32,7 @@ export class ExternalToolValidationService { await this.externalToolParameterValidationService.validateCommon(externalTool); - const loadedTool: ExternalTool = await this.externalToolService.findExternalToolById(toolId); + const loadedTool: ExternalTool = await this.externalToolService.findById(toolId); if ( ExternalTool.isOauth2Config(loadedTool.config) && externalTool.config && diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts index d2913e5401a..4db2a5be0b0 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts @@ -308,7 +308,7 @@ describe('ExternalToolService', () => { }); }); - describe('findExternalToolById', () => { + describe('findById', () => { describe('when external tool id is set', () => { const setup = () => { const { externalTool } = createTools(); @@ -320,7 +320,7 @@ describe('ExternalToolService', () => { it('should get domain object', async () => { const { externalTool } = setup(); - const result: ExternalTool = await service.findExternalToolById('toolId'); + const result: ExternalTool = await service.findById('toolId'); expect(result).toEqual(externalTool); }); @@ -340,7 +340,7 @@ describe('ExternalToolService', () => { it('should get domain object and add external oauth2 data', async () => { const { externalTool, oauth2ToolConfig } = setup(); - const result: ExternalTool = await service.findExternalToolById('toolId'); + const result: ExternalTool = await service.findById('toolId'); expect(result).toEqual({ ...externalTool, config: oauth2ToolConfig }); }); @@ -362,7 +362,7 @@ describe('ExternalToolService', () => { it('should throw UnprocessableEntityException ', async () => { const { externalTool } = setup(); - const func = () => service.findExternalToolById('toolId'); + const func = () => service.findById('toolId'); await expect(func()).rejects.toThrow(`Could not resolve oauth2Config of tool ${externalTool.name}.`); }); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts index 2a53f8aae45..fcc1a7e2d5c 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts @@ -75,7 +75,7 @@ export class ExternalToolService { return tools; } - async findExternalToolById(id: EntityId): Promise { + async findById(id: EntityId): Promise { const tool: ExternalTool = await this.externalToolRepo.findById(id); if (ExternalTool.isOauth2Config(tool.config)) { try { diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index 5490f0c546b..0ed3a3317f8 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -12,14 +12,14 @@ import { } from '@shared/testing'; import { AuthorizationContextBuilder } from '@src/modules/authorization'; import { CustomParameterScope, ToolContextType } from '../../common/enum'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ExternalTool } from '../domain'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolConfigurationService } from '../service'; +import { ExternalToolConfigurationService, ExternalToolLogoService, ExternalToolService } from '../service'; import { ExternalToolConfigurationUc } from './external-tool-configuration.uc'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; describe('ExternalToolConfigurationUc', () => { let module: TestingModule; @@ -439,8 +439,8 @@ describe('ExternalToolConfigurationUc', () => { schoolExternalToolId ); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); return { externalTool, @@ -478,7 +478,7 @@ describe('ExternalToolConfigurationUc', () => { schoolExternalToolId ); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); toolPermissionHelper.ensureSchoolPermissions.mockImplementation(() => { throw new UnauthorizedException(); }); @@ -512,8 +512,8 @@ describe('ExternalToolConfigurationUc', () => { schoolExternalToolId ); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); return { schoolExternalToolId, @@ -553,9 +553,9 @@ describe('ExternalToolConfigurationUc', () => { contextExternalToolId ); - contextExternalToolService.getContextExternalToolById.mockResolvedValueOnce(contextExternalTool); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); return { externalTool, @@ -593,7 +593,7 @@ describe('ExternalToolConfigurationUc', () => { contextExternalToolId ); - contextExternalToolService.getContextExternalToolById.mockResolvedValueOnce(contextExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); toolPermissionHelper.ensureContextPermissions.mockImplementation(() => { throw new UnauthorizedException(); }); @@ -632,9 +632,9 @@ describe('ExternalToolConfigurationUc', () => { contextExternalToolId ); - contextExternalToolService.getContextExternalToolById.mockResolvedValueOnce(contextExternalTool); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + externalToolService.findById.mockResolvedValueOnce(externalTool); return { contextExternalToolId, diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 9607beb84df..16dd9626d0c 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -4,14 +4,14 @@ import { EntityId, Permission } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; import { CustomParameterScope, ToolContextType } from '../../common/enum'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ExternalTool } from '../domain'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolConfigurationService } from '../service'; +import { ExternalToolConfigurationService, ExternalToolLogoService, ExternalToolService } from '../service'; import { ContextExternalToolTemplateInfo } from './dto'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; @Injectable() export class ExternalToolConfigurationUc { @@ -117,14 +117,12 @@ export class ExternalToolConfigurationUc { userId: EntityId, schoolExternalToolId: EntityId ): Promise { - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( - schoolExternalToolId - ); + 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 externalTool: ExternalTool = await this.externalToolService.findExternalToolById(schoolExternalTool.toolId); + const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); if (externalTool.isHidden) { throw new NotFoundException('Could not find the Tool Template'); @@ -139,18 +137,18 @@ export class ExternalToolConfigurationUc { userId: EntityId, contextExternalToolId: EntityId ): Promise { - const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.getContextExternalToolById( + const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( contextExternalToolId ); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( contextExternalTool.schoolToolRef.schoolToolId ); - const externalTool: ExternalTool = await this.externalToolService.findExternalToolById(schoolExternalTool.toolId); + const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); if (externalTool.isHidden) { throw new NotFoundException('Could not find the Tool Template'); 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 d0b02f1e4f3..f2baadd885c 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 @@ -301,7 +301,7 @@ describe('ExternalToolUc', () => { it('should fetch a tool', async () => { const { currentUser } = setupAuthorization(); const { externalTool, toolId } = setup(); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const result: ExternalTool = await uc.getExternalTool(currentUser.userId, toolId); @@ -327,7 +327,7 @@ describe('ExternalToolUc', () => { }); externalToolService.updateExternalTool.mockResolvedValue(updatedExternalToolDO); - externalToolService.findExternalToolById.mockResolvedValue(new ExternalTool(externalToolDOtoUpdate)); + externalToolService.findById.mockResolvedValue(new ExternalTool(externalToolDOtoUpdate)); return { externalTool, 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 240977b2b38..3fb81e4f74a 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 @@ -35,7 +35,7 @@ export class ExternalToolUc { await this.toolValidationService.validateUpdate(toolId, externalTool); - const loaded: ExternalTool = await this.externalToolService.findExternalToolById(toolId); + const loaded: ExternalTool = await this.externalToolService.findById(toolId); const configToUpdate: ExternalToolConfig = { ...loaded.config, ...externalTool.config }; const toUpdate: ExternalTool = new ExternalTool({ ...loaded, @@ -63,7 +63,7 @@ export class ExternalToolUc { async getExternalTool(userId: EntityId, toolId: EntityId): Promise { await this.ensurePermission(userId, Permission.TOOL_ADMIN); - const tool: ExternalTool = await this.externalToolService.findExternalToolById(toolId); + const tool: ExternalTool = await this.externalToolService.findById(toolId); return tool; } diff --git a/apps/server/src/modules/tool/external-tool/uc/index.ts b/apps/server/src/modules/tool/external-tool/uc/index.ts index 46f3a860080..0a61273b29b 100644 --- a/apps/server/src/modules/tool/external-tool/uc/index.ts +++ b/apps/server/src/modules/tool/external-tool/uc/index.ts @@ -1,4 +1,3 @@ export * from './dto'; export * from './external-tool.uc'; -export * from './tool-reference.uc'; export * from './external-tool-configuration.uc'; diff --git a/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.spec.ts deleted file mode 100644 index e06c34e5e8b..00000000000 --- a/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { ForbiddenException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '@shared/domain'; -import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; -import { ToolReferenceUc } from './tool-reference.uc'; -import { ToolConfigurationStatus, ToolContextType } from '../../common/enum'; -import { CommonToolService } from '../../common/service'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { SchoolExternalToolService } from '../../school-external-tool/service'; -import { ExternalTool, ToolReference } from '../domain'; -import { ExternalToolLogoService, ExternalToolService } from '../service'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; - -describe('ToolReferenceUc', () => { - let module: TestingModule; - let uc: ToolReferenceUc; - - let externalToolService: DeepMocked; - let schoolExternalToolService: DeepMocked; - let contextExternalToolService: DeepMocked; - let toolPermissionHelper: DeepMocked; - let commonToolService: DeepMocked; - let logoService: DeepMocked; - - beforeAll(async () => { - module = await Test.createTestingModule({ - providers: [ - ToolReferenceUc, - { - provide: ExternalToolService, - useValue: createMock(), - }, - { - provide: SchoolExternalToolService, - useValue: createMock(), - }, - { - provide: ContextExternalToolService, - useValue: createMock(), - }, - { - provide: CommonToolService, - useValue: createMock(), - }, - { - provide: ExternalToolLogoService, - useValue: createMock(), - }, - { - provide: ToolPermissionHelper, - useValue: createMock(), - }, - ], - }).compile(); - - uc = module.get(ToolReferenceUc); - - externalToolService = module.get(ExternalToolService); - schoolExternalToolService = module.get(SchoolExternalToolService); - contextExternalToolService = module.get(ContextExternalToolService); - toolPermissionHelper = module.get(ToolPermissionHelper); - commonToolService = module.get(CommonToolService); - logoService = module.get(ExternalToolLogoService); - }); - - afterAll(async () => { - await module.close(); - }); - - describe('getToolReferences', () => { - describe('when called with a context type and id', () => { - const setup = () => { - const userId = 'userId'; - - const externalTool: ExternalTool = externalToolFactory.withBase64Logo().buildWithId(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ - toolId: externalTool.id, - }); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory - .withSchoolExternalToolRef('schoolToolId', 'schoolId') - .buildWithId(); - - const contextType: ToolContextType = ToolContextType.COURSE; - const contextId = 'contextId'; - - contextExternalToolService.findAllByContext.mockResolvedValueOnce([contextExternalTool]); - toolPermissionHelper.ensureContextPermissions.mockResolvedValueOnce(); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); - commonToolService.determineToolConfigurationStatus.mockReturnValueOnce(ToolConfigurationStatus.LATEST); - - return { - userId, - contextType, - contextId, - contextExternalTool, - schoolExternalTool, - externalTool, - externalToolId: externalTool.id as string, - }; - }; - - it('should call toolPermissionHelper.ensureContextPermissions', async () => { - const { userId, contextType, contextId, contextExternalTool } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( - userId, - contextExternalTool, - AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]) - ); - }); - - it('should call contextExternalToolService.findAllByContext', async () => { - const { userId, contextType, contextId } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(contextExternalToolService.findAllByContext).toHaveBeenCalledWith({ - type: contextType, - id: contextId, - }); - }); - - it('should call schoolExternalToolService.findByExternalToolId', async () => { - const { userId, contextType, contextId, contextExternalTool } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(schoolExternalToolService.getSchoolExternalToolById).toHaveBeenCalledWith( - contextExternalTool.schoolToolRef.schoolToolId - ); - }); - - it('should call externalToolService.findById', async () => { - const { userId, contextType, contextId, externalToolId } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(externalToolId); - }); - - it('should call commonToolService.determineToolConfigurationStatus', async () => { - const { userId, contextType, contextId, contextExternalTool, schoolExternalTool, externalTool } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(commonToolService.determineToolConfigurationStatus).toHaveBeenCalledWith( - externalTool, - schoolExternalTool, - contextExternalTool - ); - }); - - it('should call externalToolLogoService.buildLogoUrl', async () => { - const { userId, contextType, contextId, externalTool } = setup(); - - await uc.getToolReferences(userId, contextType, contextId, '/v3/tools/external-tools/{id}/logo'); - - expect(logoService.buildLogoUrl).toHaveBeenCalledWith('/v3/tools/external-tools/{id}/logo', externalTool); - }); - - it('should return a list of tool references', async () => { - const { userId, contextType, contextId, contextExternalTool, externalTool } = setup(); - - const result: ToolReference[] = await uc.getToolReferences( - userId, - contextType, - contextId, - '/v3/tools/external-tools/{id}/logo' - ); - - expect(result).toEqual([ - { - logoUrl: `${Configuration.get('PUBLIC_BACKEND_URL') as string}/v3/tools/external-tools/${ - externalTool.id as string - }/logo`, - openInNewTab: externalTool.openNewTab, - contextToolId: contextExternalTool.id as string, - displayName: contextExternalTool.displayName as string, - status: ToolConfigurationStatus.LATEST, - }, - ]); - }); - }); - - describe('when user does not have permission to a tool', () => { - const setup = () => { - const userId = 'userId'; - - const externalTool: ExternalTool = externalToolFactory.buildWithId(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ - toolId: externalTool.id, - }); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory - .withSchoolExternalToolRef('schoolToolId', 'schoolId') - .buildWithId(); - - const contextType: ToolContextType = ToolContextType.COURSE; - const contextId = 'contextId'; - - contextExternalToolService.findAllByContext.mockResolvedValueOnce([contextExternalTool]); - toolPermissionHelper.ensureContextPermissions.mockRejectedValueOnce(new ForbiddenException()); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValueOnce(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValueOnce(externalTool); - - return { - userId, - contextType, - contextId, - }; - }; - - it('should filter out tool references if a ForbiddenException is thrown', async () => { - const { userId, contextType, contextId } = setup(); - - const result: ToolReference[] = await uc.getToolReferences( - userId, - contextType, - contextId, - '/v3/tools/external-tools/{id}/logo' - ); - - expect(result).toEqual([]); - }); - }); - }); -}); diff --git a/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.ts b/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.ts deleted file mode 100644 index 5ddf0e467c6..00000000000 --- a/apps/server/src/modules/tool/external-tool/uc/tool-reference.uc.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ForbiddenException, Injectable } from '@nestjs/common'; -import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; -import { ExternalTool, ToolReference } from '../domain'; -import { ToolConfigurationStatus, ToolContextType } from '../../common/enum'; -import { CommonToolService } from '../../common/service'; -import { ContextExternalTool, ContextRef } from '../../context-external-tool/domain'; -import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { SchoolExternalToolService } from '../../school-external-tool/service'; -import { ToolReferenceMapper } from '../mapper/tool-reference.mapper'; -import { ExternalToolLogoService, ExternalToolService } from '../service'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; - -@Injectable() -export class ToolReferenceUc { - constructor( - private readonly externalToolService: ExternalToolService, - private readonly schoolExternalToolService: SchoolExternalToolService, - private readonly contextExternalToolService: ContextExternalToolService, - private readonly toolPermissionHelper: ToolPermissionHelper, - private readonly commonToolService: CommonToolService, - private readonly externalToolLogoService: ExternalToolLogoService - ) {} - - async getToolReferences( - userId: EntityId, - contextType: ToolContextType, - contextId: string, - logoUrlTemplate: string - ): Promise { - const contextRef = new ContextRef({ type: contextType, id: contextId }); - - const contextExternalTools: ContextExternalTool[] = await this.contextExternalToolService.findAllByContext( - contextRef - ); - - const toolReferencesPromises: Promise[] = contextExternalTools.map( - (contextExternalTool: ContextExternalTool) => - this.buildToolReference(userId, contextExternalTool, logoUrlTemplate) - ); - - const toolReferencesWithNull: (ToolReference | null)[] = await Promise.all(toolReferencesPromises); - const filteredToolReferences: ToolReference[] = toolReferencesWithNull.filter( - (toolReference: ToolReference | null): toolReference is ToolReference => toolReference !== null - ); - - return filteredToolReferences; - } - - private async buildToolReference( - userId: EntityId, - contextExternalTool: ContextExternalTool, - logoUrlTemplate: string - ): Promise { - try { - await this.ensureToolPermissions(userId, contextExternalTool); - } catch (e: unknown) { - if (e instanceof ForbiddenException) { - return null; - } - } - - const schoolExternalTool: SchoolExternalTool = await this.fetchSchoolExternalTool(contextExternalTool); - const externalTool: ExternalTool = await this.fetchExternalTool(schoolExternalTool); - - const status: ToolConfigurationStatus = this.commonToolService.determineToolConfigurationStatus( - externalTool, - schoolExternalTool, - contextExternalTool - ); - - const toolReference: ToolReference = ToolReferenceMapper.mapToToolReference( - externalTool, - contextExternalTool, - status - ); - toolReference.logoUrl = this.externalToolLogoService.buildLogoUrl(logoUrlTemplate, externalTool); - - return toolReference; - } - - private async ensureToolPermissions(userId: EntityId, contextExternalTool: ContextExternalTool): Promise { - const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - - const promise: Promise = this.toolPermissionHelper.ensureContextPermissions( - userId, - contextExternalTool, - context - ); - - return promise; - } - - private async fetchSchoolExternalTool(contextExternalTool: ContextExternalTool): Promise { - return this.schoolExternalToolService.getSchoolExternalToolById(contextExternalTool.schoolToolRef.schoolToolId); - } - - private async fetchExternalTool(schoolExternalTool: SchoolExternalTool): Promise { - return this.externalToolService.findExternalToolById(schoolExternalTool.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 89bbe0c2cb7..c2504b42de2 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 @@ -12,6 +12,9 @@ import { userFactory, } from '@shared/testing'; import { ServerTestModule } from '@src/modules/server'; +import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ExternalToolEntity } from '../../../external-tool/entity'; +import { SchoolExternalToolEntity } from '../../entity'; import { CustomParameterEntryParam, SchoolExternalToolPostParams, @@ -19,9 +22,6 @@ import { SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, } from '../dto'; -import { ToolConfigurationStatusResponse } from '../../../external-tool/controller/dto'; -import { SchoolExternalToolEntity } from '../../entity'; -import { ExternalToolEntity } from '../../../external-tool/entity'; describe('ToolSchoolController (API)', () => { let app: INestApplication; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts index 62ad203fb02..32dd35f10bd 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; import { CustomParameterEntryResponse } from './custom-parameter-entry.response'; -import { ToolConfigurationStatusResponse } from '../../../external-tool/controller/dto'; export class SchoolExternalToolResponse { @ApiProperty() diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts index 916445770e3..ca2296e6df7 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts @@ -1,5 +1,5 @@ import { schoolExternalToolFactory } from '@shared/testing/factory'; -import { ToolConfigurationStatusResponse } from '../../external-tool/controller/dto'; +import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse } from '../controller/dto'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolResponseMapper } from './school-external-tool-response.mapper'; diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts index 10ee706dd81..7388b1a6a41 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { CustomParameterEntry } from '../../common/domain'; -import { ToolConfigurationStatus } from '../../common/enum'; -import { ToolConfigurationStatusResponse } from '../../external-tool/controller/dto'; +import { ToolStatusResponseMapper } from '../../common/mapper/tool-status-response.mapper'; +import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; import { CustomParameterEntryResponse, SchoolExternalToolResponse, @@ -9,12 +9,6 @@ import { } from '../controller/dto'; import { SchoolExternalTool } from '../domain'; -export const statusMapping: Record = { - [ToolConfigurationStatus.LATEST]: ToolConfigurationStatusResponse.LATEST, - [ToolConfigurationStatus.OUTDATED]: ToolConfigurationStatusResponse.OUTDATED, - [ToolConfigurationStatus.UNKNOWN]: ToolConfigurationStatusResponse.UNKNOWN, -}; - @Injectable() export class SchoolExternalToolResponseMapper { mapToSearchListResponse(externalTools: SchoolExternalTool[]): SchoolExternalToolSearchListResponse { @@ -33,7 +27,7 @@ export class SchoolExternalToolResponseMapper { parameters: this.mapToCustomParameterEntryResponse(schoolExternalTool.parameters), toolVersion: schoolExternalTool.toolVersion, status: schoolExternalTool.status - ? statusMapping[schoolExternalTool.status] + ? ToolStatusResponseMapper.mapToResponse(schoolExternalTool.status) : ToolConfigurationStatusResponse.UNKNOWN, }; } diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts index 7ca001675b0..e43bdeb42e0 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts @@ -51,7 +51,7 @@ describe('SchoolExternalToolValidationService', () => { ...externalToolFactory.buildWithId(), ...externalToolDoMock, }); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const schoolExternalToolId = schoolExternalTool.id as string; return { schoolExternalTool, @@ -66,7 +66,7 @@ describe('SchoolExternalToolValidationService', () => { await service.validate(schoolExternalTool); - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(schoolExternalTool.toolId); + expect(externalToolService.findById).toHaveBeenCalledWith(schoolExternalTool.toolId); }); it('should call commonToolValidationService.checkForDuplicateParameters', async () => { diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts index 8cc50097d5f..315d738ca64 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts @@ -15,9 +15,7 @@ export class SchoolExternalToolValidationService { async validate(schoolExternalTool: SchoolExternalTool): Promise { this.commonToolValidationService.checkForDuplicateParameters(schoolExternalTool); - const loadedExternalTool: ExternalTool = await this.externalToolService.findExternalToolById( - schoolExternalTool.toolId - ); + const loadedExternalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); this.checkVersionMatch(schoolExternalTool.toolVersion, loadedExternalTool.version); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts index 7c4031ef0b7..52f9b0a4c02 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts @@ -3,11 +3,11 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SchoolExternalToolRepo } from '@shared/repo'; import { externalToolFactory } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { schoolExternalToolFactory } from '@shared/testing/factory/domainobject/tool/school-external-tool.factory'; -import { ExternalToolService } from '../../external-tool/service'; -import { SchoolExternalToolService } from './school-external-tool.service'; +import { ToolConfigurationStatus } from '../../common/enum'; import { ExternalTool } from '../../external-tool/domain'; +import { ExternalToolService } from '../../external-tool/service'; import { SchoolExternalTool } from '../domain'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { SchoolExternalToolService } from './school-external-tool.service'; describe('SchoolExternalToolService', () => { let module: TestingModule; @@ -77,7 +77,7 @@ describe('SchoolExternalToolService', () => { await service.findSchoolExternalTools(schoolExternalTool); - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(schoolExternalTool.toolId); + expect(externalToolService.findById).toHaveBeenCalledWith(schoolExternalTool.toolId); }); describe('when determine status', () => { @@ -86,7 +86,7 @@ describe('SchoolExternalToolService', () => { const { schoolExternalTool, externalTool } = setup(); externalTool.version = 1337; schoolExternalToolRepo.find.mockResolvedValue([schoolExternalTool]); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); @@ -100,7 +100,7 @@ describe('SchoolExternalToolService', () => { schoolExternalTool.toolVersion = 1; externalTool.version = 0; schoolExternalToolRepo.find.mockResolvedValue([schoolExternalTool]); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); @@ -114,7 +114,7 @@ describe('SchoolExternalToolService', () => { schoolExternalTool.toolVersion = 1; externalTool.version = 1; schoolExternalToolRepo.find.mockResolvedValue([schoolExternalTool]); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + externalToolService.findById.mockResolvedValue(externalTool); const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); @@ -136,12 +136,12 @@ describe('SchoolExternalToolService', () => { }); }); - describe('getSchoolExternalToolById', () => { + describe('findById', () => { describe('when schoolExternalToolId is given', () => { it('should call schoolExternalToolRepo.findById', async () => { const { schoolExternalToolId } = setup(); - await service.getSchoolExternalToolById(schoolExternalToolId); + await service.findById(schoolExternalToolId); expect(schoolExternalToolRepo.findById).toHaveBeenCalledWith(schoolExternalToolId); }); @@ -163,7 +163,7 @@ describe('SchoolExternalToolService', () => { await service.saveSchoolExternalTool(schoolExternalTool); - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(schoolExternalTool.toolId); + expect(externalToolService.findById).toHaveBeenCalledWith(schoolExternalTool.toolId); }); }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts index 9ee30d70db6..2f011560f6a 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { SchoolExternalToolRepo } from '@shared/repo'; import { EntityId } from '@shared/domain'; -import { SchoolExternalToolQuery } from '../uc/dto/school-external-tool.types'; +import { SchoolExternalToolRepo } from '@shared/repo'; +import { ToolConfigurationStatus } from '../../common/enum'; +import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; import { SchoolExternalTool } from '../domain'; -import { ExternalTool } from '../../external-tool/domain'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { SchoolExternalToolQuery } from '../uc/dto/school-external-tool.types'; @Injectable() export class SchoolExternalToolService { @@ -14,7 +14,7 @@ export class SchoolExternalToolService { private readonly externalToolService: ExternalToolService ) {} - async getSchoolExternalToolById(schoolExternalToolId: EntityId): Promise { + async findById(schoolExternalToolId: EntityId): Promise { const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolRepo.findById(schoolExternalToolId); return schoolExternalTool; } @@ -38,7 +38,7 @@ export class SchoolExternalToolService { } private async enrichDataFromExternalTool(tool: SchoolExternalTool): Promise { - const externalTool: ExternalTool = await this.externalToolService.findExternalToolById(tool.toolId); + const externalTool: ExternalTool = await this.externalToolService.findById(tool.toolId); const status: ToolConfigurationStatus = this.determineStatus(tool, externalTool); const schoolExternalTool: SchoolExternalTool = new SchoolExternalTool({ ...tool, 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 c0daab13cff..85f26f83679 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 @@ -3,12 +3,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, Permission, User } from '@shared/domain'; import { schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; import { AuthorizationContextBuilder } from '@src/modules/authorization'; -import { SchoolExternalToolUc } from './school-external-tool.uc'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; import { SchoolExternalTool } from '../domain'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; +import { SchoolExternalToolUc } from './school-external-tool.uc'; describe('SchoolExternalToolUc', () => { let module: TestingModule; @@ -259,7 +259,7 @@ describe('SchoolExternalToolUc', () => { const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const user: User = userFactory.buildWithId(); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValue(tool); + schoolExternalToolService.findById.mockResolvedValue(tool); return { user, @@ -285,7 +285,7 @@ describe('SchoolExternalToolUc', () => { const setup = () => { const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const user: User = userFactory.buildWithId(); - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValue(tool); + schoolExternalToolService.findById.mockResolvedValue(tool); return { user, 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 63067c234d7..2640def12d5 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 @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalToolDto, SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; import { SchoolExternalTool } from '../domain'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { SchoolExternalToolDto, SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; @Injectable() export class SchoolExternalToolUc { @@ -57,9 +57,7 @@ export class SchoolExternalToolUc { } async deleteSchoolExternalTool(userId: EntityId, schoolExternalToolId: EntityId): Promise { - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( - schoolExternalToolId - ); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById(schoolExternalToolId); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); await this.toolPermissionHelper.ensureSchoolPermissions(userId, schoolExternalTool, context); @@ -71,9 +69,7 @@ export class SchoolExternalToolUc { } async getSchoolExternalTool(userId: EntityId, schoolExternalToolId: EntityId): Promise { - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( - schoolExternalToolId - ); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById(schoolExternalToolId); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); await this.toolPermissionHelper.ensureSchoolPermissions(userId, schoolExternalTool, context); diff --git a/apps/server/src/modules/tool/tool-api.module.ts b/apps/server/src/modules/tool/tool-api.module.ts index fe775e01fd3..dea3405801d 100644 --- a/apps/server/src/modules/tool/tool-api.module.ts +++ b/apps/server/src/modules/tool/tool-api.module.ts @@ -4,11 +4,14 @@ import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '@src/modules/authorization'; import { LegacySchoolModule } from '@src/modules/legacy-school'; import { UserModule } from '@src/modules/user'; +import { CommonToolModule } from './common'; import { ToolContextController } from './context-external-tool/controller'; -import { ContextExternalToolUc } from './context-external-tool/uc'; +import { ToolReferenceController } from './context-external-tool/controller/tool-reference.controller'; +import { ContextExternalToolUc, ToolReferenceUc } from './context-external-tool/uc'; import { ToolConfigurationController, ToolController } from './external-tool/controller'; import { ExternalToolRequestMapper, ExternalToolResponseMapper } from './external-tool/mapper'; -import { ExternalToolConfigurationUc, ExternalToolUc, ToolReferenceUc } from './external-tool/uc'; +import { ExternalToolConfigurationService } from './external-tool/service'; +import { ExternalToolConfigurationUc, ExternalToolUc } from './external-tool/uc'; import { ToolSchoolController } from './school-external-tool/controller'; import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from './school-external-tool/mapper'; import { SchoolExternalToolUc } from './school-external-tool/uc'; @@ -16,8 +19,6 @@ import { ToolConfigModule } from './tool-config.module'; import { ToolLaunchController } from './tool-launch/controller/tool-launch.controller'; import { ToolLaunchUc } from './tool-launch/uc'; import { ToolModule } from './tool.module'; -import { ExternalToolConfigurationService } from './external-tool/service'; -import { CommonToolModule } from './common'; @Module({ imports: [ @@ -34,6 +35,7 @@ import { CommonToolModule } from './common'; ToolConfigurationController, ToolSchoolController, ToolContextController, + ToolReferenceController, ToolController, ], providers: [ diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts index 7dba13fd2f5..12f8716b5b3 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts @@ -133,18 +133,6 @@ describe('AbstractLaunchStrategy', () => { name: 'autoSchoolIdParam', type: CustomParameterType.AUTO_SCHOOLID, }); - const autoCourseIdCustomParameter = customParameterFactory.build({ - scope: CustomParameterScope.GLOBAL, - location: CustomParameterLocation.BODY, - name: 'autoCourseIdParam', - type: CustomParameterType.AUTO_CONTEXTID, - }); - const autoCourseNameCustomParameter = customParameterFactory.build({ - scope: CustomParameterScope.GLOBAL, - location: CustomParameterLocation.BODY, - name: 'autoCourseNameParam', - type: CustomParameterType.AUTO_CONTEXTNAME, - }); const autoSchoolNumberCustomParameter = customParameterFactory.build({ scope: CustomParameterScope.GLOBAL, location: CustomParameterLocation.BODY, @@ -158,8 +146,6 @@ describe('AbstractLaunchStrategy', () => { schoolCustomParameter, contextCustomParameter, autoSchoolIdCustomParameter, - autoCourseIdCustomParameter, - autoCourseNameCustomParameter, autoSchoolNumberCustomParameter, ], }); @@ -191,15 +177,7 @@ describe('AbstractLaunchStrategy', () => { schoolId ); - const course: Course = courseFactory.buildWithId( - { - name: 'testName', - }, - contextExternalTool.contextRef.id - ); - schoolService.getSchoolById.mockResolvedValue(school); - courseService.findById.mockResolvedValue(course); const sortFn = (a: PropertyData, b: PropertyData) => { if (a.name < b.name) { @@ -215,15 +193,12 @@ describe('AbstractLaunchStrategy', () => { globalCustomParameter, schoolCustomParameter, autoSchoolIdCustomParameter, - autoCourseIdCustomParameter, - autoCourseNameCustomParameter, autoSchoolNumberCustomParameter, schoolParameterEntry, contextParameterEntry, externalTool, schoolExternalTool, contextExternalTool, - course, school, sortFn, }; @@ -235,14 +210,11 @@ describe('AbstractLaunchStrategy', () => { schoolCustomParameter, contextParameterEntry, autoSchoolIdCustomParameter, - autoCourseIdCustomParameter, - autoCourseNameCustomParameter, autoSchoolNumberCustomParameter, schoolParameterEntry, externalTool, schoolExternalTool, contextExternalTool, - course, school, sortFn, } = setup(); @@ -280,18 +252,131 @@ describe('AbstractLaunchStrategy', () => { location: PropertyLocation.BODY, }, { - name: autoCourseIdCustomParameter.name, - value: course.id, + name: autoSchoolNumberCustomParameter.name, + value: school.officialSchoolNumber as string, location: PropertyLocation.BODY, }, + { + name: concreteConfigParameter.name, + value: concreteConfigParameter.value, + location: concreteConfigParameter.location, + }, + ].sort(sortFn), + }); + }); + }); + + describe('when launching with context name parameter for the context "course"', () => { + const setup = () => { + const autoCourseNameCustomParameter = customParameterFactory.build({ + scope: CustomParameterScope.GLOBAL, + location: CustomParameterLocation.BODY, + name: 'autoCourseNameParam', + type: CustomParameterType.AUTO_CONTEXTNAME, + }); + + const externalTool: ExternalTool = externalToolFactory.build({ + parameters: [autoCourseNameCustomParameter], + }); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({ + contextRef: { + type: ToolContextType.COURSE, + }, + }); + + const course: Course = courseFactory.buildWithId( + { + name: 'testName', + }, + contextExternalTool.contextRef.id + ); + + courseService.findById.mockResolvedValue(course); + + return { + autoCourseNameCustomParameter, + externalTool, + schoolExternalTool, + contextExternalTool, + course, + }; + }; + + it('should return ToolLaunchData with the course name as parameter value', async () => { + const { externalTool, schoolExternalTool, contextExternalTool, autoCourseNameCustomParameter, course } = + setup(); + + const result: ToolLaunchData = await launchStrategy.createLaunchData('userId', { + externalTool, + schoolExternalTool, + contextExternalTool, + }); + + expect(result).toEqual({ + baseUrl: externalTool.config.baseUrl, + type: ToolLaunchDataType.BASIC, + openNewTab: false, + properties: [ { name: autoCourseNameCustomParameter.name, value: course.name, location: PropertyLocation.BODY, }, { - name: autoSchoolNumberCustomParameter.name, - value: school.officialSchoolNumber as string, + name: concreteConfigParameter.name, + value: concreteConfigParameter.value, + location: concreteConfigParameter.location, + }, + ], + }); + }); + }); + + describe('when launching with context id parameter', () => { + const setup = () => { + const autoContextIdCustomParameter = customParameterFactory.build({ + scope: CustomParameterScope.GLOBAL, + location: CustomParameterLocation.BODY, + name: 'autoContextIdParam', + type: CustomParameterType.AUTO_CONTEXTID, + }); + + const externalTool: ExternalTool = externalToolFactory.build({ + parameters: [autoContextIdCustomParameter], + }); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); + + return { + autoContextIdCustomParameter, + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return ToolLaunchData with the context id as parameter value', async () => { + const { externalTool, schoolExternalTool, contextExternalTool, autoContextIdCustomParameter } = setup(); + + const result: ToolLaunchData = await launchStrategy.createLaunchData('userId', { + externalTool, + schoolExternalTool, + contextExternalTool, + }); + + expect(result).toEqual({ + baseUrl: externalTool.config.baseUrl, + type: ToolLaunchDataType.BASIC, + openNewTab: false, + properties: [ + { + name: autoContextIdCustomParameter.name, + value: contextExternalTool.contextRef.id, location: PropertyLocation.BODY, }, { @@ -299,7 +384,7 @@ describe('AbstractLaunchStrategy', () => { value: concreteConfigParameter.value, location: concreteConfigParameter.location, }, - ].sort(sortFn), + ], }); }); }); diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts index 9004e461ae2..644105a3c2b 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts @@ -215,15 +215,18 @@ export abstract class AbstractLaunchStrategy implements IToolLaunchStrategy { return contextExternalTool.contextRef.id; } case CustomParameterType.AUTO_CONTEXTNAME: { - if (contextExternalTool.contextRef.type === ToolContextType.COURSE) { - const course: Course = await this.courseService.findById(contextExternalTool.contextRef.id); - - return course.name; + switch (contextExternalTool.contextRef.type) { + case ToolContextType.COURSE: { + const course: Course = await this.courseService.findById(contextExternalTool.contextRef.id); + + return course.name; + } + default: { + throw new ParameterTypeNotImplementedLoggableException( + `${customParameter.type}/${contextExternalTool.contextRef.type as string}` + ); + } } - - throw new ParameterTypeNotImplementedLoggableException( - `${customParameter.type}/${contextExternalTool.contextRef.type as string}` - ); } case CustomParameterType.AUTO_SCHOOLNUMBER: { const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolExternalTool.schoolId); diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts index a0db37651be..1c31aac36b6 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts @@ -9,12 +9,12 @@ import { userDoFactory, } from '@shared/testing'; import { pseudonymFactory } from '@shared/testing/factory/domainobject/pseudonym.factory'; -import { PseudonymService } from '@src/modules/pseudonym/service'; +import { CourseService } from '@src/modules/learnroom/service'; import { LegacySchoolService } from '@src/modules/legacy-school'; +import { PseudonymService } from '@src/modules/pseudonym/service'; import { UserService } from '@src/modules/user'; import { ObjectId } from 'bson'; import { Authorization } from 'oauth-1.0a'; -import { CourseService } from '@src/modules/learnroom/service'; import { LtiMessageType, LtiPrivacyPermission, LtiRole, ToolContextType } from '../../../common/enum'; import { ContextExternalTool } from '../../../context-external-tool/domain'; import { ExternalTool } from '../../../external-tool/domain'; diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts index 8c957ca9421..654e14b45d9 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts @@ -9,7 +9,7 @@ import { Authorization } from 'oauth-1.0a'; import { LtiRole } from '../../../common/enum'; import { ExternalTool } from '../../../external-tool/domain'; import { LtiRoleMapper } from '../../mapper'; -import { LaunchRequestMethod, PropertyData, PropertyLocation, AuthenticationValues } from '../../types'; +import { AuthenticationValues, LaunchRequestMethod, PropertyData, PropertyLocation } from '../../types'; import { Lti11EncryptionService } from '../lti11-encryption.service'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; import { IToolLaunchParams } from './tool-launch-params.interface'; diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts index 02bb484093f..3330b0c9f0e 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts @@ -7,7 +7,14 @@ import { externalToolFactory, schoolExternalToolFactory, } from '@shared/testing'; +import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; +import { CommonToolService } from '../../common/service'; import { ContextExternalTool } from '../../context-external-tool/domain'; +import { BasicToolConfig, ExternalTool } from '../../external-tool/domain'; +import { ExternalToolService } from '../../external-tool/service'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; +import { ToolStatusOutdatedLoggableException } from '../error'; import { LaunchRequestMethod, ToolLaunchData, ToolLaunchDataType, ToolLaunchRequest } from '../types'; import { BasicToolLaunchStrategy, @@ -16,13 +23,6 @@ import { OAuth2ToolLaunchStrategy, } from './strategy'; import { ToolLaunchService } from './tool-launch.service'; -import { ToolStatusOutdatedLoggableException } from '../error'; -import { SchoolExternalToolService } from '../../school-external-tool/service'; -import { ExternalToolService } from '../../external-tool/service'; -import { CommonToolService } from '../../common/service'; -import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { BasicToolConfig, ExternalTool } from '../../external-tool/domain'; -import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; describe('ToolLaunchService', () => { let module: TestingModule; @@ -104,8 +104,8 @@ describe('ToolLaunchService', () => { contextExternalTool, }; - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValue(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); + externalToolService.findById.mockResolvedValue(externalTool); basicToolLaunchStrategy.createLaunchData.mockResolvedValue(launchDataDO); commonToolService.determineToolConfigurationStatus.mockReturnValueOnce(ToolConfigurationStatus.LATEST); @@ -136,9 +136,7 @@ describe('ToolLaunchService', () => { await service.getLaunchData('userId', launchParams.contextExternalTool); - expect(schoolExternalToolService.getSchoolExternalToolById).toHaveBeenCalledWith( - launchParams.schoolExternalTool.id - ); + expect(schoolExternalToolService.findById).toHaveBeenCalledWith(launchParams.schoolExternalTool.id); }); it('should call findExternalToolById', async () => { @@ -146,7 +144,7 @@ describe('ToolLaunchService', () => { await service.getLaunchData('userId', launchParams.contextExternalTool); - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(launchParams.schoolExternalTool.toolId); + expect(externalToolService.findById).toHaveBeenCalledWith(launchParams.schoolExternalTool.toolId); }); }); @@ -165,8 +163,8 @@ describe('ToolLaunchService', () => { contextExternalTool, }; - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValue(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); + externalToolService.findById.mockResolvedValue(externalTool); commonToolService.determineToolConfigurationStatus.mockReturnValueOnce(ToolConfigurationStatus.LATEST); return { @@ -209,8 +207,8 @@ describe('ToolLaunchService', () => { const userId = 'userId'; - schoolExternalToolService.getSchoolExternalToolById.mockResolvedValue(schoolExternalTool); - externalToolService.findExternalToolById.mockResolvedValue(externalTool); + schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); + externalToolService.findById.mockResolvedValue(externalTool); basicToolLaunchStrategy.createLaunchData.mockResolvedValue(launchDataDO); commonToolService.determineToolConfigurationStatus.mockReturnValueOnce(ToolConfigurationStatus.OUTDATED); diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts index 3321e782f09..46d2efdeb70 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts @@ -1,6 +1,13 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; +import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; import { CommonToolService } from '../../common/service'; +import { ContextExternalTool } from '../../context-external-tool/domain'; +import { ExternalTool } from '../../external-tool/domain'; +import { ExternalToolService } from '../../external-tool/service'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; +import { ToolStatusOutdatedLoggableException } from '../error'; import { ToolLaunchMapper } from '../mapper'; import { ToolLaunchData, ToolLaunchRequest } from '../types'; import { @@ -9,13 +16,6 @@ import { Lti11ToolLaunchStrategy, OAuth2ToolLaunchStrategy, } from './strategy'; -import { ToolStatusOutdatedLoggableException } from '../error'; -import { SchoolExternalToolService } from '../../school-external-tool/service'; -import { ExternalToolService } from '../../external-tool/service'; -import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ExternalTool } from '../../external-tool/domain'; -import { SchoolExternalTool } from '../../school-external-tool/domain'; @Injectable() export class ToolLaunchService { @@ -73,11 +73,9 @@ export class ToolLaunchService { private async loadToolHierarchy( schoolExternalToolId: string ): Promise<{ schoolExternalTool: SchoolExternalTool; externalTool: ExternalTool }> { - const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.getSchoolExternalToolById( - schoolExternalToolId - ); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById(schoolExternalToolId); - const externalTool: ExternalTool = await this.externalToolService.findExternalToolById(schoolExternalTool.toolId); + const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); return { schoolExternalTool, diff --git a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts index 62424d8b8aa..e9b7311e06a 100644 --- a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts @@ -2,12 +2,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { contextExternalToolFactory } from '@shared/testing'; import { ObjectId } from 'bson'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { ContextExternalTool } from '../../context-external-tool/domain'; +import { ContextExternalToolService } from '../../context-external-tool/service'; import { ToolLaunchService } from '../service'; import { ToolLaunchData, ToolLaunchDataType, ToolLaunchRequest } from '../types'; import { ToolLaunchUc } from './tool-launch.uc'; -import { ContextExternalToolService } from '../../context-external-tool/service'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; describe('ToolLaunchUc', () => { let module: TestingModule; @@ -66,7 +66,7 @@ describe('ToolLaunchUc', () => { const userId: string = new ObjectId().toHexString(); toolPermissionHelper.ensureContextPermissions.mockResolvedValueOnce(); - contextExternalToolService.getContextExternalToolById.mockResolvedValueOnce(contextExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); toolLaunchService.getLaunchData.mockResolvedValueOnce(toolLaunchData); return { @@ -82,7 +82,7 @@ describe('ToolLaunchUc', () => { await uc.getToolLaunchRequest(userId, contextExternalToolId); - expect(contextExternalToolService.getContextExternalToolById).toHaveBeenCalledWith(contextExternalToolId); + expect(contextExternalToolService.findById).toHaveBeenCalledWith(contextExternalToolId); }); it('should call service to get data', async () => { diff --git a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts index c397ae1d1af..fed27fa9aad 100644 --- a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts +++ b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { ContextExternalTool } from '../../context-external-tool/domain'; +import { ContextExternalToolService } from '../../context-external-tool/service'; import { ToolLaunchService } from '../service'; import { ToolLaunchData, ToolLaunchRequest } from '../types'; -import { ContextExternalToolService } from '../../context-external-tool/service'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; @Injectable() export class ToolLaunchUc { @@ -16,7 +16,7 @@ export class ToolLaunchUc { ) {} async getToolLaunchRequest(userId: EntityId, contextExternalToolId: EntityId): Promise { - const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.getContextExternalToolById( + const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( contextExternalToolId ); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); 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 7c02cbc8a75..6806aeb3f71 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 @@ -127,7 +127,7 @@ describe('ContextExternalToolRepo', () => { }); describe('save', () => { - describe('when context is known', () => { + describe('when context is course', () => { function setup() { const domainObject: ContextExternalTool = contextExternalToolFactory.build({ displayName: 'displayName', @@ -159,6 +159,38 @@ describe('ContextExternalToolRepo', () => { }); }); + describe('when context is board card', () => { + function setup() { + const domainObject: ContextExternalTool = contextExternalToolFactory.build({ + displayName: 'displayName', + contextRef: { + id: new ObjectId().toHexString(), + type: ToolContextType.BOARD_ELEMENT, + }, + parameters: [new CustomParameterEntry({ name: 'param', value: 'value' })], + schoolToolRef: { + schoolToolId: new ObjectId().toHexString(), + schoolId: undefined, + }, + toolVersion: 1, + }); + + return { + domainObject, + }; + } + + it('should save a ContextExternalToolDO', async () => { + const { domainObject } = setup(); + const { id, ...expected } = domainObject; + + const result: ContextExternalTool = await repo.save(domainObject); + + expect(result).toMatchObject(expected); + expect(result.id).toBeDefined(); + }); + }); + describe('when context is unknown', () => { const setup = () => { const domainObject: ContextExternalTool = contextExternalToolFactory.build({ 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 b766828beff..084adb4b727 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 @@ -115,6 +115,8 @@ export class ContextExternalToolRepo extends BaseDORepo< switch (type) { case ToolContextType.COURSE: return ContextExternalToolType.COURSE; + case ToolContextType.BOARD_ELEMENT: + return ContextExternalToolType.BOARD_ELEMENT; default: throw new Error('Unknown ToolContextType'); } @@ -124,6 +126,8 @@ export class ContextExternalToolRepo extends BaseDORepo< switch (type) { case ContextExternalToolType.COURSE: return ToolContextType.COURSE; + case ContextExternalToolType.BOARD_ELEMENT: + return ToolContextType.BOARD_ELEMENT; default: throw new Error('Unknown ContextExternalToolType'); } From fa3a4f7d671acb15f3c90c075b2a97b1e762a358 Mon Sep 17 00:00:00 2001 From: virgilchiriac <17074330+virgilchiriac@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:10:55 +0200 Subject: [PATCH 02/19] BC-5566 - fix merlin feathers service (#4480) Rename the merlinToken feathers service path to a unique path instead of subpath of edu-shraring Not clear why, but feathers service was suddenly mapped wrong and calling edu-sharing service instead of merlinToken service. Changing the order of the services load helped, but then lots of tests fail. --- src/services/edusharing/index.js | 4 +- src/services/lesson/hooks/index.js | 90 ++++++++++--------- .../services/merlinGenerator.test.js | 2 +- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/services/edusharing/index.js b/src/services/edusharing/index.js index d002686ebea..27a45244494 100644 --- a/src/services/edusharing/index.js +++ b/src/services/edusharing/index.js @@ -35,7 +35,7 @@ class EduSharingPlayer { throw new MethodNotAllowed('This feature is disabled on this instance'); } const esPlayer = EduSharingConnectorV7.getPlayerForNode(uuid); - + return esPlayer; } } @@ -49,7 +49,7 @@ class MerlinToken { module.exports = (app) => { const eduSharingRoute = '/edu-sharing'; const eduSharingPlayerRoute = '/edu-sharing/player'; - const merlinRoute = '/edu-sharing/merlinToken'; + const merlinRoute = '/edu-sharing-merlinToken'; const docRoute = '/edu-sharing/api'; app.use(eduSharingRoute, new EduSharing()); diff --git a/src/services/lesson/hooks/index.js b/src/services/lesson/hooks/index.js index b7b0b0ea5e7..458b64785ab 100644 --- a/src/services/lesson/hooks/index.js +++ b/src/services/lesson/hooks/index.js @@ -60,7 +60,7 @@ const convertMerlinUrl = async (context) => { await Promise.all( content.content.resources.map(async (resource) => { if (resource && resource.merlinReference) { - resource.url = await context.app.service('edu-sharing/merlinToken').find({ + resource.url = await context.app.service('edu-sharing-merlinToken').find({ ...context.params, query: { ...context.params.query, merlinReference: resource.merlinReference }, }); @@ -82,7 +82,7 @@ const convertMerlinUrl = async (context) => { await Promise.all( materialIds.map(async (material) => { if (material.merlinReference) { - material.url = await context.app.service('edu-sharing/merlinToken').find({ + material.url = await context.app.service('edu-sharing-merlinToken').find({ ...context.params, query: { ...context.params.query, @@ -229,48 +229,50 @@ const populateWhitelist = { ], }; -exports.before = () => ({ - all: [authenticate('jwt'), mapUsers], - find: [ - hasPermission('TOPIC_VIEW'), - iff(isProvider('external'), validateLessonFind), - iff(isProvider('external'), getRestrictPopulatesHook(populateWhitelist)), - iff(isProvider('external'), restrictToUsersCoursesLessons), - ], - get: [ - hasPermission('TOPIC_VIEW'), - iff(isProvider('external'), getRestrictPopulatesHook(populateWhitelist)), - iff(isProvider('external'), restrictToUsersCoursesLessons), - ], - create: [ - checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_CREATE', 'TOPIC_CREATE', true), - injectUserId, - checkCorrectCourseOrTeamId, - setPosition, - iff(isProvider('external'), preventPopulate), - ], - update: [ - iff(isProvider('external'), preventPopulate), - permitGroupOperation, - ifNotLocal(checkCorrectCourseOrTeamId), - iff(isProvider('external'), restrictToUsersCoursesLessons), - checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_EDIT', 'TOPIC_EDIT', false), - ], - patch: [ - attachMerlinReferenceToLesson, - checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_EDIT', 'TOPIC_EDIT', false), - permitGroupOperation, - ifNotLocal(checkCorrectCourseOrTeamId), - iff(isProvider('external'), restrictToUsersCoursesLessons), - iff(isProvider('external'), preventPopulate), - ], - remove: [ - checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_CREATE', 'TOPIC_CREATE', false), - permitGroupOperation, - iff(isProvider('external'), restrictToUsersCoursesLessons), - iff(isProvider('external'), preventPopulate), - ], -}); +exports.before = () => { + return { + all: [authenticate('jwt'), mapUsers], + find: [ + hasPermission('TOPIC_VIEW'), + iff(isProvider('external'), validateLessonFind), + iff(isProvider('external'), getRestrictPopulatesHook(populateWhitelist)), + iff(isProvider('external'), restrictToUsersCoursesLessons), + ], + get: [ + hasPermission('TOPIC_VIEW'), + iff(isProvider('external'), getRestrictPopulatesHook(populateWhitelist)), + iff(isProvider('external'), restrictToUsersCoursesLessons), + ], + create: [ + checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_CREATE', 'TOPIC_CREATE', true), + injectUserId, + checkCorrectCourseOrTeamId, + setPosition, + iff(isProvider('external'), preventPopulate), + ], + update: [ + iff(isProvider('external'), preventPopulate), + permitGroupOperation, + ifNotLocal(checkCorrectCourseOrTeamId), + iff(isProvider('external'), restrictToUsersCoursesLessons), + checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_EDIT', 'TOPIC_EDIT', false), + ], + patch: [ + attachMerlinReferenceToLesson, + checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_EDIT', 'TOPIC_EDIT', false), + permitGroupOperation, + ifNotLocal(checkCorrectCourseOrTeamId), + iff(isProvider('external'), restrictToUsersCoursesLessons), + iff(isProvider('external'), preventPopulate), + ], + remove: [ + checkIfCourseGroupLesson.bind(this, 'COURSEGROUP_CREATE', 'TOPIC_CREATE', false), + permitGroupOperation, + iff(isProvider('external'), restrictToUsersCoursesLessons), + iff(isProvider('external'), preventPopulate), + ], + }; +}; exports.after = { all: [], diff --git a/test/services/edusharing/services/merlinGenerator.test.js b/test/services/edusharing/services/merlinGenerator.test.js index 43d5fbde13e..00e83f80461 100644 --- a/test/services/edusharing/services/merlinGenerator.test.js +++ b/test/services/edusharing/services/merlinGenerator.test.js @@ -17,7 +17,7 @@ describe('Merlin Token Generator', () => { before(async () => { app = await appPromise(); - MerlinTokenGeneratorService = app.service('edu-sharing/merlinToken'); + MerlinTokenGeneratorService = app.service('edu-sharing-merlinToken'); server = await app.listen(0); nestServices = await setupNestServices(app); }); From 4e8cfa0df589dd97e8aa7de42df840be46eca040 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:33:56 +0200 Subject: [PATCH 03/19] BC-4942 - authorization reference service (#4413) * Split reference authorisation service from authorisation service * Replace on some places the authorisation methodes * Move rules into authorisation module to fix dependency issues * Fix invalid tests * Fix invalid imports --- .../src/modules/authorization/README.md | 101 ++--- .../authorization-reference.module.ts | 43 ++ .../authorization/authorization.module.ts | 69 ++-- .../authorization/authorization.service.ts | 100 ----- .../error}/forbidden.loggable-exception.ts | 2 +- .../authorization/domain/error/index.ts | 1 + .../src/modules/authorization/domain/index.ts | 4 + .../authorization-context.builder.spec.ts | 2 +- .../mapper}/authorization-context.builder.ts | 2 +- .../authorization/domain/mapper/index.ts | 1 + .../domain/rules/board-do.rule.spec.ts | 8 +- .../domain/rules/board-do.rule.ts | 8 +- .../rules/context-external-tool.rule.spec.ts | 9 +- .../rules/context-external-tool.rule.ts | 6 +- .../domain/rules/course-group.rule.spec.ts | 4 +- .../domain/rules/course-group.rule.ts | 4 +- .../domain/rules/course.rule.spec.ts | 4 +- .../domain/rules/course.rule.ts | 4 +- .../authorization/domain/rules/index.ts | 16 + .../domain/rules/legacy-school.rule.spec.ts | 4 +- .../domain/rules/legacy-school.rule.ts | 6 +- .../domain/rules/lesson.rule.spec.ts | 108 +++-- .../domain/rules/lesson.rule.ts | 12 +- .../rules/school-external-tool.rule.spec.ts | 8 +- .../domain/rules/school-external-tool.rule.ts | 6 +- .../domain/rules/submission.rule.spec.ts | 43 +- .../domain/rules/submission.rule.ts | 8 +- .../domain/rules/task.rule.spec.ts | 9 +- .../authorization}/domain/rules/task.rule.ts | 4 +- .../domain/rules/team.rule.spec.ts | 9 +- .../authorization}/domain/rules/team.rule.ts | 4 +- .../rules/user-login-migration.rule.spec.ts | 8 +- .../domain/rules/user-login-migration.rule.ts | 8 +- .../domain/rules/user.rule.spec.ts | 4 +- .../authorization}/domain/rules/user.rule.ts | 4 +- .../authorization-reference.service.spec.ts | 183 +++++++++ .../authorization-reference.service.ts | 41 ++ .../service}/authorization.helper.spec.ts | 0 .../service}/authorization.helper.ts | 0 .../service}/authorization.service.spec.ts | 186 ++------- .../domain/service/authorization.service.ts | 59 +++ .../authorization/domain/service/index.ts | 5 + .../service}/reference.loader.spec.ts | 35 +- .../{ => domain/service}/reference.loader.ts | 15 +- .../{ => domain/service}/rule-manager.spec.ts | 8 +- .../{ => domain/service}/rule-manager.ts | 10 +- .../{types => domain/type}/action.enum.ts | 0 .../allowed-authorization-object-type.enum.ts | 0 .../type}/authorization-context.interface.ts | 0 .../type}/authorization-loader-service.ts | 0 .../{types => domain/type}/index.ts | 2 +- .../{types => domain/type}/rule.interface.ts | 0 .../server/src/modules/authorization/index.ts | 20 +- apps/server/src/modules/board/uc/board.uc.ts | 4 +- apps/server/src/modules/board/uc/card.uc.ts | 3 +- .../server/src/modules/board/uc/element.uc.ts | 3 +- .../board/uc/submission-item.uc.spec.ts | 3 +- .../modules/board/uc/submission-item.uc.ts | 3 +- .../src/modules/files-storage/README.md | 2 +- .../files-storage/files-storage-api.module.ts | 4 +- .../mapper/files-storage.mapper.spec.ts | 2 +- .../mapper/files-storage.mapper.ts | 2 +- .../uc/files-storage-copy.uc.spec.ts | 43 +- .../uc/files-storage-delete.uc.spec.ts | 26 +- .../files-storage-download-preview.uc.spec.ts | 14 +- .../uc/files-storage-download.uc.spec.ts | 18 +- .../uc/files-storage-get.uc.spec.ts | 18 +- .../uc/files-storage-restore.uc.spec.ts | 24 +- .../uc/files-storage-update.uc.spec.ts | 14 +- .../uc/files-storage-upload.uc.spec.ts | 19 +- .../files-storage/uc/files-storage.uc.ts | 7 +- .../api-test/rooms-copy-timeout.api.spec.ts | 11 +- apps/server/src/modules/learnroom/index.ts | 1 + .../modules/learnroom/learnroom-api.module.ts | 3 +- .../learnroom/uc/course-copy.uc.spec.ts | 150 +++---- .../modules/learnroom/uc/course-copy.uc.ts | 12 +- .../learnroom/uc/course-export.uc.spec.ts | 83 +++- .../modules/learnroom/uc/course-export.uc.ts | 17 +- .../learnroom/uc/lesson-copy.uc.spec.ts | 388 +++++++++++------- .../modules/learnroom/uc/lesson-copy.uc.ts | 54 +-- .../learnroom/uc/room-board-dto.factory.ts | 3 +- .../legacy-school/uc/legacy-school.uc.spec.ts | 17 +- .../legacy-school/uc/legacy-school.uc.ts | 33 +- .../uc/oauth-provider.client-crud.uc.ts | 2 +- .../api-test/sharing-create-token.api.spec.ts | 32 +- .../api-test/sharing-import-token.api.spec.ts | 55 ++- .../api-test/sharing-lookup-token.api.spec.ts | 255 +++++++----- .../mapper/context-type.mapper.spec.ts | 2 +- .../sharing/mapper/context-type.mapper.ts | 3 +- .../sharing/mapper/parent-type.mapper.spec.ts | 2 +- .../sharing/mapper/parent-type.mapper.ts | 2 +- .../src/modules/sharing/sharing.module.ts | 13 +- .../modules/sharing/uc/share-token.uc.spec.ts | 44 +- .../src/modules/sharing/uc/share-token.uc.ts | 31 +- .../src/modules/task/uc/task-copy.uc.spec.ts | 157 +++---- .../src/modules/task/uc/task-copy.uc.ts | 85 ++-- .../modules/tool/common/common-tool.module.ts | 3 +- .../common/mapper/context-type.mapper.spec.ts | 11 + .../tool/common/mapper/context-type.mapper.ts | 2 +- .../tool/common/uc/tool-permission-helper.ts | 45 +- .../common/uc/tool-permissions-helper.spec.ts | 119 +++++- .../api-test/tool-context.api.spec.ts | 117 +++--- .../controller/tool-context.controller.ts | 1 + .../uc/context-external-tool.uc.spec.ts | 8 +- .../uc/context-external-tool.uc.ts | 14 +- .../api-test/tool-configuration.api.spec.ts | 84 ++-- .../uc/external-tool-configuration.uc.ts | 2 +- .../tool/tool-launch/tool-launch.module.ts | 4 +- .../mapper/video-conference.mapper.ts | 8 +- .../service/video-conference.service.spec.ts | 166 ++++++-- .../service/video-conference.service.ts | 91 ++-- .../uc/video-conference-create.uc.ts | 6 + .../uc/video-conference-deprecated.uc.spec.ts | 13 +- .../uc/video-conference-deprecated.uc.ts | 14 +- .../uc/video-conference-end.uc.ts | 6 + .../uc/video-conference-info.uc.ts | 6 + .../video-conference.module.ts | 2 + apps/server/src/shared/domain/rules/index.ts | 39 -- .../src/shared/infra/antivirus/index.ts | 6 +- .../src/shared/testing/test-api-client.ts | 4 + 120 files changed, 2162 insertions(+), 1470 deletions(-) create mode 100644 apps/server/src/modules/authorization/authorization-reference.module.ts delete mode 100644 apps/server/src/modules/authorization/authorization.service.ts rename apps/server/src/modules/authorization/{errors => domain/error}/forbidden.loggable-exception.ts (94%) create mode 100644 apps/server/src/modules/authorization/domain/error/index.ts create mode 100644 apps/server/src/modules/authorization/domain/index.ts rename apps/server/src/modules/authorization/{ => domain/mapper}/authorization-context.builder.spec.ts (96%) rename apps/server/src/modules/authorization/{ => domain/mapper}/authorization-context.builder.ts (91%) create mode 100644 apps/server/src/modules/authorization/domain/mapper/index.ts rename apps/server/src/{shared => modules/authorization}/domain/rules/board-do.rule.spec.ts (95%) rename apps/server/src/{shared => modules/authorization}/domain/rules/board-do.rule.ts (81%) rename apps/server/src/{shared => modules/authorization}/domain/rules/context-external-tool.rule.spec.ts (93%) rename apps/server/src/{shared => modules/authorization}/domain/rules/context-external-tool.rule.ts (85%) rename apps/server/src/{shared => modules/authorization}/domain/rules/course-group.rule.spec.ts (97%) rename apps/server/src/{shared => modules/authorization}/domain/rules/course-group.rule.ts (84%) rename apps/server/src/{shared => modules/authorization}/domain/rules/course.rule.spec.ts (95%) rename apps/server/src/{shared => modules/authorization}/domain/rules/course.rule.ts (82%) create mode 100644 apps/server/src/modules/authorization/domain/rules/index.ts rename apps/server/src/{shared => modules/authorization}/domain/rules/legacy-school.rule.spec.ts (93%) rename apps/server/src/{shared => modules/authorization}/domain/rules/legacy-school.rule.ts (78%) rename apps/server/src/{shared => modules/authorization}/domain/rules/lesson.rule.spec.ts (50%) rename apps/server/src/{shared => modules/authorization}/domain/rules/lesson.rule.ts (88%) rename apps/server/src/{shared => modules/authorization}/domain/rules/school-external-tool.rule.spec.ts (92%) rename apps/server/src/{shared => modules/authorization}/domain/rules/school-external-tool.rule.ts (85%) rename apps/server/src/{shared => modules/authorization}/domain/rules/submission.rule.spec.ts (93%) rename apps/server/src/{shared => modules/authorization}/domain/rules/submission.rule.ts (88%) rename apps/server/src/{shared => modules/authorization}/domain/rules/task.rule.spec.ts (96%) rename apps/server/src/{shared => modules/authorization}/domain/rules/task.rule.ts (90%) rename apps/server/src/{shared => modules/authorization}/domain/rules/team.rule.spec.ts (91%) rename apps/server/src/{shared => modules/authorization}/domain/rules/team.rule.ts (81%) rename apps/server/src/{shared => modules/authorization}/domain/rules/user-login-migration.rule.spec.ts (94%) rename apps/server/src/{shared => modules/authorization}/domain/rules/user-login-migration.rule.ts (72%) rename apps/server/src/{shared => modules/authorization}/domain/rules/user.rule.spec.ts (94%) rename apps/server/src/{shared => modules/authorization}/domain/rules/user.rule.ts (78%) create mode 100644 apps/server/src/modules/authorization/domain/service/authorization-reference.service.spec.ts create mode 100644 apps/server/src/modules/authorization/domain/service/authorization-reference.service.ts rename apps/server/src/modules/authorization/{ => domain/service}/authorization.helper.spec.ts (100%) rename apps/server/src/modules/authorization/{ => domain/service}/authorization.helper.ts (100%) rename apps/server/src/modules/authorization/{ => domain/service}/authorization.service.spec.ts (60%) create mode 100644 apps/server/src/modules/authorization/domain/service/authorization.service.ts create mode 100644 apps/server/src/modules/authorization/domain/service/index.ts rename apps/server/src/modules/authorization/{ => domain/service}/reference.loader.spec.ts (87%) rename apps/server/src/modules/authorization/{ => domain/service}/reference.loader.ts (88%) rename apps/server/src/modules/authorization/{ => domain/service}/rule-manager.spec.ts (97%) rename apps/server/src/modules/authorization/{ => domain/service}/rule-manager.ts (88%) rename apps/server/src/modules/authorization/{types => domain/type}/action.enum.ts (100%) rename apps/server/src/modules/authorization/{types => domain/type}/allowed-authorization-object-type.enum.ts (100%) rename apps/server/src/modules/authorization/{types => domain/type}/authorization-context.interface.ts (100%) rename apps/server/src/modules/authorization/{types => domain/type}/authorization-loader-service.ts (100%) rename apps/server/src/modules/authorization/{types => domain/type}/index.ts (100%) rename apps/server/src/modules/authorization/{types => domain/type}/rule.interface.ts (100%) create mode 100644 apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts delete mode 100644 apps/server/src/shared/domain/rules/index.ts diff --git a/apps/server/src/modules/authorization/README.md b/apps/server/src/modules/authorization/README.md index 645feed2e64..7d2b69d209b 100644 --- a/apps/server/src/modules/authorization/README.md +++ b/apps/server/src/modules/authorization/README.md @@ -132,17 +132,7 @@ When calling other internal micro service for already authorized operations plea // next orchestration steps ``` -### Example 2 - Execute a Single Operation with Loading Resources - -```javascript -// If you don't have an entity but an entity type and id, you can check permission by reference -await this.authorizationService.checkPermissionByReferences(userId, AllowedEntity.course, courseId, AuthorizationContextBuilder.read([])); -// or -await this.authorizationService.hasPermissionByReferences(userId, AllowedEntity.course, courseId, AuthorizationContextBuilder.read([])); -// next orchestration steps -``` - -### Example 3 - Set Permission(s) of User as Required +### Example 2 - Set Permission(s) of User as Required ```javascript // Multiple permissions can be added. For a successful authorization, the user need all of them. @@ -173,14 +163,13 @@ this.authorizationService.hasPermission(userId, course, PermissionContexts.creat ```ts async createSchoolBySuperhero(userId: EntityId, params: { name: string }) { - const user = this.authorizationService.getUserWithPermissions(userId); - this.authorizationService.hasAllPermissions(user, [Permission.SCHOOL_CREATE]); - - const school = new School(params); + const user = this.authorizationService.getUserWithPermissions(userId); + this.authorizationService.hasAllPermissions(user, [Permission.SCHOOL_CREATE]); - await this.schoolService.save(school); + const school = new School(params); + await this.schoolService.save(school); - return true; + return true; } ``` @@ -191,15 +180,15 @@ async createSchoolBySuperhero(userId: EntityId, params: { name: string }) { async createUserByAdmin(userId: EntityId, params: { email: string, firstName: string, lastName: string, schoolId: EntityId }) { - const user = this.authorizationService.getUserWithPermissions(userId); - - await this.authorizationService.checkPermissionByReferences(userId, AllowedEntity.school, schoolId, AuthorizationContextBuilder.write([Permission.INSTANCE, Permission.CREATE_USER])); - - const newUser = new User(params) + const user = this.authorizationService.getUserWithPermissions(userId); + + const context = AuthorizationContextBuilder.write([Permission.INSTANCE, Permission.CREATE_USER]) + await this.authorizationService.checkPermission(user, school, context); - await this.userService.save(newUser); + const newUser = new User(params) + await this.userService.save(newUser); - return true; + return true; } ``` @@ -210,18 +199,17 @@ async createUserByAdmin(userId: EntityId, params: { email: string, firstName: st // admin async editCourseByAdmin(userId: EntityId, params: { courseId: EntityId, description: string }) { - const course = this.courseService.getCourse(params.courseId); - const user = this.authorizationService.getUserWithPermissions(userId); - - const school = course.school - - this.authorizationService.hasPermissions(user, school, [Permission.INSTANCE, Permission.COURSE_EDIT]); + const course = this.courseService.getCourse(params.courseId); + const user = this.authorizationService.getUserWithPermissions(userId); + const school = course.school; - course.description = params.description; + const context = AuthorizationContextBuilder.write([Permission.INSTANCE, Permission.CREATE_USER]); + this.authorizationService.checkPermissions(user, school, context); - await this.courseService.save(course); + course.description = params.description; + await this.courseService.save(course); - return true; + return true; } ``` @@ -234,18 +222,17 @@ async createCourse(userId: EntityId, params: { schoolId: EntityId }) { const user = this.authorizationService.getUserWithPermissions(userId); const school = this.schoolService.getSchool(params.schoolId); - this.authorizationService.checkPermission(user, school - { - action: Actions.write, - requiredPermissions: [Permission.COURSE_CREATE], - } - ); + this.authorizationService.checkPermission(user, school + { + action: Actions.write, + requiredPermissions: [Permission.COURSE_CREATE], + } + ); - const course = new Course({ school }); + const course = new Course({ school }); + await this.courseService.saveCourse(course); - await this.courseService.saveCourse(course); - - return course; + return course; } ``` @@ -255,21 +242,20 @@ async createCourse(userId: EntityId, params: { schoolId: EntityId }) { ```ts // User can create a lesson to course, so you have a courseId async createLesson(userId: EntityId, params: { courseId: EntityId }) { - const course = this.courseService.getCourse(params.courseId); - const user = this.authorizationService.getUserWithPermissions(userId); + const course = this.courseService.getCourse(params.courseId); + const user = this.authorizationService.getUserWithPermissions(userId); // check authorization for user and course - this.authorizationService.checkPermission(user, course - { - action: Actions.write, - requiredPermissions: [Permission.COURSE_EDIT], - } - ); - - const lesson = new Lesson({course}); + this.authorizationService.checkPermission(user, course + { + action: Actions.write, + requiredPermissions: [Permission.COURSE_EDIT], + } + ); - await this.lessonService.saveLesson(lesson); + const lesson = new Lesson({course}); + await this.lessonService.saveLesson(lesson); - return true; + return true; } ``` @@ -345,8 +331,9 @@ The authorization module is the core of authorization. It collects all needed in ### Reference.loader -For situations where only the id and the domain object (string) type is known, it is possible to use the \*ByReferences methods. -They load the reference directly. +It should be use only inside of the authorization module. +It is use to load registrated ressouces by the id and name of the ressource. +This is needed to solve the API requests from external services. (API implementation is missing for now) > Please keep in mind that it can have an impact on the performance if you use it wrongly. > We keep it as a seperate method to avoid the usage in areas where the domain object should exist, because we see the risk that a developer could be tempted by the ease of only passing the id. diff --git a/apps/server/src/modules/authorization/authorization-reference.module.ts b/apps/server/src/modules/authorization/authorization-reference.module.ts new file mode 100644 index 00000000000..7346f2178dd --- /dev/null +++ b/apps/server/src/modules/authorization/authorization-reference.module.ts @@ -0,0 +1,43 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { + CourseGroupRepo, + CourseRepo, + LessonRepo, + SchoolExternalToolRepo, + LegacySchoolRepo, + SubmissionRepo, + TaskRepo, + TeamsRepo, + UserRepo, +} from '@shared/repo'; +import { ToolModule } from '@src/modules/tool'; +import { LoggerModule } from '@src/core/logger'; +import { BoardModule } from '@src/modules/board'; +import { ReferenceLoader, AuthorizationReferenceService, AuthorizationHelper } from './domain'; +import { AuthorizationModule } from './authorization.module'; + +/** + * This module is part of an intermediate state. In the future it should be replaced by an AuthorizationApiModule. + * For now it is used where the authorization itself needs to load data from the database. + * Avoid using this module and load the needed data in your use cases and then use the normal AuthorizationModule! + */ +@Module({ + // TODO: remove forwardRef to TooModule N21-1055 + imports: [AuthorizationModule, forwardRef(() => ToolModule), forwardRef(() => BoardModule), LoggerModule], + providers: [ + AuthorizationHelper, + ReferenceLoader, + UserRepo, + CourseRepo, + CourseGroupRepo, + TaskRepo, + LegacySchoolRepo, + LessonRepo, + TeamsRepo, + SubmissionRepo, + SchoolExternalToolRepo, + AuthorizationReferenceService, + ], + exports: [AuthorizationReferenceService], +}) +export class AuthorizationReferenceModule {} diff --git a/apps/server/src/modules/authorization/authorization.module.ts b/apps/server/src/modules/authorization/authorization.module.ts index c983ee187fd..37ca0a2b229 100644 --- a/apps/server/src/modules/authorization/authorization.module.ts +++ b/apps/server/src/modules/authorization/authorization.module.ts @@ -1,53 +1,46 @@ -import { forwardRef, Module } from '@nestjs/common'; -import { ALL_RULES } from '@shared/domain/rules'; +import { Module } from '@nestjs/common'; +import { UserRepo } from '@shared/repo'; +import { LoggerModule } from '@src/core/logger'; import { FeathersModule } from '@shared/infra/feathers'; import { - CourseGroupRepo, - CourseRepo, - LessonRepo, - SchoolExternalToolRepo, - LegacySchoolRepo, - SubmissionRepo, - TaskRepo, - TeamsRepo, - UserRepo, -} from '@shared/repo'; -import { LoggerModule } from '@src/core/logger'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { ToolModule } from '@src/modules/tool'; -import { BoardModule } from '../board'; -import { AuthorizationHelper } from './authorization.helper'; -import { AuthorizationService } from './authorization.service'; + BoardDoRule, + ContextExternalToolRule, + CourseGroupRule, + CourseRule, + LessonRule, + SchoolExternalToolRule, + SubmissionRule, + TaskRule, + TeamRule, + UserRule, + UserLoginMigrationRule, + LegacySchoolRule, +} from './domain/rules'; +import { AuthorizationHelper, AuthorizationService, RuleManager } from './domain'; import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers'; -import { ReferenceLoader } from './reference.loader'; -import { RuleManager } from './rule-manager'; @Module({ - // TODO: remove forwardRef to TooModule N21-1055 - imports: [ - FeathersModule, - LoggerModule, - LegacySchoolModule, - forwardRef(() => ToolModule), - forwardRef(() => BoardModule), - ], + imports: [FeathersModule, LoggerModule], providers: [ FeathersAuthorizationService, FeathersAuthProvider, AuthorizationService, - ...ALL_RULES, - ReferenceLoader, UserRepo, - CourseRepo, - CourseGroupRepo, - TaskRepo, - LegacySchoolRepo, - LessonRepo, - TeamsRepo, - SubmissionRepo, - SchoolExternalToolRepo, RuleManager, AuthorizationHelper, + // rules + BoardDoRule, + ContextExternalToolRule, + CourseGroupRule, + CourseRule, + LessonRule, + SchoolExternalToolRule, + SubmissionRule, + TaskRule, + TeamRule, + UserRule, + UserLoginMigrationRule, + LegacySchoolRule, ], exports: [FeathersAuthorizationService, AuthorizationService], }) diff --git a/apps/server/src/modules/authorization/authorization.service.ts b/apps/server/src/modules/authorization/authorization.service.ts deleted file mode 100644 index b89561f1c30..00000000000 --- a/apps/server/src/modules/authorization/authorization.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common'; -import { BaseDO, EntityId, User } from '@shared/domain'; -import { AuthorizableObject } from '@shared/domain/domain-object'; -import { ErrorUtils } from '@src/core/error/utils'; -import { AuthorizationHelper } from './authorization.helper'; -import { ForbiddenLoggableException } from './errors/forbidden.loggable-exception'; -import { ReferenceLoader } from './reference.loader'; -import { RuleManager } from './rule-manager'; -import { AuthorizableReferenceType, AuthorizationContext } from './types'; - -@Injectable() -export class AuthorizationService { - constructor( - private readonly ruleManager: RuleManager, - private readonly loader: ReferenceLoader, - private readonly authorizationHelper: AuthorizationHelper - ) {} - - public checkPermission(user: User, object: AuthorizableObject | BaseDO, context: AuthorizationContext): void { - if (!this.hasPermission(user, object, context)) { - throw new ForbiddenLoggableException(user.id, object.constructor.name, context); - } - } - - public hasPermission(user: User, object: AuthorizableObject | BaseDO, context: AuthorizationContext): boolean { - const rule = this.ruleManager.selectRule(user, object, context); - const hasPermission = rule.hasPermission(user, object, context); - - return hasPermission; - } - - /** - * @deprecated - */ - public async checkPermissionByReferences( - userId: EntityId, - entityName: AuthorizableReferenceType, - entityId: EntityId, - context: AuthorizationContext - ): Promise { - if (!(await this.hasPermissionByReferences(userId, entityName, entityId, context))) { - throw new ForbiddenLoggableException(userId, entityName, context); - } - } - - /** - * @deprecated - */ - public async hasPermissionByReferences( - userId: EntityId, - entityName: AuthorizableReferenceType, - entityId: EntityId, - context: AuthorizationContext - ): Promise { - // TODO: This try-catch-block should be removed. See ticket: https://ticketsystem.dbildungscloud.de/browse/BC-4023 - try { - const [user, object] = await Promise.all([ - this.getUserWithPermissions(userId), - this.loader.loadAuthorizableObject(entityName, entityId), - ]); - const rule = this.ruleManager.selectRule(user, object, context); - const hasPermission = rule.hasPermission(user, object, context); - - return hasPermission; - } catch (error) { - throw new ForbiddenException( - null, - ErrorUtils.createHttpExceptionOptions(error, 'AuthorizationService:hasPermissionByReferences') - ); - } - } - - public checkAllPermissions(user: User, requiredPermissions: string[]): void { - if (!this.authorizationHelper.hasAllPermissions(user, requiredPermissions)) { - // TODO: Should be ForbiddenException - throw new UnauthorizedException(); - } - } - - public hasAllPermissions(user: User, requiredPermissions: string[]): boolean { - return this.authorizationHelper.hasAllPermissions(user, requiredPermissions); - } - - public checkOneOfPermissions(user: User, requiredPermissions: string[]): void { - if (!this.authorizationHelper.hasOneOfPermissions(user, requiredPermissions)) { - // TODO: Should be ForbiddenException - throw new UnauthorizedException(); - } - } - - public hasOneOfPermissions(user: User, requiredPermissions: string[]): boolean { - return this.authorizationHelper.hasOneOfPermissions(user, requiredPermissions); - } - - public async getUserWithPermissions(userId: EntityId): Promise { - const userWithPermissions = await this.loader.getUserWithPermissions(userId); - - return userWithPermissions; - } -} diff --git a/apps/server/src/modules/authorization/errors/forbidden.loggable-exception.ts b/apps/server/src/modules/authorization/domain/error/forbidden.loggable-exception.ts similarity index 94% rename from apps/server/src/modules/authorization/errors/forbidden.loggable-exception.ts rename to apps/server/src/modules/authorization/domain/error/forbidden.loggable-exception.ts index f775fb903df..9557ed14ede 100644 --- a/apps/server/src/modules/authorization/errors/forbidden.loggable-exception.ts +++ b/apps/server/src/modules/authorization/domain/error/forbidden.loggable-exception.ts @@ -2,7 +2,7 @@ import { ForbiddenException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { Loggable } from '@src/core/logger/interfaces'; import { ErrorLogMessage } from '@src/core/logger/types'; -import { AuthorizationContext } from '../types'; +import { AuthorizationContext } from '../type'; export class ForbiddenLoggableException extends ForbiddenException implements Loggable { constructor( diff --git a/apps/server/src/modules/authorization/domain/error/index.ts b/apps/server/src/modules/authorization/domain/error/index.ts new file mode 100644 index 00000000000..f2c782cbe56 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/error/index.ts @@ -0,0 +1 @@ +export * from './forbidden.loggable-exception'; diff --git a/apps/server/src/modules/authorization/domain/index.ts b/apps/server/src/modules/authorization/domain/index.ts new file mode 100644 index 00000000000..0f5cfe67874 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/index.ts @@ -0,0 +1,4 @@ +export * from './service'; +export * from './mapper'; +export * from './error'; +export * from './type'; diff --git a/apps/server/src/modules/authorization/authorization-context.builder.spec.ts b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.spec.ts similarity index 96% rename from apps/server/src/modules/authorization/authorization-context.builder.spec.ts rename to apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.spec.ts index 856ec92d4a6..5944d7f22e0 100644 --- a/apps/server/src/modules/authorization/authorization-context.builder.spec.ts +++ b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.spec.ts @@ -1,6 +1,6 @@ import { Permission } from '@shared/domain'; import { AuthorizationContextBuilder } from './authorization-context.builder'; -import { Action } from './types'; +import { Action } from '../type'; describe('AuthorizationContextBuilder', () => { it('Should allow to set required permissions.', () => { diff --git a/apps/server/src/modules/authorization/authorization-context.builder.ts b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts similarity index 91% rename from apps/server/src/modules/authorization/authorization-context.builder.ts rename to apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts index 58259aa1b6f..86b16685b58 100644 --- a/apps/server/src/modules/authorization/authorization-context.builder.ts +++ b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts @@ -1,5 +1,5 @@ import { Permission } from '@shared/domain'; -import { AuthorizationContext, Action } from './types'; +import { AuthorizationContext, Action } from '../type'; export class AuthorizationContextBuilder { private static build(requiredPermissions: Permission[], action: Action): AuthorizationContext { diff --git a/apps/server/src/modules/authorization/domain/mapper/index.ts b/apps/server/src/modules/authorization/domain/mapper/index.ts new file mode 100644 index 00000000000..6f21d79acad --- /dev/null +++ b/apps/server/src/modules/authorization/domain/mapper/index.ts @@ -0,0 +1 @@ +export * from './authorization-context.builder'; diff --git a/apps/server/src/shared/domain/rules/board-do.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/board-do.rule.spec.ts similarity index 95% rename from apps/server/src/shared/domain/rules/board-do.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/board-do.rule.spec.ts index 3574250b67c..bda3680b460 100644 --- a/apps/server/src/shared/domain/rules/board-do.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/board-do.rule.spec.ts @@ -1,10 +1,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { Action } from '@src/modules'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; import { ObjectId } from 'bson'; -import { BoardDoAuthorizable, BoardRoles, UserRoleEnum } from '../domainobject'; -import { Permission } from '../interface'; +import { BoardDoAuthorizable, BoardRoles, UserRoleEnum } from '@shared/domain/domainobject'; +import { Permission } from '@shared/domain/interface'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { BoardDoRule } from './board-do.rule'; describe(BoardDoRule.name, () => { diff --git a/apps/server/src/shared/domain/rules/board-do.rule.ts b/apps/server/src/modules/authorization/domain/rules/board-do.rule.ts similarity index 81% rename from apps/server/src/shared/domain/rules/board-do.rule.ts rename to apps/server/src/modules/authorization/domain/rules/board-do.rule.ts index 575c9f0db5a..2042365e071 100644 --- a/apps/server/src/shared/domain/rules/board-do.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/board-do.rule.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; -import { BoardDoAuthorizable, BoardRoles } from '../domainobject'; -import { User } from '../entity'; +import { BoardDoAuthorizable, BoardRoles } from '@shared/domain/domainobject'; +import { User } from '@shared/domain/entity'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class BoardDoRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/context-external-tool.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts similarity index 93% rename from apps/server/src/shared/domain/rules/context-external-tool.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts index 90a25a1ca82..ddd458959ed 100644 --- a/apps/server/src/shared/domain/rules/context-external-tool.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts @@ -7,16 +7,15 @@ import { setupEntities, userFactory, } from '@shared/testing'; - -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; import { ContextExternalTool } from '@src/modules/tool/context-external-tool/domain'; import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; -import { Role, User } from '../entity'; -import { Permission } from '../interface'; +import { Role, User } from '@shared/domain/entity'; +import { Permission } from '@shared/domain/interface'; import { ContextExternalToolRule } from './context-external-tool.rule'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; describe('ContextExternalToolRule', () => { let service: ContextExternalToolRule; diff --git a/apps/server/src/shared/domain/rules/context-external-tool.rule.ts b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts similarity index 85% rename from apps/server/src/shared/domain/rules/context-external-tool.rule.ts rename to apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts index 35be641e550..5d57c95a160 100644 --- a/apps/server/src/shared/domain/rules/context-external-tool.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; import { ContextExternalTool } from '@src/modules/tool/context-external-tool/domain'; import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; -import { User } from '../entity'; +import { User } from '@shared/domain/entity'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class ContextExternalToolRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/course-group.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/course-group.rule.spec.ts similarity index 97% rename from apps/server/src/shared/domain/rules/course-group.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/course-group.rule.spec.ts index 8296f75f917..62c14baa138 100644 --- a/apps/server/src/shared/domain/rules/course-group.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/course-group.rule.spec.ts @@ -2,10 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { CourseGroup, User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; import { courseFactory, courseGroupFactory, roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; import { CourseGroupRule } from './course-group.rule'; import { CourseRule } from './course.rule'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; describe('CourseGroupRule', () => { let service: CourseGroupRule; diff --git a/apps/server/src/shared/domain/rules/course-group.rule.ts b/apps/server/src/modules/authorization/domain/rules/course-group.rule.ts similarity index 84% rename from apps/server/src/shared/domain/rules/course-group.rule.ts rename to apps/server/src/modules/authorization/domain/rules/course-group.rule.ts index 14638862ba2..863d7072ec8 100644 --- a/apps/server/src/shared/domain/rules/course-group.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/course-group.rule.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { CourseGroup, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; import { CourseRule } from './course.rule'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class CourseGroupRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/course.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/course.rule.spec.ts similarity index 95% rename from apps/server/src/shared/domain/rules/course.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/course.rule.spec.ts index eff0bd41fee..1c4dcc7d670 100644 --- a/apps/server/src/shared/domain/rules/course.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/course.rule.spec.ts @@ -2,9 +2,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Course, User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; import { courseFactory, roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; import { CourseRule } from './course.rule'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; describe('CourseRule', () => { let service: CourseRule; diff --git a/apps/server/src/shared/domain/rules/course.rule.ts b/apps/server/src/modules/authorization/domain/rules/course.rule.ts similarity index 82% rename from apps/server/src/shared/domain/rules/course.rule.ts rename to apps/server/src/modules/authorization/domain/rules/course.rule.ts index 90183dbfd0f..e923e1ab967 100644 --- a/apps/server/src/shared/domain/rules/course.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/course.rule.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Course, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class CourseRule implements Rule { diff --git a/apps/server/src/modules/authorization/domain/rules/index.ts b/apps/server/src/modules/authorization/domain/rules/index.ts new file mode 100644 index 00000000000..bd4ffe27a59 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/rules/index.ts @@ -0,0 +1,16 @@ +/** + * Rules are currently placed in authorization module to avoid dependency cycles. + * In future they must be moved to the feature modules and register it in registration service. + */ +export * from './board-do.rule'; +export * from './context-external-tool.rule'; +export * from './course-group.rule'; +export * from './course.rule'; +export * from './legacy-school.rule'; +export * from './lesson.rule'; +export * from './school-external-tool.rule'; +export * from './submission.rule'; +export * from './task.rule'; +export * from './team.rule'; +export * from './user-login-migration.rule'; +export * from './user.rule'; diff --git a/apps/server/src/shared/domain/rules/legacy-school.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/legacy-school.rule.spec.ts similarity index 93% rename from apps/server/src/shared/domain/rules/legacy-school.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/legacy-school.rule.spec.ts index c547f772de5..489def39318 100644 --- a/apps/server/src/shared/domain/rules/legacy-school.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/legacy-school.rule.spec.ts @@ -1,9 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain/interface'; import { roleFactory, legacySchoolDoFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; import { ObjectID } from 'bson'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { LegacySchoolRule } from './legacy-school.rule'; describe('LegacySchoolRule', () => { diff --git a/apps/server/src/shared/domain/rules/legacy-school.rule.ts b/apps/server/src/modules/authorization/domain/rules/legacy-school.rule.ts similarity index 78% rename from apps/server/src/shared/domain/rules/legacy-school.rule.ts rename to apps/server/src/modules/authorization/domain/rules/legacy-school.rule.ts index 5068d327c35..e115727091a 100644 --- a/apps/server/src/shared/domain/rules/legacy-school.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/legacy-school.rule.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { BaseDO, LegacySchoolDo } from '@shared/domain'; import { User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; -import { AuthorizableObject } from '../domain-object'; +import { AuthorizableObject } from '@shared/domain/domain-object'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; /** * @deprecated because it uses the deprecated LegacySchoolDo. diff --git a/apps/server/src/shared/domain/rules/lesson.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/lesson.rule.spec.ts similarity index 50% rename from apps/server/src/shared/domain/rules/lesson.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/lesson.rule.spec.ts index 13c605f77e4..a8e4bdd8038 100644 --- a/apps/server/src/shared/domain/rules/lesson.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/lesson.rule.spec.ts @@ -10,17 +10,20 @@ import { setupEntities, userFactory, } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; -import { CourseGroupRule, CourseRule } from '.'; +import { NotImplementedException } from '@nestjs/common'; +import { Action, AuthorizationContext } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; +import { CourseGroupRule } from './course-group.rule'; +import { CourseRule } from './course.rule'; import { LessonRule } from './lesson.rule'; +import { AuthorizationContextBuilder } from '../mapper'; describe('LessonRule', () => { - let service: LessonRule; + let rule: LessonRule; let authorizationHelper: AuthorizationHelper; let courseRule: DeepPartial; let courseGroupRule: DeepPartial; - let user: User; + let globalUser: User; let entity: LessonEntity; const permissionA = 'a' as Permission; const permissionB = 'b' as Permission; @@ -33,7 +36,7 @@ describe('LessonRule', () => { providers: [AuthorizationHelper, LessonRule, CourseRule, CourseGroupRule], }).compile(); - service = await module.get(LessonRule); + rule = await module.get(LessonRule); authorizationHelper = await module.get(AuthorizationHelper); courseRule = await module.get(CourseRule); courseGroupRule = await module.get(CourseGroupRule); @@ -41,58 +44,117 @@ describe('LessonRule', () => { beforeEach(() => { const role = roleFactory.build({ permissions: [permissionA, permissionB] }); - user = userFactory.build({ roles: [role] }); + globalUser = userFactory.build({ roles: [role] }); }); it('should call hasAllPermissions on AuthorizationHelper', () => { entity = lessonFactory.build(); const spy = jest.spyOn(authorizationHelper, 'hasAllPermissions'); - service.hasPermission(user, entity, { action: Action.read, requiredPermissions: [] }); - expect(spy).toBeCalledWith(user, []); + rule.hasPermission(globalUser, entity, { action: Action.read, requiredPermissions: [] }); + expect(spy).toBeCalledWith(globalUser, []); }); it('should call courseRule.hasPermission', () => { - const course = courseFactory.build({ teachers: [user] }); + const course = courseFactory.build({ teachers: [globalUser] }); entity = lessonFactory.build({ course }); const spy = jest.spyOn(courseRule, 'hasPermission'); - service.hasPermission(user, entity, { action: Action.write, requiredPermissions: [permissionA] }); - expect(spy).toBeCalledWith(user, entity.course, { action: Action.write, requiredPermissions: [] }); + rule.hasPermission(globalUser, entity, { action: Action.write, requiredPermissions: [permissionA] }); + expect(spy).toBeCalledWith(globalUser, entity.course, { action: Action.write, requiredPermissions: [] }); }); it('should call courseGroupRule.hasPermission', () => { - const course = courseFactory.build({ teachers: [user] }); + const course = courseFactory.build({ teachers: [globalUser] }); const courseGroup = courseGroupFactory.build({ course }); entity = lessonFactory.build({ course: undefined, courseGroup }); const spy = jest.spyOn(courseGroupRule, 'hasPermission'); - service.hasPermission(user, entity, { action: Action.write, requiredPermissions: [permissionA] }); - expect(spy).toBeCalledWith(user, entity.courseGroup, { action: Action.write, requiredPermissions: [] }); + rule.hasPermission(globalUser, entity, { action: Action.write, requiredPermissions: [permissionA] }); + expect(spy).toBeCalledWith(globalUser, entity.courseGroup, { action: Action.write, requiredPermissions: [] }); + }); + + describe('Given user request not implemented action', () => { + const getContext = (): AuthorizationContext => { + const context: AuthorizationContext = { + requiredPermissions: [], + // @ts-expect-error Testcase + action: 'not_implemented', + }; + + return context; + }; + + describe('when valid data exists', () => { + const setup = () => { + const user = userFactory.build(); + const course = courseFactory.build({ teachers: [user] }); + const lesson = lessonFactory.build({ course }); + const context = getContext(); + + return { + user, + lesson, + context, + }; + }; + + it('should reject with NotImplementedException', () => { + const { user, lesson, context } = setup(); + + expect(() => rule.hasPermission(user, lesson, context)).toThrowError(NotImplementedException); + }); + }); + }); + + describe('Given user request Action.write', () => { + const getWriteContext = () => AuthorizationContextBuilder.write([]); + + describe('when lesson has no course or coursegroup', () => { + const setup = () => { + const user = userFactory.build(); + const lessonEntity = lessonFactory.build({ course: undefined }); + const context = getWriteContext(); + + return { + user, + lessonEntity, + context, + }; + }; + + it('should return false', () => { + const { user, lessonEntity, context } = setup(); + + const result = rule.hasPermission(user, lessonEntity, context); + + expect(result).toBe(false); + }); + }); }); describe('User [TEACHER]', () => { it('should return "true" if user in scope', () => { - const course = courseFactory.build({ teachers: [user] }); + const course = courseFactory.build({ teachers: [globalUser] }); entity = lessonFactory.build({ course }); - const res = service.hasPermission(user, entity, { action: Action.read, requiredPermissions: [permissionA] }); + const res = rule.hasPermission(globalUser, entity, { action: Action.read, requiredPermissions: [permissionA] }); expect(res).toBe(true); }); it('should return "true" if user has access to hidden entity', () => { - const course = courseFactory.build({ teachers: [user] }); + const course = courseFactory.build({ teachers: [globalUser] }); entity = lessonFactory.build({ course, hidden: true }); - const res = service.hasPermission(user, entity, { action: Action.read, requiredPermissions: [permissionA] }); + const res = rule.hasPermission(globalUser, entity, { action: Action.read, requiredPermissions: [permissionA] }); expect(res).toBe(true); }); it('should return "false" if user has not permission', () => { entity = lessonFactory.build(); - const res = service.hasPermission(user, entity, { action: Action.read, requiredPermissions: [permissionC] }); + const res = rule.hasPermission(globalUser, entity, { action: Action.read, requiredPermissions: [permissionC] }); expect(res).toBe(false); }); it('should return "false" if user has not access to entity', () => { entity = lessonFactory.build(); - const res = service.hasPermission(user, entity, { action: Action.read, requiredPermissions: [permissionC] }); + const res = rule.hasPermission(globalUser, entity, { action: Action.read, requiredPermissions: [permissionC] }); expect(res).toBe(false); }); }); @@ -106,14 +168,14 @@ describe('LessonRule', () => { it('should return "false" if user has access to entity', () => { const course = courseFactory.build({ students: [student] }); entity = lessonFactory.build({ course }); - const res = service.hasPermission(student, entity, { action: Action.read, requiredPermissions: [permissionA] }); + const res = rule.hasPermission(student, entity, { action: Action.read, requiredPermissions: [permissionA] }); expect(res).toBe(true); }); it('should return "false" if user has not access to hidden entity', () => { const course = courseFactory.build({ students: [student] }); entity = lessonFactory.build({ course, hidden: true }); - const res = service.hasPermission(student, entity, { action: Action.read, requiredPermissions: [permissionA] }); + const res = rule.hasPermission(student, entity, { action: Action.read, requiredPermissions: [permissionA] }); expect(res).toBe(false); }); }); diff --git a/apps/server/src/shared/domain/rules/lesson.rule.ts b/apps/server/src/modules/authorization/domain/rules/lesson.rule.ts similarity index 88% rename from apps/server/src/shared/domain/rules/lesson.rule.ts rename to apps/server/src/modules/authorization/domain/rules/lesson.rule.ts index ff264af13ae..1f59f98ad49 100644 --- a/apps/server/src/shared/domain/rules/lesson.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/lesson.rule.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotImplementedException } from '@nestjs/common'; import { Course, CourseGroup, LessonEntity, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { CourseGroupRule } from './course-group.rule'; import { CourseRule } from './course.rule'; @@ -27,6 +27,8 @@ export class LessonRule implements Rule { hasLessonPermission = this.lessonReadPermission(user, entity); } else if (action === Action.write) { hasLessonPermission = this.lessonWritePermission(user, entity); + } else { + throw new NotImplementedException('Action is not supported.'); } const hasUserPermission = this.authorizationHelper.hasAllPermissions(user, requiredPermissions); @@ -55,12 +57,14 @@ export class LessonRule implements Rule { } private parentPermission(user: User, entity: LessonEntity, action: Action): boolean { - let result = false; + let result: boolean; if (entity.courseGroup) { result = this.courseGroupPermission(user, entity.courseGroup, action); } else if (entity.course) { result = this.coursePermission(user, entity.course, action); // ask course for student = read || teacher, sub-teacher = write + } else { + result = false; } return result; diff --git a/apps/server/src/shared/domain/rules/school-external-tool.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts similarity index 92% rename from apps/server/src/shared/domain/rules/school-external-tool.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts index b24ed4d0ac8..d1781bb8576 100644 --- a/apps/server/src/shared/domain/rules/school-external-tool.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts @@ -7,13 +7,11 @@ import { userFactory, schoolExternalToolFactory, } from '@shared/testing'; - -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; -import { Role, User } from '../entity'; -import { Permission } from '../interface'; +import { Role, User, Permission } from '@shared/domain'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { SchoolExternalToolRule } from './school-external-tool.rule'; describe('SchoolExternalToolRule', () => { diff --git a/apps/server/src/shared/domain/rules/school-external-tool.rule.ts b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts similarity index 85% rename from apps/server/src/shared/domain/rules/school-external-tool.rule.ts rename to apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts index bd28502faa2..041c8b523e2 100644 --- a/apps/server/src/shared/domain/rules/school-external-tool.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; -import { User } from '../entity'; +import { User } from '@shared/domain/entity'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class SchoolExternalToolRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/submission.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/submission.rule.spec.ts similarity index 93% rename from apps/server/src/shared/domain/rules/submission.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/submission.rule.spec.ts index 098f83547e8..8b054671970 100644 --- a/apps/server/src/shared/domain/rules/submission.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/submission.rule.spec.ts @@ -9,9 +9,14 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; -import { CourseGroupRule, CourseRule, LessonRule, SubmissionRule, TaskRule } from '.'; +import { NotImplementedException } from '@nestjs/common'; +import { Action, AuthorizationContext } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; +import { SubmissionRule } from './submission.rule'; +import { TaskRule } from './task.rule'; +import { CourseRule } from './course.rule'; +import { LessonRule } from './lesson.rule'; +import { CourseGroupRule } from './course-group.rule'; const buildUserWithPermission = (permission) => { const role = roleFactory.buildWithId({ permissions: [permission] }); @@ -76,6 +81,38 @@ describe('SubmissionRule', () => { }); describe('hasPermission', () => { + describe('Given user request not implemented action', () => { + const getContext = (): AuthorizationContext => { + const context: AuthorizationContext = { + requiredPermissions: [], + // @ts-expect-error Testcase + action: 'not_implemented', + }; + + return context; + }; + + describe('when valid data exists', () => { + const setup = () => { + const user = userFactory.build(); + const submission = submissionFactory.build({ student: user }); + const context = getContext(); + + return { + user, + submission, + context, + }; + }; + + it('should reject with NotImplementedException', () => { + const { user, submission, context } = setup(); + + expect(() => submissionRule.hasPermission(user, submission, context)).toThrowError(NotImplementedException); + }); + }); + }); + describe('when user roles do not contain required permissions', () => { const setup = () => { const permission = 'a' as Permission; diff --git a/apps/server/src/shared/domain/rules/submission.rule.ts b/apps/server/src/modules/authorization/domain/rules/submission.rule.ts similarity index 88% rename from apps/server/src/shared/domain/rules/submission.rule.ts rename to apps/server/src/modules/authorization/domain/rules/submission.rule.ts index 3234f8e8cff..6bff9504f5c 100644 --- a/apps/server/src/shared/domain/rules/submission.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/submission.rule.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotImplementedException } from '@nestjs/common'; import { Submission, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { TaskRule } from './task.rule'; @Injectable() @@ -31,6 +31,8 @@ export class SubmissionRule implements Rule { hasAccessToSubmission = this.hasWriteAccess(user, submission); } else if (action === Action.read) { hasAccessToSubmission = this.hasReadAccess(user, submission); + } else { + throw new NotImplementedException('Action is not supported.'); } return hasAccessToSubmission; diff --git a/apps/server/src/shared/domain/rules/task.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/task.rule.spec.ts similarity index 96% rename from apps/server/src/shared/domain/rules/task.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/task.rule.spec.ts index 0fc886df84f..31d68661ff0 100644 --- a/apps/server/src/shared/domain/rules/task.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/task.rule.spec.ts @@ -2,9 +2,12 @@ import { DeepPartial } from '@mikro-orm/core'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission, RoleName } from '@shared/domain/interface'; import { courseFactory, lessonFactory, roleFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { CourseGroupRule, CourseRule, LessonRule, TaskRule } from '.'; -import { Action } from '../../../modules/authorization/types/action.enum'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; +import { CourseGroupRule } from './course-group.rule'; +import { TaskRule } from './task.rule'; +import { CourseRule } from './course.rule'; +import { LessonRule } from './lesson.rule'; describe('TaskRule', () => { let service: TaskRule; diff --git a/apps/server/src/shared/domain/rules/task.rule.ts b/apps/server/src/modules/authorization/domain/rules/task.rule.ts similarity index 90% rename from apps/server/src/shared/domain/rules/task.rule.ts rename to apps/server/src/modules/authorization/domain/rules/task.rule.ts index 4c358593109..3ebc04d9f71 100644 --- a/apps/server/src/shared/domain/rules/task.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/task.rule.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Task, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action, AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { Action, AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { CourseRule } from './course.rule'; import { LessonRule } from './lesson.rule'; diff --git a/apps/server/src/shared/domain/rules/team.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/team.rule.spec.ts similarity index 91% rename from apps/server/src/shared/domain/rules/team.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/team.rule.spec.ts index d29eaaaa6f8..da99354a49b 100644 --- a/apps/server/src/shared/domain/rules/team.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/team.rule.spec.ts @@ -1,10 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain/interface'; -import { roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { teamFactory } from '@shared/testing/factory/team.factory'; -import { TeamRule } from '@shared/domain/rules/team.rule'; -import { AuthorizationContextBuilder } from '@src/modules/authorization/authorization-context.builder'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; +import { roleFactory, setupEntities, userFactory, teamFactory } from '@shared/testing'; +import { AuthorizationHelper } from '../service/authorization.helper'; +import { TeamRule } from './team.rule'; +import { AuthorizationContextBuilder } from '../mapper'; describe('TeamRule', () => { let rule: TeamRule; diff --git a/apps/server/src/shared/domain/rules/team.rule.ts b/apps/server/src/modules/authorization/domain/rules/team.rule.ts similarity index 81% rename from apps/server/src/shared/domain/rules/team.rule.ts rename to apps/server/src/modules/authorization/domain/rules/team.rule.ts index 23ad0d55cf7..2d8f5e90edf 100644 --- a/apps/server/src/shared/domain/rules/team.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/team.rule.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { TeamEntity, TeamUserEntity, User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class TeamRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/user-login-migration.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.spec.ts similarity index 94% rename from apps/server/src/shared/domain/rules/user-login-migration.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.spec.ts index a7c6b1e5f7a..f7fe9d3c53f 100644 --- a/apps/server/src/shared/domain/rules/user-login-migration.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.spec.ts @@ -2,10 +2,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { schoolFactory, setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; -import { Action, AuthorizationContext } from '@src/modules/authorization'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { UserLoginMigrationDO } from '../domainobject'; -import { Permission } from '../interface'; +import { UserLoginMigrationDO } from '@shared/domain/domainobject'; +import { Permission } from '@shared/domain/interface'; +import { Action, AuthorizationContext } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { UserLoginMigrationRule } from './user-login-migration.rule'; describe('UserLoginMigrationRule', () => { diff --git a/apps/server/src/shared/domain/rules/user-login-migration.rule.ts b/apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.ts similarity index 72% rename from apps/server/src/shared/domain/rules/user-login-migration.rule.ts rename to apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.ts index 084e4d26372..3ae82d02505 100644 --- a/apps/server/src/shared/domain/rules/user-login-migration.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/user-login-migration.rule.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; -import { UserLoginMigrationDO } from '../domainobject'; -import { User } from '../entity'; +import { UserLoginMigrationDO } from '@shared/domain/domainobject'; +import { User } from '@shared/domain/entity'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class UserLoginMigrationRule implements Rule { diff --git a/apps/server/src/shared/domain/rules/user.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/user.rule.spec.ts similarity index 94% rename from apps/server/src/shared/domain/rules/user.rule.spec.ts rename to apps/server/src/modules/authorization/domain/rules/user.rule.spec.ts index bd771961ed3..85492348f75 100644 --- a/apps/server/src/shared/domain/rules/user.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/user.rule.spec.ts @@ -2,8 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Role, User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; import { roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { Action } from '@src/modules/authorization/types'; +import { Action } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; import { UserRule } from './user.rule'; describe('UserRule', () => { diff --git a/apps/server/src/shared/domain/rules/user.rule.ts b/apps/server/src/modules/authorization/domain/rules/user.rule.ts similarity index 78% rename from apps/server/src/shared/domain/rules/user.rule.ts rename to apps/server/src/modules/authorization/domain/rules/user.rule.ts index 3dd9bc6d229..2a1365881e1 100644 --- a/apps/server/src/shared/domain/rules/user.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/user.rule.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { User } from '@shared/domain/entity'; -import { AuthorizationHelper } from '@src/modules/authorization/authorization.helper'; -import { AuthorizationContext, Rule } from '@src/modules/authorization/types'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; @Injectable() export class UserRule implements Rule { diff --git a/apps/server/src/modules/authorization/domain/service/authorization-reference.service.spec.ts b/apps/server/src/modules/authorization/domain/service/authorization-reference.service.spec.ts new file mode 100644 index 00000000000..8ab1719a72d --- /dev/null +++ b/apps/server/src/modules/authorization/domain/service/authorization-reference.service.spec.ts @@ -0,0 +1,183 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { NotFoundException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { courseFactory, setupEntities, userFactory } from '@shared/testing'; +import { ObjectId } from 'bson'; +import { AuthorizableReferenceType } from '../type'; +import { AuthorizationService } from './authorization.service'; +import { ReferenceLoader } from './reference.loader'; +import { AuthorizationContextBuilder } from '../mapper'; +import { ForbiddenLoggableException } from '../error'; +import { AuthorizationReferenceService } from './authorization-reference.service'; + +describe('AuthorizationReferenceService', () => { + let service: AuthorizationReferenceService; + let authorizationService: DeepMocked; + let loader: DeepMocked; + + beforeAll(async () => { + await setupEntities(); + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AuthorizationReferenceService, + { + provide: AuthorizationService, + useValue: createMock(), + }, + { + provide: ReferenceLoader, + useValue: createMock(), + }, + ], + }).compile(); + + service = await module.get(AuthorizationReferenceService); + authorizationService = await module.get(AuthorizationService); + loader = await module.get(ReferenceLoader); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('checkPermissionByReferences', () => { + const setupData = () => { + const entityId = new ObjectId().toHexString(); + const userId = new ObjectId().toHexString(); + const context = AuthorizationContextBuilder.read([]); + const entityName = AuthorizableReferenceType.Course; + + return { context, entityId, userId, entityName }; + }; + + describe('when hasPermissionByReferences returns false', () => { + const setup = () => { + const { entityId, userId, context, entityName } = setupData(); + + const spy = jest.spyOn(service, 'hasPermissionByReferences').mockResolvedValueOnce(false); + + return { context, userId, entityId, entityName, spy }; + }; + + it('should reject with ForbiddenLoggableException', async () => { + const { context, userId, entityId, entityName, spy } = setup(); + + await expect(service.checkPermissionByReferences(userId, entityName, entityId, context)).rejects.toThrow( + new ForbiddenLoggableException(userId, entityName, context) + ); + + spy.mockRestore(); + }); + }); + + describe('when hasPermissionByReferences returns true', () => { + const setup = () => { + const { entityId, userId, context, entityName } = setupData(); + + const spy = jest.spyOn(service, 'hasPermissionByReferences').mockResolvedValueOnce(true); + + return { context, userId, entityId, entityName, spy }; + }; + + it('should resolve without error', async () => { + const { context, userId, entityId, entityName, spy } = setup(); + + await expect(service.checkPermissionByReferences(userId, entityName, entityId, context)).resolves.not.toThrow(); + + spy.mockRestore(); + }); + }); + }); + + describe('hasPermissionByReferences', () => { + const setupData = () => { + const entity = courseFactory.buildWithId(); + const user = userFactory.buildWithId(); + const context = AuthorizationContextBuilder.read([]); + const entityName = AuthorizableReferenceType.Course; + + return { context, entity, user, entityName }; + }; + + describe('when loader throws an error', () => { + const setup = () => { + const { entity, user, context, entityName } = setupData(); + + loader.loadAuthorizableObject.mockRejectedValueOnce(new NotFoundException()); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.hasPermission.mockReturnValueOnce(true); + + return { context, userId: user.id, entityId: entity.id, entityName }; + }; + + it('should reject with this error', async () => { + const { context, userId, entityId, entityName } = setup(); + + await expect(service.hasPermissionByReferences(userId, entityName, entityId, context)).rejects.toThrow( + new NotFoundException() + ); + }); + }); + + describe('when authorizationService throws an error', () => { + const setup = () => { + const { entity, user, context, entityName } = setupData(); + + loader.loadAuthorizableObject.mockRejectedValueOnce(entity); + authorizationService.getUserWithPermissions.mockRejectedValueOnce(new NotFoundException()); + authorizationService.hasPermission.mockReturnValueOnce(true); + + return { context, userId: user.id, entityId: entity.id, entityName }; + }; + + it('should reject with this error', async () => { + const { context, userId, entityId, entityName } = setup(); + + await expect(service.hasPermissionByReferences(userId, entityName, entityId, context)).rejects.toThrow( + new NotFoundException() + ); + }); + }); + + describe('when loader can load entites and authorization resolve with true', () => { + const setup = () => { + const { entity, user, context, entityName } = setupData(); + + loader.loadAuthorizableObject.mockResolvedValueOnce(entity); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.hasPermission.mockReturnValueOnce(true); + + return { context, userId: user.id, entityId: entity.id, entityName }; + }; + + it('should resolve to true', async () => { + const { context, userId, entityId, entityName } = setup(); + + const result = await service.hasPermissionByReferences(userId, entityName, entityId, context); + + expect(result).toBe(true); + }); + }); + + describe('when loader can load entities and authorization resolve with false', () => { + const setup = () => { + const { entity, user, context, entityName } = setupData(); + + loader.loadAuthorizableObject.mockResolvedValueOnce(entity); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.hasPermission.mockReturnValueOnce(false); + + return { context, userId: user.id, entityId: entity.id, entityName }; + }; + + it('should resolve to false', async () => { + const { context, userId, entityId, entityName } = setup(); + + const result = await service.hasPermissionByReferences(userId, entityName, entityId, context); + + expect(result).toBe(false); + }); + }); + }); +}); diff --git a/apps/server/src/modules/authorization/domain/service/authorization-reference.service.ts b/apps/server/src/modules/authorization/domain/service/authorization-reference.service.ts new file mode 100644 index 00000000000..814df9378da --- /dev/null +++ b/apps/server/src/modules/authorization/domain/service/authorization-reference.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ReferenceLoader } from './reference.loader'; +import { AuthorizationContext, AuthorizableReferenceType } from '../type'; +import { ForbiddenLoggableException } from '../error'; +import { AuthorizationService } from './authorization.service'; + +/** + * Should by use only internal in authorization module. See ticket: BC-3990 + */ +@Injectable() +export class AuthorizationReferenceService { + constructor(private readonly loader: ReferenceLoader, private readonly authorizationService: AuthorizationService) {} + + public async checkPermissionByReferences( + userId: EntityId, + entityName: AuthorizableReferenceType, + entityId: EntityId, + context: AuthorizationContext + ): Promise { + if (!(await this.hasPermissionByReferences(userId, entityName, entityId, context))) { + throw new ForbiddenLoggableException(userId, entityName, context); + } + } + + public async hasPermissionByReferences( + userId: EntityId, + entityName: AuthorizableReferenceType, + entityId: EntityId, + context: AuthorizationContext + ): Promise { + const [user, object] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.loader.loadAuthorizableObject(entityName, entityId), + ]); + + const hasPermission = this.authorizationService.hasPermission(user, object, context); + + return hasPermission; + } +} diff --git a/apps/server/src/modules/authorization/authorization.helper.spec.ts b/apps/server/src/modules/authorization/domain/service/authorization.helper.spec.ts similarity index 100% rename from apps/server/src/modules/authorization/authorization.helper.spec.ts rename to apps/server/src/modules/authorization/domain/service/authorization.helper.spec.ts diff --git a/apps/server/src/modules/authorization/authorization.helper.ts b/apps/server/src/modules/authorization/domain/service/authorization.helper.ts similarity index 100% rename from apps/server/src/modules/authorization/authorization.helper.ts rename to apps/server/src/modules/authorization/domain/service/authorization.helper.ts diff --git a/apps/server/src/modules/authorization/authorization.service.spec.ts b/apps/server/src/modules/authorization/domain/service/authorization.service.spec.ts similarity index 60% rename from apps/server/src/modules/authorization/authorization.service.spec.ts rename to apps/server/src/modules/authorization/domain/service/authorization.service.spec.ts index 766c2c84d23..f113c64472c 100644 --- a/apps/server/src/modules/authorization/authorization.service.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/authorization.service.spec.ts @@ -1,33 +1,34 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ForbiddenException, InternalServerErrorException, UnauthorizedException } from '@nestjs/common'; +import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from './authorization-context.builder'; +import { UserRepo } from '@shared/repo'; +import { AuthorizationContextBuilder } from '../mapper'; import { AuthorizationHelper } from './authorization.helper'; import { AuthorizationService } from './authorization.service'; -import { ForbiddenLoggableException } from './errors/forbidden.loggable-exception'; +import { ForbiddenLoggableException } from '../error'; import { ReferenceLoader } from './reference.loader'; import { RuleManager } from './rule-manager'; -import { AuthorizableReferenceType, Rule } from './types'; +import { Rule } from '../type'; -describe('AuthorizationService', () => { - class TestRule implements Rule { - constructor(private returnValueOfhasPermission: boolean) {} +class TestRule implements Rule { + constructor(private returnValueOfhasPermission: boolean) {} - isApplicable(): boolean { - return true; - } + isApplicable(): boolean { + return true; + } - hasPermission(): boolean { - return this.returnValueOfhasPermission; - } + hasPermission(): boolean { + return this.returnValueOfhasPermission; } +} +describe('AuthorizationService', () => { let service: AuthorizationService; let ruleManager: DeepMocked; - let loader: DeepMocked; let authorizationHelper: DeepMocked; + let userRepo: DeepMocked; const testPermission = 'CAN_TEST' as Permission; @@ -49,13 +50,17 @@ describe('AuthorizationService', () => { provide: AuthorizationHelper, useValue: createMock(), }, + { + provide: UserRepo, + useValue: createMock(), + }, ], }).compile(); service = await module.get(AuthorizationService); ruleManager = await module.get(RuleManager); - loader = await module.get(ReferenceLoader); authorizationHelper = await module.get(AuthorizationHelper); + userRepo = await module.get(UserRepo); }); afterEach(() => { @@ -66,7 +71,7 @@ describe('AuthorizationService', () => { describe('when hasPermission returns false', () => { const setup = () => { const context = AuthorizationContextBuilder.read([]); - const user = userFactory.build(); + const user = userFactory.buildWithId(); const spy = jest.spyOn(service, 'hasPermission').mockReturnValueOnce(false); @@ -85,7 +90,7 @@ describe('AuthorizationService', () => { describe('when hasPermission returns true', () => { const setup = () => { const context = AuthorizationContextBuilder.read([]); - const user = userFactory.build(); + const user = userFactory.buildWithId(); const spy = jest.spyOn(service, 'hasPermission').mockReturnValueOnce(true); @@ -106,7 +111,7 @@ describe('AuthorizationService', () => { describe('when the selected rule returns false', () => { const setup = () => { const context = AuthorizationContextBuilder.read([]); - const user = userFactory.build(); + const user = userFactory.buildWithId(); const testRule = new TestRule(false); ruleManager.selectRule.mockReturnValueOnce(testRule); @@ -126,7 +131,7 @@ describe('AuthorizationService', () => { describe('when the selected rule returns true', () => { const setup = () => { const context = AuthorizationContextBuilder.read([]); - const user = userFactory.build(); + const user = userFactory.buildWithId(); const testRule = new TestRule(true); ruleManager.selectRule.mockReturnValueOnce(testRule); @@ -144,123 +149,10 @@ describe('AuthorizationService', () => { }); }); - describe('checkPermissionByReferences', () => { - describe('when hasPermissionByReferences returns false', () => { - const setup = () => { - const context = AuthorizationContextBuilder.read([]); - const userId = 'test'; - const entityId = 'test'; - const entityName = AuthorizableReferenceType.Course; - - const spy = jest.spyOn(service, 'hasPermissionByReferences').mockResolvedValueOnce(false); - - return { context, userId, entityId, entityName, spy }; - }; - - it('should reject with ForbiddenLoggableException', async () => { - const { context, userId, entityId, entityName, spy } = setup(); - - await expect(service.checkPermissionByReferences(userId, entityName, entityId, context)).rejects.toThrow( - ForbiddenLoggableException - ); - - spy.mockRestore(); - }); - }); - - describe('when hasPermissionByReferences returns true', () => { - const setup = () => { - const context = AuthorizationContextBuilder.read([]); - const userId = 'test'; - const entityId = 'test'; - const entityName = AuthorizableReferenceType.Course; - - const spy = jest.spyOn(service, 'hasPermissionByReferences').mockResolvedValueOnce(true); - - return { context, userId, entityId, entityName, spy }; - }; - - it('should resolve', async () => { - const { context, userId, entityId, entityName, spy } = setup(); - - await expect(service.checkPermissionByReferences(userId, entityName, entityId, context)).resolves.not.toThrow(); - - spy.mockRestore(); - }); - }); - }); - - describe('hasPermissionByReferences', () => { - describe('when loader throws an error', () => { - const setup = () => { - const context = AuthorizationContextBuilder.read([]); - const userId = 'test'; - const entityId = 'test'; - const entityName = AuthorizableReferenceType.Course; - - loader.loadAuthorizableObject.mockRejectedValueOnce(InternalServerErrorException); - - return { context, userId, entityId, entityName }; - }; - - it('should reject with ForbiddenException', async () => { - const { context, userId, entityId, entityName } = setup(); - - await expect(service.hasPermissionByReferences(userId, entityName, entityId, context)).rejects.toThrow( - ForbiddenException - ); - }); - }); - - describe('when the selected rule returns true', () => { - const setup = () => { - const context = AuthorizationContextBuilder.read([]); - const userId = 'test'; - const entityId = 'test'; - const entityName = AuthorizableReferenceType.Course; - const testRule = new TestRule(true); - - ruleManager.selectRule.mockReturnValueOnce(testRule); - - return { context, userId, entityId, entityName }; - }; - - it('should resolve to true', async () => { - const { context, userId, entityId, entityName } = setup(); - - const result = await service.hasPermissionByReferences(userId, entityName, entityId, context); - - expect(result).toBe(true); - }); - }); - - describe('when the selected rule returns false', () => { - const setup = () => { - const context = AuthorizationContextBuilder.read([]); - const userId = 'test'; - const entityId = 'test'; - const entityName = AuthorizableReferenceType.Course; - const testRule = new TestRule(false); - - ruleManager.selectRule.mockReturnValueOnce(testRule); - - return { context, userId, entityId, entityName }; - }; - - it('should resolve to false', async () => { - const { context, userId, entityId, entityName } = setup(); - - const result = await service.hasPermissionByReferences(userId, entityName, entityId, context); - - expect(result).toBe(false); - }); - }); - }); - describe('checkAllPermissions', () => { describe('when helper method returns false', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasAllPermissions.mockReturnValueOnce(false); @@ -277,7 +169,7 @@ describe('AuthorizationService', () => { describe('when helper method returns true', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasAllPermissions.mockReturnValueOnce(true); @@ -296,7 +188,7 @@ describe('AuthorizationService', () => { describe('hasAllPermissions', () => { describe('when helper method returns false', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasAllPermissions.mockReturnValueOnce(false); @@ -315,7 +207,7 @@ describe('AuthorizationService', () => { describe('when helper method returns true', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasAllPermissions.mockReturnValueOnce(true); @@ -336,7 +228,7 @@ describe('AuthorizationService', () => { describe('checkOneOfPermissions', () => { describe('when helper method returns false', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasOneOfPermissions.mockReturnValueOnce(false); @@ -353,7 +245,7 @@ describe('AuthorizationService', () => { describe('when helper method returns true', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasOneOfPermissions.mockReturnValueOnce(true); @@ -372,7 +264,7 @@ describe('AuthorizationService', () => { describe('hasOneOfPermissions', () => { describe('when helper method returns false', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasOneOfPermissions.mockReturnValueOnce(false); @@ -391,7 +283,7 @@ describe('AuthorizationService', () => { describe('when helper method returns true', () => { const setup = () => { - const user = userFactory.build(); + const user = userFactory.buildWithId(); const requiredPermissions = [testPermission]; authorizationHelper.hasOneOfPermissions.mockReturnValueOnce(true); @@ -410,12 +302,18 @@ describe('AuthorizationService', () => { }); describe('getUserWithPermissions', () => { + const setup = () => { + const user = userFactory.buildWithId(); + + userRepo.findById.mockResolvedValueOnce(user); + + return { user }; + }; + it('should return user received from loader', async () => { - const userId = 'test'; - const user = userFactory.build(); - loader.getUserWithPermissions.mockResolvedValueOnce(user); + const { user } = setup(); - const result = await service.getUserWithPermissions(userId); + const result = await service.getUserWithPermissions(user.id); expect(result).toEqual(user); }); diff --git a/apps/server/src/modules/authorization/domain/service/authorization.service.ts b/apps/server/src/modules/authorization/domain/service/authorization.service.ts new file mode 100644 index 00000000000..5218dffda81 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/service/authorization.service.ts @@ -0,0 +1,59 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { BaseDO, EntityId, User } from '@shared/domain'; +import { AuthorizableObject } from '@shared/domain/domain-object'; +import { UserRepo } from '@shared/repo'; +import { AuthorizationHelper } from './authorization.helper'; +import { ForbiddenLoggableException } from '../error'; +import { RuleManager } from './rule-manager'; +import { AuthorizationContext } from '../type'; + +@Injectable() +export class AuthorizationService { + constructor( + private readonly ruleManager: RuleManager, + private readonly authorizationHelper: AuthorizationHelper, + private readonly userRepo: UserRepo + ) {} + + public checkPermission(user: User, object: AuthorizableObject | BaseDO, context: AuthorizationContext): void { + if (!this.hasPermission(user, object, context)) { + throw new ForbiddenLoggableException(user.id, object.constructor.name, context); + } + } + + public hasPermission(user: User, object: AuthorizableObject | BaseDO, context: AuthorizationContext): boolean { + const rule = this.ruleManager.selectRule(user, object, context); + const hasPermission = rule.hasPermission(user, object, context); + + return hasPermission; + } + + public checkAllPermissions(user: User, requiredPermissions: string[]): void { + if (!this.authorizationHelper.hasAllPermissions(user, requiredPermissions)) { + // TODO: Should be ForbiddenLoggableException + throw new UnauthorizedException(); + } + } + + public hasAllPermissions(user: User, requiredPermissions: string[]): boolean { + return this.authorizationHelper.hasAllPermissions(user, requiredPermissions); + } + + public checkOneOfPermissions(user: User, requiredPermissions: string[]): void { + if (!this.authorizationHelper.hasOneOfPermissions(user, requiredPermissions)) { + // TODO: Should be ForbiddenLoggableException + throw new UnauthorizedException(); + } + } + + public hasOneOfPermissions(user: User, requiredPermissions: string[]): boolean { + return this.authorizationHelper.hasOneOfPermissions(user, requiredPermissions); + } + + public async getUserWithPermissions(userId: EntityId): Promise { + // replace with service method getUserWithPermissions BC-5069 + const userWithPopulatedRoles = await this.userRepo.findById(userId, true); + + return userWithPopulatedRoles; + } +} diff --git a/apps/server/src/modules/authorization/domain/service/index.ts b/apps/server/src/modules/authorization/domain/service/index.ts new file mode 100644 index 00000000000..4175cc4b7a7 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/service/index.ts @@ -0,0 +1,5 @@ +export * from './authorization.service'; +export * from './authorization.helper'; +export * from './rule-manager'; +export * from './authorization-reference.service'; +export * from './reference.loader'; diff --git a/apps/server/src/modules/authorization/reference.loader.spec.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts similarity index 87% rename from apps/server/src/modules/authorization/reference.loader.spec.ts rename to apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts index 9bba78b1880..0403ebcbfd5 100644 --- a/apps/server/src/modules/authorization/reference.loader.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts @@ -14,11 +14,11 @@ import { TeamsRepo, UserRepo, } from '@shared/repo'; -import { roleFactory, setupEntities, userFactory } from '@shared/testing'; +import { setupEntities, userFactory } from '@shared/testing'; import { BoardDoAuthorizableService } from '@src/modules/board'; import { ContextExternalToolAuthorizableService } from '@src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service'; import { ReferenceLoader } from './reference.loader'; -import { AuthorizableReferenceType } from './types'; +import { AuthorizableReferenceType } from '../type'; describe('reference.loader', () => { let service: ReferenceLoader; @@ -138,7 +138,7 @@ describe('reference.loader', () => { it('should call userRepo.findById', async () => { await service.loadAuthorizableObject(AuthorizableReferenceType.User, entityId); - expect(userRepo.findById).toBeCalledWith(entityId, true); + expect(userRepo.findById).toBeCalledWith(entityId); }); it('should call lessonRepo.findById', async () => { @@ -192,33 +192,4 @@ describe('reference.loader', () => { ).rejects.toThrow(NotImplementedException); }); }); - - describe('getUserWithPermissions', () => { - describe('when user successfully', () => { - const setup = () => { - const roles = [roleFactory.build()]; - const user = userFactory.buildWithId({ roles }); - userRepo.findById.mockResolvedValue(user); - return { - user, - }; - }; - - it('should call userRepo.findById with specific arguments', async () => { - const { user } = setup(); - - await service.getUserWithPermissions(user.id); - - expect(userRepo.findById).toBeCalledWith(user.id, true); - }); - - it('should return user', async () => { - const { user } = setup(); - - const result = await service.getUserWithPermissions(user.id); - - expect(result).toBe(user); - }); - }); - }); }); diff --git a/apps/server/src/modules/authorization/reference.loader.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.ts similarity index 88% rename from apps/server/src/modules/authorization/reference.loader.ts rename to apps/server/src/modules/authorization/domain/service/reference.loader.ts index 9afe013fd24..5c38963c6f5 100644 --- a/apps/server/src/modules/authorization/reference.loader.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.ts @@ -1,5 +1,5 @@ import { Injectable, NotImplementedException } from '@nestjs/common'; -import { BaseDO, EntityId, User } from '@shared/domain'; +import { BaseDO, EntityId } from '@shared/domain'; import { AuthorizableObject } from '@shared/domain/domain-object'; import { CourseGroupRepo, @@ -12,11 +12,10 @@ import { TeamsRepo, UserRepo, } from '@shared/repo'; -import { BoardDoAuthorizableService } from '@src/modules/board/service'; +import { BoardDoAuthorizableService } from '@src/modules/board'; import { ContextExternalToolAuthorizableService } from '@src/modules/tool/context-external-tool/service'; -import { AuthorizableReferenceType } from './types'; +import { AuthorizableReferenceType } from '../type'; -// replace later with general "base" do-repo type RepoType = | TaskRepo | CourseRepo @@ -55,7 +54,7 @@ export class ReferenceLoader { this.repos.set(AuthorizableReferenceType.Task, { repo: this.taskRepo }); this.repos.set(AuthorizableReferenceType.Course, { repo: this.courseRepo }); this.repos.set(AuthorizableReferenceType.CourseGroup, { repo: this.courseGroupRepo }); - this.repos.set(AuthorizableReferenceType.User, { repo: this.userRepo, populate: true }); + this.repos.set(AuthorizableReferenceType.User, { repo: this.userRepo }); this.repos.set(AuthorizableReferenceType.School, { repo: this.schoolRepo }); this.repos.set(AuthorizableReferenceType.Lesson, { repo: this.lessonRepo }); this.repos.set(AuthorizableReferenceType.Team, { repo: this.teamsRepo, populate: true }); @@ -90,10 +89,4 @@ export class ReferenceLoader { return object; } - - async getUserWithPermissions(userId: EntityId): Promise { - const user = await this.userRepo.findById(userId, true); - - return user; - } } diff --git a/apps/server/src/modules/authorization/rule-manager.spec.ts b/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts similarity index 97% rename from apps/server/src/modules/authorization/rule-manager.spec.ts rename to apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts index 0a2b90c7639..78ef313ade1 100644 --- a/apps/server/src/modules/authorization/rule-manager.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts @@ -1,6 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { InternalServerErrorException, NotImplementedException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; +import { courseFactory, setupEntities, userFactory } from '@shared/testing'; +import { AuthorizationContextBuilder } from '../mapper'; import { BoardDoRule, ContextExternalToolRule, @@ -13,10 +15,8 @@ import { TaskRule, TeamRule, UserRule, -} from '@shared/domain/rules'; -import { UserLoginMigrationRule } from '@shared/domain/rules/user-login-migration.rule'; -import { courseFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from './authorization-context.builder'; + UserLoginMigrationRule, +} from '../rules'; import { RuleManager } from './rule-manager'; describe('RuleManager', () => { diff --git a/apps/server/src/modules/authorization/rule-manager.ts b/apps/server/src/modules/authorization/domain/service/rule-manager.ts similarity index 88% rename from apps/server/src/modules/authorization/rule-manager.ts rename to apps/server/src/modules/authorization/domain/service/rule-manager.ts index 3aece68402a..77d09f284c2 100644 --- a/apps/server/src/modules/authorization/rule-manager.ts +++ b/apps/server/src/modules/authorization/domain/service/rule-manager.ts @@ -1,21 +1,21 @@ import { Injectable, InternalServerErrorException, NotImplementedException } from '@nestjs/common'; import { BaseDO, User } from '@shared/domain'; import { AuthorizableObject } from '@shared/domain/domain-object'; // fix import when it is avaible +import type { AuthorizationContext, Rule } from '../type'; import { BoardDoRule, + ContextExternalToolRule, CourseGroupRule, CourseRule, + LegacySchoolRule, LessonRule, SchoolExternalToolRule, - LegacySchoolRule, SubmissionRule, TaskRule, TeamRule, + UserLoginMigrationRule, UserRule, -} from '@shared/domain/rules'; -import { ContextExternalToolRule } from '@shared/domain/rules/context-external-tool.rule'; -import { UserLoginMigrationRule } from '@shared/domain/rules/user-login-migration.rule'; -import { AuthorizationContext, Rule } from './types'; +} from '../rules'; @Injectable() export class RuleManager { diff --git a/apps/server/src/modules/authorization/types/action.enum.ts b/apps/server/src/modules/authorization/domain/type/action.enum.ts similarity index 100% rename from apps/server/src/modules/authorization/types/action.enum.ts rename to apps/server/src/modules/authorization/domain/type/action.enum.ts diff --git a/apps/server/src/modules/authorization/types/allowed-authorization-object-type.enum.ts b/apps/server/src/modules/authorization/domain/type/allowed-authorization-object-type.enum.ts similarity index 100% rename from apps/server/src/modules/authorization/types/allowed-authorization-object-type.enum.ts rename to apps/server/src/modules/authorization/domain/type/allowed-authorization-object-type.enum.ts diff --git a/apps/server/src/modules/authorization/types/authorization-context.interface.ts b/apps/server/src/modules/authorization/domain/type/authorization-context.interface.ts similarity index 100% rename from apps/server/src/modules/authorization/types/authorization-context.interface.ts rename to apps/server/src/modules/authorization/domain/type/authorization-context.interface.ts diff --git a/apps/server/src/modules/authorization/types/authorization-loader-service.ts b/apps/server/src/modules/authorization/domain/type/authorization-loader-service.ts similarity index 100% rename from apps/server/src/modules/authorization/types/authorization-loader-service.ts rename to apps/server/src/modules/authorization/domain/type/authorization-loader-service.ts diff --git a/apps/server/src/modules/authorization/types/index.ts b/apps/server/src/modules/authorization/domain/type/index.ts similarity index 100% rename from apps/server/src/modules/authorization/types/index.ts rename to apps/server/src/modules/authorization/domain/type/index.ts index 92e7b0c8bf5..b1942491098 100644 --- a/apps/server/src/modules/authorization/types/index.ts +++ b/apps/server/src/modules/authorization/domain/type/index.ts @@ -1,5 +1,5 @@ export * from './action.enum'; export * from './authorization-context.interface'; export * from './rule.interface'; -export * from './allowed-authorization-object-type.enum'; export * from './authorization-loader-service'; +export * from './allowed-authorization-object-type.enum'; diff --git a/apps/server/src/modules/authorization/types/rule.interface.ts b/apps/server/src/modules/authorization/domain/type/rule.interface.ts similarity index 100% rename from apps/server/src/modules/authorization/types/rule.interface.ts rename to apps/server/src/modules/authorization/domain/type/rule.interface.ts diff --git a/apps/server/src/modules/authorization/index.ts b/apps/server/src/modules/authorization/index.ts index bee8b7d4bb1..e129df2cd11 100644 --- a/apps/server/src/modules/authorization/index.ts +++ b/apps/server/src/modules/authorization/index.ts @@ -1,5 +1,15 @@ -export * from './authorization.module'; -export * from './authorization.service'; -export * from './authorization-context.builder'; -export * from './types'; -export * from './feathers'; +export { AuthorizationModule } from './authorization.module'; +export { + AuthorizationService, + AuthorizationHelper, + AuthorizationContextBuilder, + ForbiddenLoggableException, + Rule, + AuthorizationContext, + // Action should not be exported, but hard to solve for now. The AuthorizationContextBuilder is the prefared way + Action, + AuthorizationLoaderService, + AuthorizationLoaderServiceGeneric, +} from './domain'; +// Should not used anymore +export { FeathersAuthorizationService } from './feathers'; diff --git a/apps/server/src/modules/board/uc/board.uc.ts b/apps/server/src/modules/board/uc/board.uc.ts index 7c3194916ac..3e39fd32de3 100644 --- a/apps/server/src/modules/board/uc/board.uc.ts +++ b/apps/server/src/modules/board/uc/board.uc.ts @@ -9,8 +9,8 @@ import { EntityId, } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization/authorization.service'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService } from '@src/modules/authorization/domain'; +import { Action } from '@src/modules/authorization'; import { CardService, ColumnBoardService, ColumnService } from '../service'; import { BoardDoAuthorizableService } from '../service/board-do-authorizable.service'; diff --git a/apps/server/src/modules/board/uc/card.uc.ts b/apps/server/src/modules/board/uc/card.uc.ts index 577f3a8b963..170469f0cc4 100644 --- a/apps/server/src/modules/board/uc/card.uc.ts +++ b/apps/server/src/modules/board/uc/card.uc.ts @@ -1,8 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AnyBoardDo, AnyContentElementDo, Card, ContentElementType, EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization/authorization.service'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService, Action } from '@src/modules/authorization'; import { BoardDoAuthorizableService, CardService, ContentElementService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/board/uc/element.uc.ts b/apps/server/src/modules/board/uc/element.uc.ts index e5dc039168c..08357b01798 100644 --- a/apps/server/src/modules/board/uc/element.uc.ts +++ b/apps/server/src/modules/board/uc/element.uc.ts @@ -8,8 +8,7 @@ import { UserRoleEnum, } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService, Action } from '@src/modules/authorization'; import { AnyElementContentBody } from '../controller/dto'; import { BoardDoAuthorizableService, ContentElementService } from '../service'; import { SubmissionItemService } from '../service/submission-item.service'; diff --git a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts index 33bc8468fc9..5d06172acee 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts @@ -9,8 +9,7 @@ import { userFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService, Action } from '@src/modules/authorization'; import { BoardDoAuthorizableService, ContentElementService, SubmissionItemService } from '../service'; import { SubmissionItemUc } from './submission-item.uc'; diff --git a/apps/server/src/modules/board/uc/submission-item.uc.ts b/apps/server/src/modules/board/uc/submission-item.uc.ts index e59afa4b49b..67e7951673f 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.ts @@ -9,8 +9,7 @@ import { UserRoleEnum, } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService, Action } from '@src/modules/authorization'; import { BoardDoAuthorizableService, ContentElementService, SubmissionItemService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/files-storage/README.md b/apps/server/src/modules/files-storage/README.md index 4bb6fb4ccb8..f44be536309 100644 --- a/apps/server/src/modules/files-storage/README.md +++ b/apps/server/src/modules/files-storage/README.md @@ -88,7 +88,7 @@ folder structure in S3 > schoolId/fileRecordId > .trash/schoolId/fileRecordId (see: ## Goals and Ideas > ### Deleting Files) -### Authorisation Module +### Authorization Module The authorisation is solved by parents. Therefore it is required that the parent types must be known to the authentication service. diff --git a/apps/server/src/modules/files-storage/files-storage-api.module.ts b/apps/server/src/modules/files-storage/files-storage-api.module.ts index 9d5283b47b7..aab383a158f 100644 --- a/apps/server/src/modules/files-storage/files-storage-api.module.ts +++ b/apps/server/src/modules/files-storage/files-storage-api.module.ts @@ -2,13 +2,13 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { CoreModule } from '@src/core'; import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; import { FileSecurityController, FilesStorageController } from './controller'; import { FilesStorageModule } from './files-storage.module'; import { FilesStorageUC } from './uc'; @Module({ - imports: [AuthorizationModule, FilesStorageModule, AuthenticationModule, CoreModule, HttpModule], + imports: [AuthorizationReferenceModule, FilesStorageModule, AuthenticationModule, CoreModule, HttpModule], controllers: [FilesStorageController, FileSecurityController], providers: [FilesStorageUC], }) diff --git a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts index 3165ec49021..a26103ae983 100644 --- a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts +++ b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts @@ -1,6 +1,6 @@ import { NotImplementedException } from '@nestjs/common'; import { fileRecordFactory, setupEntities } from '@shared/testing'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { DownloadFileParams, FileRecordListResponse, diff --git a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts index 3d298cd3b2e..9b30acd4ada 100644 --- a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts +++ b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException, StreamableFile } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { plainToClass } from 'class-transformer'; import { DownloadFileParams, diff --git a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts index 5d4ab900549..612558e80c1 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts @@ -8,7 +8,8 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { Action } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { CopyFileResponseBuilder } from '../mapper'; @@ -68,7 +69,7 @@ describe('FilesStorageUC', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeEach(() => { jest.resetAllMocks(); @@ -97,8 +98,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -112,7 +113,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); }); @@ -134,7 +135,7 @@ describe('FilesStorageUC', () => { const fileResponse = CopyFileResponseBuilder.build(targetFile.id, sourceFile.id, targetFile.name); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); filesStorageService.copyFilesOfParent.mockResolvedValueOnce([[fileResponse], 1]); return { sourceParams, targetParams, userId, fileResponse }; @@ -145,7 +146,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.copyFilesOfParent(userId, sourceParams, targetParams); - expect(authorizationService.checkPermissionByReferences).toHaveBeenNthCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenNthCalledWith( 1, userId, sourceParams.parentType, @@ -159,7 +160,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.copyFilesOfParent(userId, sourceParams, targetParams); - expect(authorizationService.checkPermissionByReferences).toHaveBeenNthCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenNthCalledWith( 2, userId, targetParams.target.parentType, @@ -191,7 +192,7 @@ describe('FilesStorageUC', () => { const targetParams = createTargetParams(); const error = new ForbiddenException(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error).mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error).mockResolvedValueOnce(); return { sourceParams, targetParams, userId, error }; }; @@ -210,7 +211,7 @@ describe('FilesStorageUC', () => { const targetParams = createTargetParams(); const error = new ForbiddenException(); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockRejectedValueOnce(error); return { sourceParams, targetParams, userId, error }; }; @@ -229,7 +230,9 @@ describe('FilesStorageUC', () => { const targetParams = createTargetParams(); const error = new ForbiddenException(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error).mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error); return { sourceParams, targetParams, userId, error }; }; @@ -249,7 +252,7 @@ describe('FilesStorageUC', () => { const error = new Error('test'); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); filesStorageService.copyFilesOfParent.mockRejectedValueOnce(error); return { sourceParams, targetParams, userId, error }; @@ -289,7 +292,7 @@ describe('FilesStorageUC', () => { ); filesStorageService.getFileRecord.mockResolvedValue(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); filesStorageService.copy.mockResolvedValueOnce([fileResponse]); return { singleFileParams, copyFileParams, userId, fileResponse, fileRecord }; @@ -308,7 +311,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.copyOneFile(userId, singleFileParams, copyFileParams); - expect(authorizationService.checkPermissionByReferences).toHaveBeenNthCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenNthCalledWith( 1, userId, fileRecord.parentType, @@ -322,7 +325,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.copyOneFile(userId, singleFileParams, copyFileParams); - expect(authorizationService.checkPermissionByReferences).toHaveBeenNthCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenNthCalledWith( 2, userId, copyFileParams.target.parentType, @@ -355,7 +358,7 @@ describe('FilesStorageUC', () => { const error = new ForbiddenException(); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error).mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error).mockResolvedValueOnce(); return { singleFileParams, copyFileParams, userId, fileRecord, error }; }; @@ -375,7 +378,7 @@ describe('FilesStorageUC', () => { const error = new ForbiddenException(); filesStorageService.getFileRecord.mockResolvedValue(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockRejectedValueOnce(error); return { singleFileParams, copyFileParams, userId, fileRecord, error }; }; @@ -395,7 +398,9 @@ describe('FilesStorageUC', () => { const error = new ForbiddenException(); filesStorageService.getFileRecord.mockResolvedValue(fileRecord); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error).mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error); return { singleFileParams, copyFileParams, userId, fileRecord, error }; }; @@ -434,7 +439,7 @@ describe('FilesStorageUC', () => { const error = new Error('test'); filesStorageService.getFileRecord.mockResolvedValue(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce().mockResolvedValueOnce(); filesStorageService.copy.mockRejectedValueOnce(error); return { singleFileParams, copyFileParams, userId, fileRecord, error }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts index eb13f830be6..fc461a50106 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts @@ -8,7 +8,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -57,7 +57,7 @@ describe('FilesStorageUC delete methods', () => { let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; let previewService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -82,8 +82,8 @@ describe('FilesStorageUC delete methods', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -97,7 +97,7 @@ describe('FilesStorageUC delete methods', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); previewService = module.get(PreviewService); }); @@ -122,7 +122,7 @@ describe('FilesStorageUC delete methods', () => { const fileRecord = fileRecords[0]; const mockedResult = [[fileRecord], 0] as Counted; - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.deleteFilesOfParent.mockResolvedValueOnce(mockedResult); return { params, userId, mockedResult, requestParams, fileRecord }; @@ -134,7 +134,7 @@ describe('FilesStorageUC delete methods', () => { await filesStorageUC.deleteFilesOfParent(userId, requestParams); - expect(authorizationService.checkPermissionByReferences).toBeCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toBeCalledWith( userId, allowedType, requestParams.parentId, @@ -171,7 +171,7 @@ describe('FilesStorageUC delete methods', () => { const setup = () => { const { requestParams, userId } = createParams(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); return { requestParams, userId }; }; @@ -192,7 +192,7 @@ describe('FilesStorageUC delete methods', () => { const { requestParams, userId } = createParams(); const error = new Error('test'); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.deleteFilesOfParent.mockRejectedValueOnce(error); return { requestParams, userId, error }; @@ -214,7 +214,7 @@ describe('FilesStorageUC delete methods', () => { const requestParams = { fileRecordId: fileRecord.id, parentType: fileRecord.parentType }; filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.delete.mockResolvedValueOnce(); return { requestParams, userId, fileRecord }; @@ -227,7 +227,7 @@ describe('FilesStorageUC delete methods', () => { const allowedType = FilesStorageMapper.mapToAllowedAuthorizationEntityType(requestParams.parentType); - expect(authorizationService.checkPermissionByReferences).toBeCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toBeCalledWith( userId, allowedType, fileRecord.parentId, @@ -301,7 +301,7 @@ describe('FilesStorageUC delete methods', () => { const requestParams = { fileRecordId: fileRecord.id, parentType: fileRecord.parentType }; filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); return { requestParams, userId }; }; @@ -322,7 +322,7 @@ describe('FilesStorageUC delete methods', () => { const error = new Error('test'); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.delete.mockRejectedValueOnce(error); return { requestParams, userId, error }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts index 3a2f6f1ac21..795939e5cb2 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -43,7 +43,7 @@ describe('FilesStorageUC', () => { let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; let previewService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -72,8 +72,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -83,7 +83,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); previewService = module.get(PreviewService); }); @@ -143,7 +143,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.downloadPreview(userId, fileDownloadParams, previewParams); const allowedType = FilesStorageMapper.mapToAllowedAuthorizationEntityType(fileRecord.parentType); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, allowedType, fileRecord.parentId, @@ -190,7 +190,7 @@ describe('FilesStorageUC', () => { filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); const error = new ForbiddenException(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error); return { fileDownloadParams, userId, fileRecord, previewParams, error }; }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts index b1aa6d4b437..3e7fa61fd7f 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -34,7 +34,7 @@ describe('FilesStorageUC', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -59,8 +59,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -74,7 +74,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); }); @@ -99,7 +99,7 @@ describe('FilesStorageUC', () => { const fileResponse = createMock(); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValue(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValue(); filesStorageService.download.mockResolvedValueOnce(fileResponse); return { fileDownloadParams, userId, fileRecord, fileResponse }; @@ -121,7 +121,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.download(userId, fileDownloadParams); const allowedType = FilesStorageMapper.mapToAllowedAuthorizationEntityType(fileRecord.parentType); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, allowedType, fileRecord.parentId, @@ -171,7 +171,7 @@ describe('FilesStorageUC', () => { const error = new ForbiddenException(); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error); return { fileDownloadParams, userId, fileRecord }; }; @@ -190,7 +190,7 @@ describe('FilesStorageUC', () => { const error = new Error('test'); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValue(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValue(); filesStorageService.download.mockRejectedValueOnce(error); return { fileDownloadParams, userId, error }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts index 02cdb82ded6..7f372a1fe80 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts @@ -6,7 +6,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -37,7 +37,7 @@ describe('FilesStorageUC', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -62,8 +62,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -77,7 +77,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); }); @@ -100,7 +100,7 @@ describe('FilesStorageUC', () => { const { fileRecords, params } = buildFileRecordsWithParams(); filesStorageService.getFileRecordsOfParent.mockResolvedValueOnce([fileRecords, fileRecords.length]); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); return { userId, params, fileRecords }; }; @@ -110,7 +110,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.getFileRecordsOfParent(userId, params); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, params.parentType, params.parentId, @@ -141,7 +141,7 @@ describe('FilesStorageUC', () => { const { fileRecords, params } = buildFileRecordsWithParams(); filesStorageService.getFileRecordsOfParent.mockResolvedValueOnce([fileRecords, fileRecords.length]); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(new Error('Bla')); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(new Error('Bla')); return { userId, params, fileRecords }; }; @@ -160,7 +160,7 @@ describe('FilesStorageUC', () => { const fileRecords = []; filesStorageService.getFileRecordsOfParent.mockResolvedValueOnce([fileRecords, fileRecords.length]); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); return { userId, params, fileRecords }; }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts index be8a6d32561..e01e3116b79 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { FileRecordParams, SingleFileParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -52,7 +52,7 @@ describe('FilesStorageUC', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -77,8 +77,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -92,7 +92,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); }); @@ -113,7 +113,7 @@ describe('FilesStorageUC', () => { const setup = () => { const { params, userId, fileRecords } = buildFileRecordsWithParams(); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.restoreFilesOfParent.mockResolvedValueOnce([fileRecords, fileRecords.length]); return { params, userId, fileRecords }; @@ -125,7 +125,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.restoreFilesOfParent(userId, params); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, allowedType, params.parentId, @@ -153,7 +153,7 @@ describe('FilesStorageUC', () => { describe('WHEN user is not authorised ', () => { const setup = () => { const { params, userId } = buildFileRecordsWithParams(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); return { params, userId }; }; @@ -189,7 +189,7 @@ describe('FilesStorageUC', () => { const { params, userId, fileRecord } = buildFileRecordWithParams(); filesStorageService.getFileRecordMarkedForDelete.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.restore.mockResolvedValueOnce(); return { params, userId, fileRecord }; @@ -209,7 +209,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.restoreOneFile(userId, params); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, allowedType, fileRecord.parentId, @@ -239,7 +239,7 @@ describe('FilesStorageUC', () => { const { params, userId, fileRecord } = buildFileRecordWithParams(); filesStorageService.getFileRecordMarkedForDelete.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); return { params, userId }; }; @@ -276,7 +276,7 @@ describe('FilesStorageUC', () => { const error = new Error('test'); filesStorageService.getFileRecordMarkedForDelete.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.restore.mockRejectedValueOnce(error); return { params, userId, error }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts index 57ec96cff61..19d9984eea8 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts @@ -6,7 +6,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { RenameFileParams, ScanResultParams, SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -31,7 +31,7 @@ describe('FilesStorageUC', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -56,8 +56,8 @@ describe('FilesStorageUC', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -71,7 +71,7 @@ describe('FilesStorageUC', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); filesStorageService = module.get(FilesStorageService); }); @@ -137,7 +137,7 @@ describe('FilesStorageUC', () => { const data: RenameFileParams = { fileName: 'test_new_name.txt' }; filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - authorizationService.checkPermissionByReferences.mockResolvedValueOnce(); + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); filesStorageService.patchFilename.mockResolvedValueOnce(fileRecord); return { userId, params, fileRecord, data }; @@ -155,7 +155,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.patchFilename(userId, params, data); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, fileRecord.parentType, fileRecord.parentId, diff --git a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts index 903a2f2a6a6..ed7defb54fb 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts @@ -8,7 +8,8 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { AxiosHeadersKeyValue, axiosResponseFactory, fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { Action } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { Request } from 'express'; import { of } from 'rxjs'; @@ -72,7 +73,7 @@ describe('FilesStorageUC upload methods', () => { let module: TestingModule; let filesStorageUC: FilesStorageUC; let filesStorageService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationReferenceService: DeepMocked; let httpService: DeepMocked; beforeAll(async () => { @@ -98,8 +99,8 @@ describe('FilesStorageUC upload methods', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: HttpService, @@ -113,7 +114,7 @@ describe('FilesStorageUC upload methods', () => { }).compile(); filesStorageUC = module.get(FilesStorageUC); - authorizationService = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); httpService = module.get(HttpService); filesStorageService = module.get(FilesStorageService); }); @@ -171,7 +172,7 @@ describe('FilesStorageUC upload methods', () => { await filesStorageUC.uploadFromUrl(userId, uploadFromUrlParams); - expect(authorizationService.checkPermissionByReferences).toBeCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toBeCalledWith( userId, uploadFromUrlParams.parentType, uploadFromUrlParams.parentId, @@ -218,7 +219,7 @@ describe('FilesStorageUC upload methods', () => { const setup = () => { const { userId, uploadFromUrlParams } = createUploadFromUrlParams(); const error = new Error('test'); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error); return { uploadFromUrlParams, userId, error }; }; @@ -300,7 +301,7 @@ describe('FilesStorageUC upload methods', () => { await filesStorageUC.upload(userId, params, request); const allowedType = FilesStorageMapper.mapToAllowedAuthorizationEntityType(params.parentType); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( userId, allowedType, params.parentId, @@ -365,7 +366,7 @@ describe('FilesStorageUC upload methods', () => { const request = createRequest(); const error = new ForbiddenException(); - authorizationService.checkPermissionByReferences.mockRejectedValueOnce(error); + authorizationReferenceService.checkPermissionByReferences.mockRejectedValueOnce(error); return { params, userId, request, error }; }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts index fa6a27202de..f5e6d372a6b 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts @@ -2,7 +2,8 @@ import { HttpService } from '@nestjs/axios'; import { Injectable, NotFoundException } from '@nestjs/common'; import { Counted, EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContext } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import busboy from 'busboy'; import { Request } from 'express'; @@ -32,7 +33,7 @@ import { PreviewService } from '../service/preview.service'; export class FilesStorageUC { constructor( private logger: LegacyLogger, - private readonly authorizationService: AuthorizationService, + private readonly authorizationReferenceService: AuthorizationReferenceService, private readonly httpService: HttpService, private readonly filesStorageService: FilesStorageService, private readonly previewService: PreviewService @@ -47,7 +48,7 @@ export class FilesStorageUC { context: AuthorizationContext ) { const allowedType = FilesStorageMapper.mapToAllowedAuthorizationEntityType(parentType); - await this.authorizationService.checkPermissionByReferences(userId, allowedType, parentId, context); + await this.authorizationReferenceService.checkPermissionByReferences(userId, allowedType, parentId, context); } // upload diff --git a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts index ed62a2b6ade..3b9f04e07db 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts @@ -1,5 +1,4 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; import { EntityManager } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -16,11 +15,15 @@ import { import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; import { Request } from 'express'; import request from 'supertest'; +import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { createMock } from '@golevelup/ts-jest'; +// config must be set outside before the server module is importat, otherwise the configuration is already set +const configBefore = Configuration.toObject({ plainSecrets: true }); Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); Configuration.set('INCOMING_REQUEST_TIMEOUT_COPY_API', 1); // eslint-disable-next-line import/first -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@src/modules/server'; // This needs to be in a separate test file because of the above configuration. // When we find a way to mock the config, it should be moved alongside the other API tests. @@ -28,10 +31,8 @@ describe('Rooms copy (API)', () => { let app: INestApplication; let em: EntityManager; let currentUser: ICurrentUser; - let configBefore: IConfig; beforeAll(async () => { - configBefore = Configuration.toObject({ plainSecrets: true }); const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ServerTestModule], }) @@ -43,6 +44,8 @@ describe('Rooms copy (API)', () => { return true; }, }) + .overrideProvider(FilesStorageClientAdapterService) + .useValue(createMock()) .compile(); app = moduleFixture.createNestApplication(); diff --git a/apps/server/src/modules/learnroom/index.ts b/apps/server/src/modules/learnroom/index.ts index f2dc136ce5e..e4d907784d5 100644 --- a/apps/server/src/modules/learnroom/index.ts +++ b/apps/server/src/modules/learnroom/index.ts @@ -1,2 +1,3 @@ export * from './learnroom.module'; export * from './service/course-copy.service'; +export { CourseService } from './service'; diff --git a/apps/server/src/modules/learnroom/learnroom-api.module.ts b/apps/server/src/modules/learnroom/learnroom-api.module.ts index b72db2d7f59..81a514a0a7b 100644 --- a/apps/server/src/modules/learnroom/learnroom-api.module.ts +++ b/apps/server/src/modules/learnroom/learnroom-api.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, LessonRepo, UserRepo } from '@shared/repo'; import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; import { CopyHelperModule } from '@src/modules/copy-helper'; import { LessonModule } from '@src/modules/lesson'; import { CourseController } from './controller/course.controller'; @@ -20,7 +21,7 @@ import { } from './uc'; @Module({ - imports: [AuthorizationModule, LessonModule, CopyHelperModule, LearnroomModule], + imports: [AuthorizationModule, LessonModule, CopyHelperModule, LearnroomModule, AuthorizationReferenceModule], controllers: [DashboardController, CourseController, RoomsController], providers: [ DashboardUc, diff --git a/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts b/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts index 2e7e9f739ad..33beee8c4db 100644 --- a/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts @@ -3,9 +3,9 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ForbiddenException, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { boardFactory, courseFactory, setupEntities, userFactory } from '@shared/testing'; -import { Action, AuthorizableReferenceType } from '@src/modules/authorization'; -import { AuthorizationService } from '@src/modules/authorization/authorization.service'; +import { courseFactory, setupEntities, userFactory } from '@shared/testing'; +import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { CopyElementType, CopyStatusEnum } from '@src/modules/copy-helper'; import { CourseCopyService } from '../service'; import { CourseCopyUC } from './course-copy.uc'; @@ -13,7 +13,7 @@ import { CourseCopyUC } from './course-copy.uc'; describe('course copy uc', () => { let module: TestingModule; let uc: CourseCopyUC; - let authorization: DeepMocked; + let authorization: DeepMocked; let courseCopyService: DeepMocked; beforeAll(async () => { @@ -22,8 +22,8 @@ describe('course copy uc', () => { providers: [ CourseCopyUC, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: CourseCopyService, @@ -33,7 +33,7 @@ describe('course copy uc', () => { }).compile(); uc = module.get(CourseCopyUC); - authorization = module.get(AuthorizationService); + authorization = module.get(AuthorizationReferenceService); courseCopyService = module.get(CourseCopyService); }); @@ -41,91 +41,99 @@ describe('course copy uc', () => { await module.close(); }); - beforeEach(() => { - Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); - }); + // Please be careful the Configuration.set is effects all tests !!! describe('copy course', () => { - const setup = () => { - const user = userFactory.buildWithId(); - const allCourses = courseFactory.buildList(3, { teachers: [user] }); - const course = allCourses[0]; - const originalBoard = boardFactory.build({ course }); - const courseCopy = courseFactory.buildWithId({ teachers: [user] }); - const boardCopy = boardFactory.build({ course: courseCopy }); - - authorization.getUserWithPermissions.mockResolvedValue(user); - const status = { - title: 'courseCopy', - type: CopyElementType.COURSE, - status: CopyStatusEnum.SUCCESS, - copyEntity: courseCopy, + describe('when authorization to course resolve with void and feature is deactivated', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', false); + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId({ teachers: [user] }); + + return { + userId: user.id, + courseId: course.id, + }; }; - courseCopyService.copyCourse.mockResolvedValue(status); + it('should throw if copy feature is deactivated', async () => { + const { courseId, userId } = setup(); + + await expect(uc.copyCourse(userId, courseId)).rejects.toThrowError( + new InternalServerErrorException('Copy Feature not enabled') + ); + }); + }); - return { - user, - course, - originalBoard, - courseCopy, - boardCopy, - allCourses, - status, + describe('when authorization to course resolve with void and feature is activated', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId({ teachers: [user] }); + const courseCopy = courseFactory.buildWithId({ teachers: [user] }); + + const status = { + title: 'courseCopy', + type: CopyElementType.COURSE, + status: CopyStatusEnum.SUCCESS, + copyEntity: courseCopy, + }; + + authorization.checkPermissionByReferences.mockResolvedValueOnce(); + courseCopyService.copyCourse.mockResolvedValueOnce(status); + + return { + userId: user.id, + courseId: course.id, + status, + }; }; - }; - it('should throw if copy feature is deactivated', async () => { - Configuration.set('FEATURE_COPY_SERVICE_ENABLED', false); - const { course, user } = setup(); - await expect(uc.copyCourse(user.id, course.id)).rejects.toThrowError(InternalServerErrorException); - }); + it('should check permission to create a course', async () => { + const { courseId, userId } = setup(); - it('should check permission to create a course', async () => { - const { course, user } = setup(); - await uc.copyCourse(user.id, course.id); - expect(authorization.checkPermissionByReferences).toBeCalledWith( - user.id, - AuthorizableReferenceType.Course, - course.id, - { - action: Action.write, - requiredPermissions: [Permission.COURSE_CREATE], - } - ); - }); + await uc.copyCourse(userId, courseId); - it('should call course copy service', async () => { - const { course, user } = setup(); - await uc.copyCourse(user.id, course.id); - expect(courseCopyService.copyCourse).toBeCalledWith({ userId: user.id, courseId: course.id }); - }); + const context = AuthorizationContextBuilder.write([Permission.COURSE_CREATE]); + expect(authorization.checkPermissionByReferences).toBeCalledWith( + userId, + AuthorizableReferenceType.Course, + courseId, + context + ); + }); + + it('should call course copy service', async () => { + const { courseId, userId } = setup(); + + await uc.copyCourse(userId, courseId); + + expect(courseCopyService.copyCourse).toBeCalledWith({ userId, courseId }); + }); + + it('should return status', async () => { + const { courseId, userId, status } = setup(); + + const result = await uc.copyCourse(userId, courseId); - it('should return status', async () => { - const { course, user, status } = setup(); - const result = await uc.copyCourse(user.id, course.id); - expect(result).toEqual(status); + expect(result).toEqual(status); + }); }); - describe('when access to course is forbidden', () => { + describe('when authorization to course throw a forbidden exception', () => { const setupWithCourseForbidden = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); - authorization.checkPermissionByReferences.mockImplementation(() => { - throw new ForbiddenException(); - }); + authorization.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + return { user, course }; }; it('should throw ForbiddenException', async () => { const { course, user } = setupWithCourseForbidden(); - try { - await uc.copyCourse(user.id, course.id); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(ForbiddenException); - } + await expect(uc.copyCourse(user.id, course.id)).rejects.toThrowError(new ForbiddenException()); }); }); }); diff --git a/apps/server/src/modules/learnroom/uc/course-copy.uc.ts b/apps/server/src/modules/learnroom/uc/course-copy.uc.ts index 0d806c36263..0f700d57f17 100644 --- a/apps/server/src/modules/learnroom/uc/course-copy.uc.ts +++ b/apps/server/src/modules/learnroom/uc/course-copy.uc.ts @@ -1,24 +1,23 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { CopyStatus } from '@src/modules/copy-helper'; import { CourseCopyService } from '../service'; @Injectable() export class CourseCopyUC { constructor( - private readonly authorization: AuthorizationService, + private readonly authorization: AuthorizationReferenceService, private readonly courseCopyService: CourseCopyService ) {} async copyCourse(userId: EntityId, courseId: EntityId): Promise { this.checkFeatureEnabled(); - await this.authorization.checkPermissionByReferences(userId, AuthorizableReferenceType.Course, courseId, { - action: Action.write, - requiredPermissions: [Permission.COURSE_CREATE], - }); + const context = AuthorizationContextBuilder.write([Permission.COURSE_CREATE]); + await this.authorization.checkPermissionByReferences(userId, AuthorizableReferenceType.Course, courseId, context); const result = await this.courseCopyService.copyCourse({ userId, courseId }); @@ -26,6 +25,7 @@ export class CourseCopyUC { } private checkFeatureEnabled() { + // @hpi-schul-cloud/commons is deprecated way to get envirements const enabled = Configuration.get('FEATURE_COPY_SERVICE_ENABLED') as boolean; if (!enabled) { throw new InternalServerErrorException('Copy Feature not enabled'); diff --git a/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts b/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts index 3d93827f06d..04e3d0de480 100644 --- a/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts @@ -1,7 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { CommonCartridgeExportService } from '@src/modules/learnroom/service/common-cartridge-export.service'; -import { AuthorizationService } from '@src/modules'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { ObjectId } from 'bson'; +import { ForbiddenException } from '@nestjs/common'; import { CourseExportUc } from './course-export.uc'; import { CommonCartridgeVersion } from '../common-cartridge'; @@ -9,7 +11,7 @@ describe('CourseExportUc', () => { let module: TestingModule; let courseExportUc: CourseExportUc; let courseExportServiceMock: DeepMocked; - let authorizationServiceMock: DeepMocked; + let authorizationServiceMock: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -20,33 +22,86 @@ describe('CourseExportUc', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, ], }).compile(); courseExportUc = module.get(CourseExportUc); courseExportServiceMock = module.get(CommonCartridgeExportService); - authorizationServiceMock = module.get(AuthorizationService); + authorizationServiceMock = module.get(AuthorizationReferenceService); }); afterAll(async () => { await module.close(); }); + afterEach(() => { + // is needed to solve buffer test isolation + jest.resetAllMocks(); + }); + describe('exportCourse', () => { - const version: CommonCartridgeVersion = CommonCartridgeVersion.V_1_1_0; - it('should check for permissions', async () => { - authorizationServiceMock.checkPermissionByReferences.mockResolvedValueOnce(); - await expect(courseExportUc.exportCourse('', '', version)).resolves.not.toThrow(); - expect(authorizationServiceMock.checkPermissionByReferences).toBeCalledTimes(1); + const setupParams = () => { + const courseId = new ObjectId().toHexString(); + const userId = new ObjectId().toHexString(); + const version: CommonCartridgeVersion = CommonCartridgeVersion.V_1_1_0; + + return { version, userId, courseId }; + }; + + describe('when authorization throw a error', () => { + const setup = () => { + authorizationServiceMock.checkPermissionByReferences.mockRejectedValueOnce(new ForbiddenException()); + courseExportServiceMock.exportCourse.mockResolvedValueOnce(Buffer.from('')); + + return setupParams(); + }; + + it('should pass this error', async () => { + const { courseId, userId, version } = setup(); + + await expect(courseExportUc.exportCourse(courseId, userId, version)).rejects.toThrowError( + new ForbiddenException() + ); + }); + }); + + describe('when course export service throw a error', () => { + const setup = () => { + authorizationServiceMock.checkPermissionByReferences.mockResolvedValueOnce(); + courseExportServiceMock.exportCourse.mockRejectedValueOnce(new Error()); + + return setupParams(); + }; + + it('should pass this error', async () => { + const { courseId, userId, version } = setup(); + + await expect(courseExportUc.exportCourse(courseId, userId, version)).rejects.toThrowError(new Error()); + }); }); - it('should return a binary file as buffer', async () => { - courseExportServiceMock.exportCourse.mockResolvedValueOnce(Buffer.from('')); - authorizationServiceMock.checkPermissionByReferences.mockResolvedValueOnce(); + describe('when authorization resolve', () => { + const setup = () => { + authorizationServiceMock.checkPermissionByReferences.mockResolvedValueOnce(); + courseExportServiceMock.exportCourse.mockResolvedValueOnce(Buffer.from('')); + + return setupParams(); + }; + + it('should check for permissions', async () => { + const { courseId, userId, version } = setup(); + + await expect(courseExportUc.exportCourse(courseId, userId, version)).resolves.not.toThrow(); + expect(authorizationServiceMock.checkPermissionByReferences).toBeCalledTimes(1); + }); + + it('should return a binary file as buffer', async () => { + const { courseId, userId, version } = setup(); - await expect(courseExportUc.exportCourse('', '', version)).resolves.toBeInstanceOf(Buffer); + await expect(courseExportUc.exportCourse(courseId, userId, version)).resolves.toBeInstanceOf(Buffer); + }); }); }); }); diff --git a/apps/server/src/modules/learnroom/uc/course-export.uc.ts b/apps/server/src/modules/learnroom/uc/course-export.uc.ts index 418812e0cd8..07e427c8fa8 100644 --- a/apps/server/src/modules/learnroom/uc/course-export.uc.ts +++ b/apps/server/src/modules/learnroom/uc/course-export.uc.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { CommonCartridgeVersion } from '../common-cartridge'; import { CommonCartridgeExportService } from '../service/common-cartridge-export.service'; @@ -8,14 +9,18 @@ import { CommonCartridgeExportService } from '../service/common-cartridge-export export class CourseExportUc { constructor( private readonly courseExportService: CommonCartridgeExportService, - private readonly authorizationService: AuthorizationService + private readonly authorizationService: AuthorizationReferenceService ) {} async exportCourse(courseId: EntityId, userId: EntityId, version: CommonCartridgeVersion): Promise { - await this.authorizationService.checkPermissionByReferences(userId, AuthorizableReferenceType.Course, courseId, { - action: Action.read, - requiredPermissions: [Permission.COURSE_EDIT], - }); + const context = AuthorizationContextBuilder.read([Permission.COURSE_EDIT]); + await this.authorizationService.checkPermissionByReferences( + userId, + AuthorizableReferenceType.Course, + courseId, + context + ); + return this.courseExportService.exportCourse(courseId, userId, version); } } diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts index a00e0be6c26..34d73449b4c 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts @@ -3,13 +3,12 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ObjectId } from '@mikro-orm/mongodb'; import { ForbiddenException, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { BaseDO, Permission, User } from '@shared/domain'; +import { Permission } from '@shared/domain'; import { CourseRepo, LessonRepo, UserRepo } from '@shared/repo'; import { courseFactory, lessonFactory, setupEntities, userFactory } from '@shared/testing'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; import { EtherpadService, LessonCopyService } from '@src/modules/lesson/service'; -import { AuthorizableObject } from '@shared/domain/domain-object'; import { LessonCopyUC } from './lesson-copy.uc'; describe('lesson copy uc', () => { @@ -71,193 +70,286 @@ describe('lesson copy uc', () => { copyHelperService = module.get(CopyHelperService); }); - beforeEach(() => { - Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + afterEach(() => { jest.resetAllMocks(); }); + // Please be careful the Configuration.set is effects all tests !!! + describe('copy lesson', () => { - const setup = () => { - const user = userFactory.buildWithId(); - const course = courseFactory.buildWithId({ teachers: [user] }); - const allLessons = lessonFactory.buildList(3, { course }); - const lesson = allLessons[0]; - - authorisation.getUserWithPermissions.mockResolvedValue(user); - lessonRepo.findById.mockResolvedValue(lesson); - lessonRepo.findAllByCourseIds.mockResolvedValue([allLessons, allLessons.length]); - lessonRepo.save.mockResolvedValue(undefined); - - courseRepo.findById.mockResolvedValue(course); - authorisation.hasPermission.mockReturnValue(true); - const copy = lessonFactory.buildWithId({ course }); - const status = { - title: 'lessonCopy', - type: CopyElementType.LESSON, - status: CopyStatusEnum.SUCCESS, - copyEntity: copy, - }; - lessonCopyService.copyLesson.mockResolvedValue(status); - lessonCopyService.updateCopiedEmbeddedTasks.mockReturnValue(status); - const lessonCopyName = 'Copy'; - copyHelperService.deriveCopyName.mockReturnValue(lessonCopyName); - - return { - user, - course, - lesson, - copy, - status, - lessonCopyName, - allLessons, - userId: user.id, + // missing tests + // when course repo is throw a error + // when lesson repo is throw a error + describe('when feature flag is disabled', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', false); + + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId({ teachers: [user] }); + const lesson = lessonFactory.build({ course }); + + const parentParams = { courseId: course.id, userId: user.id }; + + return { + userId: user.id, + lessonId: lesson.id, + parentParams, + }; }; - }; - - it('should throw if copy feature is deactivated', async () => { - Configuration.set('FEATURE_COPY_SERVICE_ENABLED', false); - const { course, user, lesson, userId } = setup(); - await expect(uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId })).rejects.toThrowError( - InternalServerErrorException - ); - }); - it('should fetch correct user', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(authorisation.getUserWithPermissions).toBeCalledWith(user.id); - }); + it('should throw if copy feature is deactivated', async () => { + const { userId, lessonId, parentParams } = setup(); - it('should fetch correct lesson', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(lessonRepo.findById).toBeCalledWith(lesson.id); + await expect(uc.copyLesson(userId, lessonId, parentParams)).rejects.toThrowError( + new InternalServerErrorException('Copy Feature not enabled') + ); + }); }); - it('should fetch destination course', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(courseRepo.findById).toBeCalledWith(course.id); - }); + describe('when authorization resolve and no destination course is passed', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); - it('should pass without destination course', async () => { - const { user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { userId }); - expect(courseRepo.findById).not.toHaveBeenCalled(); - }); + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId({ teachers: [user] }); + const allLessons = lessonFactory.buildList(3, { course }); + const copy = lessonFactory.buildWithId({ course }); + + const lesson = allLessons[0]; + const status = { + title: 'lessonCopy', + type: CopyElementType.LESSON, + status: CopyStatusEnum.SUCCESS, + copyEntity: copy, + }; + const lessonCopyName = 'Copy'; + const parentParams = { userId: user.id }; + + authorisation.getUserWithPermissions.mockResolvedValueOnce(user); + authorisation.hasPermission.mockReturnValue(true); + + lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonRepo.findAllByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); + courseRepo.findById.mockResolvedValueOnce(course); + + lessonCopyService.copyLesson.mockResolvedValueOnce(status); + copyHelperService.deriveCopyName.mockReturnValueOnce(lessonCopyName); + + return { + user, + userId: user.id, + course, + courseId: course.id, + lessonId: lesson.id, + parentParams, + }; + }; + + it('should pass without destination course', async () => { + const { lessonId, userId, parentParams } = setup(); - it('should check authorisation for lesson', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(authorisation.hasPermission).toBeCalledWith(user, lesson, { - action: Action.read, - requiredPermissions: [Permission.TOPIC_CREATE], + await uc.copyLesson(userId, lessonId, parentParams); + + expect(courseRepo.findById).not.toHaveBeenCalled(); }); - }); - it('should check authorisation for destination course', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(authorisation.checkPermissionByReferences).toBeCalledWith( - user.id, - AuthorizableReferenceType.Course, - course.id, - { - action: Action.write, - requiredPermissions: [], - } - ); - }); + it('should pass authorisation check without destination course', async () => { + const { course, user, lessonId, userId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); - it('should pass authorisation check without destination course', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { userId }); - expect(authorisation.hasPermission).not.toBeCalledWith(user, course, { - action: Action.write, - requiredPermissions: [], + const context = AuthorizationContextBuilder.write([]); + expect(authorisation.hasPermission).not.toBeCalledWith(user, course, context); }); }); - it('should call copy service', async () => { - const { course, user, lesson, lessonCopyName, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(lessonCopyService.copyLesson).toBeCalledWith({ - originalLessonId: lesson.id, - destinationCourse: course, - user, - copyName: lessonCopyName, + describe('when authorization resolve', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId({ teachers: [user] }); + const allLessons = lessonFactory.buildList(3, { course }); + const copy = lessonFactory.buildWithId({ course }); + + const lesson = allLessons[0]; + const status = { + title: 'lessonCopy', + type: CopyElementType.LESSON, + status: CopyStatusEnum.SUCCESS, + copyEntity: copy, + }; + const lessonCopyName = 'Copy'; + const parentParams = { courseId: course.id, userId: user.id }; + + authorisation.getUserWithPermissions.mockResolvedValueOnce(user); + authorisation.hasPermission.mockReturnValue(true); + + lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonRepo.findAllByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); + courseRepo.findById.mockResolvedValueOnce(course); + + lessonCopyService.copyLesson.mockResolvedValueOnce(status); + // lessonCopyService.updateCopiedEmbeddedTasks.mockReturnValue(status); + copyHelperService.deriveCopyName.mockReturnValueOnce(lessonCopyName); + + return { + user, + userId: user.id, + course, + courseId: course.id, + lesson, + lessonId: lesson.id, + parentParams, + copy, + status, + lessonCopyName, + allLessons, + }; + }; + + it('should fetch correct user', async () => { + const { lessonId, userId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + expect(authorisation.getUserWithPermissions).toBeCalledWith(userId); }); - }); - it('should return status', async () => { - const { course, user, lesson, status, userId } = setup(); - const result = await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(result).toEqual(status); - }); + it('should fetch correct lesson', async () => { + const { lessonId, userId, parentParams } = setup(); - it('should use copyHelperService', async () => { - const { course, user, lesson, allLessons, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - const existingNames = allLessons.map((l) => l.name); - expect(copyHelperService.deriveCopyName).toHaveBeenCalledWith(lesson.name, existingNames); - }); + await uc.copyLesson(userId, lessonId, parentParams); + + expect(lessonRepo.findById).toBeCalledWith(lessonId); + }); + + it('should fetch destination course', async () => { + const { course, lessonId, userId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + expect(courseRepo.findById).toBeCalledWith(course.id); + }); + + it('should check authorisation for lesson', async () => { + const { lessonId, userId, user, lesson, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + const context = AuthorizationContextBuilder.read([Permission.TOPIC_CREATE]); + expect(authorisation.hasPermission).toBeCalledWith(user, lesson, context); + }); + + it('should check authorisation for destination course', async () => { + const { course, user, lessonId, userId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + const context = AuthorizationContextBuilder.write([]); + expect(authorisation.checkPermission).toBeCalledWith(user, course, context); + }); + + it('should call copy service', async () => { + const { course, user, lessonId, lessonCopyName, userId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + expect(lessonCopyService.copyLesson).toBeCalledWith({ + originalLessonId: lessonId, + destinationCourse: course, + user, + copyName: lessonCopyName, + }); + }); - it('should use findAllByCourseIds to determine existing lesson names', async () => { - const { course, user, lesson, userId } = setup(); - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId }); - expect(lessonRepo.findAllByCourseIds).toHaveBeenCalledWith([course.id]); + it('should return status', async () => { + const { lessonId, status, userId, parentParams } = setup(); + + const result = await uc.copyLesson(userId, lessonId, parentParams); + + expect(result).toEqual(status); + }); + + it('should use copyHelperService', async () => { + const { lessonId, allLessons, userId, lesson, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + const existingNames = allLessons.map((l) => l.name); + expect(copyHelperService.deriveCopyName).toHaveBeenCalledWith(lesson.name, existingNames); + }); + + it('should use findAllByCourseIds to determine existing lesson names', async () => { + const { courseId, userId, lessonId, parentParams } = setup(); + + await uc.copyLesson(userId, lessonId, parentParams); + + expect(lessonRepo.findAllByCourseIds).toHaveBeenCalledWith([courseId]); + }); }); - describe('when access to lesson is forbidden', () => { - const setupWithLessonForbidden = () => { + describe('when authorization of lesson throw forbidden exception', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); const lesson = lessonFactory.buildWithId(); - userRepo.findById.mockResolvedValue(user); - lessonRepo.findById.mockResolvedValue(lesson); - // authorisation should not be mocked - authorisation.hasPermission.mockImplementation((u: User, e: AuthorizableObject | BaseDO) => e !== lesson); - return { user, course, lesson }; + const parentParams = { courseId: course.id, userId: new ObjectId().toHexString() }; + + userRepo.findById.mockResolvedValueOnce(user); + lessonRepo.findById.mockResolvedValueOnce(lesson); + courseRepo.findById.mockResolvedValueOnce(course); + authorisation.hasPermission.mockReturnValueOnce(false); + + return { + userId: user.id, + lessonId: lesson.id, + parentParams, + }; }; - it('should throw NotFoundException', async () => { - const { course, user, lesson } = setupWithLessonForbidden(); + it('should throw ForbiddenException', async () => { + const { parentParams, userId, lessonId } = setup(); - try { - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId: new ObjectId().toHexString() }); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(ForbiddenException); - } + await expect(uc.copyLesson(userId, lessonId, parentParams)).rejects.toThrowError( + new ForbiddenException('could not find lesson to copy') + ); }); }); - describe('when access to course is forbidden', () => { - const setupWithCourseForbidden = () => { + describe('when authorization of course throw with forbidden exception', () => { + const setup = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); const lesson = lessonFactory.buildWithId(); - userRepo.findById.mockResolvedValue(user); - lessonRepo.findById.mockResolvedValue(lesson); - courseRepo.findById.mockResolvedValue(course); - authorisation.hasPermission.mockReturnValue(true); - authorisation.checkPermissionByReferences.mockImplementation(() => { + + const parentParams = { courseId: course.id, userId: new ObjectId().toHexString() }; + + userRepo.findById.mockResolvedValueOnce(user); + lessonRepo.findById.mockResolvedValueOnce(lesson); + courseRepo.findById.mockResolvedValueOnce(course); + authorisation.checkPermission.mockImplementationOnce(() => { throw new ForbiddenException(); }); - return { user, course, lesson }; + authorisation.hasPermission.mockReturnValueOnce(true); + + return { + userId: user.id, + lessonId: lesson.id, + parentParams, + }; }; - it('should throw Forbidden Exception', async () => { - const { course, user, lesson } = setupWithCourseForbidden(); + it('should pass the forbidden exception', async () => { + const { parentParams, userId, lessonId } = setup(); - try { - await uc.copyLesson(user.id, lesson.id, { courseId: course.id, userId: new ObjectId().toHexString() }); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(ForbiddenException); - } + await expect(uc.copyLesson(userId, lessonId, parentParams)).rejects.toThrowError(new ForbiddenException()); }); }); }); diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts index 753200a5718..7ec51f5ef1c 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts @@ -1,14 +1,9 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ForbiddenException, Injectable, InternalServerErrorException } from '@nestjs/common'; -import { EntityId } from '@shared/domain'; +import { Course, EntityId, LessonEntity, User } from '@shared/domain'; import { Permission } from '@shared/domain/interface/permission.enum'; import { CourseRepo, LessonRepo } from '@shared/repo'; -import { - Action, - AuthorizableReferenceType, - AuthorizationContextBuilder, - AuthorizationService, -} from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { CopyHelperService, CopyStatus } from '@src/modules/copy-helper'; import { LessonCopyParentParams } from '@src/modules/lesson'; import { LessonCopyService } from '@src/modules/lesson/service'; @@ -24,27 +19,24 @@ export class LessonCopyUC { ) {} async copyLesson(userId: EntityId, lessonId: EntityId, parentParams: LessonCopyParentParams): Promise { - this.featureEnabled(); - const user = await this.authorisation.getUserWithPermissions(userId); - const originalLesson = await this.lessonRepo.findById(lessonId); - const context = AuthorizationContextBuilder.read([Permission.TOPIC_CREATE]); - if (!this.authorisation.hasPermission(user, originalLesson, context)) { - throw new ForbiddenException('could not find lesson to copy'); - } + this.checkFeatureEnabled(); + + const [user, originalLesson]: [User, LessonEntity] = await Promise.all([ + this.authorisation.getUserWithPermissions(userId), + this.lessonRepo.findById(lessonId), + ]); + this.checkOriginalLessonAuthorization(user, originalLesson); + + // should be a seperate private method const destinationCourse = parentParams.courseId ? await this.courseRepo.findById(parentParams.courseId) : originalLesson.course; - await this.authorisation.checkPermissionByReferences( - userId, - AuthorizableReferenceType.Course, - destinationCourse.id, - { - action: Action.write, - requiredPermissions: [], - } - ); + // --- + + this.checkDestinationCourseAuthorization(user, destinationCourse); + // should be a seperate private method const [existingLessons] = await this.lessonRepo.findAllByCourseIds([originalLesson.course.id]); const existingNames = existingLessons.map((l) => l.name); const copyName = this.copyHelperService.deriveCopyName(originalLesson.name, existingNames); @@ -55,11 +47,25 @@ export class LessonCopyUC { user, copyName, }); + // --- return copyStatus; } - private featureEnabled() { + private checkOriginalLessonAuthorization(user: User, originalLesson: LessonEntity): void { + const contextReadWithTopicCreate = AuthorizationContextBuilder.read([Permission.TOPIC_CREATE]); + if (!this.authorisation.hasPermission(user, originalLesson, contextReadWithTopicCreate)) { + // error message is not correct, switch to authorisation.checkPermission() makse sense for me + throw new ForbiddenException('could not find lesson to copy'); + } + } + + private checkDestinationCourseAuthorization(user: User, destinationCourse: Course): void { + const contextCanWrite = AuthorizationContextBuilder.write([]); + this.authorisation.checkPermission(user, destinationCourse, contextCanWrite); + } + + private checkFeatureEnabled() { const enabled = Configuration.get('FEATURE_COPY_SERVICE_ENABLED') as boolean; if (!enabled) { throw new InternalServerErrorException('Copy Feature not enabled'); diff --git a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts index 98a0957f3d3..cafa02e4d20 100644 --- a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts +++ b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts @@ -14,8 +14,7 @@ import { TaskWithStatusVo, User, } from '@shared/domain'; -import { AuthorizationService } from '@src/modules/authorization/authorization.service'; -import { Action } from '@src/modules/authorization/types/action.enum'; +import { AuthorizationService, Action } from '@src/modules/authorization'; import { ColumnBoardMetaData, LessonMetaData, diff --git a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts index 17dc2de5fd0..8747a07ada6 100644 --- a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts +++ b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts @@ -66,6 +66,9 @@ describe('LegacySchoolUc', () => { jest.resetAllMocks(); }); + // Tests with case of authService.checkPermission.mockImplementation(() => throw new ForbiddenException()); + // are missed for both methodes + describe('setMigration is called', () => { describe('when first starting the migration', () => { const setup = () => { @@ -77,7 +80,7 @@ describe('LegacySchoolUc', () => { }); userLoginMigrationService.findMigrationBySchool.mockResolvedValue(null); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); schoolService.getSchoolById.mockResolvedValue(school); userLoginMigrationService.setMigration.mockResolvedValue(userLoginMigration); }; @@ -107,7 +110,7 @@ describe('LegacySchoolUc', () => { }); userLoginMigrationService.findMigrationBySchool.mockResolvedValue(userLoginMigration); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); schoolService.getSchoolById.mockResolvedValue(school); userLoginMigrationService.setMigration.mockResolvedValue(updatedUserLoginMigration); schoolMigrationService.hasSchoolMigratedUser.mockResolvedValue(true); @@ -138,7 +141,7 @@ describe('LegacySchoolUc', () => { }); userLoginMigrationService.findMigrationBySchool.mockResolvedValue(userLoginMigration); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); schoolService.getSchoolById.mockResolvedValue(school); userLoginMigrationService.setMigration.mockResolvedValue(updatedUserLoginMigration); schoolMigrationService.hasSchoolMigratedUser.mockResolvedValue(false); @@ -177,7 +180,7 @@ describe('LegacySchoolUc', () => { }); userLoginMigrationService.findMigrationBySchool.mockResolvedValue(userLoginMigration); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); schoolService.getSchoolById.mockResolvedValue(school); userLoginMigrationService.setMigration.mockResolvedValue(updatedUserLoginMigration); }; @@ -208,7 +211,7 @@ describe('LegacySchoolUc', () => { }); userLoginMigrationService.findMigrationBySchool.mockResolvedValue(userLoginMigration); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); schoolService.getSchoolById.mockResolvedValue(school); userLoginMigrationService.setMigration.mockResolvedValue(updatedUserLoginMigration); schoolMigrationService.validateGracePeriod.mockImplementation(() => { @@ -241,7 +244,7 @@ describe('LegacySchoolUc', () => { userLoginMigrationService.findMigrationBySchool.mockResolvedValue(userLoginMigration); schoolService.getSchoolById.mockResolvedValue(school); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); }; it('should return a migration', async () => { @@ -265,7 +268,7 @@ describe('LegacySchoolUc', () => { userLoginMigrationService.findMigrationBySchool.mockResolvedValue(null); schoolService.getSchoolById.mockResolvedValue(school); - authService.checkPermissionByReferences.mockImplementation(() => Promise.resolve()); + authService.checkPermission.mockReturnValueOnce(); }; it('should return no migration information', async () => { diff --git a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts index 2ccc9dc5698..d1d13ffb037 100644 --- a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts +++ b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { Permission, LegacySchoolDo, UserLoginMigrationDO } from '@shared/domain'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { Permission, LegacySchoolDo, UserLoginMigrationDO, User } from '@shared/domain'; import { SchoolMigrationService, UserLoginMigrationRevertService, @@ -30,10 +30,12 @@ export class LegacySchoolUc { oauthMigrationFinished: boolean, userId: string ): Promise { - await this.authService.checkPermissionByReferences(userId, AuthorizableReferenceType.School, schoolId, { - action: Action.read, - requiredPermissions: [Permission.SCHOOL_EDIT], - }); + const [authorizableUser, school]: [User, LegacySchoolDo] = await Promise.all([ + this.authService.getUserWithPermissions(userId), + this.schoolService.getSchoolById(schoolId), + ]); + + this.checkSchoolAuthorization(authorizableUser, school); const existingUserLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationService.findMigrationBySchool(schoolId); @@ -61,8 +63,6 @@ export class LegacySchoolUc { await this.schoolMigrationService.unmarkOutdatedUsers(schoolId); } - const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId); - const migrationDto: OauthMigrationDto = new OauthMigrationDto({ oauthMigrationPossible: !updatedUserLoginMigration.closedAt ? updatedUserLoginMigration.startedAt : undefined, oauthMigrationMandatory: updatedUserLoginMigration.mandatorySince, @@ -75,17 +75,17 @@ export class LegacySchoolUc { } async getMigration(schoolId: string, userId: string): Promise { - await this.authService.checkPermissionByReferences(userId, AuthorizableReferenceType.School, schoolId, { - action: Action.read, - requiredPermissions: [Permission.SCHOOL_EDIT], - }); + const [authorizableUser, school]: [User, LegacySchoolDo] = await Promise.all([ + this.authService.getUserWithPermissions(userId), + this.schoolService.getSchoolById(schoolId), + ]); + + this.checkSchoolAuthorization(authorizableUser, school); const userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationService.findMigrationBySchool( schoolId ); - const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId); - const migrationDto: OauthMigrationDto = new OauthMigrationDto({ oauthMigrationPossible: userLoginMigration && !userLoginMigration.closedAt ? userLoginMigration.startedAt : undefined, @@ -97,4 +97,9 @@ export class LegacySchoolUc { return migrationDto; } + + private checkSchoolAuthorization(authorizableUser: User, school: LegacySchoolDo): void { + const context = AuthorizationContextBuilder.read([Permission.SCHOOL_EDIT]); + this.authService.checkPermission(authorizableUser, school, context); + } } diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts index b344f1b6cdc..d0480398ec8 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { OauthProviderService } from '@shared/infra/oauth-provider/index'; import { Permission, User } from '@shared/domain/index'; -import { AuthorizationService } from '@src/modules/authorization/authorization.service'; +import { AuthorizationService } from '@src/modules/authorization'; import { ProviderOauthClient } from '@shared/infra/oauth-provider/dto'; import { ICurrentUser } from '@src/modules/authentication'; diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts index 68c3a141f9b..2abd2019fbf 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts @@ -1,8 +1,8 @@ import { Request } from 'express'; import request from 'supertest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { EntityManager } from '@mikro-orm/mongodb'; -import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; @@ -101,27 +101,27 @@ describe(`share token creation (api)`, () => { const response = await api.post({ parentId: course.id, parentType: ShareTokenParentType.Course }); - expect(response.status).toEqual(500); + expect(response.status).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); }); }); - describe('with ivalid request data', () => { + describe('with invalid request data', () => { it('should return status 400 on empty parent id', async () => { const response = await api.post({ parentId: '', parentType: ShareTokenParentType.Course, }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); - it('should return status 403 when parent id is not found', async () => { + it('should return status 404 when parent id is not found', async () => { const response = await api.post({ - parentId: '000011112222333344445555', + parentId: new ObjectId().toHexString(), parentType: ShareTokenParentType.Course, }); - expect(response.status).toEqual(403); + expect(response.status).toEqual(HttpStatus.NOT_FOUND); }); it('should return status 400 on invalid parent id', async () => { @@ -130,7 +130,7 @@ describe(`share token creation (api)`, () => { parentType: ShareTokenParentType.Course, }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); it('should return status 400 on invalid parent type', async () => { @@ -142,7 +142,7 @@ describe(`share token creation (api)`, () => { parentType: 'invalid', }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); it('should return status 400 when expiresInDays is invalid integer', async () => { @@ -155,7 +155,7 @@ describe(`share token creation (api)`, () => { expiresInDays: 'foo', }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); it('should return status 400 when expiresInDays is negative', async () => { @@ -167,7 +167,7 @@ describe(`share token creation (api)`, () => { expiresInDays: -10, }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); it('should return status 400 when expiresInDays is not an integer', async () => { @@ -179,7 +179,7 @@ describe(`share token creation (api)`, () => { expiresInDays: 2.5, }); - expect(response.status).toEqual(400); + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); }); }); @@ -189,7 +189,7 @@ describe(`share token creation (api)`, () => { const response = await api.post({ parentId: course.id, parentType: ShareTokenParentType.Course }); - expect(response.status).toEqual(201); + expect(response.status).toEqual(HttpStatus.CREATED); }); it('should return a valid result', async () => { @@ -216,7 +216,7 @@ describe(`share token creation (api)`, () => { schoolExclusive: true, }); - expect(response.status).toEqual(201); + expect(response.status).toEqual(HttpStatus.CREATED); }); it('should return a valid result', async () => { @@ -248,7 +248,7 @@ describe(`share token creation (api)`, () => { expiresInDays: 5, }); - expect(response.status).toEqual(201); + expect(response.status).toEqual(HttpStatus.CREATED); }); it('should return a valid result containg the expiration timestamp', async () => { diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts index e58940addda..737a24b022e 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts @@ -1,6 +1,6 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { EntityManager } from '@mikro-orm/mongodb'; -import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; @@ -118,16 +118,17 @@ describe(`share token import (api)`, () => { const response = await api.post({ token }, { newName: 'NewName' }); - expect(response.status).toEqual(500); + expect(response.status).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); }); }); describe('with a valid token', () => { it('should return status 201', async () => { const { token } = await setup(); + const response = await api.post({ token }, { newName: 'NewName' }); - expect(response.status).toEqual(201); + expect(response.status).toEqual(HttpStatus.CREATED); }); it('should return a valid result', async () => { @@ -149,23 +150,53 @@ describe(`share token import (api)`, () => { describe('with invalid token', () => { it('should return status 404', async () => { await setup(); + const response = await api.post({ token: 'invalid_token' }, { newName: 'NewName' }); - expect(response.status).toEqual(404); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); }); }); describe('with invalid context', () => { - it('should return status 403', async () => { + const setup2 = async () => { + const school = schoolFactory.build(); const otherSchool = schoolFactory.build(); - await em.persistAndFlush(otherSchool); - em.clear(); + const roles = roleFactory.buildList(1, { + permissions: [Permission.COURSE_CREATE], + }); - const { token } = await setup({ + const user = userFactory.build({ school, roles }); + const course = courseFactory.build({ teachers: [user] }); + await em.persistAndFlush([user, course, otherSchool]); + + const context = { contextType: ShareTokenContextType.School, contextId: otherSchool.id, - }); - const response = await api.post({ token }, { newName: 'NewName' }); - expect(response.status).toEqual(403); + }; + + const shareToken = await shareTokenService.createToken( + { + parentType: ShareTokenParentType.Course, + parentId: course.id, + }, + { context } + ); + + em.clear(); + + currentUser = mapUserToCurrentUser(user); + + return { + shareTokenFromDifferentCourse: shareToken.token, + }; + }; + + it('should return status 403', async () => { + const { shareTokenFromDifferentCourse } = await setup2(); + + const response = await api.post({ token: shareTokenFromDifferentCourse }, { newName: 'NewName' }); + + expect(response.status).toEqual(HttpStatus.FORBIDDEN); }); }); @@ -175,7 +206,7 @@ describe(`share token import (api)`, () => { // @ts-expect-error invalid new name const response = await api.post({ token }, { newName: 42 }); - expect(response.status).toEqual(501); + expect(response.status).toEqual(HttpStatus.NOT_IMPLEMENTED); }); }); }); diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts index a5c1304a730..7065c06a026 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts @@ -1,167 +1,210 @@ -import { Request } from 'express'; -import request from 'supertest'; -import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { Configuration } from '@hpi-schul-cloud/commons'; import { EntityManager } from '@mikro-orm/mongodb'; -import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; -import { - cleanupCollections, - courseFactory, - mapUserToCurrentUser, - roleFactory, - schoolFactory, - userFactory, -} from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; +import { TestApiClient, UserAndAccountTestFactory, courseFactory, schoolFactory } from '@shared/testing'; import { ServerTestModule } from '@src/modules/server'; import { ShareTokenService } from '../../service'; -import { ShareTokenInfoResponse, ShareTokenResponse, ShareTokenUrlParams } from '../dto'; -import { ShareTokenContext, ShareTokenContextType, ShareTokenParentType } from '../../domainobject/share-token.do'; - -const baseRouteName = '/sharetoken'; - -class API { - app: INestApplication; - - constructor(app: INestApplication) { - this.app = app; - } - - async get(urlParams: ShareTokenUrlParams) { - const response = await request(this.app.getHttpServer()) - .get(`${baseRouteName}/${urlParams.token}`) - .set('Accept', 'application/json'); - - return { - result: response.body as ShareTokenResponse, - error: response.body as ApiValidationError, - status: response.status, - }; - } -} +import { ShareTokenInfoResponse } from '../dto'; +import { ShareTokenContextType, ShareTokenParentType } from '../../domainobject/share-token.do'; describe(`share token lookup (api)`, () => { let app: INestApplication; let em: EntityManager; - let currentUser: ICurrentUser; let shareTokenService: ShareTokenService; - let api: API; + let testApiClient: TestApiClient; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ServerTestModule], - }) - .overrideGuard(JwtAuthGuard) - .useValue({ - canActivate(context: ExecutionContext) { - const req: Request = context.switchToHttp().getRequest(); - req.user = currentUser; - return true; - }, - }) - .compile(); + }).compile(); app = module.createNestApplication(); await app.init(); em = module.get(EntityManager); shareTokenService = module.get(ShareTokenService); - api = new API(app); + testApiClient = new TestApiClient(app, 'sharetoken'); }); afterAll(async () => { await app.close(); }); - beforeEach(() => { - Configuration.set('FEATURE_COURSE_SHARE_NEW', true); - }); + describe('with the feature disabled', () => { + const setup = async () => { + Configuration.set('FEATURE_COURSE_SHARE_NEW', false); - const setup = async (context?: ShareTokenContext) => { - await cleanupCollections(em); - const school = schoolFactory.build(); - const roles = roleFactory.buildList(1, { - permissions: [Permission.COURSE_CREATE], - }); - const user = userFactory.build({ school, roles }); - const course = courseFactory.build({ teachers: [user] }); - await em.persistAndFlush([user, course]); - - const shareToken = await shareTokenService.createToken( - { - parentType: ShareTokenParentType.Course, - parentId: course.id, - }, - { context } - ); - - em.clear(); - - currentUser = mapUserToCurrentUser(user); - - return { - parentType: ShareTokenParentType.Course, - parentName: course.getMetadata().title, - token: shareToken.token, + const parentType = ShareTokenParentType.Course; + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({}, [Permission.COURSE_CREATE]); + const course = courseFactory.build({ teachers: [teacherUser] }); + + await em.persistAndFlush([course, teacherAccount, teacherUser]); + em.clear(); + + const shareToken = await shareTokenService.createToken( + { + parentType, + parentId: course.id, + }, + undefined + ); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + token: shareToken.token, + loggedInClient, + }; }; - }; - describe('with the feature disabled', () => { it('should return status 500', async () => { - Configuration.set('FEATURE_COURSE_SHARE_NEW', false); - const { token } = await setup(); + const { token, loggedInClient } = await setup(); - const response = await api.get({ token }); + const response = await loggedInClient.get(token); - expect(response.status).toEqual(500); + expect(response.status).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(response.body).toEqual({ + code: 500, + message: 'Import Course Feature not enabled', + title: 'Internal Server Error', + type: 'INTERNAL_SERVER_ERROR', + }); }); }); + // test and setup for other feature flags are missed + describe('with a valid token', () => { - it('should return status 200', async () => { - const { token } = await setup(); - const response = await api.get({ token }); + const setup = async () => { + Configuration.set('FEATURE_COURSE_SHARE_NEW', true); - expect(response.status).toEqual(200); - }); + const parentType = ShareTokenParentType.Course; + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({}, [Permission.COURSE_CREATE]); + const course = courseFactory.build({ teachers: [teacherUser] }); - it('should return a valid result', async () => { - const { parentType, parentName, token } = await setup(); - const response = await api.get({ token }); + await em.persistAndFlush([course, teacherAccount, teacherUser]); + em.clear(); + + const shareToken = await shareTokenService.createToken( + { + parentType, + parentId: course.id, + }, + undefined + ); + + const loggedInClient = await testApiClient.login(teacherAccount); const expectedResult: ShareTokenInfoResponse = { - token, + token: shareToken.token, parentType, - parentName, + parentName: course.getMetadata().title, }; - expect(response.result).toEqual(expectedResult); + return { + expectedResult, + token: shareToken.token, + loggedInClient, + }; + }; + + it('should return status 200 with correct formated body', async () => { + const { token, loggedInClient, expectedResult } = await setup(); + + const response = await loggedInClient.get(token); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body).toEqual(expectedResult); }); }); describe('with invalid token', () => { + const setup = async () => { + Configuration.set('FEATURE_COURSE_SHARE_NEW', true); + + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({}, [Permission.COURSE_CREATE]); + const course = courseFactory.build({ teachers: [teacherUser] }); + + await em.persistAndFlush([course, teacherAccount, teacherUser]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + invalidToken: 'invalid_token', + loggedInClient, + }; + }; + it('should return status 404', async () => { - await setup(); - const response = await api.get({ token: 'invalid_token' }); - expect(response.status).toEqual(404); + const { invalidToken, loggedInClient } = await setup(); + + const response = await loggedInClient.get(invalidToken); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body).toEqual({ + code: 404, + message: 'The requested ShareToken: [object Object] has not been found.', + title: 'Not Found', + type: 'NOT_FOUND', + }); }); }); describe('with invalid context', () => { - it('should return status 403', async () => { + const setup = async () => { + Configuration.set('FEATURE_COURSE_SHARE_NEW', true); + + const parentType = ShareTokenParentType.Course; + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({}, [Permission.COURSE_CREATE]); const otherSchool = schoolFactory.build(); - await em.persistAndFlush(otherSchool); + const course = courseFactory.build({ teachers: [teacherUser] }); + + await em.persistAndFlush([course, teacherAccount, teacherUser, otherSchool]); em.clear(); - const { token } = await setup({ + const context = { contextType: ShareTokenContextType.School, contextId: otherSchool.id, + }; + + const shareToken = await shareTokenService.createToken( + { + parentType, + parentId: course.id, + }, + { context } + ); + + const loggedInClient = await testApiClient.login(teacherAccount); + + const expectedResult: ShareTokenInfoResponse = { + token: shareToken.token, + parentType, + parentName: course.getMetadata().title, + }; + + return { + expectedResult, + token: shareToken.token, + loggedInClient, + }; + }; + + it('should return status 403', async () => { + const { token, loggedInClient } = await setup(); + + const response = await loggedInClient.get(token); + + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body).toEqual({ + code: 403, + message: 'Forbidden', + title: 'Forbidden', + type: 'FORBIDDEN', }); - const response = await api.get({ token }); - expect(response.status).toEqual(403); }); }); }); diff --git a/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts index 9684a1dbb58..68c0ccbd972 100644 --- a/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts +++ b/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { ShareTokenContextType } from '../domainobject/share-token.do'; import { ShareTokenContextTypeMapper } from './context-type.mapper'; diff --git a/apps/server/src/modules/sharing/mapper/context-type.mapper.ts b/apps/server/src/modules/sharing/mapper/context-type.mapper.ts index 7c9b4c8bb1d..05ed42843c7 100644 --- a/apps/server/src/modules/sharing/mapper/context-type.mapper.ts +++ b/apps/server/src/modules/sharing/mapper/context-type.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { ShareTokenContextType } from '../domainobject/share-token.do'; export class ShareTokenContextTypeMapper { @@ -12,6 +12,7 @@ export class ShareTokenContextTypeMapper { if (!res) { throw new NotImplementedException(); } + return res; } } diff --git a/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts b/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts index c6d8669bc70..4f8750245b4 100644 --- a/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts +++ b/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { ShareTokenParentType } from '../domainobject/share-token.do'; import { ShareTokenParentTypeMapper } from './parent-type.mapper'; diff --git a/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts b/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts index 54d8ceb0470..2ea01ea39f9 100644 --- a/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts +++ b/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { ShareTokenParentType } from '../domainobject/share-token.do'; export class ShareTokenParentTypeMapper { diff --git a/apps/server/src/modules/sharing/sharing.module.ts b/apps/server/src/modules/sharing/sharing.module.ts index f09214e9cf8..519033065b5 100644 --- a/apps/server/src/modules/sharing/sharing.module.ts +++ b/apps/server/src/modules/sharing/sharing.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; import { ShareTokenController } from './controller/share-token.controller'; import { ShareTokenUC } from './uc'; import { ShareTokenService, TokenGenerator } from './service'; @@ -10,7 +11,7 @@ import { LearnroomModule } from '../learnroom'; import { TaskModule } from '../task'; @Module({ - imports: [AuthorizationModule, LoggerModule, LearnroomModule, LessonModule, TaskModule], + imports: [AuthorizationModule, AuthorizationReferenceModule, LoggerModule, LearnroomModule, LessonModule, TaskModule], controllers: [], providers: [ShareTokenService, TokenGenerator, ShareTokenRepo], exports: [ShareTokenService], @@ -18,7 +19,15 @@ import { TaskModule } from '../task'; export class SharingModule {} @Module({ - imports: [SharingModule, AuthorizationModule, LearnroomModule, LessonModule, TaskModule, LoggerModule], + imports: [ + SharingModule, + AuthorizationModule, + AuthorizationReferenceModule, + LearnroomModule, + LessonModule, + TaskModule, + LoggerModule, + ], controllers: [ShareTokenController], providers: [ShareTokenUC], }) diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts index 73234960794..8f33076cff2 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts @@ -15,7 +15,8 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizableReferenceType, AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; import { CourseCopyService } from '@src/modules/learnroom'; import { CourseService } from '@src/modules/learnroom/service'; @@ -33,6 +34,7 @@ describe('ShareTokenUC', () => { let lessonCopyService: DeepMocked; let taskCopyService: DeepMocked; let authorization: DeepMocked; + let authorizationReferenceService: DeepMocked; let courseService: DeepMocked; let lessonRepo: DeepMocked; @@ -48,6 +50,10 @@ describe('ShareTokenUC', () => { provide: AuthorizationService, useValue: createMock(), }, + { + provide: AuthorizationReferenceService, + useValue: createMock(), + }, { provide: CourseCopyService, useValue: createMock(), @@ -81,8 +87,10 @@ describe('ShareTokenUC', () => { lessonCopyService = module.get(LessonCopyService); taskCopyService = module.get(TaskCopyService); authorization = module.get(AuthorizationService); + authorizationReferenceService = module.get(AuthorizationReferenceService); courseService = module.get(CourseService); lessonRepo = module.get(LessonRepo); + await setupEntities(); }); @@ -93,6 +101,7 @@ describe('ShareTokenUC', () => { beforeEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); + // configuration sets must be part of the setup functions and part of the describe when ...and feature x is activated Configuration.set('FEATURE_COURSE_SHARE_NEW', true); Configuration.set('FEATURE_LESSON_SHARE', true); Configuration.set('FEATURE_TASK_SHARE', true); @@ -129,7 +138,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Course, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.Course, course.id, @@ -148,7 +157,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Course, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledTimes(1); + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledTimes(1); }); it('should call the service', async () => { @@ -190,7 +199,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Lesson, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.Lesson, lesson.id, @@ -209,7 +218,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Lesson, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledTimes(1); + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledTimes(1); }); it('should call the service', async () => { @@ -251,7 +260,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Task, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.Task, task.id, @@ -270,7 +279,7 @@ describe('ShareTokenUC', () => { parentType: ShareTokenParentType.Task, }); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledTimes(1); + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledTimes(1); }); it('should call the service', async () => { @@ -309,7 +318,7 @@ describe('ShareTokenUC', () => { } ); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.Course, course.id, @@ -337,7 +346,7 @@ describe('ShareTokenUC', () => { } ); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.School, school.id, @@ -574,7 +583,7 @@ describe('ShareTokenUC', () => { await uc.lookupShareToken(user.id, shareToken.token); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.School, school.id, @@ -601,7 +610,7 @@ describe('ShareTokenUC', () => { await uc.lookupShareToken(user.id, shareToken.token); - expect(authorization.checkPermissionByReferences).not.toHaveBeenCalled(); + expect(authorizationReferenceService.checkPermissionByReferences).not.toHaveBeenCalled(); }); }); }); @@ -686,7 +695,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.School, school.id, @@ -706,7 +715,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).not.toHaveBeenCalled(); + expect(authorizationReferenceService.checkPermissionByReferences).not.toHaveBeenCalled(); }); }); }); @@ -803,7 +812,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.School, school.id, @@ -823,7 +832,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).not.toHaveBeenCalled(); + expect(authorizationReferenceService.checkPermissionByReferences).not.toHaveBeenCalled(); }); }); }); @@ -919,7 +928,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).toHaveBeenCalledWith( + expect(authorizationReferenceService.checkPermissionByReferences).toHaveBeenCalledWith( user.id, AuthorizableReferenceType.School, school.id, @@ -939,7 +948,7 @@ describe('ShareTokenUC', () => { await uc.importShareToken(user.id, shareToken.token, 'NewName'); - expect(authorization.checkPermissionByReferences).not.toHaveBeenCalled(); + expect(authorizationReferenceService.checkPermissionByReferences).not.toHaveBeenCalled(); }); }); }); @@ -962,6 +971,7 @@ describe('ShareTokenUC', () => { service.lookupToken.mockResolvedValue(shareToken); jest.spyOn(ShareTokenUC.prototype as any, 'checkFeatureEnabled').mockReturnValue(undefined); jest.spyOn(ShareTokenUC.prototype as any, 'checkCreatePermission').mockReturnValue(undefined); + await expect(uc.importShareToken('userId', shareToken.token, 'NewName')).rejects.toThrowError( NotImplementedException ); diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.ts b/apps/server/src/modules/sharing/uc/share-token.uc.ts index 0b72be7ce31..b2bbc635403 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.ts @@ -2,7 +2,8 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { BadRequestException, Injectable, InternalServerErrorException, NotImplementedException } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { CopyStatus } from '@src/modules/copy-helper'; import { CourseCopyService } from '@src/modules/learnroom'; import { CourseService } from '@src/modules/learnroom/service'; @@ -24,6 +25,7 @@ export class ShareTokenUC { constructor( private readonly shareTokenService: ShareTokenService, private readonly authorizationService: AuthorizationService, + private readonly authorizationReferenceService: AuthorizationReferenceService, private readonly courseCopyService: CourseCopyService, private readonly lessonCopyService: LessonCopyService, private readonly courseService: CourseService, @@ -177,18 +179,26 @@ export class ShareTokenUC { requiredPermissions = [Permission.HOMEWORK_CREATE]; } - await this.authorizationService.checkPermissionByReferences(userId, allowedParentType, payload.parentId, { - action: Action.write, - requiredPermissions, - }); + const authorizationContext = AuthorizationContextBuilder.write(requiredPermissions); + + await this.authorizationReferenceService.checkPermissionByReferences( + userId, + allowedParentType, + payload.parentId, + authorizationContext + ); } private async checkContextReadPermission(userId: EntityId, context: ShareTokenContext) { const allowedContextType = ShareTokenContextTypeMapper.mapToAllowedAuthorizationEntityType(context.contextType); - await this.authorizationService.checkPermissionByReferences(userId, allowedContextType, context.contextId, { - action: Action.read, - requiredPermissions: [], - }); + const authorizationContext = AuthorizationContextBuilder.read([]); + + await this.authorizationReferenceService.checkPermissionByReferences( + userId, + allowedContextType, + context.contextId, + authorizationContext + ); } private async checkCreatePermission(userId: EntityId, parentType: ShareTokenParentType) { @@ -221,16 +231,19 @@ export class ShareTokenUC { private checkFeatureEnabled(parentType: ShareTokenParentType) { switch (parentType) { case ShareTokenParentType.Course: + // Configuration.get is the deprecated way to read envirment variables if (!(Configuration.get('FEATURE_COURSE_SHARE_NEW') as boolean)) { throw new InternalServerErrorException('Import Course Feature not enabled'); } break; case ShareTokenParentType.Lesson: + // Configuration.get is the deprecated way to read envirment variables if (!(Configuration.get('FEATURE_LESSON_SHARE') as boolean)) { throw new InternalServerErrorException('Import Lesson Feature not enabled'); } break; case ShareTokenParentType.Task: + // Configuration.get is the deprecated way to read envirment variables if (!(Configuration.get('FEATURE_TASK_SHARE') as boolean)) { throw new InternalServerErrorException('Import Task Feature not enabled'); } diff --git a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts index a7666d479a9..2ad21c68dc2 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts @@ -3,15 +3,14 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ObjectId } from '@mikro-orm/mongodb'; import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { BaseDO, User } from '@shared/domain'; import { CourseRepo, LessonRepo, TaskRepo, UserRepo } from '@shared/repo'; import { courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; -import { Action, AuthorizableReferenceType, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { AuthorizableObject } from '@shared/domain/domain-object'; import { TaskCopyService } from '../service'; import { TaskCopyUC } from './task-copy.uc'; +import { TaskCopyParentParams } from '../types'; describe('task copy uc', () => { let uc: TaskCopyUC; @@ -92,14 +91,8 @@ describe('task copy uc', () => { const lesson = lessonFactory.buildWithId({ course }); const allTasks = taskFactory.buildList(3, { course }); const task = allTasks[0]; - authorisation.getUserWithPermissions.mockResolvedValue(user); - taskRepo.findById.mockResolvedValue(task); - lessonRepo.findById.mockResolvedValue(lesson); - taskRepo.findBySingleParent.mockResolvedValue([allTasks, allTasks.length]); - courseRepo.findById.mockResolvedValue(course); - authorisation.hasPermission.mockReturnValue(true); const copyName = 'name of the copy'; - copyHelperService.deriveCopyName.mockReturnValue(copyName); + const copy = taskFactory.buildWithId({ creator: user, course }); const status = { title: 'taskCopy', @@ -108,9 +101,16 @@ describe('task copy uc', () => { copyEntity: copy, originalEntity: task, }; - taskCopyService.copyTask.mockResolvedValue(status); - taskRepo.save.mockResolvedValue(undefined); - const userId = user.id; + + authorisation.getUserWithPermissions.mockResolvedValueOnce(user); + taskRepo.findById.mockResolvedValueOnce(task); + lessonRepo.findById.mockResolvedValueOnce(lesson); + taskRepo.findBySingleParent.mockResolvedValueOnce([allTasks, allTasks.length]); + courseRepo.findById.mockResolvedValueOnce(course); + authorisation.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(true); + copyHelperService.deriveCopyName.mockReturnValueOnce(copyName); + taskCopyService.copyTask.mockResolvedValueOnce(status); + taskRepo.save.mockResolvedValueOnce(); return { user, @@ -121,15 +121,16 @@ describe('task copy uc', () => { copy, allTasks, status, - userId, + userId: user.id, }; }; describe('feature is deactivated', () => { it('should throw InternalServerErrorException', async () => { + const { course, user, task, userId } = setup(); Configuration.set('FEATURE_COPY_SERVICE_ENABLED', false); - await expect(uc.copyTask('user.id', 'task.id', { courseId: 'course.id', userId: 'test' })).rejects.toThrowError( + await expect(uc.copyTask(user.id, task.id, { courseId: course.id, userId })).rejects.toThrowError( InternalServerErrorException ); }); @@ -214,15 +215,9 @@ describe('task copy uc', () => { const { course, user, task, userId } = setup(); await uc.copyTask(user.id, task.id, { courseId: course.id, userId }); - expect(authorisation.checkPermissionByReferences).toBeCalledWith( - user.id, - AuthorizableReferenceType.Course, - course.id, - { - action: Action.write, - requiredPermissions: [], - } - ); + + const context = AuthorizationContextBuilder.write([]); + expect(authorisation.checkPermission).toBeCalledWith(user, course, context); }); it('should pass authorisation check without destination course', async () => { @@ -230,10 +225,8 @@ describe('task copy uc', () => { await uc.copyTask(user.id, task.id, { userId }); - expect(authorisation.hasPermission).not.toBeCalledWith(user, course, { - action: Action.write, - requiredPermissions: [], - }); + const context = AuthorizationContextBuilder.write([]); + expect(authorisation.hasPermission).not.toBeCalledWith(user, course, context); }); it('should check authorisation for destination lesson', async () => { @@ -260,57 +253,64 @@ describe('task copy uc', () => { describe('when access to task is forbidden', () => { const setupWithTaskForbidden = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); const lesson = lessonFactory.buildWithId({ course }); const task = taskFactory.buildWithId(); - userRepo.findById.mockResolvedValue(user); - taskRepo.findById.mockResolvedValue(task); - // authorisation should not be mocked - authorisation.hasPermission.mockImplementation((u: User, e: AuthorizableObject | BaseDO) => e !== task); - return { user, course, lesson, task }; + + userRepo.findById.mockResolvedValueOnce(user); + taskRepo.findById.mockResolvedValueOnce(task); + authorisation.hasPermission.mockReturnValueOnce(false); + + const parentParams: TaskCopyParentParams = { + courseId: course.id, + lessonId: lesson.id, + userId: new ObjectId().toHexString(), + }; + + return { user, course, lesson, task, parentParams }; }; it('should throw NotFoundException', async () => { - const { course, lesson, user, task } = setupWithTaskForbidden(); - - try { - await uc.copyTask(user.id, task.id, { - courseId: course.id, - lessonId: lesson.id, - userId: new ObjectId().toHexString(), - }); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(NotFoundException); - } + const { user, task, parentParams } = setupWithTaskForbidden(); + + await expect(uc.copyTask(user.id, task.id, parentParams)).rejects.toThrowError( + new NotFoundException('could not find task to copy') + ); }); }); describe('when access to course is forbidden', () => { const setupWithCourseForbidden = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); const task = taskFactory.buildWithId(); - userRepo.findById.mockResolvedValue(user); - taskRepo.findById.mockResolvedValue(task); - // authorisation should not be mocked - authorisation.hasPermission.mockImplementation((u: User, e: AuthorizableObject | BaseDO) => e !== course); - authorisation.checkPermissionByReferences.mockImplementation(() => { + + userRepo.findById.mockResolvedValueOnce(user); + taskRepo.findById.mockResolvedValueOnce(task); + courseRepo.findById.mockResolvedValueOnce(course); + authorisation.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(true); + authorisation.checkPermission.mockImplementationOnce(() => { throw new ForbiddenException(); }); - return { user, course, task }; + + const parentParams: TaskCopyParentParams = { courseId: course.id, userId: new ObjectId().toHexString() }; + + return { + userId: user.id, + taskId: task.id, + parentParams, + }; }; it('should throw Forbidden Exception', async () => { - const { course, user, task } = setupWithCourseForbidden(); - - try { - await uc.copyTask(user.id, task.id, { courseId: course.id, userId: new ObjectId().toHexString() }); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(ForbiddenException); - } + const { userId, taskId, parentParams } = setupWithCourseForbidden(); + + await expect(uc.copyTask(userId, taskId, parentParams)).rejects.toThrowError(new ForbiddenException()); }); }); }); @@ -355,32 +355,35 @@ describe('task copy uc', () => { describe('when access to lesson is forbidden', () => { const setupWithLessonForbidden = () => { + Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); + const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); const lesson = lessonFactory.buildWithId({ course }); const task = taskFactory.buildWithId(); - userRepo.findById.mockResolvedValue(user); - taskRepo.findById.mockResolvedValue(task); - courseRepo.findById.mockResolvedValue(course); - lessonRepo.findById.mockResolvedValue(lesson); - // Authorisation should not be mocked - authorisation.hasPermission.mockImplementation((u: User, e: AuthorizableObject | BaseDO) => { - if (e === lesson) return false; - return true; - }); - return { user, lesson, task }; + userRepo.findById.mockResolvedValueOnce(user); + taskRepo.findById.mockResolvedValueOnce(task); + courseRepo.findById.mockResolvedValueOnce(course); + lessonRepo.findById.mockResolvedValueOnce(lesson); + // first canReadTask > second canWriteLesson + authorisation.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(false); + + const parentParams: TaskCopyParentParams = { lessonId: lesson.id, userId: new ObjectId().toHexString() }; + + return { + userId: user.id, + taskId: task.id, + parentParams, + }; }; it('should throw Forbidden Exception', async () => { - const { lesson, user, task } = setupWithLessonForbidden(); - - try { - await uc.copyTask(user.id, task.id, { lessonId: lesson.id, userId: new ObjectId().toHexString() }); - throw new Error('should have failed'); - } catch (err) { - expect(err).toBeInstanceOf(ForbiddenException); - } + const { userId, taskId, parentParams } = setupWithLessonForbidden(); + + await expect(uc.copyTask(userId, taskId, parentParams)).rejects.toThrowError( + new ForbiddenException('you dont have permission to add to this lesson') + ); }); }); }); diff --git a/apps/server/src/modules/task/uc/task-copy.uc.ts b/apps/server/src/modules/task/uc/task-copy.uc.ts index b1cdd212919..94b0a5a3acb 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.ts @@ -1,13 +1,8 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ForbiddenException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { Course, EntityId, LessonEntity, User } from '@shared/domain'; +import { Course, EntityId, Task, LessonEntity, User } from '@shared/domain'; import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; -import { - Action, - AuthorizableReferenceType, - AuthorizationContextBuilder, - AuthorizationService, -} from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { CopyHelperService, CopyStatus } from '@src/modules/copy-helper'; import { TaskCopyService } from '../service'; import { TaskCopyParentParams } from '../types'; @@ -24,46 +19,73 @@ export class TaskCopyUC { ) {} async copyTask(userId: EntityId, taskId: EntityId, parentParams: TaskCopyParentParams): Promise { - this.featureEnabled(); - const user = await this.authorisation.getUserWithPermissions(userId); - const originalTask = await this.taskRepo.findById(taskId); - if (!this.authorisation.hasPermission(user, originalTask, AuthorizationContextBuilder.read([]))) { - throw new NotFoundException('could not find task to copy'); - } + this.checkFeatureEnabled(); + + // i put it to promise all, it do not look like any more information can be expose over errors if it is called between the authorizations + // TODO: Add try catch around it with throw BadRequest invalid data + const [authorizableUser, originalTask, destinationCourse]: [User, Task, Course | undefined] = await Promise.all([ + this.authorisation.getUserWithPermissions(userId), + this.taskRepo.findById(taskId), + this.getDestinationCourse(parentParams.courseId), + ]); - const destinationCourse = await this.getDestinationCourse(parentParams.courseId); - if (parentParams.courseId) { - await this.authorisation.checkPermissionByReferences( - userId, - AuthorizableReferenceType.Course, - parentParams.courseId, - { - action: Action.write, - requiredPermissions: [], - } - ); + this.checkOriginalTaskAuthorization(authorizableUser, originalTask); + + if (destinationCourse) { + this.checkDestinationCourseAuthorisation(authorizableUser, destinationCourse); } - const destinationLesson = await this.getDestinationLesson(parentParams.lessonId, user); - const copyName = await this.getCopyName(originalTask.name, parentParams.courseId); + // i think getDestinationLesson can also to a promise.all on top + // then getCopyName can be put into if (destinationCourse) { + // but then the test need to cleanup + const [destinationLesson, copyName]: [LessonEntity | undefined, string | undefined] = await Promise.all([ + this.getDestinationLesson(parentParams.lessonId), + this.getCopyName(originalTask.name, parentParams.courseId), + ]); + + if (destinationLesson) { + this.checkDestinationLessonAuthorization(authorizableUser, destinationLesson); + } const status = await this.taskCopyService.copyTask({ originalTaskId: originalTask.id, destinationCourse, destinationLesson, - user, + user: authorizableUser, copyName, }); return status; } + private checkOriginalTaskAuthorization(authorizableUser: User, originalTask: Task): void { + const context = AuthorizationContextBuilder.read([]); + if (!this.authorisation.hasPermission(authorizableUser, originalTask, context)) { + // error message and erorr type are not correct + throw new NotFoundException('could not find task to copy'); + } + } + + private checkDestinationCourseAuthorisation(authorizableUser: User, destinationCourse: Course): void { + const context = AuthorizationContextBuilder.write([]); + this.authorisation.checkPermission(authorizableUser, destinationCourse, context); + } + + private checkDestinationLessonAuthorization(authorizableUser: User, destinationLesson: LessonEntity): void { + const context = AuthorizationContextBuilder.write([]); + if (!this.authorisation.hasPermission(authorizableUser, destinationLesson, context)) { + throw new ForbiddenException('you dont have permission to add to this lesson'); + } + } + private async getCopyName(originalTaskName: string, parentCourseId: EntityId | undefined) { let existingNames: string[] = []; if (parentCourseId) { + // It should really get an task where the creatorId === '' ? const [existingTasks] = await this.taskRepo.findBySingleParent('', parentCourseId); existingNames = existingTasks.map((t) => t.name); } + return this.copyHelperService.deriveCopyName(originalTaskName, existingNames); } @@ -77,19 +99,18 @@ export class TaskCopyUC { return destinationCourse; } - private async getDestinationLesson(lessonId: string | undefined, user: User): Promise { + private async getDestinationLesson(lessonId: string | undefined): Promise { if (lessonId === undefined) { return undefined; } const destinationLesson = await this.lessonRepo.findById(lessonId); - if (!this.authorisation.hasPermission(user, destinationLesson, AuthorizationContextBuilder.write([]))) { - throw new ForbiddenException('you dont have permission to add to this lesson'); - } + return destinationLesson; } - private featureEnabled() { + private checkFeatureEnabled() { + // This is the deprecated way to read envirement variables const enabled = Configuration.get('FEATURE_COPY_SERVICE_ENABLED') as boolean; if (!enabled) { throw new InternalServerErrorException('Copy Feature not enabled'); diff --git a/apps/server/src/modules/tool/common/common-tool.module.ts b/apps/server/src/modules/tool/common/common-tool.module.ts index cc7f5f86f00..0bd9ba5385b 100644 --- a/apps/server/src/modules/tool/common/common-tool.module.ts +++ b/apps/server/src/modules/tool/common/common-tool.module.ts @@ -3,11 +3,12 @@ import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '@src/modules/authorization'; import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { LearnroomModule } from '@src/modules/learnroom'; import { CommonToolService, CommonToolValidationService } from './service'; import { ToolPermissionHelper } from './uc/tool-permission-helper'; @Module({ - imports: [LoggerModule, forwardRef(() => AuthorizationModule), LegacySchoolModule], + imports: [LoggerModule, forwardRef(() => AuthorizationModule), LegacySchoolModule, LearnroomModule], // TODO: make deletion of entities cascading, adjust ExternalToolService.deleteExternalTool and remove the repos from here providers: [ CommonToolService, diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts new file mode 100644 index 00000000000..78d79f45a1b --- /dev/null +++ b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts @@ -0,0 +1,11 @@ +import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { ToolContextType } from '../enum'; +import { ContextTypeMapper } from './context-type.mapper'; + +describe('context-type.mapper', () => { + it('should map ToolContextType.COURSE to AuthorizableReferenceType.Course', () => { + const mappedCourse = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(ToolContextType.COURSE); + + expect(mappedCourse).toEqual(AuthorizableReferenceType.Course); + }); +}); diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts index bf8fb537924..883400f4258 100644 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts @@ -1,4 +1,4 @@ -import { AuthorizableReferenceType } from '@src/modules/authorization/types'; +import { AuthorizableReferenceType } from '@src/modules/authorization/domain/'; import { ToolContextType } from '../enum'; const typeMapping: Record = { diff --git a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts index dc4f339b4ab..7f0cb68ade8 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts @@ -1,16 +1,21 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { EntityId, LegacySchoolDo, User } from '@shared/domain'; -import { AuthorizableReferenceType, AuthorizationContext, AuthorizationService } from '@src/modules/authorization'; +import { Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; +import { AuthorizationContext, AuthorizationService } from '@src/modules/authorization'; import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@src/modules/learnroom'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { ContextTypeMapper } from '../mapper'; +// import { ContextTypeMapper } from '../mapper'; @Injectable() export class ToolPermissionHelper { constructor( - @Inject(forwardRef(() => AuthorizationService)) private authorizationService: AuthorizationService, - private readonly schoolService: LegacySchoolService + @Inject(forwardRef(() => AuthorizationService)) private readonly authorizationService: AuthorizationService, + private readonly schoolService: LegacySchoolService, + // invalid dependency on this place it is in UC layer in a other module + // loading of ressources should be part of service layer + // if it must resolve different loadings based on the request it can be added in own service and use in UC + private readonly courseService: CourseService ) {} // TODO build interface to get contextDO by contextType @@ -19,21 +24,19 @@ export class ToolPermissionHelper { contextExternalTool: ContextExternalTool, context: AuthorizationContext ): Promise { + // loading of ressources should be part of the UC -> unnessasary awaits + const [authorizableUser, course]: [User, Course] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.courseService.findById(contextExternalTool.contextRef.id), + ]); + if (contextExternalTool.id) { - await this.authorizationService.checkPermissionByReferences( - userId, - AuthorizableReferenceType.ContextExternalToolEntity, - contextExternalTool.id, - context - ); + this.authorizationService.checkPermission(authorizableUser, contextExternalTool, context); } - await this.authorizationService.checkPermissionByReferences( - userId, - ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(contextExternalTool.contextRef.type), - contextExternalTool.contextRef.id, - context - ); + // const type = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(contextExternalTool.contextRef.type); + // no different types possible until it is fixed. + this.authorizationService.checkPermission(authorizableUser, course, context); } public async ensureSchoolPermissions( @@ -41,8 +44,12 @@ export class ToolPermissionHelper { schoolExternalTool: SchoolExternalTool, context: AuthorizationContext ): Promise { - const user: User = await this.authorizationService.getUserWithPermissions(userId); - const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolExternalTool.schoolId); + // loading of ressources should be part of the UC -> unnessasary awaits + const [user, school]: [User, LegacySchoolDo] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.schoolService.getSchoolById(schoolExternalTool.schoolId), + ]); + this.authorizationService.checkPermission(user, school, context); } } diff --git a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts index b1567693130..0f2a1192d57 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts @@ -2,13 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { contextExternalToolFactory, + courseFactory, legacySchoolDoFactory, schoolExternalToolFactory, setupEntities, + userFactory, } from '@shared/testing'; import { Permission, LegacySchoolDo } from '@shared/domain'; import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { LegacySchoolService } from '@src/modules/legacy-school'; +import { ForbiddenException } from '@nestjs/common'; +import { CourseService } from '@src/modules/learnroom'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ToolPermissionHelper } from './tool-permission-helper'; import { SchoolExternalTool } from '../../school-external-tool/domain'; @@ -18,6 +22,7 @@ describe('ToolPermissionHelper', () => { let helper: ToolPermissionHelper; let authorizationService: DeepMocked; + let courseService: DeepMocked; let schoolService: DeepMocked; beforeAll(async () => { @@ -29,6 +34,10 @@ describe('ToolPermissionHelper', () => { provide: AuthorizationService, useValue: createMock(), }, + { + provide: CourseService, + useValue: createMock(), + }, { provide: LegacySchoolService, useValue: createMock(), @@ -38,6 +47,7 @@ describe('ToolPermissionHelper', () => { helper = module.get(ToolPermissionHelper); authorizationService = module.get(AuthorizationService); + courseService = module.get(CourseService); schoolService = module.get(LegacySchoolService); }); @@ -50,29 +60,98 @@ describe('ToolPermissionHelper', () => { }); describe('ensureContextPermissions', () => { - describe('when context external tool is given', () => { + describe('when context external tool with id is given', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + + courseService.findById.mockResolvedValueOnce(course); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.checkPermission.mockReturnValueOnce().mockReturnValueOnce(); + + return { + user, + course, + contextExternalTool, + context, + }; + }; + + it('should check permission for context external tool', async () => { + const { user, course, contextExternalTool, context } = setup(); + + await helper.ensureContextPermissions(user.id, contextExternalTool, context); + + expect(authorizationService.checkPermission).toHaveBeenCalledTimes(2); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(1, user, contextExternalTool, context); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(2, user, course, context); + }); + + it('should return undefined', async () => { + const { user, contextExternalTool, context } = setup(); + + const result = await helper.ensureContextPermissions(user.id, contextExternalTool, context); + + expect(result).toBeUndefined(); + }); + }); + + describe('when context external tool without id is given', () => { const setup = () => { - const userId = 'userId'; + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + courseService.findById.mockResolvedValueOnce(course); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + return { - userId, + user, + course, contextExternalTool, context, }; }; it('should check permission for context external tool', async () => { - const { userId, contextExternalTool, context } = setup(); + const { user, course, contextExternalTool, context } = setup(); + + await helper.ensureContextPermissions(user.id, contextExternalTool, context); - await helper.ensureContextPermissions(userId, contextExternalTool, context); + expect(authorizationService.checkPermission).toHaveBeenCalledTimes(1); + expect(authorizationService.checkPermission).toHaveBeenCalledWith(user, course, context); + }); + }); - expect(authorizationService.checkPermissionByReferences).toHaveBeenCalledWith( - userId, - 'courses', - contextExternalTool.contextRef.id, - context + describe('when user is unauthorized', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const course = courseFactory.buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + + courseService.findById.mockResolvedValueOnce(course); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.checkPermission.mockImplementationOnce(() => { + throw new ForbiddenException(); + }); + + return { + user, + course, + contextExternalTool, + context, + }; + }; + + it('should check permission for context external tool', async () => { + const { user, contextExternalTool, context } = setup(); + + await expect(helper.ensureContextPermissions(user.id, contextExternalTool, context)).rejects.toThrowError( + new ForbiddenException() ); }); }); @@ -81,15 +160,16 @@ describe('ToolPermissionHelper', () => { describe('ensureSchoolPermissions', () => { describe('when school external tool is given', () => { const setup = () => { - const userId = 'userId'; + const user = userFactory.buildWithId(); const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); const school: LegacySchoolDo = legacySchoolDoFactory.build({ id: schoolExternalTool.schoolId }); schoolService.getSchoolById.mockResolvedValue(school); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); return { - userId, + user, schoolExternalTool, school, context, @@ -97,11 +177,20 @@ describe('ToolPermissionHelper', () => { }; it('should check permission for school external tool', async () => { - const { userId, schoolExternalTool, context, school } = setup(); + const { user, schoolExternalTool, context, school } = setup(); + + await helper.ensureSchoolPermissions(user.id, schoolExternalTool, context); + + expect(authorizationService.checkPermission).toHaveBeenCalledTimes(1); + expect(authorizationService.checkPermission).toHaveBeenCalledWith(user, school, context); + }); + + it('should return undefined', async () => { + const { user, schoolExternalTool, context } = setup(); - await helper.ensureSchoolPermissions(userId, schoolExternalTool, context); + const result = await helper.ensureSchoolPermissions(user.id, schoolExternalTool, context); - expect(authorizationService.checkPermission).toHaveBeenCalledWith(userId, school, context); + expect(result).toBeUndefined(); }); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts index 6e8ca18253f..b5584426763 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts @@ -114,29 +114,29 @@ describe('ToolContextController (API)', () => { }); }); - describe('when creation of contextExternalTool failed', () => { + describe('when user is not authorized for the requested context', () => { const setup = async () => { const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); - - const course: Course = courseFactory.buildWithId({ teachers: [teacherUser] }); - - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const school = schoolFactory.build(); + const course = courseFactory.build({ teachers: [teacherUser] }); + const otherCourse = courseFactory.build(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ schoolParameters: [], toolVersion: 1, + school, }); - const randomTestId = new ObjectId().toString(); + await em.persistAndFlush([course, otherCourse, school, teacherUser, teacherAccount, schoolExternalToolEntity]); + em.clear(); + const postParams: ContextExternalToolPostParams = { - schoolToolId: randomTestId, - contextId: randomTestId, + schoolToolId: school.id, + contextId: otherCourse.id, contextType: ToolContextType.COURSE, parameters: [], toolVersion: 1, }; - await em.persistAndFlush([course, teacherUser, teacherAccount, schoolExternalToolEntity]); - em.clear(); - const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); return { @@ -145,12 +145,13 @@ describe('ToolContextController (API)', () => { }; }; - it('when user is not authorized, it should return forbidden', async () => { + it('it should return forbidden', async () => { const { postParams, loggedInClient } = await setup(); const response = await loggedInClient.post().send(postParams); expect(response.statusCode).toEqual(HttpStatus.FORBIDDEN); + // expected body is missed }); }); }); @@ -204,23 +205,26 @@ describe('ToolContextController (API)', () => { describe('when deletion of contextExternalTool failed', () => { const setup = async () => { const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); - - const course: Course = courseFactory.buildWithId({ teachers: [teacherUser] }); - - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const course = courseFactory.buildWithId({ teachers: [teacherUser] }); + const schoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ toolVersion: 1, }); - - const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + const contextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ schoolTool: schoolExternalToolEntity, + contextId: course.id, toolVersion: 1, }); - em.persist([course, teacherUser, teacherAccount, schoolExternalToolEntity, contextExternalToolEntity]); - await em.flush(); + await em.persistAndFlush([ + course, + teacherUser, + teacherAccount, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); em.clear(); - const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); + const loggedInClient = await testApiClient.login(teacherAccount); return { contextExternalToolEntity, @@ -234,6 +238,7 @@ describe('ToolContextController (API)', () => { const result = await loggedInClient.delete(`${contextExternalToolEntity.id}`); expect(result.statusCode).toEqual(HttpStatus.FORBIDDEN); + // result.body is missed }); }); }); @@ -543,37 +548,29 @@ describe('ToolContextController (API)', () => { describe('when user has not the required permission', () => { const setup = async () => { - const school: SchoolEntity = schoolFactory.buildWithId(); - + const school = schoolFactory.build(); const { studentUser, studentAccount } = UserAndAccountTestFactory.buildStudent({ school }); - - const course: Course = courseFactory.buildWithId({ + const course = courseFactory.build({ teachers: [studentUser], school, }); - - const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId(); - const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const externalTool: ExternalToolEntity = externalToolEntityFactory.build(); + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ school, tool: externalTool, toolVersion: 1, }); - const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + + await em.persistAndFlush([school, course, externalTool, schoolExternalTool, studentAccount, studentUser]); + + const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.build({ contextId: course.id, schoolTool: schoolExternalTool, toolVersion: 1, contextType: ContextExternalToolType.COURSE, }); - await em.persistAndFlush([ - school, - course, - externalTool, - schoolExternalTool, - contextExternalTool, - studentAccount, - studentUser, - ]); + await em.persistAndFlush([contextExternalTool]); em.clear(); const loggedInClient: TestApiClient = await testApiClient.login(studentAccount); @@ -591,6 +588,7 @@ describe('ToolContextController (API)', () => { const response = await loggedInClient.get(`${contextExternalTool.id}`); expect(response.status).toEqual(HttpStatus.FORBIDDEN); + // body check }); }); }); @@ -688,34 +686,37 @@ describe('ToolContextController (API)', () => { describe('when the user is not authorized', () => { const setup = async () => { - const roleWithoutPermission = roleFactory.buildWithId(); - const { teacherUser, teacherAccount } = UserAndAccountTestFactory.buildTeacher(); - + const roleWithoutPermission = roleFactory.build(); teacherUser.roles.set([roleWithoutPermission]); - const school: SchoolEntity = schoolFactory.buildWithId(); - - const course: Course = courseFactory.buildWithId({ teachers: [teacherUser], school }); - + const school = schoolFactory.build(); + const course = courseFactory.build({ teachers: [teacherUser], school }); const contextParameter = customParameterEntityFactory.build({ scope: CustomParameterScope.CONTEXT, regex: 'testValue123', }); - - const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.build({ parameters: [contextParameter], version: 2, }); - - const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ tool: externalToolEntity, school, schoolParameters: [], toolVersion: 2, }); - const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + await em.persistAndFlush([ + course, + school, + teacherUser, + teacherAccount, + externalToolEntity, + schoolExternalToolEntity, + ]); + + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.build({ schoolTool: schoolExternalToolEntity, contextId: course.id, contextType: ContextExternalToolType.COURSE, @@ -724,6 +725,10 @@ describe('ToolContextController (API)', () => { toolVersion: 1, }); + await em.persistAndFlush([contextExternalToolEntity]); + + em.clear(); + const postParams: ContextExternalToolPostParams = { schoolToolId: schoolExternalToolEntity.id, contextId: course.id, @@ -738,17 +743,6 @@ describe('ToolContextController (API)', () => { toolVersion: 2, }; - await em.persistAndFlush([ - course, - school, - teacherUser, - teacherAccount, - externalToolEntity, - schoolExternalToolEntity, - contextExternalToolEntity, - ]); - em.clear(); - const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); return { loggedInClient, postParams, contextExternalToolEntity }; @@ -760,6 +754,7 @@ describe('ToolContextController (API)', () => { const response = await loggedInClient.put(`${contextExternalToolEntity.id}`).send(postParams); expect(response.status).toEqual(HttpStatus.FORBIDDEN); + // body missed }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts index 491e7d9f2d6..3e7d8199fa2 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts @@ -58,6 +58,7 @@ export class ToolContextController { ContextExternalToolResponseMapper.mapContextExternalToolResponse(createdTool); this.logger.debug(`ContextExternalTool with id ${response.id} was created by user with id ${currentUser.userId}`); + return response; } diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts index 801765f80e5..8be134b80f2 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts @@ -5,8 +5,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, Permission, User } from '@shared/domain'; import { contextExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { ForbiddenLoggableException } from '@src/modules/authorization/errors/forbidden.loggable-exception'; +import { + Action, + AuthorizationContextBuilder, + AuthorizationService, + ForbiddenLoggableException, +} from '@src/modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts index 04002cf9fc6..9b6e3e3fe66 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts @@ -61,18 +61,20 @@ export class ContextExternalToolUc { return saved; } - async deleteContextExternalTool(userId: EntityId, contextExternalToolId: EntityId): Promise { + public async deleteContextExternalTool(userId: EntityId, contextExternalToolId: EntityId): Promise { const tool: ContextExternalTool = await this.contextExternalToolService.findById(contextExternalToolId); - const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); + const context = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); await this.toolPermissionHelper.ensureContextPermissions(userId, tool, context); - const promise: Promise = this.contextExternalToolService.deleteContextExternalTool(tool); - - return promise; + await this.contextExternalToolService.deleteContextExternalTool(tool); } - async getContextExternalToolsForContext(userId: EntityId, contextType: ToolContextType, contextId: string) { + public async getContextExternalToolsForContext( + userId: EntityId, + contextType: ToolContextType, + contextId: string + ): Promise { const tools: ContextExternalTool[] = await this.contextExternalToolService.findAllByContext( new ContextRef({ id: contextId, type: contextType }) ); diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index 70ec7d4be67..d0030bf7b46 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -33,7 +33,6 @@ describe('ToolConfigurationController (API)', () => { let app: INestApplication; let em: EntityManager; let orm: MikroORM; - let testApiClient: TestApiClient; beforeAll(async () => { @@ -346,22 +345,19 @@ describe('ToolConfigurationController (API)', () => { describe('GET tools/school-external-tools/:schoolExternalToolId/configuration-template', () => { describe('when the user is not authorized', () => { const setup = async () => { - const school: SchoolEntity = schoolFactory.buildWithId(); - - const user: User = userFactory.buildWithId({ school, roles: [] }); - const account: Account = accountFactory.buildWithId({ userId: user.id }); - - const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId(); - - const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const school = schoolFactory.build(); + // not on same school like the tool + const { adminAccount, adminUser } = UserAndAccountTestFactory.buildAdmin({}, []); + const externalTool: ExternalToolEntity = externalToolEntityFactory.build(); + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ school, tool: externalTool, }); - await em.persistAndFlush([user, account, school, externalTool, schoolExternalTool]); + await em.persistAndFlush([adminAccount, adminUser, school, externalTool, schoolExternalTool]); em.clear(); - const loggedInClient: TestApiClient = await testApiClient.login(account); + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); return { loggedInClient, @@ -477,51 +473,43 @@ describe('ToolConfigurationController (API)', () => { describe('GET tools/context-external-tools/:contextExternalToolId/configuration-template', () => { describe('when the user is not authorized', () => { const setup = async () => { - const school: SchoolEntity = schoolFactory.buildWithId(); - - const course: Course = courseFactory.buildWithId(); - - const user: User = userFactory.buildWithId({ school, roles: [] }); - const account: Account = accountFactory.buildWithId({ userId: user.id }); - - const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId(); - - const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const school = schoolFactory.build(); + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.SCHOOL_TOOL_ADMIN]); + // user is not part of the course + const course = courseFactory.build(); + const externalTool: ExternalToolEntity = externalToolEntityFactory.build(); + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ school, tool: externalTool, }); - const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + await em.persistAndFlush([course, adminUser, adminAccount, school, externalTool, schoolExternalTool]); + + const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.build({ schoolTool: schoolExternalTool, + contextId: course.id, }); - await em.persistAndFlush([ - user, - account, - school, - externalTool, - schoolExternalTool, - contextExternalTool, - course, - ]); + await em.persistAndFlush([contextExternalTool]); em.clear(); - const loggedInClient: TestApiClient = await testApiClient.login(account); + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); return { loggedInClient, - contextExternalTool, + contextExternalToolId: contextExternalTool.id, }; }; it('should return a forbidden status', async () => { - const { loggedInClient, contextExternalTool } = await setup(); + const { loggedInClient, contextExternalToolId } = await setup(); const response: Response = await loggedInClient.get( - `context-external-tools/${contextExternalTool.id}/configuration-template` + `context-external-tools/${contextExternalToolId}/configuration-template` ); expect(response.status).toEqual(HttpStatus.FORBIDDEN); + // body }); }); @@ -607,36 +595,26 @@ describe('ToolConfigurationController (API)', () => { describe('when tool is hidden', () => { const setup = async () => { - const school: SchoolEntity = schoolFactory.buildWithId(); - + const school = schoolFactory.build(); const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }, [ Permission.CONTEXT_TOOL_ADMIN, ]); - - const course: Course = courseFactory.buildWithId({ school, teachers: [teacherUser] }); - - const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId({ isHidden: true }); - - const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + const course = courseFactory.build({ school, teachers: [teacherUser] }); + const externalTool: ExternalToolEntity = externalToolEntityFactory.build({ isHidden: true }); + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ school, tool: externalTool, }); - const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + await em.persistAndFlush([teacherUser, school, teacherAccount, externalTool, schoolExternalTool, course]); + + const contextExternalTool: ContextExternalToolEntity = contextExternalToolEntityFactory.build({ schoolTool: schoolExternalTool, contextType: ContextExternalToolType.COURSE, contextId: course.id, }); - await em.persistAndFlush([ - teacherUser, - school, - teacherAccount, - externalTool, - schoolExternalTool, - contextExternalTool, - course, - ]); + await em.persistAndFlush([contextExternalTool]); em.clear(); const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 16dd9626d0c..1e91214ccda 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -140,8 +140,8 @@ export class ExternalToolConfigurationUc { const contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( contextExternalToolId ); - const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( diff --git a/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts b/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts index 6799a50bca2..95d8a42ce1f 100644 --- a/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts +++ b/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { LearnroomModule } from '@src/modules/learnroom'; import { LegacySchoolModule } from '@src/modules/legacy-school'; import { PseudonymModule } from '@src/modules/pseudonym'; @@ -18,7 +18,7 @@ import { BasicToolLaunchStrategy, Lti11ToolLaunchStrategy, OAuth2ToolLaunchStrat ContextExternalToolModule, LegacySchoolModule, UserModule, - PseudonymModule, + forwardRef(() => PseudonymModule), // i do not like this solution, the root problem is on other place but not detectable for me LearnroomModule, ], providers: [ diff --git a/apps/server/src/modules/video-conference/mapper/video-conference.mapper.ts b/apps/server/src/modules/video-conference/mapper/video-conference.mapper.ts index 727cfdfce7d..1f51a8cb3ff 100644 --- a/apps/server/src/modules/video-conference/mapper/video-conference.mapper.ts +++ b/apps/server/src/modules/video-conference/mapper/video-conference.mapper.ts @@ -1,5 +1,4 @@ -import { Permission, VideoConferenceScope } from '@shared/domain'; -import { AuthorizableReferenceType } from '@src/modules/authorization'; +import { Permission } from '@shared/domain'; import { BBBRole } from '../bbb'; import { VideoConferenceCreateParams, @@ -16,11 +15,6 @@ export const PermissionMapping = { [BBBRole.VIEWER]: Permission.JOIN_MEETING, }; -export const PermissionScopeMapping = { - [VideoConferenceScope.COURSE]: AuthorizableReferenceType.Course, - [VideoConferenceScope.EVENT]: AuthorizableReferenceType.Team, -}; - const stateMapping = { [VideoConferenceState.NOT_STARTED]: VideoConferenceStateResponse.NOT_STARTED, [VideoConferenceState.RUNNING]: VideoConferenceStateResponse.RUNNING, diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts index b0c28b6a3c9..280a11976d7 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts @@ -14,14 +14,10 @@ import { } from '@shared/domain'; import { CalendarEventDto, CalendarService } from '@shared/infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { - AuthorizableReferenceType, - AuthorizationContextBuilder, - AuthorizationService, -} from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; import { LegacySchoolService } from '@src/modules/legacy-school'; import { UserService } from '@src/modules/user'; -import { courseFactory, roleFactory, setupEntities, userDoFactory } from '@shared/testing'; +import { courseFactory, roleFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory'; import { ObjectId } from 'bson'; import { teamFactory } from '@shared/testing/factory/team.factory'; @@ -332,64 +328,160 @@ describe('VideoConferenceService', () => { }); describe('checkPermission', () => { - const setup = () => { - const userId = 'user-id'; - const conferenceScope = VideoConferenceScope.COURSE; - const entityId = 'entity-id'; + describe('when user has START_MEETING permission and is in course scope', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const entity = courseFactory.buildWithId(); + const conferenceScope = VideoConferenceScope.COURSE; - return { - userId, - conferenceScope, - entityId, + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(false); + courseService.findById.mockResolvedValueOnce(entity); + + return { + user, + userId: user.id, + entity, + entityId: entity.id, + conferenceScope, + }; }; - }; - describe('when user has START_MEETING permission', () => { + it('should call the correct authorization order', async () => { + const { user, entity, userId, conferenceScope, entityId } = setup(); + + await service.determineBbbRole(userId, entityId, conferenceScope); + + expect(authorizationService.hasPermission).toHaveBeenCalledWith( + user, + entity, + AuthorizationContextBuilder.read([Permission.START_MEETING]) + ); + }); + it('should return BBBRole.MODERATOR', async () => { const { userId, conferenceScope, entityId } = setup(); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(true); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(false); - - const result: BBBRole = await service.determineBbbRole(userId, entityId, conferenceScope); + const result = await service.determineBbbRole(userId, entityId, conferenceScope); expect(result).toBe(BBBRole.MODERATOR); - expect(authorizationService.hasPermissionByReferences).toHaveBeenCalledWith( - userId, - AuthorizableReferenceType.Course, - entityId, + }); + }); + + // can be removed when team / course / user is passed from UC + // missing when course / team loading throw an error, but also not nessasary if it is passed to UC. + describe('when user has START_MEETING permission and is in team(event) scope', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const entity = teamFactory.buildWithId(); + const conferenceScope = VideoConferenceScope.EVENT; + + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + authorizationService.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(false); + teamsRepo.findById.mockResolvedValueOnce(entity); + + return { + user, + userId: user.id, + entity, + entityId: entity.id, + conferenceScope, + }; + }; + + it('should call the correct authorization order', async () => { + const { user, entity, userId, conferenceScope, entityId } = setup(); + + await service.determineBbbRole(userId, entityId, conferenceScope); + + expect(authorizationService.hasPermission).toHaveBeenCalledWith( + user, + entity, AuthorizationContextBuilder.read([Permission.START_MEETING]) ); }); + + it('should return BBBRole.MODERATOR', async () => { + const { userId, conferenceScope, entityId } = setup(); + + const result = await service.determineBbbRole(userId, entityId, conferenceScope); + + expect(result).toBe(BBBRole.MODERATOR); + }); }); describe('when user has JOIN_MEETING permission', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const entity = courseFactory.buildWithId(); + const conferenceScope = VideoConferenceScope.COURSE; + + authorizationService.hasPermission.mockReturnValueOnce(false).mockReturnValueOnce(true); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + courseService.findById.mockResolvedValueOnce(entity); + + return { + user, + userId: user.id, + entity, + entityId: entity.id, + conferenceScope, + }; + }; + + it('should call the correct authorization order', async () => { + const { user, entity, userId, conferenceScope, entityId } = setup(); + + await service.determineBbbRole(userId, entityId, conferenceScope); + + expect(authorizationService.hasPermission).toHaveBeenNthCalledWith( + 1, + user, + entity, + AuthorizationContextBuilder.read([Permission.START_MEETING]) + ); + expect(authorizationService.hasPermission).toHaveBeenNthCalledWith( + 2, + user, + entity, + AuthorizationContextBuilder.read([Permission.JOIN_MEETING]) + ); + }); + it('should return BBBRole.VIEWER', async () => { const { userId, conferenceScope, entityId } = setup(); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(false); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(true); - const result: BBBRole = await service.determineBbbRole(userId, entityId, conferenceScope); + const result = await service.determineBbbRole(userId, entityId, conferenceScope); expect(result).toBe(BBBRole.VIEWER); - expect(authorizationService.hasPermissionByReferences).toHaveBeenCalledWith( - userId, - AuthorizableReferenceType.Course, - entityId, - AuthorizationContextBuilder.read([Permission.JOIN_MEETING]) - ); }); }); describe('when user has neither START_MEETING nor JOIN_MEETING permission', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const entity = courseFactory.buildWithId(); + const conferenceScope = VideoConferenceScope.COURSE; + + authorizationService.hasPermission.mockReturnValueOnce(false).mockReturnValueOnce(false); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + courseService.findById.mockResolvedValueOnce(entity); + + return { + user, + userId: user.id, + entity, + entityId: entity.id, + conferenceScope, + }; + }; + it('should throw a ForbiddenException', async () => { const { userId, conferenceScope, entityId } = setup(); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(false); - authorizationService.hasPermissionByReferences.mockResolvedValueOnce(false); - const func = () => service.determineBbbRole(userId, entityId, conferenceScope); + const callDetermineBbbRole = () => service.determineBbbRole(userId, entityId, conferenceScope); - await expect(func).rejects.toThrow(new ForbiddenException(ErrorStatus.INSUFFICIENT_PERMISSION)); + await expect(callDetermineBbbRole).rejects.toThrow(new ForbiddenException(ErrorStatus.INSUFFICIENT_PERMISSION)); }); }); }); diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.ts b/apps/server/src/modules/video-conference/service/video-conference.service.ts index 0201b150f33..69a1a7fd74f 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.ts @@ -8,6 +8,7 @@ import { SchoolFeatures, TeamEntity, TeamUserEntity, + User, UserDO, VideoConferenceDO, VideoConferenceOptionsDO, @@ -15,19 +16,13 @@ import { } from '@shared/domain'; import { CalendarEventDto, CalendarService } from '@shared/infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { - Action, - AuthorizableReferenceType, - AuthorizationContextBuilder, - AuthorizationService, -} from '@src/modules/authorization'; -import { CourseService } from '@src/modules/learnroom/service'; +import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { CourseService } from '@src/modules/learnroom'; import { LegacySchoolService } from '@src/modules/legacy-school'; import { UserService } from '@src/modules/user'; import { BBBRole } from '../bbb'; import { ErrorStatus } from '../error'; import { IVideoConferenceSettings, VideoConferenceOptions, VideoConferenceSettings } from '../interface'; -import { PermissionScopeMapping } from '../mapper/video-conference.mapper'; import { IScopeInfo, VideoConferenceState } from '../uc/dto'; @Injectable() @@ -97,39 +92,59 @@ export class VideoConferenceService { return isExpert; } - async determineBbbRole(userId: EntityId, scopeId: EntityId, scope: VideoConferenceScope): Promise { - const permissionMap: Map> = this.hasPermissions( - userId, - PermissionScopeMapping[scope], - scopeId, - [Permission.START_MEETING, Permission.JOIN_MEETING], - Action.read - ); - - if (await permissionMap.get(Permission.START_MEETING)) { - return BBBRole.MODERATOR; - } - if (await permissionMap.get(Permission.JOIN_MEETING)) { - return BBBRole.VIEWER; + // should be public to expose ressources to UC for passing it to authrisation and improve performance + private async loadScopeRessources( + scopeId: EntityId, + scope: VideoConferenceScope + ): Promise { + let scopeRessource: Course | TeamEntity | null = null; + + if (scope === VideoConferenceScope.COURSE) { + scopeRessource = await this.courseService.findById(scopeId); + } else if (scope === VideoConferenceScope.EVENT) { + scopeRessource = await this.teamsRepo.findById(scopeId); + } else { + // Need to be solve the null with throw by it self. } - throw new ForbiddenException(ErrorStatus.INSUFFICIENT_PERMISSION); + + return scopeRessource; + } + + private isNullOrUndefined(value: unknown): value is null { + return !value; } - private hasPermissions( - userId: EntityId, - entityName: AuthorizableReferenceType, - entityId: EntityId, - permissions: Permission[], - action: Action - ): Map> { - const returnMap: Map> = new Map(); - permissions.forEach((perm) => { - const context = - action === Action.read ? AuthorizationContextBuilder.read([perm]) : AuthorizationContextBuilder.write([perm]); - const ret = this.authorizationService.hasPermissionByReferences(userId, entityName, entityId, context); - returnMap.set(perm, ret); - }); - return returnMap; + private hasStartMeetingAndCanRead(authorizableUser: User, entity: Course | TeamEntity): boolean { + const context = AuthorizationContextBuilder.read([Permission.START_MEETING]); + const hasPermission = this.authorizationService.hasPermission(authorizableUser, entity, context); + + return hasPermission; + } + + private hasJoinMeetingAndCanRead(authorizableUser: User, entity: Course | TeamEntity): boolean { + const context = AuthorizationContextBuilder.read([Permission.JOIN_MEETING]); + const hasPermission = this.authorizationService.hasPermission(authorizableUser, entity, context); + + return hasPermission; + } + + async determineBbbRole(userId: EntityId, scopeId: EntityId, scope: VideoConferenceScope): Promise { + // ressource loading need to be move to uc + const [authorizableUser, scopeRessource]: [User, TeamEntity | Course | null] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.loadScopeRessources(scopeId, scope), + ]); + + if (!this.isNullOrUndefined(scopeRessource)) { + if (this.hasStartMeetingAndCanRead(authorizableUser, scopeRessource)) { + return BBBRole.MODERATOR; + } + if (this.hasJoinMeetingAndCanRead(authorizableUser, scopeRessource)) { + return BBBRole.VIEWER; + } + } + + throw new ForbiddenException(ErrorStatus.INSUFFICIENT_PERMISSION); } async throwOnFeaturesDisabled(schoolId: EntityId): Promise { diff --git a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts index e7a28c9a7ca..853bf0f13bc 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts @@ -38,6 +38,12 @@ export class VideoConferenceCreateUc { } private async create(currentUserId: EntityId, scope: ScopeRef, options: VideoConferenceOptions): Promise { + /* need to be replace with + const [authorizableUser, scopeRessource]: [User, TeamEntity | Course] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.videoConferenceService.loadScopeRessources(scopeId, scope), + ]); + */ const user: UserDO = await this.userService.findById(currentUserId); await this.verifyFeaturesEnabled(user.schoolId); diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts index 3680c4519da..4ff494a4cc0 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts @@ -19,9 +19,10 @@ import { CalendarEventDto } from '@shared/infra/calendar/dto/calendar-event.dto' import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; import { roleFactory, setupEntities, userDoFactory } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { AuthorizationService, LegacySchoolService, UserService } from '@src/modules'; +import { LegacySchoolService, UserService } from '@src/modules'; +import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { ICurrentUser } from '@src/modules/authentication'; -import { CourseService } from '@src/modules/learnroom/service'; +import { CourseService } from '@src/modules/learnroom'; import { IScopeInfo, VideoConference, VideoConferenceJoin, VideoConferenceState } from './dto'; import { VideoConferenceDeprecatedUc } from './video-conference-deprecated.uc'; import { @@ -63,7 +64,7 @@ describe('VideoConferenceUc', () => { let useCase: VideoConferenceDeprecatedUcSpec; let bbbService: DeepMocked; - let authorizationService: DeepMocked; + let authorizationService: DeepMocked; let videoConferenceRepo: DeepMocked; let teamsRepo: DeepMocked; let courseService: DeepMocked; @@ -118,8 +119,8 @@ describe('VideoConferenceUc', () => { useValue: createMock(), }, { - provide: AuthorizationService, - useValue: createMock(), + provide: AuthorizationReferenceService, + useValue: createMock(), }, { provide: VideoConferenceRepo, @@ -149,7 +150,7 @@ describe('VideoConferenceUc', () => { }).compile(); useCase = module.get(VideoConferenceDeprecatedUcSpec); schoolService = module.get(LegacySchoolService); - authorizationService = module.get(AuthorizationService); + authorizationService = module.get(AuthorizationReferenceService); courseService = module.get(CourseService); calendarService = module.get(CalendarService); videoConferenceRepo = module.get(VideoConferenceRepo); diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts index 7bce5b2f5a4..e6b1f11ed50 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts @@ -18,14 +18,10 @@ import { CalendarEventDto } from '@shared/infra/calendar/dto/calendar-event.dto' import { TeamsRepo } from '@shared/repo'; import { VideoConferenceRepo } from '@shared/repo/videoconference/video-conference.repo'; import { ICurrentUser } from '@src/modules/authentication'; -import { - Action, - AuthorizableReferenceType, - AuthorizationContextBuilder, - AuthorizationService, -} from '@src/modules/authorization'; -import { CourseService } from '@src/modules/learnroom/service'; +import { Action, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@src/modules/learnroom'; import { UserService } from '@src/modules/user'; import { BBBBaseMeetingConfig, @@ -62,7 +58,7 @@ export class VideoConferenceDeprecatedUc { constructor( private readonly bbbService: BBBService, - private readonly authorizationService: AuthorizationService, + private readonly authorizationReferenceService: AuthorizationReferenceService, private readonly videoConferenceRepo: VideoConferenceRepo, private readonly teamsRepo: TeamsRepo, private readonly courseService: CourseService, @@ -413,7 +409,7 @@ export class VideoConferenceDeprecatedUc { permissions.forEach((perm) => { const context = action === Action.read ? AuthorizationContextBuilder.read([perm]) : AuthorizationContextBuilder.write([perm]); - const ret = this.authorizationService.hasPermissionByReferences(userId, entityName, entityId, context); + const ret = this.authorizationReferenceService.hasPermissionByReferences(userId, entityName, entityId, context); returnMap.set(perm, ret); }); return returnMap; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts index a9799f67a89..50318c001c0 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts @@ -16,6 +16,12 @@ export class VideoConferenceEndUc { ) {} async end(currentUserId: EntityId, scope: ScopeRef): Promise> { + /* need to be replace with + const [authorizableUser, scopeRessource]: [User, TeamEntity | Course] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.videoConferenceService.loadScopeRessources(scopeId, scope), + ]); + */ const user: UserDO = await this.userService.findById(currentUserId); const userId: string = user.id as string; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts index 91ebb23ea2b..79a1f95b8d1 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts @@ -17,6 +17,12 @@ export class VideoConferenceInfoUc { ) {} async getMeetingInfo(currentUserId: EntityId, scope: ScopeRef): Promise { + /* need to be replace with + const [authorizableUser, scopeRessource]: [User, TeamEntity | Course] = await Promise.all([ + this.authorizationService.getUserWithPermissions(userId), + this.videoConferenceService.loadScopeRessources(scopeId, scope), + ]); + */ const user: UserDO = await this.userService.findById(currentUserId); await this.videoConferenceService.throwOnFeaturesDisabled(user.schoolId); diff --git a/apps/server/src/modules/video-conference/video-conference.module.ts b/apps/server/src/modules/video-conference/video-conference.module.ts index 70a999437c0..6277f0dde0a 100644 --- a/apps/server/src/modules/video-conference/video-conference.module.ts +++ b/apps/server/src/modules/video-conference/video-conference.module.ts @@ -3,6 +3,7 @@ import { HttpModule } from '@nestjs/axios'; import { CalendarModule } from '@shared/infra/calendar'; import { VideoConferenceRepo } from '@shared/repo/videoconference/video-conference.repo'; import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; import { TeamsRepo } from '@shared/repo'; import { LegacySchoolModule } from '@src/modules/legacy-school'; import { LoggerModule } from '@src/core/logger'; @@ -19,6 +20,7 @@ import { LearnroomModule } from '../learnroom'; @Module({ imports: [ AuthorizationModule, + AuthorizationReferenceModule, // can be removed wenn video-conference-deprecated is removed CalendarModule, HttpModule, LegacySchoolModule, diff --git a/apps/server/src/shared/domain/rules/index.ts b/apps/server/src/shared/domain/rules/index.ts deleted file mode 100644 index 888b2ee8501..00000000000 --- a/apps/server/src/shared/domain/rules/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BoardDoRule } from './board-do.rule'; -import { ContextExternalToolRule } from './context-external-tool.rule'; -import { CourseGroupRule } from './course-group.rule'; -import { CourseRule } from './course.rule'; -import { LessonRule } from './lesson.rule'; -import { SchoolExternalToolRule } from './school-external-tool.rule'; -import { LegacySchoolRule } from './legacy-school.rule'; -import { SubmissionRule } from './submission.rule'; -import { TaskRule } from './task.rule'; -import { TeamRule } from './team.rule'; -import { UserLoginMigrationRule } from './user-login-migration.rule'; -import { UserRule } from './user.rule'; - -export * from './board-do.rule'; -export * from './course-group.rule'; -export * from './course.rule'; -export * from './lesson.rule'; -export * from './school-external-tool.rule'; -export * from './legacy-school.rule'; -export * from './submission.rule'; -export * from './task.rule'; -export * from './team.rule'; -export * from './user.rule'; -export * from './context-external-tool.rule'; - -export const ALL_RULES = [ - LessonRule, - CourseRule, - CourseGroupRule, - LegacySchoolRule, - SubmissionRule, - TaskRule, - TeamRule, - UserRule, - SchoolExternalToolRule, - BoardDoRule, - ContextExternalToolRule, - UserLoginMigrationRule, -]; diff --git a/apps/server/src/shared/infra/antivirus/index.ts b/apps/server/src/shared/infra/antivirus/index.ts index 833c46d81a7..2816f95ee57 100644 --- a/apps/server/src/shared/infra/antivirus/index.ts +++ b/apps/server/src/shared/infra/antivirus/index.ts @@ -1,3 +1,3 @@ -export * from './interfaces'; -export * from './antivirus.module'; -export * from './antivirus.service'; +export * from './interfaces'; +export * from './antivirus.module'; +export * from './antivirus.service'; diff --git a/apps/server/src/shared/testing/test-api-client.ts b/apps/server/src/shared/testing/test-api-client.ts index 733dcdd5cfc..75be7b7dc5f 100644 --- a/apps/server/src/shared/testing/test-api-client.ts +++ b/apps/server/src/shared/testing/test-api-client.ts @@ -140,6 +140,10 @@ export class TestApiClient { } private getJwtFromResponse(response: Response): string { + if (response.error) { + const error = JSON.stringify(response.error); + throw new Error(error); + } if (!this.isAuthenticationResponse(response.body)) { const body = JSON.stringify(response.body); throw new Error(`${testReqestConst.errorMessage} ${body}`); From 7b42e1efc1c007dea7d3c745c42bbf694806483b Mon Sep 17 00:00:00 2001 From: Martin Schuhmacher <55735359+MartinSchuhmacher@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:56:47 +0200 Subject: [PATCH 04/19] BC-4293 - Topic does not copy with empty content Learning material (#4485) * copy lernstore content without set resource --- .../service/lesson-copy.service.spec.ts | 49 +++++++++++++++++++ .../lesson/service/lesson-copy.service.ts | 39 ++++++++------- .../src/shared/domain/entity/lesson.entity.ts | 2 +- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts index 39f3f07882a..6097a3b87f6 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts @@ -531,6 +531,55 @@ describe('lesson copy service', () => { }); }); + describe('when lesson contains LernStore content element without set resource', () => { + const setup = () => { + const lernStoreContent: IComponentProperties = { + title: 'text component 1', + hidden: false, + component: ComponentType.LERNSTORE, + }; + const user = userFactory.build(); + const originalCourse = courseFactory.build({ school: user.school }); + const destinationCourse = courseFactory.build({ school: user.school, teachers: [user] }); + const originalLesson = lessonFactory.build({ + course: originalCourse, + contents: [lernStoreContent], + }); + lessonRepo.findById.mockResolvedValueOnce(originalLesson); + + return { user, originalCourse, destinationCourse, originalLesson, lernStoreContent }; + }; + + it('the content should be fully copied', async () => { + const { user, destinationCourse, originalLesson, lernStoreContent } = setup(); + + const status = await copyService.copyLesson({ + originalLessonId: originalLesson.id, + destinationCourse, + user, + }); + + const copiedLessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + expect(copiedLessonContents[0]).toEqual(lernStoreContent); + }); + + it('should set content type to LESSON_CONTENT_LERNSTORE', async () => { + const { user, destinationCourse, originalLesson } = setup(); + + const status = await copyService.copyLesson({ + originalLessonId: originalLesson.id, + destinationCourse, + user, + }); + const contentsStatus = status.elements?.find((el) => el.type === CopyElementType.LESSON_CONTENT_GROUP); + expect(contentsStatus).toBeDefined(); + if (contentsStatus?.elements) { + expect(contentsStatus.elements[0].type).toEqual(CopyElementType.LESSON_CONTENT_LERNSTORE); + expect(contentsStatus.elements[0].status).toEqual(CopyStatusEnum.SUCCESS); + } + }); + }); + describe('when lesson contains geoGebra content element', () => { const setup = () => { const geoGebraContent: IComponentProperties = { diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.ts index 4a6835b05f2..cd83681a2e7 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.ts @@ -266,29 +266,32 @@ export class LessonCopyService { } private copyLernStore(element: IComponentProperties): IComponentProperties { - const resources = ((element.content as IComponentLernstoreProperties).resources ?? []).map( - ({ client, description, merlinReference, title, url }) => { - const result = { - client, - description, - merlinReference, - title, - url, - }; - return result; - } - ); - - const lernstore = { + const lernstore: IComponentProperties = { title: element.title, hidden: element.hidden, component: ComponentType.LERNSTORE, user: element.user, // TODO should be params.user - but that made the server crash, but property is normally undefined - content: { - resources, - }, }; - return lernstore as IComponentProperties; + + if (element.content) { + const resources = ((element.content as IComponentLernstoreProperties).resources ?? []).map( + ({ client, description, merlinReference, title, url }) => { + const result = { + client, + description, + merlinReference, + title, + url, + }; + return result; + } + ); + + const lernstoreContent: IComponentLernstoreProperties = { resources }; + lernstore.content = lernstoreContent; + } + + return lernstore; } private static copyGeogebra(originalElement: IComponentProperties): IComponentProperties { diff --git a/apps/server/src/shared/domain/entity/lesson.entity.ts b/apps/server/src/shared/domain/entity/lesson.entity.ts index d83cd2f182a..a47aab2a8b3 100644 --- a/apps/server/src/shared/domain/entity/lesson.entity.ts +++ b/apps/server/src/shared/domain/entity/lesson.entity.ts @@ -73,7 +73,7 @@ export type IComponentProperties = { | { component: ComponentType.ETHERPAD; content: IComponentEtherpadProperties } | { component: ComponentType.GEOGEBRA; content: IComponentGeogebraProperties } | { component: ComponentType.INTERNAL; content: IComponentInternalProperties } - | { component: ComponentType.LERNSTORE; content: IComponentLernstoreProperties } + | { component: ComponentType.LERNSTORE; content?: IComponentLernstoreProperties } | { component: ComponentType.NEXBOARD; content: IComponentNexboardProperties } ); From 0ab2dfbfb2ca4f7a2c7dd5a965c2b720674f800b Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:42:06 +0200 Subject: [PATCH 05/19] Bc 4922 cleanup exports authentication (#4487) --- .../account/controller/account.controller.ts | 3 +-- apps/server/src/modules/account/index.ts | 1 + .../src/modules/account/services/index.ts | 1 + .../controllers/login.controller.ts | 2 +- .../modules/authentication/decorator/index.ts | 1 + .../modules/authentication/errors/index.ts | 4 ++++ .../src/modules/authentication/index.ts | 4 ++-- .../authentication/interface/jwt-payload.ts | 1 + .../modules/authentication/interface/user.ts | 24 ------------------- .../services/authentication.service.ts | 9 +++---- .../api-test/board-delete.api.spec.ts | 2 +- .../api-test/card-delete.api.spec.ts | 2 +- .../controller/board-submission.controller.ts | 5 ++-- .../board/controller/board.controller.ts | 3 +-- .../board/controller/card.controller.ts | 3 +-- .../board/controller/column.controller.ts | 3 +-- .../board/controller/element.controller.ts | 3 +-- .../collaborative-storage.controller.ts | 3 +-- .../controller/files-storage.controller.ts | 3 +-- .../fwu-learning-contents.controller.ts | 2 +- .../group/controller/group.controller.ts | 3 +-- .../controller/h5p-editor.controller.ts | 2 +- .../learnroom/controller/course.controller.ts | 3 +-- .../controller/dashboard.controller.ts | 3 +-- .../learnroom/controller/rooms.controller.ts | 3 +-- .../controller/legacy-school.controller.ts | 3 +-- .../lesson/controller/lesson.controller.ts | 3 +-- .../news/controller/news.controller.ts | 3 +-- .../news/controller/team-news.controller.ts | 5 +--- .../controller/oauth-provider.controller.ts | 22 ++++++++--------- .../oauth/controller/oauth-sso.controller.ts | 7 +++--- .../controller/pseudonym.controller.ts | 3 +-- .../controller/share-token.controller.ts | 4 ++-- .../task/controller/submission.controller.ts | 3 +-- .../task/controller/task.controller.ts | 4 ++-- .../controller/tool-context.controller.ts | 3 +-- .../controller/tool-reference.controller.ts | 3 +-- .../tool-configuration.controller.ts | 3 +-- .../controller/tool.controller.ts | 3 +-- .../controller/tool-school.controller.ts | 3 +-- .../controller/tool-launch.controller.ts | 3 +-- .../controller/import-user.controller.ts | 5 +--- .../user-login-migration.controller.ts | 3 +-- .../controller/dto/resolved-user.response.ts | 9 ++++--- .../user/controller/user.controller.ts | 3 +-- .../src/modules/user/service/user.service.ts | 15 ++++++++---- .../video-conference-deprecated.controller.ts | 3 +-- .../controller/video-conference.controller.ts | 3 +-- .../request-logging.interceptor.ts | 2 +- 49 files changed, 86 insertions(+), 125 deletions(-) create mode 100644 apps/server/src/modules/account/services/index.ts create mode 100644 apps/server/src/modules/authentication/decorator/index.ts create mode 100644 apps/server/src/modules/authentication/errors/index.ts diff --git a/apps/server/src/modules/account/controller/account.controller.ts b/apps/server/src/modules/account/controller/account.controller.ts index 23c07326d82..86d1f8287f8 100644 --- a/apps/server/src/modules/account/controller/account.controller.ts +++ b/apps/server/src/modules/account/controller/account.controller.ts @@ -1,8 +1,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { AccountUc } from '../uc/account.uc'; import { AccountByIdBodyParams, diff --git a/apps/server/src/modules/account/index.ts b/apps/server/src/modules/account/index.ts index 3893bd18f88..2fa0bcc2334 100644 --- a/apps/server/src/modules/account/index.ts +++ b/apps/server/src/modules/account/index.ts @@ -1,2 +1,3 @@ export * from './account.module'; export * from './account-config'; +export { AccountService } from './services'; diff --git a/apps/server/src/modules/account/services/index.ts b/apps/server/src/modules/account/services/index.ts new file mode 100644 index 00000000000..72778be1f1e --- /dev/null +++ b/apps/server/src/modules/account/services/index.ts @@ -0,0 +1 @@ +export * from './account.service'; diff --git a/apps/server/src/modules/authentication/controllers/login.controller.ts b/apps/server/src/modules/authentication/controllers/login.controller.ts index 4db3f825acf..68af396233e 100644 --- a/apps/server/src/modules/authentication/controllers/login.controller.ts +++ b/apps/server/src/modules/authentication/controllers/login.controller.ts @@ -2,7 +2,7 @@ import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ForbiddenOperationError, ValidationError } from '@shared/common'; -import { CurrentUser } from '../decorator/auth.decorator'; +import { CurrentUser } from '../decorator'; import type { ICurrentUser, OauthCurrentUser } from '../interface'; import { LoginDto } from '../uc/dto'; import { LoginUc } from '../uc/login.uc'; diff --git a/apps/server/src/modules/authentication/decorator/index.ts b/apps/server/src/modules/authentication/decorator/index.ts new file mode 100644 index 00000000000..9795c6d38ee --- /dev/null +++ b/apps/server/src/modules/authentication/decorator/index.ts @@ -0,0 +1 @@ +export * from './auth.decorator'; diff --git a/apps/server/src/modules/authentication/errors/index.ts b/apps/server/src/modules/authentication/errors/index.ts new file mode 100644 index 00000000000..d87d53df8bf --- /dev/null +++ b/apps/server/src/modules/authentication/errors/index.ts @@ -0,0 +1,4 @@ +export * from './brute-force.error'; +export * from './ldap-connection.error'; +export * from './school-in-migration.error'; +export * from './unauthorized.loggable-exception'; diff --git a/apps/server/src/modules/authentication/index.ts b/apps/server/src/modules/authentication/index.ts index eaaa9dbb61a..904c64ff97b 100644 --- a/apps/server/src/modules/authentication/index.ts +++ b/apps/server/src/modules/authentication/index.ts @@ -1,2 +1,2 @@ -export * from './interface'; -export * from './guard/jwt-auth.guard'; +export { ICurrentUser } from './interface'; +export { JWT, CurrentUser, Authenticate } from './decorator'; diff --git a/apps/server/src/modules/authentication/interface/jwt-payload.ts b/apps/server/src/modules/authentication/interface/jwt-payload.ts index ed69630ccde..aad11700e60 100644 --- a/apps/server/src/modules/authentication/interface/jwt-payload.ts +++ b/apps/server/src/modules/authentication/interface/jwt-payload.ts @@ -5,6 +5,7 @@ export interface CreateJwtPayload { roles: string[]; systemId?: string; // without this the user needs to change his PW during first login support?: boolean; + // support UserId is missed see featherJS } export interface JwtPayload extends CreateJwtPayload { diff --git a/apps/server/src/modules/authentication/interface/user.ts b/apps/server/src/modules/authentication/interface/user.ts index e2874d1a8e0..a070367a43b 100644 --- a/apps/server/src/modules/authentication/interface/user.ts +++ b/apps/server/src/modules/authentication/interface/user.ts @@ -1,29 +1,5 @@ import { EntityId } from '@shared/domain'; -export interface IRole { - name: string; - - id: string; -} - -export interface IResolvedUser { - firstName: string; - - lastName: string; - - id: string; - - createdAt: Date; - - updatedAt: Date; - - roles: IRole[]; - - permissions: string[]; - - schoolId: string; -} - export interface ICurrentUser { /** authenticated users id */ userId: EntityId; diff --git a/apps/server/src/modules/authentication/services/authentication.service.ts b/apps/server/src/modules/authentication/services/authentication.service.ts index f4bdf319e6b..f2c25ff9e9d 100644 --- a/apps/server/src/modules/authentication/services/authentication.service.ts +++ b/apps/server/src/modules/authentication/services/authentication.service.ts @@ -1,14 +1,15 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; -import { AccountService } from '@src/modules/account/services/account.service'; +import { AccountService } from '@src/modules/account'; +// invalid import import { AccountDto } from '@src/modules/account/services/dto'; -import { JwtValidationAdapter } from '@src/modules/authentication/strategy/jwt-validation.adapter'; +// invalid import, can produce dependency cycles import type { IServerConfig } from '@src/modules/server'; import { randomUUID } from 'crypto'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import { BruteForceError } from '../errors/brute-force.error'; -import { UnauthorizedLoggableException } from '../errors/unauthorized.loggable-exception'; +import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; +import { BruteForceError, UnauthorizedLoggableException } from '../errors'; import { CreateJwtPayload } from '../interface/jwt-payload'; import { LoginDto } from '../uc/dto'; diff --git a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts index cd5a1d5c34b..24bb1c69999 100644 --- a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts @@ -13,7 +13,7 @@ import { } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@src/modules/server'; import { Request } from 'express'; import request from 'supertest'; import { BoardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts index 297fc296998..2e38143ebc1 100644 --- a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts @@ -15,7 +15,7 @@ import { } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@src/modules/server'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/board-submission.controller.ts b/apps/server/src/modules/board/controller/board-submission.controller.ts index 56c6ae76ce2..f7b7e5da8ca 100644 --- a/apps/server/src/modules/board/controller/board-submission.controller.ts +++ b/apps/server/src/modules/board/controller/board-submission.controller.ts @@ -1,9 +1,8 @@ import { Body, Controller, ForbiddenException, Get, HttpCode, NotFoundException, Param, Patch } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; -import { SubmissionsResponse } from '@src/modules/board/controller/dto/submission-item/submissions.response'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { SubmissionsResponse } from './dto/submission-item/submissions.response'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { SubmissionItemUc } from '../uc/submission-item.uc'; diff --git a/apps/server/src/modules/board/controller/board.controller.ts b/apps/server/src/modules/board/controller/board.controller.ts index f52d03142d7..16cee839964 100644 --- a/apps/server/src/modules/board/controller/board.controller.ts +++ b/apps/server/src/modules/board/controller/board.controller.ts @@ -12,8 +12,7 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { BoardUc } from '../uc'; import { BoardResponse, BoardUrlParams, ColumnResponse, RenameBodyParams } from './dto'; import { BoardContextResponse } from './dto/board/board-context.reponse'; diff --git a/apps/server/src/modules/board/controller/card.controller.ts b/apps/server/src/modules/board/controller/card.controller.ts index 38a979dbf1e..94007d9296e 100644 --- a/apps/server/src/modules/board/controller/card.controller.ts +++ b/apps/server/src/modules/board/controller/card.controller.ts @@ -14,8 +14,7 @@ import { } from '@nestjs/common'; import { ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { BoardUc, CardUc } from '../uc'; import { AnyContentElementResponse, diff --git a/apps/server/src/modules/board/controller/column.controller.ts b/apps/server/src/modules/board/controller/column.controller.ts index 57359f4cc93..fd7239e517a 100644 --- a/apps/server/src/modules/board/controller/column.controller.ts +++ b/apps/server/src/modules/board/controller/column.controller.ts @@ -12,8 +12,7 @@ import { } from '@nestjs/common'; import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { BoardUc } from '../uc'; import { CardResponse, ColumnUrlParams, MoveColumnBodyParams, RenameBodyParams } from './dto'; import { CardResponseMapper } from './mapper'; diff --git a/apps/server/src/modules/board/controller/element.controller.ts b/apps/server/src/modules/board/controller/element.controller.ts index 2dacd2cf539..25a3c553ef9 100644 --- a/apps/server/src/modules/board/controller/element.controller.ts +++ b/apps/server/src/modules/board/controller/element.controller.ts @@ -12,8 +12,7 @@ import { } from '@nestjs/common'; import { ApiBody, ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { diff --git a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts index 8293ea7452f..ecfd50ffda2 100644 --- a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts +++ b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts @@ -1,8 +1,7 @@ import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { Body, Controller, Param, Patch } from '@nestjs/common'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '../../authentication/interface/user'; import { CollaborativeStorageUc } from '../uc/collaborative-storage.uc'; import { TeamPermissionsBody } from './dto/team-permissions.body.params'; import { TeamRoleDto } from './dto/team-role.params'; diff --git a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts index c1c17d685b4..a8bd1b35e9b 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts @@ -24,8 +24,7 @@ import { import { ApiConsumes, ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError, RequestLoggingInterceptor } from '@shared/common'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { Request, Response } from 'express'; import { GetFileResponse } from '../interface'; import { FilesStorageMapper } from '../mapper'; diff --git a/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts b/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts index 169d9d136b6..8dedd3e5f96 100644 --- a/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts +++ b/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts @@ -10,7 +10,7 @@ import { StreamableFile, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate } from '@src/modules/authentication'; import { Request, Response } from 'express'; import { FwuLearningContentsUc } from '../uc/fwu-learning-contents.uc'; import { GetFwuLearningContentParams } from './dto/fwu-learning-contents.params'; diff --git a/apps/server/src/modules/group/controller/group.controller.ts b/apps/server/src/modules/group/controller/group.controller.ts index e810e200d85..344fbe6b98f 100644 --- a/apps/server/src/modules/group/controller/group.controller.ts +++ b/apps/server/src/modules/group/controller/group.controller.ts @@ -3,8 +3,7 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; import { Page } from '@shared/domain'; import { ErrorResponse } from '@src/core/error/dto'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { GroupUc } from '../uc'; import { ClassInfoDto } from '../uc/dto'; import { ClassInfoSearchListResponse, ClassSortParams } from './dto'; diff --git a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts index 9f2a15c1a1d..6161b7d7c23 100644 --- a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts +++ b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts @@ -1,7 +1,7 @@ import { BadRequestException, Controller, ForbiddenException, Get, InternalServerErrorException } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { Authenticate } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate } from '@src/modules/authentication'; // Dummy html response so we can test i-frame integration const dummyResponse = (title: string) => ` diff --git a/apps/server/src/modules/learnroom/controller/course.controller.ts b/apps/server/src/modules/learnroom/controller/course.controller.ts index 14ae2b595f0..7b09111edff 100644 --- a/apps/server/src/modules/learnroom/controller/course.controller.ts +++ b/apps/server/src/modules/learnroom/controller/course.controller.ts @@ -1,8 +1,7 @@ import { Controller, Get, NotFoundException, Param, Query, Res, StreamableFile } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { PaginationParams } from '@shared/controller/'; -import { ICurrentUser } from '@src/modules/authentication'; import { Response } from 'express'; import { ConfigService } from '@nestjs/config'; import { CourseUc } from '../uc/course.uc'; diff --git a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts index dae1120be8a..da667a85f20 100644 --- a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts +++ b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { DashboardMapper } from '../mapper/dashboard.mapper'; import { DashboardUc } from '../uc/dashboard.uc'; import { DashboardResponse, DashboardUrlParams, MoveElementParams, PatchGroupParams } from './dto'; diff --git a/apps/server/src/modules/learnroom/controller/rooms.controller.ts b/apps/server/src/modules/learnroom/controller/rooms.controller.ts index bf0013dbc54..e01950e7570 100644 --- a/apps/server/src/modules/learnroom/controller/rooms.controller.ts +++ b/apps/server/src/modules/learnroom/controller/rooms.controller.ts @@ -1,8 +1,7 @@ import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { RequestTimeout } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; import { serverConfig } from '@src/modules/server/server.config'; import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper'; diff --git a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts index a91d8662752..91d6892741e 100644 --- a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts +++ b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts @@ -6,8 +6,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { MigrationMapper } from '../mapper/migration.mapper'; import { OauthMigrationDto } from '../uc/dto/oauth-migration.dto'; import { LegacySchoolUc } from '../uc'; diff --git a/apps/server/src/modules/lesson/controller/lesson.controller.ts b/apps/server/src/modules/lesson/controller/lesson.controller.ts index 8082060cbb7..7b65eb8afc1 100644 --- a/apps/server/src/modules/lesson/controller/lesson.controller.ts +++ b/apps/server/src/modules/lesson/controller/lesson.controller.ts @@ -1,7 +1,6 @@ import { Controller, Delete, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { LessonUC } from '../uc'; import { LessonUrlParams } from './dto'; diff --git a/apps/server/src/modules/news/controller/news.controller.ts b/apps/server/src/modules/news/controller/news.controller.ts index 2d2781fbef5..325a5c3f6fc 100644 --- a/apps/server/src/modules/news/controller/news.controller.ts +++ b/apps/server/src/modules/news/controller/news.controller.ts @@ -1,8 +1,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc/news.uc'; import { diff --git a/apps/server/src/modules/news/controller/team-news.controller.ts b/apps/server/src/modules/news/controller/team-news.controller.ts index 7fad91e88c2..29199932eaf 100644 --- a/apps/server/src/modules/news/controller/team-news.controller.ts +++ b/apps/server/src/modules/news/controller/team-news.controller.ts @@ -1,10 +1,7 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; - +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; import { PaginationParams } from '@shared/controller'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; - import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc'; import { FilterNewsParams, NewsListResponse, TeamUrlParams } from './dto'; diff --git a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts index b603739357a..5c1a0e8e1e8 100644 --- a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts +++ b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts @@ -1,22 +1,22 @@ -import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { Configuration } from '@hpi-schul-cloud/commons'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; -import { OauthProviderLogoutFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; -import { OauthProviderLoginFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.login-flow.uc'; -import { OauthProviderResponseMapper } from '@src/modules/oauth-provider/mapper/oauth-provider-response.mapper'; -import { OauthProviderConsentFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; +import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +// import should be @shared/infra/oauth-provider import { ProviderConsentResponse, ProviderLoginResponse, ProviderOauthClient, ProviderRedirectResponse, + ProviderConsentSessionResponse, } from '@shared/infra/oauth-provider/dto'; -import { ConsentResponse } from '@src/modules/oauth-provider/controller/dto/response/consent.response'; -import { ICurrentUser } from '@src/modules/authentication'; -import { OauthProviderClientCrudUc } from '@src/modules/oauth-provider/uc/oauth-provider.client-crud.uc'; -import { RedirectResponse } from '@src/modules/oauth-provider/controller/dto/response/redirect.response'; -import { ProviderConsentSessionResponse } from '@shared/infra/oauth-provider/dto/response/consent-session.response'; import { ApiTags } from '@nestjs/swagger'; +import { OauthProviderLogoutFlowUc } from '../uc/oauth-provider.logout-flow.uc'; +import { OauthProviderLoginFlowUc } from '../uc/oauth-provider.login-flow.uc'; +import { OauthProviderResponseMapper } from '../mapper/oauth-provider-response.mapper'; +import { OauthProviderConsentFlowUc } from '../uc/oauth-provider.consent-flow.uc'; +import { ConsentResponse } from './dto/response/consent.response'; +import { OauthProviderClientCrudUc } from '../uc/oauth-provider.client-crud.uc'; +import { RedirectResponse } from './dto/response/redirect.response'; import { OauthProviderUc } from '../uc/oauth-provider.uc'; import { AcceptQuery, diff --git a/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts b/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts index 3c30984cc7a..a2c1b116d6d 100644 --- a/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts +++ b/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts @@ -14,13 +14,12 @@ import { import { ApiOkResponse, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ISession } from '@shared/domain/types/session'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser, JWT } from '@src/modules/authentication/decorator/auth.decorator'; -import { UserMigrationResponse } from '@src/modules/oauth/controller/dto/user-migration.response'; -import { HydraOauthUc } from '@src/modules/oauth/uc/hydra-oauth.uc'; +import { ICurrentUser, Authenticate, CurrentUser, JWT } from '@src/modules/authentication'; import { OAuthMigrationError } from '@src/modules/user-login-migration/error/oauth-migration.error'; import { MigrationDto } from '@src/modules/user-login-migration/service/dto'; import { CookieOptions, Request, Response } from 'express'; +import { HydraOauthUc } from '../uc/hydra-oauth.uc'; +import { UserMigrationResponse } from './dto/user-migration.response'; import { OAuthSSOError } from '../loggable/oauth-sso.error'; import { OAuthTokenDto } from '../interface'; import { OauthLoginStateMapper } from '../mapper/oauth-login-state.mapper'; diff --git a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts index 02aade8a446..16d55fba9db 100644 --- a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts +++ b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts @@ -7,8 +7,7 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { Pseudonym } from '@shared/domain'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; -import { ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { PseudonymMapper } from '../mapper/pseudonym.mapper'; import { PseudonymUc } from '../uc'; import { PseudonymResponse } from './dto'; diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.ts b/apps/server/src/modules/sharing/controller/share-token.controller.ts index bd627977aad..b4120dc02ae 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.ts @@ -11,9 +11,9 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError, RequestTimeout } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; +// invalid import can produce dependency cycles import { serverConfig } from '@src/modules/server/server.config'; import { ShareTokenInfoResponseMapper, ShareTokenResponseMapper } from '../mapper'; import { ShareTokenUC } from '../uc'; diff --git a/apps/server/src/modules/task/controller/submission.controller.ts b/apps/server/src/modules/task/controller/submission.controller.ts index df1d08f9953..66d65261ded 100644 --- a/apps/server/src/modules/task/controller/submission.controller.ts +++ b/apps/server/src/modules/task/controller/submission.controller.ts @@ -1,7 +1,6 @@ import { Controller, Delete, Get, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { SubmissionMapper } from '../mapper'; import { SubmissionUc } from '../uc'; import { SubmissionStatusListResponse, SubmissionUrlParams, TaskUrlParams } from './dto'; diff --git a/apps/server/src/modules/task/controller/task.controller.ts b/apps/server/src/modules/task/controller/task.controller.ts index b62400d0426..c438526ede1 100644 --- a/apps/server/src/modules/task/controller/task.controller.ts +++ b/apps/server/src/modules/task/controller/task.controller.ts @@ -2,9 +2,9 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestj import { ApiTags } from '@nestjs/swagger'; import { RequestTimeout } from '@shared/common'; import { PaginationParams } from '@shared/controller/'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; +// invalid import can produce dependency cycles import { serverConfig } from '@src/modules/server/server.config'; import { TaskMapper } from '../mapper'; import { TaskCopyUC } from '../uc/task-copy.uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts index 3e7d8199fa2..e42b9b59932 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts @@ -12,8 +12,7 @@ import { } from '@nestjs/swagger'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolRequestMapper, ContextExternalToolResponseMapper } from '../mapper'; import { ContextExternalToolUc } from '../uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts index c414f4423de..0b9658b1182 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts @@ -1,7 +1,6 @@ import { Controller, Get, Param } from '@nestjs/common'; import { ApiForbiddenResponse, ApiOkResponse, ApiOperation, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ToolReference } from '../domain'; import { ContextExternalToolResponseMapper } from '../mapper'; import { ToolReferenceUc } from '../uc'; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index ecdcc32f2a2..a82d7353b1e 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -7,8 +7,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; import { ContextExternalToolTemplateInfo, ExternalToolConfigurationUc } from '../uc'; 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 4c3658d8025..1400da6490a 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 @@ -14,8 +14,7 @@ import { ValidationError } from '@shared/common'; import { PaginationParams } from '@shared/controller'; import { IFindOptions, Page } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool } from '../domain'; 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 c34b669f75c..0977cdf478f 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 @@ -12,9 +12,8 @@ import { } from '@nestjs/swagger'; import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpStatus } from '@nestjs/common'; import { ValidationError } from '@shared/common'; -import { ICurrentUser } from '@src/modules/authentication'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from '../mapper'; import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; import { diff --git a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts index 957ec92de92..73ce027b654 100644 --- a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts +++ b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts @@ -7,8 +7,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ToolLaunchUc } from '../uc'; import { ToolLaunchParams, ToolLaunchRequestResponse } from './dto'; import { ToolLaunchMapper } from '../mapper'; diff --git a/apps/server/src/modules/user-import/controller/import-user.controller.ts b/apps/server/src/modules/user-import/controller/import-user.controller.ts index 6b87a9f66d2..03aa2741fc2 100644 --- a/apps/server/src/modules/user-import/controller/import-user.controller.ts +++ b/apps/server/src/modules/user-import/controller/import-user.controller.ts @@ -1,11 +1,8 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; - import { PaginationParams } from '@shared/controller'; import { IFindOptions, ImportUser, User } from '@shared/domain'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; -import { ICurrentUser } from '@src/modules/authentication'; - +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ImportUserMapper } from '../mapper/import-user.mapper'; import { UserMatchMapper } from '../mapper/user-match.mapper'; import { UserImportUc } from '../uc/user-import.uc'; diff --git a/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts b/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts index 19098167d96..d94a79b5b94 100644 --- a/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts +++ b/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts @@ -11,8 +11,7 @@ import { ApiUnprocessableEntityResponse, } from '@nestjs/swagger'; import { Page, UserLoginMigrationDO } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser, JWT } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser, JWT } from '@src/modules/authentication'; import { SchoolNumberMissingLoggableException, UserLoginMigrationAlreadyClosedLoggableException, diff --git a/apps/server/src/modules/user/controller/dto/resolved-user.response.ts b/apps/server/src/modules/user/controller/dto/resolved-user.response.ts index 31347ad0054..e61114be6bf 100644 --- a/apps/server/src/modules/user/controller/dto/resolved-user.response.ts +++ b/apps/server/src/modules/user/controller/dto/resolved-user.response.ts @@ -1,9 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IResolvedUser, IRole } from '@src/modules/authentication/interface/user'; -export type Role = IRole; +export type Role = { + name: string; -export class ResolvedUserResponse implements IResolvedUser { + id: string; +}; + +export class ResolvedUserResponse { @ApiProperty() firstName!: string; diff --git a/apps/server/src/modules/user/controller/user.controller.ts b/apps/server/src/modules/user/controller/user.controller.ts index 1cf26a90465..ffe5115bc8f 100644 --- a/apps/server/src/modules/user/controller/user.controller.ts +++ b/apps/server/src/modules/user/controller/user.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Patch } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { ResolvedUserMapper } from '../mapper'; import { UserUc } from '../uc'; import { ChangeLanguageParams, ResolvedUserResponse, SuccessfulResponse } from './dto'; diff --git a/apps/server/src/modules/user/service/user.service.ts b/apps/server/src/modules/user/service/user.service.ts index 60f62e5ee28..199e8966a61 100644 --- a/apps/server/src/modules/user/service/user.service.ts +++ b/apps/server/src/modules/user/service/user.service.ts @@ -1,13 +1,12 @@ import { ConfigService } from '@nestjs/config'; import { EntityId, IFindOptions, LanguageType, User } from '@shared/domain'; -import { RoleReference } from '@shared/domain/domainobject'; -import { Page } from '@shared/domain/domainobject/page'; -import { UserDO } from '@shared/domain/domainobject/user.do'; +import { RoleReference, Page, UserDO } from '@shared/domain/domainobject'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; -import { AccountService } from '@src/modules/account/services/account.service'; +import { AccountService } from '@src/modules/account'; import { AccountDto } from '@src/modules/account/services/dto'; import { ICurrentUser } from '@src/modules/authentication'; +// invalid import import { CurrentUserMapper } from '@src/modules/authentication/mapper'; import { RoleDto } from '@src/modules/role/service/dto/role.dto'; import { RoleService } from '@src/modules/role/service/role.service'; @@ -40,6 +39,7 @@ export class UserService { async getUser(id: string): Promise { const userEntity = await this.userRepo.findById(id, true); const userDto = UserMapper.mapFromEntityToDto(userEntity); + return userDto; } @@ -48,36 +48,43 @@ export class UserService { const account: AccountDto = await this.accountService.findByUserIdOrFail(userId); const resolvedUser: ICurrentUser = CurrentUserMapper.userToICurrentUser(account.id, user, account.systemId); + return resolvedUser; } async findById(id: string): Promise { const userDO = await this.userDORepo.findById(id, true); + return userDO; } async save(user: UserDO): Promise { const savedUser: Promise = this.userDORepo.save(user); + return savedUser; } async saveAll(users: UserDO[]): Promise { const savedUsers: Promise = this.userDORepo.saveAll(users); + return savedUsers; } async findUsers(query: UserQuery, options?: IFindOptions): Promise> { const users: Page = await this.userDORepo.find(query, options); + return users; } async findByExternalId(externalId: string, systemId: EntityId): Promise { const user: Promise = this.userDORepo.findByExternalId(externalId, systemId); + return user; } async findByEmail(email: string): Promise { const user: Promise = this.userRepo.findByEmail(email); + return user; } diff --git a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts index 79c22cc21e1..1f6657c28b2 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts @@ -11,8 +11,7 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; -import { ICurrentUser } from '@src/modules/authentication/interface'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { BBBBaseResponse } from '../bbb'; import { defaultVideoConferenceOptions } from '../interface'; import { VideoConferenceResponseDeprecatedMapper } from '../mapper/vc-deprecated-response.mapper'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts index 884be5512cb..76b8a5c53ad 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, HttpStatus, Param, Put, Req } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; import { Request } from 'express'; import { InvalidOriginForLogoutUrlLoggableException } from '../error'; import { VideoConferenceOptions } from '../interface'; diff --git a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts index bfcb738847a..4c9a7bf7135 100644 --- a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts +++ b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts @@ -3,7 +3,7 @@ import { LegacyLogger, RequestLoggingBody } from '@src/core/logger'; import { Request } from 'express'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { ICurrentUser } from '@src/modules/authentication/interface/user'; +import { ICurrentUser } from '@src/modules/authentication'; @Injectable() export class RequestLoggingInterceptor implements NestInterceptor { From fc68ac096e56f3471cbcef798b954a4d9d9294c4 Mon Sep 17 00:00:00 2001 From: ezzato Date: Fri, 20 Oct 2023 14:11:07 +0200 Subject: [PATCH 06/19] BC-2804 remap imports modules folder (#4468) * update tsconfig, remove index file * split imports from bundles import @module * update jest config to support @modules path * mass change imports --- .../src/apps/files-storage-consumer.app.ts | 2 +- apps/server/src/apps/files-storage.app.ts | 2 +- .../src/apps/fwu-learning-contents.app.ts | 2 +- apps/server/src/apps/h5p-editor.app.ts | 2 +- apps/server/src/apps/management.app.ts | 2 +- apps/server/src/apps/server.app.ts | 18 ++++---- .../api-test/test-bootstrap.console.ts | 2 +- apps/server/src/console/console.module.ts | 10 ++-- .../account/controller/account.controller.ts | 3 +- .../controller/api-test/account.api.spec.ts | 4 +- .../mapper/account-response.mapper.spec.ts | 2 +- .../account/mapper/account-response.mapper.ts | 2 +- .../services/account-db.service.spec.ts | 6 +-- .../account-idm.service.integration.spec.ts | 2 +- .../services/account-lookup.service.ts | 2 +- .../account/services/account.service.spec.ts | 2 +- .../src/modules/account/uc/account.uc.spec.ts | 8 ++-- .../src/modules/account/uc/account.uc.ts | 6 +-- .../authentication/authentication.module.ts | 8 ++-- .../controllers/api-test/login.api.spec.ts | 6 +-- .../decorator/auth.decorator.spec.ts | 4 +- .../services/authentication.service.spec.ts | 6 +-- .../services/authentication.service.ts | 6 +-- .../strategy/ldap.strategy.spec.ts | 2 +- .../authentication/strategy/ldap.strategy.ts | 2 +- .../strategy/local.strategy.spec.ts | 6 +-- .../authentication/strategy/local.strategy.ts | 2 +- .../strategy/oauth2.strategy.spec.ts | 8 ++-- .../strategy/oauth2.strategy.ts | 8 ++-- .../authorization-reference.module.ts | 4 +- .../rules/context-external-tool.rule.spec.ts | 8 ++-- .../rules/context-external-tool.rule.ts | 4 +- .../rules/school-external-tool.rule.spec.ts | 4 +- .../domain/rules/school-external-tool.rule.ts | 4 +- .../domain/service/reference.loader.spec.ts | 4 +- .../domain/service/reference.loader.ts | 4 +- .../src/modules/board/board-api.module.ts | 2 +- .../api-test/board-context.api.spec.ts | 2 +- .../api-test/board-delete.api.spec.ts | 6 +-- .../api-test/board-lookup.api.spec.ts | 6 +-- .../api-test/board-update-title.api.spec.ts | 2 +- .../api-test/card-create.api.spec.ts | 6 +-- .../api-test/card-delete.api.spec.ts | 6 +-- .../api-test/card-lookup.api.spec.ts | 6 +-- .../controller/api-test/card-move.api.spec.ts | 6 +-- .../api-test/card-update-height.api.spec.ts | 2 +- .../api-test/card-update-title.api.spec.ts | 2 +- .../api-test/column-create.api.spec.ts | 6 +-- .../api-test/column-delete.api.spec.ts | 6 +-- .../api-test/column-move.api.spec.ts | 6 +-- .../api-test/column-update-title.api.spec.ts | 2 +- .../content-element-create.api.spec.ts | 2 +- .../content-element-delete.api.spec.ts | 6 +-- .../api-test/content-element-move.api.spec.ts | 6 +-- .../content-element-update-content.spec.ts | 2 +- .../submission-item-create.api.spec.ts | 2 +- .../submission-item-lookup.api.spec.ts | 2 +- .../submission-item-update.api.spec.ts | 2 +- .../controller/board-submission.controller.ts | 2 +- .../board/controller/board.controller.ts | 2 +- .../board/controller/card.controller.ts | 2 +- .../board/controller/column.controller.ts | 2 +- .../board/controller/element.controller.ts | 2 +- .../modules/board/repo/board-do.repo.spec.ts | 2 +- .../repo/recursive-delete.visitor.spec.ts | 2 +- .../board/repo/recursive-delete.vistor.ts | 2 +- .../board/repo/recursive-save.visitor.ts | 2 +- .../service/board-do-authorizable.service.ts | 2 +- .../board-do-copy.service.spec.ts | 2 +- .../board-do-copy.service.ts | 2 +- .../recursive-copy.visitor.ts | 2 +- ...specific-file-copy-service.factory.spec.ts | 4 +- ...hool-specific-file-copy-service.factory.ts | 2 +- .../school-specific-file-copy.interface.ts | 4 +- .../school-specific-file-copy.service.ts | 4 +- .../service/column-board-copy.service.spec.ts | 4 +- .../service/column-board-copy.service.ts | 4 +- .../board/uc/board-management.uc.spec.ts | 2 +- .../src/modules/board/uc/board.uc.spec.ts | 2 +- apps/server/src/modules/board/uc/board.uc.ts | 4 +- .../src/modules/board/uc/card.uc.spec.ts | 2 +- apps/server/src/modules/board/uc/card.uc.ts | 2 +- .../src/modules/board/uc/element.uc.spec.ts | 2 +- .../server/src/modules/board/uc/element.uc.ts | 2 +- .../board/uc/submission-item.uc.spec.ts | 2 +- .../modules/board/uc/submission-item.uc.ts | 2 +- .../class/entity/testing/class.entity.spec.ts | 2 +- .../testing/factory/class.entity.factory.ts | 2 +- .../modules/class/repo/classes.repo.spec.ts | 2 +- .../class/service/class.service.spec.ts | 2 +- .../collaborative-storage.module.ts | 10 ++-- .../collaborative-storage.controller.spec.ts | 6 +-- .../collaborative-storage.controller.ts | 2 +- .../mapper/team-permissions.mapper.spec.ts | 4 +- .../mapper/team.mapper.spec.ts | 2 +- .../collaborative-storage.service.spec.ts | 10 ++-- .../services/collaborative-storage.service.ts | 4 +- .../uc/collaborative-storage.uc.spec.ts | 12 ++--- .../uc/collaborative-storage.uc.ts | 6 +-- .../modules/copy-helper/dto/copy.response.ts | 2 +- .../copy-helper/mapper/copy.mapper.spec.ts | 10 ++-- .../modules/copy-helper/mapper/copy.mapper.ts | 8 ++-- .../files-storage-client.module.ts | 2 +- .../service/copy-files.service.spec.ts | 2 +- .../service/copy-files.service.ts | 2 +- .../api-test/files-security.api.spec.ts | 8 ++-- .../files-storage-copy-files.api.spec.ts | 8 ++-- .../files-storage-delete-files.api.spec.ts | 8 ++-- .../files-storage-download-upload.api.spec.ts | 8 ++-- .../files-storage-list-files.api.spec.ts | 8 ++-- .../files-storage-preview.api.spec.ts | 8 ++-- .../files-storage-rename-file.api.spec.ts | 8 ++-- .../files-storage-restore-files.api.spec.ts | 8 ++-- .../controller/files-storage.controller.ts | 2 +- .../files-storage/files-storage-api.module.ts | 4 +- .../files-storage-test.module.ts | 4 +- .../mapper/files-storage.mapper.spec.ts | 2 +- .../mapper/files-storage.mapper.ts | 2 +- .../uc/files-storage-copy.uc.spec.ts | 4 +- .../uc/files-storage-delete.uc.spec.ts | 2 +- .../files-storage-download-preview.uc.spec.ts | 2 +- .../uc/files-storage-download.uc.spec.ts | 2 +- .../uc/files-storage-get.uc.spec.ts | 2 +- .../uc/files-storage-restore.uc.spec.ts | 2 +- .../uc/files-storage-update.uc.spec.ts | 2 +- .../uc/files-storage-upload.uc.spec.ts | 4 +- .../files-storage/uc/files-storage.uc.ts | 4 +- .../modules/files/entity/file.entity.spec.ts | 2 +- .../fwu-learning-contents.api.spec.ts | 2 +- .../fwu-learning-contents.controller.ts | 2 +- .../fwu-learning-contents-test.module.ts | 4 +- .../fwu-learning-contents.module.ts | 2 +- .../controller/api-test/group.api.spec.ts | 6 +-- .../group/controller/group.controller.ts | 2 +- .../src/modules/group/group-api.module.ts | 12 ++--- .../modules/group/service/group.service.ts | 2 +- .../group/uc/dto/resolved-group-user.ts | 2 +- .../src/modules/group/uc/group.uc.spec.ts | 18 ++++---- apps/server/src/modules/group/uc/group.uc.ts | 16 +++---- .../group/uc/mapper/group-uc.mapper.ts | 4 +- .../api-test/h5p-editor.api.spec.ts | 2 +- .../controller/h5p-editor.controller.ts | 2 +- .../h5p-editor/h5p-editor-test.module.ts | 4 +- .../modules/h5p-editor/h5p-editor.module.ts | 2 +- apps/server/src/modules/index.ts | 25 ---------- .../controller/api-test/course.api.spec.ts | 4 +- .../controller/api-test/dashboard.api.spec.ts | 8 ++-- .../api-test/rooms-copy-timeout.api.spec.ts | 8 ++-- .../controller/api-test/rooms.api.spec.ts | 12 ++--- .../learnroom/controller/course.controller.ts | 2 +- .../controller/dashboard.controller.spec.ts | 2 +- .../controller/dashboard.controller.ts | 2 +- .../board-element.response.ts | 2 +- .../controller/rooms.controller.spec.ts | 4 +- .../learnroom/controller/rooms.controller.ts | 6 +-- .../modules/learnroom/learnroom-api.module.ts | 8 ++-- .../src/modules/learnroom/learnroom.module.ts | 8 ++-- .../service/board-copy.service.spec.ts | 8 ++-- .../learnroom/service/board-copy.service.ts | 10 ++-- .../column-board-target.service.spec.ts | 2 +- .../service/column-board-target.service.ts | 2 +- .../common-cartridge-export.service.spec.ts | 8 ++-- .../common-cartridge-export.service.ts | 4 +- .../service/course-copy.service.spec.ts | 4 +- .../learnroom/service/course-copy.service.ts | 2 +- .../learnroom/service/rooms.service.spec.ts | 4 +- .../learnroom/service/rooms.service.ts | 4 +- .../learnroom/uc/course-copy.uc.spec.ts | 6 +-- .../modules/learnroom/uc/course-copy.uc.ts | 6 +-- .../learnroom/uc/course-export.uc.spec.ts | 4 +- .../modules/learnroom/uc/course-export.uc.ts | 4 +- .../learnroom/uc/lesson-copy.uc.spec.ts | 6 +-- .../modules/learnroom/uc/lesson-copy.uc.ts | 8 ++-- .../uc/room-board-dto.factory.spec.ts | 2 +- .../learnroom/uc/room-board-dto.factory.ts | 2 +- .../legacy-school.controller.spec.ts | 2 +- .../controller/legacy-school.controller.ts | 2 +- .../legacy-school/legacy-school-api.module.ts | 4 +- .../legacy-school/uc/legacy-school.uc.spec.ts | 8 ++-- .../legacy-school/uc/legacy-school.uc.ts | 4 +- .../api-test/lesson-delete.api.spec.ts | 4 +- .../lesson/controller/lesson.controller.ts | 2 +- .../src/modules/lesson/lesson-api.module.ts | 2 +- .../src/modules/lesson/lesson.module.ts | 6 +-- .../service/lesson-copy.service.spec.ts | 6 +-- .../lesson/service/lesson-copy.service.ts | 14 ++---- .../lesson/service/lesson.service.spec.ts | 2 +- .../modules/lesson/service/lesson.service.ts | 2 +- .../src/modules/lesson/uc/lesson.uc.spec.ts | 2 +- .../server/src/modules/lesson/uc/lesson.uc.ts | 2 +- .../api-test/database-management.api.spec.ts | 2 +- .../modules/management/management.module.ts | 2 +- .../news/controller/api-test/news.api.spec.ts | 8 ++-- .../news/controller/news.controller.ts | 2 +- .../news/controller/team-news.controller.ts | 2 +- apps/server/src/modules/news/news.module.ts | 2 +- .../src/modules/news/uc/news.uc.spec.ts | 2 +- apps/server/src/modules/news/uc/news.uc.ts | 2 +- .../dto/request/oauth-client.body.ts | 4 +- .../dto/response/consent.response.ts | 4 +- .../controller/dto/response/login.response.ts | 4 +- .../oauth-provider.controller.spec.ts | 12 ++--- .../controller/oauth-provider.controller.ts | 2 +- .../mapper/oauth-provider-request.mapper.ts | 2 +- .../oauth-provider-response.mapper.spec.ts | 4 +- .../mapper/oauth-provider-response.mapper.ts | 2 +- .../oauth-provider-api.module.ts | 6 +-- .../oauth-provider/oauth-provider.module.ts | 10 ++-- .../service/id-token.service.spec.ts | 12 ++--- .../service/id-token.service.ts | 6 +-- .../oauth-provider.login-flow.service.spec.ts | 8 ++-- .../oauth-provider.login-flow.service.ts | 8 ++-- .../uc/oauth-provider.client-crud.uc.spec.ts | 4 +- .../uc/oauth-provider.client-crud.uc.ts | 4 +- .../uc/oauth-provider.consent-flow.uc.spec.ts | 10 ++-- .../uc/oauth-provider.consent-flow.uc.ts | 8 ++-- .../uc/oauth-provider.login-flow.uc.spec.ts | 8 ++-- .../uc/oauth-provider.login-flow.uc.ts | 12 ++--- .../uc/oauth-provider.logout-flow.uc.spec.ts | 2 +- .../uc/oauth-provider.uc.spec.ts | 2 +- .../controller/api-test/oauth-sso.api.spec.ts | 8 ++-- .../controller/oauth-sso.controller.spec.ts | 4 +- .../oauth/controller/oauth-sso.controller.ts | 6 +-- .../oauth/mapper/user-migration.mapper.ts | 2 +- .../src/modules/oauth/oauth-api.module.ts | 14 +++--- apps/server/src/modules/oauth/oauth.module.ts | 12 ++--- .../oauth/service/dto/hydra.redirect.dto.ts | 2 +- .../oauth/service/hydra.service.spec.ts | 6 +-- .../modules/oauth/service/hydra.service.ts | 6 +-- .../oauth/service/oauth.service.spec.ts | 16 +++---- .../modules/oauth/service/oauth.service.ts | 14 +++--- .../modules/oauth/uc/hydra-oauth.uc.spec.ts | 6 +-- .../src/modules/oauth/uc/hydra-oauth.uc.ts | 2 +- .../src/modules/oauth/uc/oauth.uc.spec.ts | 26 +++++------ apps/server/src/modules/oauth/uc/oauth.uc.ts | 20 ++++---- .../provisioning/dto/external-group.dto.ts | 2 +- .../provisioning-system-input.mapper.spec.ts | 2 +- .../provisioning-system-input.mapper.ts | 2 +- .../provisioning/provisioning.module.ts | 12 ++--- .../service/provisioning.service.spec.ts | 4 +- .../service/provisioning.service.ts | 4 +- .../strategy/iserv/iserv.strategy.spec.ts | 6 +-- .../strategy/iserv/iserv.strategy.ts | 6 +-- .../oidc-mock/oidc-mock.strategy.spec.ts | 2 +- .../strategy/oidc-mock/oidc-mock.strategy.ts | 2 +- .../service/oidc-provisioning.service.spec.ts | 14 +++--- .../oidc/service/oidc-provisioning.service.ts | 16 +++---- .../sanis/sanis-response.mapper.spec.ts | 2 +- .../strategy/sanis/sanis-response.mapper.ts | 2 +- .../strategy/sanis/sanis.strategy.spec.ts | 2 +- .../controller/api-test/pseudonym.api.spec.ts | 4 +- .../controller/pseudonym.controller.ts | 2 +- .../modules/pseudonym/pseudonym-api.module.ts | 4 +- .../src/modules/pseudonym/pseudonym.module.ts | 8 ++-- .../service/feathers-roster.service.spec.ts | 18 ++++---- .../service/feathers-roster.service.ts | 18 ++++---- .../service/pseudonym.service.spec.ts | 2 +- .../pseudonym/service/pseudonym.service.ts | 2 +- .../modules/pseudonym/uc/pseudonym.uc.spec.ts | 4 +- .../src/modules/pseudonym/uc/pseudonym.uc.ts | 4 +- .../modules/role/mapper/role.mapper.spec.ts | 4 +- .../src/modules/role/mapper/role.mapper.ts | 2 +- apps/server/src/modules/role/role.module.ts | 4 +- .../src/modules/role/uc/role.uc.spec.ts | 6 +-- apps/server/src/modules/role/uc/role.uc.ts | 4 +- .../controller/api-test/server.api.spec.ts | 2 +- .../src/modules/server/server.config.ts | 6 ++- .../src/modules/server/server.module.ts | 46 +++++++++---------- .../api-test/sharing-create-token.api.spec.ts | 6 +-- .../api-test/sharing-import-token.api.spec.ts | 8 ++-- .../api-test/sharing-lookup-token.api.spec.ts | 2 +- .../controller/share-token.controller.spec.ts | 4 +- .../controller/share-token.controller.ts | 6 +-- .../mapper/context-type.mapper.spec.ts | 2 +- .../sharing/mapper/context-type.mapper.ts | 2 +- .../sharing/mapper/parent-type.mapper.spec.ts | 2 +- .../sharing/mapper/parent-type.mapper.ts | 2 +- .../service/share-token.service.spec.ts | 6 +-- .../sharing/service/share-token.service.ts | 6 +-- .../src/modules/sharing/sharing.module.ts | 4 +- .../modules/sharing/uc/share-token.uc.spec.ts | 14 +++--- .../src/modules/sharing/uc/share-token.uc.ts | 14 +++--- .../controller/api-test/system.api.spec.ts | 6 +-- .../controller/dto/public-system-response.ts | 2 +- .../mapper/system-response.mapper.ts | 6 +-- .../system/controller/system.controller.ts | 2 +- .../system/mapper/system-oidc.mapper.spec.ts | 2 +- .../system/mapper/system-oidc.mapper.ts | 2 +- .../system/mapper/system.mapper.spec.ts | 2 +- .../modules/system/mapper/system.mapper.ts | 4 +- .../modules/system/service/dto/system.dto.ts | 2 +- .../system/service/system-oidc.service.ts | 2 +- .../modules/system/service/system.service.ts | 4 +- .../src/modules/system/system-api.module.ts | 4 +- .../src/modules/system/system.module.ts | 2 +- .../src/modules/system/uc/system.uc.spec.ts | 8 ++-- .../server/src/modules/system/uc/system.uc.ts | 4 +- .../api-test/submission.api.spec.ts | 10 ++-- .../api-test/task-copy-timeout.api.spec.ts | 6 +-- .../api-test/task-delete.api.spec.ts | 4 +- .../api-test/task-finish.api.spec.ts | 2 +- .../api-test/task-finished.api.spec.ts | 8 ++-- .../api-test/task-restore.api.spec.ts | 2 +- .../task-revert-published.api.spec.ts | 2 +- .../task/controller/api-test/task.api.spec.ts | 4 +- .../task/controller/submission.controller.ts | 2 +- .../task/controller/task.controller.spec.ts | 6 +-- .../task/controller/task.controller.ts | 6 +-- .../task/service/submission.service.spec.ts | 2 +- .../task/service/submission.service.ts | 2 +- .../task/service/task-copy.service.spec.ts | 4 +- .../modules/task/service/task-copy.service.ts | 6 +-- .../modules/task/service/task.service.spec.ts | 2 +- .../src/modules/task/service/task.service.ts | 2 +- .../src/modules/task/task-api.module.ts | 4 +- apps/server/src/modules/task/task.module.ts | 6 +-- .../src/modules/task/uc/submission.uc.spec.ts | 2 +- .../src/modules/task/uc/submission.uc.ts | 2 +- .../src/modules/task/uc/task-copy.uc.spec.ts | 6 +-- .../src/modules/task/uc/task-copy.uc.ts | 4 +- .../src/modules/task/uc/task.uc.spec.ts | 2 +- apps/server/src/modules/task/uc/task.uc.ts | 2 +- .../src/modules/teams/teams-api.module.ts | 2 +- .../modules/tool/common/common-tool.module.ts | 6 +-- .../common/mapper/context-type.mapper.spec.ts | 2 +- .../tool/common/mapper/context-type.mapper.ts | 2 +- .../tool/common/uc/tool-permission-helper.ts | 6 +-- .../common/uc/tool-permissions-helper.spec.ts | 6 +-- .../api-test/tool-context.api.spec.ts | 2 +- .../api-test/tool-reference.api.spec.ts | 2 +- .../controller/tool-context.controller.ts | 2 +- .../controller/tool-reference.controller.ts | 2 +- ...text-external-tool-authorizable.service.ts | 2 +- .../context-external-tool.service.spec.ts | 2 +- .../uc/context-external-tool.uc.spec.ts | 2 +- .../uc/context-external-tool.uc.ts | 2 +- .../uc/tool-reference.uc.spec.ts | 2 +- .../uc/tool-reference.uc.ts | 2 +- .../api-test/tool-configuration.api.spec.ts | 4 +- .../controller/api-test/tool.api.spec.ts | 2 +- .../tool-configuration.controller.ts | 2 +- .../controller/tool.controller.ts | 2 +- .../uc/external-tool-configuration.uc.spec.ts | 2 +- .../uc/external-tool-configuration.uc.ts | 2 +- .../external-tool/uc/external-tool.uc.spec.ts | 4 +- .../tool/external-tool/uc/external-tool.uc.ts | 2 +- .../api-test/tool-school.api.spec.ts | 2 +- .../controller/tool-school.controller.ts | 2 +- .../uc/school-external-tool.uc.spec.ts | 2 +- .../uc/school-external-tool.uc.ts | 2 +- .../src/modules/tool/tool-api.module.ts | 6 +-- .../tool-launch.controller.api.spec.ts | 2 +- .../controller/tool-launch.controller.ts | 2 +- .../strategy/abstract-launch.strategy.spec.ts | 4 +- .../strategy/abstract-launch.strategy.ts | 4 +- .../basic-tool-launch.strategy.spec.ts | 4 +- .../lti11-tool-launch.strategy.spec.ts | 8 ++-- .../strategy/lti11-tool-launch.strategy.ts | 8 ++-- .../oauth2-tool-launch.strategy.spec.ts | 4 +- .../tool/tool-launch/tool-launch.module.ts | 8 ++-- .../tool/tool-launch/uc/tool-launch.uc.ts | 2 +- .../api-test/import-user.api.spec.ts | 8 ++-- .../controller/import-user.controller.spec.ts | 6 +-- .../controller/import-user.controller.ts | 2 +- .../user-import/uc/user-import.uc.spec.ts | 6 +-- .../modules/user-import/uc/user-import.uc.ts | 8 ++-- .../modules/user-import/user-import.module.ts | 2 +- .../api-test/user-login-migration.api.spec.ts | 6 +-- .../user-login-migration.controller.ts | 2 +- .../error/oauth-migration.error.ts | 2 +- .../service/migration-check.service.spec.ts | 4 +- .../service/migration-check.service.ts | 4 +- .../service/school-migration.service.spec.ts | 8 ++-- .../service/school-migration.service.ts | 4 +- ...ser-login-migration-revert.service.spec.ts | 2 +- .../user-login-migration-revert.service.ts | 2 +- .../user-login-migration.service.spec.ts | 8 ++-- .../service/user-login-migration.service.ts | 6 +-- .../service/user-migration.service.spec.ts | 14 +++--- .../service/user-migration.service.ts | 10 ++-- .../uc/close-user-login-migration.uc.spec.ts | 2 +- .../uc/close-user-login-migration.uc.ts | 2 +- .../restart-user-login-migration.uc.spec.ts | 4 +- .../uc/restart-user-login-migration.uc.ts | 4 +- .../uc/start-user-login-migration.uc.spec.ts | 4 +- .../uc/start-user-login-migration.uc.ts | 4 +- .../uc/toggle-user-login-migration.uc.spec.ts | 4 +- .../uc/toggle-user-login-migration.uc.ts | 4 +- .../uc/user-login-migration.uc.spec.ts | 14 +++--- .../uc/user-login-migration.uc.ts | 12 ++--- .../user-login-migration-api.module.ts | 10 ++-- .../user-login-migration.module.ts | 8 ++-- .../api-test/user-language.api.spec.ts | 6 +-- .../controller/api-test/user-me.api.spec.ts | 8 ++-- .../user/controller/user.controller.ts | 2 +- .../modules/user/mapper/user.mapper.spec.ts | 4 +- .../src/modules/user/mapper/user.mapper.ts | 2 +- .../modules/user/service/user.service.spec.ts | 12 ++--- .../src/modules/user/service/user.service.ts | 12 ++--- .../src/modules/user/uc/user.uc.spec.ts | 2 +- apps/server/src/modules/user/user.module.ts | 6 +-- .../api-test/video-conference.api.spec.ts | 2 +- ...o-conference-deprecated.controller.spec.ts | 2 +- .../video-conference-deprecated.controller.ts | 2 +- .../controller/video-conference.controller.ts | 2 +- .../service/video-conference.service.spec.ts | 8 ++-- .../service/video-conference.service.ts | 8 ++-- .../uc/video-conference-create.uc.spec.ts | 2 +- .../uc/video-conference-create.uc.ts | 2 +- .../uc/video-conference-deprecated.uc.spec.ts | 9 ++-- .../uc/video-conference-deprecated.uc.ts | 12 ++--- .../uc/video-conference-end.uc.spec.ts | 2 +- .../uc/video-conference-end.uc.ts | 4 +- .../uc/video-conference-info.uc.spec.ts | 2 +- .../uc/video-conference-info.uc.ts | 4 +- .../uc/video-conference-join.uc.spec.ts | 2 +- .../uc/video-conference-join.uc.ts | 4 +- .../video-conference-api.module.ts | 4 +- .../video-conference.module.ts | 8 ++-- .../request-logging.interceptor.ts | 2 +- .../src/shared/controller/swagger.spec.ts | 2 +- .../src/shared/domain/entity/all-entities.ts | 14 +++--- .../external-tool-element-node.entity.ts | 2 +- .../src/shared/domain/entity/course.entity.ts | 4 +- .../infra/antivirus/antivirus.service.ts | 2 +- .../collaborative-storage-adapter.module.ts | 6 +-- .../collaborative-storage.adapter.spec.ts | 2 +- .../collaborative-storage.adapter.ts | 6 +-- .../collaborative-storage-adapter.mapper.ts | 6 +-- .../strategy/base.interface.strategy.ts | 2 +- .../nextcloud/nextcloud.strategy.spec.ts | 8 ++-- .../strategy/nextcloud/nextcloud.strategy.ts | 10 ++-- .../identity-management-oauth.service.ts | 2 +- .../keycloak-configuration.controller.spec.ts | 2 +- .../keycloak-configuration.module.ts | 3 +- .../mapper/identity-provider.mapper.spec.ts | 2 +- .../mapper/identity-provider.mapper.ts | 2 +- ...-configuration.service.integration.spec.ts | 2 +- .../keycloak-configuration.service.spec.ts | 4 +- .../service/keycloak-configuration.service.ts | 6 +-- .../keycloak-migration.service.spec.ts | 4 +- .../service/keycloak-migration.service.ts | 4 +- ...cloak-identity-management-oauth.service.ts | 2 +- ...ity-management.service.integration.spec.ts | 2 +- .../request/accept-consent-request.body.ts | 2 +- ...ext-external-tool.repo.integration.spec.ts | 12 ++--- .../context-external-tool.repo.ts | 12 ++--- .../context-external-tool.scope.spec.ts | 4 +- .../context-external-tool.scope.ts | 4 +- .../external-tool-sorting.mapper.spec.ts | 4 +- .../external-tool-sorting.mapper.ts | 4 +- .../external-tool.repo.integration.spec.ts | 15 ++---- .../externaltool/external-tool.repo.mapper.ts | 15 ++---- .../repo/externaltool/external-tool.repo.ts | 8 ++-- .../repo/externaltool/external-tool.scope.ts | 2 +- ...ool-external-tool.repo.integration.spec.ts | 10 ++-- .../school-external-tool.repo.ts | 8 ++-- .../school-external-tool.scope.ts | 2 +- .../user/user-do.repo.integration.spec.ts | 2 +- .../src/shared/repo/user/user-do.repo.ts | 2 +- .../testing/factory/account-dto.factory.ts | 2 +- .../context-external-tool-entity.factory.ts | 4 +- .../domainobject/groups/group.factory.ts | 2 +- .../tool/context-external-tool.factory.ts | 6 +-- .../tool/external-tool.factory.ts | 6 +-- .../tool/school-external-tool.factory.ts | 4 +- .../factory/external-group-dto.factory.ts | 4 +- .../factory/external-tool-entity.factory.ts | 4 +- .../external-tool-pseudonym.factory.ts | 2 +- .../testing/factory/filerecord.factory.ts | 2 +- .../testing/factory/group-entity.factory.ts | 2 +- .../testing/factory/pseudonym.factory.ts | 2 +- .../testing/factory/role-dto.factory.ts | 2 +- .../school-external-tool-entity.factory.ts | 2 +- .../testing/factory/share-token.do.factory.ts | 2 +- .../testing/map-user-to-current-user.ts | 2 +- jest.config.ts | 1 + tsconfig.json | 3 +- 478 files changed, 1121 insertions(+), 1155 deletions(-) delete mode 100644 apps/server/src/modules/index.ts diff --git a/apps/server/src/apps/files-storage-consumer.app.ts b/apps/server/src/apps/files-storage-consumer.app.ts index 777176e1176..a18b5f4604b 100644 --- a/apps/server/src/apps/files-storage-consumer.app.ts +++ b/apps/server/src/apps/files-storage-consumer.app.ts @@ -3,7 +3,7 @@ import { NestFactory } from '@nestjs/core'; // register source-map-support for debugging -import { FilesStorageAMQPModule } from '@src/modules/files-storage'; +import { FilesStorageAMQPModule } from '@modules/files-storage'; import { install as sourceMapInstall } from 'source-map-support'; async function bootstrap() { diff --git a/apps/server/src/apps/files-storage.app.ts b/apps/server/src/apps/files-storage.app.ts index e10a2b3a162..2d2f9343ac2 100644 --- a/apps/server/src/apps/files-storage.app.ts +++ b/apps/server/src/apps/files-storage.app.ts @@ -10,7 +10,7 @@ import { install as sourceMapInstall } from 'source-map-support'; // application imports import { SwaggerDocumentOptions } from '@nestjs/swagger'; import { LegacyLogger } from '@src/core/logger'; -import { API_VERSION_PATH, FilesStorageApiModule } from '@src/modules/files-storage'; +import { API_VERSION_PATH, FilesStorageApiModule } from '@modules/files-storage'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; async function bootstrap() { diff --git a/apps/server/src/apps/fwu-learning-contents.app.ts b/apps/server/src/apps/fwu-learning-contents.app.ts index b7f2e9f1e25..15279392f12 100644 --- a/apps/server/src/apps/fwu-learning-contents.app.ts +++ b/apps/server/src/apps/fwu-learning-contents.app.ts @@ -9,7 +9,7 @@ import { install as sourceMapInstall } from 'source-map-support'; // application imports import { LegacyLogger } from '@src/core/logger'; -import { FwuLearningContentsModule } from '@src/modules/fwu-learning-contents'; +import { FwuLearningContentsModule } from '@modules/fwu-learning-contents'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; async function bootstrap() { diff --git a/apps/server/src/apps/h5p-editor.app.ts b/apps/server/src/apps/h5p-editor.app.ts index fcd550c34fa..518eb5c45bc 100644 --- a/apps/server/src/apps/h5p-editor.app.ts +++ b/apps/server/src/apps/h5p-editor.app.ts @@ -9,7 +9,7 @@ import { install as sourceMapInstall } from 'source-map-support'; // application imports import { LegacyLogger } from '@src/core/logger'; -import { H5PEditorModule } from '@src/modules/h5p-editor'; +import { H5PEditorModule } from '@modules/h5p-editor'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; async function bootstrap() { diff --git a/apps/server/src/apps/management.app.ts b/apps/server/src/apps/management.app.ts index 8be7cfdd866..dd66443300a 100644 --- a/apps/server/src/apps/management.app.ts +++ b/apps/server/src/apps/management.app.ts @@ -9,7 +9,7 @@ import { install as sourceMapInstall } from 'source-map-support'; // application imports import { LegacyLogger } from '@src/core/logger'; -import { ManagementServerModule } from '@src/modules/management'; +import { ManagementServerModule } from '@modules/management'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; async function bootstrap() { diff --git a/apps/server/src/apps/server.app.ts b/apps/server/src/apps/server.app.ts index c486adc1915..bd235d5261f 100644 --- a/apps/server/src/apps/server.app.ts +++ b/apps/server/src/apps/server.app.ts @@ -7,20 +7,20 @@ import { ExpressAdapter } from '@nestjs/platform-express'; import { enableOpenApiDocs } from '@shared/controller/swagger'; import { Mail, MailService } from '@shared/infra/mail'; import { LegacyLogger, Logger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { TeamService } from '@src/modules/teams/service/team.service'; -import { AccountValidationService } from '@src/modules/account/services/account.validation.service'; -import { AccountUc } from '@src/modules/account/uc/account.uc'; -import { CollaborativeStorageUc } from '@src/modules/collaborative-storage/uc/collaborative-storage.uc'; -import { GroupService } from '@src/modules/group'; -import { RocketChatService } from '@src/modules/rocketchat'; -import { ServerModule } from '@src/modules/server'; +import { AccountService } from '@modules/account/services/account.service'; +import { TeamService } from '@modules/teams/service/team.service'; +import { AccountValidationService } from '@modules/account/services/account.validation.service'; +import { AccountUc } from '@modules/account/uc/account.uc'; +import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc'; +import { GroupService } from '@modules/group'; +import { RocketChatService } from '@modules/rocketchat'; +import { ServerModule } from '@modules/server'; import express from 'express'; import { join } from 'path'; // register source-map-support for debugging import { install as sourceMapInstall } from 'source-map-support'; -import { FeathersRosterService } from '@src/modules/pseudonym'; +import { FeathersRosterService } from '@modules/pseudonym'; import legacyAppPromise = require('../../../../src/app'); import { AppStartLoggable } from './helpers/app-start-loggable'; diff --git a/apps/server/src/console/api-test/test-bootstrap.console.ts b/apps/server/src/console/api-test/test-bootstrap.console.ts index 282d448b05d..edb196b6a54 100644 --- a/apps/server/src/console/api-test/test-bootstrap.console.ts +++ b/apps/server/src/console/api-test/test-bootstrap.console.ts @@ -1,7 +1,7 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { ConsoleWriterService } from '@shared/infra/console'; -import { DatabaseManagementUc } from '@src/modules/management/uc/database-management.uc'; +import { DatabaseManagementUc } from '@modules/management/uc/database-management.uc'; import { AbstractBootstrapConsole, BootstrapConsole } from 'nestjs-console'; export class TestBootstrapConsole extends AbstractBootstrapConsole { diff --git a/apps/server/src/console/console.module.ts b/apps/server/src/console/console.module.ts index 2aed716e719..2cad08943eb 100644 --- a/apps/server/src/console/console.module.ts +++ b/apps/server/src/console/console.module.ts @@ -7,11 +7,11 @@ import { ALL_ENTITIES } from '@shared/domain'; import { ConsoleWriterModule } from '@shared/infra/console/console-writer/console-writer.module'; import { KeycloakModule } from '@shared/infra/identity-management/keycloak/keycloak.module'; import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; -import { FilesModule } from '@src/modules/files'; -import { FileEntity } from '@src/modules/files/entity'; -import { FileRecord } from '@src/modules/files-storage/entity'; -import { ManagementModule } from '@src/modules/management/management.module'; -import { serverConfig } from '@src/modules/server'; +import { FilesModule } from '@modules/files'; +import { FileEntity } from '@modules/files/entity'; +import { FileRecord } from '@modules/files-storage/entity'; +import { ManagementModule } from '@modules/management/management.module'; +import { serverConfig } from '@modules/server'; import { ConsoleModule } from 'nestjs-console'; import { ServerConsole } from './server.console'; diff --git a/apps/server/src/modules/account/controller/account.controller.ts b/apps/server/src/modules/account/controller/account.controller.ts index 86d1f8287f8..2f45de2403e 100644 --- a/apps/server/src/modules/account/controller/account.controller.ts +++ b/apps/server/src/modules/account/controller/account.controller.ts @@ -1,7 +1,8 @@ import { Body, Controller, Delete, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; +import { Authenticate, CurrentUser } from '@modules/authentication/decorator/auth.decorator'; import { AccountUc } from '../uc/account.uc'; import { AccountByIdBodyParams, diff --git a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts index 9e7dd1a7d18..44d47a3c3d2 100644 --- a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts +++ b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts @@ -16,8 +16,8 @@ import { AccountSearchType, PatchMyAccountParams, PatchMyPasswordParams, -} from '@src/modules/account/controller/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +} from '@modules/account/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; describe('Account Controller (API)', () => { const basePath = '/account'; diff --git a/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts b/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts index 05c345f166b..64858623c67 100644 --- a/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts +++ b/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts @@ -1,5 +1,5 @@ import { Account } from '@shared/domain'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; import { accountDtoFactory, accountFactory } from '@shared/testing'; import { AccountResponseMapper } from '.'; diff --git a/apps/server/src/modules/account/mapper/account-response.mapper.ts b/apps/server/src/modules/account/mapper/account-response.mapper.ts index 94437737df9..84e20519bfe 100644 --- a/apps/server/src/modules/account/mapper/account-response.mapper.ts +++ b/apps/server/src/modules/account/mapper/account-response.mapper.ts @@ -1,5 +1,5 @@ import { Account } from '@shared/domain'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; import { AccountResponse } from '../controller/dto'; export class AccountResponseMapper { diff --git a/apps/server/src/modules/account/services/account-db.service.spec.ts b/apps/server/src/modules/account/services/account-db.service.spec.ts index 8cc6a33cbb6..7fa4c4e44a8 100644 --- a/apps/server/src/modules/account/services/account-db.service.spec.ts +++ b/apps/server/src/modules/account/services/account-db.service.spec.ts @@ -6,9 +6,9 @@ import { EntityNotFoundError } from '@shared/common'; import { Account, EntityId } from '@shared/domain'; import { IdentityManagementService } from '@shared/infra/identity-management/identity-management.service'; import { accountFactory, setupEntities, userFactory } from '@shared/testing'; -import { AccountEntityToDtoMapper } from '@src/modules/account/mapper'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { IServerConfig } from '@src/modules/server'; +import { AccountEntityToDtoMapper } from '@modules/account/mapper'; +import { AccountDto } from '@modules/account/services/dto'; +import { IServerConfig } from '@modules/server'; import bcrypt from 'bcryptjs'; import { LegacyLogger } from '../../../core/logger'; import { AccountRepo } from '../repo/account.repo'; diff --git a/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts b/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts index f50e8fcc07e..2249a485f98 100644 --- a/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts +++ b/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts @@ -5,7 +5,7 @@ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { IdmAccount } from '@shared/domain'; import { KeycloakAdministrationService } from '@shared/infra/identity-management/keycloak-administration/service/keycloak-administration.service'; -import { AccountSaveDto } from '@src/modules/account/services/dto'; +import { AccountSaveDto } from '@modules/account/services/dto'; import { LoggerModule } from '@src/core/logger'; import { IdentityManagementModule } from '@shared/infra/identity-management'; import { IdentityManagementService } from '../../../shared/infra/identity-management/identity-management.service'; diff --git a/apps/server/src/modules/account/services/account-lookup.service.ts b/apps/server/src/modules/account/services/account-lookup.service.ts index a0e18870177..b1549590c5b 100644 --- a/apps/server/src/modules/account/services/account-lookup.service.ts +++ b/apps/server/src/modules/account/services/account-lookup.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { EntityId } from '@shared/domain'; import { IdentityManagementService } from '@shared/infra/identity-management'; -import { IServerConfig } from '@src/modules/server/server.config'; +import { IServerConfig } from '@modules/server/server.config'; import { ObjectId } from 'bson'; /** diff --git a/apps/server/src/modules/account/services/account.service.spec.ts b/apps/server/src/modules/account/services/account.service.spec.ts index 834bc5b0f89..67851d0a6b0 100644 --- a/apps/server/src/modules/account/services/account.service.spec.ts +++ b/apps/server/src/modules/account/services/account.service.spec.ts @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { IServerConfig } from '@src/modules/server'; +import { IServerConfig } from '@modules/server'; import { LegacyLogger } from '../../../core/logger'; import { AccountServiceDb } from './account-db.service'; import { AccountServiceIdm } from './account-idm.service'; diff --git a/apps/server/src/modules/account/uc/account.uc.spec.ts b/apps/server/src/modules/account/uc/account.uc.spec.ts index e210a5c9ab5..526720c43f7 100644 --- a/apps/server/src/modules/account/uc/account.uc.spec.ts +++ b/apps/server/src/modules/account/uc/account.uc.spec.ts @@ -16,10 +16,10 @@ import { import { UserRepo } from '@shared/repo'; import { accountFactory, schoolFactory, setupEntities, systemFactory, userFactory } from '@shared/testing'; import { BruteForcePrevention } from '@src/imports-from-feathers'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountSaveDto } from '@src/modules/account/services/dto'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; -import { ICurrentUser } from '@src/modules/authentication'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountSaveDto } from '@modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; +import { ICurrentUser } from '@modules/authentication'; import { ObjectId } from 'bson'; import { AccountByIdBodyParams, diff --git a/apps/server/src/modules/account/uc/account.uc.ts b/apps/server/src/modules/account/uc/account.uc.ts index 2db4ed8843f..f9e21b28c63 100644 --- a/apps/server/src/modules/account/uc/account.uc.ts +++ b/apps/server/src/modules/account/uc/account.uc.ts @@ -9,11 +9,11 @@ import { import { Account, EntityId, Permission, PermissionService, Role, RoleName, SchoolEntity, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; // TODO: module internals should be imported with relative paths -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; import { BruteForcePrevention } from '@src/imports-from-feathers'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { ObjectId } from 'bson'; import { IAccountConfig } from '../account-config'; import { diff --git a/apps/server/src/modules/authentication/authentication.module.ts b/apps/server/src/modules/authentication/authentication.module.ts index 1cfdca45fde..26d20a4dfc8 100644 --- a/apps/server/src/modules/authentication/authentication.module.ts +++ b/apps/server/src/modules/authentication/authentication.module.ts @@ -5,10 +5,10 @@ import { CacheWrapperModule } from '@shared/infra/cache'; import { IdentityManagementModule } from '@shared/infra/identity-management'; import { LegacySchoolRepo, SystemRepo, UserRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AccountModule } from '@src/modules/account'; -import { OauthModule } from '@src/modules/oauth/oauth.module'; -import { RoleModule } from '@src/modules/role'; -import { SystemModule } from '@src/modules/system'; +import { AccountModule } from '@modules/account'; +import { OauthModule } from '@modules/oauth/oauth.module'; +import { RoleModule } from '@modules/role'; +import { SystemModule } from '@modules/system'; import { Algorithm, SignOptions } from 'jsonwebtoken'; import { jwtConstants } from './constants'; import { AuthenticationService } from './services/authentication.service'; diff --git a/apps/server/src/modules/authentication/controllers/api-test/login.api.spec.ts b/apps/server/src/modules/authentication/controllers/api-test/login.api.spec.ts index 8d8ea8b305d..253d692055d 100644 --- a/apps/server/src/modules/authentication/controllers/api-test/login.api.spec.ts +++ b/apps/server/src/modules/authentication/controllers/api-test/login.api.spec.ts @@ -3,9 +3,9 @@ import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, RoleName, SchoolEntity, SystemEntity, User } from '@shared/domain'; import { accountFactory, roleFactory, schoolFactory, systemFactory, userFactory } from '@shared/testing'; -import { SSOErrorCode } from '@src/modules/oauth/loggable'; -import { OauthTokenResponse } from '@src/modules/oauth/service/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { SSOErrorCode } from '@modules/oauth/loggable'; +import { OauthTokenResponse } from '@modules/oauth/service/dto'; +import { ServerTestModule } from '@modules/server/server.module'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import crypto, { KeyPairKeyObjectResult } from 'crypto'; diff --git a/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts b/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts index bc5a9361b78..f193bd67516 100644 --- a/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts +++ b/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts @@ -2,8 +2,8 @@ import { Controller, ExecutionContext, ForbiddenException, Get, INestApplication } from '@nestjs/common'; import request from 'supertest'; import { Test, TestingModule } from '@nestjs/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { ServerTestModule } from '@modules/server/server.module'; import { JwtAuthGuard } from '../guard/jwt-auth.guard'; import { Authenticate, CurrentUser, JWT } from './auth.decorator'; diff --git a/apps/server/src/modules/authentication/services/authentication.service.spec.ts b/apps/server/src/modules/authentication/services/authentication.service.spec.ts index 461fe0e7246..3d5b6d3a1b7 100644 --- a/apps/server/src/modules/authentication/services/authentication.service.spec.ts +++ b/apps/server/src/modules/authentication/services/authentication.service.spec.ts @@ -3,9 +3,9 @@ import { UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { ICurrentUser } from '@src/modules/authentication'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { ICurrentUser } from '@modules/authentication'; import jwt from 'jsonwebtoken'; import { BruteForceError } from '../errors/brute-force.error'; import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; diff --git a/apps/server/src/modules/authentication/services/authentication.service.ts b/apps/server/src/modules/authentication/services/authentication.service.ts index f2c25ff9e9d..41aab6153ea 100644 --- a/apps/server/src/modules/authentication/services/authentication.service.ts +++ b/apps/server/src/modules/authentication/services/authentication.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; -import { AccountService } from '@src/modules/account'; +import { AccountService } from '@modules/account'; // invalid import -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto'; // invalid import, can produce dependency cycles -import type { IServerConfig } from '@src/modules/server'; +import type { IServerConfig } from '@modules/server'; import { randomUUID } from 'crypto'; import jwt, { JwtPayload } from 'jsonwebtoken'; import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; diff --git a/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts index db4a1a60878..d686dfcac72 100644 --- a/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts @@ -15,7 +15,7 @@ import { userFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto'; import { LdapAuthorizationBodyParams } from '../controllers/dto'; import { ICurrentUser } from '../interface'; import { AuthenticationService } from '../services/authentication.service'; diff --git a/apps/server/src/modules/authentication/strategy/ldap.strategy.ts b/apps/server/src/modules/authentication/strategy/ldap.strategy.ts index 5edc650ee9c..1622e434310 100644 --- a/apps/server/src/modules/authentication/strategy/ldap.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/ldap.strategy.ts @@ -4,7 +4,7 @@ import { LegacySchoolDo, SystemEntity, User } from '@shared/domain'; import { LegacySchoolRepo, SystemRepo, UserRepo } from '@shared/repo'; import { ErrorLoggable } from '@src/core/error/loggable/error.loggable'; import { Logger } from '@src/core/logger'; -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto'; import { Strategy } from 'passport-custom'; import { LdapAuthorizationBodyParams } from '../controllers/dto'; import { ICurrentUser } from '../interface'; diff --git a/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts index cac3d204a4b..d1330270fb7 100644 --- a/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts @@ -5,9 +5,9 @@ import { RoleName, User } from '@shared/domain'; import { IdentityManagementOauthService } from '@shared/infra/identity-management'; import { UserRepo } from '@shared/repo'; import { accountFactory, setupEntities, userFactory } from '@shared/testing'; -import { AccountEntityToDtoMapper } from '@src/modules/account/mapper'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { IServerConfig } from '@src/modules/server'; +import { AccountEntityToDtoMapper } from '@modules/account/mapper'; +import { AccountDto } from '@modules/account/services/dto'; +import { IServerConfig } from '@modules/server'; import bcrypt from 'bcryptjs'; import { AuthenticationService } from '../services/authentication.service'; import { LocalStrategy } from './local.strategy'; diff --git a/apps/server/src/modules/authentication/strategy/local.strategy.ts b/apps/server/src/modules/authentication/strategy/local.strategy.ts index ab34fe05b64..7963a5166e7 100644 --- a/apps/server/src/modules/authentication/strategy/local.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/local.strategy.ts @@ -4,7 +4,7 @@ import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { UserRepo } from '@shared/repo'; -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto'; import { GuardAgainst } from '@shared/common/utils/guard-against'; import { IdentityManagementOauthService, IIdentityManagementConfig } from '@shared/infra/identity-management'; import { CurrentUserMapper } from '../mapper'; diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts index 722ba125574..8fd8f096dbe 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts @@ -4,10 +4,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, RoleName } from '@shared/domain'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { userDoFactory } from '@shared/testing'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { OAuthTokenDto } from '@src/modules/oauth'; -import { OAuthService } from '@src/modules/oauth/service/oauth.service'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { OAuthTokenDto } from '@modules/oauth'; +import { OAuthService } from '@modules/oauth/service/oauth.service'; import { SchoolInMigrationError } from '../errors/school-in-migration.error'; import { ICurrentUser, OauthCurrentUser } from '../interface'; import { Oauth2Strategy } from './oauth2.strategy'; diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts index 6d532538e68..599744cc1a7 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts @@ -1,10 +1,10 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { UserDO } from '@shared/domain/domainobject/user.do'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { OAuthTokenDto } from '@src/modules/oauth'; -import { OAuthService } from '@src/modules/oauth/service/oauth.service'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { OAuthTokenDto } from '@modules/oauth'; +import { OAuthService } from '@modules/oauth/service/oauth.service'; import { Strategy } from 'passport-custom'; import { Oauth2AuthorizationBodyParams } from '../controllers/dto'; import { SchoolInMigrationError } from '../errors/school-in-migration.error'; diff --git a/apps/server/src/modules/authorization/authorization-reference.module.ts b/apps/server/src/modules/authorization/authorization-reference.module.ts index 7346f2178dd..e253587af7b 100644 --- a/apps/server/src/modules/authorization/authorization-reference.module.ts +++ b/apps/server/src/modules/authorization/authorization-reference.module.ts @@ -10,9 +10,9 @@ import { TeamsRepo, UserRepo, } from '@shared/repo'; -import { ToolModule } from '@src/modules/tool'; +import { ToolModule } from '@modules/tool'; import { LoggerModule } from '@src/core/logger'; -import { BoardModule } from '@src/modules/board'; +import { BoardModule } from '@modules/board'; import { ReferenceLoader, AuthorizationReferenceService, AuthorizationHelper } from './domain'; import { AuthorizationModule } from './authorization.module'; diff --git a/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts index ddd458959ed..c47f5c0ef1e 100644 --- a/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.spec.ts @@ -7,10 +7,10 @@ import { setupEntities, userFactory, } from '@shared/testing'; -import { ContextExternalTool } from '@src/modules/tool/context-external-tool/domain'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { ContextExternalTool } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { Role, User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; import { ContextExternalToolRule } from './context-external-tool.rule'; diff --git a/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts index 5d57c95a160..0aee8334aac 100644 --- a/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/context-external-tool.rule.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { ContextExternalTool } from '@src/modules/tool/context-external-tool/domain'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; +import { ContextExternalTool } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity'; import { User } from '@shared/domain/entity'; import { AuthorizationContext, Rule } from '../type'; import { AuthorizationHelper } from '../service/authorization.helper'; diff --git a/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts index d1781bb8576..453f77fc968 100644 --- a/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts +++ b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.spec.ts @@ -7,8 +7,8 @@ import { userFactory, schoolExternalToolFactory, } from '@shared/testing'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { Role, User, Permission } from '@shared/domain'; import { Action } from '../type'; import { AuthorizationHelper } from '../service/authorization.helper'; diff --git a/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts index 041c8b523e2..46126fe0589 100644 --- a/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts +++ b/apps/server/src/modules/authorization/domain/rules/school-external-tool.rule.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { User } from '@shared/domain/entity'; import { AuthorizationContext, Rule } from '../type'; import { AuthorizationHelper } from '../service/authorization.helper'; diff --git a/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts index 0403ebcbfd5..e2e77212cab 100644 --- a/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts @@ -15,8 +15,8 @@ import { UserRepo, } from '@shared/repo'; import { setupEntities, userFactory } from '@shared/testing'; -import { BoardDoAuthorizableService } from '@src/modules/board'; -import { ContextExternalToolAuthorizableService } from '@src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service'; +import { BoardDoAuthorizableService } from '@modules/board'; +import { ContextExternalToolAuthorizableService } from '@modules/tool/context-external-tool/service/context-external-tool-authorizable.service'; import { ReferenceLoader } from './reference.loader'; import { AuthorizableReferenceType } from '../type'; diff --git a/apps/server/src/modules/authorization/domain/service/reference.loader.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.ts index 5c38963c6f5..d584561be9e 100644 --- a/apps/server/src/modules/authorization/domain/service/reference.loader.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.ts @@ -12,8 +12,8 @@ import { TeamsRepo, UserRepo, } from '@shared/repo'; -import { BoardDoAuthorizableService } from '@src/modules/board'; -import { ContextExternalToolAuthorizableService } from '@src/modules/tool/context-external-tool/service'; +import { BoardDoAuthorizableService } from '@modules/board'; +import { ContextExternalToolAuthorizableService } from '@modules/tool/context-external-tool/service'; import { AuthorizableReferenceType } from '../type'; type RepoType = diff --git a/apps/server/src/modules/board/board-api.module.ts b/apps/server/src/modules/board/board-api.module.ts index 2ac4581dfd0..10b868e67b3 100644 --- a/apps/server/src/modules/board/board-api.module.ts +++ b/apps/server/src/modules/board/board-api.module.ts @@ -1,6 +1,6 @@ import { forwardRef, Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { BoardModule } from './board.module'; import { BoardController, diff --git a/apps/server/src/modules/board/controller/api-test/board-context.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-context.api.spec.ts index 99bb5403692..0cbdad3b8c2 100644 --- a/apps/server/src/modules/board/controller/api-test/board-context.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-context.api.spec.ts @@ -9,7 +9,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { BoardContextResponse } from '../dto/board/board-context.reponse'; const baseRouteName = '/boards'; diff --git a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts index 24bb1c69999..e92bb645872 100644 --- a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts @@ -11,9 +11,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; import { BoardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts index ebcb9c280a0..8423d68f793 100644 --- a/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts @@ -12,9 +12,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { BoardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/board-update-title.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-update-title.api.spec.ts index 4dc93d91417..9b77f2f3824 100644 --- a/apps/server/src/modules/board/controller/api-test/board-update-title.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-update-title.api.spec.ts @@ -9,7 +9,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; const baseRouteName = '/boards'; diff --git a/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts index a108d352759..4b0f57c361d 100644 --- a/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts @@ -11,9 +11,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { CardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts index 2e38143ebc1..3fff41b3660 100644 --- a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts @@ -13,9 +13,9 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts index 8f2c7bee9a9..711ccf1ac8f 100644 --- a/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts @@ -15,9 +15,9 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { CardIdsParams, CardListResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts index 892234c122a..e9e55b27dd2 100644 --- a/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts @@ -12,9 +12,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/card-update-height.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-update-height.api.spec.ts index 6e0de6e8739..2d4ab15b365 100644 --- a/apps/server/src/modules/board/controller/api-test/card-update-height.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-update-height.api.spec.ts @@ -11,7 +11,7 @@ import { columnNodeFactory, courseFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; describe(`card update height (api)`, () => { let app: INestApplication; diff --git a/apps/server/src/modules/board/controller/api-test/card-update-title.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-update-title.api.spec.ts index 5939c9c0cc0..bc598d69051 100644 --- a/apps/server/src/modules/board/controller/api-test/card-update-title.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-update-title.api.spec.ts @@ -11,7 +11,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; const baseRouteName = '/cards'; diff --git a/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts index 1f6777ab447..52aaa4608ca 100644 --- a/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts @@ -10,9 +10,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { ColumnResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts index 1d03e6579ba..86e3e666c84 100644 --- a/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts @@ -12,9 +12,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts index 2f65a6f2a0e..6fda90f739a 100644 --- a/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts @@ -12,9 +12,9 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/column-update-title.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-update-title.api.spec.ts index 82fa2264f7a..350406041c7 100644 --- a/apps/server/src/modules/board/controller/api-test/column-update-title.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-update-title.api.spec.ts @@ -10,7 +10,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; const baseRouteName = '/columns'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-create.api.spec.ts index 0f7a3795580..57ef692ace1 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-create.api.spec.ts @@ -11,7 +11,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; import { AnyContentElementResponse, SubmissionContainerElementResponse } from '../dto'; const baseRouteName = '/cards'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts index 107c12a723a..6b99a64e7ab 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts @@ -13,9 +13,9 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts index 0793dce8951..dbcd9acbc31 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts @@ -13,9 +13,9 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-update-content.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-update-content.spec.ts index 6a292ffa93d..3be8e7dda15 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-update-content.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-update-content.spec.ts @@ -22,7 +22,7 @@ import { richTextElementNodeFactory, submissionContainerElementNodeFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; describe(`content element update content (api)`, () => { let app: INestApplication; diff --git a/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts index 49cd3af657c..0fb70869e18 100644 --- a/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts @@ -13,7 +13,7 @@ import { submissionContainerElementNodeFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { SubmissionItemResponse } from '../dto'; const baseRouteName = '/elements'; diff --git a/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts index 4686f953d29..c119ace1d3c 100644 --- a/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts @@ -14,7 +14,7 @@ import { submissionItemNodeFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { SubmissionsResponse } from '../dto'; const baseRouteName = '/board-submissions'; diff --git a/apps/server/src/modules/board/controller/api-test/submission-item-update.api.spec.ts b/apps/server/src/modules/board/controller/api-test/submission-item-update.api.spec.ts index dfa8bf017b3..f8058a3ee4b 100644 --- a/apps/server/src/modules/board/controller/api-test/submission-item-update.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/submission-item-update.api.spec.ts @@ -13,7 +13,7 @@ import { submissionContainerElementNodeFactory, submissionItemNodeFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { SubmissionItemResponse } from '../dto'; const baseRouteName = '/board-submissions'; diff --git a/apps/server/src/modules/board/controller/board-submission.controller.ts b/apps/server/src/modules/board/controller/board-submission.controller.ts index f7b7e5da8ca..cffdcd64467 100644 --- a/apps/server/src/modules/board/controller/board-submission.controller.ts +++ b/apps/server/src/modules/board/controller/board-submission.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, ForbiddenException, Get, HttpCode, NotFoundException, Param, Patch } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { SubmissionsResponse } from './dto/submission-item/submissions.response'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; diff --git a/apps/server/src/modules/board/controller/board.controller.ts b/apps/server/src/modules/board/controller/board.controller.ts index 16cee839964..0d77aa80b3d 100644 --- a/apps/server/src/modules/board/controller/board.controller.ts +++ b/apps/server/src/modules/board/controller/board.controller.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { BoardUc } from '../uc'; import { BoardResponse, BoardUrlParams, ColumnResponse, RenameBodyParams } from './dto'; import { BoardContextResponse } from './dto/board/board-context.reponse'; diff --git a/apps/server/src/modules/board/controller/card.controller.ts b/apps/server/src/modules/board/controller/card.controller.ts index 94007d9296e..62afa262439 100644 --- a/apps/server/src/modules/board/controller/card.controller.ts +++ b/apps/server/src/modules/board/controller/card.controller.ts @@ -14,7 +14,7 @@ import { } from '@nestjs/common'; import { ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { BoardUc, CardUc } from '../uc'; import { AnyContentElementResponse, diff --git a/apps/server/src/modules/board/controller/column.controller.ts b/apps/server/src/modules/board/controller/column.controller.ts index fd7239e517a..9862ef23a74 100644 --- a/apps/server/src/modules/board/controller/column.controller.ts +++ b/apps/server/src/modules/board/controller/column.controller.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/common'; import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { BoardUc } from '../uc'; import { CardResponse, ColumnUrlParams, MoveColumnBodyParams, RenameBodyParams } from './dto'; import { CardResponseMapper } from './mapper'; diff --git a/apps/server/src/modules/board/controller/element.controller.ts b/apps/server/src/modules/board/controller/element.controller.ts index 25a3c553ef9..229d2d6f2e1 100644 --- a/apps/server/src/modules/board/controller/element.controller.ts +++ b/apps/server/src/modules/board/controller/element.controller.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/common'; import { ApiBody, ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { diff --git a/apps/server/src/modules/board/repo/board-do.repo.spec.ts b/apps/server/src/modules/board/repo/board-do.repo.spec.ts index 446d8b8cfa3..aa1c49224fe 100644 --- a/apps/server/src/modules/board/repo/board-do.repo.spec.ts +++ b/apps/server/src/modules/board/repo/board-do.repo.spec.ts @@ -26,7 +26,7 @@ import { richTextElementFactory, richTextElementNodeFactory, } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { BoardDoRepo } from './board-do.repo'; import { BoardNodeRepo } from './board-node.repo'; import { RecursiveDeleteVisitor } from './recursive-delete.vistor'; diff --git a/apps/server/src/modules/board/repo/recursive-delete.visitor.spec.ts b/apps/server/src/modules/board/repo/recursive-delete.visitor.spec.ts index d94e7ae5557..9142cb33553 100644 --- a/apps/server/src/modules/board/repo/recursive-delete.visitor.spec.ts +++ b/apps/server/src/modules/board/repo/recursive-delete.visitor.spec.ts @@ -12,7 +12,7 @@ import { submissionContainerElementFactory, submissionItemFactory, } from '@shared/testing'; -import { FileDto, FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FileDto, FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { RecursiveDeleteVisitor } from './recursive-delete.vistor'; describe(RecursiveDeleteVisitor.name, () => { diff --git a/apps/server/src/modules/board/repo/recursive-delete.vistor.ts b/apps/server/src/modules/board/repo/recursive-delete.vistor.ts index c2177e5dd1c..1c407391da4 100644 --- a/apps/server/src/modules/board/repo/recursive-delete.vistor.ts +++ b/apps/server/src/modules/board/repo/recursive-delete.vistor.ts @@ -14,7 +14,7 @@ import { SubmissionItem, } from '@shared/domain'; import { LinkElement } from '@shared/domain/domainobject/board/link-element.do'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; @Injectable() export class RecursiveDeleteVisitor implements BoardCompositeVisitorAsync { diff --git a/apps/server/src/modules/board/repo/recursive-save.visitor.ts b/apps/server/src/modules/board/repo/recursive-save.visitor.ts index 7b2c7901605..5e8249f1fee 100644 --- a/apps/server/src/modules/board/repo/recursive-save.visitor.ts +++ b/apps/server/src/modules/board/repo/recursive-save.visitor.ts @@ -24,7 +24,7 @@ import { } from '@shared/domain'; import { LinkElement } from '@shared/domain/domainobject/board/link-element.do'; import { LinkElementNode } from '@shared/domain/entity/boardnode/link-element-node.entity'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity'; import { BoardNodeRepo } from './board-node.repo'; type ParentData = { diff --git a/apps/server/src/modules/board/service/board-do-authorizable.service.ts b/apps/server/src/modules/board/service/board-do-authorizable.service.ts index 7b8b653f9cf..46ef7d3d45b 100644 --- a/apps/server/src/modules/board/service/board-do-authorizable.service.ts +++ b/apps/server/src/modules/board/service/board-do-authorizable.service.ts @@ -11,7 +11,7 @@ import { UserRoleEnum, } from '@shared/domain'; import { CourseRepo } from '@shared/repo'; -import { AuthorizationLoaderService } from '@src/modules/authorization'; +import { AuthorizationLoaderService } from '@modules/authorization'; import { BoardDoRepo } from '../repo'; @Injectable() diff --git a/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.spec.ts b/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.spec.ts index 4b5393854d2..d7c71352166 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.spec.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.spec.ts @@ -31,7 +31,7 @@ import { submissionContainerElementFactory, submissionItemFactory, } from '@shared/testing'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { ObjectId } from 'bson'; import { BoardDoCopyService } from './board-do-copy.service'; import { SchoolSpecificFileCopyService } from './school-specific-file-copy.interface'; diff --git a/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.ts b/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.ts index 0d457436f44..b2458dd6cfb 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/board-do-copy.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { AnyBoardDo } from '@shared/domain'; -import { CopyStatus } from '@src/modules/copy-helper'; +import { CopyStatus } from '@modules/copy-helper'; import { RecursiveCopyVisitor } from './recursive-copy.visitor'; import { SchoolSpecificFileCopyService } from './school-specific-file-copy.interface'; diff --git a/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts b/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts index 7b17194c15f..ba76693bb93 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts @@ -13,7 +13,7 @@ import { } from '@shared/domain'; import { LinkElement } from '@shared/domain/domainobject/board/link-element.do'; import { FileRecordParentType } from '@shared/infra/rabbitmq'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { ObjectId } from 'bson'; import { SchoolSpecificFileCopyService } from './school-specific-file-copy.interface'; diff --git a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.spec.ts b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.spec.ts index 9d6eaf1b24e..c780f9b9c50 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.spec.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { FileRecordParentType } from '@src/modules/files-storage/entity'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { FileRecordParentType } from '@modules/files-storage/entity'; import { ObjectId } from 'bson'; import { SchoolSpecificFileCopyServiceFactory } from './school-specific-file-copy-service.factory'; import { SchoolSpecificFileCopyServiceImpl } from './school-specific-file-copy.service'; diff --git a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.ts b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.ts index dda07c8c1f9..424033fa974 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy-service.factory.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { SchoolSpecificFileCopyService, SchoolSpecificFileCopyServiceProps, diff --git a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.interface.ts b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.interface.ts index e3b03d0b059..ce7870f6c86 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.interface.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.interface.ts @@ -1,6 +1,6 @@ import { EntityId } from '@shared/domain'; -import { CopyFileDto } from '@src/modules/files-storage-client/dto'; -import { FileRecordParentType } from '@src/modules/files-storage/entity'; +import { CopyFileDto } from '@modules/files-storage-client/dto'; +import { FileRecordParentType } from '@modules/files-storage/entity'; export type SchoolSpecificFileCopyServiceCopyParams = { sourceParentId: EntityId; diff --git a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.service.ts b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.service.ts index c162dbafdc7..1f3fa5f5193 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.service.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/school-specific-file-copy.service.ts @@ -1,5 +1,5 @@ -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { CopyFileDto } from '@src/modules/files-storage-client/dto'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { CopyFileDto } from '@modules/files-storage-client/dto'; import { SchoolSpecificFileCopyService, SchoolSpecificFileCopyServiceCopyParams, diff --git a/apps/server/src/modules/board/service/column-board-copy.service.spec.ts b/apps/server/src/modules/board/service/column-board-copy.service.spec.ts index 6824b82e133..bbfeb27a1f3 100644 --- a/apps/server/src/modules/board/service/column-board-copy.service.spec.ts +++ b/apps/server/src/modules/board/service/column-board-copy.service.spec.ts @@ -3,8 +3,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { BoardExternalReferenceType, ColumnBoard, UserDO } from '@shared/domain'; import { CourseRepo } from '@shared/repo'; import { columnBoardFactory, courseFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { UserService } from '@src/modules/user'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { UserService } from '@modules/user'; import { BoardDoRepo } from '../repo'; import { BoardDoCopyService, diff --git a/apps/server/src/modules/board/service/column-board-copy.service.ts b/apps/server/src/modules/board/service/column-board-copy.service.ts index c57e01d6c26..79ef29f4752 100644 --- a/apps/server/src/modules/board/service/column-board-copy.service.ts +++ b/apps/server/src/modules/board/service/column-board-copy.service.ts @@ -7,8 +7,8 @@ import { isColumnBoard, } from '@shared/domain'; import { CourseRepo } from '@shared/repo'; -import { CopyStatus } from '@src/modules/copy-helper'; -import { UserService } from '@src/modules/user'; +import { CopyStatus } from '@modules/copy-helper'; +import { UserService } from '@modules/user'; import { BoardDoRepo } from '../repo'; import { BoardDoCopyService, SchoolSpecificFileCopyServiceFactory } from './board-do-copy-service'; diff --git a/apps/server/src/modules/board/uc/board-management.uc.spec.ts b/apps/server/src/modules/board/uc/board-management.uc.spec.ts index 3d0d1fa835f..7c3464a8a52 100644 --- a/apps/server/src/modules/board/uc/board-management.uc.spec.ts +++ b/apps/server/src/modules/board/uc/board-management.uc.spec.ts @@ -4,7 +4,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConsoleWriterService } from '@shared/infra/console'; import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { courseFactory } from '@shared/testing'; -import { BoardManagementUc } from '@src/modules/management/uc/board-management.uc'; +import { BoardManagementUc } from '@modules/management/uc/board-management.uc'; describe(BoardManagementUc.name, () => { let module: TestingModule; diff --git a/apps/server/src/modules/board/uc/board.uc.spec.ts b/apps/server/src/modules/board/uc/board.uc.spec.ts index efd41e49ce6..c644bb120da 100644 --- a/apps/server/src/modules/board/uc/board.uc.spec.ts +++ b/apps/server/src/modules/board/uc/board.uc.spec.ts @@ -5,7 +5,7 @@ import { BoardDoAuthorizable, BoardRoles, ContentElementType, UserRoleEnum } fro import { setupEntities, userFactory } from '@shared/testing'; import { cardFactory, columnBoardFactory, columnFactory } from '@shared/testing/factory/domainobject'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ObjectId } from 'bson'; import { ContentElementService } from '../service'; import { BoardDoAuthorizableService } from '../service/board-do-authorizable.service'; diff --git a/apps/server/src/modules/board/uc/board.uc.ts b/apps/server/src/modules/board/uc/board.uc.ts index 3e39fd32de3..36d45dcd5fc 100644 --- a/apps/server/src/modules/board/uc/board.uc.ts +++ b/apps/server/src/modules/board/uc/board.uc.ts @@ -9,8 +9,8 @@ import { EntityId, } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization/domain'; -import { Action } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization/domain'; +import { Action } from '@modules/authorization'; import { CardService, ColumnBoardService, ColumnService } from '../service'; import { BoardDoAuthorizableService } from '../service/board-do-authorizable.service'; diff --git a/apps/server/src/modules/board/uc/card.uc.spec.ts b/apps/server/src/modules/board/uc/card.uc.spec.ts index 130a7feac40..fce595085c7 100644 --- a/apps/server/src/modules/board/uc/card.uc.spec.ts +++ b/apps/server/src/modules/board/uc/card.uc.spec.ts @@ -4,7 +4,7 @@ import { BoardDoAuthorizable, BoardRoles, ContentElementType, UserRoleEnum } fro import { setupEntities, userFactory } from '@shared/testing'; import { cardFactory, richTextElementFactory } from '@shared/testing/factory/domainobject'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ObjectId } from 'bson'; import { BoardDoAuthorizableService, ContentElementService } from '../service'; import { CardService } from '../service/card.service'; diff --git a/apps/server/src/modules/board/uc/card.uc.ts b/apps/server/src/modules/board/uc/card.uc.ts index 170469f0cc4..488f93fd4d8 100644 --- a/apps/server/src/modules/board/uc/card.uc.ts +++ b/apps/server/src/modules/board/uc/card.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AnyBoardDo, AnyContentElementDo, Card, ContentElementType, EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService, Action } from '@src/modules/authorization'; +import { AuthorizationService, Action } from '@modules/authorization'; import { BoardDoAuthorizableService, CardService, ContentElementService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/board/uc/element.uc.spec.ts b/apps/server/src/modules/board/uc/element.uc.spec.ts index 9c4b2706ab6..03124305dfa 100644 --- a/apps/server/src/modules/board/uc/element.uc.spec.ts +++ b/apps/server/src/modules/board/uc/element.uc.spec.ts @@ -10,7 +10,7 @@ import { userFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ObjectId } from 'bson'; import { BoardDoAuthorizableService, ContentElementService } from '../service'; import { SubmissionItemService } from '../service/submission-item.service'; diff --git a/apps/server/src/modules/board/uc/element.uc.ts b/apps/server/src/modules/board/uc/element.uc.ts index 08357b01798..6f71f202e66 100644 --- a/apps/server/src/modules/board/uc/element.uc.ts +++ b/apps/server/src/modules/board/uc/element.uc.ts @@ -8,7 +8,7 @@ import { UserRoleEnum, } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationService, Action } from '@src/modules/authorization'; +import { AuthorizationService, Action } from '@modules/authorization'; import { AnyElementContentBody } from '../controller/dto'; import { BoardDoAuthorizableService, ContentElementService } from '../service'; import { SubmissionItemService } from '../service/submission-item.service'; diff --git a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts index 5d06172acee..8e9b0d052b7 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts @@ -9,7 +9,7 @@ import { userFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationService, Action } from '@src/modules/authorization'; +import { AuthorizationService, Action } from '@modules/authorization'; import { BoardDoAuthorizableService, ContentElementService, SubmissionItemService } from '../service'; import { SubmissionItemUc } from './submission-item.uc'; diff --git a/apps/server/src/modules/board/uc/submission-item.uc.ts b/apps/server/src/modules/board/uc/submission-item.uc.ts index 67e7951673f..4748b64d84e 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.ts @@ -9,7 +9,7 @@ import { UserRoleEnum, } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationService, Action } from '@src/modules/authorization'; +import { AuthorizationService, Action } from '@modules/authorization'; import { BoardDoAuthorizableService, ContentElementService, SubmissionItemService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/class/entity/testing/class.entity.spec.ts b/apps/server/src/modules/class/entity/testing/class.entity.spec.ts index eb16f608238..7339e74ddb4 100644 --- a/apps/server/src/modules/class/entity/testing/class.entity.spec.ts +++ b/apps/server/src/modules/class/entity/testing/class.entity.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable no-new */ import { setupEntities } from '@shared/testing'; -import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory'; +import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory'; import { ObjectId } from '@mikro-orm/mongodb'; import { ClassEntity } from '../class.entity'; diff --git a/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts b/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts index 68e7514d3bc..b98d20853fc 100644 --- a/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts +++ b/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts @@ -1,6 +1,6 @@ import { DeepPartial } from 'fishery'; import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { ClassEntity, ClassSourceOptionsEntity, IClassEntityProps } from '@src/modules/class/entity'; +import { ClassEntity, ClassSourceOptionsEntity, IClassEntityProps } from '@modules/class/entity'; import { ObjectId } from 'bson'; class ClassEntityFactory extends BaseFactory { diff --git a/apps/server/src/modules/class/repo/classes.repo.spec.ts b/apps/server/src/modules/class/repo/classes.repo.spec.ts index 8059cdf8557..302a8f2de8e 100644 --- a/apps/server/src/modules/class/repo/classes.repo.spec.ts +++ b/apps/server/src/modules/class/repo/classes.repo.spec.ts @@ -4,7 +4,7 @@ import { TestingModule } from '@nestjs/testing/testing-module'; import { SchoolEntity } from '@shared/domain'; import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { cleanupCollections, schoolFactory } from '@shared/testing'; -import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory'; +import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory'; import { Class } from '../domain'; import { ClassEntity } from '../entity'; import { ClassesRepo } from './classes.repo'; diff --git a/apps/server/src/modules/class/service/class.service.spec.ts b/apps/server/src/modules/class/service/class.service.spec.ts index 3d1e851bd32..850eaf655a6 100644 --- a/apps/server/src/modules/class/service/class.service.spec.ts +++ b/apps/server/src/modules/class/service/class.service.spec.ts @@ -4,7 +4,7 @@ import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId } from '@shared/domain'; import { setupEntities } from '@shared/testing'; -import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory'; +import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory'; import { Class } from '../domain'; import { classFactory } from '../domain/testing/factory/class.factory'; import { ClassesRepo } from '../repo'; diff --git a/apps/server/src/modules/collaborative-storage/collaborative-storage.module.ts b/apps/server/src/modules/collaborative-storage/collaborative-storage.module.ts index d8580f9292b..eedf1b5638e 100644 --- a/apps/server/src/modules/collaborative-storage/collaborative-storage.module.ts +++ b/apps/server/src/modules/collaborative-storage/collaborative-storage.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common'; import { CollaborativeStorageAdapterModule } from '@shared/infra/collaborative-storage/collaborative-storage-adapter.module'; import { TeamsRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { TeamPermissionsMapper } from '@src/modules/collaborative-storage/mapper/team-permissions.mapper'; -import { TeamMapper } from '@src/modules/collaborative-storage/mapper/team.mapper'; -import { CollaborativeStorageService } from '@src/modules/collaborative-storage/services/collaborative-storage.service'; -import { RoleModule } from '@src/modules/role/role.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { TeamPermissionsMapper } from '@modules/collaborative-storage/mapper/team-permissions.mapper'; +import { TeamMapper } from '@modules/collaborative-storage/mapper/team.mapper'; +import { CollaborativeStorageService } from '@modules/collaborative-storage/services/collaborative-storage.service'; +import { RoleModule } from '@modules/role/role.module'; import { CollaborativeStorageController } from './controller/collaborative-storage.controller'; import { CollaborativeStorageUc } from './uc/collaborative-storage.uc'; diff --git a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts index b3f940747fa..4622fed4c00 100644 --- a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts +++ b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts @@ -1,8 +1,8 @@ -import { CollaborativeStorageController } from '@src/modules/collaborative-storage/controller/collaborative-storage.controller'; +import { CollaborativeStorageController } from '@modules/collaborative-storage/controller/collaborative-storage.controller'; import { Test, TestingModule } from '@nestjs/testing'; -import { CollaborativeStorageUc } from '@src/modules/collaborative-storage/uc/collaborative-storage.uc'; +import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc'; import { createMock } from '@golevelup/ts-jest'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { LegacyLogger } from '@src/core/logger'; describe('CollaborativeStorage Controller', () => { diff --git a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts index ecfd50ffda2..19fbccbed1c 100644 --- a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts +++ b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts @@ -1,6 +1,6 @@ import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { Body, Controller, Param, Patch } from '@nestjs/common'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { LegacyLogger } from '@src/core/logger'; import { CollaborativeStorageUc } from '../uc/collaborative-storage.uc'; import { TeamPermissionsBody } from './dto/team-permissions.body.params'; diff --git a/apps/server/src/modules/collaborative-storage/mapper/team-permissions.mapper.spec.ts b/apps/server/src/modules/collaborative-storage/mapper/team-permissions.mapper.spec.ts index 195319c0d89..3e3e8c434e5 100644 --- a/apps/server/src/modules/collaborative-storage/mapper/team-permissions.mapper.spec.ts +++ b/apps/server/src/modules/collaborative-storage/mapper/team-permissions.mapper.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { TeamPermissionsBody } from '@src/modules/collaborative-storage/controller/dto/team-permissions.body.params'; -import { TeamPermissionsMapper } from '@src/modules/collaborative-storage/mapper/team-permissions.mapper'; +import { TeamPermissionsBody } from '@modules/collaborative-storage/controller/dto/team-permissions.body.params'; +import { TeamPermissionsMapper } from '@modules/collaborative-storage/mapper/team-permissions.mapper'; describe('TeamMapper', () => { let module: TestingModule; diff --git a/apps/server/src/modules/collaborative-storage/mapper/team.mapper.spec.ts b/apps/server/src/modules/collaborative-storage/mapper/team.mapper.spec.ts index c46577499f0..a2d9808f5a6 100644 --- a/apps/server/src/modules/collaborative-storage/mapper/team.mapper.spec.ts +++ b/apps/server/src/modules/collaborative-storage/mapper/team.mapper.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { TeamMapper } from '@src/modules/collaborative-storage/mapper/team.mapper'; +import { TeamMapper } from '@modules/collaborative-storage/mapper/team.mapper'; describe('TeamMapper', () => { let module: TestingModule; diff --git a/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.spec.ts b/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.spec.ts index 1ed1aa3635c..4f95ae44a11 100644 --- a/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.spec.ts +++ b/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.spec.ts @@ -8,11 +8,11 @@ import { TeamsRepo } from '@shared/repo'; import { setupEntities } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationService } from '@src/modules/authorization'; -import { TeamMapper } from '@src/modules/collaborative-storage/mapper/team.mapper'; -import { CollaborativeStorageService } from '@src/modules/collaborative-storage/services/collaborative-storage.service'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { RoleService } from '@src/modules/role/service/role.service'; +import { AuthorizationService } from '@modules/authorization'; +import { TeamMapper } from '@modules/collaborative-storage/mapper/team.mapper'; +import { CollaborativeStorageService } from '@modules/collaborative-storage/services/collaborative-storage.service'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { RoleService } from '@modules/role/service/role.service'; import { TeamDto } from './dto/team.dto'; describe('Collaborative Storage Service', () => { diff --git a/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.ts b/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.ts index dccb28a3bb3..f9807cf691c 100644 --- a/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.ts +++ b/apps/server/src/modules/collaborative-storage/services/collaborative-storage.service.ts @@ -3,8 +3,8 @@ import { EntityId, Permission } from '@shared/domain'; import { CollaborativeStorageAdapter } from '@shared/infra/collaborative-storage'; import { TeamsRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { RoleService } from '@src/modules/role/service/role.service'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { RoleService } from '@modules/role/service/role.service'; import { TeamMapper } from '../mapper/team.mapper'; import { TeamPermissionsDto } from './dto/team-permissions.dto'; import { TeamDto } from './dto/team.dto'; diff --git a/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.spec.ts b/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.spec.ts index cef601131b9..bfc52a11a59 100644 --- a/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.spec.ts +++ b/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.spec.ts @@ -1,11 +1,11 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; -import { TeamPermissionsBody } from '@src/modules/collaborative-storage/controller/dto/team-permissions.body.params'; -import { TeamRoleDto } from '@src/modules/collaborative-storage/controller/dto/team-role.params'; -import { TeamPermissionsMapper } from '@src/modules/collaborative-storage/mapper/team-permissions.mapper'; -import { CollaborativeStorageService } from '@src/modules/collaborative-storage/services/collaborative-storage.service'; -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; -import { CollaborativeStorageUc } from '@src/modules/collaborative-storage/uc/collaborative-storage.uc'; +import { TeamPermissionsBody } from '@modules/collaborative-storage/controller/dto/team-permissions.body.params'; +import { TeamRoleDto } from '@modules/collaborative-storage/controller/dto/team-role.params'; +import { TeamPermissionsMapper } from '@modules/collaborative-storage/mapper/team-permissions.mapper'; +import { CollaborativeStorageService } from '@modules/collaborative-storage/services/collaborative-storage.service'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; +import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc'; describe('TeamStorageUc', () => { let module: TestingModule; diff --git a/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.ts b/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.ts index 046c2f22112..137f89a54c6 100644 --- a/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.ts +++ b/apps/server/src/modules/collaborative-storage/uc/collaborative-storage.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { CollaborativeStorageService } from '@src/modules/collaborative-storage/services/collaborative-storage.service'; -import { TeamPermissionsMapper } from '@src/modules/collaborative-storage/mapper/team-permissions.mapper'; -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; +import { CollaborativeStorageService } from '@modules/collaborative-storage/services/collaborative-storage.service'; +import { TeamPermissionsMapper } from '@modules/collaborative-storage/mapper/team-permissions.mapper'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; import { TeamPermissionsBody } from '../controller/dto/team-permissions.body.params'; import { TeamRoleDto } from '../controller/dto/team-role.params'; diff --git a/apps/server/src/modules/copy-helper/dto/copy.response.ts b/apps/server/src/modules/copy-helper/dto/copy.response.ts index b343042f6e3..549dcac7014 100644 --- a/apps/server/src/modules/copy-helper/dto/copy.response.ts +++ b/apps/server/src/modules/copy-helper/dto/copy.response.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { CopyElementType, CopyStatusEnum } from '@src/modules/copy-helper/types/copy.types'; +import { CopyElementType, CopyStatusEnum } from '@modules/copy-helper/types/copy.types'; /** * DTO for returning a copy status document via api. diff --git a/apps/server/src/modules/copy-helper/mapper/copy.mapper.spec.ts b/apps/server/src/modules/copy-helper/mapper/copy.mapper.spec.ts index b2cd8db4212..bb82db63761 100644 --- a/apps/server/src/modules/copy-helper/mapper/copy.mapper.spec.ts +++ b/apps/server/src/modules/copy-helper/mapper/copy.mapper.spec.ts @@ -1,11 +1,11 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; -import { CopyElementType, CopyStatusEnum } from '@src/modules/copy-helper'; -import { LessonCopyApiParams } from '@src/modules/learnroom/controller/dto/lesson/lesson-copy.params'; -import { LessonCopyParentParams } from '@src/modules/lesson'; -import { TaskCopyApiParams } from '@src/modules/task/controller/dto/task-copy.params'; -import { TaskCopyParentParams } from '@src/modules/task/types'; +import { CopyElementType, CopyStatusEnum } from '@modules/copy-helper'; +import { LessonCopyApiParams } from '@modules/learnroom/controller/dto/lesson/lesson-copy.params'; +import { LessonCopyParentParams } from '@modules/lesson'; +import { TaskCopyApiParams } from '@modules/task/controller/dto/task-copy.params'; +import { TaskCopyParentParams } from '@modules/task/types'; import { CopyApiResponse } from '../dto/copy.response'; import { CopyMapper } from './copy.mapper'; diff --git a/apps/server/src/modules/copy-helper/mapper/copy.mapper.ts b/apps/server/src/modules/copy-helper/mapper/copy.mapper.ts index dcc266d607a..20aae3e888b 100644 --- a/apps/server/src/modules/copy-helper/mapper/copy.mapper.ts +++ b/apps/server/src/modules/copy-helper/mapper/copy.mapper.ts @@ -1,8 +1,8 @@ import { EntityId, LessonEntity, Task } from '@shared/domain'; -import { LessonCopyApiParams } from '@src/modules/learnroom/controller/dto/lesson/lesson-copy.params'; -import { LessonCopyParentParams } from '@src/modules/lesson/types'; -import { TaskCopyApiParams } from '@src/modules/task/controller/dto/task-copy.params'; -import { TaskCopyParentParams } from '@src/modules/task/types'; +import { LessonCopyApiParams } from '@modules/learnroom/controller/dto/lesson/lesson-copy.params'; +import { LessonCopyParentParams } from '@modules/lesson/types'; +import { TaskCopyApiParams } from '@modules/task/controller/dto/task-copy.params'; +import { TaskCopyParentParams } from '@modules/task/types'; import { CopyApiResponse } from '../dto/copy.response'; import { CopyStatus, CopyStatusEnum } from '../types/copy.types'; diff --git a/apps/server/src/modules/files-storage-client/files-storage-client.module.ts b/apps/server/src/modules/files-storage-client/files-storage-client.module.ts index 6c9036f91f9..11c8eccdd3b 100644 --- a/apps/server/src/modules/files-storage-client/files-storage-client.module.ts +++ b/apps/server/src/modules/files-storage-client/files-storage-client.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { CopyHelperModule } from '@src/modules/copy-helper'; +import { CopyHelperModule } from '@modules/copy-helper'; import { CopyFilesService } from './service/copy-files.service'; import { FilesStorageClientAdapterService } from './service/files-storage-client.service'; import { FilesStorageProducer } from './service/files-storage.producer'; diff --git a/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts b/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts index 0dadb1a87e1..5bfc98a361a 100644 --- a/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts +++ b/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts @@ -8,7 +8,7 @@ import { legacyFileEntityMockFactory, setupEntities, } from '@shared/testing'; -import { CopyElementType, CopyHelperService } from '@src/modules/copy-helper'; +import { CopyElementType, CopyHelperService } from '@modules/copy-helper'; import { CopyFilesService } from './copy-files.service'; import { FilesStorageClientAdapterService } from './files-storage-client.service'; diff --git a/apps/server/src/modules/files-storage-client/service/copy-files.service.ts b/apps/server/src/modules/files-storage-client/service/copy-files.service.ts index 60cbf06c27d..1dc5904eb9b 100644 --- a/apps/server/src/modules/files-storage-client/service/copy-files.service.ts +++ b/apps/server/src/modules/files-storage-client/service/copy-files.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { CopyFileDto } from '../dto'; import { EntityWithEmbeddedFiles } from '../interfaces'; import { CopyFilesOfParentParamBuilder, FileParamBuilder } from '../mapper'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts index 956697a11d4..d6b6f4b7479 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts @@ -3,7 +3,7 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, fileRecordFactory, @@ -12,9 +12,9 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordListResponse, ScanResultParams } from '@src/modules/files-storage/controller/dto'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordListResponse, ScanResultParams } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import request from 'supertest'; import { FileRecord, FileRecordParentType } from '../../entity'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts index 659197c73d9..e1b792dea36 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts @@ -15,15 +15,15 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@src/modules/files-storage'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; import { CopyFileParams, CopyFilesOfParentParams, FileRecordListResponse, FileRecordResponse, -} from '@src/modules/files-storage/controller/dto'; +} from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts index e63bc036510..7a25915b3d5 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts @@ -14,10 +14,10 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@src/modules/files-storage/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts index de8cda56198..331282060e2 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts @@ -7,10 +7,10 @@ import { EntityId, Permission } from '@shared/domain'; import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordResponse } from '@src/modules/files-storage/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordResponse } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts index 6cc436fa7f6..b4d974fb24e 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts @@ -11,10 +11,10 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@src/modules/files-storage/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import request from 'supertest'; import { FileRecordParentType, PreviewStatus } from '../../entity'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts index f905cae5399..82cc8545a97 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts @@ -7,10 +7,10 @@ import { EntityId, Permission } from '@shared/domain'; import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordResponse } from '@src/modules/files-storage/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordResponse } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts index 2b3cb75f55d..e28f4dc327f 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts @@ -3,7 +3,7 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, fileRecordFactory, @@ -12,9 +12,9 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordResponse, RenameFileParams } from '@src/modules/files-storage/controller/dto'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordResponse, RenameFileParams } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import request from 'supertest'; import { FileRecord, FileRecordParentType } from '../../entity'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts index 7835c0217d4..f6e9a694ad3 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts @@ -14,10 +14,10 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@src/modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@src/modules/files-storage/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; +import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; diff --git a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts index a8bd1b35e9b..564919670e4 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts @@ -24,7 +24,7 @@ import { import { ApiConsumes, ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError, RequestLoggingInterceptor } from '@shared/common'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { Request, Response } from 'express'; import { GetFileResponse } from '../interface'; import { FilesStorageMapper } from '../mapper'; diff --git a/apps/server/src/modules/files-storage/files-storage-api.module.ts b/apps/server/src/modules/files-storage/files-storage-api.module.ts index aab383a158f..3bf18aa4047 100644 --- a/apps/server/src/modules/files-storage/files-storage-api.module.ts +++ b/apps/server/src/modules/files-storage/files-storage-api.module.ts @@ -1,8 +1,8 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { CoreModule } from '@src/core'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationReferenceModule } from '@modules/authorization/authorization-reference.module'; import { FileSecurityController, FilesStorageController } from './controller'; import { FilesStorageModule } from './files-storage.module'; import { FilesStorageUC } from './uc'; diff --git a/apps/server/src/modules/files-storage/files-storage-test.module.ts b/apps/server/src/modules/files-storage/files-storage-test.module.ts index ac3777a041f..6f3d865ebb2 100644 --- a/apps/server/src/modules/files-storage/files-storage-test.module.ts +++ b/apps/server/src/modules/files-storage/files-storage-test.module.ts @@ -5,8 +5,8 @@ import { MongoDatabaseModuleOptions } from '@shared/infra/database/mongo-memory- import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq/rabbitmq.module'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationModule } from '@modules/authorization'; import { FileRecord } from './entity'; import { FilesStorageApiModule } from './files-storage-api.module'; diff --git a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts index a26103ae983..1f681c371d1 100644 --- a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts +++ b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.spec.ts @@ -1,6 +1,6 @@ import { NotImplementedException } from '@nestjs/common'; import { fileRecordFactory, setupEntities } from '@shared/testing'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { DownloadFileParams, FileRecordListResponse, diff --git a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts index 9b30acd4ada..5b786aff96e 100644 --- a/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts +++ b/apps/server/src/modules/files-storage/mapper/files-storage.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException, StreamableFile } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { plainToClass } from 'class-transformer'; import { DownloadFileParams, diff --git a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts index 612558e80c1..2b7f1052121 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts @@ -8,8 +8,8 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action } from '@src/modules/authorization'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { Action } from '@modules/authorization'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { CopyFileResponseBuilder } from '../mapper'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts index fc461a50106..a1aaf0342ee 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts @@ -8,7 +8,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts index 795939e5cb2..81b54553d1e 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts index 3e7fa61fd7f..51ad0fd0b77 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts index 7f372a1fe80..60d3fdd1a64 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts @@ -6,7 +6,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts index e01e3116b79..b66c9c8821d 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts @@ -7,7 +7,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { FileRecordParams, SingleFileParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts index 19d9984eea8..c59f37d2599 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts @@ -6,7 +6,7 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { RenameFileParams, ScanResultParams, SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts index ed7defb54fb..43d9e9b7750 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts @@ -8,8 +8,8 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { AxiosHeadersKeyValue, axiosResponseFactory, fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action } from '@src/modules/authorization'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { Action } from '@modules/authorization'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { Request } from 'express'; import { of } from 'rxjs'; diff --git a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts index f5e6d372a6b..833d7575bdf 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts @@ -2,8 +2,8 @@ import { HttpService } from '@nestjs/axios'; import { Injectable, NotFoundException } from '@nestjs/common'; import { Counted, EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext } from '@src/modules/authorization'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { AuthorizationContext } from '@modules/authorization'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import busboy from 'busboy'; import { Request } from 'express'; diff --git a/apps/server/src/modules/files/entity/file.entity.spec.ts b/apps/server/src/modules/files/entity/file.entity.spec.ts index 1f6150b12c5..ea9649f4c66 100644 --- a/apps/server/src/modules/files/entity/file.entity.spec.ts +++ b/apps/server/src/modules/files/entity/file.entity.spec.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { setupEntities, storageProviderFactory } from '@shared/testing'; -import { FileOwnerModel } from '@src/modules/files/domain'; +import { FileOwnerModel } from '@modules/files/domain'; import { fileEntityFactory, filePermissionEntityFactory } from './testing'; import { FileEntity } from './file.entity'; import { FileSecurityCheckEntity } from './file-security-check.entity'; diff --git a/apps/server/src/modules/fwu-learning-contents/controller/api-test/fwu-learning-contents.api.spec.ts b/apps/server/src/modules/fwu-learning-contents/controller/api-test/fwu-learning-contents.api.spec.ts index 9f64e07129a..9eee09a30af 100644 --- a/apps/server/src/modules/fwu-learning-contents/controller/api-test/fwu-learning-contents.api.spec.ts +++ b/apps/server/src/modules/fwu-learning-contents/controller/api-test/fwu-learning-contents.api.spec.ts @@ -3,7 +3,7 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { INestApplication, NotFoundException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { S3ClientAdapter } from '@shared/infra/s3-client'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { Readable } from 'stream'; import request from 'supertest'; import { FwuLearningContentsTestModule } from '../../fwu-learning-contents-test.module'; diff --git a/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts b/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts index 8dedd3e5f96..f9e7bd6a238 100644 --- a/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts +++ b/apps/server/src/modules/fwu-learning-contents/controller/fwu-learning-contents.controller.ts @@ -10,7 +10,7 @@ import { StreamableFile, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate } from '@src/modules/authentication'; +import { Authenticate } from '@modules/authentication'; import { Request, Response } from 'express'; import { FwuLearningContentsUc } from '../uc/fwu-learning-contents.uc'; import { GetFwuLearningContentParams } from './dto/fwu-learning-contents.params'; diff --git a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents-test.module.ts b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents-test.module.ts index 5b6efa3bbb1..62e25bef4e2 100644 --- a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents-test.module.ts +++ b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents-test.module.ts @@ -9,8 +9,8 @@ import { S3ClientModule } from '@shared/infra/s3-client'; import { createConfigModuleOptions } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationModule } from '@modules/authorization'; import { FwuLearningContentsController } from './controller/fwu-learning-contents.controller'; import { config, s3Config } from './fwu-learning-contents.config'; import { FwuLearningContentsUc } from './uc/fwu-learning-contents.uc'; diff --git a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts index 2f4cd148d1b..b15c8a04054 100644 --- a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts +++ b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts @@ -9,7 +9,7 @@ import { S3ClientModule } from '@shared/infra/s3-client'; import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { AuthenticationModule } from '../authentication/authentication.module'; import { FwuLearningContentsController } from './controller/fwu-learning-contents.controller'; import { config, s3Config } from './fwu-learning-contents.config'; diff --git a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts index 39bb86a4caa..471d8b348af 100644 --- a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts +++ b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts @@ -12,9 +12,9 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ClassEntity } from '@src/modules/class/entity'; -import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory'; -import { ServerTestModule } from '@src/modules/server'; +import { ClassEntity } from '@modules/class/entity'; +import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory'; +import { ServerTestModule } from '@modules/server'; import { GroupEntity, GroupEntityTypes } from '../../entity'; import { ClassRootType } from '../../uc/dto/class-root-type'; import { ClassInfoSearchListResponse, ClassSortBy } from '../dto'; diff --git a/apps/server/src/modules/group/controller/group.controller.ts b/apps/server/src/modules/group/controller/group.controller.ts index 344fbe6b98f..553d07b0d5c 100644 --- a/apps/server/src/modules/group/controller/group.controller.ts +++ b/apps/server/src/modules/group/controller/group.controller.ts @@ -3,7 +3,7 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; import { Page } from '@shared/domain'; import { ErrorResponse } from '@src/core/error/dto'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { GroupUc } from '../uc'; import { ClassInfoDto } from '../uc/dto'; import { ClassInfoSearchListResponse, ClassSortParams } from './dto'; diff --git a/apps/server/src/modules/group/group-api.module.ts b/apps/server/src/modules/group/group-api.module.ts index 913fb2ef903..14a564b4741 100644 --- a/apps/server/src/modules/group/group-api.module.ts +++ b/apps/server/src/modules/group/group-api.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { ClassModule } from '@src/modules/class'; -import { RoleModule } from '@src/modules/role'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { SystemModule } from '@src/modules/system'; -import { UserModule } from '@src/modules/user'; +import { AuthorizationModule } from '@modules/authorization'; +import { ClassModule } from '@modules/class'; +import { RoleModule } from '@modules/role'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { SystemModule } from '@modules/system'; +import { UserModule } from '@modules/user'; import { GroupController } from './controller'; import { GroupModule } from './group.module'; import { GroupUc } from './uc'; diff --git a/apps/server/src/modules/group/service/group.service.ts b/apps/server/src/modules/group/service/group.service.ts index f3ce6a287e8..0ccded2442b 100644 --- a/apps/server/src/modules/group/service/group.service.ts +++ b/apps/server/src/modules/group/service/group.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { EntityId, type UserDO } from '@shared/domain'; -import { AuthorizationLoaderServiceGeneric } from '@src/modules/authorization'; +import { AuthorizationLoaderServiceGeneric } from '@modules/authorization'; import { Group } from '../domain'; import { GroupRepo } from '../repo'; diff --git a/apps/server/src/modules/group/uc/dto/resolved-group-user.ts b/apps/server/src/modules/group/uc/dto/resolved-group-user.ts index 862abdba594..80db4973ef2 100644 --- a/apps/server/src/modules/group/uc/dto/resolved-group-user.ts +++ b/apps/server/src/modules/group/uc/dto/resolved-group-user.ts @@ -1,5 +1,5 @@ import { UserDO } from '@shared/domain'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; export class ResolvedGroupUser { user: UserDO; diff --git a/apps/server/src/modules/group/uc/group.uc.spec.ts b/apps/server/src/modules/group/uc/group.uc.spec.ts index ed089007a72..3de4d262679 100644 --- a/apps/server/src/modules/group/uc/group.uc.spec.ts +++ b/apps/server/src/modules/group/uc/group.uc.spec.ts @@ -13,15 +13,15 @@ import { userDoFactory, userFactory, } from '@shared/testing'; -import { Action, AuthorizationContext, AuthorizationService } from '@src/modules/authorization'; -import { ClassService } from '@src/modules/class'; -import { Class } from '@src/modules/class/domain'; -import { classFactory } from '@src/modules/class/domain/testing/factory/class.factory'; -import { LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school'; -import { RoleService } from '@src/modules/role'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { SystemDto, SystemService } from '@src/modules/system'; -import { UserService } from '@src/modules/user'; +import { Action, AuthorizationContext, AuthorizationService } from '@modules/authorization'; +import { ClassService } from '@modules/class'; +import { Class } from '@modules/class/domain'; +import { classFactory } from '@modules/class/domain/testing/factory/class.factory'; +import { LegacySchoolService, SchoolYearService } from '@modules/legacy-school'; +import { RoleService } from '@modules/role'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { SystemDto, SystemService } from '@modules/system'; +import { UserService } from '@modules/user'; import { Group } from '../domain'; import { GroupService } from '../service'; import { ClassInfoDto } from './dto'; diff --git a/apps/server/src/modules/group/uc/group.uc.ts b/apps/server/src/modules/group/uc/group.uc.ts index 1d884c5a325..23cac984a6d 100644 --- a/apps/server/src/modules/group/uc/group.uc.ts +++ b/apps/server/src/modules/group/uc/group.uc.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; import { EntityId, LegacySchoolDo, Page, Permission, SchoolYearEntity, SortOrder, User, UserDO } from '@shared/domain'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { ClassService } from '@src/modules/class'; -import { Class } from '@src/modules/class/domain'; -import { LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school'; -import { RoleService } from '@src/modules/role'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { SystemDto, SystemService } from '@src/modules/system'; -import { UserService } from '@src/modules/user'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { ClassService } from '@modules/class'; +import { Class } from '@modules/class/domain'; +import { LegacySchoolService, SchoolYearService } from '@modules/legacy-school'; +import { RoleService } from '@modules/role'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { SystemDto, SystemService } from '@modules/system'; +import { UserService } from '@modules/user'; import { Group, GroupUser } from '../domain'; import { GroupService } from '../service'; import { SortHelper } from '../util'; diff --git a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts index 596302c4a5c..d5a415498db 100644 --- a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts +++ b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts @@ -1,6 +1,6 @@ import { RoleName, SchoolYearEntity, UserDO } from '@shared/domain'; -import { Class } from '@src/modules/class/domain'; -import { SystemDto } from '@src/modules/system'; +import { Class } from '@modules/class/domain'; +import { SystemDto } from '@modules/system'; import { Group } from '../../domain'; import { ClassInfoDto, ResolvedGroupUser } from '../dto'; import { ClassRootType } from '../dto/class-root-type'; diff --git a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor.api.spec.ts b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor.api.spec.ts index f2e40310645..57a8a66b347 100644 --- a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor.api.spec.ts +++ b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor.api.spec.ts @@ -2,7 +2,7 @@ import { EntityManager } from '@mikro-orm/core'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; -import { H5PEditorTestModule } from '@src/modules/h5p-editor/h5p-editor-test.module'; +import { H5PEditorTestModule } from '@modules/h5p-editor/h5p-editor-test.module'; describe('H5PEditor Controller (api)', () => { let app: INestApplication; diff --git a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts index 6161b7d7c23..519f96e75e1 100644 --- a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts +++ b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts @@ -1,7 +1,7 @@ import { BadRequestException, Controller, ForbiddenException, Get, InternalServerErrorException } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { Authenticate } from '@src/modules/authentication'; +import { Authenticate } from '@modules/authentication/decorator/auth.decorator'; // Dummy html response so we can test i-frame integration const dummyResponse = (title: string) => ` diff --git a/apps/server/src/modules/h5p-editor/h5p-editor-test.module.ts b/apps/server/src/modules/h5p-editor/h5p-editor-test.module.ts index dfe1b1ec846..fccb5e2841b 100644 --- a/apps/server/src/modules/h5p-editor/h5p-editor-test.module.ts +++ b/apps/server/src/modules/h5p-editor/h5p-editor-test.module.ts @@ -5,8 +5,8 @@ import { MongoDatabaseModuleOptions } from '@shared/infra/database/mongo-memory- import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationModule } from '@modules/authorization'; import { AuthenticationApiModule } from '../authentication/authentication-api.module'; import { H5PEditorModule } from './h5p-editor.module'; diff --git a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts index 869f76d3a86..442f0a04409 100644 --- a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts +++ b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts @@ -6,7 +6,7 @@ import { Account, Role, SchoolEntity, SchoolYearEntity, SystemEntity, User } fro import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; import { CoreModule } from '@src/core'; import { Logger } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { AuthenticationModule } from '../authentication/authentication.module'; import { H5PEditorController } from './controller/h5p-editor.controller'; import { config } from './h5p-editor.config'; diff --git a/apps/server/src/modules/index.ts b/apps/server/src/modules/index.ts deleted file mode 100644 index 111a64d9f33..00000000000 --- a/apps/server/src/modules/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export * from './account'; -export * from './authentication'; -export * from './authorization'; -export * from './board'; -export * from './collaborative-storage'; -export * from './files-storage'; -export * from './files-storage-client'; -export * from './fwu-learning-contents'; -export * from './learnroom'; -export * from './lesson'; -export * from './news'; -export * from './oauth'; -export * from './oauth-provider'; -export * from './provisioning'; -export * from './rocketchat'; -export * from './role'; -export * from './legacy-school'; -export * from './sharing'; -export * from './system'; -export * from './task'; -export * from './tool'; -export * from './user'; -export * from './user-import'; -export * from './user-login-migration'; -export * from './video-conference'; diff --git a/apps/server/src/modules/learnroom/controller/api-test/course.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/course.api.spec.ts index 0d6518878f7..964df864abb 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/course.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/course.api.spec.ts @@ -3,8 +3,8 @@ import { INestApplication, StreamableFile } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { cleanupCollections, courseFactory, UserAndAccountTestFactory, TestApiClient } from '@shared/testing'; -import { CourseMetadataListResponse } from '@src/modules/learnroom/controller/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { CourseMetadataListResponse } from '@modules/learnroom/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; const createStudent = () => { const { studentUser, studentAccount } = UserAndAccountTestFactory.buildStudent({}, [Permission.COURSE_VIEW]); diff --git a/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts index c3e9dc3d5e0..b6c27736d93 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts @@ -2,12 +2,12 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { DashboardEntity, GridElement, Permission, User, RoleName } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { IDashboardRepo } from '@shared/repo'; import { courseFactory, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { DashboardResponse } from '@src/modules/learnroom/controller/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { DashboardResponse } from '@modules/learnroom/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts index 3b9f04e07db..2941e185ca1 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts @@ -3,7 +3,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -12,10 +12,10 @@ import { roleFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { Request } from 'express'; import request from 'supertest'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { createMock } from '@golevelup/ts-jest'; // config must be set outside before the server module is importat, otherwise the configuration is already set @@ -23,7 +23,7 @@ const configBefore = Configuration.toObject({ plainSecrets: true }); Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); Configuration.set('INCOMING_REQUEST_TIMEOUT_COPY_API', 1); // eslint-disable-next-line import/first -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; // This needs to be in a separate test file because of the above configuration. // When we find a way to mock the config, it should be moved alongside the other API tests. diff --git a/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts index 2c62b74c9fd..6db5100405a 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts @@ -14,12 +14,12 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { CopyApiResponse } from '@src/modules/copy-helper'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { SingleColumnBoardResponse } from '@src/modules/learnroom/controller/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { CopyApiResponse } from '@modules/copy-helper'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { SingleColumnBoardResponse } from '@modules/learnroom/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/learnroom/controller/course.controller.ts b/apps/server/src/modules/learnroom/controller/course.controller.ts index 7b09111edff..dfb4e920957 100644 --- a/apps/server/src/modules/learnroom/controller/course.controller.ts +++ b/apps/server/src/modules/learnroom/controller/course.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, NotFoundException, Param, Query, Res, StreamableFile } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { PaginationParams } from '@shared/controller/'; import { Response } from 'express'; import { ConfigService } from '@nestjs/config'; diff --git a/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts b/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts index 59445f2c1f8..284f18ef862 100644 --- a/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts +++ b/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts @@ -7,7 +7,7 @@ import { LearnroomMetadata, LearnroomTypes, } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { DashboardUc } from '../uc/dashboard.uc'; import { DashboardController } from './dashboard.controller'; import { DashboardResponse } from './dto'; diff --git a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts index da667a85f20..224f6c41ca7 100644 --- a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts +++ b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { DashboardMapper } from '../mapper/dashboard.mapper'; import { DashboardUc } from '../uc/dashboard.uc'; import { DashboardResponse, DashboardUrlParams, MoveElementParams, PatchGroupParams } from './dto'; diff --git a/apps/server/src/modules/learnroom/controller/dto/single-column-board/board-element.response.ts b/apps/server/src/modules/learnroom/controller/dto/single-column-board/board-element.response.ts index fb110ff95d9..4e9d139124b 100644 --- a/apps/server/src/modules/learnroom/controller/dto/single-column-board/board-element.response.ts +++ b/apps/server/src/modules/learnroom/controller/dto/single-column-board/board-element.response.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { RoomBoardElementTypes } from '@src/modules/learnroom/types'; +import { RoomBoardElementTypes } from '@modules/learnroom/types'; import { BoardColumnBoardResponse } from './board-column-board.response'; import { BoardLessonResponse } from './board-lesson.response'; import { BoardTaskResponse } from './board-task.response'; diff --git a/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts b/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts index 5692cc3427b..c63e2e380ae 100644 --- a/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts +++ b/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; -import { CopyApiResponse, CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { ICurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper'; import { RoomBoardDTO } from '../types'; import { CourseCopyUC } from '../uc/course-copy.uc'; diff --git a/apps/server/src/modules/learnroom/controller/rooms.controller.ts b/apps/server/src/modules/learnroom/controller/rooms.controller.ts index e01950e7570..0e0b2f7c7a0 100644 --- a/apps/server/src/modules/learnroom/controller/rooms.controller.ts +++ b/apps/server/src/modules/learnroom/controller/rooms.controller.ts @@ -1,9 +1,9 @@ import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { RequestTimeout } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; -import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; -import { serverConfig } from '@src/modules/server/server.config'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; +import { serverConfig } from '@modules/server/server.config'; import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper'; import { CourseCopyUC } from '../uc/course-copy.uc'; import { LessonCopyUC } from '../uc/lesson-copy.uc'; diff --git a/apps/server/src/modules/learnroom/learnroom-api.module.ts b/apps/server/src/modules/learnroom/learnroom-api.module.ts index 81a514a0a7b..5cfaada65b8 100644 --- a/apps/server/src/modules/learnroom/learnroom-api.module.ts +++ b/apps/server/src/modules/learnroom/learnroom-api.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, LessonRepo, UserRepo } from '@shared/repo'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; -import { CopyHelperModule } from '@src/modules/copy-helper'; -import { LessonModule } from '@src/modules/lesson'; +import { AuthorizationModule } from '@modules/authorization'; +import { AuthorizationReferenceModule } from '@modules/authorization/authorization-reference.module'; +import { CopyHelperModule } from '@modules/copy-helper'; +import { LessonModule } from '@modules/lesson'; import { CourseController } from './controller/course.controller'; import { DashboardController } from './controller/dashboard.controller'; import { RoomsController } from './controller/rooms.controller'; diff --git a/apps/server/src/modules/learnroom/learnroom.module.ts b/apps/server/src/modules/learnroom/learnroom.module.ts index 1149b477c30..c84310ba05e 100644 --- a/apps/server/src/modules/learnroom/learnroom.module.ts +++ b/apps/server/src/modules/learnroom/learnroom.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, LessonRepo, UserRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { BoardModule } from '@src/modules/board'; -import { CopyHelperModule } from '@src/modules/copy-helper'; -import { LessonModule } from '@src/modules/lesson'; -import { TaskModule } from '@src/modules/task'; +import { BoardModule } from '@modules/board'; +import { CopyHelperModule } from '@modules/copy-helper'; +import { LessonModule } from '@modules/lesson'; +import { TaskModule } from '@modules/task'; import { BoardCopyService, ColumnBoardTargetService, diff --git a/apps/server/src/modules/learnroom/service/board-copy.service.spec.ts b/apps/server/src/modules/learnroom/service/board-copy.service.spec.ts index 87542f7efb2..51ebc404d83 100644 --- a/apps/server/src/modules/learnroom/service/board-copy.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/board-copy.service.spec.ts @@ -17,10 +17,10 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ColumnBoardCopyService } from '@src/modules/board/service/column-board-copy.service'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { LessonCopyService } from '@src/modules/lesson/service'; -import { TaskCopyService } from '@src/modules/task/service'; +import { ColumnBoardCopyService } from '@modules/board/service/column-board-copy.service'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { LessonCopyService } from '@modules/lesson/service'; +import { TaskCopyService } from '@modules/task/service'; import { BoardCopyService } from './board-copy.service'; describe('board copy service', () => { diff --git a/apps/server/src/modules/learnroom/service/board-copy.service.ts b/apps/server/src/modules/learnroom/service/board-copy.service.ts index da31f4cef4e..f695dfd2c05 100644 --- a/apps/server/src/modules/learnroom/service/board-copy.service.ts +++ b/apps/server/src/modules/learnroom/service/board-copy.service.ts @@ -19,11 +19,11 @@ import { } from '@shared/domain'; import { BoardRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { ColumnBoardCopyService } from '@src/modules/board/service/column-board-copy.service'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { getResolvedValues } from '@src/modules/files-storage/helper'; -import { LessonCopyService } from '@src/modules/lesson/service'; -import { TaskCopyService } from '@src/modules/task/service'; +import { ColumnBoardCopyService } from '@modules/board/service/column-board-copy.service'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { getResolvedValues } from '@modules/files-storage/helper'; +import { LessonCopyService } from '@modules/lesson/service'; +import { TaskCopyService } from '@modules/task/service'; import { sortBy } from 'lodash'; type BoardCopyParams = { diff --git a/apps/server/src/modules/learnroom/service/column-board-target.service.spec.ts b/apps/server/src/modules/learnroom/service/column-board-target.service.spec.ts index 59a6169a2a3..64db859ddac 100644 --- a/apps/server/src/modules/learnroom/service/column-board-target.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/column-board-target.service.spec.ts @@ -4,7 +4,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ColumnBoardTarget } from '@shared/domain'; import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { cleanupCollections, columnBoardTargetFactory } from '@shared/testing'; -import { ColumnBoardService } from '@src/modules/board'; +import { ColumnBoardService } from '@modules/board'; import { ColumnBoardTargetService } from './column-board-target.service'; describe(ColumnBoardTargetService.name, () => { diff --git a/apps/server/src/modules/learnroom/service/column-board-target.service.ts b/apps/server/src/modules/learnroom/service/column-board-target.service.ts index 8f72cb3e44e..79b476c4fe6 100644 --- a/apps/server/src/modules/learnroom/service/column-board-target.service.ts +++ b/apps/server/src/modules/learnroom/service/column-board-target.service.ts @@ -2,7 +2,7 @@ import { FilterQuery } from '@mikro-orm/core'; import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { ColumnBoardTarget, EntityId } from '@shared/domain'; -import { ColumnBoardService } from '@src/modules/board'; +import { ColumnBoardService } from '@modules/board'; @Injectable() export class ColumnBoardTargetService { diff --git a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts index e8e3d43f9bc..af1bc727d6a 100644 --- a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts @@ -9,10 +9,10 @@ import { Task, } from '@shared/domain'; import { courseFactory, lessonFactory, setupEntities, taskFactory } from '@shared/testing'; -import { CommonCartridgeExportService } from '@src/modules/learnroom/service/common-cartridge-export.service'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LessonService } from '@src/modules/lesson/service'; -import { TaskService } from '@src/modules/task/service/task.service'; +import { CommonCartridgeExportService } from '@modules/learnroom/service/common-cartridge-export.service'; +import { CourseService } from '@modules/learnroom/service'; +import { LessonService } from '@modules/lesson/service'; +import { TaskService } from '@modules/task/service/task.service'; import AdmZip from 'adm-zip'; import { CommonCartridgeVersion } from '../common-cartridge'; diff --git a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts index b41b6314fa8..e25d2a62367 100644 --- a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts +++ b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { Course, EntityId, IComponentProperties, Task } from '@shared/domain'; -import { LessonService } from '@src/modules/lesson/service'; +import { LessonService } from '@modules/lesson/service'; import { ComponentType } from '@src/shared/domain/entity/lesson.entity'; -import { TaskService } from '@src/modules/task/service'; +import { TaskService } from '@modules/task/service'; import { CommonCartridgeFileBuilder, CommonCartridgeIntendedUseType, diff --git a/apps/server/src/modules/learnroom/service/course-copy.service.spec.ts b/apps/server/src/modules/learnroom/service/course-copy.service.spec.ts index 866ba655d97..969360dcc2e 100644 --- a/apps/server/src/modules/learnroom/service/course-copy.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/course-copy.service.spec.ts @@ -10,8 +10,8 @@ import { setupEntities, userFactory, } from '@shared/testing'; -import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; -import { LessonCopyService } from '@src/modules/lesson/service'; +import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; +import { LessonCopyService } from '@modules/lesson/service'; import { BoardCopyService } from './board-copy.service'; import { CourseCopyService } from './course-copy.service'; import { RoomsService } from './rooms.service'; diff --git a/apps/server/src/modules/learnroom/service/course-copy.service.ts b/apps/server/src/modules/learnroom/service/course-copy.service.ts index a8a0c236673..51f98bb436b 100644 --- a/apps/server/src/modules/learnroom/service/course-copy.service.ts +++ b/apps/server/src/modules/learnroom/service/course-copy.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Course, EntityId, User } from '@shared/domain'; import { BoardRepo, CourseRepo, UserRepo } from '@shared/repo'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { BoardCopyService } from './board-copy.service'; import { RoomsService } from './rooms.service'; diff --git a/apps/server/src/modules/learnroom/service/rooms.service.spec.ts b/apps/server/src/modules/learnroom/service/rooms.service.spec.ts index 789edcb9099..2358e2b2067 100644 --- a/apps/server/src/modules/learnroom/service/rooms.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/rooms.service.spec.ts @@ -6,8 +6,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { BoardExternalReference, BoardExternalReferenceType, EntityId } from '@shared/domain'; import { BoardRepo, LessonRepo } from '@shared/repo'; import { boardFactory, courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; -import { CardService, ColumnBoardService, ColumnService, ContentElementService } from '@src/modules/board'; -import { TaskService } from '@src/modules/task/service'; +import { CardService, ColumnBoardService, ColumnService, ContentElementService } from '@modules/board'; +import { TaskService } from '@modules/task/service'; import { ColumnBoardTargetService } from './column-board-target.service'; import { RoomsService } from './rooms.service'; diff --git a/apps/server/src/modules/learnroom/service/rooms.service.ts b/apps/server/src/modules/learnroom/service/rooms.service.ts index 24bffb03f4f..cc8b95e09b0 100644 --- a/apps/server/src/modules/learnroom/service/rooms.service.ts +++ b/apps/server/src/modules/learnroom/service/rooms.service.ts @@ -2,8 +2,8 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { Injectable } from '@nestjs/common'; import { Board, BoardExternalReferenceType, ColumnBoardTarget, EntityId } from '@shared/domain'; import { BoardRepo, LessonRepo } from '@shared/repo'; -import { ColumnBoardService } from '@src/modules/board'; -import { TaskService } from '@src/modules/task/service'; +import { ColumnBoardService } from '@modules/board'; +import { TaskService } from '@modules/task/service'; import { ColumnBoardTargetService } from './column-board-target.service'; @Injectable() diff --git a/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts b/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts index 33beee8c4db..9e051dae6da 100644 --- a/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/course-copy.uc.spec.ts @@ -4,9 +4,9 @@ import { ForbiddenException, InternalServerErrorException } from '@nestjs/common import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { courseFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; -import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; -import { CopyElementType, CopyStatusEnum } from '@src/modules/copy-helper'; +import { AuthorizationContextBuilder } from '@modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@modules/authorization/domain'; +import { CopyElementType, CopyStatusEnum } from '@modules/copy-helper'; import { CourseCopyService } from '../service'; import { CourseCopyUC } from './course-copy.uc'; diff --git a/apps/server/src/modules/learnroom/uc/course-copy.uc.ts b/apps/server/src/modules/learnroom/uc/course-copy.uc.ts index 0f700d57f17..19b94a9c238 100644 --- a/apps/server/src/modules/learnroom/uc/course-copy.uc.ts +++ b/apps/server/src/modules/learnroom/uc/course-copy.uc.ts @@ -1,9 +1,9 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; -import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; -import { CopyStatus } from '@src/modules/copy-helper'; +import { AuthorizationContextBuilder } from '@modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@modules/authorization/domain'; +import { CopyStatus } from '@modules/copy-helper'; import { CourseCopyService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts b/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts index 04e3d0de480..8ca36158f5d 100644 --- a/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/course-export.uc.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { CommonCartridgeExportService } from '@src/modules/learnroom/service/common-cartridge-export.service'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; +import { CommonCartridgeExportService } from '@modules/learnroom/service/common-cartridge-export.service'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { ObjectId } from 'bson'; import { ForbiddenException } from '@nestjs/common'; import { CourseExportUc } from './course-export.uc'; diff --git a/apps/server/src/modules/learnroom/uc/course-export.uc.ts b/apps/server/src/modules/learnroom/uc/course-export.uc.ts index 07e427c8fa8..758b1b550d7 100644 --- a/apps/server/src/modules/learnroom/uc/course-export.uc.ts +++ b/apps/server/src/modules/learnroom/uc/course-export.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; -import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizationContextBuilder } from '@modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@modules/authorization/domain'; import { CommonCartridgeVersion } from '../common-cartridge'; import { CommonCartridgeExportService } from '../service/common-cartridge-export.service'; diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts index 34d73449b4c..49c7c53435c 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts @@ -6,9 +6,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { CourseRepo, LessonRepo, UserRepo } from '@shared/repo'; import { courseFactory, lessonFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; -import { EtherpadService, LessonCopyService } from '@src/modules/lesson/service'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; +import { EtherpadService, LessonCopyService } from '@modules/lesson/service'; import { LessonCopyUC } from './lesson-copy.uc'; describe('lesson copy uc', () => { diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts index 7ec51f5ef1c..ae41fb6881b 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts @@ -3,10 +3,10 @@ import { ForbiddenException, Injectable, InternalServerErrorException } from '@n import { Course, EntityId, LessonEntity, User } from '@shared/domain'; import { Permission } from '@shared/domain/interface/permission.enum'; import { CourseRepo, LessonRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { CopyHelperService, CopyStatus } from '@src/modules/copy-helper'; -import { LessonCopyParentParams } from '@src/modules/lesson'; -import { LessonCopyService } from '@src/modules/lesson/service'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyHelperService, CopyStatus } from '@modules/copy-helper'; +import { LessonCopyParentParams } from '@modules/lesson'; +import { LessonCopyService } from '@modules/lesson/service'; @Injectable() export class LessonCopyUC { diff --git a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.spec.ts b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.spec.ts index 9c7ddf0c742..f983342f4f8 100644 --- a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.spec.ts +++ b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.spec.ts @@ -13,7 +13,7 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { LessonMetaData } from '../types'; import { RoomBoardDTOFactory } from './room-board-dto.factory'; import { RoomsAuthorisationService } from './rooms.authorisation.service'; diff --git a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts index cafa02e4d20..91bd043d472 100644 --- a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts +++ b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts @@ -14,7 +14,7 @@ import { TaskWithStatusVo, User, } from '@shared/domain'; -import { AuthorizationService, Action } from '@src/modules/authorization'; +import { AuthorizationService, Action } from '@modules/authorization'; import { ColumnBoardMetaData, LessonMetaData, diff --git a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.spec.ts b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.spec.ts index 1777ad02faa..764d71b6abf 100644 --- a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.spec.ts +++ b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.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 { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { MigrationMapper } from '../mapper/migration.mapper'; import { OauthMigrationDto } from '../uc/dto/oauth-migration.dto'; import { LegacySchoolUc } from '../uc'; diff --git a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts index 91d6892741e..58b591faea1 100644 --- a/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts +++ b/apps/server/src/modules/legacy-school/controller/legacy-school.controller.ts @@ -6,7 +6,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { MigrationMapper } from '../mapper/migration.mapper'; import { OauthMigrationDto } from '../uc/dto/oauth-migration.dto'; import { LegacySchoolUc } from '../uc'; diff --git a/apps/server/src/modules/legacy-school/legacy-school-api.module.ts b/apps/server/src/modules/legacy-school/legacy-school-api.module.ts index 3072fa8f6ca..aaf1f6acad2 100644 --- a/apps/server/src/modules/legacy-school/legacy-school-api.module.ts +++ b/apps/server/src/modules/legacy-school/legacy-school-api.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { LoggerModule } from '@src/core/logger'; -import { UserLoginMigrationModule } from '@src/modules/user-login-migration'; +import { UserLoginMigrationModule } from '@modules/user-login-migration'; import { LegacySchoolUc } from './uc'; import { LegacySchoolModule } from './legacy-school.module'; import { LegacySchoolController } from './controller/legacy-school.controller'; diff --git a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts index 8747a07ada6..138bcd81a0a 100644 --- a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts +++ b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.spec.ts @@ -3,14 +3,14 @@ import { UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, UserLoginMigrationDO } from '@shared/domain'; import { legacySchoolDoFactory, userLoginMigrationDOFactory } from '@shared/testing/factory'; -import { AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school/service'; -import { LegacySchoolUc } from '@src/modules/legacy-school/uc'; +import { AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school/service'; +import { LegacySchoolUc } from '@modules/legacy-school/uc'; import { SchoolMigrationService, UserLoginMigrationRevertService, UserLoginMigrationService, -} from '@src/modules/user-login-migration'; +} from '@modules/user-login-migration'; import { OauthMigrationDto } from './dto/oauth-migration.dto'; describe('LegacySchoolUc', () => { diff --git a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts index d1d13ffb037..50fd7faf5a5 100644 --- a/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts +++ b/apps/server/src/modules/legacy-school/uc/legacy-school.uc.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { Permission, LegacySchoolDo, UserLoginMigrationDO, User } from '@shared/domain'; import { SchoolMigrationService, UserLoginMigrationRevertService, UserLoginMigrationService, -} from '@src/modules/user-login-migration'; +} from '@modules/user-login-migration'; import { LegacySchoolService } from '../service'; import { OauthMigrationDto } from './dto/oauth-migration.dto'; diff --git a/apps/server/src/modules/lesson/controller/api-test/lesson-delete.api.spec.ts b/apps/server/src/modules/lesson/controller/api-test/lesson-delete.api.spec.ts index 96b9875cf15..d4a707420de 100644 --- a/apps/server/src/modules/lesson/controller/api-test/lesson-delete.api.spec.ts +++ b/apps/server/src/modules/lesson/controller/api-test/lesson-delete.api.spec.ts @@ -10,8 +10,8 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { ServerTestModule } from '@src/modules/server'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { ServerTestModule } from '@modules/server'; import { ObjectId } from 'bson'; describe('Lesson Controller (API) - delete', () => { diff --git a/apps/server/src/modules/lesson/controller/lesson.controller.ts b/apps/server/src/modules/lesson/controller/lesson.controller.ts index 7b65eb8afc1..14762a47d8c 100644 --- a/apps/server/src/modules/lesson/controller/lesson.controller.ts +++ b/apps/server/src/modules/lesson/controller/lesson.controller.ts @@ -1,6 +1,6 @@ import { Controller, Delete, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { LessonUC } from '../uc'; import { LessonUrlParams } from './dto'; diff --git a/apps/server/src/modules/lesson/lesson-api.module.ts b/apps/server/src/modules/lesson/lesson-api.module.ts index 6185b5da2a4..1f17893f582 100644 --- a/apps/server/src/modules/lesson/lesson-api.module.ts +++ b/apps/server/src/modules/lesson/lesson-api.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { LessonController } from './controller'; import { LessonModule } from './lesson.module'; import { LessonUC } from './uc'; diff --git a/apps/server/src/modules/lesson/lesson.module.ts b/apps/server/src/modules/lesson/lesson.module.ts index 021f4ab4efa..2e246c63211 100644 --- a/apps/server/src/modules/lesson/lesson.module.ts +++ b/apps/server/src/modules/lesson/lesson.module.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { FeathersServiceProvider } from '@shared/infra/feathers'; import { LessonRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { CopyHelperModule } from '@src/modules/copy-helper'; -import { FilesStorageClientModule } from '@src/modules/files-storage-client'; -import { TaskModule } from '@src/modules/task'; +import { CopyHelperModule } from '@modules/copy-helper'; +import { FilesStorageClientModule } from '@modules/files-storage-client'; +import { TaskModule } from '@modules/task'; import { EtherpadService, LessonCopyService, LessonService, NexboardService } from './service'; @Module({ diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts index 6097a3b87f6..34392c91c0c 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts @@ -24,9 +24,9 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { CopyFilesService } from '@src/modules/files-storage-client'; -import { TaskCopyService } from '@src/modules/task/service'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService } from '@modules/files-storage-client'; +import { TaskCopyService } from '@modules/task/service'; import { EtherpadService } from './etherpad.service'; import { LessonCopyService } from './lesson-copy.service'; import { NexboardService } from './nexboard.service'; diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.ts index cd83681a2e7..b6d7e7849c7 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.ts @@ -12,16 +12,10 @@ import { Material, } from '@shared/domain'; import { LessonRepo } from '@shared/repo'; -import { - CopyDictionary, - CopyElementType, - CopyHelperService, - CopyStatus, - CopyStatusEnum, -} from '@src/modules/copy-helper'; -import { CopyFilesService } from '@src/modules/files-storage-client'; -import { FileUrlReplacement } from '@src/modules/files-storage-client/service/copy-files.service'; -import { TaskCopyService } from '@src/modules/task/service/task-copy.service'; +import { CopyDictionary, CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService } from '@modules/files-storage-client'; +import { FileUrlReplacement } from '@modules/files-storage-client/service/copy-files.service'; +import { TaskCopyService } from '@modules/task/service/task-copy.service'; import { randomBytes } from 'crypto'; import { LessonCopyParams } from '../types'; import { EtherpadService } from './etherpad.service'; diff --git a/apps/server/src/modules/lesson/service/lesson.service.spec.ts b/apps/server/src/modules/lesson/service/lesson.service.spec.ts index 7f0179640b5..a94ecfe9c8b 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { LessonRepo } from '@shared/repo'; import { lessonFactory, setupEntities } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { ObjectId } from '@mikro-orm/mongodb'; import { ComponentType, IComponentProperties } from '@shared/domain'; import { LessonService } from './lesson.service'; diff --git a/apps/server/src/modules/lesson/service/lesson.service.ts b/apps/server/src/modules/lesson/service/lesson.service.ts index 5a69a19f305..2dee6f05563 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Counted, EntityId, IComponentProperties, LessonEntity } from '@shared/domain'; import { LessonRepo } from '@shared/repo'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; @Injectable() export class LessonService { diff --git a/apps/server/src/modules/lesson/uc/lesson.uc.spec.ts b/apps/server/src/modules/lesson/uc/lesson.uc.spec.ts index cd6b347b048..72448f8b770 100644 --- a/apps/server/src/modules/lesson/uc/lesson.uc.spec.ts +++ b/apps/server/src/modules/lesson/uc/lesson.uc.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { lessonFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { LessonService } from '../service'; import { LessonUC } from './lesson.uc'; diff --git a/apps/server/src/modules/lesson/uc/lesson.uc.ts b/apps/server/src/modules/lesson/uc/lesson.uc.ts index 68519a4e0e1..063a43ce9b9 100644 --- a/apps/server/src/modules/lesson/uc/lesson.uc.ts +++ b/apps/server/src/modules/lesson/uc/lesson.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { LessonService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/management/controller/api-test/database-management.api.spec.ts b/apps/server/src/modules/management/controller/api-test/database-management.api.spec.ts index d4c9967dcf4..012ee8f3cf2 100644 --- a/apps/server/src/modules/management/controller/api-test/database-management.api.spec.ts +++ b/apps/server/src/modules/management/controller/api-test/database-management.api.spec.ts @@ -1,7 +1,7 @@ import { MikroORM } from '@mikro-orm/core'; import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ManagementServerTestModule } from '@src/modules/management/management-server.module'; +import { ManagementServerTestModule } from '@modules/management/management-server.module'; import request from 'supertest'; describe('Database Management Controller (API)', () => { diff --git a/apps/server/src/modules/management/management.module.ts b/apps/server/src/modules/management/management.module.ts index 3fb5bf4e3c2..fc4c8bc08d3 100644 --- a/apps/server/src/modules/management/management.module.ts +++ b/apps/server/src/modules/management/management.module.ts @@ -8,7 +8,7 @@ import { FileSystemModule } from '@shared/infra/file-system'; import { KeycloakConfigurationModule } from '@shared/infra/identity-management/keycloak-configuration/keycloak-configuration.module'; import { createConfigModuleOptions } from '@src/config'; import { LoggerModule } from '@src/core/logger'; -import { serverConfig } from '@src/modules/server'; +import { serverConfig } from '@modules/server'; import { BoardManagementConsole } from './console/board-management.console'; import { DatabaseManagementConsole } from './console/database-management.console'; import { DatabaseManagementController } from './controller/database-management.controller'; diff --git a/apps/server/src/modules/news/controller/api-test/news.api.spec.ts b/apps/server/src/modules/news/controller/api-test/news.api.spec.ts index 644dde5a371..31e4a10af9a 100644 --- a/apps/server/src/modules/news/controller/api-test/news.api.spec.ts +++ b/apps/server/src/modules/news/controller/api-test/news.api.spec.ts @@ -3,10 +3,10 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, News, NewsTargetModel } from '@shared/domain'; import { API_VALIDATION_ERROR_TYPE } from '@src/core/error/server-error-types'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { FeathersAuthorizationService } from '@src/modules/authorization'; -import { CreateNewsParams, NewsListResponse, NewsResponse, UpdateNewsParams } from '@src/modules/news/controller/dto'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FeathersAuthorizationService } from '@modules/authorization'; +import { CreateNewsParams, NewsListResponse, NewsResponse, UpdateNewsParams } from '@modules/news/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import moment from 'moment'; import request from 'supertest'; diff --git a/apps/server/src/modules/news/controller/news.controller.ts b/apps/server/src/modules/news/controller/news.controller.ts index 325a5c3f6fc..2f1c227401a 100644 --- a/apps/server/src/modules/news/controller/news.controller.ts +++ b/apps/server/src/modules/news/controller/news.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc/news.uc'; import { diff --git a/apps/server/src/modules/news/controller/team-news.controller.ts b/apps/server/src/modules/news/controller/team-news.controller.ts index 29199932eaf..2344d6f2ac9 100644 --- a/apps/server/src/modules/news/controller/team-news.controller.ts +++ b/apps/server/src/modules/news/controller/team-news.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { PaginationParams } from '@shared/controller'; import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc'; diff --git a/apps/server/src/modules/news/news.module.ts b/apps/server/src/modules/news/news.module.ts index 261a4b2b6ba..3766b26052e 100644 --- a/apps/server/src/modules/news/news.module.ts +++ b/apps/server/src/modules/news/news.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { NewsRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { AuthorizationModule } from '@modules/authorization'; import { NewsController } from './controller/news.controller'; import { TeamNewsController } from './controller/team-news.controller'; import { NewsUc } from './uc/news.uc'; diff --git a/apps/server/src/modules/news/uc/news.uc.spec.ts b/apps/server/src/modules/news/uc/news.uc.spec.ts index 39e4c9af9f7..d0671dd4259 100644 --- a/apps/server/src/modules/news/uc/news.uc.spec.ts +++ b/apps/server/src/modules/news/uc/news.uc.spec.ts @@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ICreateNews, NewsTargetModel, Permission } from '@shared/domain'; import { NewsRepo } from '@shared/repo'; import { Logger } from '@src/core/logger'; -import { FeathersAuthorizationService } from '@src/modules/authorization'; +import { FeathersAuthorizationService } from '@modules/authorization'; import { NewsUc } from './news.uc'; describe('NewsUc', () => { diff --git a/apps/server/src/modules/news/uc/news.uc.ts b/apps/server/src/modules/news/uc/news.uc.ts index 39aeb05f03b..de0608b0234 100644 --- a/apps/server/src/modules/news/uc/news.uc.ts +++ b/apps/server/src/modules/news/uc/news.uc.ts @@ -14,7 +14,7 @@ import { import { NewsRepo, NewsTargetFilter } from '@shared/repo'; import { CrudOperation } from '@shared/types'; import { Logger } from '@src/core/logger'; -import { FeathersAuthorizationService } from '@src/modules/authorization'; +import { FeathersAuthorizationService } from '@modules/authorization'; import { NewsCrudOperationLoggable } from '../loggable/news-crud-operation.loggable'; type NewsPermission = Permission.NEWS_VIEW | Permission.NEWS_EDIT; diff --git a/apps/server/src/modules/oauth-provider/controller/dto/request/oauth-client.body.ts b/apps/server/src/modules/oauth-provider/controller/dto/request/oauth-client.body.ts index 735ebc02d5f..277922526f7 100644 --- a/apps/server/src/modules/oauth-provider/controller/dto/request/oauth-client.body.ts +++ b/apps/server/src/modules/oauth-provider/controller/dto/request/oauth-client.body.ts @@ -1,7 +1,7 @@ import { IsArray, IsEnum, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { SubjectTypeEnum } from '@src/modules/oauth-provider/interface/subject-type.enum'; -import { TokenAuthMethod } from '@src/modules/oauth-provider/interface/token-auth-method.enum'; +import { SubjectTypeEnum } from '@modules/oauth-provider/interface/subject-type.enum'; +import { TokenAuthMethod } from '@modules/oauth-provider/interface/token-auth-method.enum'; export class OauthClientBody { @IsString() diff --git a/apps/server/src/modules/oauth-provider/controller/dto/response/consent.response.ts b/apps/server/src/modules/oauth-provider/controller/dto/response/consent.response.ts index 3953273c2bf..a5c90fe11a2 100644 --- a/apps/server/src/modules/oauth-provider/controller/dto/response/consent.response.ts +++ b/apps/server/src/modules/oauth-provider/controller/dto/response/consent.response.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsOptional, IsString } from 'class-validator'; -import { OidcContextResponse } from '@src/modules/oauth-provider/controller/dto/response/oidc-context.response'; -import { OauthClientResponse } from '@src/modules/oauth-provider/controller/dto/response/oauth-client.response'; +import { OidcContextResponse } from '@modules/oauth-provider/controller/dto/response/oidc-context.response'; +import { OauthClientResponse } from '@modules/oauth-provider/controller/dto/response/oauth-client.response'; export class ConsentResponse { constructor(consentResponse: ConsentResponse) { diff --git a/apps/server/src/modules/oauth-provider/controller/dto/response/login.response.ts b/apps/server/src/modules/oauth-provider/controller/dto/response/login.response.ts index ddde211e219..761276bdc5c 100644 --- a/apps/server/src/modules/oauth-provider/controller/dto/response/login.response.ts +++ b/apps/server/src/modules/oauth-provider/controller/dto/response/login.response.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { OauthClientResponse } from '@src/modules/oauth-provider/controller/dto/response/oauth-client.response'; -import { OidcContextResponse } from '@src/modules/oauth-provider/controller/dto/response/oidc-context.response'; +import { OauthClientResponse } from '@modules/oauth-provider/controller/dto/response/oauth-client.response'; +import { OidcContextResponse } from '@modules/oauth-provider/controller/dto/response/oidc-context.response'; import { IsArray, IsOptional, IsString } from 'class-validator'; export class LoginResponse { diff --git a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts index 7edec7ece8d..83a3e3ac47b 100644 --- a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts +++ b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { OauthProviderLogoutFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; +import { OauthProviderLogoutFlowUc } from '@modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { OauthProviderResponseMapper } from '@src/modules/oauth-provider/mapper/oauth-provider-response.mapper'; +import { OauthProviderResponseMapper } from '@modules/oauth-provider/mapper/oauth-provider-response.mapper'; import { AcceptQuery, ChallengeParams, @@ -14,16 +14,16 @@ import { OauthClientBody, OauthClientResponse, RedirectResponse, -} from '@src/modules/oauth-provider/controller/dto'; +} from '@modules/oauth-provider/controller/dto'; import { ProviderConsentResponse, ProviderConsentSessionResponse, ProviderLoginResponse, ProviderRedirectResponse, } from '@shared/infra/oauth-provider/dto'; -import { OauthProviderConsentFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; -import { ICurrentUser } from '@src/modules/authentication'; -import { OauthProviderUc } from '@src/modules/oauth-provider/uc/oauth-provider.uc'; +import { OauthProviderConsentFlowUc } from '@modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; +import { ICurrentUser } from '@modules/authentication'; +import { OauthProviderUc } from '@modules/oauth-provider/uc/oauth-provider.uc'; import { OauthProviderController } from './oauth-provider.controller'; import { OauthProviderClientCrudUc } from '../uc/oauth-provider.client-crud.uc'; import { OauthProviderLoginFlowUc } from '../uc/oauth-provider.login-flow.uc'; diff --git a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts index 5c1a0e8e1e8..054cca37ffa 100644 --- a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts +++ b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts @@ -1,6 +1,6 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@src/modules/authentication'; +import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; // import should be @shared/infra/oauth-provider import { ProviderConsentResponse, diff --git a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-request.mapper.ts b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-request.mapper.ts index c44bda25da6..e0d4c4aaef4 100644 --- a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-request.mapper.ts +++ b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-request.mapper.ts @@ -1,5 +1,5 @@ import { AcceptLoginRequestBody } from '@shared/infra/oauth-provider/dto'; -import { LoginRequestBody } from '@src/modules/oauth-provider/controller/dto'; +import { LoginRequestBody } from '@modules/oauth-provider/controller/dto'; export class OauthProviderRequestMapper { static mapCreateAcceptLoginRequestBody( diff --git a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.spec.ts b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.spec.ts index 6e62e1ce6c7..13119635f75 100644 --- a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.spec.ts +++ b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.spec.ts @@ -1,4 +1,4 @@ -import { OauthProviderResponseMapper } from '@src/modules/oauth-provider/mapper/oauth-provider-response.mapper'; +import { OauthProviderResponseMapper } from '@modules/oauth-provider/mapper/oauth-provider-response.mapper'; import { ProviderConsentResponse, ProviderConsentSessionResponse, @@ -12,7 +12,7 @@ import { LoginResponse, OauthClientResponse, RedirectResponse, -} from '@src/modules/oauth-provider/controller/dto/'; +} from '@modules/oauth-provider/controller/dto/'; describe('OauthProviderResponseMapper', () => { let mapper: OauthProviderResponseMapper; diff --git a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.ts b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.ts index 19fef36d0f5..01038c23526 100644 --- a/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.ts +++ b/apps/server/src/modules/oauth-provider/mapper/oauth-provider-response.mapper.ts @@ -12,7 +12,7 @@ import { LoginResponse, OauthClientResponse, RedirectResponse, -} from '@src/modules/oauth-provider/controller/dto'; +} from '@modules/oauth-provider/controller/dto'; @Injectable() export class OauthProviderResponseMapper { diff --git a/apps/server/src/modules/oauth-provider/oauth-provider-api.module.ts b/apps/server/src/modules/oauth-provider/oauth-provider-api.module.ts index 907a28c1fc7..ccbd1566cda 100644 --- a/apps/server/src/modules/oauth-provider/oauth-provider-api.module.ts +++ b/apps/server/src/modules/oauth-provider/oauth-provider-api.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { OauthProviderServiceModule } from '@shared/infra/oauth-provider'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { PseudonymModule } from '@src/modules/pseudonym'; -import { UserModule } from '@src/modules/user'; +import { AuthorizationModule } from '@modules/authorization'; +import { PseudonymModule } from '@modules/pseudonym'; +import { UserModule } from '@modules/user'; import { OauthProviderController } from './controller/oauth-provider.controller'; import { OauthProviderResponseMapper } from './mapper/oauth-provider-response.mapper'; import { OauthProviderModule } from './oauth-provider.module'; diff --git a/apps/server/src/modules/oauth-provider/oauth-provider.module.ts b/apps/server/src/modules/oauth-provider/oauth-provider.module.ts index d963d247a93..4289644d29e 100644 --- a/apps/server/src/modules/oauth-provider/oauth-provider.module.ts +++ b/apps/server/src/modules/oauth-provider/oauth-provider.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common'; import { OauthProviderServiceModule } from '@shared/infra/oauth-provider'; import { TeamsRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { LtiToolModule } from '@src/modules/lti-tool'; -import { PseudonymModule } from '@src/modules/pseudonym'; -import { ToolModule } from '@src/modules/tool'; -import { ToolConfigModule } from '@src/modules/tool/tool-config.module'; -import { UserModule } from '@src/modules/user'; +import { LtiToolModule } from '@modules/lti-tool'; +import { PseudonymModule } from '@modules/pseudonym'; +import { ToolModule } from '@modules/tool'; +import { ToolConfigModule } from '@modules/tool/tool-config.module'; +import { UserModule } from '@modules/user'; import { IdTokenService } from './service/id-token.service'; import { OauthProviderLoginFlowService } from './service/oauth-provider.login-flow.service'; diff --git a/apps/server/src/modules/oauth-provider/service/id-token.service.spec.ts b/apps/server/src/modules/oauth-provider/service/id-token.service.spec.ts index 48f21de4077..4f8eff19c80 100644 --- a/apps/server/src/modules/oauth-provider/service/id-token.service.spec.ts +++ b/apps/server/src/modules/oauth-provider/service/id-token.service.spec.ts @@ -4,12 +4,12 @@ import { Pseudonym, TeamEntity, UserDO } from '@shared/domain'; import { TeamsRepo } from '@shared/repo'; import { externalToolFactory, pseudonymFactory, setupEntities, userDoFactory } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { IdToken } from '@src/modules/oauth-provider/interface/id-token'; -import { OauthScope } from '@src/modules/oauth-provider/interface/oauth-scope.enum'; -import { IdTokenService } from '@src/modules/oauth-provider/service/id-token.service'; -import { PseudonymService } from '@src/modules/pseudonym/service'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { UserService } from '@src/modules/user/service/user.service'; +import { IdToken } from '@modules/oauth-provider/interface/id-token'; +import { OauthScope } from '@modules/oauth-provider/interface/oauth-scope.enum'; +import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; +import { PseudonymService } from '@modules/pseudonym/service'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { UserService } from '@modules/user/service/user.service'; import { IdTokenCreationLoggableException } from '../error/id-token-creation-exception.loggable'; import { OauthProviderLoginFlowService } from './oauth-provider.login-flow.service'; import resetAllMocks = jest.resetAllMocks; diff --git a/apps/server/src/modules/oauth-provider/service/id-token.service.ts b/apps/server/src/modules/oauth-provider/service/id-token.service.ts index dbe1b2c54fc..998ff46f15f 100644 --- a/apps/server/src/modules/oauth-provider/service/id-token.service.ts +++ b/apps/server/src/modules/oauth-provider/service/id-token.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { LtiToolDO, Pseudonym, TeamEntity, UserDO } from '@shared/domain'; import { TeamsRepo } from '@shared/repo'; -import { PseudonymService } from '@src/modules/pseudonym'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { UserService } from '@src/modules/user'; +import { PseudonymService } from '@modules/pseudonym'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { UserService } from '@modules/user'; import { IdTokenCreationLoggableException } from '../error/id-token-creation-exception.loggable'; import { GroupNameIdTuple, IdToken, OauthScope } from '../interface'; import { OauthProviderLoginFlowService } from './oauth-provider.login-flow.service'; diff --git a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts index 3d382fffe3b..3c85dde6c62 100644 --- a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts +++ b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts @@ -3,10 +3,10 @@ import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { LtiToolDO } from '@shared/domain'; import { externalToolFactory, ltiToolDOFactory, setupEntities } from '@shared/testing'; -import { LtiToolService } from '@src/modules/lti-tool'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { IToolFeatures, ToolFeatures } from '@src/modules/tool/tool-config'; +import { LtiToolService } from '@modules/lti-tool'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { IToolFeatures, ToolFeatures } from '@modules/tool/tool-config'; import { OauthProviderLoginFlowService } from './oauth-provider.login-flow.service'; describe('OauthProviderLoginFlowService', () => { diff --git a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts index 367efdcf6f7..0b1948baa28 100644 --- a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts +++ b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts @@ -2,10 +2,10 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; -import { LtiToolService } from '@src/modules/lti-tool/service'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { IToolFeatures, ToolFeatures } from '@src/modules/tool/tool-config'; +import { LtiToolService } from '@modules/lti-tool/service'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { IToolFeatures, ToolFeatures } from '@modules/tool/tool-config'; @Injectable() export class OauthProviderLoginFlowService { diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts index b9d2abc6117..6ce203ab5b7 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts @@ -5,8 +5,8 @@ import { Permission, User } from '@shared/domain'; import { OauthProviderService } from '@shared/infra/oauth-provider'; import { ProviderOauthClient } from '@shared/infra/oauth-provider/dto'; import { setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationService } from '@src/modules'; -import { ICurrentUser } from '@src/modules/authentication'; +import { AuthorizationService } from '@modules/authorization'; +import { ICurrentUser } from '@modules/authentication'; import { OauthProviderClientCrudUc } from './oauth-provider.client-crud.uc'; import resetAllMocks = jest.resetAllMocks; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts index d0480398ec8..3595f00679b 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { OauthProviderService } from '@shared/infra/oauth-provider/index'; import { Permission, User } from '@shared/domain/index'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ProviderOauthClient } from '@shared/infra/oauth-provider/dto'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; @Injectable() export class OauthProviderClientCrudUc { diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts index 9abb5c8564c..b397b048dd4 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts @@ -1,17 +1,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { OauthProviderService } from '@shared/infra/oauth-provider/index'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { AcceptQuery, ConsentRequestBody } from '@src/modules/oauth-provider/controller/dto'; +import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; import { AcceptConsentRequestBody, ProviderConsentResponse, ProviderRedirectResponse, } from '@shared/infra/oauth-provider/dto'; -import { OauthProviderConsentFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; -import { ICurrentUser } from '@src/modules/authentication'; +import { OauthProviderConsentFlowUc } from '@modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; +import { ICurrentUser } from '@modules/authentication'; import { ForbiddenException } from '@nestjs/common'; -import { IdTokenService } from '@src/modules/oauth-provider/service/id-token.service'; -import { IdToken } from '@src/modules/oauth-provider/interface/id-token'; +import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; +import { IdToken } from '@modules/oauth-provider/interface/id-token'; describe('OauthProviderConsentFlowUc', () => { let module: TestingModule; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts index 3fcf82687f6..eb91d8132fe 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts @@ -4,12 +4,12 @@ import { ProviderRedirectResponse, RejectRequestBody, } from '@shared/infra/oauth-provider/dto'; -import { AcceptQuery, ConsentRequestBody } from '@src/modules/oauth-provider/controller/dto'; -import { ICurrentUser } from '@src/modules/authentication'; +import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; +import { ICurrentUser } from '@modules/authentication'; import { ForbiddenException, Injectable } from '@nestjs/common'; -import { IdTokenService } from '@src/modules/oauth-provider/service/id-token.service'; +import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; import { OauthProviderService } from '@shared/infra/oauth-provider'; -import { IdToken } from '@src/modules/oauth-provider/interface/id-token'; +import { IdToken } from '@modules/oauth-provider/interface/id-token'; @Injectable() export class OauthProviderConsentFlowUc { diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.spec.ts index e95aea26594..a9225031d04 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.spec.ts @@ -12,10 +12,10 @@ import { userDoFactory, userFactory, } from '@shared/testing'; -import { AuthorizationService } from '@src/modules/authorization'; -import { PseudonymService } from '@src/modules/pseudonym'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { UserService } from '@src/modules/user'; +import { AuthorizationService } from '@modules/authorization'; +import { PseudonymService } from '@modules/pseudonym'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { UserService } from '@modules/user'; import { AcceptQuery, LoginRequestBody, OAuthRejectableBody } from '../controller/dto'; import { OauthProviderLoginFlowService } from '../service/oauth-provider.login-flow.service'; import { OauthProviderLoginFlowUc } from './oauth-provider.login-flow.uc'; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.ts index 8901b506f99..dade1cb3f07 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.login-flow.uc.ts @@ -7,12 +7,12 @@ import { ProviderLoginResponse, ProviderRedirectResponse, } from '@shared/infra/oauth-provider/dto'; -import { AuthorizationService } from '@src/modules/authorization'; -import { AcceptQuery, LoginRequestBody, OAuthRejectableBody } from '@src/modules/oauth-provider/controller/dto'; -import { OauthProviderRequestMapper } from '@src/modules/oauth-provider/mapper/oauth-provider-request.mapper'; -import { PseudonymService } from '@src/modules/pseudonym/service'; -import { ExternalTool, Oauth2ToolConfig } from '@src/modules/tool/external-tool/domain'; -import { UserService } from '@src/modules/user'; +import { AuthorizationService } from '@modules/authorization'; +import { AcceptQuery, LoginRequestBody, OAuthRejectableBody } from '@modules/oauth-provider/controller/dto'; +import { OauthProviderRequestMapper } from '@modules/oauth-provider/mapper/oauth-provider-request.mapper'; +import { PseudonymService } from '@modules/pseudonym/service'; +import { ExternalTool, Oauth2ToolConfig } from '@modules/tool/external-tool/domain'; +import { UserService } from '@modules/user'; import { OauthProviderLoginFlowService } from '../service/oauth-provider.login-flow.service'; @Injectable() diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc.spec.ts index 7814ee90a7a..778112840b2 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { OauthProviderLogoutFlowUc } from '@src/modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; +import { OauthProviderLogoutFlowUc } from '@modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; import { OauthProviderService } from '@shared/infra/oauth-provider/index'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.uc.spec.ts index 90a1e5262d8..f1205db3d28 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.uc.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { OauthProviderUc } from '@src/modules/oauth-provider/uc/oauth-provider.uc'; +import { OauthProviderUc } from '@modules/oauth-provider/uc/oauth-provider.uc'; import { OauthProviderService } from '@shared/infra/oauth-provider/index'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ProviderConsentSessionResponse } from '@shared/infra/oauth-provider/dto'; diff --git a/apps/server/src/modules/oauth/controller/api-test/oauth-sso.api.spec.ts b/apps/server/src/modules/oauth/controller/api-test/oauth-sso.api.spec.ts index 7ddecbbaa54..eaaf07f4500 100644 --- a/apps/server/src/modules/oauth/controller/api-test/oauth-sso.api.spec.ts +++ b/apps/server/src/modules/oauth/controller/api-test/oauth-sso.api.spec.ts @@ -16,10 +16,10 @@ import { } from '@shared/testing'; import { JwtTestFactory } from '@shared/testing/factory/jwt.test.factory'; import { userLoginMigrationFactory } from '@shared/testing/factory/user-login-migration.factory'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { SanisResponse, SanisRole } from '@src/modules/provisioning/strategy/sanis/response'; -import { ServerTestModule } from '@src/modules/server'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { SanisResponse, SanisRole } from '@modules/provisioning/strategy/sanis/response'; +import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { UUID } from 'bson'; diff --git a/apps/server/src/modules/oauth/controller/oauth-sso.controller.spec.ts b/apps/server/src/modules/oauth/controller/oauth-sso.controller.spec.ts index ee86f8e1b25..3d1a470e227 100644 --- a/apps/server/src/modules/oauth/controller/oauth-sso.controller.spec.ts +++ b/apps/server/src/modules/oauth/controller/oauth-sso.controller.spec.ts @@ -3,8 +3,8 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { HydraOauthUc } from '@src/modules/oauth/uc/hydra-oauth.uc'; +import { ICurrentUser } from '@modules/authentication'; +import { HydraOauthUc } from '@modules/oauth/uc/hydra-oauth.uc'; import { Request } from 'express'; import { OauthSSOController } from './oauth-sso.controller'; import { StatelessAuthorizationParams } from './dto/stateless-authorization.params'; diff --git a/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts b/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts index a2c1b116d6d..5ff7e7cae02 100644 --- a/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts +++ b/apps/server/src/modules/oauth/controller/oauth-sso.controller.ts @@ -14,9 +14,9 @@ import { import { ApiOkResponse, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ISession } from '@shared/domain/types/session'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser, Authenticate, CurrentUser, JWT } from '@src/modules/authentication'; -import { OAuthMigrationError } from '@src/modules/user-login-migration/error/oauth-migration.error'; -import { MigrationDto } from '@src/modules/user-login-migration/service/dto'; +import { ICurrentUser, Authenticate, CurrentUser, JWT } from '@modules/authentication'; +import { OAuthMigrationError } from '@modules/user-login-migration/error/oauth-migration.error'; +import { MigrationDto } from '@modules/user-login-migration/service/dto'; import { CookieOptions, Request, Response } from 'express'; import { HydraOauthUc } from '../uc/hydra-oauth.uc'; import { UserMigrationResponse } from './dto/user-migration.response'; diff --git a/apps/server/src/modules/oauth/mapper/user-migration.mapper.ts b/apps/server/src/modules/oauth/mapper/user-migration.mapper.ts index a6c7694aa60..42134d0b4d2 100644 --- a/apps/server/src/modules/oauth/mapper/user-migration.mapper.ts +++ b/apps/server/src/modules/oauth/mapper/user-migration.mapper.ts @@ -1,4 +1,4 @@ -import { MigrationDto } from '@src/modules/user-login-migration/service/dto'; +import { MigrationDto } from '@modules/user-login-migration/service/dto'; import { UserMigrationResponse } from '../controller/dto'; export class UserMigrationMapper { diff --git a/apps/server/src/modules/oauth/oauth-api.module.ts b/apps/server/src/modules/oauth/oauth-api.module.ts index 4bde06ce6a0..98e62d87eca 100644 --- a/apps/server/src/modules/oauth/oauth-api.module.ts +++ b/apps/server/src/modules/oauth/oauth-api.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { ProvisioningModule } from '@src/modules/provisioning'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { SystemModule } from '@src/modules/system'; -import { UserModule } from '@src/modules/user'; -import { UserLoginMigrationModule } from '@src/modules/user-login-migration'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { ProvisioningModule } from '@modules/provisioning'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { SystemModule } from '@modules/system'; +import { UserModule } from '@modules/user'; +import { UserLoginMigrationModule } from '@modules/user-login-migration'; import { OauthSSOController } from './controller/oauth-sso.controller'; import { OauthModule } from './oauth.module'; import { HydraOauthUc, OauthUc } from './uc'; diff --git a/apps/server/src/modules/oauth/oauth.module.ts b/apps/server/src/modules/oauth/oauth.module.ts index b8519e3eaa2..273a099159b 100644 --- a/apps/server/src/modules/oauth/oauth.module.ts +++ b/apps/server/src/modules/oauth/oauth.module.ts @@ -4,12 +4,12 @@ import { CacheWrapperModule } from '@shared/infra/cache'; import { EncryptionModule } from '@shared/infra/encryption'; import { LtiToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { ProvisioningModule } from '@src/modules/provisioning'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { SystemModule } from '@src/modules/system'; -import { UserModule } from '@src/modules/user'; -import { UserLoginMigrationModule } from '@src/modules/user-login-migration'; +import { AuthorizationModule } from '@modules/authorization'; +import { ProvisioningModule } from '@modules/provisioning'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { SystemModule } from '@modules/system'; +import { UserModule } from '@modules/user'; +import { UserLoginMigrationModule } from '@modules/user-login-migration'; import { HydraSsoService } from './service/hydra.service'; import { OauthAdapterService } from './service/oauth-adapter.service'; import { OAuthService } from './service/oauth.service'; diff --git a/apps/server/src/modules/oauth/service/dto/hydra.redirect.dto.ts b/apps/server/src/modules/oauth/service/dto/hydra.redirect.dto.ts index fe4a244de4e..4a1ead7c157 100644 --- a/apps/server/src/modules/oauth/service/dto/hydra.redirect.dto.ts +++ b/apps/server/src/modules/oauth/service/dto/hydra.redirect.dto.ts @@ -1,4 +1,4 @@ -import { CookiesDto } from '@src/modules/oauth/service/dto/cookies.dto'; +import { CookiesDto } from '@modules/oauth/service/dto/cookies.dto'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; export class HydraRedirectDto { diff --git a/apps/server/src/modules/oauth/service/hydra.service.spec.ts b/apps/server/src/modules/oauth/service/hydra.service.spec.ts index 4912bc039ca..3886aa40a58 100644 --- a/apps/server/src/modules/oauth/service/hydra.service.spec.ts +++ b/apps/server/src/modules/oauth/service/hydra.service.spec.ts @@ -10,9 +10,9 @@ import { DefaultEncryptionService, SymetricKeyEncryptionService } from '@shared/ import { LtiToolRepo } from '@shared/repo'; import { axiosResponseFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { CookiesDto } from '@src/modules/oauth/service/dto/cookies.dto'; -import { HydraRedirectDto } from '@src/modules/oauth/service/dto/hydra.redirect.dto'; -import { HydraSsoService } from '@src/modules/oauth/service/hydra.service'; +import { CookiesDto } from '@modules/oauth/service/dto/cookies.dto'; +import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; +import { HydraSsoService } from '@modules/oauth/service/hydra.service'; import { AxiosResponse } from 'axios'; import { of } from 'rxjs'; import { StatelessAuthorizationParams } from '../controller/dto/stateless-authorization.params'; diff --git a/apps/server/src/modules/oauth/service/hydra.service.ts b/apps/server/src/modules/oauth/service/hydra.service.ts index 94ab8c66ff8..9b335604825 100644 --- a/apps/server/src/modules/oauth/service/hydra.service.ts +++ b/apps/server/src/modules/oauth/service/hydra.service.ts @@ -7,9 +7,9 @@ import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; import { DefaultEncryptionService, IEncryptionService } from '@shared/infra/encryption'; import { LtiToolRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationParams } from '@src/modules/oauth/controller/dto/authorization.params'; -import { CookiesDto } from '@src/modules/oauth/service/dto/cookies.dto'; -import { HydraRedirectDto } from '@src/modules/oauth/service/dto/hydra.redirect.dto'; +import { AuthorizationParams } from '@modules/oauth/controller/dto/authorization.params'; +import { CookiesDto } from '@modules/oauth/service/dto/cookies.dto'; +import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { nanoid } from 'nanoid'; import QueryString from 'qs'; diff --git a/apps/server/src/modules/oauth/service/oauth.service.spec.ts b/apps/server/src/modules/oauth/service/oauth.service.spec.ts index 81bb7724dbd..2743037e214 100644 --- a/apps/server/src/modules/oauth/service/oauth.service.spec.ts +++ b/apps/server/src/modules/oauth/service/oauth.service.spec.ts @@ -7,14 +7,14 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-prov import { DefaultEncryptionService, IEncryptionService, SymetricKeyEncryptionService } from '@shared/infra/encryption'; import { legacySchoolDoFactory, setupEntities, systemFactory, userDoFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ProvisioningDto, ProvisioningService } from '@src/modules/provisioning'; -import { ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@src/modules/provisioning/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { OauthConfigDto } from '@src/modules/system/service'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { SystemService } from '@src/modules/system/service/system.service'; -import { UserService } from '@src/modules/user'; -import { MigrationCheckService, UserMigrationService } from '@src/modules/user-login-migration'; +import { ProvisioningDto, ProvisioningService } from '@modules/provisioning'; +import { ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@modules/provisioning/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { OauthConfigDto } from '@modules/system/service'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { SystemService } from '@modules/system/service/system.service'; +import { UserService } from '@modules/user'; +import { MigrationCheckService, UserMigrationService } from '@modules/user-login-migration'; import jwt, { JwtPayload } from 'jsonwebtoken'; import { OAuthSSOError, UserNotFoundAfterProvisioningLoggableException } from '../loggable'; import { OAuthTokenDto } from '../interface'; diff --git a/apps/server/src/modules/oauth/service/oauth.service.ts b/apps/server/src/modules/oauth/service/oauth.service.ts index 4445438b11f..28a24c0534a 100644 --- a/apps/server/src/modules/oauth/service/oauth.service.ts +++ b/apps/server/src/modules/oauth/service/oauth.service.ts @@ -4,13 +4,13 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator' import { EntityId, LegacySchoolDo, OauthConfig, SchoolFeatures, UserDO } from '@shared/domain'; import { DefaultEncryptionService, IEncryptionService } from '@shared/infra/encryption'; import { LegacyLogger } from '@src/core/logger'; -import { ProvisioningService } from '@src/modules/provisioning'; -import { OauthDataDto } from '@src/modules/provisioning/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemService } from '@src/modules/system'; -import { SystemDto } from '@src/modules/system/service'; -import { UserService } from '@src/modules/user'; -import { MigrationCheckService, UserMigrationService } from '@src/modules/user-login-migration'; +import { ProvisioningService } from '@modules/provisioning'; +import { OauthDataDto } from '@modules/provisioning/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemService } from '@modules/system'; +import { SystemDto } from '@modules/system/service'; +import { UserService } from '@modules/user'; +import { MigrationCheckService, UserMigrationService } from '@modules/user-login-migration'; import jwt, { JwtPayload } from 'jsonwebtoken'; import { OAuthSSOError, SSOErrorCode, UserNotFoundAfterProvisioningLoggableException } from '../loggable'; import { OAuthTokenDto } from '../interface'; diff --git a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts index 4736e1905d1..3d42b0e977f 100644 --- a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts +++ b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts @@ -6,9 +6,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { OauthConfig } from '@shared/domain'; import { axiosResponseFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { HydraRedirectDto } from '@src/modules/oauth/service/dto/hydra.redirect.dto'; -import { HydraSsoService } from '@src/modules/oauth/service/hydra.service'; -import { OAuthService } from '@src/modules/oauth/service/oauth.service'; +import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; +import { HydraSsoService } from '@modules/oauth/service/hydra.service'; +import { OAuthService } from '@modules/oauth/service/oauth.service'; import { AxiosResponse } from 'axios'; import { HydraOauthUc } from '.'; import { AuthorizationParams } from '../controller/dto'; diff --git a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.ts b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.ts index 7889ad31def..905cd3c8802 100644 --- a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.ts +++ b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.ts @@ -2,7 +2,7 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { OauthConfig } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { HydraRedirectDto } from '@src/modules/oauth/service/dto/hydra.redirect.dto'; +import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { AuthorizationParams } from '../controller/dto'; import { OAuthSSOError } from '../loggable/oauth-sso.error'; diff --git a/apps/server/src/modules/oauth/uc/oauth.uc.spec.ts b/apps/server/src/modules/oauth/uc/oauth.uc.spec.ts index 15907d5dde2..4323cd5bc85 100644 --- a/apps/server/src/modules/oauth/uc/oauth.uc.spec.ts +++ b/apps/server/src/modules/oauth/uc/oauth.uc.spec.ts @@ -6,19 +6,19 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-prov import { ISession } from '@shared/domain/types/session'; import { legacySchoolDoFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { AuthenticationService } from '@src/modules/authentication/services/authentication.service'; -import { OauthUc } from '@src/modules/oauth/uc/oauth.uc'; -import { ProvisioningService } from '@src/modules/provisioning'; -import { ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@src/modules/provisioning/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemService } from '@src/modules/system'; -import { OauthConfigDto, SystemDto } from '@src/modules/system/service'; -import { UserService } from '@src/modules/user'; -import { UserMigrationService } from '@src/modules/user-login-migration'; -import { OAuthMigrationError } from '@src/modules/user-login-migration/error/oauth-migration.error'; -import { SchoolMigrationService } from '@src/modules/user-login-migration/service'; -import { MigrationDto } from '@src/modules/user-login-migration/service/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { OauthUc } from '@modules/oauth/uc/oauth.uc'; +import { ProvisioningService } from '@modules/provisioning'; +import { ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@modules/provisioning/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemService } from '@modules/system'; +import { OauthConfigDto, SystemDto } from '@modules/system/service'; +import { UserService } from '@modules/user'; +import { UserMigrationService } from '@modules/user-login-migration'; +import { OAuthMigrationError } from '@modules/user-login-migration/error/oauth-migration.error'; +import { SchoolMigrationService } from '@modules/user-login-migration/service'; +import { MigrationDto } from '@modules/user-login-migration/service/dto'; import { OAuthSSOError } from '../loggable/oauth-sso.error'; import { AuthorizationParams } from '../controller/dto'; import { OAuthTokenDto } from '../interface'; diff --git a/apps/server/src/modules/oauth/uc/oauth.uc.ts b/apps/server/src/modules/oauth/uc/oauth.uc.ts index e4dd7e68264..53d986bf029 100644 --- a/apps/server/src/modules/oauth/uc/oauth.uc.ts +++ b/apps/server/src/modules/oauth/uc/oauth.uc.ts @@ -2,16 +2,16 @@ import { Injectable, UnauthorizedException, UnprocessableEntityException } from import { EntityId, LegacySchoolDo, UserDO } from '@shared/domain'; import { ISession } from '@shared/domain/types/session'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { AuthenticationService } from '@src/modules/authentication/services/authentication.service'; -import { ProvisioningService } from '@src/modules/provisioning'; -import { OauthDataDto } from '@src/modules/provisioning/dto'; -import { SystemService } from '@src/modules/system'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { UserService } from '@src/modules/user'; -import { UserMigrationService } from '@src/modules/user-login-migration'; -import { SchoolMigrationService } from '@src/modules/user-login-migration/service'; -import { MigrationDto } from '@src/modules/user-login-migration/service/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { ProvisioningService } from '@modules/provisioning'; +import { OauthDataDto } from '@modules/provisioning/dto'; +import { SystemService } from '@modules/system'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { UserService } from '@modules/user'; +import { UserMigrationService } from '@modules/user-login-migration'; +import { SchoolMigrationService } from '@modules/user-login-migration/service'; +import { MigrationDto } from '@modules/user-login-migration/service/dto'; import { nanoid } from 'nanoid'; import { AuthorizationParams } from '../controller/dto'; import { OAuthTokenDto } from '../interface'; diff --git a/apps/server/src/modules/provisioning/dto/external-group.dto.ts b/apps/server/src/modules/provisioning/dto/external-group.dto.ts index 57cdc78e44c..e01093fa4ea 100644 --- a/apps/server/src/modules/provisioning/dto/external-group.dto.ts +++ b/apps/server/src/modules/provisioning/dto/external-group.dto.ts @@ -1,4 +1,4 @@ -import { GroupTypes } from '@src/modules/group'; +import { GroupTypes } from '@modules/group'; import { ExternalGroupUserDto } from './external-group-user.dto'; export class ExternalGroupDto { diff --git a/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.spec.ts b/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.spec.ts index fe7d866e2c9..f25054ce1ac 100644 --- a/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.spec.ts +++ b/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.spec.ts @@ -1,5 +1,5 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; import { ProvisioningSystemDto } from '../dto'; import { ProvisioningSystemInputMapper } from './provisioning-system-input.mapper'; diff --git a/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.ts b/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.ts index b0b6a81a5af..9668bbfffea 100644 --- a/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.ts +++ b/apps/server/src/modules/provisioning/mapper/provisioning-system-input.mapper.ts @@ -1,5 +1,5 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; import { ProvisioningSystemDto } from '../dto'; export class ProvisioningSystemInputMapper { diff --git a/apps/server/src/modules/provisioning/provisioning.module.ts b/apps/server/src/modules/provisioning/provisioning.module.ts index 854d1834387..185516e3f38 100644 --- a/apps/server/src/modules/provisioning/provisioning.module.ts +++ b/apps/server/src/modules/provisioning/provisioning.module.ts @@ -1,12 +1,12 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AccountModule } from '@src/modules/account/account.module'; -import { RoleModule } from '@src/modules/role'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { SystemModule } from '@src/modules/system/system.module'; -import { UserModule } from '@src/modules/user'; -import { GroupModule } from '@src/modules/group'; +import { AccountModule } from '@modules/account/account.module'; +import { RoleModule } from '@modules/role'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { SystemModule } from '@modules/system/system.module'; +import { UserModule } from '@modules/user'; +import { GroupModule } from '@modules/group'; import { ProvisioningService } from './service/provisioning.service'; import { IservProvisioningStrategy, OidcMockProvisioningStrategy, SanisProvisioningStrategy } from './strategy'; import { OidcProvisioningService } from './strategy/oidc/service/oidc-provisioning.service'; diff --git a/apps/server/src/modules/provisioning/service/provisioning.service.spec.ts b/apps/server/src/modules/provisioning/service/provisioning.service.spec.ts index fee48a9b457..1d80c6c7b90 100644 --- a/apps/server/src/modules/provisioning/service/provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/service/provisioning.service.spec.ts @@ -2,8 +2,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { SystemService } from '@src/modules/system/service/system.service'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { SystemService } from '@modules/system/service/system.service'; import { ExternalUserDto, OauthDataDto, diff --git a/apps/server/src/modules/provisioning/service/provisioning.service.ts b/apps/server/src/modules/provisioning/service/provisioning.service.ts index 015c0d02fc9..50ee527001c 100644 --- a/apps/server/src/modules/provisioning/service/provisioning.service.ts +++ b/apps/server/src/modules/provisioning/service/provisioning.service.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { SystemService } from '@src/modules/system'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { SystemService } from '@modules/system'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; import { OauthDataDto, OauthDataStrategyInputDto, ProvisioningDto, ProvisioningSystemDto } from '../dto'; import { ProvisioningSystemInputMapper } from '../mapper/provisioning-system-input.mapper'; import { diff --git a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts index fad80012e06..e91ec8ca159 100644 --- a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts @@ -4,10 +4,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, RoleName, User, UserDO } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { legacySchoolDoFactory, schoolFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import jwt from 'jsonwebtoken'; -import { OAuthSSOError } from '@src/modules/oauth/loggable'; +import { OAuthSSOError } from '@modules/oauth/loggable'; import { RoleDto } from '../../../role/service/dto/role.dto'; import { ExternalSchoolDto, diff --git a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts index 4a449b9287f..8d5e1b5378f 100644 --- a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts +++ b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { LegacySchoolDo, RoleName, RoleReference, User, UserDO } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import { OAuthSSOError } from '@src/modules/oauth/loggable'; +import { OAuthSSOError } from '@modules/oauth/loggable'; import { ExternalSchoolDto, ExternalUserDto, diff --git a/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.spec.ts index c6cff205686..f9235522100 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import jwt from 'jsonwebtoken'; -import { OAuthSSOError } from '@src/modules/oauth/loggable'; +import { OAuthSSOError } from '@modules/oauth/loggable'; import { ExternalUserDto, OauthDataDto, diff --git a/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.ts b/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.ts index 5daa69ed7d2..e505386ca97 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc-mock/oidc-mock.strategy.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import { OAuthSSOError } from '@src/modules/oauth/loggable'; +import { OAuthSSOError } from '@modules/oauth/loggable'; import { ExternalUserDto, OauthDataDto, OauthDataStrategyInputDto, ProvisioningDto } from '../../dto'; import { ProvisioningStrategy } from '../base.strategy'; diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts index 7e6e05c7b2e..09e253dddbf 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts @@ -14,13 +14,13 @@ import { roleFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountSaveDto } from '@src/modules/account/services/dto'; -import { Group, GroupService } from '@src/modules/group'; -import { RoleService } from '@src/modules/role'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { FederalStateService, LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountSaveDto } from '@modules/account/services/dto'; +import { Group, GroupService } from '@modules/group'; +import { RoleService } from '@modules/role'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { FederalStateService, LegacySchoolService, SchoolYearService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import CryptoJS from 'crypto-js'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { ExternalGroupDto, ExternalSchoolDto, ExternalUserDto } from '../../../dto'; diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts index 31b2d1ab8f3..66c243e6457 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts @@ -2,14 +2,14 @@ import { Injectable, UnprocessableEntityException } from '@nestjs/common'; import { EntityId, ExternalSource, FederalStateEntity, SchoolFeatures, SchoolYearEntity } from '@shared/domain'; import { LegacySchoolDo, RoleReference, UserDO } from '@shared/domain/domainobject'; import { Logger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountSaveDto } from '@src/modules/account/services/dto'; -import { Group, GroupService, GroupUser } from '@src/modules/group'; -import { FederalStateService, LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school'; -import { FederalStateNames } from '@src/modules/legacy-school/types'; -import { RoleService } from '@src/modules/role'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { UserService } from '@src/modules/user'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountSaveDto } from '@modules/account/services/dto'; +import { Group, GroupService, GroupUser } from '@modules/group'; +import { FederalStateService, LegacySchoolService, SchoolYearService } from '@modules/legacy-school'; +import { FederalStateNames } from '@modules/legacy-school/types'; +import { RoleService } from '@modules/role'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { UserService } from '@modules/user'; import { ObjectId } from 'bson'; import CryptoJS from 'crypto-js'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; diff --git a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts index c52e654155b..2fe68c0163b 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts @@ -2,7 +2,7 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { RoleName } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { GroupTypes } from '@src/modules/group'; +import { GroupTypes } from '@modules/group'; import { UUID } from 'bson'; import { ExternalGroupDto, ExternalSchoolDto, ExternalUserDto } from '../../dto'; import { diff --git a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts index e11e57e9068..23d9b15fbdc 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { RoleName } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { GroupTypes } from '@src/modules/group'; +import { GroupTypes } from '@modules/group'; import { ExternalGroupDto, ExternalGroupUserDto, ExternalSchoolDto, ExternalUserDto } from '../../dto'; import { GroupRoleUnknownLoggable } from '../../loggable'; import { diff --git a/apps/server/src/modules/provisioning/strategy/sanis/sanis.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/sanis/sanis.strategy.spec.ts index b1324cbf74b..f0ea97f89fd 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/sanis.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/sanis.strategy.spec.ts @@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { RoleName } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { axiosResponseFactory, setupEntities } from '@shared/testing'; -import { GroupTypes } from '@src/modules/group'; +import { GroupTypes } from '@modules/group'; import { UUID } from 'bson'; import { of } from 'rxjs'; import { diff --git a/apps/server/src/modules/pseudonym/controller/api-test/pseudonym.api.spec.ts b/apps/server/src/modules/pseudonym/controller/api-test/pseudonym.api.spec.ts index 05bd5d4f2f5..fa72cf82075 100644 --- a/apps/server/src/modules/pseudonym/controller/api-test/pseudonym.api.spec.ts +++ b/apps/server/src/modules/pseudonym/controller/api-test/pseudonym.api.spec.ts @@ -11,8 +11,8 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'supertest'; import { SchoolEntity } from '@shared/domain'; -import { ServerTestModule } from '@src/modules/server'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +import { ServerTestModule } from '@modules/server'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; import { UUID } from 'bson'; import { ExternalToolPseudonymEntity } from '../../entity'; import { PseudonymResponse } from '../dto'; diff --git a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts index 16d55fba9db..f7e378e050a 100644 --- a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts +++ b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts @@ -7,7 +7,7 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { Pseudonym } from '@shared/domain'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { PseudonymMapper } from '../mapper/pseudonym.mapper'; import { PseudonymUc } from '../uc'; import { PseudonymResponse } from './dto'; diff --git a/apps/server/src/modules/pseudonym/pseudonym-api.module.ts b/apps/server/src/modules/pseudonym/pseudonym-api.module.ts index 8ba18f1cb72..2b9fdd3afed 100644 --- a/apps/server/src/modules/pseudonym/pseudonym-api.module.ts +++ b/apps/server/src/modules/pseudonym/pseudonym-api.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { AuthorizationModule } from '@modules/authorization'; +import { LegacySchoolModule } from '@modules/legacy-school'; import { PseudonymModule } from './pseudonym.module'; import { PseudonymController } from './controller/pseudonym.controller'; import { PseudonymUc } from './uc'; diff --git a/apps/server/src/modules/pseudonym/pseudonym.module.ts b/apps/server/src/modules/pseudonym/pseudonym.module.ts index 21da4ef3c59..d282c5dd9fe 100644 --- a/apps/server/src/modules/pseudonym/pseudonym.module.ts +++ b/apps/server/src/modules/pseudonym/pseudonym.module.ts @@ -1,9 +1,9 @@ import { forwardRef, Module } from '@nestjs/common'; import { LegacyLogger } from '@src/core/logger'; -import { LearnroomModule } from '@src/modules/learnroom'; -import { UserModule } from '@src/modules/user'; -import { ToolModule } from '@src/modules/tool'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { LearnroomModule } from '@modules/learnroom'; +import { UserModule } from '@modules/user'; +import { ToolModule } from '@modules/tool'; +import { AuthorizationModule } from '@modules/authorization'; import { ExternalToolPseudonymRepo, PseudonymsRepo } from './repo'; import { FeathersRosterService, PseudonymService } from './service'; diff --git a/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts b/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts index e2a8484d630..59de8663ea6 100644 --- a/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts +++ b/apps/server/src/modules/pseudonym/service/feathers-roster.service.spec.ts @@ -15,15 +15,15 @@ import { UserAndAccountTestFactory, userDoFactory, } from '@shared/testing'; -import { CourseService } from '@src/modules/learnroom/service/course.service'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { ContextExternalTool, ContextRef } from '@src/modules/tool/context-external-tool/domain'; -import { ContextExternalToolService } from '@src/modules/tool/context-external-tool/service'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolService } from '@src/modules/tool/school-external-tool/service'; -import { UserService } from '@src/modules/user'; +import { CourseService } from '@modules/learnroom/service/course.service'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { ContextExternalTool, ContextRef } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolService } from '@modules/tool/context-external-tool/service'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolService } from '@modules/tool/school-external-tool/service'; +import { UserService } from '@modules/user'; import { ObjectId } from 'bson'; import { FeathersRosterService } from './feathers-roster.service'; import { PseudonymService } from './pseudonym.service'; diff --git a/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts b/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts index e808a2fc59f..a6b512d13c2 100644 --- a/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts +++ b/apps/server/src/modules/pseudonym/service/feathers-roster.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { Course, EntityId, Pseudonym, RoleName, RoleReference, UserDO } from '@shared/domain'; -import { CourseService } from '@src/modules/learnroom/service'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { ContextExternalTool, ContextRef } from '@src/modules/tool/context-external-tool/domain'; -import { ContextExternalToolService } from '@src/modules/tool/context-external-tool/service'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolService } from '@src/modules/tool/school-external-tool/service'; -import { UserService } from '@src/modules/user'; +import { CourseService } from '@modules/learnroom/service'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { ContextExternalTool, ContextRef } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolService } from '@modules/tool/context-external-tool/service'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolService } from '@modules/tool/school-external-tool/service'; +import { UserService } from '@modules/user'; import { PseudonymService } from './pseudonym.service'; interface UserMetdata { diff --git a/apps/server/src/modules/pseudonym/service/pseudonym.service.spec.ts b/apps/server/src/modules/pseudonym/service/pseudonym.service.spec.ts index e2fbb6e1b1f..026d28d039f 100644 --- a/apps/server/src/modules/pseudonym/service/pseudonym.service.spec.ts +++ b/apps/server/src/modules/pseudonym/service/pseudonym.service.spec.ts @@ -4,7 +4,7 @@ import { InternalServerErrorException, NotFoundException } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing'; import { IFindOptions, LtiToolDO, Page, Pseudonym, UserDO } from '@shared/domain'; import { externalToolFactory, ltiToolDOFactory, pseudonymFactory, userDoFactory } from '@shared/testing/factory'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; import { PseudonymSearchQuery } from '../domain'; import { ExternalToolPseudonymRepo, PseudonymsRepo } from '../repo'; import { PseudonymService } from './pseudonym.service'; diff --git a/apps/server/src/modules/pseudonym/service/pseudonym.service.ts b/apps/server/src/modules/pseudonym/service/pseudonym.service.ts index 23819f2fd3b..6d15d6a1ec9 100644 --- a/apps/server/src/modules/pseudonym/service/pseudonym.service.ts +++ b/apps/server/src/modules/pseudonym/service/pseudonym.service.ts @@ -2,7 +2,7 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { IFindOptions, LtiToolDO, Page, Pseudonym, UserDO } from '@shared/domain'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; import { v4 as uuidv4 } from 'uuid'; import { PseudonymSearchQuery } from '../domain'; import { ExternalToolPseudonymRepo, PseudonymsRepo } from '../repo'; diff --git a/apps/server/src/modules/pseudonym/uc/pseudonym.uc.spec.ts b/apps/server/src/modules/pseudonym/uc/pseudonym.uc.spec.ts index 87fbfcbb526..26140da8a28 100644 --- a/apps/server/src/modules/pseudonym/uc/pseudonym.uc.spec.ts +++ b/apps/server/src/modules/pseudonym/uc/pseudonym.uc.spec.ts @@ -3,8 +3,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, Pseudonym, SchoolEntity, User } from '@shared/domain'; import { legacySchoolDoFactory, pseudonymFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; import { ForbiddenException } from '@nestjs/common'; -import { Action, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { Action, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { PseudonymService } from '../service'; import { PseudonymUc } from './pseudonym.uc'; diff --git a/apps/server/src/modules/pseudonym/uc/pseudonym.uc.ts b/apps/server/src/modules/pseudonym/uc/pseudonym.uc.ts index a960a33bc3c..4c0ecb19a36 100644 --- a/apps/server/src/modules/pseudonym/uc/pseudonym.uc.ts +++ b/apps/server/src/modules/pseudonym/uc/pseudonym.uc.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { EntityId, LegacySchoolDo, Pseudonym, User } from '@shared/domain'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { PseudonymService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/role/mapper/role.mapper.spec.ts b/apps/server/src/modules/role/mapper/role.mapper.spec.ts index 806d21d01e8..c7152cfd949 100644 --- a/apps/server/src/modules/role/mapper/role.mapper.spec.ts +++ b/apps/server/src/modules/role/mapper/role.mapper.spec.ts @@ -1,7 +1,7 @@ import { Permission, Role } from '@shared/domain'; import { roleFactory, setupEntities } from '@shared/testing'; -import { RoleMapper } from '@src/modules/role/mapper/role.mapper'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleMapper } from '@modules/role/mapper/role.mapper'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; describe('RoleMapper', () => { beforeAll(async () => { diff --git a/apps/server/src/modules/role/mapper/role.mapper.ts b/apps/server/src/modules/role/mapper/role.mapper.ts index a805098544b..7b427bb809d 100644 --- a/apps/server/src/modules/role/mapper/role.mapper.ts +++ b/apps/server/src/modules/role/mapper/role.mapper.ts @@ -1,5 +1,5 @@ import { Role } from '@shared/domain'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; export class RoleMapper { static mapFromEntityToDto(entity: Role): RoleDto { diff --git a/apps/server/src/modules/role/role.module.ts b/apps/server/src/modules/role/role.module.ts index 9189d3ab191..42063b400e3 100644 --- a/apps/server/src/modules/role/role.module.ts +++ b/apps/server/src/modules/role/role.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { RoleRepo } from '@shared/repo'; -import { RoleService } from '@src/modules/role/service/role.service'; -import { RoleUc } from '@src/modules/role/uc/role.uc'; +import { RoleService } from '@modules/role/service/role.service'; +import { RoleUc } from '@modules/role/uc/role.uc'; @Module({ providers: [RoleRepo, RoleService, RoleUc], diff --git a/apps/server/src/modules/role/uc/role.uc.spec.ts b/apps/server/src/modules/role/uc/role.uc.spec.ts index 8be660bcffb..b026687af3f 100644 --- a/apps/server/src/modules/role/uc/role.uc.spec.ts +++ b/apps/server/src/modules/role/uc/role.uc.spec.ts @@ -1,9 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { RoleName } from '@shared/domain'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { RoleService } from '@src/modules/role/service/role.service'; -import { RoleUc } from '@src/modules/role/uc/role.uc'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { RoleService } from '@modules/role/service/role.service'; +import { RoleUc } from '@modules/role/uc/role.uc'; describe('RoleUc', () => { let module: TestingModule; diff --git a/apps/server/src/modules/role/uc/role.uc.ts b/apps/server/src/modules/role/uc/role.uc.ts index faa29b7df9b..a1b5ba0b649 100644 --- a/apps/server/src/modules/role/uc/role.uc.ts +++ b/apps/server/src/modules/role/uc/role.uc.ts @@ -1,5 +1,5 @@ -import { RoleService } from '@src/modules/role/service/role.service'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleService } from '@modules/role/service/role.service'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; import { Injectable } from '@nestjs/common'; import { RoleName } from '@shared/domain'; diff --git a/apps/server/src/modules/server/controller/api-test/server.api.spec.ts b/apps/server/src/modules/server/controller/api-test/server.api.spec.ts index e730fe016d6..a6e42141c6a 100644 --- a/apps/server/src/modules/server/controller/api-test/server.api.spec.ts +++ b/apps/server/src/modules/server/controller/api-test/server.api.spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import request from 'supertest'; describe('Server Controller (API)', () => { diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index 1f3222b0250..09dd2210928 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -1,8 +1,10 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import type { IIdentityManagementConfig } from '@shared/infra/identity-management'; import type { ICoreModuleConfig } from '@src/core'; -import type { IAccountConfig, IFilesStorageClientConfig, IUserConfig } from '@src/modules/'; -import type { ICommonCartridgeConfig } from '@src/modules/learnroom/common-cartridge'; +import type { IAccountConfig } from '@modules/account'; +import type { IFilesStorageClientConfig } from '@modules/files-storage-client'; +import type { IUserConfig } from '@modules/user'; +import type { ICommonCartridgeConfig } from '@modules/learnroom/common-cartridge'; export enum NodeEnvType { TEST = 'test', diff --git a/apps/server/src/modules/server/server.module.ts b/apps/server/src/modules/server/server.module.ts index 20b346929cd..084ca72fca3 100644 --- a/apps/server/src/modules/server/server.module.ts +++ b/apps/server/src/modules/server/server.module.ts @@ -11,32 +11,32 @@ import { REDIS_CLIENT, RedisModule } from '@shared/infra/redis'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LegacyLogger, LoggerModule } from '@src/core/logger'; -import { AccountApiModule } from '@src/modules/account/account-api.module'; -import { AuthenticationApiModule } from '@src/modules/authentication/authentication-api.module'; -import { BoardApiModule } from '@src/modules/board/board-api.module'; -import { CollaborativeStorageModule } from '@src/modules/collaborative-storage'; -import { FilesStorageClientModule } from '@src/modules/files-storage-client'; -import { GroupApiModule } from '@src/modules/group/group-api.module'; -import { LearnroomApiModule } from '@src/modules/learnroom/learnroom-api.module'; -import { LessonApiModule } from '@src/modules/lesson/lesson-api.module'; -import { NewsModule } from '@src/modules/news'; -import { OauthProviderApiModule } from '@src/modules/oauth-provider'; -import { OauthApiModule } from '@src/modules/oauth/oauth-api.module'; -import { RocketChatModule } from '@src/modules/rocketchat'; -import { LegacySchoolApiModule } from '@src/modules/legacy-school/legacy-school-api.module'; -import { SharingApiModule } from '@src/modules/sharing/sharing.module'; -import { SystemApiModule } from '@src/modules/system/system-api.module'; -import { TaskApiModule } from '@src/modules/task/task-api.module'; -import { ToolApiModule } from '@src/modules/tool/tool-api.module'; -import { ImportUserModule } from '@src/modules/user-import'; -import { UserLoginMigrationApiModule } from '@src/modules/user-login-migration/user-login-migration-api.module'; -import { UserApiModule } from '@src/modules/user/user-api.module'; -import { VideoConferenceApiModule } from '@src/modules/video-conference/video-conference-api.module'; +import { AccountApiModule } from '@modules/account/account-api.module'; +import { AuthenticationApiModule } from '@modules/authentication/authentication-api.module'; +import { BoardApiModule } from '@modules/board/board-api.module'; +import { CollaborativeStorageModule } from '@modules/collaborative-storage'; +import { FilesStorageClientModule } from '@modules/files-storage-client'; +import { GroupApiModule } from '@modules/group/group-api.module'; +import { LearnroomApiModule } from '@modules/learnroom/learnroom-api.module'; +import { LessonApiModule } from '@modules/lesson/lesson-api.module'; +import { NewsModule } from '@modules/news'; +import { OauthProviderApiModule } from '@modules/oauth-provider'; +import { OauthApiModule } from '@modules/oauth/oauth-api.module'; +import { RocketChatModule } from '@modules/rocketchat'; +import { LegacySchoolApiModule } from '@modules/legacy-school/legacy-school-api.module'; +import { SharingApiModule } from '@modules/sharing/sharing.module'; +import { SystemApiModule } from '@modules/system/system-api.module'; +import { TaskApiModule } from '@modules/task/task-api.module'; +import { ToolApiModule } from '@modules/tool/tool-api.module'; +import { ImportUserModule } from '@modules/user-import'; +import { UserLoginMigrationApiModule } from '@modules/user-login-migration/user-login-migration-api.module'; +import { UserApiModule } from '@modules/user/user-api.module'; +import { VideoConferenceApiModule } from '@modules/video-conference/video-conference-api.module'; import connectRedis from 'connect-redis'; import session from 'express-session'; import { RedisClient } from 'redis'; -import { TeamsApiModule } from '@src/modules/teams/teams-api.module'; -import { PseudonymApiModule } from '@src/modules/pseudonym/pseudonym-api.module'; +import { TeamsApiModule } from '@modules/teams/teams-api.module'; +import { PseudonymApiModule } from '@modules/pseudonym/pseudonym-api.module'; import { ServerController } from './controller/server.controller'; import { serverConfig } from './server.config'; diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts index 2abd2019fbf..7890f778f86 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts @@ -6,7 +6,7 @@ import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -15,8 +15,8 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ShareTokenBodyParams, ShareTokenResponse } from '../dto'; import { ShareTokenParentType } from '../../domainobject/share-token.do'; diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts index 737a24b022e..f19dea681f2 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts @@ -4,7 +4,7 @@ import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -13,9 +13,9 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { CopyApiResponse, CopyElementType, CopyStatusEnum } from '@src/modules/copy-helper'; -import { ServerTestModule } from '@src/modules/server'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { CopyApiResponse, CopyElementType, CopyStatusEnum } from '@modules/copy-helper'; +import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; import { ShareTokenContext, ShareTokenContextType, ShareTokenParentType } from '../../domainobject/share-token.do'; diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts index 7065c06a026..88f23408be4 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-lookup-token.api.spec.ts @@ -4,7 +4,7 @@ import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { TestApiClient, UserAndAccountTestFactory, courseFactory, schoolFactory } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { ShareTokenService } from '../../service'; import { ShareTokenInfoResponse } from '../dto'; import { ShareTokenContextType, ShareTokenParentType } from '../../domainobject/share-token.do'; diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts b/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts index b694c60731a..7d6f51fa471 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { courseFactory, setupEntities, shareTokenFactory } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; +import { ICurrentUser } from '@modules/authentication'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { ShareTokenParentType } from '../domainobject/share-token.do'; import { ShareTokenUC } from '../uc'; import { ShareTokenInfoDto } from '../uc/dto'; diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.ts b/apps/server/src/modules/sharing/controller/share-token.controller.ts index b4120dc02ae..373e1169600 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.ts @@ -11,10 +11,10 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError, RequestTimeout } from '@shared/common'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; -import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; // invalid import can produce dependency cycles -import { serverConfig } from '@src/modules/server/server.config'; +import { serverConfig } from '@modules/server/server.config'; import { ShareTokenInfoResponseMapper, ShareTokenResponseMapper } from '../mapper'; import { ShareTokenUC } from '../uc'; import { diff --git a/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts index 68c0ccbd972..6f106c3e90a 100644 --- a/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts +++ b/apps/server/src/modules/sharing/mapper/context-type.mapper.spec.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { ShareTokenContextType } from '../domainobject/share-token.do'; import { ShareTokenContextTypeMapper } from './context-type.mapper'; diff --git a/apps/server/src/modules/sharing/mapper/context-type.mapper.ts b/apps/server/src/modules/sharing/mapper/context-type.mapper.ts index 05ed42843c7..fbb6526eac9 100644 --- a/apps/server/src/modules/sharing/mapper/context-type.mapper.ts +++ b/apps/server/src/modules/sharing/mapper/context-type.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { ShareTokenContextType } from '../domainobject/share-token.do'; export class ShareTokenContextTypeMapper { diff --git a/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts b/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts index 4f8750245b4..99a9ea19453 100644 --- a/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts +++ b/apps/server/src/modules/sharing/mapper/parent-type.mapper.spec.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { ShareTokenParentType } from '../domainobject/share-token.do'; import { ShareTokenParentTypeMapper } from './parent-type.mapper'; diff --git a/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts b/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts index 2ea01ea39f9..7f2f0fdea60 100644 --- a/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts +++ b/apps/server/src/modules/sharing/mapper/parent-type.mapper.ts @@ -1,5 +1,5 @@ import { NotImplementedException } from '@nestjs/common'; -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { ShareTokenParentType } from '../domainobject/share-token.do'; export class ShareTokenParentTypeMapper { diff --git a/apps/server/src/modules/sharing/service/share-token.service.spec.ts b/apps/server/src/modules/sharing/service/share-token.service.spec.ts index c3436fb54ce..08680427058 100644 --- a/apps/server/src/modules/sharing/service/share-token.service.spec.ts +++ b/apps/server/src/modules/sharing/service/share-token.service.spec.ts @@ -2,9 +2,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { courseFactory, lessonFactory, setupEntities, shareTokenFactory, taskFactory } from '@shared/testing'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LessonService } from '@src/modules/lesson/service'; -import { TaskService } from '@src/modules/task/service'; +import { CourseService } from '@modules/learnroom/service'; +import { LessonService } from '@modules/lesson/service'; +import { TaskService } from '@modules/task/service'; import { ObjectId } from 'bson'; import { ShareTokenContextType, ShareTokenParentType } from '../domainobject/share-token.do'; import { ShareTokenRepo } from '../repo/share-token.repo'; diff --git a/apps/server/src/modules/sharing/service/share-token.service.ts b/apps/server/src/modules/sharing/service/share-token.service.ts index e6431d64ed5..befdc580b2b 100644 --- a/apps/server/src/modules/sharing/service/share-token.service.ts +++ b/apps/server/src/modules/sharing/service/share-token.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LessonService } from '@src/modules/lesson/service'; -import { TaskService } from '@src/modules/task/service'; +import { CourseService } from '@modules/learnroom/service'; +import { LessonService } from '@modules/lesson/service'; +import { TaskService } from '@modules/task/service'; import { ShareTokenContext, ShareTokenDO, diff --git a/apps/server/src/modules/sharing/sharing.module.ts b/apps/server/src/modules/sharing/sharing.module.ts index 519033065b5..183141e19a7 100644 --- a/apps/server/src/modules/sharing/sharing.module.ts +++ b/apps/server/src/modules/sharing/sharing.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { AuthorizationReferenceModule } from '@modules/authorization/authorization-reference.module'; import { ShareTokenController } from './controller/share-token.controller'; import { ShareTokenUC } from './uc'; import { ShareTokenService, TokenGenerator } from './service'; diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts index 8f33076cff2..72d2a824327 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts @@ -15,13 +15,13 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationService } from '@src/modules/authorization'; -import { AuthorizableReferenceType, AuthorizationReferenceService } from '@src/modules/authorization/domain'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { CourseCopyService } from '@src/modules/learnroom'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LessonCopyService } from '@src/modules/lesson/service'; -import { TaskCopyService } from '@src/modules/task/service'; +import { Action, AuthorizationService } from '@modules/authorization'; +import { AuthorizableReferenceType, AuthorizationReferenceService } from '@modules/authorization/domain'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CourseCopyService } from '@modules/learnroom'; +import { CourseService } from '@modules/learnroom/service'; +import { LessonCopyService } from '@modules/lesson/service'; +import { TaskCopyService } from '@modules/task/service'; import { ShareTokenContextType, ShareTokenParentType, ShareTokenPayload } from '../domainobject/share-token.do'; import { ShareTokenService } from '../service'; import { ShareTokenUC } from './share-token.uc'; diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.ts b/apps/server/src/modules/sharing/uc/share-token.uc.ts index b2bbc635403..c2a12054cb7 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.ts @@ -2,13 +2,13 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { BadRequestException, Injectable, InternalServerErrorException, NotImplementedException } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; -import { CopyStatus } from '@src/modules/copy-helper'; -import { CourseCopyService } from '@src/modules/learnroom'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LessonCopyService } from '@src/modules/lesson/service'; -import { TaskCopyService } from '@src/modules/task/service'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; +import { CopyStatus } from '@modules/copy-helper'; +import { CourseCopyService } from '@modules/learnroom'; +import { CourseService } from '@modules/learnroom/service'; +import { LessonCopyService } from '@modules/lesson/service'; +import { TaskCopyService } from '@modules/task/service'; import { ShareTokenContext, ShareTokenContextType, diff --git a/apps/server/src/modules/system/controller/api-test/system.api.spec.ts b/apps/server/src/modules/system/controller/api-test/system.api.spec.ts index 823b0d82abf..d067abf1889 100644 --- a/apps/server/src/modules/system/controller/api-test/system.api.spec.ts +++ b/apps/server/src/modules/system/controller/api-test/system.api.spec.ts @@ -3,9 +3,9 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { OauthConfig, SystemEntity } from '@shared/domain'; import { cleanupCollections, systemFactory } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request, { Response } from 'supertest'; import { PublicSystemListResponse } from '../dto/public-system-list.response'; diff --git a/apps/server/src/modules/system/controller/dto/public-system-response.ts b/apps/server/src/modules/system/controller/dto/public-system-response.ts index 2d37338273c..b00055a1849 100644 --- a/apps/server/src/modules/system/controller/dto/public-system-response.ts +++ b/apps/server/src/modules/system/controller/dto/public-system-response.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { OauthConfigResponse } from '@src/modules/system/controller/dto/oauth-config.response'; +import { OauthConfigResponse } from '@modules/system/controller/dto/oauth-config.response'; export class PublicSystemResponse { @ApiProperty({ diff --git a/apps/server/src/modules/system/controller/mapper/system-response.mapper.ts b/apps/server/src/modules/system/controller/mapper/system-response.mapper.ts index f996a59af99..20a215a3dbe 100644 --- a/apps/server/src/modules/system/controller/mapper/system-response.mapper.ts +++ b/apps/server/src/modules/system/controller/mapper/system-response.mapper.ts @@ -1,6 +1,6 @@ -import { OauthConfigResponse } from '@src/modules/system/controller/dto/oauth-config.response'; -import { OauthConfigDto } from '@src/modules/system/service/dto/oauth-config.dto'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { OauthConfigResponse } from '@modules/system/controller/dto/oauth-config.response'; +import { OauthConfigDto } from '@modules/system/service/dto/oauth-config.dto'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; import { PublicSystemListResponse } from '../dto/public-system-list.response'; import { PublicSystemResponse } from '../dto/public-system-response'; diff --git a/apps/server/src/modules/system/controller/system.controller.ts b/apps/server/src/modules/system/controller/system.controller.ts index 95881cad907..bbeca71b83c 100644 --- a/apps/server/src/modules/system/controller/system.controller.ts +++ b/apps/server/src/modules/system/controller/system.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { SystemFilterParams } from '@src/modules/system/controller/dto/system.filter.params'; +import { SystemFilterParams } from '@modules/system/controller/dto/system.filter.params'; import { SystemDto } from '../service'; import { SystemUc } from '../uc/system.uc'; import { PublicSystemListResponse } from './dto/public-system-list.response'; diff --git a/apps/server/src/modules/system/mapper/system-oidc.mapper.spec.ts b/apps/server/src/modules/system/mapper/system-oidc.mapper.spec.ts index 5cc88f020ad..2486d4a1872 100644 --- a/apps/server/src/modules/system/mapper/system-oidc.mapper.spec.ts +++ b/apps/server/src/modules/system/mapper/system-oidc.mapper.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SystemEntity } from '@shared/domain'; import { systemFactory } from '@shared/testing'; -import { SystemOidcMapper } from '@src/modules/system/mapper/system-oidc.mapper'; +import { SystemOidcMapper } from '@modules/system/mapper/system-oidc.mapper'; describe('SystemOidcMapper', () => { let module: TestingModule; diff --git a/apps/server/src/modules/system/mapper/system-oidc.mapper.ts b/apps/server/src/modules/system/mapper/system-oidc.mapper.ts index f62a2e022c9..8726ce09a78 100644 --- a/apps/server/src/modules/system/mapper/system-oidc.mapper.ts +++ b/apps/server/src/modules/system/mapper/system-oidc.mapper.ts @@ -1,5 +1,5 @@ import { OidcConfig, SystemEntity } from '@shared/domain'; -import { OidcConfigDto } from '@src/modules/system/service/dto/oidc-config.dto'; +import { OidcConfigDto } from '@modules/system/service/dto/oidc-config.dto'; export class SystemOidcMapper { static mapFromEntityToDto(entity: SystemEntity): OidcConfigDto | undefined { diff --git a/apps/server/src/modules/system/mapper/system.mapper.spec.ts b/apps/server/src/modules/system/mapper/system.mapper.spec.ts index 21e9cf5f9b3..54c20cc0cff 100644 --- a/apps/server/src/modules/system/mapper/system.mapper.spec.ts +++ b/apps/server/src/modules/system/mapper/system.mapper.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SystemEntity } from '@shared/domain'; import { systemFactory } from '@shared/testing'; -import { SystemMapper } from '@src/modules/system/mapper/system.mapper'; +import { SystemMapper } from '@modules/system/mapper/system.mapper'; describe('SystemMapper', () => { let module: TestingModule; diff --git a/apps/server/src/modules/system/mapper/system.mapper.ts b/apps/server/src/modules/system/mapper/system.mapper.ts index b464e54f263..ae29fea67c8 100644 --- a/apps/server/src/modules/system/mapper/system.mapper.ts +++ b/apps/server/src/modules/system/mapper/system.mapper.ts @@ -1,6 +1,6 @@ import { OauthConfig, SystemEntity } from '@shared/domain'; -import { OauthConfigDto } from '@src/modules/system/service/dto/oauth-config.dto'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { OauthConfigDto } from '@modules/system/service/dto/oauth-config.dto'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; export class SystemMapper { static mapFromEntityToDto(entity: SystemEntity): SystemDto { diff --git a/apps/server/src/modules/system/service/dto/system.dto.ts b/apps/server/src/modules/system/service/dto/system.dto.ts index dbb5b7f7315..1ea7e4a84ee 100644 --- a/apps/server/src/modules/system/service/dto/system.dto.ts +++ b/apps/server/src/modules/system/service/dto/system.dto.ts @@ -1,6 +1,6 @@ import { EntityId } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { OauthConfigDto } from '@src/modules/system/service/dto/oauth-config.dto'; +import { OauthConfigDto } from '@modules/system/service/dto/oauth-config.dto'; export class SystemDto { id?: EntityId; diff --git a/apps/server/src/modules/system/service/system-oidc.service.ts b/apps/server/src/modules/system/service/system-oidc.service.ts index a4987ff47d8..c1f1cf0a4c1 100644 --- a/apps/server/src/modules/system/service/system-oidc.service.ts +++ b/apps/server/src/modules/system/service/system-oidc.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { EntityNotFoundError } from '@shared/common'; import { EntityId, SystemEntity, SystemTypeEnum } from '@shared/domain'; import { SystemRepo } from '@shared/repo'; -import { SystemOidcMapper } from '@src/modules/system/mapper/system-oidc.mapper'; +import { SystemOidcMapper } from '@modules/system/mapper/system-oidc.mapper'; import { OidcConfigDto } from './dto'; @Injectable() diff --git a/apps/server/src/modules/system/service/system.service.ts b/apps/server/src/modules/system/service/system.service.ts index ec15c7d8bb3..960c15f7945 100644 --- a/apps/server/src/modules/system/service/system.service.ts +++ b/apps/server/src/modules/system/service/system.service.ts @@ -3,8 +3,8 @@ import { EntityNotFoundError } from '@shared/common'; import { EntityId, SystemEntity, SystemTypeEnum } from '@shared/domain'; import { IdentityManagementOauthService } from '@shared/infra/identity-management/identity-management-oauth.service'; import { SystemRepo } from '@shared/repo'; -import { SystemMapper } from '@src/modules/system/mapper/system.mapper'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; +import { SystemMapper } from '@modules/system/mapper/system.mapper'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; @Injectable() export class SystemService { diff --git a/apps/server/src/modules/system/system-api.module.ts b/apps/server/src/modules/system/system-api.module.ts index 27c14327e1f..e7213c1fac7 100644 --- a/apps/server/src/modules/system/system-api.module.ts +++ b/apps/server/src/modules/system/system-api.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { SystemController } from '@src/modules/system/controller/system.controller'; -import { SystemUc } from '@src/modules/system/uc/system.uc'; +import { SystemController } from '@modules/system/controller/system.controller'; +import { SystemUc } from '@modules/system/uc/system.uc'; import { SystemModule } from './system.module'; @Module({ diff --git a/apps/server/src/modules/system/system.module.ts b/apps/server/src/modules/system/system.module.ts index f72c73855a0..64caef0df61 100644 --- a/apps/server/src/modules/system/system.module.ts +++ b/apps/server/src/modules/system/system.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { IdentityManagementModule } from '@shared/infra/identity-management/identity-management.module'; import { SystemRepo } from '@shared/repo'; -import { SystemService } from '@src/modules/system/service/system.service'; +import { SystemService } from '@modules/system/service/system.service'; import { SystemOidcService } from './service/system-oidc.service'; @Module({ diff --git a/apps/server/src/modules/system/uc/system.uc.spec.ts b/apps/server/src/modules/system/uc/system.uc.spec.ts index 21fc452c134..45bd65694d9 100644 --- a/apps/server/src/modules/system/uc/system.uc.spec.ts +++ b/apps/server/src/modules/system/uc/system.uc.spec.ts @@ -3,10 +3,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; import { EntityId, SystemEntity, SystemTypeEnum } from '@shared/domain'; import { systemFactory } from '@shared/testing'; -import { SystemMapper } from '@src/modules/system/mapper/system.mapper'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { SystemService } from '@src/modules/system/service/system.service'; -import { SystemUc } from '@src/modules/system/uc/system.uc'; +import { SystemMapper } from '@modules/system/mapper/system.mapper'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { SystemService } from '@modules/system/service/system.service'; +import { SystemUc } from '@modules/system/uc/system.uc'; describe('SystemUc', () => { let module: TestingModule; diff --git a/apps/server/src/modules/system/uc/system.uc.ts b/apps/server/src/modules/system/uc/system.uc.ts index 89953cd29dc..00665191da7 100644 --- a/apps/server/src/modules/system/uc/system.uc.ts +++ b/apps/server/src/modules/system/uc/system.uc.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { EntityNotFoundError } from '@shared/common'; import { EntityId, SystemEntity, SystemType, SystemTypeEnum } from '@shared/domain'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { SystemService } from '@src/modules/system/service/system.service'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { SystemService } from '@modules/system/service/system.service'; @Injectable() export class SystemUc { diff --git a/apps/server/src/modules/task/controller/api-test/submission.api.spec.ts b/apps/server/src/modules/task/controller/api-test/submission.api.spec.ts index f01b3f5baf0..6aea7ac2521 100644 --- a/apps/server/src/modules/task/controller/api-test/submission.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/submission.api.spec.ts @@ -4,7 +4,7 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission, Submission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseGroupFactory, @@ -14,10 +14,10 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; -import { SubmissionStatusListResponse } from '@src/modules/task/controller/dto/submission.response'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; +import { SubmissionStatusListResponse } from '@modules/task/controller/dto/submission.response'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts index f8e431b4d09..446a0fab032 100644 --- a/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts @@ -3,7 +3,7 @@ import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; import { EntityManager } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -12,14 +12,14 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { Request } from 'express'; import request from 'supertest'; Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); Configuration.set('INCOMING_REQUEST_TIMEOUT_COPY_API', 1); // eslint-disable-next-line import/first -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ServerTestModule } from '@modules/server/server.module'; // This needs to be in a separate test file because of the above configuration. // When we find a way to mock the config, it should be moved alongside the other API tests. diff --git a/apps/server/src/modules/task/controller/api-test/task-delete.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-delete.api.spec.ts index a85474c7862..efdc78d53b2 100644 --- a/apps/server/src/modules/task/controller/api-test/task-delete.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-delete.api.spec.ts @@ -10,8 +10,8 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { ServerTestModule } from '@src/modules/server'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { ServerTestModule } from '@modules/server'; const createStudent = () => { const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({}, [ diff --git a/apps/server/src/modules/task/controller/api-test/task-finish.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-finish.api.spec.ts index 43597913adb..acf66c36a1d 100644 --- a/apps/server/src/modules/task/controller/api-test/task-finish.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-finish.api.spec.ts @@ -9,7 +9,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; const createStudent = () => { const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({}, [ diff --git a/apps/server/src/modules/task/controller/api-test/task-finished.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-finished.api.spec.ts index 9d48217bcde..93c79e4015d 100644 --- a/apps/server/src/modules/task/controller/api-test/task-finished.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-finished.api.spec.ts @@ -2,7 +2,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -12,9 +12,9 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; -import { TaskListResponse } from '@src/modules/task/controller/dto'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; +import { TaskListResponse } from '@modules/task/controller/dto'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/task/controller/api-test/task-restore.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-restore.api.spec.ts index 221d53f0f75..9d8f50e0950 100644 --- a/apps/server/src/modules/task/controller/api-test/task-restore.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-restore.api.spec.ts @@ -9,7 +9,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; const createStudent = () => { const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({}, [ diff --git a/apps/server/src/modules/task/controller/api-test/task-revert-published.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-revert-published.api.spec.ts index b26555c7c7c..d4e6d4dee64 100644 --- a/apps/server/src/modules/task/controller/api-test/task-revert-published.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-revert-published.api.spec.ts @@ -9,7 +9,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; const createStudent = () => { const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({}, [ diff --git a/apps/server/src/modules/task/controller/api-test/task.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task.api.spec.ts index 369216b0d50..b7afee9c257 100644 --- a/apps/server/src/modules/task/controller/api-test/task.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task.api.spec.ts @@ -10,8 +10,8 @@ import { submissionFactory, taskFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server/server.module'; -import { TaskListResponse } from '@src/modules/task/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; +import { TaskListResponse } from '@modules/task/controller/dto'; const tomorrow = new Date(Date.now() + 86400000); diff --git a/apps/server/src/modules/task/controller/submission.controller.ts b/apps/server/src/modules/task/controller/submission.controller.ts index 66d65261ded..64d3a23296f 100644 --- a/apps/server/src/modules/task/controller/submission.controller.ts +++ b/apps/server/src/modules/task/controller/submission.controller.ts @@ -1,6 +1,6 @@ import { Controller, Delete, Get, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { SubmissionMapper } from '../mapper'; import { SubmissionUc } from '../uc'; import { SubmissionStatusListResponse, SubmissionUrlParams, TaskUrlParams } from './dto'; diff --git a/apps/server/src/modules/task/controller/task.controller.spec.ts b/apps/server/src/modules/task/controller/task.controller.spec.ts index 652f4797fa8..e44deee06c3 100644 --- a/apps/server/src/modules/task/controller/task.controller.spec.ts +++ b/apps/server/src/modules/task/controller/task.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { CopyApiResponse } from '@src/modules/copy-helper/dto/copy.response'; +import { ICurrentUser } from '@modules/authentication'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyApiResponse } from '@modules/copy-helper/dto/copy.response'; import { TaskCopyUC, TaskUC } from '../uc'; import { TaskController } from './task.controller'; diff --git a/apps/server/src/modules/task/controller/task.controller.ts b/apps/server/src/modules/task/controller/task.controller.ts index c438526ede1..44911973ffd 100644 --- a/apps/server/src/modules/task/controller/task.controller.ts +++ b/apps/server/src/modules/task/controller/task.controller.ts @@ -2,10 +2,10 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestj import { ApiTags } from '@nestjs/swagger'; import { RequestTimeout } from '@shared/common'; import { PaginationParams } from '@shared/controller/'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; -import { CopyApiResponse, CopyMapper } from '@src/modules/copy-helper'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; // invalid import can produce dependency cycles -import { serverConfig } from '@src/modules/server/server.config'; +import { serverConfig } from '@modules/server/server.config'; import { TaskMapper } from '../mapper'; import { TaskCopyUC } from '../uc/task-copy.uc'; import { TaskUC } from '../uc/task.uc'; diff --git a/apps/server/src/modules/task/service/submission.service.spec.ts b/apps/server/src/modules/task/service/submission.service.spec.ts index 97267be74df..4d7373570cf 100644 --- a/apps/server/src/modules/task/service/submission.service.spec.ts +++ b/apps/server/src/modules/task/service/submission.service.spec.ts @@ -4,7 +4,7 @@ import { Counted, Submission } from '@shared/domain'; import { FileRecordParentType } from '@shared/infra/rabbitmq'; import { SubmissionRepo } from '@shared/repo'; import { setupEntities, submissionFactory, taskFactory } from '@shared/testing'; -import { FileDto, FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FileDto, FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { SubmissionService } from './submission.service'; describe('Submission Service', () => { diff --git a/apps/server/src/modules/task/service/submission.service.ts b/apps/server/src/modules/task/service/submission.service.ts index 378bd29c67e..c451dc8ad95 100644 --- a/apps/server/src/modules/task/service/submission.service.ts +++ b/apps/server/src/modules/task/service/submission.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Counted, EntityId, Submission } from '@shared/domain'; import { SubmissionRepo } from '@shared/repo'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; @Injectable() export class SubmissionService { diff --git a/apps/server/src/modules/task/service/task-copy.service.spec.ts b/apps/server/src/modules/task/service/task-copy.service.spec.ts index a54ac5ca002..ca320ac60d6 100644 --- a/apps/server/src/modules/task/service/task-copy.service.spec.ts +++ b/apps/server/src/modules/task/service/task-copy.service.spec.ts @@ -11,8 +11,8 @@ import { userFactory, legacyFileEntityMockFactory, } from '@shared/testing'; -import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; -import { CopyFilesService } from '@src/modules/files-storage-client'; +import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService } from '@modules/files-storage-client'; import { TaskCopyService } from './task-copy.service'; describe('task copy service', () => { diff --git a/apps/server/src/modules/task/service/task-copy.service.ts b/apps/server/src/modules/task/service/task-copy.service.ts index 024ed123f4b..f7faa684e8d 100644 --- a/apps/server/src/modules/task/service/task-copy.service.ts +++ b/apps/server/src/modules/task/service/task-copy.service.ts @@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { Course, LessonEntity, Task, User } from '@shared/domain/entity'; import { TaskRepo } from '@shared/repo'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@src/modules/copy-helper'; -import { CopyFilesService } from '@src/modules/files-storage-client'; -import { FileUrlReplacement } from '@src/modules/files-storage-client/service/copy-files.service'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService } from '@modules/files-storage-client'; +import { FileUrlReplacement } from '@modules/files-storage-client/service/copy-files.service'; type TaskCopyParams = { originalTaskId: EntityId; diff --git a/apps/server/src/modules/task/service/task.service.spec.ts b/apps/server/src/modules/task/service/task.service.spec.ts index 3183337a540..4cf950964bd 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { TaskRepo } from '@shared/repo'; import { setupEntities, submissionFactory, taskFactory } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { SubmissionService } from './submission.service'; import { TaskService } from './task.service'; diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index c7e433ae73b..78f9dca9a51 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Counted, EntityId, IFindOptions, Task } from '@shared/domain'; import { TaskRepo } from '@shared/repo'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { SubmissionService } from './submission.service'; @Injectable() diff --git a/apps/server/src/modules/task/task-api.module.ts b/apps/server/src/modules/task/task-api.module.ts index 442b5d2d111..cdb998eab4a 100644 --- a/apps/server/src/modules/task/task-api.module.ts +++ b/apps/server/src/modules/task/task-api.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { CopyHelperModule } from '@src/modules/copy-helper/copy-helper.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { CopyHelperModule } from '@modules/copy-helper/copy-helper.module'; import { SubmissionController, TaskController } from './controller'; import { TaskModule } from './task.module'; import { SubmissionUc, TaskCopyUC, TaskUC } from './uc'; diff --git a/apps/server/src/modules/task/task.module.ts b/apps/server/src/modules/task/task.module.ts index 0d95bcc19b0..696d608d0a3 100644 --- a/apps/server/src/modules/task/task.module.ts +++ b/apps/server/src/modules/task/task.module.ts @@ -1,8 +1,8 @@ import { forwardRef, Module } from '@nestjs/common'; import { CourseRepo, LessonRepo, SubmissionRepo, TaskRepo } from '@shared/repo'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { CopyHelperModule } from '@src/modules/copy-helper'; -import { FilesStorageClientModule } from '@src/modules/files-storage-client'; +import { AuthorizationModule } from '@modules/authorization'; +import { CopyHelperModule } from '@modules/copy-helper'; +import { FilesStorageClientModule } from '@modules/files-storage-client'; import { SubmissionService, TaskCopyService, TaskService } from './service'; @Module({ diff --git a/apps/server/src/modules/task/uc/submission.uc.spec.ts b/apps/server/src/modules/task/uc/submission.uc.spec.ts index 5747e0c6cc8..63bac0f52f1 100644 --- a/apps/server/src/modules/task/uc/submission.uc.spec.ts +++ b/apps/server/src/modules/task/uc/submission.uc.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { Counted, Permission, Submission } from '@shared/domain'; import { setupEntities, submissionFactory, taskFactory, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { SubmissionService } from '../service/submission.service'; import { SubmissionUc } from './submission.uc'; diff --git a/apps/server/src/modules/task/uc/submission.uc.ts b/apps/server/src/modules/task/uc/submission.uc.ts index 2edc26a97a3..50c2430e8de 100644 --- a/apps/server/src/modules/task/uc/submission.uc.ts +++ b/apps/server/src/modules/task/uc/submission.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission, Submission, User } from '@shared/domain'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { SubmissionService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts index 2ad21c68dc2..dc381cda22e 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts @@ -5,9 +5,9 @@ import { ForbiddenException, InternalServerErrorException, NotFoundException } f import { Test, TestingModule } from '@nestjs/testing'; import { CourseRepo, LessonRepo, TaskRepo, UserRepo } from '@shared/repo'; import { courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; -import { Action, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@src/modules/copy-helper'; -import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; +import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { TaskCopyService } from '../service'; import { TaskCopyUC } from './task-copy.uc'; import { TaskCopyParentParams } from '../types'; diff --git a/apps/server/src/modules/task/uc/task-copy.uc.ts b/apps/server/src/modules/task/uc/task-copy.uc.ts index 94b0a5a3acb..69fd99e224f 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.ts @@ -2,8 +2,8 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { ForbiddenException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { Course, EntityId, Task, LessonEntity, User } from '@shared/domain'; import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { CopyHelperService, CopyStatus } from '@src/modules/copy-helper'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyHelperService, CopyStatus } from '@modules/copy-helper'; import { TaskCopyService } from '../service'; import { TaskCopyParentParams } from '../types'; diff --git a/apps/server/src/modules/task/uc/task.uc.spec.ts b/apps/server/src/modules/task/uc/task.uc.spec.ts index e190db57d74..90bb29db444 100644 --- a/apps/server/src/modules/task/uc/task.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task.uc.spec.ts @@ -13,7 +13,7 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationService } from '@modules/authorization'; import { TaskService } from '../service'; import { TaskUC } from './task.uc'; diff --git a/apps/server/src/modules/task/uc/task.uc.ts b/apps/server/src/modules/task/uc/task.uc.ts index 47186c304fc..a6e40dd3b6d 100644 --- a/apps/server/src/modules/task/uc/task.uc.ts +++ b/apps/server/src/modules/task/uc/task.uc.ts @@ -12,7 +12,7 @@ import { User, } from '@shared/domain'; import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; -import { Action, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { TaskService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/teams/teams-api.module.ts b/apps/server/src/modules/teams/teams-api.module.ts index 5fc620fe76a..e5df732a951 100644 --- a/apps/server/src/modules/teams/teams-api.module.ts +++ b/apps/server/src/modules/teams/teams-api.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { TeamsModule } from '@src/modules/teams/teams.module'; +import { TeamsModule } from '@modules/teams/teams.module'; @Module({ imports: [TeamsModule], diff --git a/apps/server/src/modules/tool/common/common-tool.module.ts b/apps/server/src/modules/tool/common/common-tool.module.ts index 0bd9ba5385b..f56f595a99c 100644 --- a/apps/server/src/modules/tool/common/common-tool.module.ts +++ b/apps/server/src/modules/tool/common/common-tool.module.ts @@ -1,9 +1,9 @@ import { forwardRef, Module } from '@nestjs/common'; import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { LearnroomModule } from '@src/modules/learnroom'; +import { AuthorizationModule } from '@modules/authorization'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { LearnroomModule } from '@modules/learnroom'; import { CommonToolService, CommonToolValidationService } from './service'; import { ToolPermissionHelper } from './uc/tool-permission-helper'; diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts index 78d79f45a1b..b05f50fc46c 100644 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts +++ b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts @@ -1,4 +1,4 @@ -import { AuthorizableReferenceType } from '@src/modules/authorization/domain'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { ToolContextType } from '../enum'; import { ContextTypeMapper } from './context-type.mapper'; diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts index 883400f4258..3ae6902c232 100644 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts @@ -1,4 +1,4 @@ -import { AuthorizableReferenceType } from '@src/modules/authorization/domain/'; +import { AuthorizableReferenceType } from '@modules/authorization/domain/'; import { ToolContextType } from '../enum'; const typeMapping: Record = { diff --git a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts index 7f0cb68ade8..542903bc4be 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts @@ -1,8 +1,8 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; -import { AuthorizationContext, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { CourseService } from '@src/modules/learnroom'; +import { AuthorizationContext, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { CourseService } from '@modules/learnroom'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; // import { ContextTypeMapper } from '../mapper'; diff --git a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts index 0f2a1192d57..ad697a94694 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts @@ -9,10 +9,10 @@ import { userFactory, } from '@shared/testing'; import { Permission, LegacySchoolDo } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { ForbiddenException } from '@nestjs/common'; -import { CourseService } from '@src/modules/learnroom'; +import { CourseService } from '@modules/learnroom'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ToolPermissionHelper } from './tool-permission-helper'; import { SchoolExternalTool } from '../../school-external-tool/domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts index b5584426763..e2dbe6ec9ac 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts @@ -15,7 +15,7 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { ObjectId } from 'bson'; import { CustomParameterScope, ToolContextType } from '../../../common/enum'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts index f3072a95131..9eae3c7298e 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts @@ -12,7 +12,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { Response } from 'supertest'; import { ToolContextType } from '../../../common/enum'; import { ExternalToolEntity } from '../../../external-tool/entity'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts index e42b9b59932..a80ad3f234f 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/swagger'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolRequestMapper, ContextExternalToolResponseMapper } from '../mapper'; import { ContextExternalToolUc } from '../uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts index 0b9658b1182..3c00d30e6e2 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Param } from '@nestjs/common'; import { ApiForbiddenResponse, ApiOkResponse, ApiOperation, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ToolReference } from '../domain'; import { ContextExternalToolResponseMapper } from '../mapper'; import { ToolReferenceUc } from '../uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service.ts index 9f0606c6e14..20dbc16fff8 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-authorizable.service.ts @@ -1,4 +1,4 @@ -import { AuthorizationLoaderService } from '@src/modules/authorization'; +import { AuthorizationLoaderService } from '@modules/authorization'; import { EntityId } from '@shared/domain'; import { ContextExternalToolRepo } from '@shared/repo'; import { Injectable } from '@nestjs/common'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts index 819e2895896..60275c71d0d 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts @@ -7,7 +7,7 @@ import { legacySchoolDoFactory, schoolExternalToolFactory, } from '@shared/testing/factory/domainobject'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ContextExternalTool, ContextRef } from '../domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts index 8be134b80f2..648ccb491fa 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts @@ -10,7 +10,7 @@ import { AuthorizationContextBuilder, AuthorizationService, ForbiddenLoggableException, -} from '@src/modules/authorization'; +} from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts index 9b6e3e3fe66..d29cc1454d4 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission, User } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool, ContextRef } from '../domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts index e0fbdcf6a6a..9b18e7ffd3b 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts @@ -3,7 +3,7 @@ import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; import { contextExternalToolFactory, externalToolFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContextBuilder } from '@modules/authorization'; import { ToolConfigurationStatus, ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ExternalTool } from '../../external-tool/domain'; diff --git a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts index c044e01dfeb..ac6ccda016f 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool, ContextRef, ToolReference } from '../domain'; diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index d0030bf7b46..68543982174 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -15,8 +15,8 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; -import { CustomParameterTypeParams } from '@src/modules/tool/common/enum'; +import { ServerTestModule } from '@modules/server'; +import { CustomParameterTypeParams } from '@modules/tool/common/enum'; import { Response } from 'supertest'; import { CustomParameterLocationParams, CustomParameterScopeTypeParams } from '../../../common/enum'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; 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 0d0c3fb08dc..ebff659529d 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 @@ -10,7 +10,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { Response } from 'supertest'; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index a82d7353b1e..589a1e38e7c 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -7,7 +7,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; import { ContextExternalToolTemplateInfo, ExternalToolConfigurationUc } from '../uc'; 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 1400da6490a..80139a586cb 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 @@ -14,7 +14,7 @@ import { ValidationError } from '@shared/common'; import { PaginationParams } from '@shared/controller'; import { IFindOptions, Page } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool } from '../domain'; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index 0ed3a3317f8..5327c8d4f80 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -10,7 +10,7 @@ import { schoolExternalToolFactory, setupEntities, } from '@shared/testing'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContextBuilder } from '@modules/authorization'; import { CustomParameterScope, ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../../context-external-tool/domain'; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 1e91214ccda..f0c1af811fb 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -2,7 +2,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; import { EntityId, Permission } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; -import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/authorization'; import { CustomParameterScope, ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../../context-external-tool/domain'; 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 f2baadd885c..2d371f47c9e 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 @@ -8,8 +8,8 @@ import { externalToolFactory, oauth2ToolConfigFactory, } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; -import { ICurrentUser } from '@src/modules/authentication'; -import { AuthorizationService } from '@src/modules/authorization'; +import { ICurrentUser } from '@modules/authentication'; +import { AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool, Oauth2ToolConfig } from '../domain'; import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../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 3fb81e4f74a..2cf49867103 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 @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, IFindOptions, Page, Permission, User } from '@shared/domain'; -import { AuthorizationService } from '@src/modules/authorization'; +import { AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool, ExternalToolConfig } from '../domain'; import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; 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 c2504b42de2..3512e66038e 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 @@ -11,7 +11,7 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; import { ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; 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 0977cdf478f..79d6f789443 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 @@ -13,7 +13,7 @@ import { import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpStatus } from '@nestjs/common'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from '../mapper'; import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; import { 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 85f26f83679..377a5d24a38 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 @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, Permission, User } from '@shared/domain'; import { schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../domain'; 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 2640def12d5..d7adf3f4937 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 @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +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'; diff --git a/apps/server/src/modules/tool/tool-api.module.ts b/apps/server/src/modules/tool/tool-api.module.ts index dea3405801d..8513eaed3fc 100644 --- a/apps/server/src/modules/tool/tool-api.module.ts +++ b/apps/server/src/modules/tool/tool-api.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { LtiToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { UserModule } from '@src/modules/user'; +import { AuthorizationModule } from '@modules/authorization'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { UserModule } from '@modules/user'; import { CommonToolModule } from './common'; import { ToolContextController } from './context-external-tool/controller'; import { ToolReferenceController } from './context-external-tool/controller/tool-reference.controller'; diff --git a/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts b/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts index 33512062711..04defeeb0dc 100644 --- a/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts @@ -13,7 +13,7 @@ import { TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import { Response } from 'supertest'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; import { LaunchRequestMethod } from '../../types'; diff --git a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts index 73ce027b654..12424b294ed 100644 --- a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts +++ b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts @@ -7,7 +7,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ToolLaunchUc } from '../uc'; import { ToolLaunchParams, ToolLaunchRequestResponse } from './dto'; import { ToolLaunchMapper } from '../mapper'; diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts index 12f8716b5b3..7ee237b3e93 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.spec.ts @@ -12,8 +12,8 @@ import { schoolExternalToolFactory, setupEntities, } from '@shared/testing'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; import { CustomParameterEntry } from '../../../common/domain'; import { CustomParameterLocation, diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts index 644105a3c2b..63ba0680734 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/abstract-launch.strategy.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Course, EntityId, LegacySchoolDo } from '@shared/domain'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; import { URLSearchParams } from 'url'; import { CustomParameter, CustomParameterEntry } from '../../../common/domain'; import { diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/basic-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/basic-tool-launch.strategy.spec.ts index a18a760b0b3..3bb95b97755 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/basic-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/basic-tool-launch.strategy.spec.ts @@ -1,8 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; import { ContextExternalTool } from '../../../context-external-tool/domain'; import { ExternalTool } from '../../../external-tool/domain'; import { SchoolExternalTool } from '../../../school-external-tool/domain'; diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts index 1c31aac36b6..5113ff3cc76 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.spec.ts @@ -9,10 +9,10 @@ import { userDoFactory, } from '@shared/testing'; import { pseudonymFactory } from '@shared/testing/factory/domainobject/pseudonym.factory'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { PseudonymService } from '@src/modules/pseudonym/service'; -import { UserService } from '@src/modules/user'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { PseudonymService } from '@modules/pseudonym/service'; +import { UserService } from '@modules/user'; import { ObjectId } from 'bson'; import { Authorization } from 'oauth-1.0a'; import { LtiMessageType, LtiPrivacyPermission, LtiRole, ToolContextType } from '../../../common/enum'; diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts index 654e14b45d9..09d04e388f3 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts @@ -1,10 +1,10 @@ import { Injectable, InternalServerErrorException, UnprocessableEntityException } from '@nestjs/common'; import { EntityId, LtiPrivacyPermission, Pseudonym, RoleName, UserDO } from '@shared/domain'; import { RoleReference } from '@shared/domain/domainobject'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { PseudonymService } from '@src/modules/pseudonym/service'; -import { UserService } from '@src/modules/user'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { PseudonymService } from '@modules/pseudonym/service'; +import { UserService } from '@modules/user'; import { Authorization } from 'oauth-1.0a'; import { LtiRole } from '../../../common/enum'; import { ExternalTool } from '../../../external-tool/domain'; diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts index f1e3388ed15..bd97fafde71 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts @@ -1,8 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; -import { CourseService } from '@src/modules/learnroom/service'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; import { ContextExternalTool } from '../../../context-external-tool/domain'; import { ExternalTool } from '../../../external-tool/domain'; import { SchoolExternalTool } from '../../../school-external-tool/domain'; diff --git a/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts b/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts index 95d8a42ce1f..4ae6a3a38a5 100644 --- a/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts +++ b/apps/server/src/modules/tool/tool-launch/tool-launch.module.ts @@ -1,8 +1,8 @@ import { Module, forwardRef } from '@nestjs/common'; -import { LearnroomModule } from '@src/modules/learnroom'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { PseudonymModule } from '@src/modules/pseudonym'; -import { UserModule } from '@src/modules/user'; +import { LearnroomModule } from '@modules/learnroom'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { PseudonymModule } from '@modules/pseudonym'; +import { UserModule } from '@modules/user'; import { CommonToolModule } from '../common'; import { ContextExternalToolModule } from '../context-external-tool'; import { ExternalToolModule } from '../external-tool'; diff --git a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts index fed27fa9aad..272d8dfd376 100644 --- a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts +++ b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityId, Permission } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder } from '@src/modules/authorization'; +import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ContextExternalToolService } from '../../context-external-tool/service'; diff --git a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts index 0315d40ee26..29b7796b40d 100644 --- a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts +++ b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts @@ -14,7 +14,7 @@ import { SystemEntity, User, } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, importUserFactory, @@ -24,8 +24,8 @@ import { systemFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { FilterImportUserParams, FilterMatchType, @@ -41,7 +41,7 @@ import { UserMatchListResponse, UserMatchResponse, UserRole, -} from '@src/modules/user-import/controller/dto'; +} from '@modules/user-import/controller/dto'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/user-import/controller/import-user.controller.spec.ts b/apps/server/src/modules/user-import/controller/import-user.controller.spec.ts index 91b1ebc6ec7..c93c7bc58a7 100644 --- a/apps/server/src/modules/user-import/controller/import-user.controller.spec.ts +++ b/apps/server/src/modules/user-import/controller/import-user.controller.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ImportUserRepo, SystemRepo, UserRepo } from '@shared/repo'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AccountService } from '@modules/account/services/account.service'; +import { AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { LoggerModule } from '@src/core/logger'; import { ConfigModule } from '@nestjs/config'; import { UserImportUc } from '../uc/user-import.uc'; diff --git a/apps/server/src/modules/user-import/controller/import-user.controller.ts b/apps/server/src/modules/user-import/controller/import-user.controller.ts index 03aa2741fc2..c4368b5ea3f 100644 --- a/apps/server/src/modules/user-import/controller/import-user.controller.ts +++ b/apps/server/src/modules/user-import/controller/import-user.controller.ts @@ -2,7 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestj import { ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; import { IFindOptions, ImportUser, User } from '@shared/domain'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ImportUserMapper } from '../mapper/import-user.mapper'; import { UserMatchMapper } from '../mapper/user-match.mapper'; import { UserImportUc } from '../uc/user-import.uc'; diff --git a/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts b/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts index 8dcccaf84e2..3c3baa56475 100644 --- a/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts +++ b/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts @@ -21,9 +21,9 @@ import { ImportUserRepo, SystemRepo, UserRepo } from '@shared/repo'; import { federalStateFactory, importUserFactory, schoolFactory, userFactory } from '@shared/testing'; import { systemFactory } from '@shared/testing/factory/system.factory'; import { LoggerModule } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AccountService } from '@modules/account/services/account.service'; +import { AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { LdapAlreadyPersistedException, MigrationAlreadyActivatedException, diff --git a/apps/server/src/modules/user-import/uc/user-import.uc.ts b/apps/server/src/modules/user-import/uc/user-import.uc.ts index ecef86207a0..2dda936b1fb 100644 --- a/apps/server/src/modules/user-import/uc/user-import.uc.ts +++ b/apps/server/src/modules/user-import/uc/user-import.uc.ts @@ -19,10 +19,10 @@ import { } from '@shared/domain'; import { ImportUserRepo, SystemRepo, UserRepo } from '@shared/repo'; import { Logger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; -import { AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; +import { AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { AccountSaveDto } from '../../account/services/dto'; import { MigrationMayBeCompleted, diff --git a/apps/server/src/modules/user-import/user-import.module.ts b/apps/server/src/modules/user-import/user-import.module.ts index 6e881ff5b2f..2cf4d94704e 100644 --- a/apps/server/src/modules/user-import/user-import.module.ts +++ b/apps/server/src/modules/user-import/user-import.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { ImportUserRepo, LegacySchoolRepo, SystemRepo, UserRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { LegacySchoolModule } from '@modules/legacy-school'; import { AccountModule } from '../account'; import { AuthorizationModule } from '../authorization'; import { ImportUserController } from './controller/import-user.controller'; diff --git a/apps/server/src/modules/user-login-migration/controller/api-test/user-login-migration.api.spec.ts b/apps/server/src/modules/user-login-migration/controller/api-test/user-login-migration.api.spec.ts index 486c814ccd7..b2de2ac9fc0 100644 --- a/apps/server/src/modules/user-login-migration/controller/api-test/user-login-migration.api.spec.ts +++ b/apps/server/src/modules/user-login-migration/controller/api-test/user-login-migration.api.spec.ts @@ -15,13 +15,13 @@ import { userLoginMigrationFactory, } from '@shared/testing'; import { JwtTestFactory } from '@shared/testing/factory/jwt.test.factory'; -import { OauthTokenResponse } from '@src/modules/oauth/service/dto'; -import { ServerTestModule } from '@src/modules/server'; +import { OauthTokenResponse } from '@modules/oauth/service/dto'; +import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { UUID } from 'bson'; import { Response } from 'supertest'; -import { SanisResponse, SanisRole } from '@src/modules/provisioning/strategy/sanis/response'; +import { SanisResponse, SanisRole } from '@modules/provisioning/strategy/sanis/response'; import { UserLoginMigrationResponse } from '../dto'; import { Oauth2MigrationParams } from '../dto/oauth2-migration.params'; diff --git a/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts b/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts index d94a79b5b94..3e788a54725 100644 --- a/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts +++ b/apps/server/src/modules/user-login-migration/controller/user-login-migration.controller.ts @@ -11,7 +11,7 @@ import { ApiUnprocessableEntityResponse, } from '@nestjs/swagger'; import { Page, UserLoginMigrationDO } from '@shared/domain'; -import { Authenticate, CurrentUser, ICurrentUser, JWT } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser, JWT } from '@modules/authentication'; import { SchoolNumberMissingLoggableException, UserLoginMigrationAlreadyClosedLoggableException, diff --git a/apps/server/src/modules/user-login-migration/error/oauth-migration.error.ts b/apps/server/src/modules/user-login-migration/error/oauth-migration.error.ts index 06f0c0c235e..c21185f1c93 100644 --- a/apps/server/src/modules/user-login-migration/error/oauth-migration.error.ts +++ b/apps/server/src/modules/user-login-migration/error/oauth-migration.error.ts @@ -1,4 +1,4 @@ -import { OAuthSSOError } from '@src/modules/oauth/loggable'; +import { OAuthSSOError } from '@modules/oauth/loggable'; export class OAuthMigrationError extends OAuthSSOError { readonly message: string; diff --git a/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts b/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts index c61382b9f63..7afc017cf98 100644 --- a/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts @@ -3,8 +3,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; import { legacySchoolDoFactory, userDoFactory } from '@shared/testing'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { MigrationCheckService } from './migration-check.service'; describe('MigrationCheckService', () => { diff --git a/apps/server/src/modules/user-login-migration/service/migration-check.service.ts b/apps/server/src/modules/user-login-migration/service/migration-check.service.ts index 7a0596e3599..70d0ab94066 100644 --- a/apps/server/src/modules/user-login-migration/service/migration-check.service.ts +++ b/apps/server/src/modules/user-login-migration/service/migration-check.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { EntityId, LegacySchoolDo, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; @Injectable() export class MigrationCheckService { diff --git a/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts b/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts index 19841ffb6de..8addd2afae6 100644 --- a/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts @@ -7,10 +7,10 @@ import { LegacySchoolDo, Page, UserDO, UserLoginMigrationDO } from '@shared/doma import { UserLoginMigrationRepo } from '@shared/repo/userloginmigration/user-login-migration.repo'; import { legacySchoolDoFactory, setupEntities, userDoFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; -import { OAuthMigrationError } from '@src/modules/user-login-migration/error/oauth-migration.error'; +import { ICurrentUser } from '@modules/authentication'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; +import { OAuthMigrationError } from '@modules/user-login-migration/error/oauth-migration.error'; import { SchoolMigrationService } from './school-migration.service'; describe('SchoolMigrationService', () => { diff --git a/apps/server/src/modules/user-login-migration/service/school-migration.service.ts b/apps/server/src/modules/user-login-migration/service/school-migration.service.ts index 04f3e44b265..147d9ec112b 100644 --- a/apps/server/src/modules/user-login-migration/service/school-migration.service.ts +++ b/apps/server/src/modules/user-login-migration/service/school-migration.service.ts @@ -3,8 +3,8 @@ import { ValidationError } from '@shared/common'; import { Page, LegacySchoolDo, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { performance } from 'perf_hooks'; import { OAuthMigrationError } from '../error'; diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts index 0b677fd8cf6..36f2176e00f 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { SchoolFeatures } from '@shared/domain'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { LegacySchoolService } from '@modules/legacy-school'; import { setupEntities, userLoginMigrationDOFactory } from '@shared/testing'; import { UserLoginMigrationRevertService } from './user-login-migration-revert.service'; import { UserLoginMigrationService } from './user-login-migration.service'; diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.ts index 88397e0b179..6d6512bc700 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { SchoolFeatures, UserLoginMigrationDO } from '@shared/domain'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationService } from './user-login-migration.service'; @Injectable() diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts index 0b755b68415..01e12e0df19 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts @@ -6,10 +6,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, LegacySchoolDo, SchoolFeatures, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; import { legacySchoolDoFactory, userDoFactory, userLoginMigrationDOFactory } from '@shared/testing'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemService } from '@src/modules/system'; -import { SystemDto } from '@src/modules/system/service'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemService } from '@modules/system'; +import { SystemDto } from '@modules/system/service'; +import { UserService } from '@modules/user'; import { UserLoginMigrationNotFoundLoggableException } from '../error'; import { SchoolMigrationService } from './school-migration.service'; import { UserLoginMigrationService } from './user-login-migration.service'; diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts index 9f3d6c59e84..534bb71e104 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts @@ -2,9 +2,9 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { Injectable, InternalServerErrorException, UnprocessableEntityException } from '@nestjs/common'; import { EntityId, LegacySchoolDo, SchoolFeatures, SystemTypeEnum, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemDto, SystemService } from '@src/modules/system'; -import { UserService } from '@src/modules/user'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemDto, SystemService } from '@modules/system'; +import { UserService } from '@modules/user'; import { UserLoginMigrationNotFoundLoggableException } from '../error'; import { SchoolMigrationService } from './school-migration.service'; diff --git a/apps/server/src/modules/user-login-migration/service/user-migration.service.spec.ts b/apps/server/src/modules/user-login-migration/service/user-migration.service.spec.ts index 24cb59030a5..c098066664d 100644 --- a/apps/server/src/modules/user-login-migration/service/user-migration.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/user-migration.service.spec.ts @@ -7,13 +7,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, RoleName, UserDO } from '@shared/domain'; import { legacySchoolDoFactory, setupEntities, userDoFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto, AccountSaveDto } from '@src/modules/account/services/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemService } from '@src/modules/system'; -import { OauthConfigDto } from '@src/modules/system/service/dto/oauth-config.dto'; -import { SystemDto } from '@src/modules/system/service/dto/system.dto'; -import { UserService } from '@src/modules/user'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto, AccountSaveDto } from '@modules/account/services/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemService } from '@modules/system'; +import { OauthConfigDto } from '@modules/system/service/dto/oauth-config.dto'; +import { SystemDto } from '@modules/system/service/dto/system.dto'; +import { UserService } from '@modules/user'; import { PageTypes } from '../interface/page-types.enum'; import { PageContentDto } from './dto'; import { UserMigrationService } from './user-migration.service'; diff --git a/apps/server/src/modules/user-login-migration/service/user-migration.service.ts b/apps/server/src/modules/user-login-migration/service/user-migration.service.ts index a2e999994f5..c9a6e8648c8 100644 --- a/apps/server/src/modules/user-login-migration/service/user-migration.service.ts +++ b/apps/server/src/modules/user-login-migration/service/user-migration.service.ts @@ -3,11 +3,11 @@ import { BadRequestException, Injectable, NotFoundException, UnprocessableEntity import { LegacySchoolDo } from '@shared/domain'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { LegacyLogger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { SystemDto, SystemService } from '@src/modules/system/service'; -import { UserService } from '@src/modules/user'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { SystemDto, SystemService } from '@modules/system/service'; +import { UserService } from '@modules/user'; import { EntityId } from '@src/shared/domain/types'; import { PageTypes } from '../interface/page-types.enum'; import { MigrationDto } from './dto/migration.dto'; diff --git a/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.spec.ts index 6b796e69bba..b14ab751d40 100644 --- a/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission, UserLoginMigrationDO } from '@shared/domain'; import { setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationService } from '@modules/authorization'; import { UserLoginMigrationNotFoundLoggableException } from '../error'; import { SchoolMigrationService, UserLoginMigrationRevertService, UserLoginMigrationService } from '../service'; import { CloseUserLoginMigrationUc } from './close-user-login-migration.uc'; diff --git a/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.ts index 13058e64c49..65bdad24782 100644 --- a/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/close-user-login-migration.uc.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { EntityId, Permission, User, UserLoginMigrationDO } from '@shared/domain'; -import { Action, AuthorizationService } from '@src/modules/authorization'; +import { Action, AuthorizationService } from '@modules/authorization'; import { UserLoginMigrationGracePeriodExpiredLoggableException, UserLoginMigrationNotFoundLoggableException, diff --git a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.spec.ts index c6e53ab483d..dd4ac4f835b 100644 --- a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.spec.ts @@ -4,8 +4,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { legacySchoolDoFactory, setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationGracePeriodExpiredLoggableException, UserLoginMigrationNotFoundLoggableException, diff --git a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts index 42412e50fe3..997276c3661 100644 --- a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationGracePeriodExpiredLoggableException, UserLoginMigrationNotFoundLoggableException, diff --git a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.spec.ts index e4c7510e684..3f0c03c07ff 100644 --- a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.spec.ts @@ -4,8 +4,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { legacySchoolDoFactory, setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { SchoolNumberMissingLoggableException, UserLoginMigrationAlreadyClosedLoggableException } from '../error'; import { UserLoginMigrationService } from '../service'; import { StartUserLoginMigrationUc } from './start-user-login-migration.uc'; diff --git a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts index 9e670e7fdf3..3dd84cc6ef5 100644 --- a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { SchoolNumberMissingLoggableException, UserLoginMigrationAlreadyClosedLoggableException } from '../error'; import { UserLoginMigrationStartLoggable } from '../loggable'; import { UserLoginMigrationService } from '../service'; diff --git a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts index cd97ad1e4ef..b0ff1f67e54 100644 --- a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts @@ -4,8 +4,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { legacySchoolDoFactory, setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationAlreadyClosedLoggableException, UserLoginMigrationGracePeriodExpiredLoggableException, diff --git a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts index d07578e7b23..45de7b6e1e3 100644 --- a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationAlreadyClosedLoggableException, UserLoginMigrationGracePeriodExpiredLoggableException, diff --git a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts index e9fc294f2ce..f5f710ae990 100644 --- a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts @@ -12,13 +12,13 @@ import { userLoginMigrationDOFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthenticationService } from '@src/modules/authentication/services/authentication.service'; -import { Action, AuthorizationService } from '@src/modules/authorization'; -import { OAuthTokenDto } from '@src/modules/oauth'; -import { OAuthService } from '@src/modules/oauth/service/oauth.service'; -import { ProvisioningService } from '@src/modules/provisioning'; -import { ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@src/modules/provisioning/dto'; -import { LegacySchoolService } from '@src/modules/legacy-school'; +import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { Action, AuthorizationService } from '@modules/authorization'; +import { OAuthTokenDto } from '@modules/oauth'; +import { OAuthService } from '@modules/oauth/service/oauth.service'; +import { ProvisioningService } from '@modules/provisioning'; +import { ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@modules/provisioning/dto'; +import { LegacySchoolService } from '@modules/legacy-school'; import { Oauth2MigrationParams } from '../controller/dto/oauth2-migration.params'; import { OAuthMigrationError, SchoolMigrationError, UserLoginMigrationError } from '../error'; import { PageTypes } from '../interface/page-types.enum'; diff --git a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts index bfbce03c9ca..a637afe01f6 100644 --- a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts @@ -2,12 +2,12 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { EntityId, Page, Permission, LegacySchoolDo, User, UserLoginMigrationDO } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthenticationService } from '@src/modules/authentication/services/authentication.service'; -import { Action, AuthorizationService } from '@src/modules/authorization'; -import { OAuthTokenDto } from '@src/modules/oauth'; -import { OAuthService } from '@src/modules/oauth/service/oauth.service'; -import { ProvisioningService } from '@src/modules/provisioning'; -import { OauthDataDto } from '@src/modules/provisioning/dto'; +import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { Action, AuthorizationService } from '@modules/authorization'; +import { OAuthTokenDto } from '@modules/oauth'; +import { OAuthService } from '@modules/oauth/service/oauth.service'; +import { ProvisioningService } from '@modules/provisioning'; +import { OauthDataDto } from '@modules/provisioning/dto'; import { OAuthMigrationError, SchoolMigrationError, UserLoginMigrationError } from '../error'; import { PageTypes } from '../interface/page-types.enum'; import { SchoolMigrationService, UserLoginMigrationService, UserMigrationService } from '../service'; diff --git a/apps/server/src/modules/user-login-migration/user-login-migration-api.module.ts b/apps/server/src/modules/user-login-migration/user-login-migration-api.module.ts index 4556103658d..b30a3f40f4d 100644 --- a/apps/server/src/modules/user-login-migration/user-login-migration-api.module.ts +++ b/apps/server/src/modules/user-login-migration/user-login-migration-api.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { OauthModule } from '@src/modules/oauth'; -import { ProvisioningModule } from '@src/modules/provisioning'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { AuthenticationModule } from '@modules/authentication/authentication.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { OauthModule } from '@modules/oauth'; +import { ProvisioningModule } from '@modules/provisioning'; +import { LegacySchoolModule } from '@modules/legacy-school'; import { UserLoginMigrationController } from './controller/user-login-migration.controller'; import { UserMigrationController } from './controller/user-migration.controller'; import { PageContentMapper } from './mapper'; diff --git a/apps/server/src/modules/user-login-migration/user-login-migration.module.ts b/apps/server/src/modules/user-login-migration/user-login-migration.module.ts index 40e965a4df3..705e5cb4094 100644 --- a/apps/server/src/modules/user-login-migration/user-login-migration.module.ts +++ b/apps/server/src/modules/user-login-migration/user-login-migration.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { UserLoginMigrationRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AccountModule } from '@src/modules/account'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; -import { SystemModule } from '@src/modules/system'; -import { UserModule } from '@src/modules/user'; +import { AccountModule } from '@modules/account'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { SystemModule } from '@modules/system'; +import { UserModule } from '@modules/user'; import { MigrationCheckService, SchoolMigrationService, diff --git a/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts b/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts index f7929db5d37..28bc7c5d14e 100644 --- a/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts +++ b/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts @@ -5,9 +5,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { LanguageType, User } from '@shared/domain'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; -import { ICurrentUser } from '@src/modules/authentication'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts b/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts index 4fbf963266a..370de7e4fa6 100644 --- a/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts +++ b/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts @@ -7,11 +7,11 @@ import request from 'supertest'; import { ApiValidationError } from '@shared/common'; import { LanguageType } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; -import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@src/modules/server/server.module'; -import { ResolvedUserResponse } from '@src/modules/user/controller/dto'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; +import { ResolvedUserResponse } from '@modules/user/controller/dto'; const baseRouteName = '/user/me'; diff --git a/apps/server/src/modules/user/controller/user.controller.ts b/apps/server/src/modules/user/controller/user.controller.ts index ffe5115bc8f..16729c2667b 100644 --- a/apps/server/src/modules/user/controller/user.controller.ts +++ b/apps/server/src/modules/user/controller/user.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, Patch } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ResolvedUserMapper } from '../mapper'; import { UserUc } from '../uc'; import { ChangeLanguageParams, ResolvedUserResponse, SuccessfulResponse } from './dto'; diff --git a/apps/server/src/modules/user/mapper/user.mapper.spec.ts b/apps/server/src/modules/user/mapper/user.mapper.spec.ts index fd97d8e9c9b..dbb5d475f05 100644 --- a/apps/server/src/modules/user/mapper/user.mapper.spec.ts +++ b/apps/server/src/modules/user/mapper/user.mapper.spec.ts @@ -1,7 +1,7 @@ import { User } from '@shared/domain'; import { roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { UserMapper } from '@src/modules/user/mapper/user.mapper'; -import { UserDto } from '@src/modules/user/uc/dto/user.dto'; +import { UserMapper } from '@modules/user/mapper/user.mapper'; +import { UserDto } from '@modules/user/uc/dto/user.dto'; describe('UserMapper', () => { let userEntity: User; diff --git a/apps/server/src/modules/user/mapper/user.mapper.ts b/apps/server/src/modules/user/mapper/user.mapper.ts index 72dd3e626d7..2238f0307b7 100644 --- a/apps/server/src/modules/user/mapper/user.mapper.ts +++ b/apps/server/src/modules/user/mapper/user.mapper.ts @@ -1,5 +1,5 @@ import { Role, User } from '@shared/domain'; -import { UserDto } from '@src/modules/user/uc/dto/user.dto'; +import { UserDto } from '@modules/user/uc/dto/user.dto'; export class UserMapper { static mapFromEntityToDto(entity: User): UserDto { diff --git a/apps/server/src/modules/user/service/user.service.spec.ts b/apps/server/src/modules/user/service/user.service.spec.ts index a24704014f5..1b208764c8f 100644 --- a/apps/server/src/modules/user/service/user.service.spec.ts +++ b/apps/server/src/modules/user/service/user.service.spec.ts @@ -7,12 +7,12 @@ import { UserDO } from '@shared/domain/domainobject/user.do'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; import { roleFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { ICurrentUser } from '@src/modules/authentication'; -import { RoleService } from '@src/modules/role/service/role.service'; -import { UserService } from '@src/modules/user/service/user.service'; -import { UserDto } from '@src/modules/user/uc/dto/user.dto'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { RoleService } from '@modules/role/service/role.service'; +import { UserService } from '@modules/user/service/user.service'; +import { UserDto } from '@modules/user/uc/dto/user.dto'; import { UserQuery } from './user-query.type'; describe('UserService', () => { diff --git a/apps/server/src/modules/user/service/user.service.ts b/apps/server/src/modules/user/service/user.service.ts index 199e8966a61..cc15404fc63 100644 --- a/apps/server/src/modules/user/service/user.service.ts +++ b/apps/server/src/modules/user/service/user.service.ts @@ -3,13 +3,13 @@ import { EntityId, IFindOptions, LanguageType, User } from '@shared/domain'; import { RoleReference, Page, UserDO } from '@shared/domain/domainobject'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; -import { AccountService } from '@src/modules/account'; -import { AccountDto } from '@src/modules/account/services/dto'; -import { ICurrentUser } from '@src/modules/authentication'; +import { AccountService } from '@modules/account'; +import { AccountDto } from '@modules/account/services/dto'; +import { ICurrentUser } from '@modules/authentication'; // invalid import -import { CurrentUserMapper } from '@src/modules/authentication/mapper'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; -import { RoleService } from '@src/modules/role/service/role.service'; +import { CurrentUserMapper } from '@modules/authentication/mapper'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; +import { RoleService } from '@modules/role/service/role.service'; import { BadRequestException, Injectable } from '@nestjs/common'; import { IUserConfig } from '../interfaces'; import { UserMapper } from '../mapper/user.mapper'; diff --git a/apps/server/src/modules/user/uc/user.uc.spec.ts b/apps/server/src/modules/user/uc/user.uc.spec.ts index 2988ff38671..3781a8c914a 100644 --- a/apps/server/src/modules/user/uc/user.uc.spec.ts +++ b/apps/server/src/modules/user/uc/user.uc.spec.ts @@ -5,7 +5,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LanguageType, Permission, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; import { roleFactory, setupEntities, userFactory } from '@shared/testing'; -import { UserService } from '@src/modules/user/service/user.service'; +import { UserService } from '@modules/user/service/user.service'; import { UserUc } from './user.uc'; describe('UserUc', () => { diff --git a/apps/server/src/modules/user/user.module.ts b/apps/server/src/modules/user/user.module.ts index 8b462c8ca20..d58c24546c6 100644 --- a/apps/server/src/modules/user/user.module.ts +++ b/apps/server/src/modules/user/user.module.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; import { LoggerModule } from '@src/core/logger'; -import { AccountModule } from '@src/modules/account'; -import { RoleModule } from '@src/modules/role/role.module'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { AccountModule } from '@modules/account'; +import { RoleModule } from '@modules/role/role.module'; +import { LegacySchoolModule } from '@modules/legacy-school'; import { UserService } from './service/user.service'; @Module({ diff --git a/apps/server/src/modules/video-conference/controller/api-test/video-conference.api.spec.ts b/apps/server/src/modules/video-conference/controller/api-test/video-conference.api.spec.ts index fad7295bee3..d8b81c0c698 100644 --- a/apps/server/src/modules/video-conference/controller/api-test/video-conference.api.spec.ts +++ b/apps/server/src/modules/video-conference/controller/api-test/video-conference.api.spec.ts @@ -25,7 +25,7 @@ import { userFactory, } from '@shared/testing'; import { videoConferenceFactory } from '@shared/testing/factory/video-conference.factory'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { Response } from 'supertest'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts index 84e78921410..6d78b513d75 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; import { BBBBaseResponse, BBBCreateResponse } from '../bbb'; import { defaultVideoConferenceOptions } from '../interface'; import { VideoConferenceDeprecatedUc } from '../uc'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts index 1f6657c28b2..8dbec66972e 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts @@ -11,7 +11,7 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { BBBBaseResponse } from '../bbb'; import { defaultVideoConferenceOptions } from '../interface'; import { VideoConferenceResponseDeprecatedMapper } from '../mapper/vc-deprecated-response.mapper'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts index 76b8a5c53ad..442e9132758 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, HttpStatus, Param, Put, Req } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Request } from 'express'; import { InvalidOriginForLogoutUrlLoggableException } from '../error'; import { VideoConferenceOptions } from '../interface'; diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts index 280a11976d7..c7a1ed30668 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts @@ -14,16 +14,16 @@ import { } from '@shared/domain'; import { CalendarEventDto, CalendarService } from '@shared/infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { courseFactory, roleFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory'; import { ObjectId } from 'bson'; import { teamFactory } from '@shared/testing/factory/team.factory'; import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; import { teamUserFactory } from '@shared/testing/factory/teamuser.factory'; -import { CourseService } from '@src/modules/learnroom/service'; +import { CourseService } from '@modules/learnroom/service'; import { VideoConferenceService } from './video-conference.service'; import { ErrorStatus } from '../error'; import { BBBRole } from '../bbb'; diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.ts b/apps/server/src/modules/video-conference/service/video-conference.service.ts index 69a1a7fd74f..22e7a7462f1 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.ts @@ -16,10 +16,10 @@ import { } from '@shared/domain'; import { CalendarEventDto, CalendarService } from '@shared/infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization'; -import { CourseService } from '@src/modules/learnroom'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { UserService } from '@src/modules/user'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { BBBRole } from '../bbb'; import { ErrorStatus } from '../error'; import { IVideoConferenceSettings, VideoConferenceOptions, VideoConferenceSettings } from '../interface'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts index 7570d6d38ff..4c38bf18467 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { UserService } from '@src/modules/user'; +import { UserService } from '@modules/user'; import { userDoFactory } from '@shared/testing'; import { UserDO, VideoConferenceScope } from '@shared/domain'; import { ObjectId } from 'bson'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts index 853bf0f13bc..940113276a2 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts @@ -1,6 +1,6 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO } from '@shared/domain'; -import { UserService } from '@src/modules/user'; +import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBCreateConfigBuilder, diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts index 4ff494a4cc0..4d15397548b 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts @@ -19,10 +19,11 @@ import { CalendarEventDto } from '@shared/infra/calendar/dto/calendar-event.dto' import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; import { roleFactory, setupEntities, userDoFactory } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { LegacySchoolService, UserService } from '@src/modules'; -import { AuthorizationReferenceService } from '@src/modules/authorization/domain'; -import { ICurrentUser } from '@src/modules/authentication'; -import { CourseService } from '@src/modules/learnroom'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; +import { ICurrentUser } from '@modules/authentication'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { IScopeInfo, VideoConference, VideoConferenceJoin, VideoConferenceState } from './dto'; import { VideoConferenceDeprecatedUc } from './video-conference-deprecated.uc'; import { diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts index e6b1f11ed50..41e011c4acd 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts @@ -17,12 +17,12 @@ import { CalendarService } from '@shared/infra/calendar'; import { CalendarEventDto } from '@shared/infra/calendar/dto/calendar-event.dto'; import { TeamsRepo } from '@shared/repo'; import { VideoConferenceRepo } from '@shared/repo/videoconference/video-conference.repo'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Action, AuthorizationContextBuilder } from '@src/modules/authorization'; -import { AuthorizationReferenceService, AuthorizableReferenceType } from '@src/modules/authorization/domain'; -import { LegacySchoolService } from '@src/modules/legacy-school'; -import { CourseService } from '@src/modules/learnroom'; -import { UserService } from '@src/modules/user'; +import { ICurrentUser } from '@modules/authentication'; +import { Action, AuthorizationContextBuilder } from '@modules/authorization'; +import { AuthorizationReferenceService, AuthorizableReferenceType } from '@modules/authorization/domain'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { CourseService } from '@modules/learnroom'; +import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBBaseResponse, diff --git a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts index 2b5744b4d79..d1dbfc18b9d 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { UserService } from '@src/modules/user'; +import { UserService } from '@modules/user'; import { userDoFactory } from '@shared/testing'; import { UserDO, VideoConferenceScope } from '@shared/domain'; import { ObjectId } from 'bson'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts index 50318c001c0..6cb70edc176 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts @@ -1,7 +1,7 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO } from '@shared/domain'; -import { ErrorStatus } from '@src/modules/video-conference/error/error-status.enum'; -import { UserService } from '@src/modules/user'; +import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; +import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBBaseResponse, BBBResponse, BBBRole, BBBService } from '../bbb'; import { IScopeInfo, ScopeRef, VideoConference, VideoConferenceState } from './dto'; import { VideoConferenceService } from '../service'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts index 1d044c7b9db..ebc8daa4084 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { UserService } from '@src/modules/user'; +import { UserService } from '@modules/user'; import { userDoFactory } from '@shared/testing'; import { Permission, UserDO, VideoConferenceDO, VideoConferenceScope } from '@shared/domain'; import { ObjectId } from 'bson'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts index 79a1f95b8d1..1c3736d1d01 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts @@ -1,7 +1,7 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO, VideoConferenceDO, VideoConferenceOptionsDO } from '@shared/domain'; -import { ErrorStatus } from '@src/modules/video-conference/error/error-status.enum'; -import { UserService } from '@src/modules/user'; +import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; +import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBMeetingInfoResponse, BBBResponse, BBBRole, BBBService } from '../bbb'; import { IScopeInfo, ScopeRef, VideoConferenceInfo, VideoConferenceState } from './dto'; import { VideoConferenceService } from '../service'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-join.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-join.uc.spec.ts index 0f46fcde450..525a43b4c69 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-join.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-join.uc.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { UserService } from '@src/modules/user'; +import { UserService } from '@modules/user'; import { userDoFactory } from '@shared/testing'; import { Permission, UserDO, VideoConferenceDO, VideoConferenceScope } from '@shared/domain'; import { ObjectId } from 'bson'; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-join.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-join.uc.ts index 5728a4b0da2..2015c20d381 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-join.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-join.uc.ts @@ -1,7 +1,7 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO, VideoConferenceDO } from '@shared/domain'; -import { ErrorStatus } from '@src/modules/video-conference/error/error-status.enum'; -import { UserService } from '@src/modules/user'; +import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; +import { UserService } from '@modules/user'; import { BBBJoinConfigBuilder, BBBRole, BBBService } from '../bbb'; import { ScopeRef, VideoConferenceJoin, VideoConferenceState } from './dto'; import { VideoConferenceService } from '../service'; diff --git a/apps/server/src/modules/video-conference/video-conference-api.module.ts b/apps/server/src/modules/video-conference/video-conference-api.module.ts index 4c2448d55d2..d4594a10a34 100644 --- a/apps/server/src/modules/video-conference/video-conference-api.module.ts +++ b/apps/server/src/modules/video-conference/video-conference-api.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { UserModule } from '@src/modules/user'; -import { AuthorizationModule } from '@src/modules/authorization'; +import { UserModule } from '@modules/user'; +import { AuthorizationModule } from '@modules/authorization'; import { VideoConferenceController } from './controller'; import { VideoConferenceCreateUc, VideoConferenceJoinUc, VideoConferenceEndUc, VideoConferenceInfoUc } from './uc'; import { VideoConferenceModule } from './video-conference.module'; diff --git a/apps/server/src/modules/video-conference/video-conference.module.ts b/apps/server/src/modules/video-conference/video-conference.module.ts index 6277f0dde0a..c9708b16dc9 100644 --- a/apps/server/src/modules/video-conference/video-conference.module.ts +++ b/apps/server/src/modules/video-conference/video-conference.module.ts @@ -2,13 +2,13 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { CalendarModule } from '@shared/infra/calendar'; import { VideoConferenceRepo } from '@shared/repo/videoconference/video-conference.repo'; -import { AuthorizationModule } from '@src/modules/authorization'; -import { AuthorizationReferenceModule } from '@src/modules/authorization/authorization-reference.module'; +import { AuthorizationModule } from '@modules/authorization'; +import { AuthorizationReferenceModule } from '@modules/authorization/authorization-reference.module'; import { TeamsRepo } from '@shared/repo'; -import { LegacySchoolModule } from '@src/modules/legacy-school'; +import { LegacySchoolModule } from '@modules/legacy-school'; import { LoggerModule } from '@src/core/logger'; import { ConverterUtil } from '@shared/common'; -import { UserModule } from '@src/modules/user'; +import { UserModule } from '@modules/user'; import { BBBService, BbbSettings } from './bbb'; import { VideoConferenceService } from './service'; import { VideoConferenceDeprecatedUc } from './uc'; diff --git a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts index 4c9a7bf7135..838ae020039 100644 --- a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts +++ b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts @@ -3,7 +3,7 @@ import { LegacyLogger, RequestLoggingBody } from '@src/core/logger'; import { Request } from 'express'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication/interface/user'; @Injectable() export class RequestLoggingInterceptor implements NestInterceptor { diff --git a/apps/server/src/shared/controller/swagger.spec.ts b/apps/server/src/shared/controller/swagger.spec.ts index 74236b00811..413a3fa3a9c 100644 --- a/apps/server/src/shared/controller/swagger.spec.ts +++ b/apps/server/src/shared/controller/swagger.spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { ServerTestModule } from '@src/modules/server'; +import { ServerTestModule } from '@modules/server'; import request from 'supertest'; import { enableOpenApiDocs } from './swagger'; diff --git a/apps/server/src/shared/domain/entity/all-entities.ts b/apps/server/src/shared/domain/entity/all-entities.ts index 6cbb3bf9810..9dc33c55b78 100644 --- a/apps/server/src/shared/domain/entity/all-entities.ts +++ b/apps/server/src/shared/domain/entity/all-entities.ts @@ -1,10 +1,10 @@ -import { ClassEntity } from '@src/modules/class/entity'; -import { GroupEntity } from '@src/modules/group/entity'; -import { ExternalToolPseudonymEntity, PseudonymEntity } from '@src/modules/pseudonym/entity'; -import { ShareToken } from '@src/modules/sharing/entity/share-token.entity'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { ClassEntity } from '@modules/class/entity'; +import { GroupEntity } from '@modules/group/entity'; +import { ExternalToolPseudonymEntity, PseudonymEntity } from '@modules/pseudonym/entity'; +import { ShareToken } from '@modules/sharing/entity/share-token.entity'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { Account } from './account.entity'; import { BoardNode, diff --git a/apps/server/src/shared/domain/entity/boardnode/external-tool-element-node.entity.ts b/apps/server/src/shared/domain/entity/boardnode/external-tool-element-node.entity.ts index 68df0b696b5..ffe2ef83bec 100644 --- a/apps/server/src/shared/domain/entity/boardnode/external-tool-element-node.entity.ts +++ b/apps/server/src/shared/domain/entity/boardnode/external-tool-element-node.entity.ts @@ -1,6 +1,6 @@ import { Entity, ManyToOne } from '@mikro-orm/core'; import { AnyBoardDo } from '@shared/domain/domainobject'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity/context-external-tool.entity'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity/context-external-tool.entity'; import { BoardNode, BoardNodeProps } from './boardnode.entity'; import { BoardDoBuilder, BoardNodeType } from './types'; diff --git a/apps/server/src/shared/domain/entity/course.entity.ts b/apps/server/src/shared/domain/entity/course.entity.ts index e873ed05300..81fdcb741d9 100644 --- a/apps/server/src/shared/domain/entity/course.entity.ts +++ b/apps/server/src/shared/domain/entity/course.entity.ts @@ -1,8 +1,8 @@ import { Collection, Entity, Enum, Index, ManyToMany, ManyToOne, OneToMany, Property, Unique } from '@mikro-orm/core'; import { InternalServerErrorException } from '@nestjs/common/exceptions/internal-server-error.exception'; import { IEntityWithSchool, ILearnroom } from '@shared/domain/interface'; -import { ClassEntity } from '@src/modules/class/entity/class.entity'; -import { GroupEntity } from '@src/modules/group/entity/group.entity'; +import { ClassEntity } from '@modules/class/entity/class.entity'; +import { GroupEntity } from '@modules/group/entity/group.entity'; import { EntityId, LearnroomMetadata, LearnroomTypes } from '../types'; import { BaseEntityWithTimestamps } from './base.entity'; import { CourseGroup } from './coursegroup.entity'; diff --git a/apps/server/src/shared/infra/antivirus/antivirus.service.ts b/apps/server/src/shared/infra/antivirus/antivirus.service.ts index 1f49b9907fb..15fbcbf4c1e 100644 --- a/apps/server/src/shared/infra/antivirus/antivirus.service.ts +++ b/apps/server/src/shared/infra/antivirus/antivirus.service.ts @@ -1,7 +1,7 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; import { ErrorUtils } from '@src/core/error/utils'; -import { API_VERSION_PATH, FilesStorageInternalActions } from '@src/modules/files-storage/files-storage.const'; +import { API_VERSION_PATH, FilesStorageInternalActions } from '@modules/files-storage/files-storage.const'; import NodeClam from 'clamscan'; import { Readable } from 'stream'; import { AntivirusServiceOptions, ScanResult } from './interfaces'; diff --git a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage-adapter.module.ts b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage-adapter.module.ts index 0580777a666..84e4f4596d6 100644 --- a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage-adapter.module.ts +++ b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage-adapter.module.ts @@ -6,9 +6,9 @@ import { NextcloudClient } from '@shared/infra/collaborative-storage/strategy/ne import { NextcloudStrategy } from '@shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy'; import { LtiToolRepo } from '@shared/repo/ltitool/'; import { LoggerModule } from '@src/core/logger'; -import { ToolModule } from '@src/modules/tool'; -import { PseudonymModule } from '@src/modules/pseudonym'; -import { UserModule } from '@src/modules/user'; +import { ToolModule } from '@modules/tool'; +import { PseudonymModule } from '@modules/pseudonym'; +import { UserModule } from '@modules/user'; import { CollaborativeStorageAdapter } from './collaborative-storage.adapter'; const storageStrategy: Provider = { diff --git a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.spec.ts b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.spec.ts index db2a05e26b5..c33f9be282a 100644 --- a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.spec.ts +++ b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.spec.ts @@ -6,7 +6,7 @@ import { CollaborativeStorageAdapter } from '@shared/infra/collaborative-storage import { CollaborativeStorageAdapterMapper } from '@shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper'; import { ICollaborativeStorageStrategy } from '@shared/infra/collaborative-storage/strategy/base.interface.strategy'; import { LegacyLogger } from '@src/core/logger'; -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; class TestStrategy implements ICollaborativeStorageStrategy { baseURL: string; diff --git a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.ts b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.ts index c6ef05c1e5b..9edcafbdc12 100644 --- a/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.ts +++ b/apps/server/src/shared/infra/collaborative-storage/collaborative-storage.adapter.ts @@ -1,10 +1,10 @@ -import { TeamPermissionsDto } from '@src/modules/collaborative-storage/services/dto/team-permissions.dto'; -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; +import { TeamPermissionsDto } from '@modules/collaborative-storage/services/dto/team-permissions.dto'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; import { ICollaborativeStorageStrategy } from '@shared/infra/collaborative-storage/strategy/base.interface.strategy'; import { Inject, Injectable } from '@nestjs/common'; import { CollaborativeStorageAdapterMapper } from '@shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper'; import { LegacyLogger } from '@src/core/logger'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; /** * Provides an Adapter to an external collaborative storage. diff --git a/apps/server/src/shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper.ts b/apps/server/src/shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper.ts index e3595440f9f..a04ace04490 100644 --- a/apps/server/src/shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper.ts +++ b/apps/server/src/shared/infra/collaborative-storage/mapper/collaborative-storage-adapter.mapper.ts @@ -1,7 +1,7 @@ -import { TeamPermissionsDto } from '@src/modules/collaborative-storage/services/dto/team-permissions.dto'; -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; +import { TeamPermissionsDto } from '@modules/collaborative-storage/services/dto/team-permissions.dto'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; import { Injectable } from '@nestjs/common'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; import { TeamRolePermissionsDto } from '../dto/team-role-permissions.dto'; @Injectable() diff --git a/apps/server/src/shared/infra/collaborative-storage/strategy/base.interface.strategy.ts b/apps/server/src/shared/infra/collaborative-storage/strategy/base.interface.strategy.ts index 51d741105f0..f960452df5f 100644 --- a/apps/server/src/shared/infra/collaborative-storage/strategy/base.interface.strategy.ts +++ b/apps/server/src/shared/infra/collaborative-storage/strategy/base.interface.strategy.ts @@ -1,4 +1,4 @@ -import { TeamDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; import { TeamRolePermissionsDto } from '../dto/team-role-permissions.dto'; /** diff --git a/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.spec.ts b/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.spec.ts index bc4b3878ea5..7684b14dbb0 100644 --- a/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.spec.ts +++ b/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.spec.ts @@ -9,10 +9,10 @@ import { NextcloudStrategy } from '@shared/infra/collaborative-storage/strategy/ import { LtiToolRepo } from '@shared/repo'; import { ltiToolDOFactory, pseudonymFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { TeamDto, TeamUserDto } from '@src/modules/collaborative-storage/services/dto/team.dto'; -import { PseudonymService } from '@src/modules/pseudonym'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { UserService } from '@src/modules/user'; +import { TeamDto, TeamUserDto } from '@modules/collaborative-storage/services/dto/team.dto'; +import { PseudonymService } from '@modules/pseudonym'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { UserService } from '@modules/user'; class NextcloudStrategySpec extends NextcloudStrategy { static specGenerateGroupId(dto: TeamRolePermissionsDto): string { diff --git a/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts b/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts index 1292bff3a42..6b75d6ec76f 100644 --- a/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts +++ b/apps/server/src/shared/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts @@ -3,11 +3,11 @@ import { Pseudonym, UserDO } from '@shared/domain/'; import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; import { LtiToolRepo } from '@shared/repo/ltitool/'; import { LegacyLogger } from '@src/core/logger'; -import { TeamDto, TeamUserDto } from '@src/modules/collaborative-storage'; -import { PseudonymService } from '@src/modules/pseudonym'; -import { UserService } from '@src/modules/user'; -import { ExternalToolService } from '@src/modules/tool/external-tool/service'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; +import { TeamDto, TeamUserDto } from '@modules/collaborative-storage'; +import { PseudonymService } from '@modules/pseudonym'; +import { UserService } from '@modules/user'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; import { TeamRolePermissionsDto } from '../../dto/team-role-permissions.dto'; import { ICollaborativeStorageStrategy } from '../base.interface.strategy'; import { NextcloudClient } from './nextcloud.client'; diff --git a/apps/server/src/shared/infra/identity-management/identity-management-oauth.service.ts b/apps/server/src/shared/infra/identity-management/identity-management-oauth.service.ts index 5003da4c62e..2a486e87c32 100644 --- a/apps/server/src/shared/infra/identity-management/identity-management-oauth.service.ts +++ b/apps/server/src/shared/infra/identity-management/identity-management-oauth.service.ts @@ -1,4 +1,4 @@ -import { OauthConfigDto } from '@src/modules/system/service/dto'; +import { OauthConfigDto } from '@modules/system/service/dto'; export abstract class IdentityManagementOauthService { /** diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/controller/keycloak-configuration.controller.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/controller/keycloak-configuration.controller.spec.ts index d1155cc3f88..734e8f628b2 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/controller/keycloak-configuration.controller.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/controller/keycloak-configuration.controller.spec.ts @@ -3,7 +3,7 @@ import { ServiceUnavailableException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { LegacyLogger } from '@src/core/logger'; -import { NodeEnvType } from '@src/modules/server/server.config'; +import { NodeEnvType } from '@modules/server/server.config'; import { KeycloakConfigurationUc } from '../uc/keycloak-configuration.uc'; import { KeycloakManagementController } from './keycloak-configuration.controller'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/keycloak-configuration.module.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/keycloak-configuration.module.ts index 8c51103495d..2012dad00a5 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/keycloak-configuration.module.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/keycloak-configuration.module.ts @@ -2,7 +2,8 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; import { EncryptionModule } from '@shared/infra/encryption'; import { ConsoleWriterModule } from '@shared/infra/console'; -import { AccountModule, SystemModule } from '@src/modules'; +import { AccountModule } from '@modules/account'; +import { SystemModule } from '@modules/system'; import { KeycloakAdministrationModule } from '../keycloak-administration/keycloak-administration.module'; import { KeycloakConsole } from './console/keycloak-configuration.console'; import { KeycloakConfigurationInputFiles } from './interface/keycloak-configuration-input-files.interface'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.spec.ts index 2b565bc77e8..b28d74ca3d5 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.spec.ts @@ -3,7 +3,7 @@ import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/ import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { DefaultEncryptionService, SymetricKeyEncryptionService } from '@shared/infra/encryption'; -import { OidcConfigDto } from '@src/modules/system/service'; +import { OidcConfigDto } from '@modules/system/service'; import { OidcIdentityProviderMapper } from './identity-provider.mapper'; describe('OidcIdentityProviderMapper', () => { diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts index c7f6ec65af7..75737263cac 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts @@ -1,7 +1,7 @@ import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation'; import { Inject } from '@nestjs/common'; import { DefaultEncryptionService, IEncryptionService } from '@shared/infra/encryption'; -import { OidcConfigDto } from '@src/modules/system/service'; +import { OidcConfigDto } from '@modules/system/service'; export class OidcIdentityProviderMapper { constructor(@Inject(DefaultEncryptionService) private readonly defaultEncryptionService: IEncryptionService) {} diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.integration.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.integration.spec.ts index a18a07e9fe7..ad5af6a1d75 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.integration.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.integration.spec.ts @@ -8,7 +8,7 @@ import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { SystemRepo } from '@shared/repo/system/system.repo'; import { systemFactory } from '@shared/testing/factory'; import { LoggerModule } from '@src/core/logger'; -import { SystemService } from '@src/modules/system/service/system.service'; +import { SystemService } from '@modules/system/service/system.service'; import { v1 } from 'uuid'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; import { KeycloakConfigurationModule } from '../keycloak-configuration.module'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts index 012d63d18c2..1388392995e 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts @@ -12,8 +12,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SystemEntity, SystemTypeEnum } from '@shared/domain'; import { SymetricKeyEncryptionService } from '@shared/infra/encryption'; import { systemFactory } from '@shared/testing'; -import { SystemOidcMapper } from '@src/modules/system/mapper/system-oidc.mapper'; -import { SystemOidcService } from '@src/modules/system/service/system-oidc.service'; +import { SystemOidcMapper } from '@modules/system/mapper/system-oidc.mapper'; +import { SystemOidcService } from '@modules/system/service/system-oidc.service'; import { AxiosResponse } from 'axios'; import { of } from 'rxjs'; import { v1 } from 'uuid'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts index 02f27db9eb2..ae7f2631bce 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts @@ -6,9 +6,9 @@ import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/ import ProtocolMapperRepresentation from '@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { IServerConfig } from '@src/modules/server/server.config'; -import { OidcConfigDto } from '@src/modules/system/service'; -import { SystemOidcService } from '@src/modules/system/service/system-oidc.service'; +import { IServerConfig } from '@modules/server/server.config'; +import { OidcConfigDto } from '@modules/system/service'; +import { SystemOidcService } from '@modules/system/service/system-oidc.service'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; import { OidcIdentityProviderMapper } from '../mapper/identity-provider.mapper'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.spec.ts index c8a1011e2c1..e02ade6a26b 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto/account.dto'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; import KeycloakAdminClient from '@keycloak/keycloak-admin-client-cjs/keycloak-admin-client-cjs-index'; import { Users } from '@keycloak/keycloak-admin-client/lib/resources/users'; import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.ts b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.ts index b64f69d5043..ce87478f637 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak-configuration/service/keycloak-migration.service.ts @@ -1,8 +1,8 @@ import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation'; import { Injectable } from '@nestjs/common'; import { LegacyLogger } from '@src/core/logger'; -import { AccountService } from '@src/modules/account/services/account.service'; -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; @Injectable() diff --git a/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts b/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts index e6526a34c22..7e10179b2cd 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts @@ -2,7 +2,7 @@ import { HttpService } from '@nestjs/axios'; import { Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { DefaultEncryptionService, IEncryptionService } from '@shared/infra/encryption'; -import { OauthConfigDto } from '@src/modules/system/service'; +import { OauthConfigDto } from '@modules/system/service'; import qs from 'qs'; import { lastValueFrom } from 'rxjs'; import { IdentityManagementOauthService } from '../../identity-management-oauth.service'; diff --git a/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts b/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts index b4e6535011d..fd66603f730 100644 --- a/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts +++ b/apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts @@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { IdmAccount, IdmAccountUpdate } from '@shared/domain'; import { KeycloakAdministrationService } from '@shared/infra/identity-management/keycloak-administration/service/keycloak-administration.service'; import { KeycloakModule } from '@shared/infra/identity-management/keycloak/keycloak.module'; -import { ServerModule } from '@src/modules/server'; +import { ServerModule } from '@modules/server'; import { v1 } from 'uuid'; import { IdentityManagementService } from '../../identity-management.service'; import { KeycloakIdentityManagementService } from './keycloak-identity-management.service'; diff --git a/apps/server/src/shared/infra/oauth-provider/dto/request/accept-consent-request.body.ts b/apps/server/src/shared/infra/oauth-provider/dto/request/accept-consent-request.body.ts index 6c553afdd50..235cb9191ea 100644 --- a/apps/server/src/shared/infra/oauth-provider/dto/request/accept-consent-request.body.ts +++ b/apps/server/src/shared/infra/oauth-provider/dto/request/accept-consent-request.body.ts @@ -1,4 +1,4 @@ -import { IdToken } from '@src/modules/oauth-provider/interface/id-token'; +import { IdToken } from '@modules/oauth-provider/interface/id-token'; export interface AcceptConsentRequestBody { grant_access_token_audience?: string[]; 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 6806aeb3f71..0a9151d8c9c 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 @@ -12,12 +12,12 @@ import { schoolFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { CustomParameterEntry } from '@src/modules/tool/common/domain'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { ContextExternalTool, ContextExternalToolProps } from '@src/modules/tool/context-external-tool/domain'; -import { ContextExternalToolEntity, ContextExternalToolType } from '@src/modules/tool/context-external-tool/entity'; -import { ContextExternalToolQuery } from '@src/modules/tool/context-external-tool/uc/dto/context-external-tool.types'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +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', () => { 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 084adb4b727..5ad1629f0c2 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 @@ -3,16 +3,16 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { BaseDORepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { ToolContextType } from '@src/modules/tool/common/enum/tool-context-type.enum'; -import { ContextExternalTool, ContextRef } from '@src/modules/tool/context-external-tool/domain'; +import { ToolContextType } from '@modules/tool/common/enum/tool-context-type.enum'; +import { ContextExternalTool, ContextRef } from '@modules/tool/context-external-tool/domain'; import { ContextExternalToolEntity, ContextExternalToolType, IContextExternalToolProperties, -} from '@src/modules/tool/context-external-tool/entity'; -import { ContextExternalToolQuery } from '@src/modules/tool/context-external-tool/uc/dto/context-external-tool.types'; -import { SchoolExternalToolRefDO } from '@src/modules/tool/school-external-tool/domain'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +} from '@modules/tool/context-external-tool/entity'; +import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; +import { SchoolExternalToolRefDO } from '@modules/tool/school-external-tool/domain'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { EntityId } from '../../domain'; import { ExternalToolRepoMapper } from '../externaltool'; import { ContextExternalToolScope } from './context-external-tool.scope'; diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.spec.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.spec.ts index b6896ae497f..25c77bf5beb 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.spec.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.spec.ts @@ -1,6 +1,6 @@ import { schoolExternalToolEntityFactory } from '@shared/testing'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { ContextExternalToolScope } from './context-external-tool.scope'; describe('CourseExternalToolScope', () => { diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.ts index 51540ed17ba..b2382c71c2c 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.scope.ts @@ -1,7 +1,7 @@ import { Scope } from '@shared/repo'; import { EntityId } from '@shared/domain'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { ContextExternalToolEntity } from '@src/modules/tool/context-external-tool/entity'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { ContextExternalToolEntity } from '@modules/tool/context-external-tool/entity'; export class ContextExternalToolScope extends Scope { byId(id: EntityId | undefined): ContextExternalToolScope { diff --git a/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.spec.ts b/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.spec.ts index d40c8654878..d3fe01c1b07 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.spec.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.spec.ts @@ -1,8 +1,8 @@ import { QueryOrderMap } from '@mikro-orm/core'; import { LtiTool, SortOrder, SortOrderMap } from '@shared/domain'; import { ExternalToolSortingMapper } from '@shared/repo'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; describe('ExternalToolSortingMapper', () => { describe('mapDOSortOrderToQueryOrder', () => { diff --git a/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.ts b/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.ts index 751130ad784..893f5cbc923 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool-sorting.mapper.ts @@ -1,7 +1,7 @@ import { QueryOrderMap } from '@mikro-orm/core'; import { SortOrderMap } from '@shared/domain'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; export class ExternalToolSortingMapper { static mapDOSortOrderToQueryOrder(sort: SortOrderMap): QueryOrderMap { diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts index 1ba9fa09aea..56cba80ec69 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts @@ -6,8 +6,8 @@ import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { ExternalToolRepo, ExternalToolRepoMapper } from '@shared/repo'; import { cleanupCollections, externalToolEntityFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ExternalToolSearchQuery } from '@src/modules/tool'; -import { CustomParameter } from '@src/modules/tool/common/domain'; +import { ExternalToolSearchQuery } from '@modules/tool'; +import { CustomParameter } from '@modules/tool/common/domain'; import { CustomParameterLocation, CustomParameterScope, @@ -15,14 +15,9 @@ import { LtiMessageType, LtiPrivacyPermission, ToolConfigType, -} from '@src/modules/tool/common/enum'; -import { - BasicToolConfig, - ExternalTool, - Lti11ToolConfig, - Oauth2ToolConfig, -} from '@src/modules/tool/external-tool/domain'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +} from '@modules/tool/common/enum'; +import { BasicToolConfig, ExternalTool, Lti11ToolConfig, Oauth2ToolConfig } from '@modules/tool/external-tool/domain'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; describe('ExternalToolRepo', () => { let module: TestingModule; diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts index bcd319de14d..ab35c7b5fda 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts @@ -1,13 +1,8 @@ import { UnprocessableEntityException } from '@nestjs/common'; -import { CustomParameter, CustomParameterEntry } from '@src/modules/tool/common/domain'; -import { CustomParameterEntryEntity } from '@src/modules/tool/common/entity'; -import { ToolConfigType } from '@src/modules/tool/common/enum'; -import { - BasicToolConfig, - ExternalTool, - Lti11ToolConfig, - Oauth2ToolConfig, -} from '@src/modules/tool/external-tool/domain'; +import { CustomParameter, CustomParameterEntry } from '@modules/tool/common/domain'; +import { CustomParameterEntryEntity } from '@modules/tool/common/entity'; +import { ToolConfigType } from '@modules/tool/common/enum'; +import { BasicToolConfig, ExternalTool, Lti11ToolConfig, Oauth2ToolConfig } from '@modules/tool/external-tool/domain'; import { BasicToolConfigEntity, CustomParameterEntity, @@ -15,7 +10,7 @@ import { IExternalToolProperties, Lti11ToolConfigEntity, Oauth2ToolConfigEntity, -} from '@src/modules/tool/external-tool/entity'; +} from '@modules/tool/external-tool/entity'; // TODO: maybe rename because of usage in external tool repo and school external tool repo export class ExternalToolRepoMapper { diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.ts index 05f3a2d47d1..4ea69a54855 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.ts @@ -4,10 +4,10 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator' import { IFindOptions, IPagination, Page, SortOrder } from '@shared/domain'; import { BaseDORepo, ExternalToolRepoMapper, ExternalToolSortingMapper, Scope } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { ToolConfigType } from '@src/modules/tool/common/enum'; -import { ExternalToolSearchQuery } from '@src/modules/tool/common/interface'; -import { ExternalTool } from '@src/modules/tool/external-tool/domain'; -import { ExternalToolEntity, IExternalToolProperties } from '@src/modules/tool/external-tool/entity'; +import { ToolConfigType } from '@modules/tool/common/enum'; +import { ExternalToolSearchQuery } from '@modules/tool/common/interface'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolEntity, IExternalToolProperties } from '@modules/tool/external-tool/entity'; import { ExternalToolScope } from './external-tool.scope'; @Injectable() diff --git a/apps/server/src/shared/repo/externaltool/external-tool.scope.ts b/apps/server/src/shared/repo/externaltool/external-tool.scope.ts index 38e7fbf7754..5065c84413c 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.scope.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.scope.ts @@ -1,5 +1,5 @@ import { Scope } from '@shared/repo/scope'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; export class ExternalToolScope extends Scope { byName(name: string | undefined): this { 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 98fceceee5f..a2844e8e426 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 @@ -11,11 +11,11 @@ import { } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; import { createMock } from '@golevelup/ts-jest'; -import { SchoolExternalToolQuery } from '@src/modules/tool/school-external-tool/uc/dto/school-external-tool.types'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; -import { CustomParameterEntry } from '@src/modules/tool/common/domain'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; +import { SchoolExternalToolQuery } from '@modules/tool/school-external-tool/uc/dto/school-external-tool.types'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; +import { CustomParameterEntry } from '@modules/tool/common/domain'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; import { SchoolExternalToolRepo } from './school-external-tool.repo'; describe('SchoolExternalToolRepo', () => { 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 bc9ab0cc868..64f55c715e8 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 @@ -4,10 +4,10 @@ import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator' import { SchoolEntity } from '@shared/domain'; import { BaseDORepo } from '@shared/repo/base.do.repo'; import { LegacyLogger } from '@src/core/logger'; -import { SchoolExternalToolQuery } from '@src/modules/tool/school-external-tool/uc/dto/school-external-tool.types'; -import { ISchoolExternalToolProperties, SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; -import { SchoolExternalTool } from '@src/modules/tool/school-external-tool/domain'; -import { ExternalToolEntity } from '@src/modules/tool/external-tool/entity'; +import { SchoolExternalToolQuery } from '@modules/tool/school-external-tool/uc/dto/school-external-tool.types'; +import { ISchoolExternalToolProperties, SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; +import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain'; +import { ExternalToolEntity } from '@modules/tool/external-tool/entity'; import { SchoolExternalToolScope } from './school-external-tool.scope'; import { ExternalToolRepoMapper } from '../externaltool'; diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.scope.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.scope.ts index 89ca466b72a..eed938cd7f3 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.scope.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.scope.ts @@ -1,6 +1,6 @@ import { Scope } from '@shared/repo/scope'; import { EntityId } from '@shared/domain'; -import { SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; export class SchoolExternalToolScope extends Scope { bySchoolId(schoolId: EntityId | undefined): this { diff --git a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts index 0d273283ec2..ddcfb55b520 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts @@ -27,7 +27,7 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { UserQuery } from '@src/modules/user/service/user-query.type'; +import { UserQuery } from '@modules/user/service/user-query.type'; describe('UserRepo', () => { let module: TestingModule; diff --git a/apps/server/src/shared/repo/user/user-do.repo.ts b/apps/server/src/shared/repo/user/user-do.repo.ts index 1856befc18b..e9eae128a1f 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -17,7 +17,7 @@ import { RoleReference } from '@shared/domain/domainobject'; import { Page } from '@shared/domain/domainobject/page'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { BaseDORepo, Scope } from '@shared/repo'; -import { UserQuery } from '@src/modules/user/service/user-query.type'; +import { UserQuery } from '@modules/user/service/user-query.type'; import { UserScope } from './user.scope'; @Injectable() diff --git a/apps/server/src/shared/testing/factory/account-dto.factory.ts b/apps/server/src/shared/testing/factory/account-dto.factory.ts index c8c5b07e183..126d469e644 100644 --- a/apps/server/src/shared/testing/factory/account-dto.factory.ts +++ b/apps/server/src/shared/testing/factory/account-dto.factory.ts @@ -1,4 +1,4 @@ -import { AccountDto } from '@src/modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto'; import { ObjectId } from 'bson'; import { defaultTestPasswordHash } from './account.factory'; import { BaseFactory } from './base.factory'; diff --git a/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts index 1299340095e..fb545c1e2e7 100644 --- a/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts @@ -1,10 +1,10 @@ import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { CustomParameterEntryEntity } from '@src/modules/tool/common/entity'; +import { CustomParameterEntryEntity } from '@modules/tool/common/entity'; import { ContextExternalToolEntity, ContextExternalToolType, IContextExternalToolProperties, -} from '@src/modules/tool/context-external-tool/entity'; +} from '@modules/tool/context-external-tool/entity'; import { courseFactory } from './course.factory'; import { schoolExternalToolEntityFactory } from './school-external-tool-entity.factory'; diff --git a/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts b/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts index a65d5141b61..ba0f8899249 100644 --- a/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts @@ -1,5 +1,5 @@ import { ExternalSource } from '@shared/domain'; -import { Group, GroupProps, GroupTypes } from '@src/modules/group/domain'; +import { Group, GroupProps, GroupTypes } from '@modules/group/domain'; import { ObjectId } from 'bson'; import { DomainObjectFactory } from '../domain-object.factory'; diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/context-external-tool.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/context-external-tool.factory.ts index 015713dd997..8bb20364db1 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/context-external-tool.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/context-external-tool.factory.ts @@ -1,7 +1,7 @@ import { ObjectId } from '@mikro-orm/mongodb'; -import { CustomParameterEntry } from '@src/modules/tool/common/domain'; -import { ToolContextType } from '@src/modules/tool/common/enum'; -import { ContextExternalTool, ContextExternalToolProps } from '@src/modules/tool/context-external-tool/domain'; +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 { DeepPartial } from 'fishery'; import { DoBaseFactory } from '../do-base.factory'; diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/external-tool.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/external-tool.factory.ts index cc33b3d63b9..7815d863a69 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/external-tool.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/external-tool.factory.ts @@ -1,4 +1,4 @@ -import { CustomParameter } from '@src/modules/tool/common/domain'; +import { CustomParameter } from '@modules/tool/common/domain'; import { CustomParameterLocation, CustomParameterScope, @@ -7,14 +7,14 @@ import { LtiPrivacyPermission, TokenEndpointAuthMethod, ToolConfigType, -} from '@src/modules/tool/common/enum'; +} from '@modules/tool/common/enum'; import { BasicToolConfig, ExternalTool, ExternalToolProps, Lti11ToolConfig, Oauth2ToolConfig, -} from '@src/modules/tool/external-tool/domain'; +} from '@modules/tool/external-tool/domain'; import { DeepPartial } from 'fishery'; import { DoBaseFactory } from '../do-base.factory'; diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts index 08c52e487cc..a10ae7797fe 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts @@ -1,5 +1,5 @@ -import { CustomParameterEntry, ToolConfigurationStatus } from '@src/modules/tool/common/domain'; -import { SchoolExternalTool, SchoolExternalToolProps } from '@src/modules/tool/school-external-tool/domain'; +import { CustomParameterEntry, ToolConfigurationStatus } from '@modules/tool/common/domain'; +import { SchoolExternalTool, SchoolExternalToolProps } from '@modules/tool/school-external-tool/domain'; import { DeepPartial } from 'fishery'; import { DoBaseFactory } from '../do-base.factory'; diff --git a/apps/server/src/shared/testing/factory/external-group-dto.factory.ts b/apps/server/src/shared/testing/factory/external-group-dto.factory.ts index 241b1fb45bd..562b68f8767 100644 --- a/apps/server/src/shared/testing/factory/external-group-dto.factory.ts +++ b/apps/server/src/shared/testing/factory/external-group-dto.factory.ts @@ -1,7 +1,7 @@ import { RoleName } from '@shared/domain'; import { ObjectId } from 'bson'; -import { ExternalGroupDto } from '@src/modules/provisioning/dto'; -import { GroupTypes } from '@src/modules/group'; +import { ExternalGroupDto } from '@modules/provisioning/dto'; +import { GroupTypes } from '@modules/group'; import { BaseFactory } from './base.factory'; export const externalGroupDtoFactory = BaseFactory.define( diff --git a/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts index ed47f93b28b..32c077e2529 100644 --- a/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts @@ -5,7 +5,7 @@ import { LtiMessageType, LtiPrivacyPermission, ToolConfigType, -} from '@src/modules/tool/common/enum'; +} from '@modules/tool/common/enum'; import { BasicToolConfigEntity, CustomParameterEntity, @@ -13,7 +13,7 @@ import { IExternalToolProperties, Lti11ToolConfigEntity, Oauth2ToolConfigEntity, -} from '@src/modules/tool/external-tool/entity'; +} from '@modules/tool/external-tool/entity'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; diff --git a/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts b/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts index 3e263dc9ae5..b3ee2595412 100644 --- a/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts +++ b/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts @@ -1,6 +1,6 @@ import { BaseFactory } from '@shared/testing/factory/base.factory'; import { ObjectId } from '@mikro-orm/mongodb'; -import { ExternalToolPseudonymEntity, IExternalToolPseudonymEntityProps } from '@src/modules/pseudonym/entity'; +import { ExternalToolPseudonymEntity, IExternalToolPseudonymEntityProps } from '@modules/pseudonym/entity'; export const externalToolPseudonymEntityFactory = BaseFactory.define< ExternalToolPseudonymEntity, diff --git a/apps/server/src/shared/testing/factory/filerecord.factory.ts b/apps/server/src/shared/testing/factory/filerecord.factory.ts index 4a0c73966fd..36811ed9752 100644 --- a/apps/server/src/shared/testing/factory/filerecord.factory.ts +++ b/apps/server/src/shared/testing/factory/filerecord.factory.ts @@ -1,5 +1,5 @@ import { FileRecordParentType } from '@shared/infra/rabbitmq'; -import { FileRecord, FileRecordSecurityCheck, IFileRecordProperties } from '@src/modules/files-storage/entity'; +import { FileRecord, FileRecordSecurityCheck, IFileRecordProperties } from '@modules/files-storage/entity'; import { ObjectId } from 'bson'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; diff --git a/apps/server/src/shared/testing/factory/group-entity.factory.ts b/apps/server/src/shared/testing/factory/group-entity.factory.ts index 591b7c37d41..482aca971cf 100644 --- a/apps/server/src/shared/testing/factory/group-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/group-entity.factory.ts @@ -1,5 +1,5 @@ import { ExternalSourceEntity, RoleName } from '@shared/domain'; -import { GroupEntity, GroupEntityProps, GroupEntityTypes, GroupValidPeriodEntity } from '@src/modules/group/entity'; +import { GroupEntity, GroupEntityProps, GroupEntityTypes, GroupValidPeriodEntity } from '@modules/group/entity'; import { BaseFactory } from './base.factory'; import { roleFactory } from './role.factory'; import { schoolFactory } from './school.factory'; diff --git a/apps/server/src/shared/testing/factory/pseudonym.factory.ts b/apps/server/src/shared/testing/factory/pseudonym.factory.ts index 6f2f60e371c..96be2d1c3c0 100644 --- a/apps/server/src/shared/testing/factory/pseudonym.factory.ts +++ b/apps/server/src/shared/testing/factory/pseudonym.factory.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { PseudonymEntity, PseudonymEntityProps } from '@src/modules/pseudonym/entity'; +import { PseudonymEntity, PseudonymEntityProps } from '@modules/pseudonym/entity'; export const pseudonymEntityFactory = BaseFactory.define( PseudonymEntity, diff --git a/apps/server/src/shared/testing/factory/role-dto.factory.ts b/apps/server/src/shared/testing/factory/role-dto.factory.ts index 03d14965d41..2158580753d 100644 --- a/apps/server/src/shared/testing/factory/role-dto.factory.ts +++ b/apps/server/src/shared/testing/factory/role-dto.factory.ts @@ -1,6 +1,6 @@ import { RoleName } from '@shared/domain'; import { ObjectId } from 'bson'; -import { RoleDto } from '@src/modules/role/service/dto/role.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; import { BaseFactory } from './base.factory'; import { userPermissions } from '../user-role-permissions'; diff --git a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts index c991d5abc75..456356e6e23 100644 --- a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts @@ -1,5 +1,5 @@ import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { ISchoolExternalToolProperties, SchoolExternalToolEntity } from '@src/modules/tool/school-external-tool/entity'; +import { ISchoolExternalToolProperties, SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { externalToolEntityFactory } from './external-tool-entity.factory'; import { schoolFactory } from './school.factory'; diff --git a/apps/server/src/shared/testing/factory/share-token.do.factory.ts b/apps/server/src/shared/testing/factory/share-token.do.factory.ts index 02262f4d175..2c23ca904be 100644 --- a/apps/server/src/shared/testing/factory/share-token.do.factory.ts +++ b/apps/server/src/shared/testing/factory/share-token.do.factory.ts @@ -1,6 +1,6 @@ /* istanbul ignore file */ import { EntityId } from '@shared/domain'; -import { ShareTokenDO, ShareTokenParentType } from '@src/modules/sharing/domainobject/share-token.do'; +import { ShareTokenDO, ShareTokenParentType } from '@modules/sharing/domainobject/share-token.do'; import { ObjectId } from 'bson'; import { Factory } from 'fishery'; diff --git a/apps/server/src/shared/testing/map-user-to-current-user.ts b/apps/server/src/shared/testing/map-user-to-current-user.ts index 1d77d8d2bb3..d835c822066 100644 --- a/apps/server/src/shared/testing/map-user-to-current-user.ts +++ b/apps/server/src/shared/testing/map-user-to-current-user.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Account, EntityId, User } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; +import { ICurrentUser } from '@modules/authentication'; export const mapUserToCurrentUser = ( user: User, diff --git a/jest.config.ts b/jest.config.ts index dde0a796711..032f4828dde 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -25,6 +25,7 @@ let config: Config.InitialOptions = { // add ts-config path's here as regex '^@shared/(.*)$': '/apps/server/src/shared/$1', '^@src/(.*)$': '/apps/server/src/$1', + '^@modules/(.*)$': '/apps/server/src/modules/$1', }, maxWorkers: 2, // limited for not taking all workers within of a single github action }; diff --git a/tsconfig.json b/tsconfig.json index 912ea1b5a78..9bb7c6f72f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ "incremental": true, "paths": { "@shared/*": ["apps/server/src/shared/*"], - "@src/*": ["apps/server/src/*"] + "@src/*": ["apps/server/src/*"], + "@modules/*": ["apps/server/src/modules/*"], }, } } From e70a7b7a804f44640a60542859600ce9a262d285 Mon Sep 17 00:00:00 2001 From: Igor Richter <93926487+IgorCapCoder@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:55:37 +0200 Subject: [PATCH 07/19] N21 1264 new class page extension (#4474) * add boolean to class --- .../controller/api-test/group.api.spec.ts | 1 + .../dto/response/class-info.response.ts | 4 ++ .../mapper/group-response.mapper.ts | 1 + .../modules/group/uc/dto/class-info.dto.ts | 3 ++ .../src/modules/group/uc/group.uc.spec.ts | 2 + .../group/uc/mapper/group-uc.mapper.ts | 2 + backup/setup/groups.json | 37 +++++++++++++++++++ 7 files changed, 50 insertions(+) create mode 100644 backup/setup/groups.json diff --git a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts index 471d8b348af..3ace7386565 100644 --- a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts +++ b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts @@ -126,6 +126,7 @@ describe('Group (API)', () => { name: clazz.gradeLevel ? `${clazz.gradeLevel}${clazz.name}` : clazz.name, teachers: [teacherUser.lastName], schoolYear: schoolYear.name, + isUpgradable: false, }, ], skip: 0, diff --git a/apps/server/src/modules/group/controller/dto/response/class-info.response.ts b/apps/server/src/modules/group/controller/dto/response/class-info.response.ts index 62c52501b95..a62b8134158 100644 --- a/apps/server/src/modules/group/controller/dto/response/class-info.response.ts +++ b/apps/server/src/modules/group/controller/dto/response/class-info.response.ts @@ -20,6 +20,9 @@ export class ClassInfoResponse { @ApiPropertyOptional() schoolYear?: string; + @ApiPropertyOptional() + isUpgradable?: boolean; + constructor(props: ClassInfoResponse) { this.id = props.id; this.type = props.type; @@ -27,5 +30,6 @@ export class ClassInfoResponse { this.externalSourceName = props.externalSourceName; this.teachers = props.teachers; this.schoolYear = props.schoolYear; + this.isUpgradable = props.isUpgradable; } } diff --git a/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts b/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts index 958aeee2c6b..5f337eb204c 100644 --- a/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts +++ b/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts @@ -30,6 +30,7 @@ export class GroupResponseMapper { externalSourceName: classInfo.externalSourceName, teachers: classInfo.teachers, schoolYear: classInfo.schoolYear, + isUpgradable: classInfo.isUpgradable, }); return mapped; diff --git a/apps/server/src/modules/group/uc/dto/class-info.dto.ts b/apps/server/src/modules/group/uc/dto/class-info.dto.ts index d17c0169c93..8c564d9e106 100644 --- a/apps/server/src/modules/group/uc/dto/class-info.dto.ts +++ b/apps/server/src/modules/group/uc/dto/class-info.dto.ts @@ -13,6 +13,8 @@ export class ClassInfoDto { schoolYear?: string; + isUpgradable?: boolean; + constructor(props: ClassInfoDto) { this.id = props.id; this.type = props.type; @@ -20,5 +22,6 @@ export class ClassInfoDto { this.externalSourceName = props.externalSourceName; this.teachers = props.teachers; this.schoolYear = props.schoolYear; + this.isUpgradable = props.isUpgradable; } } diff --git a/apps/server/src/modules/group/uc/group.uc.spec.ts b/apps/server/src/modules/group/uc/group.uc.spec.ts index 3de4d262679..8e882c052d3 100644 --- a/apps/server/src/modules/group/uc/group.uc.spec.ts +++ b/apps/server/src/modules/group/uc/group.uc.spec.ts @@ -248,6 +248,7 @@ describe('GroupUc', () => { externalSourceName: clazz.source, teachers: [teacherUser.lastName], schoolYear: schoolYear.name, + isUpgradable: false, }, { id: group.id, @@ -290,6 +291,7 @@ describe('GroupUc', () => { externalSourceName: clazz.source, teachers: [teacherUser.lastName], schoolYear: schoolYear.name, + isUpgradable: false, }, { id: groupWithSystem.id, diff --git a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts index d5a415498db..ebeba60117e 100644 --- a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts +++ b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts @@ -26,6 +26,7 @@ export class GroupUcMapper { public static mapClassToClassInfoDto(clazz: Class, teachers: UserDO[], schoolYear?: SchoolYearEntity): ClassInfoDto { const name = clazz.gradeLevel ? `${clazz.gradeLevel}${clazz.name}` : clazz.name; + const isUpgradable = clazz.gradeLevel !== 13 && !clazz.successor; const mapped: ClassInfoDto = new ClassInfoDto({ id: clazz.id, @@ -34,6 +35,7 @@ export class GroupUcMapper { externalSourceName: clazz.source, teachers: teachers.map((user: UserDO) => user.lastName), schoolYear: schoolYear?.name, + isUpgradable, }); return mapped; diff --git a/backup/setup/groups.json b/backup/setup/groups.json new file mode 100644 index 00000000000..a682580f824 --- /dev/null +++ b/backup/setup/groups.json @@ -0,0 +1,37 @@ +[ + { + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group", + "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + } +] From d570dd6a0cabfb4efcbf18f13df20d3dbb8fe168 Mon Sep 17 00:00:00 2001 From: agnisa-cap Date: Fri, 20 Oct 2023 16:08:46 +0200 Subject: [PATCH 08/19] N21-1207 show group members (#4479) * adds get /groups/:id * adds script for adding permission GROUP_LIST and GROUP_VIEW --- .../authorization/authorization.module.ts | 2 + .../domain/rules/group.rule.spec.ts | 210 ++++++++++++++++++ .../authorization/domain/rules/group.rule.ts | 24 ++ .../authorization/domain/rules/index.ts | 1 + .../domain/service/rule-manager.spec.ts | 8 + .../domain/service/rule-manager.ts | 5 +- .../controller/api-test/group.api.spec.ts | 118 +++++++++- .../controller/dto/request/group-id-params.ts | 8 + .../group/controller/dto/request/index.ts | 1 + .../dto/response/external-source.response.ts | 14 ++ .../dto/response/group-type.response.ts | 3 + .../dto/response/group-user.response.ts | 23 ++ .../controller/dto/response/group.response.ts | 33 +++ .../group/controller/dto/response/index.ts | 4 + .../group/controller/group.controller.ts | 22 +- .../mapper/group-response.mapper.ts | 42 +++- apps/server/src/modules/group/domain/group.ts | 4 + apps/server/src/modules/group/uc/dto/index.ts | 1 + .../group/uc/dto/resolved-group.dto.ts | 26 +++ .../src/modules/group/uc/group.uc.spec.ts | 149 ++++++++++++- apps/server/src/modules/group/uc/group.uc.ts | 28 ++- .../group/uc/mapper/group-uc.mapper.ts | 14 +- .../domain/interface/permission.enum.ts | 2 + apps/server/src/shared/domain/rules/index.ts | 0 .../shared/testing/user-role-permissions.ts | 2 + backup/setup/migrations.json | 11 + backup/setup/roles.json | 20 +- ...4886-add-group-view-and-list-permission.js | 100 +++++++++ 28 files changed, 853 insertions(+), 22 deletions(-) create mode 100644 apps/server/src/modules/authorization/domain/rules/group.rule.spec.ts create mode 100644 apps/server/src/modules/authorization/domain/rules/group.rule.ts create mode 100644 apps/server/src/modules/group/controller/dto/request/group-id-params.ts create mode 100644 apps/server/src/modules/group/controller/dto/response/external-source.response.ts create mode 100644 apps/server/src/modules/group/controller/dto/response/group-type.response.ts create mode 100644 apps/server/src/modules/group/controller/dto/response/group-user.response.ts create mode 100644 apps/server/src/modules/group/controller/dto/response/group.response.ts create mode 100644 apps/server/src/modules/group/uc/dto/resolved-group.dto.ts create mode 100644 apps/server/src/shared/domain/rules/index.ts create mode 100644 migrations/1697553524886-add-group-view-and-list-permission.js diff --git a/apps/server/src/modules/authorization/authorization.module.ts b/apps/server/src/modules/authorization/authorization.module.ts index 37ca0a2b229..c555f13dc7b 100644 --- a/apps/server/src/modules/authorization/authorization.module.ts +++ b/apps/server/src/modules/authorization/authorization.module.ts @@ -15,6 +15,7 @@ import { UserRule, UserLoginMigrationRule, LegacySchoolRule, + GroupRule, } from './domain/rules'; import { AuthorizationHelper, AuthorizationService, RuleManager } from './domain'; import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers'; @@ -33,6 +34,7 @@ import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers'; ContextExternalToolRule, CourseGroupRule, CourseRule, + GroupRule, LessonRule, SchoolExternalToolRule, SubmissionRule, diff --git a/apps/server/src/modules/authorization/domain/rules/group.rule.spec.ts b/apps/server/src/modules/authorization/domain/rules/group.rule.spec.ts new file mode 100644 index 00000000000..bb2bc2e48b3 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/rules/group.rule.spec.ts @@ -0,0 +1,210 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Permission, Role, SchoolEntity, User } from '@shared/domain'; +import { groupFactory, roleFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; +import { Action, AuthorizationContext, AuthorizationHelper } from '@src/modules/authorization'; +import { Group } from '@src/modules/group'; +import { ObjectId } from 'bson'; +import { GroupRule } from './group.rule'; + +describe('GroupRule', () => { + let module: TestingModule; + let rule: GroupRule; + + let authorizationHelper: DeepMocked; + + beforeAll(async () => { + await setupEntities(); + + module = await Test.createTestingModule({ + providers: [ + GroupRule, + { + provide: AuthorizationHelper, + useValue: createMock(), + }, + ], + }).compile(); + + rule = module.get(GroupRule); + authorizationHelper = module.get(AuthorizationHelper); + }); + + afterAll(async () => { + await module.close(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('isApplicable', () => { + describe('when the entity is applicable', () => { + const setup = () => { + const role: Role = roleFactory.buildWithId(); + const user: User = userFactory.buildWithId({ roles: [role] }); + const group: Group = groupFactory.build({ + users: [ + { + userId: user.id, + roleId: user.roles[0].id, + }, + ], + }); + + return { + user, + group, + }; + }; + + it('should return true', () => { + const { user, group } = setup(); + + const result = rule.isApplicable(user, group); + + expect(result).toEqual(true); + }); + }); + + describe('when the entity is not applicable', () => { + const setup = () => { + const role: Role = roleFactory.buildWithId(); + const userNotInGroup: User = userFactory.buildWithId({ roles: [role] }); + + return { + userNotInGroup, + }; + }; + + it('should return false', () => { + const { userNotInGroup } = setup(); + + const result = rule.isApplicable(userNotInGroup, {} as unknown as Group); + + expect(result).toEqual(false); + }); + }); + }); + + describe('hasPermission', () => { + describe('when the user has all required permissions and is at the same school then the group', () => { + const setup = () => { + const role: Role = roleFactory.buildWithId(); + const school: SchoolEntity = schoolFactory.buildWithId(); + const user: User = userFactory.buildWithId({ school, roles: [role] }); + const group: Group = groupFactory.build({ + users: [ + { + userId: user.id, + roleId: user.roles[0].id, + }, + ], + organizationId: user.school.id, + }); + const context: AuthorizationContext = { + action: Action.write, + requiredPermissions: [Permission.GROUP_VIEW], + }; + + authorizationHelper.hasAllPermissions.mockReturnValue(true); + + return { + user, + group, + context, + }; + }; + + it('should check all permissions', () => { + const { user, group, context } = setup(); + + rule.hasPermission(user, group, context); + + expect(authorizationHelper.hasAllPermissions).toHaveBeenCalledWith(user, context.requiredPermissions); + }); + + it('should return true', () => { + const { user, group, context } = setup(); + + const result = rule.hasPermission(user, group, context); + + expect(result).toEqual(true); + }); + }); + + describe('when the user has not the required permission', () => { + const setup = () => { + const role: Role = roleFactory.buildWithId({ permissions: [] }); + const school: SchoolEntity = schoolFactory.buildWithId(); + const user: User = userFactory.buildWithId({ school, roles: [role] }); + const group: Group = groupFactory.build({ + users: [ + { + userId: user.id, + roleId: user.roles[0].id, + }, + ], + organizationId: user.school.id, + }); + const context: AuthorizationContext = { + action: Action.write, + requiredPermissions: [Permission.GROUP_VIEW], + }; + + authorizationHelper.hasAllPermissions.mockReturnValue(false); + + return { + user, + group, + context, + }; + }; + + it('should return false', () => { + const { user, group, context } = setup(); + + const result = rule.hasPermission(user, group, context); + + expect(result).toEqual(false); + }); + }); + + describe('when the user is at another school then the group', () => { + const setup = () => { + const role: Role = roleFactory.buildWithId({ permissions: [] }); + const school: SchoolEntity = schoolFactory.buildWithId(); + const user: User = userFactory.buildWithId({ school, roles: [role] }); + const group: Group = groupFactory.build({ + users: [ + { + userId: user.id, + roleId: user.roles[0].id, + }, + ], + organizationId: new ObjectId().toHexString(), + }); + const context: AuthorizationContext = { + action: Action.write, + requiredPermissions: [Permission.GROUP_VIEW], + }; + + authorizationHelper.hasAllPermissions.mockReturnValue(true); + + return { + user, + group, + context, + }; + }; + + it('should return false', () => { + const { user, group, context } = setup(); + + const result = rule.hasPermission(user, group, context); + + expect(result).toEqual(false); + }); + }); + }); +}); diff --git a/apps/server/src/modules/authorization/domain/rules/group.rule.ts b/apps/server/src/modules/authorization/domain/rules/group.rule.ts new file mode 100644 index 00000000000..e25e90230c8 --- /dev/null +++ b/apps/server/src/modules/authorization/domain/rules/group.rule.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { User } from '@shared/domain'; +import { Group } from '@src/modules/group'; +import { AuthorizationContext, Rule } from '../type'; +import { AuthorizationHelper } from '../service/authorization.helper'; + +@Injectable() +export class GroupRule implements Rule { + constructor(private readonly authorizationHelper: AuthorizationHelper) {} + + public isApplicable(user: User, domainObject: Group): boolean { + const isMatched: boolean = domainObject instanceof Group; + + return isMatched; + } + + public hasPermission(user: User, domainObject: Group, context: AuthorizationContext): boolean { + const hasPermission: boolean = + this.authorizationHelper.hasAllPermissions(user, context.requiredPermissions) && + (domainObject.organizationId ? user.school.id === domainObject.organizationId : true); + + return hasPermission; + } +} diff --git a/apps/server/src/modules/authorization/domain/rules/index.ts b/apps/server/src/modules/authorization/domain/rules/index.ts index bd4ffe27a59..b78f43051d0 100644 --- a/apps/server/src/modules/authorization/domain/rules/index.ts +++ b/apps/server/src/modules/authorization/domain/rules/index.ts @@ -14,3 +14,4 @@ export * from './task.rule'; export * from './team.rule'; export * from './user-login-migration.rule'; export * from './user.rule'; +export * from './group.rule'; diff --git a/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts b/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts index 78ef313ade1..5b62f850416 100644 --- a/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/rule-manager.spec.ts @@ -16,6 +16,7 @@ import { TeamRule, UserRule, UserLoginMigrationRule, + GroupRule, } from '../rules'; import { RuleManager } from './rule-manager'; @@ -33,6 +34,7 @@ describe('RuleManager', () => { let boardDoRule: DeepMocked; let contextExternalToolRule: DeepMocked; let userLoginMigrationRule: DeepMocked; + let groupRule: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -42,6 +44,7 @@ describe('RuleManager', () => { RuleManager, { provide: CourseRule, useValue: createMock() }, { provide: CourseGroupRule, useValue: createMock() }, + { provide: GroupRule, useValue: createMock() }, { provide: LessonRule, useValue: createMock() }, { provide: LegacySchoolRule, useValue: createMock() }, { provide: UserRule, useValue: createMock() }, @@ -68,6 +71,7 @@ describe('RuleManager', () => { boardDoRule = await module.get(BoardDoRule); contextExternalToolRule = await module.get(ContextExternalToolRule); userLoginMigrationRule = await module.get(UserLoginMigrationRule); + groupRule = await module.get(GroupRule); }); afterEach(() => { @@ -98,6 +102,7 @@ describe('RuleManager', () => { boardDoRule.isApplicable.mockReturnValueOnce(false); contextExternalToolRule.isApplicable.mockReturnValueOnce(false); userLoginMigrationRule.isApplicable.mockReturnValueOnce(false); + groupRule.isApplicable.mockReturnValueOnce(false); return { user, object, context }; }; @@ -119,6 +124,7 @@ describe('RuleManager', () => { expect(boardDoRule.isApplicable).toBeCalled(); expect(contextExternalToolRule.isApplicable).toBeCalled(); expect(userLoginMigrationRule.isApplicable).toBeCalled(); + expect(groupRule.isApplicable).toBeCalled(); }); it('should return CourseRule', () => { @@ -148,6 +154,7 @@ describe('RuleManager', () => { boardDoRule.isApplicable.mockReturnValueOnce(false); contextExternalToolRule.isApplicable.mockReturnValueOnce(false); userLoginMigrationRule.isApplicable.mockReturnValueOnce(false); + groupRule.isApplicable.mockReturnValueOnce(false); return { user, object, context }; }; @@ -177,6 +184,7 @@ describe('RuleManager', () => { boardDoRule.isApplicable.mockReturnValueOnce(false); contextExternalToolRule.isApplicable.mockReturnValueOnce(false); userLoginMigrationRule.isApplicable.mockReturnValueOnce(false); + groupRule.isApplicable.mockReturnValueOnce(false); return { user, object, context }; }; diff --git a/apps/server/src/modules/authorization/domain/service/rule-manager.ts b/apps/server/src/modules/authorization/domain/service/rule-manager.ts index 77d09f284c2..6e6237d125f 100644 --- a/apps/server/src/modules/authorization/domain/service/rule-manager.ts +++ b/apps/server/src/modules/authorization/domain/service/rule-manager.ts @@ -15,6 +15,7 @@ import { TeamRule, UserLoginMigrationRule, UserRule, + GroupRule, } from '../rules'; @Injectable() @@ -33,7 +34,8 @@ export class RuleManager { private readonly schoolExternalToolRule: SchoolExternalToolRule, private readonly boardDoRule: BoardDoRule, private readonly contextExternalToolRule: ContextExternalToolRule, - private readonly userLoginMigrationRule: UserLoginMigrationRule + private readonly userLoginMigrationRule: UserLoginMigrationRule, + private readonly groupRule: GroupRule ) { this.rules = [ this.courseRule, @@ -48,6 +50,7 @@ export class RuleManager { this.boardDoRule, this.contextExternalToolRule, this.userLoginMigrationRule, + this.groupRule, ]; } diff --git a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts index 3ace7386565..34a49c03a35 100644 --- a/apps/server/src/modules/group/controller/api-test/group.api.spec.ts +++ b/apps/server/src/modules/group/controller/api-test/group.api.spec.ts @@ -15,6 +15,7 @@ import { import { ClassEntity } from '@modules/class/entity'; import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory'; import { ServerTestModule } from '@modules/server'; +import { ObjectId } from 'bson'; import { GroupEntity, GroupEntityTypes } from '../../entity'; import { ClassRootType } from '../../uc/dto/class-root-type'; import { ClassInfoSearchListResponse, ClassSortBy } from '../dto'; @@ -41,7 +42,7 @@ describe('Group (API)', () => { await app.close(); }); - describe('findClassesForSchool', () => { + describe('[GET] /groups/class', () => { describe('when an admin requests a list of classes', () => { const setup = async () => { const school: SchoolEntity = schoolFactory.buildWithId(); @@ -158,4 +159,119 @@ describe('Group (API)', () => { }); }); }); + + describe('[GET] /groups/:groupId', () => { + describe('when authorized user requests a group', () => { + describe('when group exists', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); + + const group: GroupEntity = groupEntityFactory.buildWithId({ + users: [ + { + user: teacherUser, + role: teacherUser.roles[0], + }, + ], + organization: school, + }); + + await em.persistAndFlush([teacherAccount, teacherUser, group]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + loggedInClient, + group, + teacherUser, + }; + }; + + it('should return the group', async () => { + const { loggedInClient, group, teacherUser } = await setup(); + + const response = await loggedInClient.get(`${group.id}`); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ + id: group.id, + name: group.name, + type: group.type, + users: [ + { + id: teacherUser.id, + firstName: teacherUser.firstName, + lastName: teacherUser.lastName, + role: teacherUser.roles[0].name, + }, + ], + externalSource: { + externalId: group.externalSource?.externalId, + systemId: group.externalSource?.system.id, + }, + }); + }); + }); + + describe('when group does not exist', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + + await em.persistAndFlush([teacherAccount, teacherUser]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + loggedInClient, + }; + }; + + it('should return not found', async () => { + const { loggedInClient } = await setup(); + + const response = await loggedInClient.get(`${new ObjectId().toHexString()}`); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body).toEqual({ + code: HttpStatus.NOT_FOUND, + message: 'Not Found', + title: 'Not Found', + type: 'NOT_FOUND', + }); + }); + }); + }); + + describe('when unauthorized user requests a group', () => { + const setup = async () => { + const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent(); + + const group: GroupEntity = groupEntityFactory.buildWithId(); + + await em.persistAndFlush([studentAccount, studentUser, group]); + em.clear(); + + return { + groupId: group.id, + }; + }; + + it('should return unauthorized', async () => { + const { groupId } = await setup(); + + const response = await testApiClient.get(`${groupId}`); + + expect(response.status).toEqual(HttpStatus.UNAUTHORIZED); + expect(response.body).toEqual({ + code: HttpStatus.UNAUTHORIZED, + message: 'Unauthorized', + title: 'Unauthorized', + type: 'UNAUTHORIZED', + }); + }); + }); + }); }); diff --git a/apps/server/src/modules/group/controller/dto/request/group-id-params.ts b/apps/server/src/modules/group/controller/dto/request/group-id-params.ts new file mode 100644 index 00000000000..9423966009f --- /dev/null +++ b/apps/server/src/modules/group/controller/dto/request/group-id-params.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsMongoId } from 'class-validator'; + +export class GroupIdParams { + @IsMongoId() + @ApiProperty({ nullable: false, required: true }) + groupId!: string; +} diff --git a/apps/server/src/modules/group/controller/dto/request/index.ts b/apps/server/src/modules/group/controller/dto/request/index.ts index 2255e9aac09..17ecd658b7d 100644 --- a/apps/server/src/modules/group/controller/dto/request/index.ts +++ b/apps/server/src/modules/group/controller/dto/request/index.ts @@ -1 +1,2 @@ export * from './class-sort-params'; +export * from './group-id-params'; diff --git a/apps/server/src/modules/group/controller/dto/response/external-source.response.ts b/apps/server/src/modules/group/controller/dto/response/external-source.response.ts new file mode 100644 index 00000000000..f03327c8a8c --- /dev/null +++ b/apps/server/src/modules/group/controller/dto/response/external-source.response.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ExternalSourceResponse { + @ApiProperty() + externalId: string; + + @ApiProperty() + systemId: string; + + constructor(props: ExternalSourceResponse) { + this.externalId = props.externalId; + this.systemId = props.systemId; + } +} diff --git a/apps/server/src/modules/group/controller/dto/response/group-type.response.ts b/apps/server/src/modules/group/controller/dto/response/group-type.response.ts new file mode 100644 index 00000000000..54c32148ca1 --- /dev/null +++ b/apps/server/src/modules/group/controller/dto/response/group-type.response.ts @@ -0,0 +1,3 @@ +export enum GroupTypeResponse { + CLASS = 'class', +} diff --git a/apps/server/src/modules/group/controller/dto/response/group-user.response.ts b/apps/server/src/modules/group/controller/dto/response/group-user.response.ts new file mode 100644 index 00000000000..000958c96cf --- /dev/null +++ b/apps/server/src/modules/group/controller/dto/response/group-user.response.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { RoleName } from '@shared/domain'; + +export class GroupUserResponse { + @ApiProperty() + id: string; + + @ApiProperty() + firstName: string; + + @ApiProperty() + lastName: string; + + @ApiProperty({ enum: RoleName }) + role: RoleName; + + constructor(user: GroupUserResponse) { + this.id = user.id; + this.firstName = user.firstName; + this.lastName = user.lastName; + this.role = user.role; + } +} diff --git a/apps/server/src/modules/group/controller/dto/response/group.response.ts b/apps/server/src/modules/group/controller/dto/response/group.response.ts new file mode 100644 index 00000000000..1abb28a8a30 --- /dev/null +++ b/apps/server/src/modules/group/controller/dto/response/group.response.ts @@ -0,0 +1,33 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ExternalSourceResponse } from './external-source.response'; +import { GroupTypeResponse } from './group-type.response'; +import { GroupUserResponse } from './group-user.response'; + +export class GroupResponse { + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty({ enum: GroupTypeResponse }) + type: GroupTypeResponse; + + @ApiProperty({ type: [GroupUserResponse] }) + users: GroupUserResponse[]; + + @ApiPropertyOptional() + externalSource?: ExternalSourceResponse; + + @ApiPropertyOptional() + organizationId?: string; + + constructor(group: GroupResponse) { + this.id = group.id; + this.name = group.name; + this.type = group.type; + this.users = group.users; + this.externalSource = group.externalSource; + this.organizationId = group.organizationId; + } +} diff --git a/apps/server/src/modules/group/controller/dto/response/index.ts b/apps/server/src/modules/group/controller/dto/response/index.ts index 1ec8a62f0d4..9593930f21e 100644 --- a/apps/server/src/modules/group/controller/dto/response/index.ts +++ b/apps/server/src/modules/group/controller/dto/response/index.ts @@ -1,2 +1,6 @@ export * from './class-info.response'; export * from './class-info-search-list.response'; +export * from './external-source.response'; +export * from './group.response'; +export * from './group-type.response'; +export * from './group-user.response'; diff --git a/apps/server/src/modules/group/controller/group.controller.ts b/apps/server/src/modules/group/controller/group.controller.ts index 553d07b0d5c..9e5f4b3b51a 100644 --- a/apps/server/src/modules/group/controller/group.controller.ts +++ b/apps/server/src/modules/group/controller/group.controller.ts @@ -1,12 +1,12 @@ -import { Controller, Get, HttpStatus, Query } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Param, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; import { Page } from '@shared/domain'; import { ErrorResponse } from '@src/core/error/dto'; import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { GroupUc } from '../uc'; -import { ClassInfoDto } from '../uc/dto'; -import { ClassInfoSearchListResponse, ClassSortParams } from './dto'; +import { ClassInfoDto, ResolvedGroupDto } from '../uc/dto'; +import { ClassInfoSearchListResponse, ClassSortParams, GroupIdParams, GroupResponse } from './dto'; import { GroupResponseMapper } from './mapper'; @ApiTags('Group') @@ -42,4 +42,20 @@ export class GroupController { return response; } + + @Get('/:groupId') + @ApiOperation({ summary: 'Get a group by id.' }) + @ApiResponse({ status: HttpStatus.OK, type: GroupResponse }) + @ApiResponse({ status: '4XX', type: ErrorResponse }) + @ApiResponse({ status: '5XX', type: ErrorResponse }) + public async getGroup( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: GroupIdParams + ): Promise { + const group: ResolvedGroupDto = await this.groupUc.getGroup(currentUser.userId, params.groupId); + + const response: GroupResponse = GroupResponseMapper.mapToGroupResponse(group); + + return response; + } } diff --git a/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts b/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts index 5f337eb204c..6efd02d899d 100644 --- a/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts +++ b/apps/server/src/modules/group/controller/mapper/group-response.mapper.ts @@ -1,6 +1,18 @@ import { Page } from '@shared/domain'; -import { ClassInfoDto } from '../../uc/dto'; -import { ClassInfoResponse, ClassInfoSearchListResponse } from '../dto'; +import { GroupTypes } from '../../domain'; +import { ClassInfoDto, ResolvedGroupDto } from '../../uc/dto'; +import { + ClassInfoResponse, + ClassInfoSearchListResponse, + ExternalSourceResponse, + GroupResponse, + GroupTypeResponse, + GroupUserResponse, +} from '../dto'; + +const typeMapping: Record = { + [GroupTypes.CLASS]: GroupTypeResponse.CLASS, +}; export class GroupResponseMapper { static mapToClassInfosToListResponse( @@ -35,4 +47,30 @@ export class GroupResponseMapper { return mapped; } + + static mapToGroupResponse(resolvedGroup: ResolvedGroupDto): GroupResponse { + const mapped: GroupResponse = new GroupResponse({ + id: resolvedGroup.id, + name: resolvedGroup.name, + type: typeMapping[resolvedGroup.type], + externalSource: resolvedGroup.externalSource + ? new ExternalSourceResponse({ + externalId: resolvedGroup.externalSource.externalId, + systemId: resolvedGroup.externalSource.systemId, + }) + : undefined, + users: resolvedGroup.users.map( + (user) => + new GroupUserResponse({ + id: user.user.id as string, + role: user.role.name, + firstName: user.user.firstName, + lastName: user.user.lastName, + }) + ), + organizationId: resolvedGroup.organizationId, + }); + + return mapped; + } } diff --git a/apps/server/src/modules/group/domain/group.ts b/apps/server/src/modules/group/domain/group.ts index 826bbd36b22..3d1f19bc312 100644 --- a/apps/server/src/modules/group/domain/group.ts +++ b/apps/server/src/modules/group/domain/group.ts @@ -38,6 +38,10 @@ export class Group extends DomainObject { return this.props.organizationId; } + get type(): GroupTypes { + return this.props.type; + } + removeUser(user: UserDO): void { this.props.users = this.props.users.filter((groupUser: GroupUser): boolean => groupUser.userId !== user.id); } diff --git a/apps/server/src/modules/group/uc/dto/index.ts b/apps/server/src/modules/group/uc/dto/index.ts index 389a31da162..d795f1c30d3 100644 --- a/apps/server/src/modules/group/uc/dto/index.ts +++ b/apps/server/src/modules/group/uc/dto/index.ts @@ -1,2 +1,3 @@ export * from './class-info.dto'; export * from './resolved-group-user'; +export * from './resolved-group.dto'; diff --git a/apps/server/src/modules/group/uc/dto/resolved-group.dto.ts b/apps/server/src/modules/group/uc/dto/resolved-group.dto.ts new file mode 100644 index 00000000000..4d288f936a0 --- /dev/null +++ b/apps/server/src/modules/group/uc/dto/resolved-group.dto.ts @@ -0,0 +1,26 @@ +import { ExternalSource } from '@shared/domain'; +import { GroupTypes } from '../../domain'; +import { ResolvedGroupUser } from './resolved-group-user'; + +export class ResolvedGroupDto { + id: string; + + name: string; + + type: GroupTypes; + + users: ResolvedGroupUser[]; + + externalSource?: ExternalSource; + + organizationId?: string; + + constructor(group: ResolvedGroupDto) { + this.id = group.id; + this.name = group.name; + this.type = group.type; + this.users = group.users; + this.externalSource = group.externalSource; + this.organizationId = group.organizationId; + } +} diff --git a/apps/server/src/modules/group/uc/group.uc.spec.ts b/apps/server/src/modules/group/uc/group.uc.spec.ts index 8e882c052d3..34cb55a1354 100644 --- a/apps/server/src/modules/group/uc/group.uc.spec.ts +++ b/apps/server/src/modules/group/uc/group.uc.spec.ts @@ -2,6 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { LegacySchoolDo, Page, Permission, SchoolYearEntity, SortOrder, User, UserDO } from '@shared/domain'; import { groupFactory, @@ -22,9 +23,9 @@ import { RoleService } from '@modules/role'; import { RoleDto } from '@modules/role/service/dto/role.dto'; import { SystemDto, SystemService } from '@modules/system'; import { UserService } from '@modules/user'; -import { Group } from '../domain'; +import { Group, GroupTypes } from '../domain'; import { GroupService } from '../service'; -import { ClassInfoDto } from './dto'; +import { ClassInfoDto, ResolvedGroupDto } from './dto'; import { ClassRootType } from './dto/class-root-type'; import { GroupUc } from './group.uc'; @@ -218,7 +219,7 @@ describe('GroupUc', () => { }; }; - it('should check the CLASS_LIST permission', async () => { + it('should check the required permissions', async () => { const { teacherUser, school } = setup(); await uc.findAllClassesForSchool(teacherUser.id, teacherUser.school.id); @@ -228,7 +229,7 @@ describe('GroupUc', () => { school, { action: Action.read, - requiredPermissions: [Permission.CLASS_LIST], + requiredPermissions: [Permission.CLASS_LIST, Permission.GROUP_LIST], } ); }); @@ -340,4 +341,144 @@ describe('GroupUc', () => { }); }); }); + + describe('getGroup', () => { + describe('when the user has no permission', () => { + const setup = () => { + const user: User = userFactory.buildWithId(); + const error = new ForbiddenException(); + + authorizationService.getUserWithPermissions.mockResolvedValue(user); + authorizationService.checkPermission.mockImplementation(() => { + throw error; + }); + + return { + user, + error, + }; + }; + + it('should throw forbidden', async () => { + const { user, error } = setup(); + + const func = () => uc.getGroup(user.id, 'groupId'); + + await expect(func).rejects.toThrow(error); + }); + }); + + describe('when the group is not found', () => { + const setup = () => { + groupService.findById.mockRejectedValue(new NotFoundLoggableException(Group.name, 'id', 'groupId')); + const { teacherUser } = UserAndAccountTestFactory.buildTeacher(); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(teacherUser); + + return { + teacherId: teacherUser.id, + }; + }; + + it('should throw not found', async () => { + const { teacherId } = setup(); + + const func = () => uc.getGroup(teacherId, 'groupId'); + + await expect(func).rejects.toThrow(NotFoundLoggableException); + }); + }); + + describe('when the group is found', () => { + const setup = () => { + const { teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { studentUser } = UserAndAccountTestFactory.buildStudent(); + const group: Group = groupFactory.build({ + users: [ + { userId: teacherUser.id, roleId: teacherUser.roles[0].id }, + { userId: studentUser.id, roleId: studentUser.roles[0].id }, + ], + }); + const teacherRole: RoleDto = roleDtoFactory.build({ + id: teacherUser.roles[0].id, + name: teacherUser.roles[0].name, + }); + const studentRole: RoleDto = roleDtoFactory.build({ + id: studentUser.roles[0].id, + name: studentUser.roles[0].name, + }); + const teacherUserDo: UserDO = userDoFactory.build({ + id: teacherUser.id, + firstName: teacherUser.firstName, + lastName: teacherUser.lastName, + email: teacherUser.email, + roles: [{ id: teacherUser.roles[0].id, name: teacherUser.roles[0].name }], + }); + const studentUserDo: UserDO = userDoFactory.build({ + id: studentUser.id, + firstName: teacherUser.firstName, + lastName: studentUser.lastName, + email: studentUser.email, + roles: [{ id: studentUser.roles[0].id, name: studentUser.roles[0].name }], + }); + + groupService.findById.mockResolvedValueOnce(group); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(teacherUser); + userService.findById.mockResolvedValueOnce(teacherUserDo); + roleService.findById.mockResolvedValueOnce(teacherRole); + userService.findById.mockResolvedValueOnce(studentUserDo); + roleService.findById.mockResolvedValueOnce(studentRole); + + return { + teacherId: teacherUser.id, + teacherUser, + studentUser, + group, + expectedExternalId: group.externalSource?.externalId as string, + expectedSystemId: group.externalSource?.systemId as string, + }; + }; + + it('should return the resolved group', async () => { + const { teacherId, teacherUser, studentUser, group, expectedExternalId, expectedSystemId } = setup(); + + const result: ResolvedGroupDto = await uc.getGroup(teacherId, group.id); + + expect(result).toMatchObject({ + id: group.id, + name: group.name, + type: GroupTypes.CLASS, + externalSource: { + externalId: expectedExternalId, + systemId: expectedSystemId, + }, + users: [ + { + user: { + id: teacherUser.id, + firstName: teacherUser.firstName, + lastName: teacherUser.lastName, + email: teacherUser.email, + }, + role: { + id: teacherUser.roles[0].id, + name: teacherUser.roles[0].name, + }, + }, + { + user: { + id: studentUser.id, + firstName: studentUser.firstName, + lastName: studentUser.lastName, + email: studentUser.email, + }, + role: { + id: studentUser.roles[0].id, + name: studentUser.roles[0].name, + }, + }, + ], + }); + }); + }); + }); }); diff --git a/apps/server/src/modules/group/uc/group.uc.ts b/apps/server/src/modules/group/uc/group.uc.ts index 23cac984a6d..2421e444e73 100644 --- a/apps/server/src/modules/group/uc/group.uc.ts +++ b/apps/server/src/modules/group/uc/group.uc.ts @@ -11,7 +11,7 @@ import { UserService } from '@modules/user'; import { Group, GroupUser } from '../domain'; import { GroupService } from '../service'; import { SortHelper } from '../util'; -import { ClassInfoDto, ResolvedGroupUser } from './dto'; +import { ClassInfoDto, ResolvedGroupDto, ResolvedGroupUser } from './dto'; import { GroupUcMapper } from './mapper/group-uc.mapper'; @Injectable() @@ -38,7 +38,11 @@ export class GroupUc { const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId); const user: User = await this.authorizationService.getUserWithPermissions(userId); - this.authorizationService.checkPermission(user, school, AuthorizationContextBuilder.read([Permission.CLASS_LIST])); + this.authorizationService.checkPermission( + user, + school, + AuthorizationContextBuilder.read([Permission.CLASS_LIST, Permission.GROUP_LIST]) + ); const combinedClassInfo: ClassInfoDto[] = await this.findCombinedClassListForSchool(schoolId); @@ -153,4 +157,24 @@ export class GroupUc { return page; } + + public async getGroup(userId: EntityId, groupId: EntityId): Promise { + const group: Group = await this.groupService.findById(groupId); + + await this.checkPermission(userId, group); + + const resolvedUsers: ResolvedGroupUser[] = await this.findUsersForGroup(group); + const resolvedGroup: ResolvedGroupDto = GroupUcMapper.mapToResolvedGroupDto(group, resolvedUsers); + + return resolvedGroup; + } + + private async checkPermission(userId: EntityId, group: Group): Promise { + const user: User = await this.authorizationService.getUserWithPermissions(userId); + return this.authorizationService.checkPermission( + user, + group, + AuthorizationContextBuilder.read([Permission.GROUP_VIEW]) + ); + } } diff --git a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts index ebeba60117e..5ac11f0e0b6 100644 --- a/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts +++ b/apps/server/src/modules/group/uc/mapper/group-uc.mapper.ts @@ -2,7 +2,7 @@ import { RoleName, SchoolYearEntity, UserDO } from '@shared/domain'; import { Class } from '@modules/class/domain'; import { SystemDto } from '@modules/system'; import { Group } from '../../domain'; -import { ClassInfoDto, ResolvedGroupUser } from '../dto'; +import { ClassInfoDto, ResolvedGroupDto, ResolvedGroupUser } from '../dto'; import { ClassRootType } from '../dto/class-root-type'; export class GroupUcMapper { @@ -40,4 +40,16 @@ export class GroupUcMapper { return mapped; } + + public static mapToResolvedGroupDto(group: Group, resolvedGroupUsers: ResolvedGroupUser[]): ResolvedGroupDto { + const mapped: ResolvedGroupDto = new ResolvedGroupDto({ + id: group.id, + name: group.name, + type: group.type, + externalSource: group.externalSource, + users: resolvedGroupUsers, + }); + + return mapped; + } } diff --git a/apps/server/src/shared/domain/interface/permission.enum.ts b/apps/server/src/shared/domain/interface/permission.enum.ts index 3d00ef24be2..4512b95de7f 100644 --- a/apps/server/src/shared/domain/interface/permission.enum.ts +++ b/apps/server/src/shared/domain/interface/permission.enum.ts @@ -54,6 +54,8 @@ export enum Permission { FILE_MOVE = 'FILE_MOVE', FOLDER_CREATE = 'FOLDER_CREATE', FOLDER_DELETE = 'FOLDER_DELETE', + GROUP_LIST = 'GROUP_LIST', + GROUP_VIEW = 'GROUP_VIEW', HELPDESK_CREATE = 'HELPDESK_CREATE', HELPDESK_EDIT = 'HELPDESK_EDIT', HELPDESK_VIEW = 'HELPDESK_VIEW', diff --git a/apps/server/src/shared/domain/rules/index.ts b/apps/server/src/shared/domain/rules/index.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/server/src/shared/testing/user-role-permissions.ts b/apps/server/src/shared/testing/user-role-permissions.ts index c2a75fe478d..cfd38cea3a3 100644 --- a/apps/server/src/shared/testing/user-role-permissions.ts +++ b/apps/server/src/shared/testing/user-role-permissions.ts @@ -53,6 +53,7 @@ export const userPermissions = [ Permission.CLASS_VIEW, Permission.COURSE_VIEW, Permission.LERNSTORE_VIEW, + Permission.GROUP_VIEW, ] as Permission[]; export const studentPermissions = [ @@ -73,6 +74,7 @@ const sharedAdminPermissions = [ Permission.COURSE_CREATE, Permission.COURSE_EDIT, Permission.COURSE_REMOVE, + Permission.GROUP_LIST, Permission.NEWS_CREATE, Permission.NEWS_EDIT, Permission.STUDENT_SKIP_REGISTRATION, diff --git a/backup/setup/migrations.json b/backup/setup/migrations.json index 4cec0040475..8292d8fc62f 100644 --- a/backup/setup/migrations.json +++ b/backup/setup/migrations.json @@ -317,5 +317,16 @@ "$date": "2023-10-11T10:40:18.782Z" }, "__v": 0 + }, + { + "_id": { + "$oid": "652ea0196ddf74176cb57561" + }, + "state": "up", + "name": "add-group-view-and-list-permission", + "createdAt": { + "$date": "2023-10-17T14:38:44.886Z" + }, + "__v": 0 } ] diff --git a/backup/setup/roles.json b/backup/setup/roles.json index 3d7909a84da..0ad460fc526 100644 --- a/backup/setup/roles.json +++ b/backup/setup/roles.json @@ -6,7 +6,7 @@ "name": "user", "roles": [], "updatedAt": { - "$date": "2023-05-16T11:11:21.297Z" + "$date": "2023-10-18T05:58:52.716Z" }, "createdAt": { "$date": "2017-01-01T00:06:37.148Z" @@ -58,7 +58,8 @@ "TOOL_VIEW", "TOPIC_VIEW", "NEXTCLOUD_USER", - "CONTEXT_TOOL_USER" + "CONTEXT_TOOL_USER", + "GROUP_VIEW" ], "__v": 0 }, @@ -68,7 +69,7 @@ }, "name": "administrator", "updatedAt": { - "$date": "2023-09-04T12:13:44.069Z" + "$date": "2023-10-18T05:58:52.729Z" }, "createdAt": { "$date": "2017-01-01T00:06:37.148Z" @@ -132,7 +133,8 @@ "SCHOOL_TOOL_ADMIN", "USER_LOGIN_MIGRATION_ADMIN", "START_MEETING", - "JOIN_MEETING" + "JOIN_MEETING", + "GROUP_LIST" ], "__v": 2 }, @@ -142,7 +144,7 @@ }, "name": "superhero", "updatedAt": { - "$date": "2022-09-22T13:14:23.839Z" + "$date": "2023-10-18T05:58:52.729Z" }, "createdAt": { "$date": "2017-01-01T00:06:37.148Z" @@ -188,7 +190,8 @@ "TEAM_EDIT", "TOOL_CREATE", "TOOL_EDIT", - "YEARS_EDIT" + "YEARS_EDIT", + "GROUP_LIST" ], "__v": 2 }, @@ -198,7 +201,7 @@ }, "name": "teacher", "updatedAt": { - "$date": "2023-09-04T12:54:47.237Z" + "$date": "2023-10-18T05:59:16.621Z" }, "createdAt": { "$date": "2017-01-01T00:06:37.148Z" @@ -245,7 +248,8 @@ "HOMEWORK_CREATE", "HOMEWORK_EDIT", "CONTEXT_TOOL_ADMIN", - "JOIN_MEETING" + "JOIN_MEETING", + "GROUP_LIST" ], "__v": 2 }, diff --git a/migrations/1697553524886-add-group-view-and-list-permission.js b/migrations/1697553524886-add-group-view-and-list-permission.js new file mode 100644 index 00000000000..2bf46755aed --- /dev/null +++ b/migrations/1697553524886-add-group-view-and-list-permission.js @@ -0,0 +1,100 @@ +const mongoose = require('mongoose'); +// eslint-disable-next-line no-unused-vars +const { info } = require('winston'); +const { alert, error } = require('../src/logger'); + +const { connect, close } = require('../src/utils/database'); + +const Roles = mongoose.model( + 'roles2023101716394', + new mongoose.Schema( + { + name: { type: String, required: true }, + permissions: [{ type: String }], + }, + { + timestamps: true, + } + ), + 'roles' +); + +module.exports = { + up: async function up() { + // eslint-disable-next-line no-process-env + if (process.env.SC_THEME !== 'n21') { + info('Permissions GROUP_VIEW and GROUP_LIST will not be added for this instance.'); + return; + } + + await connect(); + + const groupViewPermission = await Roles.updateOne( + { name: 'user' }, + { + $addToSet: { + permissions: { + $each: ['GROUP_VIEW'], + }, + }, + } + ).exec(); + if (groupViewPermission) { + alert(`Permission GROUP_VIEW added to role user`); + } + + const groupListPermission = await Roles.updateMany( + { name: { $in: ['teacher', 'administrator', 'superhero'] } }, + { + $addToSet: { + permissions: { + $each: ['GROUP_LIST'], + }, + }, + } + ).exec(); + if (groupListPermission) { + alert(`Permission GROUP_LIST added to role user and administrator`); + } + + await close(); + }, + + down: async function down() { + // eslint-disable-next-line no-process-env + if (process.env.SC_THEME !== 'n21') { + info('Permissions GROUP_VIEW and GROUP_LIST will not be removed for this instance.'); + return; + } + + await connect(); + + const groupViewRollback = await Roles.updateOne( + { name: 'user' }, + { + $pull: { + permissions: 'GROUP_VIEW', + }, + } + ).exec(); + + if (groupViewRollback) { + alert(`Rollback: Removed permission GROUP_VIEW from role user`); + } + + const groupListRollback = await Roles.updateMany( + { name: { $in: ['teacher', 'administrator', 'superhero'] } }, + { + $pull: { + permissions: 'GROUP_LIST', + }, + } + ).exec(); + + if (groupListRollback) { + alert(`Rollback: Removed permission GROUP_LIST from roles teacher and administrator`); + } + + await close(); + }, +}; From bbcb506fc0e35755043502f48a61cb269c57a171 Mon Sep 17 00:00:00 2001 From: MBergCap <111343628+MBergCap@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:30:46 +0200 Subject: [PATCH 09/19] N21-1293 remove groups from course (#4478) * add group removel * N21-1293 add group to seed data --- backup/setup/groups.json | 148 +++++++++++++++++------ src/services/user-group/hooks/courses.js | 30 ++++- 2 files changed, 141 insertions(+), 37 deletions(-) diff --git a/backup/setup/groups.json b/backup/setup/groups.json index a682580f824..36e5e226896 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,37 +1,115 @@ [ - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group", - "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - } + { + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group", + "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group1", + "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group2", + "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + }, + { + "user": { + "$oid": "5fa30079b229544f2c6969ff" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + } ] diff --git a/src/services/user-group/hooks/courses.js b/src/services/user-group/hooks/courses.js index 41e2014feb0..92a5b987a7c 100644 --- a/src/services/user-group/hooks/courses.js +++ b/src/services/user-group/hooks/courses.js @@ -67,16 +67,42 @@ const addWholeClassToCourse = async (hook) => { * @param hook - contains and request body */ const deleteWholeClassFromCourse = (hook) => { + const { app } = hook; const requestBody = hook.data; const courseId = hook.id; - if (requestBody.classIds === undefined && requestBody.user === undefined) { + if (requestBody.classIds === undefined && requestBody.user === undefined && requestBody.groupIds === undefined) { return hook; } return CourseModel.findById(courseId) .exec() - .then((course) => { + .then(async (course) => { if (!course) return hook; + const removedGroups = _.differenceBy(course.groupIds, requestBody.groupIds, (v) => JSON.stringify(v)); + if (Configuration.get('FEATURE_GROUPS_IN_COURSE_ENABLED') && removedGroups.length > 0) { + await Promise.all( + removedGroups.map((groupId) => + app + .service('nest-group-service') + .findById(groupId) + .then((group) => group.users) + ) + ).then(async (groupUsers) => { + // flatten deep arrays and remove duplicates + const userIds = _.flattenDeep(groupUsers).map((groupUser) => groupUser.userId); + const uniqueUserIds = _.uniqWith(userIds, (a, b) => a === b); + + await CourseModel.update( + { _id: course._id }, + { $pull: { userIds: { $in: uniqueUserIds } } }, + { multi: true } + ).exec(); + hook.data.userIds = hook.data.userIds.filter((value) => !uniqueUserIds.some((id) => equalIds(id, value))); + + return undefined; + }); + } + const removedClasses = _.differenceBy(course.classIds, requestBody.classIds, (v) => JSON.stringify(v)); if (removedClasses.length < 1) return hook; return Promise.all( From 504e8d19c9da28341b1c59ad0974769db1896515 Mon Sep 17 00:00:00 2001 From: WahlMartin <132356096+WahlMartin@users.noreply.github.com> Date: Mon, 23 Oct 2023 14:16:20 +0200 Subject: [PATCH 10/19] Revert "EW-619 DataPort review testing part (#4442)" (#4490) This reverts commit a4364ecd01056abea5cdae1832f4293f32fa285c. --- .../controller/account.controller.spec.ts | 24 + .../account/controller/account.controller.ts | 6 +- .../controller/api-test/account.api.spec.ts | 779 +--- .../controller/dto/password-pattern.ts | 1 + .../account-entity-to-dto.mapper.spec.ts | 139 +- .../mapper/account-entity-to-dto.mapper.ts | 2 - .../account-idm-to-dto.mapper.db.spec.ts | 62 +- .../account-idm-to-dto.mapper.idm.spec.ts | 75 +- .../mapper/account-response.mapper.spec.ts | 81 +- .../account/mapper/account-response.mapper.ts | 1 - .../repo/account.repo.integration.spec.ts | 412 +- .../src/modules/account/repo/account.repo.ts | 5 - .../src/modules/account/review-comments.md | 12 - .../services/account-db.service.spec.ts | 995 ++-- .../account/services/account-db.service.ts | 21 +- .../account-idm.service.integration.spec.ts | 172 +- .../services/account-idm.service.spec.ts | 315 +- .../account/services/account-idm.service.ts | 9 +- .../services/account.service.abstract.ts | 3 - .../account.service.integration.spec.ts | 201 +- .../account/services/account.service.spec.ts | 710 ++- .../account/services/account.service.ts | 10 +- .../account.validation.service.spec.ts | 586 +-- .../services/account.validation.service.ts | 6 +- .../account/services/dto/account.dto.ts | 1 - .../src/modules/account/uc/account.uc.spec.ts | 4004 +++++------------ .../src/modules/account/uc/account.uc.ts | 18 +- .../shared/testing/factory/account.factory.ts | 32 +- 28 files changed, 2739 insertions(+), 5943 deletions(-) create mode 100644 apps/server/src/modules/account/controller/account.controller.spec.ts delete mode 100644 apps/server/src/modules/account/review-comments.md diff --git a/apps/server/src/modules/account/controller/account.controller.spec.ts b/apps/server/src/modules/account/controller/account.controller.spec.ts new file mode 100644 index 00000000000..ef15672edc5 --- /dev/null +++ b/apps/server/src/modules/account/controller/account.controller.spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AccountController } from './account.controller'; +import { AccountUc } from '../uc/account.uc'; + +describe('account.controller', () => { + let module: TestingModule; + let controller: AccountController; + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + AccountController, + { + provide: AccountUc, + useValue: {}, + }, + ], + }).compile(); + controller = module.get(AccountController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/server/src/modules/account/controller/account.controller.ts b/apps/server/src/modules/account/controller/account.controller.ts index 2f45de2403e..2256cb9fc90 100644 --- a/apps/server/src/modules/account/controller/account.controller.ts +++ b/apps/server/src/modules/account/controller/account.controller.ts @@ -1,8 +1,8 @@ import { Body, Controller, Delete, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; -import { ICurrentUser } from '@modules/authentication'; -import { Authenticate, CurrentUser } from '@modules/authentication/decorator/auth.decorator'; +import { ICurrentUser } from '@src/modules/authentication'; import { AccountUc } from '../uc/account.uc'; import { AccountByIdBodyParams, @@ -33,8 +33,6 @@ export class AccountController { @Query() query: AccountSearchQueryParams ): Promise { return this.accountUc.searchAccounts(currentUser, query); - - // TODO: mapping from domain to api dto should be a responsability of the controller (also every other function here) } @Get(':id') diff --git a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts index 44d47a3c3d2..fefdb006bf8 100644 --- a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts +++ b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts @@ -1,642 +1,315 @@ -import { EntityManager } from '@mikro-orm/mongodb'; -import { INestApplication } from '@nestjs/common'; +import { EntityManager } from '@mikro-orm/core'; +import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, RoleName, User } from '@shared/domain'; -import { - accountFactory, - roleFactory, - schoolFactory, - userFactory, - TestApiClient, - cleanupCollections, -} from '@shared/testing'; +import { ICurrentUser } from '@src/modules/authentication'; +import { accountFactory, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { AccountByIdBodyParams, AccountSearchQueryParams, AccountSearchType, PatchMyAccountParams, PatchMyPasswordParams, -} from '@modules/account/controller/dto'; -import { ServerTestModule } from '@modules/server/server.module'; +} from '@src/modules/account/controller/dto'; +import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@src/modules/server/server.module'; +import { Request } from 'express'; +import request from 'supertest'; describe('Account Controller (API)', () => { const basePath = '/account'; let app: INestApplication; let em: EntityManager; - let testApiClient: TestApiClient; + + let adminAccount: Account; + let teacherAccount: Account; + let studentAccount: Account; + let superheroAccount: Account; + + let adminUser: User; + let teacherUser: User; + let studentUser: User; + let superheroUser: User; + + let currentUser: ICurrentUser; const defaultPassword = 'DummyPasswd!1'; const defaultPasswordHash = '$2a$10$/DsztV5o6P5piW2eWJsxw.4nHovmJGBA.QNwiTmuZ/uvUc40b.Uhu'; - const mapUserToAccount = (user: User): Account => - accountFactory.buildWithId({ - userId: user.id, - username: user.email, - password: defaultPasswordHash, + const setup = async () => { + const school = schoolFactory.buildWithId(); + + const adminRoles = roleFactory.build({ + name: RoleName.ADMINISTRATOR, + permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], }); + const teacherRoles = roleFactory.build({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] }); + const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); + const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); + + adminUser = userFactory.buildWithId({ school, roles: [adminRoles] }); + teacherUser = userFactory.buildWithId({ school, roles: [teacherRoles] }); + studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); + superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); + + const mapUserToAccount = (user: User): Account => + accountFactory.buildWithId({ + userId: user.id, + username: user.email, + password: defaultPasswordHash, + }); + adminAccount = mapUserToAccount(adminUser); + teacherAccount = mapUserToAccount(teacherUser); + studentAccount = mapUserToAccount(studentUser); + superheroAccount = mapUserToAccount(superheroUser); + + em.persist(school); + em.persist([adminRoles, teacherRoles, studentRoles, superheroRoles]); + em.persist([adminUser, teacherUser, studentUser, superheroUser]); + em.persist([adminAccount, teacherAccount, studentAccount, superheroAccount]); + await em.flush(); + }; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ServerTestModule], - }).compile(); + }) + .overrideGuard(JwtAuthGuard) + .useValue({ + canActivate(context: ExecutionContext) { + const req: Request = context.switchToHttp().getRequest(); + req.user = currentUser; + return true; + }, + }) + .compile(); app = moduleFixture.createNestApplication(); await app.init(); em = app.get(EntityManager); - testApiClient = new TestApiClient(app, basePath); }); beforeEach(async () => { - await cleanupCollections(em); + await setup(); }); afterAll(async () => { - await cleanupCollections(em); + // await cleanupCollections(em); await app.close(); }); describe('[PATCH] me/password', () => { - describe('When patching with a valid password', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const studentAccount = mapUserToAccount(studentUser); - - em.persist([school, studentRoles, studentUser, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(studentAccount); - - const passwordPatchParams: PatchMyPasswordParams = { - password: 'Valid12$', - confirmPassword: 'Valid12$', - }; - - return { passwordPatchParams, loggedInClient, studentAccount }; + it(`should update the current user's (temporary) password`, async () => { + currentUser = mapUserToCurrentUser(studentUser, studentAccount); + const params: PatchMyPasswordParams = { + password: 'Valid12$', + confirmPassword: 'Valid12$', }; + await request(app.getHttpServer()) // + .patch(`${basePath}/me/password`) + .send(params) + .expect(200); - it(`should update the current user's (temporary) password`, async () => { - const { passwordPatchParams, loggedInClient, studentAccount } = await setup(); - - await loggedInClient.patch('/me/password', passwordPatchParams).expect(200); - - const updatedAccount = await em.findOneOrFail(Account, studentAccount.id); - expect(updatedAccount.password).not.toEqual(defaultPasswordHash); - }); + const updatedAccount = await em.findOneOrFail(Account, studentAccount.id); + expect(updatedAccount.password).not.toEqual(defaultPasswordHash); }); - - describe('When using a weak password', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const studentAccount = mapUserToAccount(studentUser); - - em.persist([school, studentRoles, studentUser, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(studentAccount); - - const passwordPatchParams: PatchMyPasswordParams = { - password: 'weak', - confirmPassword: 'weak', - }; - - return { passwordPatchParams, loggedInClient }; + it('should reject if new password is weak', async () => { + currentUser = mapUserToCurrentUser(studentUser, studentAccount); + const params: PatchMyPasswordParams = { + password: 'weak', + confirmPassword: 'weak', }; - - it('should reject the password change', async () => { - const { passwordPatchParams, loggedInClient } = await setup(); - - await loggedInClient.patch('/me/password', passwordPatchParams).expect(400); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/me/password`) + .send(params) + .expect(400); }); }); describe('[PATCH] me', () => { - describe('When patching the account with account info', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const studentAccount = mapUserToAccount(studentUser); - - em.persist([school, studentRoles, studentUser, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(studentAccount); - - const newEmailValue = 'new@mail.com'; - - const patchMyAccountParams: PatchMyAccountParams = { - passwordOld: defaultPassword, - email: newEmailValue, - }; - return { newEmailValue, patchMyAccountParams, loggedInClient, studentAccount }; + it(`should update a users account`, async () => { + const newEmailValue = 'new@mail.com'; + currentUser = mapUserToCurrentUser(studentUser, studentAccount); + const params: PatchMyAccountParams = { + passwordOld: defaultPassword, + email: newEmailValue, }; - it(`should update a users account`, async () => { - const { newEmailValue, patchMyAccountParams, loggedInClient, studentAccount } = await setup(); - - await loggedInClient.patch('/me', patchMyAccountParams).expect(200); - - const updatedAccount = await em.findOneOrFail(Account, studentAccount.id); - expect(updatedAccount.username).toEqual(newEmailValue); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/me`) + .send(params) + .expect(200); + const updatedAccount = await em.findOneOrFail(Account, studentAccount.id); + expect(updatedAccount.username).toEqual(newEmailValue); }); - describe('When patching with a not valid email', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const studentAccount = mapUserToAccount(studentUser); - - em.persist([school, studentRoles, studentUser, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(studentAccount); - - const newEmailValue = 'new@mail.com'; - - const patchMyAccountParams: PatchMyAccountParams = { - passwordOld: defaultPassword, - email: 'invalid', - }; - return { newEmailValue, patchMyAccountParams, loggedInClient }; + it('should reject if new email is not valid', async () => { + currentUser = mapUserToCurrentUser(studentUser, studentAccount); + const params: PatchMyAccountParams = { + passwordOld: defaultPassword, + email: 'invalid', }; - - it('should reject patch request', async () => { - const { patchMyAccountParams, loggedInClient } = await setup(); - - await loggedInClient.patch('/me', patchMyAccountParams).expect(400); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/me`) + .send(params) + .expect(400); }); }); describe('[GET]', () => { - describe('When searching with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const query: AccountSearchQueryParams = { - type: AccountSearchType.USER_ID, - value: studentUser.id, - skip: 5, - limit: 5, - }; - - return { query, loggedInClient }; + it('should search for user id', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + const query: AccountSearchQueryParams = { + type: AccountSearchType.USER_ID, + value: studentUser.id, + skip: 5, + limit: 5, }; - it('should successfully search for user id', async () => { - const { query, loggedInClient } = await setup(); - - await loggedInClient.get().query(query).send().expect(200); - }); + await request(app.getHttpServer()) // + .get(`${basePath}`) + .query(query) + .send() + .expect(200); }); - // If skip is too big, just return an empty list. // We testing it here, because we are mocking the database in the use case unit tests // and for realistic behavior we need database. - describe('When searching with a superhero user with large skip', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const query: AccountSearchQueryParams = { - type: AccountSearchType.USER_ID, - value: studentUser.id, - skip: 50000, - limit: 5, - }; - - return { query, loggedInClient }; + it('should search for user id with large skip', async () => { + currentUser = mapUserToCurrentUser(superheroUser); + const query: AccountSearchQueryParams = { + type: AccountSearchType.USER_ID, + value: studentUser.id, + skip: 50000, + limit: 5, }; - it('should search for user id', async () => { - const { query, loggedInClient } = await setup(); - - await loggedInClient.get().query(query).send().expect(200); - }); + await request(app.getHttpServer()) // + .get(`${basePath}`) + .query(query) + .send() + .expect(200); }); - - describe('When searching with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const query: AccountSearchQueryParams = { - type: AccountSearchType.USERNAME, - value: '', - skip: 5, - limit: 5, - }; - - return { query, loggedInClient }; + it('should search for user name', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + const query: AccountSearchQueryParams = { + type: AccountSearchType.USERNAME, + value: '', + skip: 5, + limit: 5, }; - it('should search for username', async () => { - const { query, loggedInClient } = await setup(); - - await loggedInClient.get().query(query).send().expect(200); - }); + await request(app.getHttpServer()) // + .get(`${basePath}`) + .query(query) + .send() + .expect(200); }); - - describe('When searching with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const query: AccountSearchQueryParams = { - type: '' as AccountSearchType, - value: '', - skip: 5, - limit: 5, - }; - - return { query, loggedInClient }; + it('should reject if type is unknown', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + const query: AccountSearchQueryParams = { + type: '' as AccountSearchType, + value: '', + skip: 5, + limit: 5, }; - - it('should reject if type is unknown', async () => { - const { query, loggedInClient } = await setup(); - - await loggedInClient.get().query(query).send().expect(400); - }); + await request(app.getHttpServer()) // + .get(`${basePath}`) + .query(query) + .send() + .expect(400); }); - describe('When searching with an admin user (not authorized)', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const adminRoles = roleFactory.build({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - - const adminUser = userFactory.buildWithId({ school, roles: [adminRoles] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - - const adminAccount = mapUserToAccount(adminUser); - const studentAccount = mapUserToAccount(studentUser); - - em.persist(school); - em.persist([adminRoles, studentRoles]); - em.persist([adminUser, studentUser]); - em.persist([adminAccount, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(adminAccount); - - const query: AccountSearchQueryParams = { - type: AccountSearchType.USERNAME, - value: '', - skip: 5, - limit: 5, - }; - - return { query, loggedInClient, studentAccount }; + it('should reject if user is not authorized', async () => { + currentUser = mapUserToCurrentUser(adminUser, adminAccount); + const query: AccountSearchQueryParams = { + type: AccountSearchType.USERNAME, + value: '', + skip: 5, + limit: 5, }; - - it('should reject search for user', async () => { - const { query, loggedInClient } = await setup(); - - await loggedInClient.get().query(query).send().expect(403); - }); + await request(app.getHttpServer()) // + .get(`${basePath}`) + .query(query) + .send() + .expect(403); }); }); describe('[GET] :id', () => { - describe('When searching with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - return { loggedInClient, studentAccount }; - }; - it('should return account for account id', async () => { - const { loggedInClient, studentAccount } = await setup(); - await loggedInClient.get(`/${studentAccount.id}`).expect(200); - }); + it('should return account for account id', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + await request(app.getHttpServer()) // + .get(`${basePath}/${studentAccount.id}`) + .expect(200); }); - - describe('When searching with a not authorized user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const adminRoles = roleFactory.build({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - - const adminUser = userFactory.buildWithId({ school, roles: [adminRoles] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - - const adminAccount = mapUserToAccount(adminUser); - const studentAccount = mapUserToAccount(studentUser); - - em.persist(school); - em.persist([adminRoles, studentRoles]); - em.persist([adminUser, studentUser]); - em.persist([adminAccount, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(adminAccount); - - return { loggedInClient, studentAccount }; - }; - it('should reject request', async () => { - const { loggedInClient, studentAccount } = await setup(); - await loggedInClient.get(`/${studentAccount.id}`).expect(403); - }); + it('should reject if user is not a authorized', async () => { + currentUser = mapUserToCurrentUser(adminUser, adminAccount); + await request(app.getHttpServer()) // + .get(`${basePath}/${studentAccount.id}`) + .expect(403); }); - - describe('When searching with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist([school, superheroRoles, superheroUser, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - return { loggedInClient }; - }; - - it('should reject not existing account id', async () => { - const { loggedInClient } = await setup(); - await loggedInClient.get(`/000000000000000000000000`).expect(404); - }); + it('should reject not existing account id', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + await request(app.getHttpServer()) // + .get(`${basePath}/000000000000000000000000`) + .expect(404); }); }); describe('[PATCH] :id', () => { - describe('When using a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const body: AccountByIdBodyParams = { - password: defaultPassword, - username: studentAccount.username, - activated: true, - }; - - return { body, loggedInClient, studentAccount }; + it('should update account', async () => { + currentUser = mapUserToCurrentUser(superheroUser, superheroAccount); + const body: AccountByIdBodyParams = { + password: defaultPassword, + username: studentAccount.username, + activated: true, }; - - it('should update account', async () => { - const { body, loggedInClient, studentAccount } = await setup(); - - await loggedInClient.patch(`/${studentAccount.id}`, body).expect(200); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/${studentAccount.id}`) + .send(body) + .expect(200); }); - - describe('When the user is not authorized', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const studentAccount = mapUserToAccount(studentUser); - - em.persist([school, studentRoles, studentUser, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(studentAccount); - - const body: AccountByIdBodyParams = { - password: defaultPassword, - username: studentAccount.username, - activated: true, - }; - - return { body, loggedInClient, studentAccount }; + it('should reject if user is not authorized', async () => { + currentUser = mapUserToCurrentUser(studentUser, studentAccount); + const body: AccountByIdBodyParams = { + password: defaultPassword, + username: studentAccount.username, + activated: true, }; - it('should reject update request', async () => { - const { body, loggedInClient, studentAccount } = await setup(); - - await loggedInClient.patch(`/${studentAccount.id}`, body).expect(403); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/${studentAccount.id}`) + .send(body) + .expect(403); }); - - describe('When updating with a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - const body: AccountByIdBodyParams = { - password: defaultPassword, - username: studentAccount.username, - activated: true, - }; - - return { body, loggedInClient }; + it('should reject not existing account id', async () => { + currentUser = mapUserToCurrentUser(superheroUser, studentAccount); + const body: AccountByIdBodyParams = { + password: defaultPassword, + username: studentAccount.username, + activated: true, }; - it('should reject not existing account id', async () => { - const { body, loggedInClient } = await setup(); - await loggedInClient.patch('/000000000000000000000000', body).expect(404); - }); + await request(app.getHttpServer()) // + .patch(`${basePath}/000000000000000000000000`) + .send(body) + .expect(404); }); }); describe('[DELETE] :id', () => { - describe('When using a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - - const studentAccount = mapUserToAccount(studentUser); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist(school); - em.persist([studentRoles, superheroRoles]); - em.persist([studentUser, superheroUser]); - em.persist([studentAccount, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - return { loggedInClient, studentAccount }; - }; - it('should delete account', async () => { - const { loggedInClient, studentAccount } = await setup(); - await loggedInClient.delete(`/${studentAccount.id}`).expect(200); - }); + it('should delete account', async () => { + currentUser = mapUserToCurrentUser(superheroUser, studentAccount); + await request(app.getHttpServer()) // + .delete(`${basePath}/${studentAccount.id}`) + .expect(200); }); - - describe('When using a not authorized (admin) user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - - const adminRoles = roleFactory.build({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }); - const studentRoles = roleFactory.build({ name: RoleName.STUDENT, permissions: [] }); - - const adminUser = userFactory.buildWithId({ school, roles: [adminRoles] }); - const studentUser = userFactory.buildWithId({ school, roles: [studentRoles] }); - - const adminAccount = mapUserToAccount(adminUser); - const studentAccount = mapUserToAccount(studentUser); - - em.persist(school); - em.persist([adminRoles, studentRoles]); - em.persist([adminUser, studentUser]); - em.persist([adminAccount, studentAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(adminAccount); - - return { loggedInClient, studentAccount }; - }; - - it('should reject delete request', async () => { - const { loggedInClient, studentAccount } = await setup(); - await loggedInClient.delete(`/${studentAccount.id}`).expect(403); - }); + it('should reject if user is not a authorized', async () => { + currentUser = mapUserToCurrentUser(adminUser, adminAccount); + await request(app.getHttpServer()) // + .delete(`${basePath}/${studentAccount.id}`) + .expect(403); }); - - describe('When using a superhero user', () => { - const setup = async () => { - const school = schoolFactory.buildWithId(); - const superheroRoles = roleFactory.build({ name: RoleName.SUPERHERO, permissions: [] }); - const superheroUser = userFactory.buildWithId({ roles: [superheroRoles] }); - const superheroAccount = mapUserToAccount(superheroUser); - - em.persist([school, superheroRoles, superheroUser, superheroAccount]); - await em.flush(); - - const loggedInClient = await testApiClient.login(superheroAccount); - - return { loggedInClient }; - }; - - it('should reject not existing account id', async () => { - const { loggedInClient } = await setup(); - await loggedInClient.delete('/000000000000000000000000').expect(404); - }); + it('should reject not existing account id', async () => { + currentUser = mapUserToCurrentUser(superheroUser, studentAccount); + await request(app.getHttpServer()) // + .delete(`${basePath}/000000000000000000000000`) + .expect(404); }); }); }); diff --git a/apps/server/src/modules/account/controller/dto/password-pattern.ts b/apps/server/src/modules/account/controller/dto/password-pattern.ts index d849b8db235..6ca2fd9fab2 100644 --- a/apps/server/src/modules/account/controller/dto/password-pattern.ts +++ b/apps/server/src/modules/account/controller/dto/password-pattern.ts @@ -1 +1,2 @@ +// TODO Compare with client export const passwordPattern = /^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z])(?=.*[-_!<>§$%&/()=?\\;:,.#+*~'])\S.{6,253}\S$/; diff --git a/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.spec.ts b/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.spec.ts index 4de5040113f..8e9522434eb 100644 --- a/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.spec.ts +++ b/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.spec.ts @@ -1,5 +1,5 @@ import { Account } from '@shared/domain'; -import { accountFactory } from '@shared/testing'; +import { ObjectId } from 'bson'; import { AccountEntityToDtoMapper } from './account-entity-to-dto.mapper'; describe('AccountEntityToDtoMapper', () => { @@ -14,80 +14,101 @@ describe('AccountEntityToDtoMapper', () => { }); describe('mapToDto', () => { - describe('When mapping AccountEntity to AccountDto', () => { - const setup = () => { - const accountEntity = accountFactory.withAllProperties().buildWithId({}, '000000000000000000000001'); - - const missingSystemUserIdEntity: Account = accountFactory.withoutSystemAndUserId().build(); - - return { accountEntity, missingSystemUserIdEntity }; + it('should map all fields', () => { + const testEntity: Account = { + _id: new ObjectId(), + id: 'id', + createdAt: new Date(), + updatedAt: new Date(), + userId: new ObjectId(), + username: 'username', + activated: true, + credentialHash: 'credentialHash', + expiresAt: new Date(), + lasttriedFailedLogin: new Date(), + password: 'password', + systemId: new ObjectId(), + token: 'token', }; + const ret = AccountEntityToDtoMapper.mapToDto(testEntity); + + expect(ret.id).toBe(testEntity.id); + expect(ret.createdAt).toEqual(testEntity.createdAt); + expect(ret.updatedAt).toEqual(testEntity.createdAt); + expect(ret.userId).toBe(testEntity.userId?.toString()); + expect(ret.username).toBe(testEntity.username); + expect(ret.activated).toBe(testEntity.activated); + expect(ret.credentialHash).toBe(testEntity.credentialHash); + expect(ret.expiresAt).toBe(testEntity.expiresAt); + expect(ret.lasttriedFailedLogin).toBe(testEntity.lasttriedFailedLogin); + expect(ret.password).toBe(testEntity.password); + expect(ret.systemId).toBe(testEntity.systemId?.toString()); + expect(ret.token).toBe(testEntity.token); + }); - it('should map all fields', () => { - const { accountEntity } = setup(); - - const ret = AccountEntityToDtoMapper.mapToDto(accountEntity); - - expect({ ...ret, _id: accountEntity._id }).toMatchObject(accountEntity); - }); - - it('should ignore missing ids', () => { - const { missingSystemUserIdEntity } = setup(); - - const ret = AccountEntityToDtoMapper.mapToDto(missingSystemUserIdEntity); + it('should ignore missing ids', () => { + const testEntity: Account = { + _id: new ObjectId(), + id: 'id', + username: 'username', + createdAt: new Date(), + updatedAt: new Date(), + }; + const ret = AccountEntityToDtoMapper.mapToDto(testEntity); - expect(ret.userId).toBeUndefined(); - expect(ret.systemId).toBeUndefined(); - }); + expect(ret.userId).toBeUndefined(); + expect(ret.systemId).toBeUndefined(); }); }); describe('mapSearchResult', () => { - describe('When mapping multiple Account entities', () => { - const setup = () => { - const testEntity1: Account = accountFactory.buildWithId({}, '000000000000000000000001'); - const testEntity2: Account = accountFactory.buildWithId({}, '000000000000000000000002'); - - const testAmount = 10; - - const testEntities = [testEntity1, testEntity2]; - - return { testEntities, testAmount }; + it('should use actual date if date is', () => { + const testEntity1: Account = { + _id: new ObjectId(), + id: '1', + username: '1', + createdAt: new Date(), + updatedAt: new Date(), }; + const testEntity2: Account = { + _id: new ObjectId(), + id: '2', + username: '2', + createdAt: new Date(), + updatedAt: new Date(), + }; + const testAmount = 10; - it('should map exact same amount of entities', () => { - const { testEntities, testAmount } = setup(); - - const [accounts, total] = AccountEntityToDtoMapper.mapSearchResult([testEntities, testAmount]); + const [accounts, total] = AccountEntityToDtoMapper.mapSearchResult([[testEntity1, testEntity2], testAmount]); - expect(total).toBe(testAmount); - expect(accounts).toHaveLength(2); - expect(accounts).toContainEqual(expect.objectContaining({ id: '000000000000000000000001' })); - expect(accounts).toContainEqual(expect.objectContaining({ id: '000000000000000000000002' })); - }); + expect(total).toBe(testAmount); + expect(accounts).toHaveLength(2); + expect(accounts).toContainEqual(expect.objectContaining({ id: '1' })); + expect(accounts).toContainEqual(expect.objectContaining({ id: '2' })); }); }); describe('mapAccountsToDto', () => { - describe('When mapping multiple Account entities', () => { - const setup = () => { - const testEntity1: Account = accountFactory.buildWithId({}, '000000000000000000000001'); - const testEntity2: Account = accountFactory.buildWithId({}, '000000000000000000000002'); - - const testEntities = [testEntity1, testEntity2]; - - return testEntities; + it('should use actual date if date is', () => { + const testEntity1: Account = { + _id: new ObjectId(), + username: '1', + id: '1', + createdAt: new Date(), + updatedAt: new Date(), }; + const testEntity2: Account = { + _id: new ObjectId(), + username: '2', + id: '2', + createdAt: new Date(), + updatedAt: new Date(), + }; + const ret = AccountEntityToDtoMapper.mapAccountsToDto([testEntity1, testEntity2]); - it('should map all entities', () => { - const testEntities = setup(); - - const ret = AccountEntityToDtoMapper.mapAccountsToDto(testEntities); - - expect(ret).toHaveLength(2); - expect(ret).toContainEqual(expect.objectContaining({ id: '000000000000000000000001' })); - expect(ret).toContainEqual(expect.objectContaining({ id: '000000000000000000000002' })); - }); + expect(ret).toHaveLength(2); + expect(ret).toContainEqual(expect.objectContaining({ id: '1' })); + expect(ret).toContainEqual(expect.objectContaining({ id: '2' })); }); }); }); diff --git a/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.ts b/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.ts index d8af59e6716..417497b3218 100644 --- a/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.ts +++ b/apps/server/src/modules/account/mapper/account-entity-to-dto.mapper.ts @@ -19,8 +19,6 @@ export class AccountEntityToDtoMapper { }); } - // TODO: use Counted instead of [Account[], number] - // TODO: adjust naming of accountEntities static mapSearchResult(accountEntities: [Account[], number]): Counted { const foundAccounts = accountEntities[0]; const accountDtos: AccountDto[] = AccountEntityToDtoMapper.mapAccountsToDto(foundAccounts); diff --git a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts index ee7d1644c94..2430afe6081 100644 --- a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts +++ b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts @@ -24,9 +24,9 @@ describe('AccountIdmToDtoMapperDb', () => { afterAll(async () => { await module.close(); }); - describe('mapToDto', () => { - describe('when mapping from entity to dto', () => { - const setup = () => { + describe('when mapping from entity to dto', () => { + describe('mapToDto', () => { + it('should map all fields', () => { const testIdmEntity: IdmAccount = { id: 'id', username: 'username', @@ -38,12 +38,6 @@ describe('AccountIdmToDtoMapperDb', () => { attDbcUserId: 'attDbcUserId', attDbcSystemId: 'attDbcSystemId', }; - return testIdmEntity; - }; - - it('should map all fields', () => { - const testIdmEntity = setup(); - const ret = mapper.mapToDto(testIdmEntity); expect(ret).toEqual( @@ -58,42 +52,30 @@ describe('AccountIdmToDtoMapperDb', () => { }) ); }); - }); - - describe('when date is undefined', () => { - const setup = () => { - const testIdmEntity: IdmAccount = { - id: 'id', - }; - return testIdmEntity; - }; - it('should use actual date', () => { - const testIdmEntity = setup(); + describe('when date is undefined', () => { + it('should use actual date', () => { + const testIdmEntity: IdmAccount = { + id: 'id', + }; + const ret = mapper.mapToDto(testIdmEntity); - const ret = mapper.mapToDto(testIdmEntity); - - const now = new Date(); - expect(ret.createdAt).toEqual(now); - expect(ret.updatedAt).toEqual(now); + const now = new Date(); + expect(ret.createdAt).toEqual(now); + expect(ret.updatedAt).toEqual(now); + }); }); - }); - describe('when a fields value is missing', () => { - const setup = () => { - const testIdmEntity: IdmAccount = { - id: 'id', - }; - return testIdmEntity; - }; - - it('should fill with empty string', () => { - const testIdmEntity = setup(); - - const ret = mapper.mapToDto(testIdmEntity); + describe('when a fields value is missing', () => { + it('should fill with empty string', () => { + const testIdmEntity: IdmAccount = { + id: 'id', + }; + const ret = mapper.mapToDto(testIdmEntity); - expect(ret.id).toBe(''); - expect(ret.username).toBe(''); + expect(ret.id).toBe(''); + expect(ret.username).toBe(''); + }); }); }); }); diff --git a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.idm.spec.ts b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.idm.spec.ts index 554e2d3025a..0d60a2cc57f 100644 --- a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.idm.spec.ts +++ b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.idm.spec.ts @@ -30,52 +30,39 @@ describe('AccountIdmToDtoMapperIdm', () => { await module.close(); }); - describe('mapToDto', () => { - describe('when mapping from entity to dto', () => { - const setup = () => { - const testIdmEntity: IdmAccount = { - id: 'id', - username: 'username', - email: 'email', - firstName: 'firstName', - lastName: 'lastName', - createdDate: new Date(), - attDbcAccountId: 'attDbcAccountId', - attDbcUserId: 'attDbcUserId', - attDbcSystemId: 'attDbcSystemId', - }; - return testIdmEntity; + describe('when mapping from entity to dto', () => { + it('should map all fields', () => { + const testIdmEntity: IdmAccount = { + id: 'id', + username: 'username', + email: 'email', + firstName: 'firstName', + lastName: 'lastName', + createdDate: new Date(), + attDbcAccountId: 'attDbcAccountId', + attDbcUserId: 'attDbcUserId', + attDbcSystemId: 'attDbcSystemId', }; - - it('should map all fields', () => { - const testIdmEntity = setup(); - - const ret = mapper.mapToDto(testIdmEntity); - - expect(ret).toEqual( - expect.objectContaining>({ - id: testIdmEntity.id, - idmReferenceId: undefined, - userId: testIdmEntity.attDbcUserId, - systemId: testIdmEntity.attDbcSystemId, - createdAt: testIdmEntity.createdDate, - updatedAt: testIdmEntity.createdDate, - username: testIdmEntity.username, - }) - ); - }); + const ret = mapper.mapToDto(testIdmEntity); + + expect(ret).toEqual( + expect.objectContaining>({ + id: testIdmEntity.id, + idmReferenceId: undefined, + userId: testIdmEntity.attDbcUserId, + systemId: testIdmEntity.attDbcSystemId, + createdAt: testIdmEntity.createdDate, + updatedAt: testIdmEntity.createdDate, + username: testIdmEntity.username, + }) + ); }); + describe('when date is undefined', () => { - const setup = () => { + it('should use actual date', () => { const testIdmEntity: IdmAccount = { id: 'id', }; - return testIdmEntity; - }; - - it('should use actual date', () => { - const testIdmEntity = setup(); - const ret = mapper.mapToDto(testIdmEntity); expect(ret.createdAt).toEqual(now); @@ -84,16 +71,10 @@ describe('AccountIdmToDtoMapperIdm', () => { }); describe('when a fields value is missing', () => { - const setup = () => { + it('should fill with empty string', () => { const testIdmEntity: IdmAccount = { id: 'id', }; - return testIdmEntity; - }; - - it('should fill with empty string', () => { - const testIdmEntity = setup(); - const ret = mapper.mapToDto(testIdmEntity); expect(ret.username).toBe(''); diff --git a/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts b/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts index 64858623c67..c4bd892faa0 100644 --- a/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts +++ b/apps/server/src/modules/account/mapper/account-response.mapper.spec.ts @@ -1,57 +1,62 @@ import { Account } from '@shared/domain'; import { AccountDto } from '@modules/account/services/dto/account.dto'; -import { accountDtoFactory, accountFactory } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; import { AccountResponseMapper } from '.'; describe('AccountResponseMapper', () => { describe('mapToResponseFromEntity', () => { - describe('When mapping AccountEntity to AccountResponse', () => { - const setup = () => { - const testEntityAllFields: Account = accountFactory.withAllProperties().buildWithId(); - - const testEntityMissingUserId: Account = accountFactory.withoutSystemAndUserId().build(); - - return { testEntityAllFields, testEntityMissingUserId }; + it('should map all fields', () => { + const testEntity: Account = { + _id: new ObjectId(), + id: new ObjectId().toString(), + userId: new ObjectId(), + activated: true, + username: 'username', + createdAt: new Date(), + updatedAt: new Date(), }; + const ret = AccountResponseMapper.mapToResponseFromEntity(testEntity); - it('should map all fields', () => { - const { testEntityAllFields } = setup(); - - const ret = AccountResponseMapper.mapToResponseFromEntity(testEntityAllFields); - - expect(ret.id).toBe(testEntityAllFields.id); - expect(ret.userId).toBe(testEntityAllFields.userId?.toString()); - expect(ret.activated).toBe(testEntityAllFields.activated); - expect(ret.username).toBe(testEntityAllFields.username); - }); - - it('should ignore missing userId', () => { - const { testEntityMissingUserId } = setup(); + expect(ret.id).toBe(testEntity.id); + expect(ret.userId).toBe(testEntity.userId?.toString()); + expect(ret.activated).toBe(testEntity.activated); + expect(ret.username).toBe(testEntity.username); + expect(ret.updatedAt).toBe(testEntity.updatedAt); + }); - const ret = AccountResponseMapper.mapToResponseFromEntity(testEntityMissingUserId); + it('should ignore missing userId', () => { + const testEntity: Account = { + _id: new ObjectId(), + id: new ObjectId().toString(), + userId: undefined, + activated: true, + username: 'username', + createdAt: new Date(), + updatedAt: new Date(), + }; + const ret = AccountResponseMapper.mapToResponseFromEntity(testEntity); - expect(ret.userId).toBeUndefined(); - }); + expect(ret.userId).toBeUndefined(); }); }); describe('mapToResponse', () => { - describe('When mapping AccountDto to AccountResponse', () => { - const setup = () => { - const testDto: AccountDto = accountDtoFactory.buildWithId(); - return testDto; + it('should map all fields', () => { + const testDto: AccountDto = { + id: new ObjectId().toString(), + userId: new ObjectId().toString(), + activated: true, + username: 'username', + createdAt: new Date(), + updatedAt: new Date(), }; + const ret = AccountResponseMapper.mapToResponse(testDto); - it('should map all fields', () => { - const testDto = setup(); - - const ret = AccountResponseMapper.mapToResponse(testDto); - - expect(ret.id).toBe(testDto.id); - expect(ret.userId).toBe(testDto.userId?.toString()); - expect(ret.activated).toBe(testDto.activated); - expect(ret.username).toBe(testDto.username); - }); + expect(ret.id).toBe(testDto.id); + expect(ret.userId).toBe(testDto.userId?.toString()); + expect(ret.activated).toBe(testDto.activated); + expect(ret.username).toBe(testDto.username); + expect(ret.updatedAt).toBe(testDto.updatedAt); }); }); }); diff --git a/apps/server/src/modules/account/mapper/account-response.mapper.ts b/apps/server/src/modules/account/mapper/account-response.mapper.ts index 84e20519bfe..dac10f98255 100644 --- a/apps/server/src/modules/account/mapper/account-response.mapper.ts +++ b/apps/server/src/modules/account/mapper/account-response.mapper.ts @@ -3,7 +3,6 @@ import { AccountDto } from '@modules/account/services/dto/account.dto'; import { AccountResponse } from '../controller/dto'; export class AccountResponseMapper { - // TODO: remove this one static mapToResponseFromEntity(account: Account): AccountResponse { return new AccountResponse({ id: account.id, diff --git a/apps/server/src/modules/account/repo/account.repo.integration.spec.ts b/apps/server/src/modules/account/repo/account.repo.integration.spec.ts index 1b1193cf8a4..bf4a44119fe 100644 --- a/apps/server/src/modules/account/repo/account.repo.integration.spec.ts +++ b/apps/server/src/modules/account/repo/account.repo.integration.spec.ts @@ -10,6 +10,7 @@ describe('account repo', () => { let module: TestingModule; let em: EntityManager; let repo: AccountRepo; + let mockAccounts: Account[]; beforeAll(async () => { module = await Test.createTestingModule({ @@ -24,6 +25,16 @@ describe('account repo', () => { await module.close(); }); + beforeEach(async () => { + mockAccounts = [ + accountFactory.build({ username: 'John Doe' }), + accountFactory.build({ username: 'Marry Doe' }), + accountFactory.build({ username: 'Susi Doe' }), + accountFactory.build({ username: 'Tim Doe' }), + ]; + await em.persistAndFlush(mockAccounts); + }); + afterEach(async () => { await cleanupCollections(em); }); @@ -33,340 +44,183 @@ describe('account repo', () => { }); describe('findByUserId', () => { - describe('When calling findByUserId with id', () => { - const setup = async () => { - const accountToFind = accountFactory.build(); - await em.persistAndFlush(accountToFind); - em.clear(); - return accountToFind; - }; - - it('should find user with id', async () => { - const accountToFind = await setup(); - const account = await repo.findByUserId(accountToFind.userId ?? ''); - expect(account?.id).toEqual(accountToFind.id); - }); + it('should findByUserId', async () => { + const accountToFind = accountFactory.build(); + await em.persistAndFlush(accountToFind); + em.clear(); + const account = await repo.findByUserId(accountToFind.userId ?? ''); + expect(account?.id).toEqual(accountToFind.id); }); }); describe('findByUsernameAndSystemId', () => { - describe('When username and systemId are given', () => { - const setup = async () => { - const accountToFind = accountFactory.withSystemId(new ObjectId(10)).build(); - await em.persistAndFlush(accountToFind); - em.clear(); - return accountToFind; - }; - - it('should return account', async () => { - const accountToFind = await setup(); - const account = await repo.findByUsernameAndSystemId( - accountToFind.username ?? '', - accountToFind.systemId ?? '' - ); - expect(account?.username).toEqual(accountToFind.username); - }); + it('should return account', async () => { + const accountToFind = accountFactory.withSystemId(new ObjectId(10)).build(); + await em.persistAndFlush(accountToFind); + em.clear(); + const account = await repo.findByUsernameAndSystemId(accountToFind.username ?? '', accountToFind.systemId ?? ''); + expect(account?.username).toEqual(accountToFind.username); }); - - describe('When username and systemId are not given', () => { - it('should return null', async () => { - const account = await repo.findByUsernameAndSystemId('', new ObjectId(undefined)); - expect(account).toBeNull(); - }); + it('should return null', async () => { + const account = await repo.findByUsernameAndSystemId('', new ObjectId(undefined)); + expect(account).toBeNull(); }); }); describe('findMultipleByUserId', () => { - describe('When multiple user ids are given', () => { - const setup = async () => { - const anAccountToFind = accountFactory.build(); - const anotherAccountToFind = accountFactory.build(); - await em.persistAndFlush(anAccountToFind); - await em.persistAndFlush(anotherAccountToFind); - em.clear(); - - return { anAccountToFind, anotherAccountToFind }; - }; - - it('should find multiple users', async () => { - const { anAccountToFind, anotherAccountToFind } = await setup(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const accounts = await repo.findMultipleByUserId([anAccountToFind.userId!, anotherAccountToFind.userId!]); - expect(accounts).toContainEqual(anAccountToFind); - expect(accounts).toContainEqual(anotherAccountToFind); - expect(accounts).toHaveLength(2); - }); + it('should find multiple user by id', async () => { + const anAccountToFind = accountFactory.build(); + const anotherAccountToFind = accountFactory.build(); + await em.persistAndFlush(anAccountToFind); + await em.persistAndFlush(anotherAccountToFind); + em.clear(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const accounts = await repo.findMultipleByUserId([anAccountToFind.userId!, anotherAccountToFind.userId!]); + expect(accounts).toContainEqual(anAccountToFind); + expect(accounts).toContainEqual(anotherAccountToFind); + expect(accounts).toHaveLength(2); }); - describe('When not existing user ids are given', () => { - it('should return empty list', async () => { - const accounts = await repo.findMultipleByUserId(['123456789012', '098765432101']); - expect(accounts).toHaveLength(0); - }); + it('should return empty list if no results', async () => { + const accountToFind = accountFactory.build(); + await em.persistAndFlush(accountToFind); + em.clear(); + const accounts = await repo.findMultipleByUserId(['123456789012', '098765432101']); + expect(accounts).toHaveLength(0); }); }); describe('findByUserIdOrFail', () => { - describe('When existing id is given', () => { - const setup = async () => { - const accountToFind = accountFactory.build(); - await em.persistAndFlush(accountToFind); - em.clear(); - return accountToFind; - }; - it('should find a user', async () => { - const accountToFind = await setup(); - const account = await repo.findByUserIdOrFail(accountToFind.userId ?? ''); - expect(account.id).toEqual(accountToFind.id); - }); + it('should find a user by id', async () => { + const accountToFind = accountFactory.build(); + await em.persistAndFlush(accountToFind); + em.clear(); + const account = await repo.findByUserIdOrFail(accountToFind.userId ?? ''); + expect(account.id).toEqual(accountToFind.id); }); - describe('When id does not exist', () => { - it('should throw not found error', async () => { - await expect(repo.findByUserIdOrFail('123456789012')).rejects.toThrow(NotFoundError); - }); + it('should throw if id does not exist', async () => { + const accountToFind = accountFactory.build(); + await em.persistAndFlush(accountToFind); + em.clear(); + await expect(repo.findByUserIdOrFail('123456789012')).rejects.toThrow(NotFoundError); }); }); describe('getObjectReference', () => { - describe('When a user id is given', () => { - const setup = async () => { - const user = userFactory.buildWithId(); - const account = accountFactory.build({ userId: user.id }); - await em.persistAndFlush([user, account]); - return { user, account }; - }; - it('should return a valid reference', async () => { - const { user, account } = await setup(); - - const reference = repo.getObjectReference(User, account.userId ?? ''); - - expect(reference).toBe(user); - }); + it('should return a valid reference', async () => { + const user = userFactory.buildWithId(); + const account = accountFactory.build({ userId: user.id }); + await em.persistAndFlush([user, account]); + + const reference = repo.getObjectReference(User, account.userId ?? ''); + + expect(reference).toBe(user); }); }); describe('saveWithoutFlush', () => { - describe('When calling saveWithoutFlush', () => { - const setup = () => { - const account = accountFactory.build(); - return account; - }; - it('should add an account to the persist stack', () => { - const account = setup(); - - repo.saveWithoutFlush(account); - expect(em.getUnitOfWork().getPersistStack().size).toBe(1); - }); + it('should add an account to the persist stack', () => { + const account = accountFactory.build(); + + repo.saveWithoutFlush(account); + expect(em.getUnitOfWork().getPersistStack().size).toBe(1); }); }); describe('flush', () => { - describe('When repo is flushed', () => { - const setup = () => { - const account = accountFactory.build(); - em.persist(account); - return account; - }; - - it('should save account', async () => { - const account = setup(); + it('should flush after save', async () => { + const account = accountFactory.build(); + em.persist(account); - expect(account.id).toBeNull(); + expect(account.id).toBeNull(); - await repo.flush(); + await repo.flush(); - expect(account.id).not.toBeNull(); - }); + expect(account.id).not.toBeNull(); }); }); - describe('searchByUsernamePartialMatch', () => { - describe('When searching with a partial user name', () => { - const setup = async () => { - const originalUsername = 'USER@EXAMPLE.COM'; - const partialUsername = 'user'; - const account = accountFactory.build({ username: originalUsername }); - await em.persistAndFlush([account]); - em.clear(); - return { originalUsername, partialUsername, account }; - }; - - it('should find exact one user', async () => { - const { originalUsername, partialUsername } = await setup(); - const [result] = await repo.searchByUsernamePartialMatch(partialUsername); - expect(result).toHaveLength(1); - expect(result[0]).toEqual(expect.objectContaining({ username: originalUsername })); - }); - }); - }); + describe('findByUsername', () => { + it('should find account by user name', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const account = accountFactory.build({ username: originalUsername }); + await em.persistAndFlush([account]); + em.clear(); - describe('searchByUsernameExactMatch', () => { - describe('When searching for an exact match', () => { - const setup = async () => { - const originalUsername = 'USER@EXAMPLE.COM'; - const account = accountFactory.build({ username: originalUsername }); - await em.persistAndFlush([account]); - em.clear(); - return { originalUsername, account }; - }; - - it('should find exact one account', async () => { - const { originalUsername } = await setup(); - - const [result] = await repo.searchByUsernameExactMatch(originalUsername); - expect(result).toHaveLength(1); - expect(result[0]).toEqual(expect.objectContaining({ username: originalUsername })); - }); - }); + const [result] = await repo.searchByUsernameExactMatch('USER@EXAMPLE.COM'); + expect(result).toHaveLength(1); + expect(result[0]).toEqual(expect.objectContaining({ username: originalUsername })); - describe('When searching by username', () => { - const setup = async () => { - const originalUsername = 'USER@EXAMPLE.COM'; - const partialLowerCaseUsername = 'USER@example.COM'; - const lowercaseUsername = 'user@example.com'; - const account = accountFactory.build({ username: originalUsername }); - await em.persistAndFlush([account]); - em.clear(); - return { originalUsername, partialLowerCaseUsername, lowercaseUsername, account }; - }; - - it('should find account by user name, ignoring case', async () => { - const { originalUsername, partialLowerCaseUsername, lowercaseUsername } = await setup(); - - let [accounts] = await repo.searchByUsernameExactMatch(partialLowerCaseUsername); - expect(accounts).toHaveLength(1); - expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername })); - - [accounts] = await repo.searchByUsernameExactMatch(lowercaseUsername); - expect(accounts).toHaveLength(1); - expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername })); - }); + const [result2] = await repo.searchByUsernamePartialMatch('user'); + expect(result2).toHaveLength(1); + expect(result2[0]).toEqual(expect.objectContaining({ username: originalUsername })); }); + it('should find account by user name, ignoring case', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const account = accountFactory.build({ username: originalUsername }); + await em.persistAndFlush([account]); + em.clear(); - describe('When using wildcard', () => { - const setup = async () => { - const originalUsername = 'USER@EXAMPLE.COM'; - const missingDotUserName = 'USER@EXAMPLECCOM'; - const wildcard = '.*'; - const account = accountFactory.build({ username: originalUsername }); - await em.persistAndFlush([account]); - em.clear(); - return { originalUsername, missingDotUserName, wildcard, account }; - }; - - it('should not find account', async () => { - const { missingDotUserName, wildcard } = await setup(); - - let [accounts] = await repo.searchByUsernameExactMatch(missingDotUserName); - expect(accounts).toHaveLength(0); - - [accounts] = await repo.searchByUsernameExactMatch(wildcard); - expect(accounts).toHaveLength(0); - }); + let [accounts] = await repo.searchByUsernameExactMatch('USER@example.COM'); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername })); + + [accounts] = await repo.searchByUsernameExactMatch('user@example.com'); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername })); }); - }); + it('should not find by wildcard', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const account = accountFactory.build({ username: originalUsername }); + await em.persistAndFlush([account]); + em.clear(); - describe('deleteById', () => { - describe('When an id is given', () => { - const setup = async () => { - const account = accountFactory.buildWithId(); - await em.persistAndFlush([account]); + let [accounts] = await repo.searchByUsernameExactMatch('USER@EXAMPLECCOM'); + expect(accounts).toHaveLength(0); - return account; - }; + [accounts] = await repo.searchByUsernameExactMatch('.*'); + expect(accounts).toHaveLength(0); + }); + }); - it('should delete an account by id', async () => { - const account = await setup(); + describe('deleteId', () => { + it('should delete an account by id', async () => { + const account = accountFactory.buildWithId(); + await em.persistAndFlush([account]); - await expect(repo.deleteById(account.id)).resolves.not.toThrow(); + await expect(repo.deleteById(account.id)).resolves.not.toThrow(); - await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError); - }); + await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError); }); }); describe('deleteByUserId', () => { - describe('When an user id is given', () => { - const setup = async () => { - const user = userFactory.buildWithId(); - const account = accountFactory.build({ userId: user.id }); - await em.persistAndFlush([user, account]); - - return { user, account }; - }; + it('should delete an account by user id', async () => { + const user = userFactory.buildWithId(); + const account = accountFactory.build({ userId: user.id }); + await em.persistAndFlush([user, account]); - it('should delete an account by user id', async () => { - const { user, account } = await setup(); + await expect(repo.deleteByUserId(user.id)).resolves.not.toThrow(); - await expect(repo.deleteByUserId(user.id)).resolves.not.toThrow(); - - await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError); - }); + await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError); }); }); describe('findMany', () => { - describe('When no limit and offset are given', () => { - const setup = async () => { - const mockAccounts = [ - accountFactory.build({ username: 'John Doe' }), - accountFactory.build({ username: 'Marry Doe' }), - accountFactory.build({ username: 'Susi Doe' }), - accountFactory.build({ username: 'Tim Doe' }), - ]; - await em.persistAndFlush(mockAccounts); - return mockAccounts; - }; - - it('should find all accounts', async () => { - const mockAccounts = await setup(); - const foundAccounts = await repo.findMany(); - expect(foundAccounts).toEqual(mockAccounts); - }); - }); - - describe('When limit is given', () => { - const setup = async () => { - const limit = 1; - - const mockAccounts = [ - accountFactory.build({ username: 'John Doe' }), - accountFactory.build({ username: 'Marry Doe' }), - accountFactory.build({ username: 'Susi Doe' }), - accountFactory.build({ username: 'Tim Doe' }), - ]; - await em.persistAndFlush(mockAccounts); - return { limit, mockAccounts }; - }; - - it('should limit the result set', async () => { - const { limit } = await setup(); - const foundAccounts = await repo.findMany(0, limit); - expect(foundAccounts).toHaveLength(limit); - }); - }); - - describe('When offset is given', () => { - const setup = async () => { - const offset = 2; - - const mockAccounts = [ - accountFactory.build({ username: 'John Doe' }), - accountFactory.build({ username: 'Marry Doe' }), - accountFactory.build({ username: 'Susi Doe' }), - accountFactory.build({ username: 'Tim Doe' }), - ]; - await em.persistAndFlush(mockAccounts); - return { offset, mockAccounts }; - }; - - it('should skip n entries', async () => { - const { offset, mockAccounts } = await setup(); - - const foundAccounts = await repo.findMany(offset); - expect(foundAccounts).toHaveLength(mockAccounts.length - offset); - }); + it('should find all accounts', async () => { + const foundAccounts = await repo.findMany(); + expect(foundAccounts).toEqual(mockAccounts); + }); + it('limit the result set ', async () => { + const limit = 1; + const foundAccounts = await repo.findMany(0, limit); + expect(foundAccounts).toHaveLength(limit); + }); + it('skip n entries ', async () => { + const offset = 2; + const foundAccounts = await repo.findMany(offset); + expect(foundAccounts).toHaveLength(mockAccounts.length - offset); }); }); }); diff --git a/apps/server/src/modules/account/repo/account.repo.ts b/apps/server/src/modules/account/repo/account.repo.ts index e848973c5c6..fb68f0a759b 100644 --- a/apps/server/src/modules/account/repo/account.repo.ts +++ b/apps/server/src/modules/account/repo/account.repo.ts @@ -15,9 +15,7 @@ export class AccountRepo extends BaseRepo { * Finds an account by user id. * @param userId the user id */ - // TODO: here only EntityIds should arrive async findByUserId(userId: EntityId | ObjectId): Promise { - // TODO: you can use userId directly, without constructing an objectId return this._em.findOne(Account, { userId: new ObjectId(userId) }); } @@ -49,8 +47,6 @@ export class AccountRepo extends BaseRepo { await this._em.flush(); } - // TODO: the default values for skip and limit, are they required and/or correct here? - // TODO: use counted for the return type async searchByUsernameExactMatch(username: string, skip = 0, limit = 1): Promise<[Account[], number]> { return this.searchByUsername(username, skip, limit, true); } @@ -84,7 +80,6 @@ export class AccountRepo extends BaseRepo { limit: number, exactMatch: boolean ): Promise<[Account[], number]> { - // TODO: check that injections are not possible, eg make sure sanitizeHTML has been called at some point (for username) // escapes every character, that's not a unicode letter or number const escapedUsername = username.replace(/[^(\p{L}\p{N})]/gu, '\\$&'); const searchUsername = exactMatch ? `^${escapedUsername}$` : escapedUsername; diff --git a/apps/server/src/modules/account/review-comments.md b/apps/server/src/modules/account/review-comments.md deleted file mode 100644 index fc636019cdd..00000000000 --- a/apps/server/src/modules/account/review-comments.md +++ /dev/null @@ -1,12 +0,0 @@ -# Review Comments 14.7.23 - -- move mapper into repo folder -- write an md file or flow diagram describing how things work -- in what layer do the services belong? - -- naming of DO vs Entity (DO is the leading, "Account", entity is just the datalayer representation "AccountEntity") - -- new decisions for loggables - - -looked at this module only. \ No newline at end of file diff --git a/apps/server/src/modules/account/services/account-db.service.spec.ts b/apps/server/src/modules/account/services/account-db.service.spec.ts index 7fa4c4e44a8..64075bcb40c 100644 --- a/apps/server/src/modules/account/services/account-db.service.spec.ts +++ b/apps/server/src/modules/account/services/account-db.service.spec.ts @@ -3,9 +3,9 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; -import { Account, EntityId } from '@shared/domain'; +import { Account, EntityId, Permission, Role, RoleName, SchoolEntity, User } from '@shared/domain'; import { IdentityManagementService } from '@shared/infra/identity-management/identity-management.service'; -import { accountFactory, setupEntities, userFactory } from '@shared/testing'; +import { accountFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; import { AccountEntityToDtoMapper } from '@modules/account/mapper'; import { AccountDto } from '@modules/account/services/dto'; import { IServerConfig } from '@modules/server'; @@ -19,11 +19,23 @@ import { AbstractAccountService } from './account.service.abstract'; describe('AccountDbService', () => { let module: TestingModule; let accountService: AbstractAccountService; - let accountRepo: DeepMocked; + let mockAccounts: Account[]; + let accountRepo: AccountRepo; let accountLookupServiceMock: DeepMocked; const defaultPassword = 'DummyPasswd!1'; + let mockSchool: SchoolEntity; + + let mockTeacherUser: User; + let mockStudentUser: User; + let mockUserWithoutAccount: User; + + let mockTeacherAccount: Account; + let mockStudentAccount: Account; + + let mockAccountWithSystemId: Account; + afterAll(async () => { await module.close(); }); @@ -35,7 +47,69 @@ describe('AccountDbService', () => { AccountLookupService, { provide: AccountRepo, - useValue: createMock(), + useValue: { + save: jest.fn().mockImplementation((account: Account): Promise => { + if (account.username === 'fail@to.update') { + return Promise.reject(); + } + const accountEntity = mockAccounts.find((tempAccount) => tempAccount.userId === account.userId); + if (accountEntity) { + Object.assign(accountEntity, account); + } + + return Promise.resolve(); + }), + deleteById: jest.fn().mockImplementation((): Promise => Promise.resolve()), + findMultipleByUserId: (userIds: EntityId[]): Promise => { + const accounts = mockAccounts.filter((tempAccount) => + userIds.find((userId) => tempAccount.userId?.toString() === userId) + ); + return Promise.resolve(accounts); + }, + findByUserId: (userId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.userId?.toString() === userId); + if (account) { + return Promise.resolve(account); + } + return Promise.resolve(null); + }, + findByUserIdOrFail: (userId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.userId?.toString() === userId); + + if (account) { + return Promise.resolve(account); + } + throw new EntityNotFoundError(Account.name); + }, + findByUsernameAndSystemId: (username: string, systemId: EntityId | ObjectId): Promise => { + const account = mockAccounts.find( + (tempAccount) => tempAccount.username === username && tempAccount.systemId === systemId + ); + if (account) { + return Promise.resolve(account); + } + return Promise.resolve(null); + }, + + findById: jest.fn().mockImplementation((accountId: EntityId | ObjectId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.id === accountId.toString()); + + if (account) { + return Promise.resolve(account); + } + throw new EntityNotFoundError(Account.name); + }), + searchByUsernameExactMatch: jest + .fn() + .mockImplementation((): Promise<[Account[], number]> => Promise.resolve([[mockTeacherAccount], 1])), + searchByUsernamePartialMatch: jest + .fn() + .mockImplementation( + (): Promise<[Account[], number]> => Promise.resolve([mockAccounts, mockAccounts.length]) + ), + deleteByUserId: jest.fn().mockImplementation((): Promise => Promise.resolve()), + findMany: jest.fn().mockImplementation((): Promise => Promise.resolve(mockAccounts)), + }, }, { provide: LegacyLogger, @@ -51,7 +125,14 @@ describe('AccountDbService', () => { }, { provide: AccountLookupService, - useValue: createMock(), + useValue: createMock({ + getInternalId: (id: EntityId | ObjectId): Promise => { + if (ObjectId.isValid(id)) { + return Promise.resolve(new ObjectId(id)); + } + return Promise.resolve(null); + }, + }), }, ], }).compile(); @@ -62,9 +143,28 @@ describe('AccountDbService', () => { }); beforeEach(() => { - jest.resetAllMocks(); jest.useFakeTimers(); jest.setSystemTime(new Date(2020, 1, 1)); + + mockSchool = schoolFactory.buildWithId(); + + mockTeacherUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], + }); + mockStudentUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], + }); + mockUserWithoutAccount = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], + }); + mockTeacherAccount = accountFactory.buildWithId({ userId: mockTeacherUser.id, password: defaultPassword }); + mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id, password: defaultPassword }); + + mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId()).build(); + mockAccounts = [mockTeacherAccount, mockStudentAccount, mockAccountWithSystemId]; }); afterEach(() => { @@ -73,615 +173,294 @@ describe('AccountDbService', () => { }); describe('findById', () => { - describe('when searching by Id', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - - mockTeacherAccountDto.username = 'changedUsername@example.org'; - mockTeacherAccountDto.activated = false; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - - return { mockTeacherAccount }; - }; - it( - 'should return accountDto', - async () => { - const { mockTeacherAccount } = setup(); - - const resultAccount = await accountService.findById(mockTeacherAccount.id); - expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - }, - 10 * 60 * 1000 - ); - }); + it( + 'should return accountDto', + async () => { + const resultAccount = await accountService.findById(mockTeacherAccount.id); + expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); + }, + 10 * 60 * 1000 + ); }); describe('findByUserId', () => { - describe('when user id exists', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId(); - - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.findByUserId.mockImplementation((userId: EntityId | ObjectId): Promise => { - if (userId === mockTeacherUser.id) { - return Promise.resolve(mockTeacherAccount); - } - return Promise.resolve(null); - }); - - return { mockTeacherUser, mockTeacherAccount }; - }; - it('should return accountDto', async () => { - const { mockTeacherUser, mockTeacherAccount } = setup(); - const resultAccount = await accountService.findByUserId(mockTeacherUser.id); - expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - }); + it('should return accountDto', async () => { + const resultAccount = await accountService.findByUserId(mockTeacherUser.id); + expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); }); - - describe('when user id not exists', () => { - it('should return null', async () => { - const resultAccount = await accountService.findByUserId('nonExistentId'); - expect(resultAccount).toBeNull(); - }); + it('should return null', async () => { + const resultAccount = await accountService.findByUserId('nonExistentId'); + expect(resultAccount).toBeNull(); }); }); describe('findByUsernameAndSystemId', () => { - describe('when user name and system id exists', () => { - const setup = () => { - const mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId()).build(); - accountRepo.findByUsernameAndSystemId.mockResolvedValue(mockAccountWithSystemId); - return { mockAccountWithSystemId }; - }; - it('should return accountDto', async () => { - const { mockAccountWithSystemId } = setup(); - const resultAccount = await accountService.findByUsernameAndSystemId( - mockAccountWithSystemId.username, - mockAccountWithSystemId.systemId ?? '' - ); - expect(resultAccount).not.toBe(undefined); - }); + it('should return accountDto', async () => { + const resultAccount = await accountService.findByUsernameAndSystemId( + mockAccountWithSystemId.username, + mockAccountWithSystemId.systemId ?? '' + ); + expect(resultAccount).not.toBe(undefined); }); - - describe('when only system id exists', () => { - const setup = () => { - const mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId()).build(); - accountRepo.findByUsernameAndSystemId.mockImplementation( - (username: string, systemId: EntityId | ObjectId): Promise => { - if (mockAccountWithSystemId.username === username && mockAccountWithSystemId.systemId === systemId) { - return Promise.resolve(mockAccountWithSystemId); - } - return Promise.resolve(null); - } - ); - return { mockAccountWithSystemId }; - }; - it('should return null if username does not exist', async () => { - const { mockAccountWithSystemId } = setup(); - const resultAccount = await accountService.findByUsernameAndSystemId( - 'nonExistentUsername', - mockAccountWithSystemId.systemId ?? '' - ); - expect(resultAccount).toBeNull(); - }); + it('should return null if username does not exist', async () => { + const resultAccount = await accountService.findByUsernameAndSystemId( + 'nonExistentUsername', + mockAccountWithSystemId.systemId ?? '' + ); + expect(resultAccount).toBeNull(); }); - - describe('when only user name exists', () => { - const setup = () => { - const mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId()).build(); - accountRepo.findByUsernameAndSystemId.mockImplementation( - (username: string, systemId: EntityId | ObjectId): Promise => { - if (mockAccountWithSystemId.username === username && mockAccountWithSystemId.systemId === systemId) { - return Promise.resolve(mockAccountWithSystemId); - } - return Promise.resolve(null); - } - ); - return { mockAccountWithSystemId }; - }; - it('should return null if system id does not exist', async () => { - const { mockAccountWithSystemId } = setup(); - const resultAccount = await accountService.findByUsernameAndSystemId( - mockAccountWithSystemId.username, - 'nonExistentSystemId' ?? '' - ); - expect(resultAccount).toBeNull(); - }); + it('should return null if system id does not exist', async () => { + const resultAccount = await accountService.findByUsernameAndSystemId( + mockAccountWithSystemId.username, + 'nonExistentSystemId' ?? '' + ); + expect(resultAccount).toBeNull(); }); }); describe('findMultipleByUserId', () => { - describe('when searching for multiple existing ids', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId(); - const mockStudentUser = userFactory.buildWithId(); - - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPassword, - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPassword, - }); - - accountRepo.findMultipleByUserId.mockImplementation((userIds: (EntityId | ObjectId)[]): Promise => { - const accounts = [mockStudentAccount, mockTeacherAccount].filter((tempAccount) => - userIds.find((userId) => tempAccount.userId?.toString() === userId) - ); - return Promise.resolve(accounts); - }); - return { mockStudentUser, mockStudentAccount, mockTeacherUser, mockTeacherAccount }; - }; - it('should return multiple accountDtos', async () => { - const { mockStudentUser, mockStudentAccount, mockTeacherUser, mockTeacherAccount } = setup(); - const resultAccounts = await accountService.findMultipleByUserId([mockTeacherUser.id, mockStudentUser.id]); - expect(resultAccounts).toContainEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - expect(resultAccounts).toContainEqual(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - expect(resultAccounts).toHaveLength(2); - }); + it('should return multiple accountDtos', async () => { + const resultAccounts = await accountService.findMultipleByUserId([mockTeacherUser.id, mockStudentUser.id]); + expect(resultAccounts).toContainEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); + expect(resultAccounts).toContainEqual(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); + expect(resultAccounts).toHaveLength(2); }); - - describe('when only user name exists', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockStudentAccount = accountFactory.buildWithId(); - - accountRepo.findMultipleByUserId.mockImplementation((userIds: (EntityId | ObjectId)[]): Promise => { - const accounts = [mockStudentAccount, mockTeacherAccount].filter((tempAccount) => - userIds.find((userId) => tempAccount.userId?.toString() === userId) - ); - return Promise.resolve(accounts); - }); - return {}; - }; - it('should return empty array on mismatch', async () => { - setup(); - const resultAccount = await accountService.findMultipleByUserId(['nonExistentId1']); - expect(resultAccount).toHaveLength(0); - }); + it('should return empty array on mismatch', async () => { + const resultAccount = await accountService.findMultipleByUserId(['nonExistentId1']); + expect(resultAccount).toHaveLength(0); }); }); describe('findByUserIdOrFail', () => { - describe('when user exists', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId(); - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPassword, - }); - - accountRepo.findByUserIdOrFail.mockResolvedValue(mockTeacherAccount); - - return { mockTeacherUser, mockTeacherAccount }; - }; - - it('should return accountDto', async () => { - const { mockTeacherUser, mockTeacherAccount } = setup(); - const resultAccount = await accountService.findByUserIdOrFail(mockTeacherUser.id); - expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - }); + it('should return accountDto', async () => { + const resultAccount = await accountService.findByUserIdOrFail(mockTeacherUser.id); + expect(resultAccount).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); }); - - describe('when user does not exist', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId(); - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPassword, - }); - accountRepo.findByUserIdOrFail.mockImplementation((userId: EntityId | ObjectId): Promise => { - if (mockTeacherUser.id === userId) { - return Promise.resolve(mockTeacherAccount); - } - throw new EntityNotFoundError(Account.name); - }); - return {}; - }; - it('should throw EntityNotFoundError', async () => { - setup(); - await expect(accountService.findByUserIdOrFail('nonExistentId')).rejects.toThrow(EntityNotFoundError); - }); + it('should throw EntityNotFoundError', async () => { + await expect(accountService.findByUserIdOrFail('nonExistentId')).rejects.toThrow(EntityNotFoundError); }); }); describe('save', () => { - describe('when update an existing account', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - - mockTeacherAccountDto.username = 'changedUsername@example.org'; - mockTeacherAccountDto.activated = false; - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - accountRepo.save.mockResolvedValue(); - - return { mockTeacherAccountDto, mockTeacherAccount }; - }; - - it('should update account', async () => { - const { mockTeacherAccountDto, mockTeacherAccount } = setup(); - const ret = await accountService.save(mockTeacherAccountDto); - - expect(accountRepo.save).toBeCalledTimes(1); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - id: mockTeacherAccount.id, - username: mockTeacherAccountDto.username, - activated: mockTeacherAccountDto.activated, - systemId: mockTeacherAccount.systemId, - userId: mockTeacherAccount.userId, - }); + it('should update an existing account', async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + mockTeacherAccountDto.username = 'changedUsername@example.org'; + mockTeacherAccountDto.activated = false; + const ret = await accountService.save(mockTeacherAccountDto); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + id: mockTeacherAccount.id, + username: mockTeacherAccountDto.username, + activated: mockTeacherAccountDto.activated, + systemId: mockTeacherAccount.systemId, + userId: mockTeacherAccount.userId, }); }); - describe("when update an existing account's system", () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - - mockTeacherAccountDto.username = 'changedUsername@example.org'; - mockTeacherAccountDto.systemId = '123456789012'; - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - accountRepo.save.mockResolvedValue(); - - return { mockTeacherAccountDto, mockTeacherAccount }; - }; - it("should update an existing account's system", async () => { - const { mockTeacherAccountDto, mockTeacherAccount } = setup(); - - const ret = await accountService.save(mockTeacherAccountDto); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - id: mockTeacherAccount.id, - username: mockTeacherAccountDto.username, - activated: mockTeacherAccount.activated, - systemId: new ObjectId(mockTeacherAccountDto.systemId), - userId: mockTeacherAccount.userId, - }); + it("should update an existing account's system", async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + mockTeacherAccountDto.username = 'changedUsername@example.org'; + mockTeacherAccountDto.systemId = '123456789012'; + const ret = await accountService.save(mockTeacherAccountDto); + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + id: mockTeacherAccount.id, + username: mockTeacherAccountDto.username, + activated: mockTeacherAccount.activated, + systemId: new ObjectId(mockTeacherAccountDto.systemId), + userId: mockTeacherAccount.userId, }); }); - - describe("when update an existing account's user", () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockStudentUser = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - - mockTeacherAccountDto.username = 'changedUsername@example.org'; - mockTeacherAccountDto.userId = mockStudentUser.id; - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - accountRepo.save.mockResolvedValue(); - - return { mockStudentUser, mockTeacherAccountDto, mockTeacherAccount }; - }; - it('should update account', async () => { - const { mockStudentUser, mockTeacherAccountDto, mockTeacherAccount } = setup(); - - const ret = await accountService.save(mockTeacherAccountDto); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - id: mockTeacherAccount.id, - username: mockTeacherAccountDto.username, - activated: mockTeacherAccount.activated, - systemId: mockTeacherAccount.systemId, - userId: new ObjectId(mockStudentUser.id), - }); + it("should update an existing account's user", async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + mockTeacherAccountDto.username = 'changedUsername@example.org'; + mockTeacherAccountDto.userId = mockStudentUser.id; + const ret = await accountService.save(mockTeacherAccountDto); + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + id: mockTeacherAccount.id, + username: mockTeacherAccountDto.username, + activated: mockTeacherAccount.activated, + systemId: mockTeacherAccount.systemId, + userId: new ObjectId(mockStudentUser.id), }); }); - describe("when existing account's system is undefined", () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - - mockTeacherAccountDto.username = 'changedUsername@example.org'; - mockTeacherAccountDto.systemId = undefined; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - accountRepo.save.mockResolvedValue(); - - return { mockTeacherAccountDto, mockTeacherAccount }; - }; - it('should keep undefined on update', async () => { - const { mockTeacherAccountDto, mockTeacherAccount } = setup(); - - const ret = await accountService.save(mockTeacherAccountDto); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - id: mockTeacherAccount.id, - username: mockTeacherAccountDto.username, - activated: mockTeacherAccount.activated, - systemId: mockTeacherAccountDto.systemId, - userId: mockTeacherAccount.userId, - }); - }); - }); - - describe('when account does not exists', () => { - const setup = () => { - const mockUserWithoutAccount = userFactory.buildWithId(); - - const accountToSave: AccountDto = { - createdAt: new Date(), - updatedAt: new Date(), - username: 'asdf@asdf.de', - userId: mockUserWithoutAccount.id, - systemId: '012345678912', - password: defaultPassword, - } as AccountDto; - (accountRepo.findById as jest.Mock).mockClear(); - (accountRepo.save as jest.Mock).mockClear(); - - return { accountToSave }; - }; - it('should save a new account', async () => { - const { accountToSave } = setup(); - - const ret = await accountService.save(accountToSave); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - username: accountToSave.username, - userId: new ObjectId(accountToSave.userId), - systemId: new ObjectId(accountToSave.systemId), - createdAt: accountToSave.createdAt, - updatedAt: accountToSave.updatedAt, - }); + it("should keep existing account's system undefined on update", async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + mockTeacherAccountDto.username = 'changedUsername@example.org'; + mockTeacherAccountDto.systemId = undefined; + const ret = await accountService.save(mockTeacherAccountDto); + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + id: mockTeacherAccount.id, + username: mockTeacherAccountDto.username, + activated: mockTeacherAccount.activated, + systemId: mockTeacherAccountDto.systemId, + userId: mockTeacherAccount.userId, }); }); - - describe("when account's system undefined", () => { - const setup = () => { - const mockUserWithoutAccount = userFactory.buildWithId(); - - const accountToSave: AccountDto = { - createdAt: new Date(), - updatedAt: new Date(), - username: 'asdf@asdf.de', - userId: mockUserWithoutAccount.id, - password: defaultPassword, - } as AccountDto; - (accountRepo.findById as jest.Mock).mockClear(); - (accountRepo.save as jest.Mock).mockClear(); - - return { accountToSave }; - }; - it('should keep undefined on save', async () => { - const { accountToSave } = setup(); - - const ret = await accountService.save(accountToSave); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - systemId: undefined, - }); + it('should save a new account', async () => { + const accountToSave: AccountDto = { + createdAt: new Date(), + updatedAt: new Date(), + username: 'asdf@asdf.de', + userId: mockUserWithoutAccount.id, + systemId: '012345678912', + password: defaultPassword, + } as AccountDto; + (accountRepo.findById as jest.Mock).mockClear(); + (accountRepo.save as jest.Mock).mockClear(); + const ret = await accountService.save(accountToSave); + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + username: accountToSave.username, + userId: new ObjectId(accountToSave.userId), + systemId: new ObjectId(accountToSave.systemId), + createdAt: accountToSave.createdAt, + updatedAt: accountToSave.updatedAt, }); }); - describe('when save account', () => { - const setup = () => { - const mockUserWithoutAccount = userFactory.buildWithId(); - - const accountToSave = { - createdAt: new Date(), - updatedAt: new Date(), - username: 'asdf@asdf.de', - userId: mockUserWithoutAccount.id, - systemId: '012345678912', - password: defaultPassword, - } as AccountDto; - (accountRepo.findById as jest.Mock).mockClear(); - (accountRepo.save as jest.Mock).mockClear(); - - return { accountToSave }; - }; - it('should encrypt password', async () => { - const { accountToSave } = setup(); - - await accountService.save(accountToSave); - const ret = await accountService.save(accountToSave); - expect(ret).toBeDefined(); - expect(ret).not.toMatchObject({ - password: defaultPassword, - }); + it("should keep account's system undefined on save", async () => { + const accountToSave: AccountDto = { + createdAt: new Date(), + updatedAt: new Date(), + username: 'asdf@asdf.de', + userId: mockUserWithoutAccount.id, + password: defaultPassword, + } as AccountDto; + (accountRepo.findById as jest.Mock).mockClear(); + (accountRepo.save as jest.Mock).mockClear(); + const ret = await accountService.save(accountToSave); + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + systemId: undefined, }); }); - describe('when creating a new account', () => { - const setup = () => { - const spy = jest.spyOn(accountRepo, 'save'); - const dto = { - username: 'john.doe@domain.tld', - password: '', - } as AccountDto; - (accountRepo.findById as jest.Mock).mockClear(); - (accountRepo.save as jest.Mock).mockClear(); - - return { spy, dto }; - }; - it('should set password to undefined if password is empty', async () => { - const { spy, dto } = setup(); - - await expect(accountService.save(dto)).resolves.not.toThrow(); - expect(accountRepo.findById).not.toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - password: undefined, - }) - ); + it('should encrypt password', async () => { + const accountToSave = { + createdAt: new Date(), + updatedAt: new Date(), + username: 'asdf@asdf.de', + userId: mockUserWithoutAccount.id, + systemId: '012345678912', + password: defaultPassword, + } as AccountDto; + (accountRepo.findById as jest.Mock).mockClear(); + (accountRepo.save as jest.Mock).mockClear(); + await accountService.save(accountToSave); + const ret = await accountService.save(accountToSave); + expect(ret).toBeDefined(); + expect(ret).not.toMatchObject({ + password: defaultPassword, }); }); - describe('when password is empty while editing an existing account', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - - const spy = jest.spyOn(accountRepo, 'save'); - const dto = { - id: mockTeacherAccount.id, + it('should set password to undefined if password is empty while creating a new account', async () => { + const spy = jest.spyOn(accountRepo, 'save'); + const dto = { + username: 'john.doe@domain.tld', + password: '', + } as AccountDto; + (accountRepo.findById as jest.Mock).mockClear(); + (accountRepo.save as jest.Mock).mockClear(); + await expect(accountService.save(dto)).resolves.not.toThrow(); + expect(accountRepo.findById).not.toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ password: undefined, - } as AccountDto; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - accountRepo.save.mockResolvedValue(); + }) + ); + }); - return { mockTeacherAccount, spy, dto }; - }; - it('should not change password', async () => { - const { mockTeacherAccount, spy, dto } = setup(); - await expect(accountService.save(dto)).resolves.not.toThrow(); - expect(accountRepo.findById).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - password: mockTeacherAccount.password, - }) - ); - }); + it('should not change password if password is empty while editing an existing account', async () => { + const spy = jest.spyOn(accountRepo, 'save'); + const dto = { + id: mockTeacherAccount.id, + // username: 'john.doe@domain.tld', + password: undefined, + } as AccountDto; + (accountRepo.findById as jest.Mock).mockClear(); + (accountRepo.save as jest.Mock).mockClear(); + await expect(accountService.save(dto)).resolves.not.toThrow(); + expect(accountRepo.findById).toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + password: defaultPassword, + }) + ); }); }); describe('updateUsername', () => { - describe('when updating username', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - const newUsername = 'newUsername'; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { mockTeacherAccount, mockTeacherAccountDto, newUsername }; - }; - it('should update only user name', async () => { - const { mockTeacherAccount, mockTeacherAccountDto, newUsername } = setup(); - const ret = await accountService.updateUsername(mockTeacherAccount.id, newUsername); - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - ...mockTeacherAccountDto, - username: newUsername, - }); + it('should update an existing account but no other information', async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + const newUsername = 'newUsername'; + const ret = await accountService.updateUsername(mockTeacherAccount.id, newUsername); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + ...mockTeacherAccountDto, + username: newUsername, }); }); }); describe('updateLastTriedFailedLogin', () => { - describe('when update last failed Login', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); - const theNewDate = new Date(); - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { mockTeacherAccount, mockTeacherAccountDto, theNewDate }; - }; - it('should update last tried failed login', async () => { - const { mockTeacherAccount, mockTeacherAccountDto, theNewDate } = setup(); - const ret = await accountService.updateLastTriedFailedLogin(mockTeacherAccount.id, theNewDate); - - expect(ret).toBeDefined(); - expect(ret).toMatchObject({ - ...mockTeacherAccountDto, - lasttriedFailedLogin: theNewDate, - }); + it('should update last tried failed login', async () => { + const mockTeacherAccountDto = AccountEntityToDtoMapper.mapToDto(mockTeacherAccount); + const theNewDate = new Date(); + const ret = await accountService.updateLastTriedFailedLogin(mockTeacherAccount.id, theNewDate); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject({ + ...mockTeacherAccountDto, + lasttriedFailedLogin: theNewDate, }); }); }); describe('validatePassword', () => { - describe('when accepted Password', () => { - const setup = async () => { - const ret = await accountService.validatePassword( - { password: await bcrypt.hash(defaultPassword, 10) } as unknown as AccountDto, - defaultPassword - ); - - return { ret }; - }; - it('should validate password', async () => { - const { ret } = await setup(); - - expect(ret).toBe(true); - }); + it('should validate password', async () => { + const ret = await accountService.validatePassword( + { password: await bcrypt.hash(defaultPassword, 10) } as unknown as AccountDto, + defaultPassword + ); + expect(ret).toBe(true); }); - - describe('when wrong Password', () => { - const setup = async () => { - const ret = await accountService.validatePassword( - { password: await bcrypt.hash(defaultPassword, 10) } as unknown as AccountDto, - 'incorrectPwd' - ); - - return { ret }; - }; - it('should report', async () => { - const { ret } = await setup(); - - expect(ret).toBe(false); - }); + it('should report wrong password', async () => { + const ret = await accountService.validatePassword( + { password: await bcrypt.hash(defaultPassword, 10) } as unknown as AccountDto, + 'incorrectPwd' + ); + expect(ret).toBe(false); }); - - describe('when missing account password', () => { - const setup = async () => { - const ret = await accountService.validatePassword({ password: undefined } as AccountDto, 'incorrectPwd'); - - return { ret }; - }; - it('should report', async () => { - const { ret } = await setup(); - - expect(ret).toBe(false); - }); + it('should report missing account password', async () => { + const ret = await accountService.validatePassword({ password: undefined } as AccountDto, 'incorrectPwd'); + expect(ret).toBe(false); }); }); describe('updatePassword', () => { - describe('when update Password', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - const newPassword = 'newPassword'; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { mockTeacherAccount, newPassword }; - }; - it('should update password', async () => { - const { mockTeacherAccount, newPassword } = setup(); + it('should update password', async () => { + const newPassword = 'newPassword'; + const ret = await accountService.updatePassword(mockTeacherAccount.id, newPassword); - const ret = await accountService.updatePassword(mockTeacherAccount.id, newPassword); - - expect(ret).toBeDefined(); - if (ret.password) { - await expect(bcrypt.compare(newPassword, ret.password)).resolves.toBe(true); - } else { - fail('return password is undefined'); - } - }); + expect(ret).toBeDefined(); + if (ret.password) { + await expect(bcrypt.compare(newPassword, ret.password)).resolves.toBe(true); + } else { + fail('return password is undefined'); + } }); }); describe('delete', () => { - describe('when delete an existing account', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { mockTeacherAccount }; - }; + describe('when deleting existing account', () => { it('should delete account via repo', async () => { - const { mockTeacherAccount } = setup(); await accountService.delete(mockTeacherAccount.id); expect(accountRepo.deleteById).toHaveBeenCalledWith(new ObjectId(mockTeacherAccount.id)); }); @@ -689,125 +468,55 @@ describe('AccountDbService', () => { describe('when deleting non existing account', () => { const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); accountLookupServiceMock.getInternalId.mockResolvedValueOnce(null); - - return { mockTeacherAccount }; }; - it('should throw account not found', async () => { - const { mockTeacherAccount } = setup(); + it('should throw', async () => { + setup(); await expect(accountService.delete(mockTeacherAccount.id)).rejects.toThrow(); }); }); }); describe('deleteByUserId', () => { - describe('when delete account with given user id', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId(); - - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPassword, - }); - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { mockTeacherUser, mockTeacherAccount }; - }; - it('should delete via repo', async () => { - const { mockTeacherUser, mockTeacherAccount } = setup(); - - await accountService.deleteByUserId(mockTeacherAccount.userId?.toString() ?? ''); - expect(accountRepo.deleteByUserId).toHaveBeenCalledWith(mockTeacherUser.id); - }); + it('should delete the account with given user id via repo', async () => { + await accountService.deleteByUserId(mockTeacherAccount.userId?.toString() ?? ''); + expect(accountRepo.deleteByUserId).toHaveBeenCalledWith(mockTeacherAccount.userId); }); }); describe('searchByUsernamePartialMatch', () => { - describe('when searching by part of username', () => { - const setup = () => { - const partialUserName = 'admin'; - const skip = 2; - const limit = 10; - const mockTeacherAccount = accountFactory.buildWithId(); - const mockStudentAccount = accountFactory.buildWithId(); - const mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId()).build(); - const mockAccounts = [mockTeacherAccount, mockStudentAccount, mockAccountWithSystemId]; - - accountRepo.findById.mockResolvedValue(mockTeacherAccount); - accountRepo.searchByUsernamePartialMatch.mockResolvedValue([ - [mockTeacherAccount, mockStudentAccount, mockAccountWithSystemId], - 3, - ]); - accountLookupServiceMock.getInternalId.mockResolvedValue(mockTeacherAccount._id); - - return { partialUserName, skip, limit, mockTeacherAccount, mockAccounts }; - }; - it('should call repo', async () => { - const { partialUserName, skip, limit, mockTeacherAccount, mockAccounts } = setup(); - const [accounts, total] = await accountService.searchByUsernamePartialMatch(partialUserName, skip, limit); - expect(accountRepo.searchByUsernamePartialMatch).toHaveBeenCalledWith(partialUserName, skip, limit); - expect(total).toBe(mockAccounts.length); + it('should call repo', async () => { + const partialUserName = 'admin'; + const skip = 2; + const limit = 10; + const [accounts, total] = await accountService.searchByUsernamePartialMatch(partialUserName, skip, limit); + expect(accountRepo.searchByUsernamePartialMatch).toHaveBeenCalledWith(partialUserName, skip, limit); + expect(total).toBe(mockAccounts.length); - expect(accounts[0]).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - }); + expect(accounts[0]).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); }); }); - describe('searchByUsernameExactMatch', () => { - describe('when searching by username', () => { - const setup = () => { - const partialUserName = 'admin'; - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.searchByUsernameExactMatch.mockResolvedValue([[mockTeacherAccount], 1]); - - return { partialUserName, mockTeacherAccount }; - }; - it('should call repo', async () => { - const { partialUserName, mockTeacherAccount } = setup(); - const [accounts, total] = await accountService.searchByUsernameExactMatch(partialUserName); - expect(accountRepo.searchByUsernameExactMatch).toHaveBeenCalledWith(partialUserName); - expect(total).toBe(1); - expect(accounts[0]).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - }); + it('should call repo', async () => { + const partialUserName = 'admin'; + const [accounts, total] = await accountService.searchByUsernameExactMatch(partialUserName); + expect(accountRepo.searchByUsernameExactMatch).toHaveBeenCalledWith(partialUserName); + expect(total).toBe(1); + expect(accounts[0]).toEqual(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); }); }); - describe('findMany', () => { - describe('when find many one time', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.findMany.mockResolvedValue([mockTeacherAccount]); - return {}; - }; - it('should call repo', async () => { - setup(); - const foundAccounts = await accountService.findMany(1, 1); - expect(accountRepo.findMany).toHaveBeenCalledWith(1, 1); - expect(foundAccounts).toBeDefined(); - }); - }); - describe('when call find many more than one time', () => { - const setup = () => { - const mockTeacherAccount = accountFactory.buildWithId(); - - accountRepo.findMany.mockResolvedValue([mockTeacherAccount]); - - return {}; - }; - it('should call repo each time', async () => { - setup(); - const foundAccounts = await accountService.findMany(); - expect(accountRepo.findMany).toHaveBeenCalledWith(0, 100); - expect(foundAccounts).toBeDefined(); - }); + describe('findMany', () => { + it('should call repo', async () => { + const foundAccounts = await accountService.findMany(1, 1); + expect(accountRepo.findMany).toHaveBeenCalledWith(1, 1); + expect(foundAccounts).toBeDefined(); + }); + it('should call repo', async () => { + const foundAccounts = await accountService.findMany(); + expect(accountRepo.findMany).toHaveBeenCalledWith(0, 100); + expect(foundAccounts).toBeDefined(); }); }); }); diff --git a/apps/server/src/modules/account/services/account-db.service.ts b/apps/server/src/modules/account/services/account-db.service.ts index 2ea02eeb3c4..1209ed86744 100644 --- a/apps/server/src/modules/account/services/account-db.service.ts +++ b/apps/server/src/modules/account/services/account-db.service.ts @@ -1,15 +1,13 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; +import bcrypt from 'bcryptjs'; import { EntityNotFoundError } from '@shared/common'; import { Account, Counted, EntityId } from '@shared/domain'; -import bcrypt from 'bcryptjs'; -import { AccountEntityToDtoMapper } from '../mapper'; import { AccountRepo } from '../repo/account.repo'; -import { AccountLookupService } from './account-lookup.service'; -import { AbstractAccountService } from './account.service.abstract'; +import { AccountEntityToDtoMapper } from '../mapper'; import { AccountDto, AccountSaveDto } from './dto'; - -// HINT: do more empty lines :) +import { AbstractAccountService } from './account.service.abstract'; +import { AccountLookupService } from './account-lookup.service'; @Injectable() export class AccountServiceDb extends AbstractAccountService { @@ -34,7 +32,10 @@ export class AccountServiceDb extends AbstractAccountService { } async findByUserIdOrFail(userId: EntityId): Promise { - const accountEntity = await this.accountRepo.findByUserIdOrFail(userId); + const accountEntity = await this.accountRepo.findByUserId(userId); + if (!accountEntity) { + throw new EntityNotFoundError('Account'); + } return AccountEntityToDtoMapper.mapToDto(accountEntity); } @@ -45,8 +46,6 @@ export class AccountServiceDb extends AbstractAccountService { async save(accountDto: AccountSaveDto): Promise { let account: Account; - // HINT: mapping could be done by a mapper (though this whole file is subject to be removed in the future) - // HINT: today we have logic to map back into unit work in the baseDO if (accountDto.id) { const internalId = await this.getInternalId(accountDto.id); account = await this.accountRepo.findById(internalId); @@ -76,7 +75,7 @@ export class AccountServiceDb extends AbstractAccountService { credentialHash: accountDto.credentialHash, }); - await this.accountRepo.save(account); // HINT: this can be done once in the end + await this.accountRepo.save(account); } return AccountEntityToDtoMapper.mapToDto(account); } @@ -129,7 +128,7 @@ export class AccountServiceDb extends AbstractAccountService { if (!account.password) { return Promise.resolve(false); } - return bcrypt.compare(comparePassword, account.password); // hint: first get result, then return seperately + return bcrypt.compare(comparePassword, account.password); } private async getInternalId(id: EntityId | ObjectId): Promise { diff --git a/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts b/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts index 2249a485f98..4761bbd80ca 100644 --- a/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts +++ b/apps/server/src/modules/account/services/account-idm.service.integration.spec.ts @@ -94,125 +94,79 @@ describe('AccountIdmService Integration', () => { } }); - describe('save', () => { - describe('when account does not exists', () => { - it('should create a new account', async () => { - if (!isIdmReachable) return; - const createdAccount = await accountIdmService.save(testAccount); - const foundAccount = await identityManagementService.findAccountById(createdAccount.idmReferenceId ?? ''); - - expect(foundAccount).toEqual( - expect.objectContaining({ - id: createdAccount.idmReferenceId ?? '', - username: createdAccount.username, - attDbcAccountId: createdAccount.id, - attDbcUserId: createdAccount.userId, - attDbcSystemId: createdAccount.systemId, - }) - ); - }); - }); + it('save should create a new account', async () => { + if (!isIdmReachable) return; + const createdAccount = await accountIdmService.save(testAccount); + const foundAccount = await identityManagementService.findAccountById(createdAccount.idmReferenceId ?? ''); + + expect(foundAccount).toEqual( + expect.objectContaining({ + id: createdAccount.idmReferenceId ?? '', + username: createdAccount.username, + attDbcAccountId: testDbcAccountId, + attDbcUserId: createdAccount.userId, + attDbcSystemId: createdAccount.systemId, + }) + ); }); - describe('save', () => { - describe('when account exists', () => { - const setup = async () => { - const newUserName = 'jane.doe@mail.tld'; - const idmId = await createAccount(); - - return { idmId, newUserName }; - }; - it('should update account', async () => { - if (!isIdmReachable) return; - const { idmId, newUserName } = await setup(); - - await accountIdmService.save({ - id: testDbcAccountId, - username: newUserName, - }); - - const foundAccount = await identityManagementService.findAccountById(idmId); - - expect(foundAccount).toEqual( - expect.objectContaining({ - id: idmId, - username: newUserName, - }) - ); - }); + it('save should update existing account', async () => { + if (!isIdmReachable) return; + const newUsername = 'jane.doe@mail.tld'; + const idmId = await createAccount(); + + await accountIdmService.save({ + id: testDbcAccountId, + username: newUsername, }); + const foundAccount = await identityManagementService.findAccountById(idmId); + + expect(foundAccount).toEqual( + expect.objectContaining({ + id: idmId, + username: newUsername, + }) + ); }); - describe('updateUsername', () => { - describe('when updating username', () => { - const setup = async () => { - const newUserName = 'jane.doe@mail.tld'; - const idmId = await createAccount(); - - return { newUserName, idmId }; - }; - it('should update only username', async () => { - if (!isIdmReachable) return; - const { newUserName, idmId } = await setup(); - - await accountIdmService.updateUsername(testDbcAccountId, newUserName); - const foundAccount = await identityManagementService.findAccountById(idmId); - - expect(foundAccount).toEqual( - expect.objectContaining>({ - username: newUserName, - }) - ); - }); - }); + it('updateUsername should update username', async () => { + if (!isIdmReachable) return; + const newUserName = 'jane.doe@mail.tld'; + const idmId = await createAccount(); + await accountIdmService.updateUsername(testDbcAccountId, newUserName); + + const foundAccount = await identityManagementService.findAccountById(idmId); + + expect(foundAccount).toEqual( + expect.objectContaining>({ + username: newUserName, + }) + ); }); - describe('updatePassword', () => { - describe('when updating with permitted password', () => { - const setup = async () => { - await createAccount(); - }; - it('should update password', async () => { - if (!isIdmReachable) return; - await setup(); - await expect(accountIdmService.updatePassword(testDbcAccountId, 'newPassword')).resolves.not.toThrow(); - }); - }); + it('updatePassword should update password', async () => { + if (!isIdmReachable) return; + await createAccount(); + await expect(accountIdmService.updatePassword(testDbcAccountId, 'newPassword')).resolves.not.toThrow(); }); - describe('delete', () => { - describe('when delete account', () => { - const setup = async () => { - const idmId = await createAccount(); - const foundAccount = await identityManagementService.findAccountById(idmId); - return { idmId, foundAccount }; - }; - it('should remove account', async () => { - if (!isIdmReachable) return; - const { idmId, foundAccount } = await setup(); - expect(foundAccount).toBeDefined(); - - await accountIdmService.delete(testDbcAccountId); - await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); - }); - }); + it('delete should remove account', async () => { + if (!isIdmReachable) return; + const idmId = await createAccount(); + const foundAccount = await identityManagementService.findAccountById(idmId); + expect(foundAccount).toBeDefined(); + + await accountIdmService.delete(testDbcAccountId); + await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); }); - describe('deleteByUserId', () => { - describe('when deleting by UserId', () => { - const setup = async () => { - const idmId = await createAccount(); - const foundAccount = await identityManagementService.findAccountById(idmId); - return { idmId, foundAccount }; - }; - it('should remove account', async () => { - if (!isIdmReachable) return; - const { idmId, foundAccount } = await setup(); - expect(foundAccount).toBeDefined(); - - await accountIdmService.deleteByUserId(testAccount.userId ?? ''); - await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); - }); - }); + it('deleteByUserId should remove account', async () => { + if (!isIdmReachable) return; + const idmId = await createAccount(); + const foundAccount = await identityManagementService.findAccountById(idmId); + expect(foundAccount).toBeDefined(); + + await accountIdmService.deleteByUserId(testAccount.userId ?? ''); + await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); }); }); diff --git a/apps/server/src/modules/account/services/account-idm.service.spec.ts b/apps/server/src/modules/account/services/account-idm.service.spec.ts index 1669b4ca4c4..4b997d1b3fe 100644 --- a/apps/server/src/modules/account/services/account-idm.service.spec.ts +++ b/apps/server/src/modules/account/services/account-idm.service.spec.ts @@ -76,203 +76,155 @@ describe('AccountIdmService', () => { }); describe('save', () => { - describe('when save an existing account', () => { - const setup = () => { - idmServiceMock.createAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccountPassword.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.findAccountById.mockResolvedValue(mockIdmAccount); - const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); - const createSpy = jest.spyOn(idmServiceMock, 'createAccount'); - - const mockAccountDto = { - id: mockIdmAccountRefId, - username: 'testUserName', - userId: 'userId', - systemId: 'systemId', - }; - return { updateSpy, createSpy, mockAccountDto }; + const setup = () => { + idmServiceMock.createAccount.mockResolvedValue(mockIdmAccount.id); + idmServiceMock.updateAccount.mockResolvedValue(mockIdmAccount.id); + idmServiceMock.updateAccountPassword.mockResolvedValue(mockIdmAccount.id); + idmServiceMock.findAccountById.mockResolvedValue(mockIdmAccount); + }; + + it('should update an existing account', async () => { + setup(); + const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); + const createSpy = jest.spyOn(idmServiceMock, 'createAccount'); + + const mockAccountDto = { + id: mockIdmAccountRefId, + username: 'testUserName', + userId: 'userId', + systemId: 'systemId', }; - - it('should update account information', async () => { - const { updateSpy, createSpy, mockAccountDto } = setup(); - - const ret = await accountIdmService.save(mockAccountDto); - - expect(updateSpy).toHaveBeenCalled(); - expect(createSpy).not.toHaveBeenCalled(); - - expect(ret).toBeDefined(); - expect(ret).toMatchObject>({ - id: mockIdmAccount.attDbcAccountId, - idmReferenceId: mockIdmAccount.id, - createdAt: mockIdmAccount.createdDate, - updatedAt: mockIdmAccount.createdDate, - username: mockIdmAccount.username, - }); + const ret = await accountIdmService.save(mockAccountDto); + + expect(updateSpy).toHaveBeenCalled(); + expect(createSpy).not.toHaveBeenCalled(); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject>({ + id: mockIdmAccount.attDbcAccountId, + idmReferenceId: mockIdmAccount.id, + createdAt: mockIdmAccount.createdDate, + updatedAt: mockIdmAccount.createdDate, + username: mockIdmAccount.username, }); }); - describe('when save an existing account', () => { - const setup = () => { - idmServiceMock.createAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccountPassword.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.findAccountById.mockResolvedValue(mockIdmAccount); - const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); - const updatePasswordSpy = jest.spyOn(idmServiceMock, 'updateAccountPassword'); - - const mockAccountDto: AccountSaveDto = { - id: mockIdmAccountRefId, - username: 'testUserName', - userId: 'userId', - systemId: 'systemId', - password: 'password', - }; - return { updateSpy, updatePasswordSpy, mockAccountDto }; + it('should update an existing accounts password', async () => { + setup(); + const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); + const updatePasswordSpy = jest.spyOn(idmServiceMock, 'updateAccountPassword'); + + const mockAccountDto: AccountSaveDto = { + id: mockIdmAccountRefId, + username: 'testUserName', + userId: 'userId', + systemId: 'systemId', + password: 'password', }; - it('should update account password', async () => { - const { updateSpy, updatePasswordSpy, mockAccountDto } = setup(); + const ret = await accountIdmService.save(mockAccountDto); - const ret = await accountIdmService.save(mockAccountDto); - - expect(updateSpy).toHaveBeenCalled(); - expect(updatePasswordSpy).toHaveBeenCalled(); - expect(ret).toBeDefined(); - }); + expect(updateSpy).toHaveBeenCalled(); + expect(updatePasswordSpy).toHaveBeenCalled(); + expect(ret).toBeDefined(); }); - describe('when save not existing account', () => { - const setup = () => { - idmServiceMock.createAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccountPassword.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.findAccountById.mockResolvedValue(mockIdmAccount); - const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); - const createSpy = jest.spyOn(idmServiceMock, 'createAccount'); - - const mockAccountDto = { username: 'testUserName', id: undefined, userId: 'userId', systemId: 'systemId' }; - - return { updateSpy, createSpy, mockAccountDto }; - }; - it('should create a new account', async () => { - const { updateSpy, createSpy, mockAccountDto } = setup(); + it('should create a new account', async () => { + setup(); + const updateSpy = jest.spyOn(idmServiceMock, 'updateAccount'); + const createSpy = jest.spyOn(idmServiceMock, 'createAccount'); - const ret = await accountIdmService.save(mockAccountDto); + const mockAccountDto = { username: 'testUserName', id: undefined, userId: 'userId', systemId: 'systemId' }; + const ret = await accountIdmService.save(mockAccountDto); - expect(updateSpy).not.toHaveBeenCalled(); - expect(createSpy).toHaveBeenCalled(); + expect(updateSpy).not.toHaveBeenCalled(); + expect(createSpy).toHaveBeenCalled(); - expect(ret).toBeDefined(); - expect(ret).toMatchObject>({ - id: mockIdmAccount.attDbcAccountId, - idmReferenceId: mockIdmAccount.id, - createdAt: mockIdmAccount.createdDate, - updatedAt: mockIdmAccount.createdDate, - username: mockIdmAccount.username, - }); + expect(ret).toBeDefined(); + expect(ret).toMatchObject>({ + id: mockIdmAccount.attDbcAccountId, + idmReferenceId: mockIdmAccount.id, + createdAt: mockIdmAccount.createdDate, + updatedAt: mockIdmAccount.createdDate, + username: mockIdmAccount.username, }); }); - - describe('when save not existing account', () => { - const setup = () => { - idmServiceMock.createAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccount.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.updateAccountPassword.mockResolvedValue(mockIdmAccount.id); - idmServiceMock.findAccountById.mockResolvedValue(mockIdmAccount); - accountLookupServiceMock.getExternalId.mockResolvedValue(null); - const mockAccountDto = { - id: mockIdmAccountRefId, - username: 'testUserName', - userId: 'userId', - systemId: 'systemId', - }; - - return { mockAccountDto }; + it('should create a new account on update error', async () => { + setup(); + accountLookupServiceMock.getExternalId.mockResolvedValue(null); + const mockAccountDto = { + id: mockIdmAccountRefId, + username: 'testUserName', + userId: 'userId', + systemId: 'systemId', }; - it('should create a new account on update error', async () => { - const { mockAccountDto } = setup(); - - const ret = await accountIdmService.save(mockAccountDto); - - expect(idmServiceMock.createAccount).toHaveBeenCalled(); - expect(ret).toBeDefined(); - expect(ret).toMatchObject>({ - id: mockIdmAccount.attDbcAccountId, - idmReferenceId: mockIdmAccount.id, - createdAt: mockIdmAccount.createdDate, - updatedAt: mockIdmAccount.createdDate, - username: mockIdmAccount.username, - }); + + const ret = await accountIdmService.save(mockAccountDto); + + expect(idmServiceMock.createAccount).toHaveBeenCalled(); + expect(ret).toBeDefined(); + expect(ret).toMatchObject>({ + id: mockIdmAccount.attDbcAccountId, + idmReferenceId: mockIdmAccount.id, + createdAt: mockIdmAccount.createdDate, + updatedAt: mockIdmAccount.createdDate, + username: mockIdmAccount.username, }); }); }); describe('updateUsername', () => { - describe('when update Username', () => { - const setup = () => { - accountLookupServiceMock.getExternalId.mockResolvedValue(mockIdmAccount.id); - }; - it('should map result correctly', async () => { - setup(); - const ret = await accountIdmService.updateUsername(mockIdmAccountRefId, 'any'); - - expect(ret).toBeDefined(); - expect(ret).toMatchObject>({ - id: mockIdmAccount.attDbcAccountId, - idmReferenceId: mockIdmAccount.id, - createdAt: mockIdmAccount.createdDate, - updatedAt: mockIdmAccount.createdDate, - username: mockIdmAccount.username, - }); + it('should map result correctly', async () => { + accountLookupServiceMock.getExternalId.mockResolvedValue(mockIdmAccount.id); + const ret = await accountIdmService.updateUsername(mockIdmAccountRefId, 'any'); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject>({ + id: mockIdmAccount.attDbcAccountId, + idmReferenceId: mockIdmAccount.id, + createdAt: mockIdmAccount.createdDate, + updatedAt: mockIdmAccount.createdDate, + username: mockIdmAccount.username, }); }); }); describe('updatePassword', () => { - describe('when update password', () => { - const setup = () => { - accountLookupServiceMock.getExternalId.mockResolvedValue(mockIdmAccount.id); - }; - it('should map result correctly', async () => { - setup(); - const ret = await accountIdmService.updatePassword(mockIdmAccountRefId, 'any'); - - expect(ret).toBeDefined(); - expect(ret).toMatchObject>({ - id: mockIdmAccount.attDbcAccountId, - idmReferenceId: mockIdmAccount.id, - createdAt: mockIdmAccount.createdDate, - updatedAt: mockIdmAccount.createdDate, - username: mockIdmAccount.username, - }); + it('should map result correctly', async () => { + accountLookupServiceMock.getExternalId.mockResolvedValue(mockIdmAccount.id); + const ret = await accountIdmService.updatePassword(mockIdmAccountRefId, 'any'); + + expect(ret).toBeDefined(); + expect(ret).toMatchObject>({ + id: mockIdmAccount.attDbcAccountId, + idmReferenceId: mockIdmAccount.id, + createdAt: mockIdmAccount.createdDate, + updatedAt: mockIdmAccount.createdDate, + username: mockIdmAccount.username, }); }); }); describe('validatePassword', () => { - describe('when validate password', () => { - const setup = (acceptPassword: boolean) => { - idmOauthServiceMock.resourceOwnerPasswordGrant.mockResolvedValue( - acceptPassword ? '{ "alg": "HS256", "typ": "JWT" }' : undefined - ); - }; - it('should validate password by checking JWT', async () => { - setup(true); - const ret = await accountIdmService.validatePassword( - { username: 'username' } as unknown as AccountDto, - 'password' - ); - expect(ret).toBe(true); - }); - it('should report wrong password, i. e. non successful JWT creation', async () => { - setup(false); - const ret = await accountIdmService.validatePassword( - { username: 'username' } as unknown as AccountDto, - 'password' - ); - expect(ret).toBe(false); - }); + const setup = (acceptPassword: boolean) => { + idmOauthServiceMock.resourceOwnerPasswordGrant.mockResolvedValue( + acceptPassword ? '{ "alg": "HS256", "typ": "JWT" }' : undefined + ); + }; + it('should validate password by checking JWT', async () => { + setup(true); + const ret = await accountIdmService.validatePassword( + { username: 'username' } as unknown as AccountDto, + 'password' + ); + expect(ret).toBe(true); + }); + it('should report wrong password, i. e. non successful JWT creation', async () => { + setup(false); + const ret = await accountIdmService.validatePassword( + { username: 'username' } as unknown as AccountDto, + 'password' + ); + expect(ret).toBe(false); }); }); @@ -296,7 +248,7 @@ describe('AccountIdmService', () => { accountLookupServiceMock.getExternalId.mockResolvedValue(null); }; - it('should throw account not found error', async () => { + it('should throw error', async () => { setup(); await expect(accountIdmService.delete(mockIdmAccountRefId)).rejects.toThrow(); }); @@ -304,19 +256,16 @@ describe('AccountIdmService', () => { }); describe('deleteByUserId', () => { - describe('when deleting an account by user id', () => { - const setup = () => { - idmServiceMock.findAccountByDbcUserId.mockResolvedValue(mockIdmAccount); - const deleteSpy = jest.spyOn(idmServiceMock, 'deleteAccountById'); - return { deleteSpy }; - }; + const setup = () => { + idmServiceMock.findAccountByDbcUserId.mockResolvedValue(mockIdmAccount); + }; - it('should delete the account with given user id via repo', async () => { - const { deleteSpy } = setup(); + it('should delete the account with given user id via repo', async () => { + setup(); + const deleteSpy = jest.spyOn(idmServiceMock, 'deleteAccountById'); - await accountIdmService.deleteByUserId(mockIdmAccount.attDbcUserId ?? ''); - expect(deleteSpy).toHaveBeenCalledWith(mockIdmAccount.id); - }); + await accountIdmService.deleteByUserId(mockIdmAccount.attDbcUserId ?? ''); + expect(deleteSpy).toHaveBeenCalledWith(mockIdmAccount.id); }); }); @@ -338,7 +287,7 @@ describe('AccountIdmService', () => { idmServiceMock.findAccountById.mockRejectedValue(new Error()); }; - it('should throw account not found', async () => { + it('should throw', async () => { setup(); await expect(accountIdmService.findById('notExistingId')).rejects.toThrow(); }); @@ -410,7 +359,7 @@ describe('AccountIdmService', () => { idmServiceMock.findAccountByDbcUserId.mockResolvedValue(undefined as unknown as IdmAccount); }; - it('should throw account not found', async () => { + it('should throw', async () => { setup(); await expect(accountIdmService.findByUserIdOrFail('notExistingId')).rejects.toThrow(EntityNotFoundError); }); @@ -516,7 +465,7 @@ describe('AccountIdmService', () => { }); }); - it('findMany should throw not implemented Exception', async () => { + it('findMany should throw', async () => { await expect(accountIdmService.findMany(0, 0)).rejects.toThrow(NotImplementedException); }); }); diff --git a/apps/server/src/modules/account/services/account-idm.service.ts b/apps/server/src/modules/account/services/account-idm.service.ts index 039db80eddf..68bcfb42bae 100644 --- a/apps/server/src/modules/account/services/account-idm.service.ts +++ b/apps/server/src/modules/account/services/account-idm.service.ts @@ -1,13 +1,13 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable, NotImplementedException } from '@nestjs/common'; import { EntityNotFoundError } from '@shared/common'; -import { IdentityManagementOauthService, IdentityManagementService } from '@shared/infra/identity-management'; import { Counted, EntityId, IdmAccount, IdmAccountUpdate } from '@shared/domain'; +import { IdentityManagementService, IdentityManagementOauthService } from '@shared/infra/identity-management'; import { LegacyLogger } from '@src/core/logger'; import { AccountIdmToDtoMapper } from '../mapper'; -import { AccountLookupService } from './account-lookup.service'; import { AbstractAccountService } from './account.service.abstract'; import { AccountDto, AccountSaveDto } from './dto'; +import { AccountLookupService } from './account-lookup.service'; @Injectable() export class AccountServiceIdm extends AbstractAccountService { @@ -27,7 +27,6 @@ export class AccountServiceIdm extends AbstractAccountService { return account; } - // TODO: this needs a better solution. probably needs followup meeting to come up with something async findMultipleByUserId(userIds: EntityId[]): Promise { const results = new Array(); for (const userId of userIds) { @@ -35,7 +34,6 @@ export class AccountServiceIdm extends AbstractAccountService { // eslint-disable-next-line no-await-in-loop results.push(await this.identityManager.findAccountByDbcUserId(userId)); } catch { - // TODO: dont simply forget errors. maybe use a filter instead? // ignore entry } } @@ -48,7 +46,6 @@ export class AccountServiceIdm extends AbstractAccountService { const result = await this.identityManager.findAccountByDbcUserId(userId); return this.accountIdmToDtoMapper.mapToDto(result); } catch { - // TODO: dont simply forget errors return null; } } @@ -96,10 +93,8 @@ export class AccountServiceIdm extends AbstractAccountService { attDbcUserId: accountDto.userId, attDbcSystemId: accountDto.systemId, }; - // TODO: probably do some method extraction here if (accountDto.id) { let idmId: string | undefined; - // TODO: extract into a method that hides the trycatch try { idmId = await this.getIdmAccountId(accountDto.id); } catch { diff --git a/apps/server/src/modules/account/services/account.service.abstract.ts b/apps/server/src/modules/account/services/account.service.abstract.ts index d25dbc0ac4a..b2e198f6a86 100644 --- a/apps/server/src/modules/account/services/account.service.abstract.ts +++ b/apps/server/src/modules/account/services/account.service.abstract.ts @@ -2,8 +2,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Counted, EntityId } from '@shared/domain'; import { AccountDto, AccountSaveDto } from './dto'; -// TODO: split functions which are only needed for feathers - export abstract class AbstractAccountService { abstract findById(id: EntityId): Promise; @@ -13,7 +11,6 @@ export abstract class AbstractAccountService { abstract findByUserIdOrFail(userId: EntityId): Promise; - // HINT: it would be preferable to use entityId here. Needs to be checked if this is blocked by lecacy code abstract findByUsernameAndSystemId(username: string, systemId: EntityId | ObjectId): Promise; abstract save(accountDto: AccountSaveDto): Promise; diff --git a/apps/server/src/modules/account/services/account.service.integration.spec.ts b/apps/server/src/modules/account/services/account.service.integration.spec.ts index 5d5caa24263..d001925000b 100644 --- a/apps/server/src/modules/account/services/account.service.integration.spec.ts +++ b/apps/server/src/modules/account/services/account.service.integration.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createMock } from '@golevelup/ts-jest'; import KeycloakAdminClient from '@keycloak/keycloak-admin-client-cjs/keycloak-admin-client-cjs-index'; import { EntityManager } from '@mikro-orm/mongodb'; @@ -159,151 +158,95 @@ describe('AccountService Integration', () => { ); }; - describe('save', () => { - describe('when account not exists', () => { - it('should create a new account', async () => { - if (!isIdmReachable) return; - const account = await accountService.save(testAccount); - await compareDbAccount(account.id, account); - await compareIdmAccount(account.idmReferenceId ?? '', account); - }); - }); - - describe('when account exists', () => { - const setup = async () => { - const newUsername = 'jane.doe@mail.tld'; - const [dbId, idmId] = await createAccount(); - const originalAccount = await accountService.findById(dbId); - return { newUsername, dbId, idmId, originalAccount }; - }; - it('save should update existing account', async () => { - if (!isIdmReachable) return; - const { newUsername, dbId, idmId, originalAccount } = await setup(); - const updatedAccount = await accountService.save({ - ...originalAccount, - username: newUsername, - }); - await compareDbAccount(dbId, updatedAccount); - await compareIdmAccount(idmId, updatedAccount); - }); - }); - - describe('when only db account exists', () => { - const setup = async () => { - const newUsername = 'jane.doe@mail.tld'; - const dbId = await createDbAccount(); - const originalAccount = await accountService.findById(dbId); - return { newUsername, dbId, originalAccount }; - }; - it('should create idm account for existing db account', async () => { - if (!isIdmReachable) return; - const { newUsername, dbId, originalAccount } = await setup(); + it('save should create a new account', async () => { + if (!isIdmReachable) return; + const account = await accountService.save(testAccount); + await compareDbAccount(account.id, account); + await compareIdmAccount(account.idmReferenceId ?? '', account); + }); - const updatedAccount = await accountService.save({ - ...originalAccount, - username: newUsername, - }); - await compareDbAccount(dbId, updatedAccount); - await compareIdmAccount(updatedAccount.idmReferenceId ?? '', updatedAccount); - }); + it('save should update existing account', async () => { + if (!isIdmReachable) return; + const newUsername = 'jane.doe@mail.tld'; + const [dbId, idmId] = await createAccount(); + const originalAccount = await accountService.findById(dbId); + const updatedAccount = await accountService.save({ + ...originalAccount, + username: newUsername, }); + await compareDbAccount(dbId, updatedAccount); + await compareIdmAccount(idmId, updatedAccount); }); - describe('updateUsername', () => { - describe('when updating Username', () => { - const setup = async () => { - const newUsername = 'jane.doe@mail.tld'; - const [dbId, idmId] = await createAccount(); - - return { newUsername, dbId, idmId }; - }; - it('should update username', async () => { - if (!isIdmReachable) return; - const { newUsername, dbId, idmId } = await setup(); - - await accountService.updateUsername(dbId, newUsername); - const foundAccount = await identityManagementService.findAccountById(idmId); - const foundDbAccount = await accountRepo.findById(dbId); - - expect(foundAccount).toEqual( - expect.objectContaining>({ - username: newUsername, - }) - ); - expect(foundDbAccount).toEqual( - expect.objectContaining>({ - username: newUsername, - }) - ); - }); + it('save should create idm account for existing db account', async () => { + if (!isIdmReachable) return; + const newUsername = 'jane.doe@mail.tld'; + const dbId = await createDbAccount(); + const originalAccount = await accountService.findById(dbId); + const updatedAccount = await accountService.save({ + ...originalAccount, + username: newUsername, }); + await compareDbAccount(dbId, updatedAccount); + await compareIdmAccount(updatedAccount.idmReferenceId ?? '', updatedAccount); }); - describe('updatePassword', () => { - describe('when updating password', () => { - const setup = async () => { - const [dbId] = await createAccount(); - - const foundDbAccountBefore = await accountRepo.findById(dbId); - const previousPasswordHash = foundDbAccountBefore.password; - const foundDbAccountAfter = await accountRepo.findById(dbId); - - return { dbId, previousPasswordHash, foundDbAccountAfter }; - }; - it('should update password', async () => { - if (!isIdmReachable) return; - const { dbId, previousPasswordHash, foundDbAccountAfter } = await setup(); + it('updateUsername should update username', async () => { + if (!isIdmReachable) return; + const newUserName = 'jane.doe@mail.tld'; + const [dbId, idmId] = await createAccount(); + await accountService.updateUsername(dbId, newUserName); - await expect(accountService.updatePassword(dbId, 'newPassword')).resolves.not.toThrow(); - - expect(foundDbAccountAfter.password).not.toEqual(previousPasswordHash); - }); - }); + const foundAccount = await identityManagementService.findAccountById(idmId); + expect(foundAccount).toEqual( + expect.objectContaining>({ + username: newUserName, + }) + ); + const foundDbAccount = await accountRepo.findById(dbId); + expect(foundDbAccount).toEqual( + expect.objectContaining>({ + username: newUserName, + }) + ); }); - describe('delete', () => { - describe('when delete an account', () => { - const setup = async () => { - const [dbId, idmId] = await createAccount(); - const foundIdmAccount = await identityManagementService.findAccountById(idmId); - const foundDbAccount = await accountRepo.findById(dbId); + it('updatePassword should update password', async () => { + if (!isIdmReachable) return; + const [dbId] = await createAccount(); - return { dbId, idmId, foundIdmAccount, foundDbAccount }; - }; - it('should remove account', async () => { - if (!isIdmReachable) return; - const { dbId, idmId, foundIdmAccount, foundDbAccount } = await setup(); + const foundDbAccountBefore = await accountRepo.findById(dbId); + const previousPasswordHash = foundDbAccountBefore.password; - expect(foundIdmAccount).toBeDefined(); - expect(foundDbAccount).toBeDefined(); + await expect(accountService.updatePassword(dbId, 'newPassword')).resolves.not.toThrow(); - await accountService.delete(dbId); - await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); - await expect(accountRepo.findById(dbId)).rejects.toThrow(); - }); - }); + const foundDbAccountAfter = await accountRepo.findById(dbId); + expect(foundDbAccountAfter.password).not.toEqual(previousPasswordHash); }); - describe('deleteByUserId', () => { - describe('when delete an account by User Id', () => { - const setup = async () => { - const [dbId, idmId] = await createAccount(); - const foundIdmAccount = await identityManagementService.findAccountById(idmId); - const foundDbAccount = await accountRepo.findById(dbId); + it('delete should remove account', async () => { + if (!isIdmReachable) return; + const [dbId, idmId] = await createAccount(); + const foundIdmAccount = await identityManagementService.findAccountById(idmId); + expect(foundIdmAccount).toBeDefined(); + const foundDbAccount = await accountRepo.findById(dbId); + expect(foundDbAccount).toBeDefined(); - return { dbId, idmId, foundIdmAccount, foundDbAccount }; - }; - it('should remove account', async () => { - if (!isIdmReachable) return; - const { dbId, idmId, foundIdmAccount, foundDbAccount } = await setup(); + await accountService.delete(dbId); + await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); + await expect(accountRepo.findById(dbId)).rejects.toThrow(); + }); - expect(foundIdmAccount).toBeDefined(); - expect(foundDbAccount).toBeDefined(); + it('deleteByUserId should remove account', async () => { + if (!isIdmReachable) return; + const [dbId, idmId] = await createAccount(); + const foundAccount = await identityManagementService.findAccountById(idmId); + expect(foundAccount).toBeDefined(); + const foundDbAccount = await accountRepo.findById(dbId); + expect(foundDbAccount).toBeDefined(); - await accountService.deleteByUserId(testAccount.userId ?? ''); - await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); - await expect(accountRepo.findById(dbId)).rejects.toThrow(); - }); - }); + await accountService.deleteByUserId(testAccount.userId ?? ''); + await expect(identityManagementService.findAccountById(idmId)).rejects.toThrow(); + await expect(accountRepo.findById(dbId)).rejects.toThrow(); }); }); diff --git a/apps/server/src/modules/account/services/account.service.spec.ts b/apps/server/src/modules/account/services/account.service.spec.ts index 67851d0a6b0..4cb95d96a36 100644 --- a/apps/server/src/modules/account/services/account.service.spec.ts +++ b/apps/server/src/modules/account/services/account.service.spec.ts @@ -63,568 +63,402 @@ describe('AccountService', () => { }); describe('findById', () => { - describe('When calling findById in accountService', () => { - it('should call findById in accountServiceDb', async () => { - await expect(accountService.findById('id')).resolves.not.toThrow(); - expect(accountServiceDb.findById).toHaveBeenCalledTimes(1); - }); - }); - - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; - - it('should call idm implementation', async () => { - const service = setup(); - await expect(service.findById('accountId')).resolves.not.toThrow(); - expect(accountServiceIdm.findById).toHaveBeenCalledTimes(1); - }); + it('should call findById in accountServiceDb', async () => { + await expect(accountService.findById('id')).resolves.not.toThrow(); + expect(accountServiceDb.findById).toHaveBeenCalledTimes(1); }); }); describe('findByUserId', () => { - describe('When calling findByUserId in accountService', () => { - it('should call findByUserId in accountServiceDb', async () => { - await expect(accountService.findByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceDb.findByUserId).toHaveBeenCalledTimes(1); - }); - }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; - - it('should call idm implementation', async () => { - const service = setup(); - await expect(service.findByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceIdm.findByUserId).toHaveBeenCalledTimes(1); - }); + it('should call findByUserId in accountServiceDb', async () => { + await expect(accountService.findByUserId('userId')).resolves.not.toThrow(); + expect(accountServiceDb.findByUserId).toHaveBeenCalledTimes(1); }); }); describe('findByUsernameAndSystemId', () => { - describe('When calling findByUsernameAndSystemId in accountService', () => { - it('should call findByUsernameAndSystemId in accountServiceDb', async () => { - await expect(accountService.findByUsernameAndSystemId('username', 'systemId')).resolves.not.toThrow(); - expect(accountServiceDb.findByUsernameAndSystemId).toHaveBeenCalledTimes(1); - }); - }); - describe('when identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; - - it('should call idm implementation', async () => { - const service = setup(); - await expect(service.findByUsernameAndSystemId('username', 'systemId')).resolves.not.toThrow(); - expect(accountServiceIdm.findByUsernameAndSystemId).toHaveBeenCalledTimes(1); - }); + it('should call findByUsernameAndSystemId in accountServiceDb', async () => { + await expect(accountService.findByUsernameAndSystemId('username', 'systemId')).resolves.not.toThrow(); + expect(accountServiceDb.findByUsernameAndSystemId).toHaveBeenCalledTimes(1); }); }); describe('findMultipleByUserId', () => { - describe('When calling findMultipleByUserId in accountService', () => { - it('should call findMultipleByUserId in accountServiceDb', async () => { - await expect(accountService.findMultipleByUserId(['userId1, userId2'])).resolves.not.toThrow(); - expect(accountServiceDb.findMultipleByUserId).toHaveBeenCalledTimes(1); - }); + it('should call findMultipleByUserId in accountServiceDb', async () => { + await expect(accountService.findMultipleByUserId(['userId1, userId2'])).resolves.not.toThrow(); + expect(accountServiceDb.findMultipleByUserId).toHaveBeenCalledTimes(1); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + }); - it('should call idm implementation', async () => { - const service = setup(); - await expect(service.findMultipleByUserId(['userId'])).resolves.not.toThrow(); - expect(accountServiceIdm.findMultipleByUserId).toHaveBeenCalledTimes(1); - }); + describe('findByUserIdOrFail', () => { + it('should call findByUserIdOrFail in accountServiceDb', async () => { + await expect(accountService.findByUserIdOrFail('userId')).resolves.not.toThrow(); + expect(accountServiceDb.findByUserIdOrFail).toHaveBeenCalledTimes(1); }); }); - describe('findByUserIdOrFail', () => { - describe('When calling findByUserIdOrFail in accountService', () => { - it('should call findByUserIdOrFail in accountServiceDb', async () => { - await expect(accountService.findByUserIdOrFail('userId')).resolves.not.toThrow(); - expect(accountServiceDb.findByUserIdOrFail).toHaveBeenCalledTimes(1); - }); + describe('save', () => { + it('should call save in accountServiceDb', async () => { + await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); + expect(accountServiceDb.save).toHaveBeenCalledTimes(1); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + it('should call save in accountServiceIdm if feature is enabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); - it('should call idm implementation', async () => { - const service = setup(); - await expect(service.findByUserIdOrFail('userId')).resolves.not.toThrow(); - expect(accountServiceIdm.findByUserIdOrFail).toHaveBeenCalledTimes(1); - }); + await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); + expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); + }); + it('should not call save in accountServiceIdm if feature is disabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(false); + + await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); + expect(accountServiceIdm.save).not.toHaveBeenCalled(); }); }); - describe('save', () => { - describe('When calling save in accountService', () => { - it('should call save in accountServiceDb', async () => { - await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); - expect(accountServiceDb.save).toHaveBeenCalledTimes(1); - }); + describe('saveWithValidation', () => { + it('should not sanitize username for external user', async () => { + const spy = jest.spyOn(accountService, 'save'); + const params: AccountSaveDto = { + username: ' John.Doe@domain.tld ', + systemId: 'ABC123', + }; + await accountService.saveWithValidation(params); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + username: ' John.Doe@domain.tld ', + }) + ); + spy.mockRestore(); + }); + it('should throw if username for a local user is not an email', async () => { + const params: AccountSaveDto = { + username: 'John Doe', + password: 'JohnsPassword', + }; + await expect(accountService.saveWithValidation(params)).rejects.toThrow('Username is not an email'); }); - describe('When calling save in accountService if feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); + it('should not throw if username for an external user is not an email', async () => { + const params: AccountSaveDto = { + username: 'John Doe', + systemId: 'ABC123', }; - it('should call save in accountServiceIdm', async () => { - setup(); - - await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); - expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); - }); + await expect(accountService.saveWithValidation(params)).resolves.not.toThrow(); }); - describe('When calling save in accountService if feature is disabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(false); + it('should not throw if username for an external user is a ldap search string', async () => { + const params: AccountSaveDto = { + username: 'dc=schul-cloud,dc=org/fake.ldap', + systemId: 'ABC123', }; - it('should not call save in accountServiceIdm', async () => { - setup(); - - await expect(accountService.save({} as AccountSaveDto)).resolves.not.toThrow(); - expect(accountServiceIdm.save).not.toHaveBeenCalled(); - }); + await expect(accountService.saveWithValidation(params)).resolves.not.toThrow(); }); - describe('when identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); + it('should throw if no password is provided for an internal user', async () => { + const params: AccountSaveDto = { + username: 'john.doe@mail.tld', }; - it('should call idm implementation', async () => { - setup(); - await expect(accountService.save({ username: 'username' })).resolves.not.toThrow(); - expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); - }); + await expect(accountService.saveWithValidation(params)).rejects.toThrow('No password provided'); }); - }); - - describe('saveWithValidation', () => { - describe('When calling saveWithValidation on accountService', () => { - const setup = () => { - const spy = jest.spyOn(accountService, 'save'); - return spy; + it('should throw if account already exists', async () => { + const params: AccountSaveDto = { + username: 'john.doe@mail.tld', + password: 'JohnsPassword', + userId: 'userId123', + }; + accountServiceDb.findByUserId.mockResolvedValueOnce({ id: 'foundAccount123' } as AccountDto); + await expect(accountService.saveWithValidation(params)).rejects.toThrow('Account already exists'); + }); + it('should throw if username already exists', async () => { + const accountIsUniqueEmailSpy = jest.spyOn(accountValidationService, 'isUniqueEmail'); + accountIsUniqueEmailSpy.mockResolvedValueOnce(false); + const params: AccountSaveDto = { + username: 'john.doe@mail.tld', + password: 'JohnsPassword', }; - it('should not sanitize username for external user', async () => { - const spy = setup(); + await expect(accountService.saveWithValidation(params)).rejects.toThrow('Username already exists'); + }); + }); - const params: AccountSaveDto = { - username: ' John.Doe@domain.tld ', - systemId: 'ABC123', - }; - await accountService.saveWithValidation(params); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - username: ' John.Doe@domain.tld ', - }) - ); - spy.mockRestore(); - }); + describe('updateUsername', () => { + it('should call updateUsername in accountServiceDb', async () => { + await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); + expect(accountServiceDb.updateUsername).toHaveBeenCalledTimes(1); }); + it('should call updateUsername in accountServiceIdm if feature is enabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); - describe('When username for a local user is not an email', () => { - it('should throw username is not an email error', async () => { - const params: AccountSaveDto = { - username: 'John Doe', - password: 'JohnsPassword', - }; - await expect(accountService.saveWithValidation(params)).rejects.toThrow('Username is not an email'); - }); + await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); + expect(accountServiceIdm.updateUsername).toHaveBeenCalledTimes(1); }); + it('should not call updateUsername in accountServiceIdm if feature is disabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(false); - describe('When username for an external user is not an email', () => { - it('should not throw an error', async () => { - const params: AccountSaveDto = { - username: 'John Doe', - systemId: 'ABC123', - }; - await expect(accountService.saveWithValidation(params)).resolves.not.toThrow(); - }); + await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); + expect(accountServiceIdm.updateUsername).not.toHaveBeenCalled(); }); + }); - describe('When username for an external user is a ldap search string', () => { - it('should not throw an error', async () => { - const params: AccountSaveDto = { - username: 'dc=schul-cloud,dc=org/fake.ldap', - systemId: 'ABC123', - }; - await expect(accountService.saveWithValidation(params)).resolves.not.toThrow(); - }); + describe('updateLastTriedFailedLogin', () => { + it('should call updateLastTriedFailedLogin in accountServiceDb', async () => { + await expect(accountService.updateLastTriedFailedLogin('accountId', {} as Date)).resolves.not.toThrow(); + expect(accountServiceDb.updateLastTriedFailedLogin).toHaveBeenCalledTimes(1); }); + }); - describe('When no password is provided for an internal user', () => { - it('should throw no password provided error', async () => { - const params: AccountSaveDto = { - username: 'john.doe@mail.tld', - }; - await expect(accountService.saveWithValidation(params)).rejects.toThrow('No password provided'); - }); + describe('updatePassword', () => { + it('should call updatePassword in accountServiceDb', async () => { + await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); + expect(accountServiceDb.updatePassword).toHaveBeenCalledTimes(1); }); + it('should call updatePassword in accountServiceIdm if feature is enabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); - describe('When account already exists', () => { - it('should throw account already exists', async () => { - const params: AccountSaveDto = { - username: 'john.doe@mail.tld', - password: 'JohnsPassword', - userId: 'userId123', - }; - accountServiceDb.findByUserId.mockResolvedValueOnce({ id: 'foundAccount123' } as AccountDto); - await expect(accountService.saveWithValidation(params)).rejects.toThrow('Account already exists'); - }); + await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); + expect(accountServiceIdm.updatePassword).toHaveBeenCalledTimes(1); }); + it('should not call updatePassword in accountServiceIdm if feature is disabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(false); - describe('When username already exists', () => { - const setup = () => { - accountValidationService.isUniqueEmail.mockResolvedValueOnce(false); - }; - it('should throw username already exists', async () => { - setup(); - const params: AccountSaveDto = { - username: 'john.doe@mail.tld', - password: 'JohnsPassword', - }; - await expect(accountService.saveWithValidation(params)).rejects.toThrow('Username already exists'); - }); + await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); + expect(accountServiceIdm.updatePassword).not.toHaveBeenCalled(); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + }); - it('should call idm implementation', async () => { - setup(); - await expect( - accountService.saveWithValidation({ username: 'username@mail.tld', password: 'password' }) - ).resolves.not.toThrow(); - expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); - }); + describe('validatePassword', () => { + const setup = () => { + configService.get.mockReturnValue(true); + return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); + }; + it('should call validatePassword in accountServiceDb', async () => { + await expect(accountService.validatePassword({} as AccountDto, 'password')).resolves.not.toThrow(); + expect(accountServiceIdm.validatePassword).toHaveBeenCalledTimes(0); + expect(accountServiceDb.validatePassword).toHaveBeenCalledTimes(1); + }); + it('should call validatePassword in accountServiceIdm if feature is enabled', async () => { + const service = setup(); + await expect(service.validatePassword({} as AccountDto, 'password')).resolves.not.toThrow(); + expect(accountServiceDb.validatePassword).toHaveBeenCalledTimes(0); + expect(accountServiceIdm.validatePassword).toHaveBeenCalledTimes(1); }); }); - describe('updateUsername', () => { - describe('When calling updateUsername in accountService', () => { - it('should call updateUsername in accountServiceDb', async () => { - await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); - expect(accountServiceDb.updateUsername).toHaveBeenCalledTimes(1); - }); + describe('delete', () => { + it('should call delete in accountServiceDb', async () => { + await expect(accountService.delete('accountId')).resolves.not.toThrow(); + expect(accountServiceDb.delete).toHaveBeenCalledTimes(1); }); + it('should call delete in accountServiceIdm if feature is enabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); - describe('When calling updateUsername in accountService if idm feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - }; - it('should call updateUsername in accountServiceIdm', async () => { - setup(); + await expect(accountService.delete('accountId')).resolves.not.toThrow(); + expect(accountServiceIdm.delete).toHaveBeenCalledTimes(1); + }); + it('should not call delete in accountServiceIdm if feature is disabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(false); - await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); - expect(accountServiceIdm.updateUsername).toHaveBeenCalledTimes(1); - }); + await expect(accountService.delete('accountId')).resolves.not.toThrow(); + expect(accountServiceIdm.delete).not.toHaveBeenCalled(); }); + }); - describe('When calling updateUsername in accountService if idm feature is disabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(false); - }; - it('should not call updateUsername in accountServiceIdm', async () => { - setup(); + describe('deleteByUserId', () => { + it('should call deleteByUserId in accountServiceDb', async () => { + await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); + expect(accountServiceDb.deleteByUserId).toHaveBeenCalledTimes(1); + }); + it('should call deleteByUserId in accountServiceIdm if feature is enabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); - await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); - expect(accountServiceIdm.updateUsername).not.toHaveBeenCalled(); - }); + await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); + expect(accountServiceIdm.deleteByUserId).toHaveBeenCalledTimes(1); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + it('should not call deleteByUserId in accountServiceIdm if feature is disabled', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(false); - it('should call idm implementation', async () => { - setup(); - await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); - expect(accountServiceIdm.updateUsername).toHaveBeenCalledTimes(1); - }); + await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); + expect(accountServiceIdm.deleteByUserId).not.toHaveBeenCalled(); }); }); - describe('updateLastTriedFailedLogin', () => { - describe('When calling updateLastTriedFailedLogin in accountService', () => { - it('should call updateLastTriedFailedLogin in accountServiceDb', async () => { - await expect(accountService.updateLastTriedFailedLogin('accountId', {} as Date)).resolves.not.toThrow(); - expect(accountServiceDb.updateLastTriedFailedLogin).toHaveBeenCalledTimes(1); - }); + describe('findMany', () => { + it('should call findMany in accountServiceDb', async () => { + await expect(accountService.findMany()).resolves.not.toThrow(); + expect(accountServiceDb.findMany).toHaveBeenCalledTimes(1); }); - describe('when identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + }); - it('should call idm implementation', async () => { - setup(); - await expect(accountService.updateLastTriedFailedLogin('accountId', new Date())).resolves.not.toThrow(); - expect(accountServiceIdm.updateLastTriedFailedLogin).toHaveBeenCalledTimes(1); - }); + describe('searchByUsernamePartialMatch', () => { + it('should call searchByUsernamePartialMatch in accountServiceDb', async () => { + await expect(accountService.searchByUsernamePartialMatch('username', 1, 1)).resolves.not.toThrow(); + expect(accountServiceDb.searchByUsernamePartialMatch).toHaveBeenCalledTimes(1); }); }); - describe('updatePassword', () => { - describe('When calling updatePassword in accountService', () => { - it('should call updatePassword in accountServiceDb', async () => { - await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); - expect(accountServiceDb.updatePassword).toHaveBeenCalledTimes(1); - }); + describe('searchByUsernameExactMatch', () => { + it('should call searchByUsernameExactMatch in accountServiceDb', async () => { + await expect(accountService.searchByUsernameExactMatch('username')).resolves.not.toThrow(); + expect(accountServiceDb.searchByUsernameExactMatch).toHaveBeenCalledTimes(1); }); + }); - describe('When calling updatePassword in accountService if feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - }; - it('should call updatePassword in accountServiceIdm', async () => { - setup(); + describe('executeIdmMethod', () => { + it('should throw an error object', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); + const spyLogger = jest.spyOn(logger, 'error'); + const testError = new Error('error'); - await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); - expect(accountServiceIdm.updatePassword).toHaveBeenCalledTimes(1); + const deleteByUserIdMock = jest.spyOn(accountServiceIdm, 'deleteByUserId'); + deleteByUserIdMock.mockImplementationOnce(() => { + throw testError; }); + + await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); + expect(spyLogger).toHaveBeenCalledWith(testError, expect.anything()); }); - describe('When calling updatePassword in accountService if feature is disabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(false); - }; - it('should not call updatePassword in accountServiceIdm', async () => { - setup(); + it('should throw an non error object', async () => { + const spy = jest.spyOn(configService, 'get'); + spy.mockReturnValueOnce(true); + const spyLogger = jest.spyOn(logger, 'error'); - await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); - expect(accountServiceIdm.updatePassword).not.toHaveBeenCalled(); + const deleteByUserIdMock = jest.spyOn(accountServiceIdm, 'deleteByUserId'); + deleteByUserIdMock.mockImplementationOnce(() => { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw 'a non error object'; }); - }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; - it('should call idm implementation', async () => { - setup(); - await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); - expect(accountServiceIdm.updatePassword).toHaveBeenCalledTimes(1); - }); + await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); + expect(spyLogger).toHaveBeenCalledWith('a non error object'); }); }); - describe('validatePassword', () => { - describe('When calling validatePassword in accountService', () => { - it('should call validatePassword in accountServiceDb', async () => { - await expect(accountService.validatePassword({} as AccountDto, 'password')).resolves.not.toThrow(); - expect(accountServiceIdm.validatePassword).toHaveBeenCalledTimes(0); - expect(accountServiceDb.validatePassword).toHaveBeenCalledTimes(1); - }); - }); + describe('when identity management is primary', () => { + const setup = () => { + configService.get.mockReturnValue(true); + return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); + }; - describe('When calling validatePassword in accountService if feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; - it('should call validatePassword in accountServiceIdm', async () => { + describe('findById', () => { + it('should call idm implementation', async () => { const service = setup(); - await expect(service.validatePassword({} as AccountDto, 'password')).resolves.not.toThrow(); - expect(accountServiceDb.validatePassword).toHaveBeenCalledTimes(0); - expect(accountServiceIdm.validatePassword).toHaveBeenCalledTimes(1); + await expect(service.findById('accountId')).resolves.not.toThrow(); + expect(accountServiceIdm.findById).toHaveBeenCalledTimes(1); }); }); - }); - describe('delete', () => { - describe('When calling delete in accountService', () => { - it('should call delete in accountServiceDb', async () => { - await expect(accountService.delete('accountId')).resolves.not.toThrow(); - expect(accountServiceDb.delete).toHaveBeenCalledTimes(1); + describe('findMultipleByUserId', () => { + it('should call idm implementation', async () => { + const service = setup(); + await expect(service.findMultipleByUserId(['userId'])).resolves.not.toThrow(); + expect(accountServiceIdm.findMultipleByUserId).toHaveBeenCalledTimes(1); }); }); - describe('When calling delete in accountService if feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - }; - it('should call delete in accountServiceIdm', async () => { - setup(); - - await expect(accountService.delete('accountId')).resolves.not.toThrow(); - expect(accountServiceIdm.delete).toHaveBeenCalledTimes(1); + describe('findByUserId', () => { + it('should call idm implementation', async () => { + const service = setup(); + await expect(service.findByUserId('userId')).resolves.not.toThrow(); + expect(accountServiceIdm.findByUserId).toHaveBeenCalledTimes(1); }); }); - describe('When calling delete in accountService if feature is disabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(false); - }; - it('should not call delete in accountServiceIdm', async () => { - setup(); - - await expect(accountService.delete('accountId')).resolves.not.toThrow(); - expect(accountServiceIdm.delete).not.toHaveBeenCalled(); + describe('findByUserIdOrFail', () => { + it('should call idm implementation', async () => { + const service = setup(); + await expect(service.findByUserIdOrFail('userId')).resolves.not.toThrow(); + expect(accountServiceIdm.findByUserIdOrFail).toHaveBeenCalledTimes(1); }); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + describe('findByUsernameAndSystemId', () => { it('should call idm implementation', async () => { - setup(); - await expect(accountService.delete('accountId')).resolves.not.toThrow(); - expect(accountServiceIdm.delete).toHaveBeenCalledTimes(1); + const service = setup(); + await expect(service.findByUsernameAndSystemId('username', 'systemId')).resolves.not.toThrow(); + expect(accountServiceIdm.findByUsernameAndSystemId).toHaveBeenCalledTimes(1); }); }); - }); - describe('deleteByUserId', () => { - describe('When calling deleteByUserId in accountService', () => { - it('should call deleteByUserId in accountServiceDb', async () => { - await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceDb.deleteByUserId).toHaveBeenCalledTimes(1); + describe('searchByUsernamePartialMatch', () => { + it('should call idm implementation', async () => { + const service = setup(); + await expect(service.searchByUsernamePartialMatch('username', 0, 1)).resolves.not.toThrow(); + expect(accountServiceIdm.searchByUsernamePartialMatch).toHaveBeenCalledTimes(1); }); }); - describe('When calling deleteByUserId in accountService if feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - }; - it('should call deleteByUserId in accountServiceIdm', async () => { - setup(); - - await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceIdm.deleteByUserId).toHaveBeenCalledTimes(1); + describe('searchByUsernameExactMatch', () => { + it('should call idm implementation', async () => { + const service = setup(); + await expect(service.searchByUsernameExactMatch('username')).resolves.not.toThrow(); + expect(accountServiceIdm.searchByUsernameExactMatch).toHaveBeenCalledTimes(1); }); }); - describe('When calling deleteByUserId in accountService if feature is disabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(false); - }; - it('should not call deleteByUserId in accountServiceIdm', async () => { + describe('save', () => { + it('should call idm implementation', async () => { setup(); - - await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceIdm.deleteByUserId).not.toHaveBeenCalled(); + await expect(accountService.save({ username: 'username' })).resolves.not.toThrow(); + expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); }); }); - describe('When identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + describe('saveWithValidation', () => { it('should call idm implementation', async () => { setup(); - await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(accountServiceIdm.deleteByUserId).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('findMany', () => { - describe('When calling findMany in accountService', () => { - it('should call findMany in accountServiceDb', async () => { - await expect(accountService.findMany()).resolves.not.toThrow(); - expect(accountServiceDb.findMany).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('searchByUsernamePartialMatch', () => { - describe('When calling searchByUsernamePartialMatch in accountService', () => { - it('should call searchByUsernamePartialMatch in accountServiceDb', async () => { - await expect(accountService.searchByUsernamePartialMatch('username', 1, 1)).resolves.not.toThrow(); - expect(accountServiceDb.searchByUsernamePartialMatch).toHaveBeenCalledTimes(1); + await expect( + accountService.saveWithValidation({ username: 'username@mail.tld', password: 'password' }) + ).resolves.not.toThrow(); + expect(accountServiceIdm.save).toHaveBeenCalledTimes(1); }); }); - describe('when identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + describe('updateUsername', () => { it('should call idm implementation', async () => { - const service = setup(); - await expect(service.searchByUsernamePartialMatch('username', 0, 1)).resolves.not.toThrow(); - expect(accountServiceIdm.searchByUsernamePartialMatch).toHaveBeenCalledTimes(1); + setup(); + await expect(accountService.updateUsername('accountId', 'username')).resolves.not.toThrow(); + expect(accountServiceIdm.updateUsername).toHaveBeenCalledTimes(1); }); }); - }); - describe('searchByUsernameExactMatch', () => { - describe('When calling searchByUsernameExactMatch in accountService', () => { - it('should call searchByUsernameExactMatch in accountServiceDb', async () => { - await expect(accountService.searchByUsernameExactMatch('username')).resolves.not.toThrow(); - expect(accountServiceDb.searchByUsernameExactMatch).toHaveBeenCalledTimes(1); + describe('updateLastTriedFailedLogin', () => { + it('should call idm implementation', async () => { + setup(); + await expect(accountService.updateLastTriedFailedLogin('accountId', new Date())).resolves.not.toThrow(); + expect(accountServiceIdm.updateLastTriedFailedLogin).toHaveBeenCalledTimes(1); }); }); - describe('when identity management is primary', () => { - const setup = () => { - configService.get.mockReturnValue(true); - return new AccountService(accountServiceDb, accountServiceIdm, configService, accountValidationService, logger); - }; + describe('updatePassword', () => { it('should call idm implementation', async () => { - const service = setup(); - await expect(service.searchByUsernameExactMatch('username')).resolves.not.toThrow(); - expect(accountServiceIdm.searchByUsernameExactMatch).toHaveBeenCalledTimes(1); + setup(); + await expect(accountService.updatePassword('accountId', 'password')).resolves.not.toThrow(); + expect(accountServiceIdm.updatePassword).toHaveBeenCalledTimes(1); }); }); - }); - - describe('executeIdmMethod', () => { - describe('When idm feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - const testError = new Error('error'); - accountServiceIdm.deleteByUserId.mockImplementationOnce(() => { - throw testError; - }); - - const spyLogger = jest.spyOn(logger, 'error'); - return { testError, spyLogger }; - }; - it('should call executeIdmMethod and throw an error object', async () => { - const { testError, spyLogger } = setup(); - - await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(spyLogger).toHaveBeenCalledWith(testError, expect.anything()); + describe('delete', () => { + it('should call idm implementation', async () => { + setup(); + await expect(accountService.delete('accountId')).resolves.not.toThrow(); + expect(accountServiceIdm.delete).toHaveBeenCalledTimes(1); }); }); - describe('When idm feature is enabled', () => { - const setup = () => { - configService.get.mockReturnValueOnce(true); - const spyLogger = jest.spyOn(logger, 'error'); - const deleteByUserIdMock = jest.spyOn(accountServiceIdm, 'deleteByUserId'); - deleteByUserIdMock.mockImplementationOnce(() => { - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw 'a non error object'; - }); - return { spyLogger }; - }; - it('should call executeIdmMethod and throw an error object', async () => { - const { spyLogger } = setup(); - + describe('deleteByUserId', () => { + it('should call idm implementation', async () => { + setup(); await expect(accountService.deleteByUserId('userId')).resolves.not.toThrow(); - expect(spyLogger).toHaveBeenCalledWith('a non error object'); + expect(accountServiceIdm.deleteByUserId).toHaveBeenCalledTimes(1); }); }); }); diff --git a/apps/server/src/modules/account/services/account.service.ts b/apps/server/src/modules/account/services/account.service.ts index 3c8a5ff2058..6c2070550ab 100644 --- a/apps/server/src/modules/account/services/account.service.ts +++ b/apps/server/src/modules/account/services/account.service.ts @@ -4,8 +4,7 @@ import { ConfigService } from '@nestjs/config'; import { ValidationError } from '@shared/common'; import { Counted } from '@shared/domain'; import { isEmail, validateOrReject } from 'class-validator'; -import { LegacyLogger } from '../../../core/logger'; // TODO: use path alias -// TODO: account needs to define its own config, which is made available for the server +import { LegacyLogger } from '../../../core/logger'; import { IServerConfig } from '../../server/server.config'; import { AccountServiceDb } from './account-db.service'; import { AccountServiceIdm } from './account-idm.service'; @@ -13,11 +12,6 @@ import { AbstractAccountService } from './account.service.abstract'; import { AccountValidationService } from './account.validation.service'; import { AccountDto, AccountSaveDto } from './dto'; -/* TODO: extract a service that contains all things required by feathers, -which is responsible for the additionally required validation - -it should be clearly visible which functions are only needed for feathers, and easy to remove them */ - @Injectable() export class AccountService extends AbstractAccountService { private readonly accountImpl: AbstractAccountService; @@ -84,7 +78,6 @@ export class AccountService extends AbstractAccountService { } async saveWithValidation(dto: AccountSaveDto): Promise { - // TODO: move as much as possible into the class validator await validateOrReject(dto); // sanatizeUsername ✔ if (!dto.systemId) { @@ -115,7 +108,6 @@ export class AccountService extends AbstractAccountService { // dto.password = undefined; // } - // TODO: split validation from saving, so it can be used independently await this.save(dto); } diff --git a/apps/server/src/modules/account/services/account.validation.service.spec.ts b/apps/server/src/modules/account/services/account.validation.service.spec.ts index c152f01a59b..dba1e2bf02a 100644 --- a/apps/server/src/modules/account/services/account.validation.service.spec.ts +++ b/apps/server/src/modules/account/services/account.validation.service.spec.ts @@ -1,9 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { Permission, Role, RoleName } from '@shared/domain'; +import { EntityNotFoundError } from '@shared/common'; +import { Account, EntityId, Permission, Role, RoleName, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; import { accountFactory, setupEntities, systemFactory, userFactory } from '@shared/testing'; import { ObjectId } from 'bson'; -import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { AccountRepo } from '../repo/account.repo'; import { AccountValidationService } from './account.validation.service'; @@ -11,8 +11,26 @@ describe('AccountValidationService', () => { let module: TestingModule; let accountValidationService: AccountValidationService; - let userRepo: DeepMocked; - let accountRepo: DeepMocked; + let mockTeacherUser: User; + let mockTeacherAccount: Account; + + let mockStudentUser: User; + let mockStudentAccount: Account; + + let mockOtherTeacherUser: User; + let mockOtherTeacherAccount: Account; + + let mockAdminUser: User; + + let mockExternalUser: User; + let mockExternalUserAccount: Account; + let mockOtherExternalUser: User; + let mockOtherExternalUserAccount: Account; + + let oprhanAccount: Account; + + let mockUsers: User[]; + let mockAccounts: Account[]; afterAll(async () => { await module.close(); @@ -24,405 +42,237 @@ describe('AccountValidationService', () => { AccountValidationService, { provide: AccountRepo, - useValue: createMock(), + useValue: { + findById: jest.fn().mockImplementation((accountId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.id === accountId); + + if (account) { + return Promise.resolve(account); + } + throw new EntityNotFoundError(Account.name); + }), + searchByUsernameExactMatch: jest + .fn() + .mockImplementation((username: string): Promise<[Account[], number]> => { + const account = mockAccounts.find((tempAccount) => tempAccount.username === username); + + if (account) { + return Promise.resolve([[account], 1]); + } + if (username === 'not@available.username') { + return Promise.resolve([[mockOtherTeacherAccount], 1]); + } + if (username === 'multiple@account.username') { + return Promise.resolve([mockAccounts, mockAccounts.length]); + } + return Promise.resolve([[], 0]); + }), + findByUserId: (userId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.userId?.toString() === userId); + if (account) { + return Promise.resolve(account); + } + return Promise.resolve(null); + }, + }, }, { provide: UserRepo, - useValue: createMock(), + useValue: { + findById: jest.fn().mockImplementation((userId: EntityId): Promise => { + const user = mockUsers.find((tempUser) => tempUser.id === userId); + if (user) { + return Promise.resolve(user); + } + throw new EntityNotFoundError(User.name); + }), + findByEmail: jest.fn().mockImplementation((email: string): Promise => { + const user = mockUsers.find((tempUser) => tempUser.email === email); + + if (user) { + return Promise.resolve([user]); + } + if (email === 'multiple@user.email') { + return Promise.resolve(mockUsers); + } + return Promise.resolve([]); + }), + }, }, ], }).compile(); accountValidationService = module.get(AccountValidationService); - - userRepo = module.get(UserRepo); - accountRepo = module.get(AccountRepo); - await setupEntities(); }); beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('isUniqueEmail', () => { - describe('When new email is available', () => { - const setup = () => { - userRepo.findByEmail.mockResolvedValueOnce([]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[], 0]); - }; - it('should return true', async () => { - setup(); - - const res = await accountValidationService.isUniqueEmail('an@available.email'); - expect(res).toBe(true); - }); + mockTeacherUser = userFactory.buildWithId({ + roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], }); - - describe('When new email is available', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[], 0]); - - return { mockStudentUser }; - }; - it('should return true and ignore current user', async () => { - const { mockStudentUser } = setup(); - const res = await accountValidationService.isUniqueEmail(mockStudentUser.email, mockStudentUser.id); - expect(res).toBe(true); - }); + mockStudentUser = userFactory.buildWithId({ + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - - describe('When new email is available', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockStudentAccount], 1]); - - return { mockStudentUser, mockStudentAccount }; - }; - it('should return true and ignore current users account', async () => { - const { mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - mockStudentAccount.username, - mockStudentUser.id, - mockStudentAccount.id - ); - expect(res).toBe(true); - }); + mockOtherTeacherUser = userFactory.buildWithId({ + roles: [ + new Role({ + name: RoleName.TEACHER, + permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], + }), + ], }); - - describe('When new email already in use by another user', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockAdminUser = userFactory.buildWithId({ - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockAdminAccount = accountFactory.buildWithId({ userId: mockAdminUser.id }); - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockAdminUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockAdminAccount], 1]); - - return { mockAdminUser, mockStudentUser, mockStudentAccount }; - }; - it('should return false', async () => { - const { mockAdminUser, mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - mockAdminUser.email, - mockStudentUser.id, - mockStudentAccount.id - ); - expect(res).toBe(false); - }); + mockExternalUser = userFactory.buildWithId({ + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - - describe('When new email already in use by any user and system id is given', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], - }); - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ userId: mockTeacherUser.id }); - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockTeacherUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockTeacherAccount], 1]); - - return { mockTeacherAccount, mockStudentUser, mockStudentAccount }; - }; - it('should return false', async () => { - const { mockTeacherAccount, mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - mockTeacherAccount.username, - mockStudentUser.id, - mockStudentAccount.id, - mockStudentAccount.systemId?.toString() - ); - expect(res).toBe(false); - }); + mockOtherExternalUser = userFactory.buildWithId({ + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - describe('When new email already in use by multiple users', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], - }); - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockOtherTeacherUser = userFactory.buildWithId({ - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - const mockUsers = [mockTeacherUser, mockStudentUser, mockOtherTeacherUser]; - - userRepo.findByEmail.mockResolvedValueOnce(mockUsers); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[], 0]); + mockTeacherAccount = accountFactory.buildWithId({ userId: mockTeacherUser.id }); + mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); + mockOtherTeacherAccount = accountFactory.buildWithId({ + userId: mockOtherTeacherUser.id, + }); + const externalSystemA = systemFactory.buildWithId(); + const externalSystemB = systemFactory.buildWithId(); + mockExternalUserAccount = accountFactory.buildWithId({ + userId: mockExternalUser.id, + username: 'unique.within@system', + systemId: externalSystemA.id, + }); + mockOtherExternalUserAccount = accountFactory.buildWithId({ + userId: mockOtherExternalUser.id, + username: 'unique.within@system', + systemId: externalSystemB.id, + }); - return { mockStudentUser, mockStudentAccount }; - }; - it('should return false', async () => { - const { mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - 'multiple@user.email', - mockStudentUser.id, - mockStudentAccount.id, - mockStudentAccount.systemId?.toString() - ); - expect(res).toBe(false); - }); + oprhanAccount = accountFactory.buildWithId({ + username: 'orphan@account', + userId: undefined, + systemId: new ObjectId(), }); - describe('When new email already in use by multiple accounts', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], - }); - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockOtherTeacherUser = userFactory.buildWithId({ - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), + mockAccounts = [ + mockTeacherAccount, + mockStudentAccount, + mockOtherTeacherAccount, + mockExternalUserAccount, + mockOtherExternalUserAccount, + oprhanAccount, + ]; + mockAdminUser = userFactory.buildWithId({ + roles: [ + new Role({ + name: RoleName.ADMINISTRATOR, + permissions: [ + Permission.TEACHER_EDIT, + Permission.STUDENT_EDIT, + Permission.STUDENT_LIST, + Permission.TEACHER_LIST, + Permission.TEACHER_CREATE, + Permission.STUDENT_CREATE, + Permission.TEACHER_DELETE, + Permission.STUDENT_DELETE, ], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ userId: mockTeacherUser.id }); - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - const mockOtherTeacherAccount = accountFactory.buildWithId({ - userId: mockOtherTeacherUser.id, - }); - - const mockAccounts = [mockTeacherAccount, mockStudentAccount, mockOtherTeacherAccount]; - userRepo.findByEmail.mockResolvedValueOnce([]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([mockAccounts, mockAccounts.length]); - - return { mockStudentUser, mockStudentAccount }; - }; - it('should return false', async () => { - const { mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - 'multiple@account.username', - mockStudentUser.id, - mockStudentAccount.id, - mockStudentAccount.systemId?.toString() - ); - expect(res).toBe(false); - }); + }), + ], }); + mockUsers = [ + mockTeacherUser, + mockStudentUser, + mockOtherTeacherUser, + mockAdminUser, + mockExternalUser, + mockOtherExternalUser, + ]; + }); - describe('When its another system', () => { - const setup = () => { - const mockExternalUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockOtherExternalUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const externalSystemA = systemFactory.buildWithId(); - const externalSystemB = systemFactory.buildWithId(); - const mockExternalUserAccount = accountFactory.buildWithId({ - userId: mockExternalUser.id, - username: 'unique.within@system', - systemId: externalSystemA.id, - }); - const mockOtherExternalUserAccount = accountFactory.buildWithId({ - userId: mockOtherExternalUser.id, - username: 'unique.within@system', - systemId: externalSystemB.id, - }); - - userRepo.findByEmail.mockResolvedValueOnce([mockExternalUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockExternalUserAccount], 1]); - - return { mockExternalUser, mockExternalUserAccount, mockOtherExternalUserAccount }; - }; - it('should ignore existing username', async () => { - const { mockExternalUser, mockExternalUserAccount, mockOtherExternalUserAccount } = setup(); - const res = await accountValidationService.isUniqueEmail( - mockExternalUser.email, - mockExternalUser.id, - mockExternalUserAccount.id, - mockOtherExternalUserAccount.systemId?.toString() - ); - expect(res).toBe(true); - }); + describe('isUniqueEmail', () => { + it('should return true if new email is available', async () => { + const res = await accountValidationService.isUniqueEmail('an@available.email'); + expect(res).toBe(true); + }); + it('should return true if new email is available and ignore current user', async () => { + const res = await accountValidationService.isUniqueEmail(mockStudentUser.email, mockStudentUser.id); + expect(res).toBe(true); + }); + it('should return true if new email is available and ignore current users account', async () => { + const res = await accountValidationService.isUniqueEmail( + mockStudentAccount.username, + mockStudentUser.id, + mockStudentAccount.id + ); + expect(res).toBe(true); + }); + it('should return false if new email already in use by another user', async () => { + const res = await accountValidationService.isUniqueEmail( + mockAdminUser.email, + mockStudentUser.id, + mockStudentAccount.id + ); + expect(res).toBe(false); + }); + it('should return false if new email is already in use by any user, system id is given', async () => { + const res = await accountValidationService.isUniqueEmail( + mockTeacherAccount.username, + mockStudentUser.id, + mockStudentAccount.id, + mockStudentAccount.systemId?.toString() + ); + expect(res).toBe(false); + }); + it('should return false if new email already in use by multiple users', async () => { + const res = await accountValidationService.isUniqueEmail( + 'multiple@user.email', + mockStudentUser.id, + mockStudentAccount.id, + mockStudentAccount.systemId?.toString() + ); + expect(res).toBe(false); + }); + it('should return false if new email already in use by multiple accounts', async () => { + const res = await accountValidationService.isUniqueEmail( + 'multiple@account.username', + mockStudentUser.id, + mockStudentAccount.id, + mockStudentAccount.systemId?.toString() + ); + expect(res).toBe(false); + }); + it('should ignore existing username if other system', async () => { + const res = await accountValidationService.isUniqueEmail( + mockExternalUser.email, + mockExternalUser.id, + mockExternalUserAccount.id, + mockOtherExternalUserAccount.systemId?.toString() + ); + expect(res).toBe(true); }); }); describe('isUniqueEmailForUser', () => { - describe('When its the email of the given user', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockStudentAccount], 1]); - accountRepo.findByUserId.mockResolvedValueOnce(mockStudentAccount); - - return { mockStudentUser }; - }; - it('should return true', async () => { - const { mockStudentUser } = setup(); - const res = await accountValidationService.isUniqueEmailForUser(mockStudentUser.email, mockStudentUser.id); - expect(res).toBe(true); - }); + it('should return true, if its the email of the given user', async () => { + const res = await accountValidationService.isUniqueEmailForUser(mockStudentUser.email, mockStudentUser.id); + expect(res).toBe(true); }); - - describe('When its not the given users email', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - const mockAdminUser = userFactory.buildWithId({ - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockAdminAccount = accountFactory.buildWithId({ userId: mockAdminUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockStudentAccount], 1]); - accountRepo.findByUserId.mockResolvedValueOnce(mockAdminAccount); - - return { mockStudentUser, mockAdminUser }; - }; - it('should return false', async () => { - const { mockStudentUser, mockAdminUser } = setup(); - const res = await accountValidationService.isUniqueEmailForUser(mockStudentUser.email, mockAdminUser.id); - expect(res).toBe(false); - }); + it('should return false, if not the given users email', async () => { + const res = await accountValidationService.isUniqueEmailForUser(mockStudentUser.email, mockAdminUser.id); + expect(res).toBe(false); }); }); describe('isUniqueEmailForAccount', () => { - describe('When its the email of the given user', () => { - const setup = () => { - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockStudentAccount], 1]); - accountRepo.findById.mockResolvedValueOnce(mockStudentAccount); - - return { mockStudentUser, mockStudentAccount }; - }; - it('should return true', async () => { - const { mockStudentUser, mockStudentAccount } = setup(); - const res = await accountValidationService.isUniqueEmailForAccount( - mockStudentUser.email, - mockStudentAccount.id - ); - expect(res).toBe(true); - }); + it('should return true, if its the email of the given user', async () => { + const res = await accountValidationService.isUniqueEmailForAccount(mockStudentUser.email, mockStudentAccount.id); + expect(res).toBe(true); }); - describe('When its not the given users email', () => { - const setup = () => { - const mockTeacherUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.TEACHER, permissions: [Permission.STUDENT_EDIT] })], - }); - const mockStudentUser = userFactory.buildWithId({ - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ userId: mockTeacherUser.id }); - const mockStudentAccount = accountFactory.buildWithId({ userId: mockStudentUser.id }); - - userRepo.findByEmail.mockResolvedValueOnce([mockStudentUser]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[mockStudentAccount], 1]); - accountRepo.findById.mockResolvedValueOnce(mockTeacherAccount); - - return { mockStudentUser, mockTeacherAccount }; - }; - it('should return false', async () => { - const { mockStudentUser, mockTeacherAccount } = setup(); - const res = await accountValidationService.isUniqueEmailForAccount( - mockStudentUser.email, - mockTeacherAccount.id - ); - expect(res).toBe(false); - }); + it('should return false, if not the given users email', async () => { + const res = await accountValidationService.isUniqueEmailForAccount(mockStudentUser.email, mockTeacherAccount.id); + expect(res).toBe(false); }); - - describe('When user is missing in account', () => { - const setup = () => { - const oprhanAccount = accountFactory.buildWithId({ - username: 'orphan@account', - userId: undefined, - systemId: new ObjectId(), - }); - - userRepo.findByEmail.mockResolvedValueOnce([]); - accountRepo.searchByUsernameExactMatch.mockResolvedValueOnce([[], 0]); - accountRepo.findById.mockResolvedValueOnce(oprhanAccount); - - return { oprhanAccount }; - }; - it('should ignore missing user for given account', async () => { - const { oprhanAccount } = setup(); - const res = await accountValidationService.isUniqueEmailForAccount(oprhanAccount.username, oprhanAccount.id); - expect(res).toBe(true); - }); + it('should ignore missing user for a given account', async () => { + const res = await accountValidationService.isUniqueEmailForAccount(oprhanAccount.username, oprhanAccount.id); + expect(res).toBe(true); }); }); }); diff --git a/apps/server/src/modules/account/services/account.validation.service.ts b/apps/server/src/modules/account/services/account.validation.service.ts index 2cabc9eabb3..fc47569ed71 100644 --- a/apps/server/src/modules/account/services/account.validation.service.ts +++ b/apps/server/src/modules/account/services/account.validation.service.ts @@ -5,11 +5,9 @@ import { AccountEntityToDtoMapper } from '../mapper/account-entity-to-dto.mapper import { AccountRepo } from '../repo/account.repo'; @Injectable() -// TODO: naming? export class AccountValidationService { constructor(private accountRepo: AccountRepo, private userRepo: UserRepo) {} - // TODO: this should be refactored and rewritten more nicely async isUniqueEmail(email: string, userId?: EntityId, accountId?: EntityId, systemId?: EntityId): Promise { const [foundUsers, [accounts]] = await Promise.all([ // Test coverage: Missing branch null check; unreachable @@ -29,12 +27,12 @@ export class AccountValidationService { } async isUniqueEmailForUser(email: string, userId: EntityId): Promise { - const account = await this.accountRepo.findByUserId(userId); // TODO: findOrFail? + const account = await this.accountRepo.findByUserId(userId); return this.isUniqueEmail(email, userId, account?.id, account?.systemId?.toString()); } async isUniqueEmailForAccount(email: string, accountId: EntityId): Promise { - const account = await this.accountRepo.findById(accountId); // TODO: findOrFail? + const account = await this.accountRepo.findById(accountId); return this.isUniqueEmail(email, account.userId?.toString(), account.id, account?.systemId?.toString()); } } diff --git a/apps/server/src/modules/account/services/dto/account.dto.ts b/apps/server/src/modules/account/services/dto/account.dto.ts index c3765576e50..760be1f2453 100644 --- a/apps/server/src/modules/account/services/dto/account.dto.ts +++ b/apps/server/src/modules/account/services/dto/account.dto.ts @@ -1,7 +1,6 @@ import { EntityId } from '@shared/domain'; import { AccountSaveDto } from './account-save.dto'; -// TODO: this vs account-save.dto? please clean up :) export class AccountDto extends AccountSaveDto { readonly id: EntityId; diff --git a/apps/server/src/modules/account/uc/account.uc.spec.ts b/apps/server/src/modules/account/uc/account.uc.spec.ts index 526720c43f7..aa4cdf56a82 100644 --- a/apps/server/src/modules/account/uc/account.uc.spec.ts +++ b/apps/server/src/modules/account/uc/account.uc.spec.ts @@ -4,11 +4,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthorizationError, EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; import { Account, + Counted, EntityId, Permission, PermissionService, Role, RoleName, + SchoolEntity, SchoolRolePermission, SchoolRoles, User, @@ -35,19 +37,62 @@ import { AccountUc } from './account.uc'; describe('AccountUc', () => { let module: TestingModule; let accountUc: AccountUc; - let userRepo: DeepMocked; - let accountService: DeepMocked; - let accountValidationService: DeepMocked; + let userRepo: UserRepo; + let accountService: AccountService; + let accountValidationService: AccountValidationService; let configService: DeepMocked; + let mockSchool: SchoolEntity; + let mockOtherSchool: SchoolEntity; + let mockSchoolWithStudentVisibility: SchoolEntity; + + let mockSuperheroUser: User; + let mockAdminUser: User; + let mockTeacherUser: User; + let mockOtherTeacherUser: User; + let mockTeacherNoUserNoSchoolPermissionUser: User; + let mockTeacherNoUserPermissionUser: User; + let mockStudentSchoolPermissionUser: User; + let mockStudentUser: User; + let mockOtherStudentUser: User; + let mockDifferentSchoolAdminUser: User; + let mockDifferentSchoolTeacherUser: User; + let mockDifferentSchoolStudentUser: User; + let mockUnknownRoleUser: User; + let mockExternalUser: User; + let mockUserWithoutAccount: User; + let mockUserWithoutRole: User; + let mockStudentUserWithoutAccount: User; + let mockOtherStudentSchoolPermissionUser: User; + + let mockSuperheroAccount: Account; + let mockTeacherAccount: Account; + let mockOtherTeacherAccount: Account; + let mockTeacherNoUserPermissionAccount: Account; + let mockTeacherNoUserNoSchoolPermissionAccount: Account; + let mockAdminAccount: Account; + let mockStudentAccount: Account; + let mockStudentSchoolPermissionAccount: Account; + let mockDifferentSchoolAdminAccount: Account; + let mockDifferentSchoolTeacherAccount: Account; + let mockDifferentSchoolStudentAccount: Account; + let mockUnknownRoleUserAccount: Account; + let mockExternalUserAccount: Account; + let mockAccountWithoutRole: Account; + let mockAccountWithoutUser: Account; + let mockAccountWithSystemId: Account; + let mockAccountWithLastFailedLogin: Account; + let mockAccountWithOldLastFailedLogin: Account; + let mockAccountWithNoLastFailedLogin: Account; + let mockAccounts: Account[]; + let mockUsers: User[]; + const defaultPassword = 'DummyPasswd!1'; const otherPassword = 'DummyPasswd!2'; const defaultPasswordHash = '$2a$10$/DsztV5o6P5piW2eWJsxw.4nHovmJGBA.QNwiTmuZ/uvUc40b.Uhu'; const LOGIN_BLOCK_TIME = 15; afterAll(async () => { - jest.restoreAllMocks(); - jest.resetAllMocks(); await module.close(); }); @@ -57,7 +102,103 @@ describe('AccountUc', () => { AccountUc, { provide: AccountService, - useValue: createMock(), + useValue: { + saveWithValidation: jest.fn().mockImplementation((account: AccountDto): Promise => { + if (account.username === 'fail@to.update') { + return Promise.reject(); + } + const accountEntity = mockAccounts.find( + (tempAccount) => tempAccount.userId?.toString() === account.userId + ); + if (accountEntity) { + Object.assign(accountEntity, account); + return Promise.resolve(); + } + return Promise.reject(); + }), + save: jest.fn().mockImplementation((account: AccountDto): Promise => { + if (account.username === 'fail@to.update') { + return Promise.reject(); + } + const accountEntity = mockAccounts.find( + (tempAccount) => tempAccount.userId?.toString() === account.userId + ); + if (accountEntity) { + Object.assign(accountEntity, account); + return Promise.resolve(); + } + return Promise.reject(); + }), + delete: (id: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.id?.toString() === id); + + if (account) { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(account)); + } + throw new EntityNotFoundError(Account.name); + }, + create: (): Promise => Promise.resolve(), + findByUserId: (userId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.userId?.toString() === userId); + + if (account) { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(account)); + } + return Promise.resolve(null); + }, + findByUserIdOrFail: (userId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.userId?.toString() === userId); + + if (account) { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(account)); + } + if (userId === 'accountWithoutUser') { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); + } + throw new EntityNotFoundError(Account.name); + }, + findById: (accountId: EntityId): Promise => { + const account = mockAccounts.find((tempAccount) => tempAccount.id === accountId); + + if (account) { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(account)); + } + throw new EntityNotFoundError(Account.name); + }, + findByUsernameAndSystemId: (username: string, systemId: EntityId | ObjectId): Promise => { + const account = mockAccounts.find( + (tempAccount) => tempAccount.username === username && tempAccount.systemId === systemId + ); + if (account) { + return Promise.resolve(AccountEntityToDtoMapper.mapToDto(account)); + } + throw new EntityNotFoundError(Account.name); + }, + searchByUsernameExactMatch: (username: string): Promise> => { + const account = mockAccounts.find((tempAccount) => tempAccount.username === username); + + if (account) { + return Promise.resolve([[AccountEntityToDtoMapper.mapToDto(account)], 1]); + } + if (username === 'not@available.username') { + return Promise.resolve([[AccountEntityToDtoMapper.mapToDto(mockOtherTeacherAccount)], 1]); + } + if (username === 'multiple@account.username') { + return Promise.resolve([ + mockAccounts.map((mockAccount) => AccountEntityToDtoMapper.mapToDto(mockAccount)), + mockAccounts.length, + ]); + } + return Promise.resolve([[], 0]); + }, + searchByUsernamePartialMatch: (): Promise> => + Promise.resolve([ + mockAccounts.map((mockAccount) => AccountEntityToDtoMapper.mapToDto(mockAccount)), + mockAccounts.length, + ]), + updateLastTriedFailedLogin: jest.fn(), + validatePassword: jest.fn().mockResolvedValue(true), + }, }, { provide: ConfigService, @@ -65,12 +206,42 @@ describe('AccountUc', () => { }, { provide: UserRepo, - useValue: createMock(), + useValue: { + findById: (userId: EntityId): Promise => { + const user = mockUsers.find((tempUser) => tempUser.id === userId); + if (user) { + return Promise.resolve(user); + } + throw new EntityNotFoundError(User.name); + }, + findByEmail: (email: string): Promise => { + const user = mockUsers.find((tempUser) => tempUser.email === email); + + if (user) { + return Promise.resolve([user]); + } + if (email === 'not@available.email') { + return Promise.resolve([mockExternalUser]); + } + if (email === 'multiple@user.email') { + return Promise.resolve(mockUsers); + } + return Promise.resolve([]); + }, + save: jest.fn().mockImplementation((user: User): Promise => { + if (user.firstName === 'failToUpdate' || user.email === 'user-fail@to.update') { + return Promise.reject(); + } + return Promise.resolve(); + }), + }, }, PermissionService, { provide: AccountValidationService, - useValue: createMock(), + useValue: { + isUniqueEmail: jest.fn().mockResolvedValue(true), + }, }, ], }).compile(); @@ -78,3052 +249,983 @@ describe('AccountUc', () => { accountUc = module.get(AccountUc); userRepo = module.get(UserRepo); accountService = module.get(AccountService); + await setupEntities(); accountValidationService = module.get(AccountValidationService); configService = module.get(ConfigService); - await setupEntities(); }); beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - describe('updateMyAccount', () => { - describe('When user does not exist', () => { - const setup = () => { - userRepo.findById.mockImplementation(() => { - throw new EntityNotFoundError(User.name); - }); - }; - - it('should throw EntityNotFoundError', async () => { - setup(); - await expect(accountUc.updateMyAccount('accountWithoutUser', { passwordOld: defaultPassword })).rejects.toThrow( - EntityNotFoundError - ); - }); + mockSchool = schoolFactory.buildWithId(); + mockOtherSchool = schoolFactory.buildWithId(); + mockSchoolWithStudentVisibility = schoolFactory.buildWithId(); + mockSchoolWithStudentVisibility.permissions = new SchoolRoles(); + mockSchoolWithStudentVisibility.permissions.teacher = new SchoolRolePermission(); + mockSchoolWithStudentVisibility.permissions.teacher.STUDENT_LIST = true; + + mockSuperheroUser = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.SUPERHERO, + permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], + }), + ], }); - - describe('When account does not exists', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockUserWithoutAccount = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), + mockAdminUser = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.ADMINISTRATOR, + permissions: [ + Permission.TEACHER_EDIT, + Permission.STUDENT_EDIT, + Permission.STUDENT_LIST, + Permission.TEACHER_LIST, + Permission.TEACHER_CREATE, + Permission.STUDENT_CREATE, + Permission.TEACHER_DELETE, + Permission.STUDENT_DELETE, ], - }); - - accountService.findByUserIdOrFail.mockImplementation((): Promise => { - throw new EntityNotFoundError(Account.name); - }); - - return { mockUserWithoutAccount }; - }; - - it('should throw entity not found error', async () => { - const { mockUserWithoutAccount } = setup(); - await expect( - accountUc.updateMyAccount(mockUserWithoutAccount.id, { - passwordOld: defaultPassword, - }) - ).rejects.toThrow(EntityNotFoundError); - }); + }), + ], }); - describe('When account is external', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockExternalUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const externalSystem = systemFactory.buildWithId(); - const mockExternalUserAccount = accountFactory.buildWithId({ - userId: mockExternalUser.id, - password: defaultPasswordHash, - systemId: externalSystem.id, - }); - - accountService.findByUserIdOrFail.mockResolvedValueOnce( - AccountEntityToDtoMapper.mapToDto(mockExternalUserAccount) - ); - - return { mockExternalUserAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockExternalUserAccount } = setup(); - - await expect( - accountUc.updateMyAccount(mockExternalUserAccount.userId?.toString() ?? '', { - passwordOld: defaultPassword, - }) - ).rejects.toThrow(ForbiddenOperationError); - }); + mockTeacherUser = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.TEACHER, + permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], + }), + ], }); - - describe('When password does not match', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(false); - - return { mockStudentUser }; - }; - it('should throw AuthorizationError', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: 'DoesNotMatch', - }) - ).rejects.toThrow(AuthorizationError); - }); + mockOtherTeacherUser = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.TEACHER, + permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], + }), + ], }); - - describe('When changing own name is not allowed', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockStudentUser }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - firstName: 'newFirstName', - }) - ).rejects.toThrow(ForbiddenOperationError); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - lastName: 'newLastName', - }) - ).rejects.toThrow(ForbiddenOperationError); - }); + mockTeacherNoUserPermissionUser = userFactory.buildWithId({ + school: mockSchoolWithStudentVisibility, + roles: [ + new Role({ + name: RoleName.TEACHER, + permissions: [], + }), + ], }); - - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockStudentUser }; - }; - it('should allow to update email', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: 'an@available.mail', - }) - ).resolves.not.toThrow(); - }); + mockTeacherNoUserNoSchoolPermissionUser = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.TEACHER, + permissions: [], + }), + ], }); - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - accountValidationService.isUniqueEmail.mockResolvedValueOnce(true); - const accountSaveSpy = jest.spyOn(accountService, 'save'); - - return { mockStudentUser, accountSaveSpy }; - }; - it('should use email as account user name in lower case', async () => { - const { mockStudentUser, accountSaveSpy } = setup(); - - const testMail = 'AN@AVAILABLE.MAIL'; - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: testMail, - }) - ).resolves.not.toThrow(); - expect(accountSaveSpy).toBeCalledWith(expect.objectContaining({ username: testMail.toLowerCase() })); - }); + mockStudentSchoolPermissionUser = userFactory.buildWithId({ + school: mockSchoolWithStudentVisibility, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - accountValidationService.isUniqueEmail.mockResolvedValueOnce(true); - - const userUpdateSpy = jest.spyOn(userRepo, 'save'); - - return { mockStudentUser, userUpdateSpy }; - }; - it('should use email as user email in lower case', async () => { - const { mockStudentUser, userUpdateSpy } = setup(); - const testMail = 'AN@AVAILABLE.MAIL'; - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: testMail, - }) - ).resolves.not.toThrow(); - expect(userUpdateSpy).toBeCalledWith(expect.objectContaining({ email: testMail.toLowerCase() })); - }); + mockOtherStudentSchoolPermissionUser = userFactory.buildWithId({ + school: mockSchoolWithStudentVisibility, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - accountValidationService.isUniqueEmail.mockResolvedValueOnce(true); - - const accountSaveSpy = jest.spyOn(accountService, 'save'); - const userUpdateSpy = jest.spyOn(userRepo, 'save'); - - return { mockStudentUser, accountSaveSpy, userUpdateSpy }; - }; - it('should always update account user name AND user email together.', async () => { - const { mockStudentUser, accountSaveSpy, userUpdateSpy } = setup(); - const testMail = 'an@available.mail'; - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: testMail, - }) - ).resolves.not.toThrow(); - expect(userUpdateSpy).toBeCalledWith(expect.objectContaining({ email: testMail.toLowerCase() })); - expect(accountSaveSpy).toBeCalledWith(expect.objectContaining({ username: testMail.toLowerCase() })); - }); + mockStudentUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - accountValidationService.isUniqueEmail.mockResolvedValueOnce(false); - - return { mockStudentUser }; - }; - it('should throw if new email already in use', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: 'already@in.use', - }) - ).rejects.toThrow(ValidationError); - }); + mockOtherStudentUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - - describe('When using student user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockStudentUser }; - }; - it('should allow to update with strong password', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - passwordNew: otherPassword, - }) - ).resolves.not.toThrow(); - }); + mockDifferentSchoolAdminUser = userFactory.buildWithId({ + school: mockOtherSchool, + roles: [...mockAdminUser.roles], }); - - describe('When using teacher user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockTeacherUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockTeacherUser }; - }; - it('should allow to update first and last name', async () => { - const { mockTeacherUser } = setup(); - await expect( - accountUc.updateMyAccount(mockTeacherUser.id, { - passwordOld: defaultPassword, - firstName: 'newFirstName', - }) - ).resolves.not.toThrow(); - await expect( - accountUc.updateMyAccount(mockTeacherUser.id, { - passwordOld: defaultPassword, - lastName: 'newLastName', - }) - ).resolves.not.toThrow(); - }); + mockDifferentSchoolTeacherUser = userFactory.buildWithId({ + school: mockOtherSchool, + roles: [...mockTeacherUser.roles], }); - - describe('When using admin user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - - const mockAdminAccount = accountFactory.buildWithId({ - userId: mockAdminUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockAdminUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockAdminAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockAdminUser }; - }; - it('should allow to update first and last name', async () => { - const { mockAdminUser } = setup(); - await expect( - accountUc.updateMyAccount(mockAdminUser.id, { - passwordOld: defaultPassword, - firstName: 'newFirstName', - }) - ).resolves.not.toThrow(); - await expect( - accountUc.updateMyAccount(mockAdminUser.id, { - passwordOld: defaultPassword, - lastName: 'newLastName', - }) - ).resolves.not.toThrow(); - }); + mockDifferentSchoolStudentUser = userFactory.buildWithId({ + school: mockOtherSchool, + roles: [...mockStudentUser.roles], }); - - describe('When using superhero user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockSuperheroAccount = accountFactory.buildWithId({ - userId: mockSuperheroUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockSuperheroUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockSuperheroAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockSuperheroUser }; - }; - it('should allow to update first and last name ', async () => { - const { mockSuperheroUser } = setup(); - await expect( - accountUc.updateMyAccount(mockSuperheroUser.id, { - passwordOld: defaultPassword, - firstName: 'newFirstName', - }) - ).resolves.not.toThrow(); - await expect( - accountUc.updateMyAccount(mockSuperheroUser.id, { - passwordOld: defaultPassword, - lastName: 'newLastName', - }) - ).resolves.not.toThrow(); - }); + mockUserWithoutAccount = userFactory.buildWithId({ + school: mockSchool, + roles: [ + new Role({ + name: RoleName.ADMINISTRATOR, + permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], + }), + ], }); - - describe('When user can not be updated', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockTeacherUser); - userRepo.save.mockRejectedValueOnce(undefined); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - accountService.validatePassword.mockResolvedValue(true); - - return { mockTeacherUser, mockTeacherAccount }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockTeacherUser } = setup(); - await expect( - accountUc.updateMyAccount(mockTeacherUser.id, { - passwordOld: defaultPassword, - firstName: 'failToUpdate', - }) - ).rejects.toThrow(EntityNotFoundError); - }); + mockUserWithoutRole = userFactory.buildWithId({ + school: mockSchool, + roles: [], }); - - describe('When account can not be updated', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - userRepo.save.mockResolvedValueOnce(undefined); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - accountService.save.mockRejectedValueOnce(undefined); - - accountValidationService.isUniqueEmail.mockResolvedValue(true); - - return { mockStudentUser }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockStudentUser } = setup(); - await expect( - accountUc.updateMyAccount(mockStudentUser.id, { - passwordOld: defaultPassword, - email: 'fail@to.update', - }) - ).rejects.toThrow(EntityNotFoundError); - }); + mockUnknownRoleUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: 'undefinedRole' as RoleName, permissions: ['' as Permission] })], + }); + mockExternalUser = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], + }); + mockStudentUserWithoutAccount = userFactory.buildWithId({ + school: mockSchool, + roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], }); - describe('When no new password is given', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValue(true); - const spyAccountServiceSave = jest.spyOn(accountService, 'save'); + mockSuperheroAccount = accountFactory.buildWithId({ + userId: mockSuperheroUser.id, + password: defaultPasswordHash, + }); + mockTeacherAccount = accountFactory.buildWithId({ + userId: mockTeacherUser.id, + password: defaultPasswordHash, + }); + mockOtherTeacherAccount = accountFactory.buildWithId({ + userId: mockOtherTeacherUser.id, + password: defaultPasswordHash, + }); + mockTeacherNoUserPermissionAccount = accountFactory.buildWithId({ + userId: mockTeacherNoUserPermissionUser.id, + password: defaultPasswordHash, + }); + mockTeacherNoUserNoSchoolPermissionAccount = accountFactory.buildWithId({ + userId: mockTeacherNoUserNoSchoolPermissionUser.id, + password: defaultPasswordHash, + }); + mockAdminAccount = accountFactory.buildWithId({ + userId: mockAdminUser.id, + password: defaultPasswordHash, + }); + mockStudentAccount = accountFactory.buildWithId({ + userId: mockStudentUser.id, + password: defaultPasswordHash, + }); + mockStudentSchoolPermissionAccount = accountFactory.buildWithId({ + userId: mockStudentSchoolPermissionUser.id, + password: defaultPasswordHash, + }); + mockAccountWithoutRole = accountFactory.buildWithId({ + userId: mockUserWithoutRole.id, + password: defaultPasswordHash, + }); + mockDifferentSchoolAdminAccount = accountFactory.buildWithId({ + userId: mockDifferentSchoolAdminUser.id, + password: defaultPasswordHash, + }); + mockDifferentSchoolTeacherAccount = accountFactory.buildWithId({ + userId: mockDifferentSchoolTeacherUser.id, + password: defaultPasswordHash, + }); + mockDifferentSchoolStudentAccount = accountFactory.buildWithId({ + userId: mockDifferentSchoolStudentUser.id, + password: defaultPasswordHash, + }); + mockUnknownRoleUserAccount = accountFactory.buildWithId({ + userId: mockUnknownRoleUser.id, + password: defaultPasswordHash, + }); + const externalSystem = systemFactory.buildWithId(); + mockExternalUserAccount = accountFactory.buildWithId({ + userId: mockExternalUser.id, + password: defaultPasswordHash, + systemId: externalSystem.id, + }); + mockAccountWithoutUser = accountFactory.buildWithId({ + userId: undefined, + password: defaultPasswordHash, + systemId: systemFactory.buildWithId().id, + }); + mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId(10)).build(); + mockAccountWithLastFailedLogin = accountFactory.buildWithId({ + userId: undefined, + password: defaultPasswordHash, + systemId: systemFactory.buildWithId().id, + lasttriedFailedLogin: new Date(), + }); + mockAccountWithOldLastFailedLogin = accountFactory.buildWithId({ + userId: undefined, + password: defaultPasswordHash, + systemId: systemFactory.buildWithId().id, + lasttriedFailedLogin: new Date(new Date().getTime() - LOGIN_BLOCK_TIME - 1), + }); + mockAccountWithNoLastFailedLogin = accountFactory.buildWithId({ + userId: undefined, + password: defaultPasswordHash, + systemId: systemFactory.buildWithId().id, + lasttriedFailedLogin: undefined, + }); - accountValidationService.isUniqueEmail.mockResolvedValue(true); + mockUsers = [ + mockSuperheroUser, + mockAdminUser, + mockTeacherUser, + mockOtherTeacherUser, + mockTeacherNoUserPermissionUser, + mockTeacherNoUserNoSchoolPermissionUser, + mockStudentUser, + mockStudentSchoolPermissionUser, + mockDifferentSchoolAdminUser, + mockDifferentSchoolTeacherUser, + mockDifferentSchoolStudentUser, + mockUnknownRoleUser, + mockExternalUser, + mockUserWithoutRole, + mockUserWithoutAccount, + mockStudentUserWithoutAccount, + mockOtherStudentUser, + mockOtherStudentSchoolPermissionUser, + ]; + + mockAccounts = [ + mockSuperheroAccount, + mockAdminAccount, + mockTeacherAccount, + mockOtherTeacherAccount, + mockTeacherNoUserPermissionAccount, + mockTeacherNoUserNoSchoolPermissionAccount, + mockStudentAccount, + mockStudentSchoolPermissionAccount, + mockDifferentSchoolAdminAccount, + mockDifferentSchoolTeacherAccount, + mockDifferentSchoolStudentAccount, + mockUnknownRoleUserAccount, + mockExternalUserAccount, + mockAccountWithoutRole, + mockAccountWithoutUser, + mockAccountWithSystemId, + mockAccountWithLastFailedLogin, + mockAccountWithOldLastFailedLogin, + mockAccountWithNoLastFailedLogin, + ]; + }); - return { mockStudentUser, spyAccountServiceSave }; - }; - it('should not update password', async () => { - const { mockStudentUser, spyAccountServiceSave } = setup(); - await accountUc.updateMyAccount(mockStudentUser.id, { + describe('updateMyAccount', () => { + it('should throw if user does not exist', async () => { + mockStudentUser.forcePasswordChange = true; + mockStudentUser.preferences = { firstLogin: true }; + await expect(accountUc.updateMyAccount('accountWithoutUser', { passwordOld: defaultPassword })).rejects.toThrow( + EntityNotFoundError + ); + }); + it('should throw if account does not exist', async () => { + await expect( + accountUc.updateMyAccount(mockUserWithoutAccount.id, { passwordOld: defaultPassword, - passwordNew: undefined, - email: 'newemail@to.update', - }); - expect(spyAccountServiceSave).toHaveBeenCalledWith( - expect.objectContaining({ - password: undefined, - }) - ); - }); + }) + ).rejects.toThrow(EntityNotFoundError); + }); + it('should throw if account is external', async () => { + await expect( + accountUc.updateMyAccount(mockExternalUserAccount.userId?.toString() ?? '', { + passwordOld: defaultPassword, + }) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should throw if password does not match', async () => { + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: 'DoesNotMatch', + }) + ).rejects.toThrow(AuthorizationError); + }); + it('should throw if changing own name is not allowed', async () => { + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + firstName: 'newFirstName', + }) + ).rejects.toThrow(ForbiddenOperationError); + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + lastName: 'newLastName', + }) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should allow to update email', async () => { + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: 'an@available.mail', + }) + ).resolves.not.toThrow(); + }); + it('should use email as account user name in lower case', async () => { + const accountSaveSpy = jest.spyOn(accountService, 'save'); + const testMail = 'AN@AVAILABLE.MAIL'; + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: testMail, + }) + ).resolves.not.toThrow(); + expect(accountSaveSpy).toBeCalledWith(expect.objectContaining({ username: testMail.toLowerCase() })); + }); + it('should use email as user email in lower case', async () => { + const userUpdateSpy = jest.spyOn(userRepo, 'save'); + const testMail = 'AN@AVAILABLE.MAIL'; + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: testMail, + }) + ).resolves.not.toThrow(); + expect(userUpdateSpy).toBeCalledWith(expect.objectContaining({ email: testMail.toLowerCase() })); + }); + it('should always update account user name AND user email together.', async () => { + const accountSaveSpy = jest.spyOn(accountService, 'save'); + const userUpdateSpy = jest.spyOn(userRepo, 'save'); + const testMail = 'an@available.mail'; + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: testMail, + }) + ).resolves.not.toThrow(); + expect(userUpdateSpy).toBeCalledWith(expect.objectContaining({ email: testMail.toLowerCase() })); + expect(accountSaveSpy).toBeCalledWith(expect.objectContaining({ username: testMail.toLowerCase() })); + }); + it('should throw if new email already in use', async () => { + const accountIsUniqueEmailSpy = jest.spyOn(accountValidationService, 'isUniqueEmail'); + accountIsUniqueEmailSpy.mockResolvedValueOnce(false); + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: mockAdminUser.email, + }) + ).rejects.toThrow(ValidationError); + }); + it('should allow to update with strong password', async () => { + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + passwordNew: otherPassword, + }) + ).resolves.not.toThrow(); + }); + it('should allow to update first and last name if teacher', async () => { + await expect( + accountUc.updateMyAccount(mockTeacherUser.id, { + passwordOld: defaultPassword, + firstName: 'newFirstName', + }) + ).resolves.not.toThrow(); + await expect( + accountUc.updateMyAccount(mockTeacherUser.id, { + passwordOld: defaultPassword, + lastName: 'newLastName', + }) + ).resolves.not.toThrow(); + }); + it('should allow to update first and last name if admin', async () => { + await expect( + accountUc.updateMyAccount(mockAdminUser.id, { + passwordOld: defaultPassword, + firstName: 'newFirstName', + }) + ).resolves.not.toThrow(); + await expect( + accountUc.updateMyAccount(mockAdminUser.id, { + passwordOld: defaultPassword, + lastName: 'newLastName', + }) + ).resolves.not.toThrow(); + }); + it('should allow to update first and last name if superhero', async () => { + await expect( + accountUc.updateMyAccount(mockSuperheroUser.id, { + passwordOld: defaultPassword, + firstName: 'newFirstName', + }) + ).resolves.not.toThrow(); + await expect( + accountUc.updateMyAccount(mockSuperheroUser.id, { + passwordOld: defaultPassword, + lastName: 'newLastName', + }) + ).resolves.not.toThrow(); + }); + it('should throw if user can not be updated', async () => { + await expect( + accountUc.updateMyAccount(mockTeacherUser.id, { + passwordOld: defaultPassword, + firstName: 'failToUpdate', + }) + ).rejects.toThrow(EntityNotFoundError); + }); + it('should throw if account can not be updated', async () => { + await expect( + accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + email: 'fail@to.update', + }) + ).rejects.toThrow(EntityNotFoundError); + }); + it('should not update password if no new password', async () => { + const spy = jest.spyOn(accountService, 'save'); + await accountUc.updateMyAccount(mockStudentUser.id, { + passwordOld: defaultPassword, + passwordNew: undefined, + email: 'newemail@to.update', + }); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + password: undefined, + }) + ); }); }); describe('replaceMyTemporaryPassword', () => { - describe('When passwords do not match', () => { - it('should throw ForbiddenOperationError', async () => { - await expect(accountUc.replaceMyTemporaryPassword('userId', defaultPassword, 'FooPasswd!1')).rejects.toThrow( - ForbiddenOperationError - ); - }); + it('should throw if passwords do not match', async () => { + await expect( + accountUc.replaceMyTemporaryPassword( + mockStudentAccount.userId?.toString() ?? '', + defaultPassword, + 'FooPasswd!1' + ) + ).rejects.toThrow(ForbiddenOperationError); }); - describe('When account does not exists', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockUserWithoutAccount = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockResolvedValueOnce(mockUserWithoutAccount); - accountService.findByUserIdOrFail.mockImplementation(() => { - throw new EntityNotFoundError(Account.name); - }); - - return { mockUserWithoutAccount }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockUserWithoutAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword(mockUserWithoutAccount.id, defaultPassword, defaultPassword) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw if account does not exist', async () => { + await expect( + accountUc.replaceMyTemporaryPassword(mockUserWithoutAccount.id, defaultPassword, defaultPassword) + ).rejects.toThrow(EntityNotFoundError); }); - - describe('When user does not exist', () => { - const setup = () => { - userRepo.findById.mockRejectedValueOnce(undefined); - }; - it('should throw EntityNotFoundError', async () => { - setup(); - await expect( - accountUc.replaceMyTemporaryPassword('accountWithoutUser', defaultPassword, defaultPassword) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw if user does not exist', async () => { + await expect( + accountUc.replaceMyTemporaryPassword('accountWithoutUser', defaultPassword, defaultPassword) + ).rejects.toThrow(EntityNotFoundError); }); + it('should throw if account is external', async () => { + await expect( + accountUc.replaceMyTemporaryPassword( + mockExternalUserAccount.userId?.toString() ?? '', + defaultPassword, + defaultPassword + ) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should throw if not the users password is temporary', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: true }; + await expect( + accountUc.replaceMyTemporaryPassword( + mockStudentAccount.userId?.toString() ?? '', + defaultPassword, + defaultPassword + ) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should throw, if old password is the same as new password', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: false }; + await expect( + accountUc.replaceMyTemporaryPassword( + mockStudentAccount.userId?.toString() ?? '', + defaultPassword, + defaultPassword + ) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should throw, if old password is undefined', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: false }; + mockStudentAccount.password = undefined; + await expect( + accountUc.replaceMyTemporaryPassword( + mockStudentAccount.userId?.toString() ?? '', + defaultPassword, + defaultPassword + ) + ).rejects.toThrow(Error); + }); + it('should allow to set strong password, if the admin manipulated the users password', async () => { + mockStudentUser.forcePasswordChange = true; + mockStudentUser.preferences = { firstLogin: true }; + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.replaceMyTemporaryPassword(mockStudentAccount.userId?.toString() ?? '', otherPassword, otherPassword) + ).resolves.not.toThrow(); + }); + it('should allow to set strong password, if this is the users first login', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: false }; + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.replaceMyTemporaryPassword(mockStudentAccount.userId?.toString() ?? '', otherPassword, otherPassword) + ).resolves.not.toThrow(); + }); + it('should allow to set strong password, if this is the users first login (if undefined)', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = undefined; + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.replaceMyTemporaryPassword(mockStudentAccount.userId?.toString() ?? '', otherPassword, otherPassword) + ).resolves.not.toThrow(); + }); + it('should throw if user can not be updated', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: false }; + mockStudentUser.firstName = 'failToUpdate'; + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.replaceMyTemporaryPassword(mockStudentAccount.userId?.toString() ?? '', otherPassword, otherPassword) + ).rejects.toThrow(EntityNotFoundError); + }); + it('should throw if account can not be updated', async () => { + mockStudentUser.forcePasswordChange = false; + mockStudentUser.preferences = { firstLogin: false }; + mockStudentAccount.username = 'fail@to.update'; + jest.spyOn(accountService, 'validatePassword').mockResolvedValueOnce(false); + await expect( + accountUc.replaceMyTemporaryPassword(mockStudentAccount.userId?.toString() ?? '', otherPassword, otherPassword) + ).rejects.toThrow(EntityNotFoundError); + }); + }); - describe('When account is external', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockExternalUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const externalSystem = systemFactory.buildWithId(); - const mockExternalUserAccount = accountFactory.buildWithId({ - userId: mockExternalUser.id, - password: defaultPasswordHash, - systemId: externalSystem.id, - }); - - userRepo.findById.mockResolvedValueOnce(mockExternalUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce( - AccountEntityToDtoMapper.mapToDto(mockExternalUserAccount) - ); + describe('searchAccounts', () => { + it('should return one account, if search type is userId', async () => { + const accounts = await accountUc.searchAccounts( + { userId: mockSuperheroUser.id } as ICurrentUser, + { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams + ); + const expected = new AccountSearchListResponse( + [AccountResponseMapper.mapToResponseFromEntity(mockStudentAccount)], + 1, + 0, + 1 + ); + expect(accounts).toStrictEqual(expected); + }); + it('should return empty list, if account is not found', async () => { + const accounts = await accountUc.searchAccounts( + { userId: mockSuperheroUser.id } as ICurrentUser, + { type: AccountSearchType.USER_ID, value: mockUserWithoutAccount.id } as AccountSearchQueryParams + ); + const expected = new AccountSearchListResponse([], 0, 0, 0); + expect(accounts).toStrictEqual(expected); + }); + it('should return one or more accounts, if search type is username', async () => { + const accounts = await accountUc.searchAccounts( + { userId: mockSuperheroUser.id } as ICurrentUser, + { type: AccountSearchType.USERNAME, value: '' } as AccountSearchQueryParams + ); + expect(accounts.skip).toEqual(0); + expect(accounts.limit).toEqual(10); + expect(accounts.total).toBeGreaterThan(1); + expect(accounts.data.length).toBeGreaterThan(1); + }); + it('should throw, if user has not the right permissions', async () => { + await expect( + accountUc.searchAccounts( + { userId: mockTeacherUser.id } as ICurrentUser, + { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams + ) + ).rejects.toThrow(ForbiddenOperationError); + + await expect( + accountUc.searchAccounts( + { userId: mockStudentUser.id } as ICurrentUser, + { type: AccountSearchType.USER_ID, value: mockOtherStudentUser.id } as AccountSearchQueryParams + ) + ).rejects.toThrow(ForbiddenOperationError); + + await expect( + accountUc.searchAccounts( + { userId: mockStudentUser.id } as ICurrentUser, + { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams + ) + ).rejects.toThrow(ForbiddenOperationError); + }); + it('should throw, if search type is unknown', async () => { + await expect( + accountUc.searchAccounts( + { userId: mockSuperheroUser.id } as ICurrentUser, + { type: '' as AccountSearchType } as AccountSearchQueryParams + ) + ).rejects.toThrow('Invalid search type.'); + }); + it('should throw, if user is no superhero', async () => { + await expect( + accountUc.searchAccounts( + { userId: mockTeacherUser.id } as ICurrentUser, + { type: AccountSearchType.USERNAME, value: mockStudentUser.id } as AccountSearchQueryParams + ) + ).rejects.toThrow(ForbiddenOperationError); + }); - return { mockExternalUserAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockExternalUserAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockExternalUserAccount.userId?.toString() ?? '', - defaultPassword, - defaultPassword - ) - ).rejects.toThrow(ForbiddenOperationError); + describe('hasPermissionsToAccessAccount', () => { + beforeEach(() => { + configService.get.mockReturnValue(false); }); - }); - describe('When not the users password is temporary', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); + it('admin can access teacher of the same school via user id', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + }); + it('admin can access student of the same school via user id', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + }); + it('admin can not access admin of the same school via user id', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); + }); + it('admin can not access any account of a foreign school via user id', async () => { + const currentUser = { userId: mockDifferentSchoolAdminUser.id } as ICurrentUser; - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - preferences: { firstLogin: true }, - }); + let params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); + params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); + }); + it('teacher can access teacher of the same school via user id', async () => { + const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockOtherTeacherUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + }); + it('teacher can access student of the same school via user id', async () => { + const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + }); + it('teacher can not access admin of the same school via user id', async () => { + const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); + }); + it('teacher can not access any account of a foreign school via user id', async () => { + const currentUser = { userId: mockDifferentSchoolTeacherUser.id } as ICurrentUser; - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); + let params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - return { mockStudentAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - defaultPassword, - defaultPassword - ) - ).rejects.toThrow(ForbiddenOperationError); + params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); + }); + it('teacher can access student of the same school via user id if school has global permission', async () => { + configService.get.mockReturnValue(true); + const currentUser = { userId: mockTeacherNoUserPermissionUser.id } as ICurrentUser; + const params = { + type: AccountSearchType.USER_ID, + value: mockStudentSchoolPermissionUser.id, + } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + }); + it('teacher can not access student of the same school if school has no global permission', async () => { + configService.get.mockReturnValue(true); + const currentUser = { userId: mockTeacherNoUserNoSchoolPermissionUser.id } as ICurrentUser; + const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(ForbiddenOperationError); }); - }); - - describe('When old password is the same as new password', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - preferences: { firstLogin: false }, - }); + it('student can not access student of the same school if school has global permission', async () => { + configService.get.mockReturnValue(true); + const currentUser = { userId: mockStudentSchoolPermissionUser.id } as ICurrentUser; + const params = { + type: AccountSearchType.USER_ID, + value: mockOtherStudentSchoolPermissionUser.id, + } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(ForbiddenOperationError); + }); + it('student can not access any other account via user id', async () => { + const currentUser = { userId: mockStudentUser.id } as ICurrentUser; - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); + let params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(true); + params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - return { mockStudentAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - defaultPassword, - defaultPassword - ) - ).rejects.toThrow(ForbiddenOperationError); + params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); }); - }); + it('superhero can access any account via username', async () => { + const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - describe('When old password is undefined', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); + let params = { type: AccountSearchType.USERNAME, value: mockAdminAccount.username } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - preferences: { firstLogin: false }, - }); + params = { type: AccountSearchType.USERNAME, value: mockTeacherAccount.username } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: undefined, - }); + params = { type: AccountSearchType.USERNAME, value: mockStudentAccount.username } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(true); + params = { + type: AccountSearchType.USERNAME, + value: mockDifferentSchoolAdminAccount.username, + } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - return { mockStudentAccount }; - }; - it('should throw Error', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - defaultPassword, - defaultPassword - ) - ).rejects.toThrow(Error); + params = { + type: AccountSearchType.USERNAME, + value: mockDifferentSchoolTeacherAccount.username, + } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); + + params = { + type: AccountSearchType.USERNAME, + value: mockDifferentSchoolStudentAccount.username, + } as AccountSearchQueryParams; + await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); }); }); + }); - describe('When the admin manipulate the users password', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: true, - preferences: { firstLogin: true }, - }); - const mockStudentAccount = accountFactory.buildWithId({ + describe('findAccountById', () => { + it('should return an account, if the current user is a superhero', async () => { + const account = await accountUc.findAccountById( + { userId: mockSuperheroUser.id } as ICurrentUser, + { id: mockStudentAccount.id } as AccountByIdParams + ); + expect(account).toStrictEqual( + expect.objectContaining({ + id: mockStudentAccount.id, + username: mockStudentAccount.username, userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(false); - return { mockStudentAccount }; - }; - it('should allow to set strong password', async () => { - const { mockStudentAccount } = setup(); - - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - otherPassword, - otherPassword - ) - ).resolves.not.toThrow(); - }); - }); - - describe('when a user logs in for the first time', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - preferences: { firstLogin: false }, - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(false); - - return { mockStudentAccount }; - }; - it('should allow to set strong password', async () => { - const { mockStudentAccount } = setup(); - - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - otherPassword, - otherPassword - ) - ).resolves.not.toThrow(); - }); + activated: mockStudentAccount.activated, + }) + ); }); - - describe('when a user logs in for the first time (if undefined)', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - }); - mockStudentUser.preferences = undefined; - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(false); - - return { mockStudentAccount }; - }; - it('should allow to set strong password', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - otherPassword, - otherPassword - ) - ).resolves.not.toThrow(); - }); + it('should throw, if the current user is no superhero', async () => { + await expect( + accountUc.findAccountById( + { userId: mockTeacherUser.id } as ICurrentUser, + { id: mockStudentAccount.id } as AccountByIdParams + ) + ).rejects.toThrow(ForbiddenOperationError); }); - - describe('When user can not be updated', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - firstName: 'failToUpdate', - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - preferences: { firstLogin: false }, - forcePasswordChange: false, - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - userRepo.save.mockRejectedValueOnce(undefined); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.validatePassword.mockResolvedValueOnce(false); - - return { mockStudentAccount }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - otherPassword, - otherPassword - ) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw, if no account matches the search term', async () => { + await expect( + accountUc.findAccountById({ userId: mockSuperheroUser.id } as ICurrentUser, { id: 'xxx' } as AccountByIdParams) + ).rejects.toThrow(EntityNotFoundError); }); - - describe('When account can not be updated', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - forcePasswordChange: false, - preferences: { firstLogin: false }, - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - username: 'fail@to.update', - }); - - userRepo.findById.mockResolvedValueOnce(mockStudentUser); - userRepo.save.mockResolvedValueOnce(); - accountService.findByUserIdOrFail.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - accountService.save.mockRejectedValueOnce(undefined); - accountService.validatePassword.mockResolvedValueOnce(false); - - return { mockStudentAccount }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockStudentAccount } = setup(); - await expect( - accountUc.replaceMyTemporaryPassword( - mockStudentAccount.userId?.toString() ?? '', - otherPassword, - otherPassword - ) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw, if target account has no user', async () => { + await expect( + accountUc.findAccountById({ userId: mockSuperheroUser.id } as ICurrentUser, { id: 'xxx' } as AccountByIdParams) + ).rejects.toThrow(EntityNotFoundError); }); }); - describe('searchAccounts', () => { - describe('When search type is userId', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockSchoolWithStudentVisibility = schoolFactory.buildWithId(); - mockSchoolWithStudentVisibility.permissions = new SchoolRoles(); - mockSchoolWithStudentVisibility.permissions.teacher = new SchoolRolePermission(); - mockSchoolWithStudentVisibility.permissions.teacher.STUDENT_LIST = true; - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockStudentUser); - accountService.findByUserId.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - return { mockSuperheroUser, mockStudentUser, mockStudentAccount }; - }; - it('should return one account', async () => { - const { mockSuperheroUser, mockStudentUser, mockStudentAccount } = setup(); - const accounts = await accountUc.searchAccounts( - { userId: mockSuperheroUser.id } as ICurrentUser, - { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams - ); - const expected = new AccountSearchListResponse( - [AccountResponseMapper.mapToResponseFromEntity(mockStudentAccount)], - 1, - 0, - 1 - ); - expect(accounts).toStrictEqual(expected); - }); + describe('saveAccount', () => { + afterEach(() => { + jest.clearAllMocks(); }); - describe('When account is not found', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockUserWithoutAccount = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockUserWithoutAccount); - - return { mockSuperheroUser, mockUserWithoutAccount }; + it('should call account service', async () => { + const spy = jest.spyOn(accountService, 'saveWithValidation'); + const params: AccountSaveDto = { + username: 'john.doe@domain.tld', + password: defaultPassword, }; - it('should return empty list', async () => { - const { mockSuperheroUser, mockUserWithoutAccount } = setup(); - const accounts = await accountUc.searchAccounts( - { userId: mockSuperheroUser.id } as ICurrentUser, - { type: AccountSearchType.USER_ID, value: mockUserWithoutAccount.id } as AccountSearchQueryParams - ); - const expected = new AccountSearchListResponse([], 0, 0, 0); - expect(accounts).toStrictEqual(expected); - }); + await accountUc.saveAccount(params); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + username: 'john.doe@domain.tld', + }) + ); }); - describe('When search type is username', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockSuperheroUser); - accountService.searchByUsernamePartialMatch.mockResolvedValueOnce([ - [ - AccountEntityToDtoMapper.mapToDto(mockStudentAccount), - AccountEntityToDtoMapper.mapToDto(mockStudentAccount), - ], - 2, - ]); + }); - return { mockSuperheroUser }; - }; - it('should return one or more accounts, ', async () => { - const { mockSuperheroUser } = setup(); - const accounts = await accountUc.searchAccounts( - { userId: mockSuperheroUser.id } as ICurrentUser, - { type: AccountSearchType.USERNAME, value: '' } as AccountSearchQueryParams - ); - expect(accounts.skip).toEqual(0); - expect(accounts.limit).toEqual(10); - expect(accounts.total).toBeGreaterThan(1); - expect(accounts.data.length).toBeGreaterThan(1); - }); + describe('updateAccountById', () => { + it('should throw if executing user does not exist', async () => { + const currentUser = { userId: '000000000000000' } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); }); - - describe('When user has not the right permissions', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockOtherStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockAdminUser); - userRepo.findById.mockResolvedValueOnce(mockStudentUser).mockResolvedValueOnce(mockOtherStudentUser); - userRepo.findById.mockResolvedValueOnce(mockStudentUser).mockResolvedValueOnce(mockTeacherUser); - - return { mockTeacherUser, mockAdminUser, mockStudentUser, mockOtherStudentUser }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockTeacherUser, mockAdminUser, mockStudentUser, mockOtherStudentUser } = setup(); - await expect( - accountUc.searchAccounts( - { userId: mockTeacherUser.id } as ICurrentUser, - { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams - ) - ).rejects.toThrow(ForbiddenOperationError); - - await expect( - accountUc.searchAccounts( - { userId: mockStudentUser.id } as ICurrentUser, - { type: AccountSearchType.USER_ID, value: mockOtherStudentUser.id } as AccountSearchQueryParams - ) - ).rejects.toThrow(ForbiddenOperationError); - - await expect( - accountUc.searchAccounts( - { userId: mockStudentUser.id } as ICurrentUser, - { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams - ) - ).rejects.toThrow(ForbiddenOperationError); - }); + it('should throw if target account does not exist', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { id: '000000000000000' } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); }); - - describe('When search type is unknown', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser); - - return { mockSuperheroUser }; - }; - it('should throw Invalid search type', async () => { - const { mockSuperheroUser } = setup(); - await expect( - accountUc.searchAccounts( - { userId: mockSuperheroUser.id } as ICurrentUser, - { type: '' as AccountSearchType } as AccountSearchQueryParams - ) - ).rejects.toThrow('Invalid search type.'); - }); + it('should update target account password', async () => { + const previousPasswordHash = mockStudentAccount.password; + const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { password: defaultPassword } as AccountByIdBodyParams; + expect(mockStudentUser.forcePasswordChange).toBeFalsy(); + await accountUc.updateAccountById(currentUser, params, body); + expect(mockStudentAccount.password).not.toBe(previousPasswordHash); + expect(mockStudentUser.forcePasswordChange).toBeTruthy(); }); - - describe('When user is not superhero', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockStudentUser); - - return { mockStudentUser, mockTeacherUser }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockTeacherUser, mockStudentUser } = setup(); - await expect( - accountUc.searchAccounts( - { userId: mockTeacherUser.id } as ICurrentUser, - { type: AccountSearchType.USERNAME, value: mockStudentUser.id } as AccountSearchQueryParams - ) - ).rejects.toThrow(ForbiddenOperationError); - }); + it('should update target account username', async () => { + const newUsername = 'newUsername'; + const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { username: newUsername } as AccountByIdBodyParams; + expect(mockStudentAccount.username).not.toBe(newUsername); + await accountUc.updateAccountById(currentUser, params, body); + expect(mockStudentAccount.username).toBe(newUsername.toLowerCase()); }); - describe('hasPermissionsToAccessAccount', () => { - describe('When using an admin', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockTeacherUser); - - return { mockAdminUser, mockTeacherUser }; - }; - it('should be able to access teacher of the same school via user id', async () => { - const { mockAdminUser, mockTeacherUser } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); - - describe('When using an admin', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockStudentUser); - - return { mockAdminUser, mockStudentUser }; - }; - it('should be able to access student of the same school via user id', async () => { - const { mockAdminUser, mockStudentUser } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); - - describe('When using an admin', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockAdminUser); - - return { mockAdminUser }; - }; - - it('should not be able to access admin of the same school via user id', async () => { - const { mockAdminUser } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - }); - }); - - describe('When using an admin', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockOtherSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockDifferentSchoolAdminUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockAdminUser.roles], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockDifferentSchoolAdminUser).mockResolvedValueOnce(mockTeacherUser); - userRepo.findById.mockResolvedValueOnce(mockDifferentSchoolAdminUser).mockResolvedValueOnce(mockStudentUser); - - return { mockDifferentSchoolAdminUser, mockTeacherUser, mockStudentUser }; - }; - it('should not be able to access any account of a foreign school via user id', async () => { - const { mockDifferentSchoolAdminUser, mockTeacherUser, mockStudentUser } = setup(); - const currentUser = { userId: mockDifferentSchoolAdminUser.id } as ICurrentUser; - - let params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - - params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - }); - }); - - describe('When using a teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockOtherTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockOtherTeacherUser); - - return { mockTeacherUser, mockOtherTeacherUser }; - }; - it('should be able to access teacher of the same school via user id', async () => { - const { mockTeacherUser, mockOtherTeacherUser } = setup(); - const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; - const params = { - type: AccountSearchType.USER_ID, - value: mockOtherTeacherUser.id, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); - - describe('When using a teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockStudentUser); - - return { mockTeacherUser, mockStudentUser }; - }; - it('should be able to access student of the same school via user id', async () => { - const { mockTeacherUser, mockStudentUser } = setup(); - const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); - - describe('When using a teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockAdminUser); - - return { mockTeacherUser, mockAdminUser }; - }; - it('should not be able to access admin of the same school via user id', async () => { - const { mockTeacherUser, mockAdminUser } = setup(); - const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - }); - }); - - describe('When using a teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockOtherSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockDifferentSchoolTeacherUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockTeacherUser.roles], - }); - - configService.get.mockReturnValue(false); - userRepo.findById - .mockResolvedValueOnce(mockDifferentSchoolTeacherUser) - .mockResolvedValueOnce(mockTeacherUser); - userRepo.findById - .mockResolvedValueOnce(mockDifferentSchoolTeacherUser) - .mockResolvedValueOnce(mockStudentUser); - - return { mockDifferentSchoolTeacherUser, mockTeacherUser, mockStudentUser }; - }; - it('should not be able to access any account of a foreign school via user id', async () => { - const { mockDifferentSchoolTeacherUser, mockTeacherUser, mockStudentUser } = setup(); - const currentUser = { userId: mockDifferentSchoolTeacherUser.id } as ICurrentUser; - - let params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - - params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - }); - }); - describe('When using a teacher', () => { - const setup = () => { - const mockSchoolWithStudentVisibility = schoolFactory.buildWithId(); - mockSchoolWithStudentVisibility.permissions = new SchoolRoles(); - mockSchoolWithStudentVisibility.permissions.teacher = new SchoolRolePermission(); - mockSchoolWithStudentVisibility.permissions.teacher.STUDENT_LIST = true; - - const mockTeacherNoUserPermissionUser = userFactory.buildWithId({ - school: mockSchoolWithStudentVisibility, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [], - }), - ], - }); - const mockStudentSchoolPermissionUser = userFactory.buildWithId({ - school: mockSchoolWithStudentVisibility, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(true); - userRepo.findById - .mockResolvedValueOnce(mockTeacherNoUserPermissionUser) - .mockResolvedValueOnce(mockStudentSchoolPermissionUser); - - return { mockTeacherNoUserPermissionUser, mockStudentSchoolPermissionUser }; - }; - it('should be able to access student of the same school via user id if school has global permission', async () => { - const { mockTeacherNoUserPermissionUser, mockStudentSchoolPermissionUser } = setup(); - - const currentUser = { userId: mockTeacherNoUserPermissionUser.id } as ICurrentUser; - const params = { - type: AccountSearchType.USER_ID, - value: mockStudentSchoolPermissionUser.id, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); - - describe('When using a teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherNoUserNoSchoolPermissionUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(false); - userRepo.findById - .mockResolvedValueOnce(mockTeacherNoUserNoSchoolPermissionUser) - .mockResolvedValueOnce(mockStudentUser); - - return { mockTeacherNoUserNoSchoolPermissionUser, mockStudentUser }; - }; - it('should not be able to access student of the same school if school has no global permission', async () => { - const { mockTeacherNoUserNoSchoolPermissionUser, mockStudentUser } = setup(); - configService.get.mockReturnValue(true); - const currentUser = { userId: mockTeacherNoUserNoSchoolPermissionUser.id } as ICurrentUser; - const params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(ForbiddenOperationError); - }); - }); - - describe('When using a student', () => { - const setup = () => { - const mockSchoolWithStudentVisibility = schoolFactory.buildWithId(); - mockSchoolWithStudentVisibility.permissions = new SchoolRoles(); - mockSchoolWithStudentVisibility.permissions.teacher = new SchoolRolePermission(); - mockSchoolWithStudentVisibility.permissions.teacher.STUDENT_LIST = true; - - const mockStudentSchoolPermissionUser = userFactory.buildWithId({ - school: mockSchoolWithStudentVisibility, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockOtherStudentSchoolPermissionUser = userFactory.buildWithId({ - school: mockSchoolWithStudentVisibility, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(false); - userRepo.findById - .mockResolvedValueOnce(mockStudentSchoolPermissionUser) - .mockResolvedValueOnce(mockOtherStudentSchoolPermissionUser); - - return { mockStudentSchoolPermissionUser, mockOtherStudentSchoolPermissionUser }; - }; - it('should not be able to access student of the same school if school has global permission', async () => { - const { mockStudentSchoolPermissionUser, mockOtherStudentSchoolPermissionUser } = setup(); - configService.get.mockReturnValue(true); - const currentUser = { userId: mockStudentSchoolPermissionUser.id } as ICurrentUser; - const params = { - type: AccountSearchType.USER_ID, - value: mockOtherStudentSchoolPermissionUser.id, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(ForbiddenOperationError); - }); - }); - - describe('When using a student', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValueOnce(mockStudentUser).mockResolvedValueOnce(mockAdminUser); - userRepo.findById.mockResolvedValueOnce(mockStudentUser).mockResolvedValueOnce(mockTeacherUser); - userRepo.findById.mockResolvedValueOnce(mockStudentUser).mockResolvedValueOnce(mockStudentUser); - - return { mockStudentUser, mockAdminUser, mockTeacherUser }; - }; - it('should not be able to access any other account via user id', async () => { - const { mockStudentUser, mockAdminUser, mockTeacherUser } = setup(); - const currentUser = { userId: mockStudentUser.id } as ICurrentUser; - - let params = { type: AccountSearchType.USER_ID, value: mockAdminUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - - params = { type: AccountSearchType.USER_ID, value: mockTeacherUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - - params = { type: AccountSearchType.USER_ID, value: mockStudentUser.id } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).rejects.toThrow(); - }); - }); - - describe('When using a superhero', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockOtherSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockDifferentSchoolAdminUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockAdminUser.roles], - }); - const mockDifferentSchoolTeacherUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockTeacherUser.roles], - }); - const mockDifferentSchoolStudentUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockStudentUser.roles], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - const mockDifferentSchoolAdminAccount = accountFactory.buildWithId({ - userId: mockDifferentSchoolAdminUser.id, - password: defaultPasswordHash, - }); - const mockDifferentSchoolTeacherAccount = accountFactory.buildWithId({ - userId: mockDifferentSchoolTeacherUser.id, - password: defaultPasswordHash, - }); - const mockAdminAccount = accountFactory.buildWithId({ - userId: mockAdminUser.id, - password: defaultPasswordHash, - }); - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPasswordHash, - }); - - const mockDifferentSchoolStudentAccount = accountFactory.buildWithId({ - userId: mockDifferentSchoolStudentUser.id, - password: defaultPasswordHash, - }); - - configService.get.mockReturnValue(false); - userRepo.findById.mockResolvedValue(mockSuperheroUser); - accountService.searchByUsernamePartialMatch.mockResolvedValue([[], 0]); - - return { - mockSuperheroUser, - mockAdminAccount, - mockTeacherAccount, - mockStudentAccount, - mockDifferentSchoolAdminAccount, - mockDifferentSchoolTeacherAccount, - mockDifferentSchoolStudentAccount, - }; - }; - it('should be able to access any account via username', async () => { - const { - mockSuperheroUser, - mockAdminAccount, - mockTeacherAccount, - mockStudentAccount, - mockDifferentSchoolAdminAccount, - mockDifferentSchoolTeacherAccount, - mockDifferentSchoolStudentAccount, - } = setup(); - - const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - - let params = { - type: AccountSearchType.USERNAME, - value: mockAdminAccount.username, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - - params = { type: AccountSearchType.USERNAME, value: mockTeacherAccount.username } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - - params = { type: AccountSearchType.USERNAME, value: mockStudentAccount.username } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - - params = { - type: AccountSearchType.USERNAME, - value: mockDifferentSchoolAdminAccount.username, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - - params = { - type: AccountSearchType.USERNAME, - value: mockDifferentSchoolTeacherAccount.username, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - - params = { - type: AccountSearchType.USERNAME, - value: mockDifferentSchoolStudentAccount.username, - } as AccountSearchQueryParams; - await expect(accountUc.searchAccounts(currentUser, params)).resolves.not.toThrow(); - }); - }); + it('should update target account activation state', async () => { + const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { activated: false } as AccountByIdBodyParams; + await accountUc.updateAccountById(currentUser, params, body); + expect(mockStudentAccount.activated).toBeFalsy(); }); - }); - - describe('findAccountById', () => { - describe('When the current user is a superhero', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - return { mockSuperheroUser, mockStudentUser, mockStudentAccount }; - }; - it('should return an account', async () => { - const { mockSuperheroUser, mockStudentUser, mockStudentAccount } = setup(); - const account = await accountUc.findAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: mockStudentAccount.id } as AccountByIdParams - ); - expect(account).toStrictEqual( - expect.objectContaining({ - id: mockStudentAccount.id, - username: mockStudentAccount.username, - userId: mockStudentUser.id, - activated: mockStudentAccount.activated, - }) - ); - }); + it('should throw if account can not be updated', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { username: 'fail@to.update' } as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); }); - - describe('When the current user is no superhero', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockTeacherUser); - - return { mockTeacherUser, mockStudentAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockTeacherUser, mockStudentAccount } = setup(); - await expect( - accountUc.findAccountById( - { userId: mockTeacherUser.id } as ICurrentUser, - { id: mockStudentAccount.id } as AccountByIdParams - ) - ).rejects.toThrow(ForbiddenOperationError); - }); + it('should throw if user can not be updated', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { username: 'user-fail@to.update' } as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); }); - - describe('When no account matches the search term', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser); - accountService.findById.mockImplementation((): Promise => { - throw new EntityNotFoundError(Account.name); - }); - - return { mockSuperheroUser }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockSuperheroUser } = setup(); - await expect( - accountUc.findAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: 'xxx' } as AccountByIdParams - ) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw if target account has no user', async () => { + await expect( + accountUc.updateAccountById( + { userId: mockSuperheroUser.id } as ICurrentUser, + { id: mockAccountWithoutUser.id } as AccountByIdParams, + { username: 'user-fail@to.update' } as AccountByIdBodyParams + ) + ).rejects.toThrow(EntityNotFoundError); }); - - describe('When target account has no user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser); - accountService.findById.mockImplementation((): Promise => { - throw new EntityNotFoundError(Account.name); - }); - - return { mockSuperheroUser }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockSuperheroUser } = setup(); - await expect( - accountUc.findAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: 'xxx' } as AccountByIdParams - ) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw if new username already in use', async () => { + const accountIsUniqueEmailSpy = jest.spyOn(accountValidationService, 'isUniqueEmail'); + accountIsUniqueEmailSpy.mockResolvedValueOnce(false); + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { id: mockStudentAccount.id } as AccountByIdParams; + const body = { username: mockOtherTeacherAccount.username } as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ValidationError); }); - }); - describe('saveAccount', () => { - describe('When saving an account', () => { - const setup = () => { - const spy = jest.spyOn(accountService, 'saveWithValidation'); - - return { spy }; - }; - it('should call account service', async () => { - const { spy } = setup(); - - const params: AccountSaveDto = { - username: 'john.doe@domain.tld', - password: defaultPassword, - }; - await accountUc.saveAccount(params); - - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - username: 'john.doe@domain.tld', - }) - ); + describe('hasPermissionsToUpdateAccount', () => { + it('admin can edit teacher', async () => { + const currentUser = { userId: mockAdminUser.id } as ICurrentUser; + const params = { id: mockTeacherAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); }); - }); - }); - - describe('updateAccountById', () => { - describe('when updating a user that does not exist', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockImplementation((): Promise => { - throw new EntityNotFoundError(User.name); - }); - - return { mockStudentAccount }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockStudentAccount } = setup(); - const currentUser = { userId: '000000000000000' } as ICurrentUser; + it('teacher can edit student', async () => { + const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; const params = { id: mockStudentAccount.id } as AccountByIdParams; const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); + await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); }); - }); - - describe('When target account does not exist', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - - const mockAccountWithoutUser = accountFactory.buildWithId({ - userId: undefined, - password: defaultPasswordHash, - systemId: systemFactory.buildWithId().id, - }); - - userRepo.findById.mockResolvedValue(mockAdminUser); - accountService.findById.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockAccountWithoutUser)); - - return { mockAdminUser }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockAdminUser } = setup(); + it('admin can edit student', async () => { const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: '000000000000000' } as AccountByIdParams; + const params = { id: mockStudentAccount.id } as AccountByIdParams; const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); + await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); }); - }); - - describe('When using superhero user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockResolvedValue(); - accountService.save.mockImplementation((account: AccountSaveDto): Promise => { - Object.assign(mockStudentAccount, account); - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - }); - - return { mockStudentAccount, mockStudentUser, mockSuperheroUser }; - }; - it('should update target account password', async () => { - const { mockStudentAccount, mockSuperheroUser, mockStudentUser } = setup(); - const previousPasswordHash = mockStudentAccount.password; - const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { password: defaultPassword } as AccountByIdBodyParams; - expect(mockStudentUser.forcePasswordChange).toBeFalsy(); - await accountUc.updateAccountById(currentUser, params, body); - expect(mockStudentAccount.password).not.toBe(previousPasswordHash); - expect(mockStudentUser.forcePasswordChange).toBeTruthy(); + it('teacher cannot edit other teacher', async () => { + const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; + const params = { id: mockOtherTeacherAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); }); - }); - - describe('When using superhero user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockResolvedValue(); - accountService.save.mockImplementation((account: AccountSaveDto): Promise => { - Object.assign(mockStudentAccount, account); - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - }); - accountValidationService.isUniqueEmail.mockResolvedValue(true); - - return { mockStudentAccount, mockSuperheroUser }; - }; - it('should update target account username', async () => { - const { mockStudentAccount, mockSuperheroUser } = setup(); - const newUsername = 'newUsername'; - const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { username: newUsername } as AccountByIdBodyParams; - expect(mockStudentAccount.username).not.toBe(newUsername); - await accountUc.updateAccountById(currentUser, params, body); - expect(mockStudentAccount.username).toBe(newUsername.toLowerCase()); + it("other school's admin cannot edit teacher", async () => { + const currentUser = { userId: mockDifferentSchoolAdminUser.id } as ICurrentUser; + const params = { id: mockTeacherAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); }); - }); - - describe('When using superhero user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockResolvedValue(); - accountService.save.mockImplementation((account: AccountSaveDto): Promise => { - Object.assign(mockStudentAccount, account); - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - }); - - return { mockStudentAccount, mockSuperheroUser }; - }; - it('should update target account activation state', async () => { - const { mockStudentAccount, mockSuperheroUser } = setup(); + it('superhero can edit admin', async () => { const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { activated: false } as AccountByIdBodyParams; - await accountUc.updateAccountById(currentUser, params, body); - expect(mockStudentAccount.activated).toBeFalsy(); + const params = { id: mockAdminAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); }); - }); - - describe('When using an admin user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockResolvedValue(); - accountService.save.mockRejectedValueOnce(undefined); - - accountValidationService.isUniqueEmail.mockResolvedValue(true); - - return { mockStudentAccount, mockAdminUser }; - }; - it('should throw if account can not be updated', async () => { - const { mockStudentAccount, mockAdminUser } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { username: 'fail@to.update' } as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); + it('undefined user role fails by default', async () => { + const currentUser = { userId: mockUnknownRoleUser.id } as ICurrentUser; + const params = { id: mockAccountWithoutRole.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); }); - }); - - describe('When user can not be updated', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockRejectedValueOnce(undefined); - - accountValidationService.isUniqueEmail.mockResolvedValue(true); - - return { mockStudentAccount, mockAdminUser }; - }; - it('should throw EntityNotFoundError', async () => { - const { mockStudentAccount, mockAdminUser } = setup(); + it('user without role cannot be edited', async () => { const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { username: 'user-fail@to.update' } as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(EntityNotFoundError); - }); - }); - - describe('if target account has no user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockAccountWithoutUser = accountFactory.buildWithId({ - userId: undefined, - password: defaultPasswordHash, - systemId: systemFactory.buildWithId().id, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockAccountWithoutUser)); - - return { mockSuperheroUser, mockAccountWithoutUser }; - }; - - it('should throw EntityNotFoundError', async () => { - const { mockSuperheroUser, mockAccountWithoutUser } = setup(); - await expect( - accountUc.updateAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: mockAccountWithoutUser.id } as AccountByIdParams, - { username: 'user-fail@to.update' } as AccountByIdBodyParams - ) - ).rejects.toThrow(EntityNotFoundError); - }); - }); - - describe('When new username already in use', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockOtherTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockOtherTeacherAccount = accountFactory.buildWithId({ - userId: mockOtherTeacherUser.id, - password: defaultPasswordHash, - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - userRepo.save.mockRejectedValueOnce(undefined); - - accountValidationService.isUniqueEmail.mockResolvedValueOnce(false); - - return { mockStudentAccount, mockAdminUser, mockOtherTeacherAccount }; - }; - it('should throw ValidationError', async () => { - const { mockStudentAccount, mockAdminUser, mockOtherTeacherAccount } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = { username: mockOtherTeacherAccount.username } as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ValidationError); - }); - }); - - describe('hasPermissionsToUpdateAccount', () => { - describe('When using an admin user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockTeacherUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - - return { mockAdminUser, mockTeacherAccount }; - }; - it('should not throw error when editing a teacher', async () => { - const { mockAdminUser, mockTeacherAccount } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockTeacherAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); - }); - }); - - describe('When using a teacher user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - return { mockStudentAccount, mockTeacherUser }; - }; - it('should not throw error when editing a student', async () => { - const { mockTeacherUser, mockStudentAccount } = setup(); - const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); - }); - }); - describe('When using an admin user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockStudentUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - return { mockStudentAccount, mockAdminUser }; - }; - it('should not throw error when editing a student', async () => { - const { mockAdminUser, mockStudentAccount } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockStudentAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); - }); - }); - - describe('When using a teacher user to edit another teacher', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockOtherTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockOtherTeacherAccount = accountFactory.buildWithId({ - userId: mockOtherTeacherUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockTeacherUser).mockResolvedValueOnce(mockOtherTeacherUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockOtherTeacherAccount)); - - return { mockOtherTeacherAccount, mockTeacherUser }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockTeacherUser, mockOtherTeacherAccount } = setup(); - const currentUser = { userId: mockTeacherUser.id } as ICurrentUser; - const params = { id: mockOtherTeacherAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); - }); - }); - - describe('When using an admin user of other school', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - const mockOtherSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockTeacherUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.TEACHER, - permissions: [Permission.STUDENT_EDIT, Permission.STUDENT_LIST, Permission.TEACHER_LIST], - }), - ], - }); - const mockDifferentSchoolAdminUser = userFactory.buildWithId({ - school: mockOtherSchool, - roles: [...mockAdminUser.roles], - }); - - const mockTeacherAccount = accountFactory.buildWithId({ - userId: mockTeacherUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockDifferentSchoolAdminUser).mockResolvedValueOnce(mockTeacherUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockTeacherAccount)); - - return { mockDifferentSchoolAdminUser, mockTeacherAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockDifferentSchoolAdminUser, mockTeacherAccount } = setup(); - const currentUser = { userId: mockDifferentSchoolAdminUser.id } as ICurrentUser; - const params = { id: mockTeacherAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); - }); - }); - - describe('When using a superhero user', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockAdminAccount = accountFactory.buildWithId({ - userId: mockAdminUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockSuperheroUser).mockResolvedValueOnce(mockAdminUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockAdminAccount)); - - return { mockAdminAccount, mockSuperheroUser }; - }; - it('should not throw error when editing a admin', async () => { - const { mockSuperheroUser, mockAdminAccount } = setup(); - const currentUser = { userId: mockSuperheroUser.id } as ICurrentUser; - const params = { id: mockAdminAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).resolves.not.toThrow(); - }); - }); - - describe('When using an user with undefined role', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockUserWithoutRole = userFactory.buildWithId({ - school: mockSchool, - roles: [], - }); - const mockUnknownRoleUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: 'undefinedRole' as RoleName, permissions: ['' as Permission] })], - }); - const mockAccountWithoutRole = accountFactory.buildWithId({ - userId: mockUserWithoutRole.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockUnknownRoleUser).mockResolvedValueOnce(mockUserWithoutRole); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockAccountWithoutRole)); - - return { mockAccountWithoutRole, mockUnknownRoleUser }; - }; - it('should fail by default', async () => { - const { mockUnknownRoleUser, mockAccountWithoutRole } = setup(); - const currentUser = { userId: mockUnknownRoleUser.id } as ICurrentUser; - const params = { id: mockAccountWithoutRole.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); - }); - }); - - describe('When editing an user without role', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - const mockUnknownRoleUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: 'undefinedRole' as RoleName, permissions: ['' as Permission] })], - }); - const mockUnknownRoleUserAccount = accountFactory.buildWithId({ - userId: mockUnknownRoleUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValueOnce(mockAdminUser).mockResolvedValueOnce(mockUnknownRoleUser); - accountService.findById.mockResolvedValueOnce(AccountEntityToDtoMapper.mapToDto(mockUnknownRoleUserAccount)); - - return { mockAdminUser, mockUnknownRoleUserAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockAdminUser, mockUnknownRoleUserAccount } = setup(); - const currentUser = { userId: mockAdminUser.id } as ICurrentUser; - const params = { id: mockUnknownRoleUserAccount.id } as AccountByIdParams; - const body = {} as AccountByIdBodyParams; - await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); - }); + const params = { id: mockUnknownRoleUserAccount.id } as AccountByIdParams; + const body = {} as AccountByIdBodyParams; + await expect(accountUc.updateAccountById(currentUser, params, body)).rejects.toThrow(ForbiddenOperationError); }); }); }); describe('deleteAccountById', () => { - describe('When current user is authorized', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockResolvedValue(mockSuperheroUser); - - accountService.findById.mockResolvedValue(AccountEntityToDtoMapper.mapToDto(mockStudentAccount)); - - return { mockSuperheroUser, mockStudentAccount }; - }; - it('should delete an account', async () => { - const { mockSuperheroUser, mockStudentAccount } = setup(); - await expect( - accountUc.deleteAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: mockStudentAccount.id } as AccountByIdParams - ) - ).resolves.not.toThrow(); - }); + it('should delete an account, if current user is authorized', async () => { + await expect( + accountUc.deleteAccountById( + { userId: mockSuperheroUser.id } as ICurrentUser, + { id: mockStudentAccount.id } as AccountByIdParams + ) + ).resolves.not.toThrow(); }); - - describe('When the current user is not superhero', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockAdminUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.ADMINISTRATOR, - permissions: [ - Permission.TEACHER_EDIT, - Permission.STUDENT_EDIT, - Permission.STUDENT_LIST, - Permission.TEACHER_LIST, - Permission.TEACHER_CREATE, - Permission.STUDENT_CREATE, - Permission.TEACHER_DELETE, - Permission.STUDENT_DELETE, - ], - }), - ], - }); - - const mockStudentUser = userFactory.buildWithId({ - school: mockSchool, - roles: [new Role({ name: RoleName.STUDENT, permissions: [] })], - }); - - const mockStudentAccount = accountFactory.buildWithId({ - userId: mockStudentUser.id, - password: defaultPasswordHash, - }); - - userRepo.findById.mockImplementation((userId: EntityId): Promise => { - if (mockAdminUser.id === userId) { - return Promise.resolve(mockAdminUser); - } - throw new EntityNotFoundError(User.name); - }); - - return { mockAdminUser, mockStudentAccount }; - }; - it('should throw ForbiddenOperationError', async () => { - const { mockAdminUser, mockStudentAccount } = setup(); - await expect( - accountUc.deleteAccountById( - { userId: mockAdminUser.id } as ICurrentUser, - { id: mockStudentAccount.id } as AccountByIdParams - ) - ).rejects.toThrow(ForbiddenOperationError); - }); + it('should throw, if the current user is no superhero', async () => { + await expect( + accountUc.deleteAccountById( + { userId: mockAdminUser.id } as ICurrentUser, + { id: mockStudentAccount.id } as AccountByIdParams + ) + ).rejects.toThrow(ForbiddenOperationError); }); - - describe('When no account matches the search term', () => { - const setup = () => { - const mockSchool = schoolFactory.buildWithId(); - - const mockSuperheroUser = userFactory.buildWithId({ - school: mockSchool, - roles: [ - new Role({ - name: RoleName.SUPERHERO, - permissions: [Permission.TEACHER_EDIT, Permission.STUDENT_EDIT], - }), - ], - }); - - userRepo.findById.mockImplementation((userId: EntityId): Promise => { - if (mockSuperheroUser.id === userId) { - return Promise.resolve(mockSuperheroUser); - } - throw new EntityNotFoundError(User.name); - }); - - accountService.findById.mockImplementation((id: EntityId): Promise => { - if (id === 'xxx') { - throw new EntityNotFoundError(Account.name); - } - return Promise.reject(); - }); - - return { mockSuperheroUser }; - }; - it('should throw, if no account matches the search term', async () => { - const { mockSuperheroUser } = setup(); - await expect( - accountUc.deleteAccountById( - { userId: mockSuperheroUser.id } as ICurrentUser, - { id: 'xxx' } as AccountByIdParams - ) - ).rejects.toThrow(EntityNotFoundError); - }); + it('should throw, if no account matches the search term', async () => { + await expect( + accountUc.deleteAccountById( + { userId: mockSuperheroUser.id } as ICurrentUser, + { id: 'xxx' } as AccountByIdParams + ) + ).rejects.toThrow(EntityNotFoundError); }); }); describe('checkBrutForce', () => { - describe('When time difference < the allowed time', () => { - const setup = () => { - const mockAccountWithLastFailedLogin = accountFactory.buildWithId({ - userId: undefined, - password: defaultPasswordHash, - systemId: systemFactory.buildWithId().id, - lasttriedFailedLogin: new Date(), - }); - - configService.get.mockReturnValue(LOGIN_BLOCK_TIME); - - accountService.findByUsernameAndSystemId.mockImplementation( - (username: string, systemId: EntityId | ObjectId): Promise => { - if ( - mockAccountWithLastFailedLogin.username === username && - mockAccountWithLastFailedLogin.systemId === systemId - ) { - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockAccountWithLastFailedLogin)); - } - throw new EntityNotFoundError(Account.name); - } - ); - - return { mockAccountWithLastFailedLogin }; - }; - - it('should throw BruteForcePrevention', async () => { - const { mockAccountWithLastFailedLogin } = setup(); - await expect( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - accountUc.checkBrutForce(mockAccountWithLastFailedLogin.username, mockAccountWithLastFailedLogin.systemId!) - ).rejects.toThrow(BruteForcePrevention); - }); + let updateMock: jest.Mock; + beforeAll(() => { + configService.get.mockReturnValue(LOGIN_BLOCK_TIME); }); - - describe('When the time difference > the allowed time', () => { - const setup = () => { - const mockAccountWithSystemId = accountFactory.withSystemId(new ObjectId(10)).build(); - - // eslint-disable-next-line jest/unbound-method - const updateMock = accountService.updateLastTriedFailedLogin as jest.Mock; - - configService.get.mockReturnValue(LOGIN_BLOCK_TIME); - - accountService.findByUsernameAndSystemId.mockImplementation( - (username: string, systemId: EntityId | ObjectId): Promise => { - if (mockAccountWithSystemId.username === username && mockAccountWithSystemId.systemId === systemId) { - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockAccountWithSystemId)); - } - throw new EntityNotFoundError(Account.name); - } - ); - - return { mockAccountWithSystemId, updateMock }; - }; - - it('should not throw Error, ', async () => { - const { mockAccountWithSystemId, updateMock } = setup(); - - await expect( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - accountUc.checkBrutForce(mockAccountWithSystemId.username, mockAccountWithSystemId.systemId!) - ).resolves.not.toThrow(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(updateMock.mock.calls[0][0]).toEqual(mockAccountWithSystemId.id); - const newDate = new Date().getTime() - 10000; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect((updateMock.mock.calls[0][1] as Date).getTime()).toBeGreaterThan(newDate); - }); + afterAll(() => { + configService.get.mockRestore(); }); - - describe('When lasttriedFailedLogin is undefined', () => { - const setup = () => { - const mockAccountWithNoLastFailedLogin = accountFactory.buildWithId({ - userId: undefined, - password: defaultPasswordHash, - systemId: systemFactory.buildWithId().id, - lasttriedFailedLogin: undefined, - }); - - configService.get.mockReturnValue(LOGIN_BLOCK_TIME); - - accountService.findByUsernameAndSystemId.mockImplementation( - (username: string, systemId: EntityId | ObjectId): Promise => { - if ( - mockAccountWithNoLastFailedLogin.username === username && - mockAccountWithNoLastFailedLogin.systemId === systemId - ) { - return Promise.resolve(AccountEntityToDtoMapper.mapToDto(mockAccountWithNoLastFailedLogin)); - } - throw new EntityNotFoundError(Account.name); - } - ); - - return { mockAccountWithNoLastFailedLogin }; - }; - it('should not throw error', async () => { - const { mockAccountWithNoLastFailedLogin } = setup(); - await expect( - accountUc.checkBrutForce( - mockAccountWithNoLastFailedLogin.username, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - mockAccountWithNoLastFailedLogin.systemId! - ) - ).resolves.not.toThrow(); - }); + beforeEach(() => { + // eslint-disable-next-line jest/unbound-method + updateMock = accountService.updateLastTriedFailedLogin as jest.Mock; + updateMock.mockClear(); + }); + it('should throw, if time difference < the allowed time', async () => { + await expect( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + accountUc.checkBrutForce(mockAccountWithLastFailedLogin.username, mockAccountWithLastFailedLogin.systemId!) + ).rejects.toThrow(BruteForcePrevention); + }); + it('should not throw Error, if the time difference > the allowed time', async () => { + await expect( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + accountUc.checkBrutForce(mockAccountWithSystemId.username, mockAccountWithSystemId.systemId!) + ).resolves.not.toThrow(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(updateMock.mock.calls[0][0]).toEqual(mockAccountWithSystemId.id); + const newDate = new Date().getTime() - 10000; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect((updateMock.mock.calls[0][1] as Date).getTime()).toBeGreaterThan(newDate); + }); + it('should not throw, if lasttriedFailedLogin is undefined', async () => { + await expect( + accountUc.checkBrutForce( + mockAccountWithNoLastFailedLogin.username, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + mockAccountWithNoLastFailedLogin.systemId! + ) + ).resolves.not.toThrow(); }); }); }); diff --git a/apps/server/src/modules/account/uc/account.uc.ts b/apps/server/src/modules/account/uc/account.uc.ts index f9e21b28c63..ac4ac053dae 100644 --- a/apps/server/src/modules/account/uc/account.uc.ts +++ b/apps/server/src/modules/account/uc/account.uc.ts @@ -8,7 +8,6 @@ import { } from '@shared/common/error'; import { Account, EntityId, Permission, PermissionService, Role, RoleName, SchoolEntity, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; -// TODO: module internals should be imported with relative paths import { AccountService } from '@modules/account/services/account.service'; import { AccountDto } from '@modules/account/services/dto/account.dto'; @@ -43,14 +42,6 @@ export class AccountUc { private readonly configService: ConfigService ) {} - /* HINT: there is a lot of logic here that would belong into service layer, - but since that wasnt decided when this code was written this work is not prioritised right now - - Also this is mostly directly ported feathers code, that needs a general refactoring/rewrite pass - - also it should use the new authorisation service - */ - /** * This method processes the request on the GET account search endpoint from the account controller. * @@ -64,9 +55,7 @@ export class AccountUc { const limit = query.limit ?? 10; const executingUser = await this.userRepo.findById(currentUser.userId, true); - // HINT: this can be extracted if (query.type === AccountSearchType.USERNAME) { - // HINT: even superheroes should in the future be permission based if (!(await this.isSuperhero(currentUser))) { throw new ForbiddenOperationError('Current user is not authorized to search for accounts.'); } @@ -83,10 +72,8 @@ export class AccountUc { } const account = await this.accountService.findByUserId(query.value); if (account) { - // HINT: skip and limit should be from the query return new AccountSearchListResponse([AccountResponseMapper.mapToResponse(account)], 1, 0, 1); } - // HINT: skip and limit should be from the query return new AccountSearchListResponse([], 0, 0, 0); } @@ -106,7 +93,7 @@ export class AccountUc { throw new ForbiddenOperationError('Current user is not authorized to search for accounts.'); } const account = await this.accountService.findById(params.id); - return AccountResponseMapper.mapToResponse(account); // TODO: mapping should be done in controller + return AccountResponseMapper.mapToResponse(account); } async saveAccount(dto: AccountSaveDto): Promise { @@ -175,8 +162,6 @@ export class AccountUc { throw new EntityNotFoundError(Account.name); } } - // TODO: mapping from domain to api dto should be a responsability of the controller - return AccountResponseMapper.mapToResponse(targetAccount); } @@ -315,7 +300,6 @@ export class AccountUc { } } - // TODO: remove /** * * @deprecated this is for legacy login strategies only. Login strategies in Nest.js should use {@link AuthenticationService} diff --git a/apps/server/src/shared/testing/factory/account.factory.ts b/apps/server/src/shared/testing/factory/account.factory.ts index a3568dbf80a..b0c0b8434c1 100644 --- a/apps/server/src/shared/testing/factory/account.factory.ts +++ b/apps/server/src/shared/testing/factory/account.factory.ts @@ -5,8 +5,6 @@ import { ObjectId } from 'bson'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; -export const defaultTestPassword = 'DummyPasswd!1'; -export const defaultTestPasswordHash = '$2a$10$/DsztV5o6P5piW2eWJsxw.4nHovmJGBA.QNwiTmuZ/uvUc40b.Uhu'; class AccountFactory extends BaseFactory { withSystemId(id: EntityId | ObjectId): this { const params: DeepPartial = { systemId: id }; @@ -23,36 +21,10 @@ class AccountFactory extends BaseFactory { return this.params(params); } - - withAllProperties(): this { - return this.params({ - userId: new ObjectId(), - username: 'username', - activated: true, - credentialHash: 'credentialHash', - expiresAt: new Date(), - lasttriedFailedLogin: new Date(), - password: defaultTestPassword, - systemId: new ObjectId(), - token: 'token', - }).afterBuild((acc) => { - return { - ...acc, - createdAt: new Date(), - updatedAt: new Date(), - }; - }); - } - - withoutSystemAndUserId(): this { - return this.params({ - username: 'username', - systemId: undefined, - userId: undefined, - }); - } } +export const defaultTestPassword = 'DummyPasswd!1'; +export const defaultTestPasswordHash = '$2a$10$/DsztV5o6P5piW2eWJsxw.4nHovmJGBA.QNwiTmuZ/uvUc40b.Uhu'; // !!! important username should not be contain a space !!! export const accountFactory = AccountFactory.define(Account, ({ sequence }) => { return { From 664a97bf6f0f9b9905597ad7a4cb295db29b2b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= <103562092+MarvinOehlerkingCap@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:44:35 +0200 Subject: [PATCH 11/19] N21-1395 Fix authorization not working for CTL board elements after authorization refactoring (#4491) --- .../modules/tool/common/common-tool.module.ts | 25 +- .../common/mapper/context-type.mapper.spec.ts | 11 - .../tool/common/mapper/context-type.mapper.ts | 13 - .../src/modules/tool/common/mapper/index.ts | 2 +- .../mapper/tool-status-response.mapper.ts | 2 +- .../tool/common/uc/tool-permission-helper.ts | 43 ++- .../common/uc/tool-permissions-helper.spec.ts | 129 +++++--- .../api-test/tool-context.api.spec.ts | 12 +- .../controller/tool-context.controller.ts | 4 +- .../context-external-tool-request.mapper.ts | 5 +- ...ontext-external-tool-validation.service.ts | 5 +- .../uc/context-external-tool.uc.spec.ts | 291 ++++++++++++++---- .../uc/context-external-tool.uc.ts | 62 +++- .../src/modules/tool/tool-api.module.ts | 12 +- .../board/board-do-authorizable.factory.ts | 14 + ...ts => external-tool-element.do.factory.ts} | 0 .../factory/domainobject/board/index.ts | 2 +- 17 files changed, 429 insertions(+), 203 deletions(-) delete mode 100644 apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts delete mode 100644 apps/server/src/modules/tool/common/mapper/context-type.mapper.ts create mode 100644 apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts rename apps/server/src/shared/testing/factory/domainobject/board/{external-tool.do.factory.ts => external-tool-element.do.factory.ts} (100%) diff --git a/apps/server/src/modules/tool/common/common-tool.module.ts b/apps/server/src/modules/tool/common/common-tool.module.ts index f56f595a99c..57375c67e96 100644 --- a/apps/server/src/modules/tool/common/common-tool.module.ts +++ b/apps/server/src/modules/tool/common/common-tool.module.ts @@ -1,28 +1,13 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { Module } from '@nestjs/common'; import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@modules/authorization'; -import { LegacySchoolModule } from '@modules/legacy-school'; -import { LearnroomModule } from '@modules/learnroom'; import { CommonToolService, CommonToolValidationService } from './service'; -import { ToolPermissionHelper } from './uc/tool-permission-helper'; @Module({ - imports: [LoggerModule, forwardRef(() => AuthorizationModule), LegacySchoolModule, LearnroomModule], + imports: [LoggerModule, LegacySchoolModule], // TODO: make deletion of entities cascading, adjust ExternalToolService.deleteExternalTool and remove the repos from here - providers: [ - CommonToolService, - CommonToolValidationService, - ToolPermissionHelper, - SchoolExternalToolRepo, - ContextExternalToolRepo, - ], - exports: [ - CommonToolService, - CommonToolValidationService, - ToolPermissionHelper, - SchoolExternalToolRepo, - ContextExternalToolRepo, - ], + providers: [CommonToolService, CommonToolValidationService, SchoolExternalToolRepo, ContextExternalToolRepo], + exports: [CommonToolService, CommonToolValidationService, SchoolExternalToolRepo, ContextExternalToolRepo], }) export class CommonToolModule {} diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts deleted file mode 100644 index b05f50fc46c..00000000000 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuthorizableReferenceType } from '@modules/authorization/domain'; -import { ToolContextType } from '../enum'; -import { ContextTypeMapper } from './context-type.mapper'; - -describe('context-type.mapper', () => { - it('should map ToolContextType.COURSE to AuthorizableReferenceType.Course', () => { - const mappedCourse = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(ToolContextType.COURSE); - - expect(mappedCourse).toEqual(AuthorizableReferenceType.Course); - }); -}); diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts deleted file mode 100644 index 3ae6902c232..00000000000 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AuthorizableReferenceType } from '@modules/authorization/domain/'; -import { ToolContextType } from '../enum'; - -const typeMapping: Record = { - [ToolContextType.COURSE]: AuthorizableReferenceType.Course, - [ToolContextType.BOARD_ELEMENT]: AuthorizableReferenceType.BoardNode, -}; - -export class ContextTypeMapper { - static mapContextTypeToAllowedAuthorizationEntityType(type: ToolContextType): AuthorizableReferenceType { - return typeMapping[type]; - } -} diff --git a/apps/server/src/modules/tool/common/mapper/index.ts b/apps/server/src/modules/tool/common/mapper/index.ts index 3da6b0fa28b..b6be27cdc1f 100644 --- a/apps/server/src/modules/tool/common/mapper/index.ts +++ b/apps/server/src/modules/tool/common/mapper/index.ts @@ -1 +1 @@ -export * from './context-type.mapper'; +export * from './tool-status-response.mapper'; diff --git a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts index c199fc6f307..0c16ca50e9a 100644 --- a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts @@ -1,4 +1,4 @@ -import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; import { ToolConfigurationStatus } from '../enum'; export const statusMapping: Record = { diff --git a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts index 542903bc4be..525c8c5d3b6 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts @@ -1,11 +1,13 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; -import { AuthorizationContext, AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; +import { AuthorizationContext, AuthorizationService, ForbiddenLoggableException } from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { BoardDoAuthorizableService, ContentElementService } from '@modules/board'; import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { BoardDoAuthorizable, Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; -// import { ContextTypeMapper } from '../mapper'; +import { ToolContextType } from '../enum'; @Injectable() export class ToolPermissionHelper { @@ -15,7 +17,9 @@ export class ToolPermissionHelper { // invalid dependency on this place it is in UC layer in a other module // loading of ressources should be part of service layer // if it must resolve different loadings based on the request it can be added in own service and use in UC - private readonly courseService: CourseService + private readonly courseService: CourseService, + private readonly boardElementService: ContentElementService, + private readonly boardService: BoardDoAuthorizableService ) {} // TODO build interface to get contextDO by contextType @@ -24,19 +28,24 @@ export class ToolPermissionHelper { contextExternalTool: ContextExternalTool, context: AuthorizationContext ): Promise { - // loading of ressources should be part of the UC -> unnessasary awaits - const [authorizableUser, course]: [User, Course] = await Promise.all([ - this.authorizationService.getUserWithPermissions(userId), - this.courseService.findById(contextExternalTool.contextRef.id), - ]); + const authorizableUser = await this.authorizationService.getUserWithPermissions(userId); - if (contextExternalTool.id) { - this.authorizationService.checkPermission(authorizableUser, contextExternalTool, context); - } + this.authorizationService.checkPermission(authorizableUser, contextExternalTool, context); + + if (contextExternalTool.contextRef.type === ToolContextType.COURSE) { + // loading of ressources should be part of the UC -> unnessasary awaits + const course: Course = await this.courseService.findById(contextExternalTool.contextRef.id); + + this.authorizationService.checkPermission(authorizableUser, course, context); + } else if (contextExternalTool.contextRef.type === ToolContextType.BOARD_ELEMENT) { + const boardElement = await this.boardElementService.findById(contextExternalTool.contextRef.id); - // const type = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(contextExternalTool.contextRef.type); - // no different types possible until it is fixed. - this.authorizationService.checkPermission(authorizableUser, course, context); + const board: BoardDoAuthorizable = await this.boardService.getBoardAuthorizable(boardElement); + + this.authorizationService.checkPermission(authorizableUser, board, context); + } else { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } } public async ensureSchoolPermissions( diff --git a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts index ad697a94694..aace35579db 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts @@ -1,29 +1,41 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { + AuthorizationContext, + AuthorizationContextBuilder, + AuthorizationService, + ForbiddenLoggableException, +} from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { BoardDoAuthorizableService, ContentElementService } from '@modules/board'; +import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { BoardDoAuthorizable, ExternalToolElement, LegacySchoolDo, Permission } from '@shared/domain'; import { contextExternalToolFactory, courseFactory, + externalToolElementFactory, legacySchoolDoFactory, schoolExternalToolFactory, setupEntities, userFactory, } from '@shared/testing'; -import { Permission, LegacySchoolDo } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { ForbiddenException } from '@nestjs/common'; -import { CourseService } from '@modules/learnroom'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ToolPermissionHelper } from './tool-permission-helper'; +import { boardDoAuthorizableFactory } from '@shared/testing/factory/domainobject/board/board-do-authorizable.factory'; +import { ContextExternalTool, ContextRef } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ToolContextType } from '../enum'; +import { ToolPermissionHelper } from './tool-permission-helper'; describe('ToolPermissionHelper', () => { let module: TestingModule; let helper: ToolPermissionHelper; let authorizationService: DeepMocked; - let courseService: DeepMocked; let schoolService: DeepMocked; + let courseService: DeepMocked; + let contentElementService: DeepMocked; + let boardDoAuthorizableService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -34,21 +46,31 @@ describe('ToolPermissionHelper', () => { provide: AuthorizationService, useValue: createMock(), }, + { + provide: LegacySchoolService, + useValue: createMock(), + }, { provide: CourseService, useValue: createMock(), }, { - provide: LegacySchoolService, - useValue: createMock(), + provide: ContentElementService, + useValue: createMock(), + }, + { + provide: BoardDoAuthorizableService, + useValue: createMock(), }, ], }).compile(); helper = module.get(ToolPermissionHelper); authorizationService = module.get(AuthorizationService); - courseService = module.get(CourseService); schoolService = module.get(LegacySchoolService); + courseService = module.get(CourseService); + contentElementService = module.get(ContentElementService); + boardDoAuthorizableService = module.get(BoardDoAuthorizableService); }); afterAll(async () => { @@ -60,16 +82,20 @@ describe('ToolPermissionHelper', () => { }); describe('ensureContextPermissions', () => { - describe('when context external tool with id is given', () => { + describe('when a context external tool for context "course" is given', () => { const setup = () => { const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: new ContextRef({ + id: course.id, + type: ToolContextType.COURSE, + }), + }); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); - authorizationService.checkPermission.mockReturnValueOnce().mockReturnValueOnce(); + courseService.findById.mockResolvedValueOnce(course); return { user, @@ -88,70 +114,97 @@ describe('ToolPermissionHelper', () => { expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(1, user, contextExternalTool, context); expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(2, user, course, context); }); + }); - it('should return undefined', async () => { - const { user, contextExternalTool, context } = setup(); + describe('when a context external tool for context "board element" is given', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const externalToolElement: ExternalToolElement = externalToolElementFactory.build(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: new ContextRef({ + id: externalToolElement.id, + type: ToolContextType.BOARD_ELEMENT, + }), + }); + const board: BoardDoAuthorizable = boardDoAuthorizableFactory.build(); + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - const result = await helper.ensureContextPermissions(user.id, contextExternalTool, context); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + contentElementService.findById.mockResolvedValueOnce(externalToolElement); + boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValueOnce(board); - expect(result).toBeUndefined(); + return { + user, + board, + contextExternalTool, + context, + }; + }; + + it('should check permission for context external tool', async () => { + const { user, board, contextExternalTool, context } = setup(); + + await helper.ensureContextPermissions(user.id, contextExternalTool, context); + + expect(authorizationService.checkPermission).toHaveBeenCalledTimes(2); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(1, user, contextExternalTool, context); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(2, user, board, context); }); }); - describe('when context external tool without id is given', () => { + describe('when the context external tool has an unkown context', () => { const setup = () => { const user = userFactory.buildWithId(); - const course = courseFactory.buildWithId(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: { + type: 'unknown type' as unknown as ToolContextType, + }, + }); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); return { user, - course, contextExternalTool, context, }; }; - it('should check permission for context external tool', async () => { - const { user, course, contextExternalTool, context } = setup(); - - await helper.ensureContextPermissions(user.id, contextExternalTool, context); + it('should throw a forbidden loggable exception', async () => { + const { user, contextExternalTool, context } = setup(); - expect(authorizationService.checkPermission).toHaveBeenCalledTimes(1); - expect(authorizationService.checkPermission).toHaveBeenCalledWith(user, course, context); + await expect(helper.ensureContextPermissions(user.id, contextExternalTool, context)).rejects.toThrowError( + new ForbiddenLoggableException(user.id, AuthorizableReferenceType.ContextExternalToolEntity, context) + ); }); }); describe('when user is unauthorized', () => { const setup = () => { const user = userFactory.buildWithId(); - const course = courseFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + const error = new ForbiddenException(); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); authorizationService.checkPermission.mockImplementationOnce(() => { - throw new ForbiddenException(); + throw error; }); return { user, - course, contextExternalTool, context, + error, }; }; - it('should check permission for context external tool', async () => { - const { user, contextExternalTool, context } = setup(); + it('should check permission for context external tool and fail', async () => { + const { user, contextExternalTool, context, error } = setup(); await expect(helper.ensureContextPermissions(user.id, contextExternalTool, context)).rejects.toThrowError( - new ForbiddenException() + error ); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts index e2dbe6ec9ac..eb570130c4e 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts @@ -1,4 +1,5 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Course, Permission, SchoolEntity, User } from '@shared/domain'; @@ -15,18 +16,17 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@modules/server'; import { ObjectId } from 'bson'; import { CustomParameterScope, ToolContextType } from '../../../common/enum'; +import { ExternalToolEntity } from '../../../external-tool/entity'; +import { CustomParameterEntryResponse } from '../../../school-external-tool/controller/dto'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; import { ContextExternalToolPostParams, ContextExternalToolResponse, ContextExternalToolSearchListResponse, } from '../dto'; -import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; -import { ExternalToolEntity } from '../../../external-tool/entity'; -import { CustomParameterEntryResponse } from '../../../school-external-tool/controller/dto'; describe('ToolContextController (API)', () => { let app: INestApplication; @@ -116,8 +116,8 @@ describe('ToolContextController (API)', () => { describe('when user is not authorized for the requested context', () => { const setup = async () => { - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); const school = schoolFactory.build(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const course = courseFactory.build({ teachers: [teacherUser] }); const otherCourse = courseFactory.build(); const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ @@ -130,7 +130,7 @@ describe('ToolContextController (API)', () => { em.clear(); const postParams: ContextExternalToolPostParams = { - schoolToolId: school.id, + schoolToolId: schoolExternalToolEntity.id, contextId: otherCourse.id, contextType: ToolContextType.COURSE, parameters: [], diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts index a80ad3f234f..20d8b96f795 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiCreatedResponse, @@ -12,7 +13,6 @@ import { } from '@nestjs/swagger'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolRequestMapper, ContextExternalToolResponseMapper } from '../mapper'; import { ContextExternalToolUc } from '../uc'; @@ -50,6 +50,7 @@ export class ToolContextController { const createdTool: ContextExternalTool = await this.contextExternalToolUc.createContextExternalTool( currentUser.userId, + currentUser.schoolId, contextExternalTool ); @@ -152,6 +153,7 @@ export class ToolContextController { const updatedTool: ContextExternalTool = await this.contextExternalToolUc.updateContextExternalTool( currentUser.userId, + currentUser.schoolId, params.contextExternalToolId, contextExternalTool ); diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts index 45f912bf52c..951559afbcc 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts @@ -1,12 +1,11 @@ -import { ContextExternalToolPostParams } from '../controller/dto'; +import { CustomParameterEntry } from '../../common/domain'; import { CustomParameterEntryParam } from '../../school-external-tool/controller/dto'; +import { ContextExternalToolPostParams } from '../controller/dto'; import { ContextExternalToolDto } from '../uc/dto/context-external-tool.types'; -import { CustomParameterEntry } from '../../common/domain'; export class ContextExternalToolRequestMapper { static mapContextExternalToolRequest(request: ContextExternalToolPostParams): ContextExternalToolDto { return { - id: '', schoolToolRef: { schoolToolId: request.schoolToolId, }, diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts index 3777273d18e..2cce83a08b2 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts @@ -6,7 +6,6 @@ import { ExternalToolService } from '../../external-tool/service'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool } from '../domain'; -import { ContextExternalToolDto } from '../uc/dto/context-external-tool.types'; import { ContextExternalToolService } from './context-external-tool.service'; @Injectable() @@ -18,9 +17,7 @@ export class ContextExternalToolValidationService { private readonly commonToolValidationService: CommonToolValidationService ) {} - async validate(toValidate: ContextExternalToolDto): Promise { - const contextExternalTool: ContextExternalTool = new ContextExternalTool(toValidate); - + async validate(contextExternalTool: ContextExternalTool): Promise { await this.checkDuplicateInContext(contextExternalTool); const loadedSchoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts index 648ccb491fa..0fcca404049 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts @@ -1,18 +1,20 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; -import { ForbiddenException, UnprocessableEntityException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { EntityId, Permission, User } from '@shared/domain'; -import { contextExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; -import { LegacyLogger } from '@src/core/logger'; import { Action, AuthorizationContextBuilder, AuthorizationService, ForbiddenLoggableException, } from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { ForbiddenException, UnprocessableEntityException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { EntityId, Permission, User } from '@shared/domain'; +import { contextExternalToolFactory, schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; import { ContextExternalToolUc } from './context-external-tool.uc'; @@ -21,6 +23,7 @@ describe('ContextExternalToolUc', () => { let module: TestingModule; let uc: ContextExternalToolUc; + let schoolExternalToolService: DeepMocked; let contextExternalToolService: DeepMocked; let contextExternalToolValidationService: DeepMocked; let toolPermissionHelper: DeepMocked; @@ -31,6 +34,10 @@ describe('ContextExternalToolUc', () => { module = await Test.createTestingModule({ providers: [ ContextExternalToolUc, + { + provide: SchoolExternalToolService, + useValue: createMock(), + }, { provide: ContextExternalToolService, useValue: createMock(), @@ -39,10 +46,6 @@ describe('ContextExternalToolUc', () => { provide: ContextExternalToolValidationService, useValue: createMock(), }, - { - provide: LegacyLogger, - useValue: createMock(), - }, { provide: ToolPermissionHelper, useValue: createMock(), @@ -55,6 +58,7 @@ describe('ContextExternalToolUc', () => { }).compile(); uc = module.get(ContextExternalToolUc); + schoolExternalToolService = module.get(SchoolExternalToolService); contextExternalToolService = module.get(ContextExternalToolService); contextExternalToolValidationService = module.get(ContextExternalToolValidationService); toolPermissionHelper = module.get(ToolPermissionHelper); @@ -73,36 +77,46 @@ describe('ContextExternalToolUc', () => { describe('when contextExternalTool is given and user has permission ', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, }, }); - toolPermissionHelper.ensureContextPermissions.mockResolvedValue(Promise.resolve()); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolService.saveContextExternalTool.mockResolvedValue(contextExternalTool); return { contextExternalTool, userId, + schoolId, }; }; it('should call contextExternalToolService', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(contextExternalToolService.saveContextExternalTool).toHaveBeenCalledWith(contextExternalTool); }); it('should call contextExternalToolService to ensure permissions', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( userId, @@ -112,28 +126,82 @@ describe('ContextExternalToolUc', () => { }); it('should call contextExternalToolValidationService', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); }); it('should return the saved object', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - const result = await uc.createContextExternalTool(userId, contextExternalTool); + const result = await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(result).toEqual(contextExternalTool); }); }); + describe('when the user is from a different school than the school external tool', () => { + const setup = () => { + const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); + + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + + return { + contextExternalTool, + userId, + }; + }; + + it('should return UnprocessableEntity and not save', async () => { + const { contextExternalTool, userId } = setup(); + + const func = () => uc.createContextExternalTool(userId, new ObjectId().toHexString(), contextExternalTool); + + await expect(func).rejects.toThrow( + new ForbiddenLoggableException( + userId, + AuthorizableReferenceType.ContextExternalToolEntity, + AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + ) + ); + expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); + }); + }); + describe('when the user does not have permission', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, @@ -142,19 +210,21 @@ describe('ContextExternalToolUc', () => { const error = new ForbiddenException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); toolPermissionHelper.ensureContextPermissions.mockRejectedValue(error); return { contextExternalTool, userId, + schoolId, error, }; }; it('should return forbidden and not save', async () => { - const { contextExternalTool, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId } = setup(); - const func = () => uc.createContextExternalTool(userId, contextExternalTool); + const func = () => uc.createContextExternalTool(userId, schoolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -164,9 +234,18 @@ describe('ContextExternalToolUc', () => { describe('when the validation fails', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, @@ -175,19 +254,21 @@ describe('ContextExternalToolUc', () => { const error = new UnprocessableEntityException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolValidationService.validate.mockRejectedValue(error); return { contextExternalTool, userId, + schoolId, error, }; }; it('should return UnprocessableEntity and not save', async () => { - const { contextExternalTool, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId } = setup(); - const func = () => uc.createContextExternalTool(userId, contextExternalTool); + const func = () => uc.createContextExternalTool(userId, schoolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -199,40 +280,48 @@ describe('ContextExternalToolUc', () => { describe('when contextExternalTool is given and user has permission ', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, }, - contextExternalToolId - ); + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolService.saveContextExternalTool.mockResolvedValue(contextExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, }; }; it('should call contextExternalToolService', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(contextExternalToolService.saveContextExternalTool).toHaveBeenCalledWith(contextExternalTool); }); it('should call contextExternalToolService to ensure permissions', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( userId, @@ -242,54 +331,114 @@ describe('ContextExternalToolUc', () => { }); it('should call contextExternalToolValidationService', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); }); it('should return the saved object', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - const result = await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const result = await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(result).toEqual(contextExternalTool); }); }); - describe('when the user does not have permission', () => { + describe('when the user is from a different school than the school external tool', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, }, - contextExternalToolId + }); + + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + + return { + contextExternalTool, + contextExternalToolId: contextExternalTool.id as string, + userId, + }; + }; + + it('should return UnprocessableEntity and not save', async () => { + const { contextExternalTool, userId, contextExternalToolId } = setup(); + + const func = () => + uc.updateContextExternalTool( + userId, + new ObjectId().toHexString(), + contextExternalToolId, + contextExternalTool + ); + + await expect(func).rejects.toThrow( + new ForbiddenLoggableException( + userId, + AuthorizableReferenceType.ContextExternalToolEntity, + AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + ) ); + expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); + }); + }); + + describe('when the user does not have permission', () => { + const setup = () => { + const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); const error = new ForbiddenException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); toolPermissionHelper.ensureContextPermissions.mockRejectedValue(error); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, error, }; }; it('should return forbidden and not save', async () => { - const { contextExternalTool, contextExternalToolId, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId, contextExternalToolId } = setup(); - const func = () => uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const func = () => uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -299,35 +448,43 @@ describe('ContextExternalToolUc', () => { describe('when the validation fails', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, }, - contextExternalToolId - ); + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); const error = new UnprocessableEntityException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); contextExternalToolValidationService.validate.mockRejectedValue(error); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, error, }; }; it('should return UnprocessableEntity and not save', async () => { - const { contextExternalTool, contextExternalToolId, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId, contextExternalToolId } = setup(); - const func = () => uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const func = () => uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts index d29cc1454d4..587ecb01c64 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts @@ -1,9 +1,16 @@ +import { + AuthorizationContext, + AuthorizationContextBuilder, + AuthorizationService, + ForbiddenLoggableException, +} from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { Injectable } from '@nestjs/common'; import { EntityId, Permission, User } from '@shared/domain'; -import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool, ContextRef } from '../domain'; import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; import { ContextExternalToolDto } from './dto/context-external-tool.types'; @@ -12,22 +19,32 @@ import { ContextExternalToolDto } from './dto/context-external-tool.types'; export class ContextExternalToolUc { constructor( private readonly toolPermissionHelper: ToolPermissionHelper, + private readonly schoolExternalToolService: SchoolExternalToolService, private readonly contextExternalToolService: ContextExternalToolService, private readonly contextExternalToolValidationService: ContextExternalToolValidationService, - private readonly authorizationService: AuthorizationService, - private readonly logger: LegacyLogger + private readonly authorizationService: AuthorizationService ) {} async createContextExternalTool( userId: EntityId, + schoolId: EntityId, contextExternalToolDto: ContextExternalToolDto ): Promise { - const contextExternalTool = new ContextExternalTool(contextExternalToolDto); const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( + contextExternalToolDto.schoolToolRef.schoolToolId + ); + + if (schoolExternalTool.schoolId !== schoolId) { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } + + contextExternalToolDto.schoolToolRef.schoolId = schoolId; + const contextExternalTool = new ContextExternalTool(contextExternalToolDto); await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); - await this.contextExternalToolValidationService.validate(contextExternalToolDto); + await this.contextExternalToolValidationService.validate(contextExternalTool); const createdTool: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool( contextExternalTool @@ -38,27 +55,38 @@ export class ContextExternalToolUc { async updateContextExternalTool( userId: EntityId, + schoolId: EntityId, contextExternalToolId: EntityId, contextExternalToolDto: ContextExternalToolDto ): Promise { - const contextExternalTool: ContextExternalTool = new ContextExternalTool(contextExternalToolDto); + const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( + contextExternalToolDto.schoolToolRef.schoolToolId + ); + + if (schoolExternalTool.schoolId !== schoolId) { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } - await this.toolPermissionHelper.ensureContextPermissions( - userId, - contextExternalTool, - AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + let contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( + contextExternalToolId ); - const updated: ContextExternalTool = new ContextExternalTool({ - ...contextExternalTool, - id: contextExternalToolId, + contextExternalTool = new ContextExternalTool({ + ...contextExternalToolDto, + id: contextExternalTool.id, }); + contextExternalTool.schoolToolRef.schoolId = schoolId; + + await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); - await this.contextExternalToolValidationService.validate(updated); + await this.contextExternalToolValidationService.validate(contextExternalTool); - const saved: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool(updated); + const updatedTool: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool( + contextExternalTool + ); - return saved; + return updatedTool; } public async deleteContextExternalTool(userId: EntityId, contextExternalToolId: EntityId): Promise { diff --git a/apps/server/src/modules/tool/tool-api.module.ts b/apps/server/src/modules/tool/tool-api.module.ts index 8513eaed3fc..b8d12a16006 100644 --- a/apps/server/src/modules/tool/tool-api.module.ts +++ b/apps/server/src/modules/tool/tool-api.module.ts @@ -1,10 +1,13 @@ -import { Module } from '@nestjs/common'; -import { LtiToolRepo } from '@shared/repo'; -import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '@modules/authorization'; import { LegacySchoolModule } from '@modules/legacy-school'; import { UserModule } from '@modules/user'; +import { Module } from '@nestjs/common'; +import { LtiToolRepo } from '@shared/repo'; +import { LoggerModule } from '@src/core/logger'; +import { BoardModule } from '../board'; +import { LearnroomModule } from '../learnroom'; import { CommonToolModule } from './common'; +import { ToolPermissionHelper } from './common/uc/tool-permission-helper'; import { ToolContextController } from './context-external-tool/controller'; import { ToolReferenceController } from './context-external-tool/controller/tool-reference.controller'; import { ContextExternalToolUc, ToolReferenceUc } from './context-external-tool/uc'; @@ -29,6 +32,8 @@ import { ToolModule } from './tool.module'; LoggerModule, LegacySchoolModule, ToolConfigModule, + LearnroomModule, + BoardModule, ], controllers: [ ToolLaunchController, @@ -51,6 +56,7 @@ import { ToolModule } from './tool.module'; ContextExternalToolUc, ToolLaunchUc, ToolReferenceUc, + ToolPermissionHelper, ], }) export class ToolApiModule {} diff --git a/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts b/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts new file mode 100644 index 00000000000..f774ba97bb4 --- /dev/null +++ b/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts @@ -0,0 +1,14 @@ +import { BoardDoAuthorizable, BoardDoAuthorizableProps, UserRoleEnum } from '@shared/domain/domainobject/board'; +import { ObjectId } from 'bson'; +import { DomainObjectFactory } from '../domain-object.factory'; + +export const boardDoAuthorizableFactory = DomainObjectFactory.define( + BoardDoAuthorizable, + () => { + return { + id: new ObjectId().toHexString(), + users: [], + requiredUserRole: UserRoleEnum.STUDENT, + }; + } +); diff --git a/apps/server/src/shared/testing/factory/domainobject/board/external-tool.do.factory.ts b/apps/server/src/shared/testing/factory/domainobject/board/external-tool-element.do.factory.ts similarity index 100% rename from apps/server/src/shared/testing/factory/domainobject/board/external-tool.do.factory.ts rename to apps/server/src/shared/testing/factory/domainobject/board/external-tool-element.do.factory.ts diff --git a/apps/server/src/shared/testing/factory/domainobject/board/index.ts b/apps/server/src/shared/testing/factory/domainobject/board/index.ts index 9a6cdf84839..802dcf744f3 100644 --- a/apps/server/src/shared/testing/factory/domainobject/board/index.ts +++ b/apps/server/src/shared/testing/factory/domainobject/board/index.ts @@ -1,7 +1,7 @@ export * from './card.do.factory'; export * from './column-board.do.factory'; export * from './column.do.factory'; -export * from './external-tool.do.factory'; +export * from './external-tool-element.do.factory'; export * from './file-element.do.factory'; export * from './link-element.do.factory'; export * from './rich-text-element.do.factory'; From 67eca87cc91e3db2bd94fe02644f131e2d8414e0 Mon Sep 17 00:00:00 2001 From: Phillip Date: Mon, 23 Oct 2023 17:50:56 +0200 Subject: [PATCH 12/19] BC-5537 namespace activator/scaled objects (#4470) Co-authored-by: mamutmk5 <3045922+mamutmk5@users.noreply.github.com> --- .../templates/configmap_file_init.yml.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible/roles/schulcloud-server-init/templates/configmap_file_init.yml.j2 b/ansible/roles/schulcloud-server-init/templates/configmap_file_init.yml.j2 index 82ff81afb8b..a3e5459077f 100644 --- a/ansible/roles/schulcloud-server-init/templates/configmap_file_init.yml.j2 +++ b/ansible/roles/schulcloud-server-init/templates/configmap_file_init.yml.j2 @@ -25,6 +25,9 @@ data: else echo "gg, hacky mongo replicaset" fi + {% if KEDA_NAMESPACE_ACTIVATOR_ENABLED is defined %} + curl -XPUT -H 'Content-Type: application/json' -Lv 'https://activate.cd.dbildungscloud.dev/namespace' -d '{"name" : "{{ NAMESPACE }}"}' + {% endif %} curl --retry 360 --retry-connrefused --retry-delay 10 -X POST 'http://mgmt-svc:3333/api/management/database/seed?with-indexes=true' # Below is a series of a MongoDB-data initializations, meant for the development and testing From 88c6f4e43d099e1599c0851f2e7252235fe4a517 Mon Sep 17 00:00:00 2001 From: Phillip Date: Mon, 23 Oct 2023 18:14:48 +0200 Subject: [PATCH 13/19] BC-5106 specify java version, update sonarcloud (#4489) --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 626be1ccf55..964ab20c8f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,8 +83,12 @@ jobs: run: | sudo apt-get install -y lcov find coverage -name *.info -exec echo -a {} \; | xargs lcov -o merged-lcov.info + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' - name: SonarCloud upload coverage - uses: SonarSource/sonarcloud-github-action@v1.9 + uses: SonarSource/sonarcloud-github-action@v2.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} From e5bd1c9efc885eb84fe4590c186ee48e8bda4f95 Mon Sep 17 00:00:00 2001 From: virgilchiriac <17074330+virgilchiriac@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:39:56 +0200 Subject: [PATCH 14/19] BC-5649 - fix prometheus test (#4498) --- apps/server/src/shared/infra/metrics/prometheus/app.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/shared/infra/metrics/prometheus/app.spec.ts b/apps/server/src/shared/infra/metrics/prometheus/app.spec.ts index 9be4558d3f8..8e751298e90 100644 --- a/apps/server/src/shared/infra/metrics/prometheus/app.spec.ts +++ b/apps/server/src/shared/infra/metrics/prometheus/app.spec.ts @@ -6,7 +6,7 @@ describe('createPrometheusMetricsApp', () => { describe('should create an app that should', () => { it('collect all the available metrics and expose them on given route', async () => { const testMetricsRoute = '/prometheus-metrics'; - const exampleDefaultMetricName = 'process_heap_bytes'; + const exampleDefaultMetricName = 'process_cpu_user_seconds_total'; const exampleMetricsRouteMetric = `sc_api_response_time_in_seconds_count{` + `method="GET",` + From 3e112cc96f7f5b0e673639a6d36495154a14854d Mon Sep 17 00:00:00 2001 From: Max Bischof <106820326+bischofmax@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:29:56 +0200 Subject: [PATCH 15/19] BC-5597 - Fix unstable tests (#4497) --- .github/workflows/test.yml | 2 +- .../account-idm-to-dto.mapper.db.spec.ts | 20 +++++++++++++++---- .../api-test/files-security.api.spec.ts | 12 +++++++---- .../files-storage-copy-files.api.spec.ts | 17 ++++++++-------- .../files-storage-delete-files.api.spec.ts | 12 +++++++---- .../files-storage-download-upload.api.spec.ts | 12 +++++++---- .../files-storage-list-files.api.spec.ts | 12 +++++++---- .../files-storage-preview.api.spec.ts | 12 +++++++---- .../files-storage-rename-file.api.spec.ts | 12 +++++++---- .../files-storage-restore-files.api.spec.ts | 12 +++++++---- .../service/school-year.service.spec.ts | 2 -- .../api-test/task-copy-timeout.api.spec.ts | 10 ++++++++-- 12 files changed, 89 insertions(+), 46 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 964ab20c8f8..cbf52acce55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,7 +94,7 @@ jobs: SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} nest_lint: runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 6 steps: - name: checkout uses: actions/checkout@v3 diff --git a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts index 2430afe6081..0774457472a 100644 --- a/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts +++ b/apps/server/src/modules/account/mapper/account-idm-to-dto.mapper.db.spec.ts @@ -54,15 +54,27 @@ describe('AccountIdmToDtoMapperDb', () => { }); describe('when date is undefined', () => { - it('should use actual date', () => { + const setup = () => { const testIdmEntity: IdmAccount = { id: 'id', }; + + const dateMock = new Date(); + jest.useFakeTimers(); + jest.setSystemTime(dateMock); + + return { testIdmEntity, dateMock }; + }; + + it('should use actual date', () => { + const { testIdmEntity, dateMock } = setup(); + const ret = mapper.mapToDto(testIdmEntity); - const now = new Date(); - expect(ret.createdAt).toEqual(now); - expect(ret.updatedAt).toEqual(now); + expect(ret.createdAt).toEqual(dateMock); + expect(ret.updatedAt).toEqual(dateMock); + + jest.useRealTimers(); }); }); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts index d6b6f4b7479..4694b1817ac 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-security.api.spec.ts @@ -1,9 +1,11 @@ +import { createMock } from '@golevelup/ts-jest'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, fileRecordFactory, @@ -12,12 +14,12 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordListResponse, ScanResultParams } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import request from 'supertest'; import { FileRecord, FileRecordParentType } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FileRecordListResponse, ScanResultParams } from '../dto'; const baseRouteName = '/file-security'; const scanResult: ScanResultParams = { virus_detected: false }; @@ -62,6 +64,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts index e1b792dea36..22a7a11fb5b 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts @@ -1,5 +1,7 @@ import { createMock } from '@golevelup/ts-jest'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -15,19 +17,14 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; -import { - CopyFileParams, - CopyFilesOfParentParams, - FileRecordListResponse, - FileRecordResponse, -} from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; import { FileRecordParentType } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FILES_STORAGE_S3_CONNECTION } from '../../files-storage.config'; +import { CopyFileParams, CopyFilesOfParentParams, FileRecordListResponse, FileRecordResponse } from '../dto'; import { availableParentTypes } from './mocks'; const baseRouteName = '/file/copy'; @@ -107,6 +104,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts index 7a25915b3d5..0557843b8eb 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts @@ -1,5 +1,7 @@ import { createMock } from '@golevelup/ts-jest'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -14,14 +16,14 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; import { FileRecordParentType, PreviewStatus } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FILES_STORAGE_S3_CONNECTION } from '../../files-storage.config'; +import { FileRecordListResponse, FileRecordResponse } from '../dto'; import { availableParentTypes } from './mocks'; const baseRouteName = '/file/delete'; @@ -101,6 +103,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts index 331282060e2..e86b778e0ce 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts @@ -1,5 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -7,16 +9,16 @@ import { EntityId, Permission } from '@shared/domain'; import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordResponse } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; import { FileRecord } from '../../entity'; import { ErrorType } from '../../error'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FILES_STORAGE_S3_CONNECTION } from '../../files-storage.config'; import { TestHelper } from '../../helper/test-helper'; +import { FileRecordResponse } from '../dto'; import { availableParentTypes } from './mocks'; jest.mock('file-type-cjs/file-type-cjs-index', () => { @@ -114,6 +116,8 @@ describe('files-storage controller (API)', () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts index b4d974fb24e..35410d712c8 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-list-files.api.spec.ts @@ -1,4 +1,7 @@ +import { createMock } from '@golevelup/ts-jest'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -11,13 +14,12 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import request from 'supertest'; import { FileRecordParentType, PreviewStatus } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FileRecordListResponse, FileRecordResponse } from '../dto'; import { availableParentTypes } from './mocks'; const baseRouteName = '/file/list'; @@ -62,6 +64,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts index 82cc8545a97..22749024b31 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts @@ -1,5 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication, NotFoundException, StreamableFile } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -7,18 +9,18 @@ import { EntityId, Permission } from '@shared/domain'; import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordResponse } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; import { FileRecord, ScanStatus } from '../../entity'; import { ErrorType } from '../../error'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FILES_STORAGE_S3_CONNECTION } from '../../files-storage.config'; import { TestHelper } from '../../helper/test-helper'; import { PreviewWidth } from '../../interface'; import { PreviewOutputMimeTypes } from '../../interface/preview-output-mime-types.enum'; +import { FileRecordResponse } from '../dto'; jest.mock('file-type-cjs/file-type-cjs-index', () => { return { @@ -111,6 +113,8 @@ describe('File Controller (API) - preview', () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts index e28f4dc327f..fb1dfa866eb 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-rename-file.api.spec.ts @@ -1,9 +1,11 @@ +import { createMock } from '@golevelup/ts-jest'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, fileRecordFactory, @@ -12,12 +14,12 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordResponse, RenameFileParams } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import request from 'supertest'; import { FileRecord, FileRecordParentType } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FileRecordResponse, RenameFileParams } from '../dto'; const baseRouteName = '/file/rename/'; @@ -62,6 +64,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts index f6e9a694ad3..496f399d41b 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts @@ -1,5 +1,7 @@ import { createMock } from '@golevelup/ts-jest'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -14,14 +16,14 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { FILES_STORAGE_S3_CONNECTION, FilesStorageTestModule } from '@modules/files-storage'; -import { FileRecordListResponse, FileRecordResponse } from '@modules/files-storage/controller/dto'; +import NodeClam from 'clamscan'; import { Request } from 'express'; import FileType from 'file-type-cjs/file-type-cjs-index'; import request from 'supertest'; import { FileRecordParentType, PreviewStatus } from '../../entity'; +import { FilesStorageTestModule } from '../../files-storage-test.module'; +import { FILES_STORAGE_S3_CONNECTION } from '../../files-storage.config'; +import { FileRecordListResponse, FileRecordResponse } from '../dto'; import { availableParentTypes } from './mocks'; const baseRouteName = '/file/restore'; @@ -126,6 +128,8 @@ describe(`${baseRouteName} (api)`, () => { return true; }, }) + .overrideProvider(NodeClam) + .useValue(createMock()) .compile(); app = module.createNestApplication(); diff --git a/apps/server/src/modules/legacy-school/service/school-year.service.spec.ts b/apps/server/src/modules/legacy-school/service/school-year.service.spec.ts index 041b80d41d1..4aded660643 100644 --- a/apps/server/src/modules/legacy-school/service/school-year.service.spec.ts +++ b/apps/server/src/modules/legacy-school/service/school-year.service.spec.ts @@ -35,7 +35,6 @@ describe('SchoolYearService', () => { describe('getCurrentSchoolYear', () => { const setup = () => { - jest.setSystemTime(new Date('2022-06-01').getTime()); const schoolYear: SchoolYearEntity = schoolYearFactory.build({ startDate: new Date('2021-09-01'), endDate: new Date('2022-12-31'), @@ -60,7 +59,6 @@ describe('SchoolYearService', () => { describe('findById', () => { const setup = () => { - jest.setSystemTime(new Date('2022-06-01').getTime()); const schoolYear: SchoolYearEntity = schoolYearFactory.build({ startDate: new Date('2021-09-01'), endDate: new Date('2022-12-31'), diff --git a/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts b/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts index 446a0fab032..d5546c435da 100644 --- a/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts +++ b/apps/server/src/modules/task/controller/api-test/task-copy-timeout.api.spec.ts @@ -1,9 +1,12 @@ +import { createMock } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -12,12 +15,13 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { Request } from 'express'; import request from 'supertest'; +// config must be set outside before the server module is imported, otherwise the configuration is already set Configuration.set('FEATURE_COPY_SERVICE_ENABLED', true); Configuration.set('INCOMING_REQUEST_TIMEOUT_COPY_API', 1); + // eslint-disable-next-line import/first import { ServerTestModule } from '@modules/server/server.module'; @@ -42,6 +46,8 @@ describe('Task copy (API)', () => { return true; }, }) + .overrideProvider(FilesStorageClientAdapterService) + .useValue(createMock()) .compile(); app = moduleFixture.createNestApplication(); From 84c44c1bf99f92b16df1c32792b5670dbb2b4d72 Mon Sep 17 00:00:00 2001 From: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> Date: Fri, 27 Oct 2023 08:56:47 +0200 Subject: [PATCH 16/19] Bc 5170 no preview for larger files (#4457) --- .github/workflows/push.yml | 18 +- .github/workflows/tag.yml | 8 +- ...file.filestorage => Dockerfile.filepreview | 0 .../schulcloud-server-core/tasks/main.yml | 29 + .../templates/api-files-deployment.yml.j2 | 2 +- .../preview-generator-configmap.yml.j2 | 8 + .../preview-generator-deployment.yml.j2 | 46 + .../preview-generator-onepassword.yml.j2 | 9 + .../preview-generator-scaled-object.yml.j2 | 23 + .../src/apps/files-storage-consumer.app.ts | 2 +- apps/server/src/apps/files-storage.app.ts | 3 +- .../apps/preview-generator-consumer.app.ts | 17 + .../files-storage-client/mapper/index.ts | 3 +- .../service/files-storage.producer.spec.ts | 3 +- .../service/files-storage.producer.ts | 53 +- .../files-storage-preview.api.spec.ts | 15 +- .../controller/files-storage.consumer.spec.ts | 21 +- .../controller/files-storage.consumer.ts | 8 +- .../controller/files-storage.controller.ts | 6 +- .../modules/files-storage/controller/index.ts | 2 +- .../src/modules/files-storage/dto/file.dto.ts | 3 +- .../entity/filerecord.entity.spec.ts | 50 ++ .../files-storage/entity/filerecord.entity.ts | 7 + .../files-preview-amqp.module.ts | 8 + .../files-storage/files-storage.config.ts | 8 +- .../files-storage/files-storage.module.ts | 2 + .../files-storage/helper/file-record.spec.ts | 44 +- .../files-storage/helper/file-record.ts | 25 + .../server/src/modules/files-storage/index.ts | 7 +- .../files-storage/interface/interfaces.ts | 7 +- .../src/modules/files-storage/mapper/index.ts | 1 + .../mapper/preview.builder.spec.ts | 61 ++ .../files-storage/mapper/preview.builder.ts | 47 ++ .../files-storage-delete.service.spec.ts | 62 +- .../service/files-storage.service.ts | 10 +- .../service/preview.service.spec.ts | 787 +++++++----------- .../files-storage/service/preview.service.ts | 110 +-- .../uc/files-storage-delete.uc.spec.ts | 12 +- .../files-storage-download-preview.uc.spec.ts | 11 +- .../files-storage/uc/files-storage.uc.ts | 13 +- .../shared/infra/preview-generator/index.ts | 4 + .../preview-generator/interface/index.ts | 1 + .../interface/preview-consumer-config.ts | 11 + .../preview-generator/interface/preview.ts | 15 + .../loggable/preview-actions.loggable.spec.ts | 37 + .../loggable/preview-actions.loggable.ts | 19 + .../preview-generator-consumer.module.ts | 36 + .../preview-generator-producer.module.ts | 11 + .../preview-generator.builder.spec.ts | 28 + .../preview-generator.builder.ts | 16 + .../preview-generator.consumer.spec.ts | 80 ++ .../preview-generator.consumer.ts | 27 + .../preview-generator.service.spec.ts | 151 ++++ .../preview-generator.service.ts | 56 ++ .../preview.producer.spec.ts | 128 +++ .../preview-generator/preview.producer.ts | 31 + .../infra/rabbitmq}/error.mapper.spec.ts | 0 .../infra/rabbitmq}/error.mapper.ts | 0 .../infra/rabbitmq/exchange/files-preview.ts | 7 + .../shared/infra/rabbitmq/exchange/index.ts | 1 + .../server/src/shared/infra/rabbitmq/index.ts | 2 + .../shared/infra/rabbitmq/rabbitmq.module.ts | 6 +- .../rabbitmq/rpc-message-producer.spec.ts | 130 +++ .../infra/rabbitmq/rpc-message-producer.ts | 37 + .../src/shared/infra/rabbitmq/rpc-message.ts | 2 +- .../shared/infra/s3-client/interface/index.ts | 1 - config/default.schema.json | 9 +- nest-cli.json | 9 + package.json | 7 +- 69 files changed, 1654 insertions(+), 759 deletions(-) rename Dockerfile.filestorage => Dockerfile.filepreview (100%) create mode 100644 ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 create mode 100644 ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 create mode 100644 ansible/roles/schulcloud-server-core/templates/preview-generator-onepassword.yml.j2 create mode 100644 ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 create mode 100644 apps/server/src/apps/preview-generator-consumer.app.ts create mode 100644 apps/server/src/modules/files-storage/files-preview-amqp.module.ts create mode 100644 apps/server/src/modules/files-storage/mapper/preview.builder.spec.ts create mode 100644 apps/server/src/modules/files-storage/mapper/preview.builder.ts create mode 100644 apps/server/src/shared/infra/preview-generator/index.ts create mode 100644 apps/server/src/shared/infra/preview-generator/interface/index.ts create mode 100644 apps/server/src/shared/infra/preview-generator/interface/preview-consumer-config.ts create mode 100644 apps/server/src/shared/infra/preview-generator/interface/preview.ts create mode 100644 apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.spec.ts create mode 100644 apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator-consumer.module.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator-producer.module.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.builder.spec.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.builder.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.consumer.spec.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.consumer.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.service.spec.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview-generator.service.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview.producer.spec.ts create mode 100644 apps/server/src/shared/infra/preview-generator/preview.producer.ts rename apps/server/src/{modules/files-storage-client/mapper => shared/infra/rabbitmq}/error.mapper.spec.ts (100%) rename apps/server/src/{modules/files-storage-client/mapper => shared/infra/rabbitmq}/error.mapper.ts (100%) create mode 100644 apps/server/src/shared/infra/rabbitmq/exchange/files-preview.ts create mode 100644 apps/server/src/shared/infra/rabbitmq/rpc-message-producer.spec.ts create mode 100644 apps/server/src/shared/infra/rabbitmq/rpc-message-producer.ts diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 94b3514822f..c042be2c2a9 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -68,36 +68,36 @@ jobs: tags: ghcr.io/${{ github.repository }}:${{ needs.branch_meta.outputs.sha }} labels: ${{ steps.docker_meta_img.outputs.labels }} - - name: Docker meta Service Name (file storage) + - name: Docker meta Service Name (file preview) id: docker_meta_img_file_storage uses: docker/metadata-action@v4 with: images: ghcr.io/${{ github.repository }} tags: | - type=ref,event=branch,enable=false,priority=600,prefix=file-storage- - type=sha,enable=true,priority=600,prefix=file-storage- + type=ref,event=branch,enable=false,priority=600,prefix=file-preview- + type=sha,enable=true,priority=600,prefix=file-preview- labels: | org.opencontainers.image.title=schulcloud-file-storage - - name: test image exists (file storage) + - name: test image exists (file preview) run: | - echo "IMAGE_EXISTS=$(docker manifest inspect ghcr.io/${{ github.repository }}:file-storage-${{ needs.branch_meta.outputs.sha }} > /dev/null && echo 1 || echo 0)" >> $GITHUB_ENV + echo "IMAGE_EXISTS=$(docker manifest inspect ghcr.io/${{ github.repository }}:file-preview-${{ needs.branch_meta.outputs.sha }} > /dev/null && echo 1 || echo 0)" >> $GITHUB_ENV - - name: Set up Docker Buildx (file storage) + - name: Set up Docker Buildx (file preview) if: ${{ env.IMAGE_EXISTS == 0 }} uses: docker/setup-buildx-action@v2 - - name: Build and push ${{ github.repository }} (file storage) + - name: Build and push ${{ github.repository }} (file preview) if: ${{ env.IMAGE_EXISTS == 0 }} uses: docker/build-push-action@v4 with: build-args: | BASE_IMAGE=ghcr.io/${{ github.repository }}:${{ needs.branch_meta.outputs.sha }} context: . - file: ./Dockerfile.filestorage + file: ./Dockerfile.filepreview platforms: linux/amd64 push: true - tags: ghcr.io/${{ github.repository }}:file-storage-${{ needs.branch_meta.outputs.sha }} + tags: ghcr.io/${{ github.repository }}:file-preview-${{ needs.branch_meta.outputs.sha }} labels: | ${{ steps.docker_meta_img_file_storage.outputs.labels }} diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index fa62df4b0ab..8f484116ca8 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -48,14 +48,14 @@ jobs: tags: ${{ steps.docker_meta_img_hub.outputs.tags }} labels: ${{ steps.docker_meta_img_hub.outputs.labels }} - - name: Docker meta Service Name for docker hub (file storage) + - name: Docker meta Service Name for docker hub (file preview) id: docker_meta_img_hub_file_storage uses: docker/metadata-action@v4 with: images: docker.io/schulcloud/schulcloud-server, quay.io/schulcloudverbund/schulcloud-server tags: | - type=semver,pattern={{version}},prefix=file-storage-,onlatest=false - type=semver,pattern={{major}}.{{minor}},prefix=file-storage-,onlatest=false + type=semver,pattern={{version}},prefix=file-preview-,onlatest=false + type=semver,pattern={{major}}.{{minor}},prefix=file-preview-,onlatest=false labels: | org.opencontainers.image.title=schulcloud-file-storage - name: Build and push ${{ github.repository }} (file-storage) @@ -64,7 +64,7 @@ jobs: build-args: | BASE_IMAGE=quay.io/schulcloudverbund/schulcloud-server:${{ github.ref_name }} context: . - file: ./Dockerfile.filestorage + file: ./Dockerfile.filepreview platforms: linux/amd64 push: true tags: ${{ steps.docker_meta_img_hub_file_storage.outputs.tags }} diff --git a/Dockerfile.filestorage b/Dockerfile.filepreview similarity index 100% rename from Dockerfile.filestorage rename to Dockerfile.filepreview diff --git a/ansible/roles/schulcloud-server-core/tasks/main.yml b/ansible/roles/schulcloud-server-core/tasks/main.yml index fff5b10197a..7f1bbeeecfe 100644 --- a/ansible/roles/schulcloud-server-core/tasks/main.yml +++ b/ansible/roles/schulcloud-server-core/tasks/main.yml @@ -83,3 +83,32 @@ kubeconfig: ~/.kube/config namespace: "{{ NAMESPACE }}" template: amqp-files-deployment.yml.j2 + + - name: Preview Generator Deployment + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: preview-generator-deployment.yml.j2 + + - name: preview generator configmap + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: preview-generator-configmap.yml.j2 + apply: yes + + - name: preview generator Secret by 1Password + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: preview-generator-onepassword.yml.j2 + when: ONEPASSWORD_OPERATOR is defined and ONEPASSWORD_OPERATOR|bool + + - name: preview generator scaled object + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: preview-generator-scaled-object.yml.j2 + when: + - KEDA_ENABLED is defined and KEDA_ENABLED|bool + - SCALED_PREVIEW_GENERATOR_ENABLED is defined and SCALED_PREVIEW_GENERATOR_ENABLED|bool diff --git a/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 index 7f5a5b7e50b..727b5a9f4f0 100644 --- a/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 @@ -29,7 +29,7 @@ spec: runAsNonRoot: true containers: - name: api-files - image: {{ SCHULCLOUD_SERVER_IMAGE }}:file-storage-{{ SCHULCLOUD_SERVER_IMAGE_TAG }} + image: {{ SCHULCLOUD_SERVER_IMAGE }}:{{ SCHULCLOUD_SERVER_IMAGE_TAG }} imagePullPolicy: IfNotPresent ports: - containerPort: 4444 diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 new file mode 100644 index 00000000000..457a5e47364 --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: preview-generator-configmap + namespace: {{ NAMESPACE }} + labels: + app: preview-generator +data: diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 new file mode 100644 index 00000000000..51d87b88755 --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: preview-generator-deployment + namespace: {{ NAMESPACE }} + labels: + app: preview-generator +spec: + replicas: {{ AMQP_FILE_PREVIEW_REPLICAS|default("1", true) }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + #maxUnavailable: 1 + revisionHistoryLimit: 4 + paused: false + selector: + matchLabels: + app: preview-generator + template: + metadata: + labels: + app: preview-generator + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + runAsNonRoot: true + containers: + - name: preview-generator + image: {{ SCHULCLOUD_SERVER_IMAGE }}:file-preview-{{ SCHULCLOUD_SERVER_IMAGE_TAG }} + imagePullPolicy: IfNotPresent + envFrom: + - configMapRef: + name: preview-generator-configmap + - secretRef: + name: preview-generator-secret + command: ['npm', 'run', 'nest:start:preview-generator-amqp:prod'] + resources: + limits: + cpu: {{ AMQP_FILE_PREVIEW_CPU_LIMITS|default("4000m", true) }} + memory: {{ AMQP_FILE_PREVIEW_MEMORY_LIMITS|default("4000Mi", true) }} + requests: + cpu: {{ AMQP_FILE_PREVIEW_CPU_REQUESTS|default("100m", true) }} + memory: {{ AMQP_FILE_PREVIEW_MEMORY_REQUESTS|default("250Mi", true) }} diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-onepassword.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-onepassword.yml.j2 new file mode 100644 index 00000000000..51d979e27e0 --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-onepassword.yml.j2 @@ -0,0 +1,9 @@ +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: preview-generator-secret + namespace: {{ NAMESPACE }} + labels: + app: preview-generator +spec: + itemPath: "vaults/{{ ONEPASSWORD_OPERATOR_VAULT }}/items/preview-generator" diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 new file mode 100644 index 00000000000..2f8f9091b8e --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 @@ -0,0 +1,23 @@ +--- +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: preview-generator-rabbitmq-scaledobject + namespace: {{ NAMESPACE }} + labels: + app: preview-generator +spec: + scaleTargetRef: + name: preview-generator-deployment + idleReplicaCount: {{ AMQP_FILE_PREVIEW_IDLE_REPLICA_COUNT|default("1", true) }} + minReplicaCount: {{ AMQP_FILE_PREVIEW_MIN_REPLICA_COUNT|default("1", true) }} + maxReplicaCount: {{ AMQP_FILE_PREVIEW_MAX_REPLICA_COUNT|default("5", true) }} + triggers: + - type: rabbitmq + metadata: + protocol: amqp + queueName: generate-preview + mode: QueueLength + value: "1" + authenticationRef: + name: rabbitmq-trigger-auth diff --git a/apps/server/src/apps/files-storage-consumer.app.ts b/apps/server/src/apps/files-storage-consumer.app.ts index a18b5f4604b..cb62c9c76f9 100644 --- a/apps/server/src/apps/files-storage-consumer.app.ts +++ b/apps/server/src/apps/files-storage-consumer.app.ts @@ -3,7 +3,7 @@ import { NestFactory } from '@nestjs/core'; // register source-map-support for debugging -import { FilesStorageAMQPModule } from '@modules/files-storage'; +import { FilesStorageAMQPModule } from '@modules/files-storage/files-storage-amqp.module'; import { install as sourceMapInstall } from 'source-map-support'; async function bootstrap() { diff --git a/apps/server/src/apps/files-storage.app.ts b/apps/server/src/apps/files-storage.app.ts index 2d2f9343ac2..adbaac9d90c 100644 --- a/apps/server/src/apps/files-storage.app.ts +++ b/apps/server/src/apps/files-storage.app.ts @@ -8,9 +8,10 @@ import express from 'express'; import { install as sourceMapInstall } from 'source-map-support'; // application imports +import { FilesStorageApiModule } from '@modules/files-storage/files-storage-api.module'; +import { API_VERSION_PATH } from '@modules/files-storage/files-storage.const'; import { SwaggerDocumentOptions } from '@nestjs/swagger'; import { LegacyLogger } from '@src/core/logger'; -import { API_VERSION_PATH, FilesStorageApiModule } from '@modules/files-storage'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; async function bootstrap() { diff --git a/apps/server/src/apps/preview-generator-consumer.app.ts b/apps/server/src/apps/preview-generator-consumer.app.ts new file mode 100644 index 00000000000..1c2be631233 --- /dev/null +++ b/apps/server/src/apps/preview-generator-consumer.app.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ +/* eslint-disable no-console */ +import { PreviewGeneratorAMQPModule } from '@modules/files-storage/files-preview-amqp.module'; +import { NestFactory } from '@nestjs/core'; +import { install as sourceMapInstall } from 'source-map-support'; + +async function bootstrap() { + sourceMapInstall(); + + const nestApp = await NestFactory.createMicroservice(PreviewGeneratorAMQPModule); + await nestApp.init(); + + console.log('#############################################'); + console.log(`### Start Preview Generator AMQP Consumer ###`); + console.log('#############################################'); +} +void bootstrap(); diff --git a/apps/server/src/modules/files-storage-client/mapper/index.ts b/apps/server/src/modules/files-storage-client/mapper/index.ts index 06f40e707e1..006cf64b627 100644 --- a/apps/server/src/modules/files-storage-client/mapper/index.ts +++ b/apps/server/src/modules/files-storage-client/mapper/index.ts @@ -1,4 +1,3 @@ -export * from './error.mapper'; +export * from './copy-files-of-parent-param.builder'; export * from './files-storage-client.mapper'; export * from './files-storage-param.builder'; -export * from './copy-files-of-parent-param.builder'; diff --git a/apps/server/src/modules/files-storage-client/service/files-storage.producer.spec.ts b/apps/server/src/modules/files-storage-client/service/files-storage.producer.spec.ts index 926f860c736..7e4ed5a1c83 100644 --- a/apps/server/src/modules/files-storage-client/service/files-storage.producer.spec.ts +++ b/apps/server/src/modules/files-storage-client/service/files-storage.producer.spec.ts @@ -3,10 +3,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { FileRecordParentType, FilesStorageEvents, FilesStorageExchange } from '@shared/infra/rabbitmq'; +import { ErrorMapper, FileRecordParentType, FilesStorageEvents, FilesStorageExchange } from '@shared/infra/rabbitmq'; import { setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { ErrorMapper } from '../mapper'; import { FilesStorageProducer } from './files-storage.producer'; describe('FilesStorageProducer', () => { diff --git a/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts b/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts index afd4f6365e1..ea049442df4 100644 --- a/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts +++ b/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts @@ -2,7 +2,6 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { EntityId } from '@shared/domain'; -import { RpcMessage } from '@shared/infra/rabbitmq/rpc-message'; import { LegacyLogger } from '@src/core/logger'; import { FilesStorageEvents, @@ -11,75 +10,45 @@ import { ICopyFilesOfParentParams, IFileDO, IFileRecordParams, + RpcMessageProducer, } from '@src/shared/infra/rabbitmq'; import { IFilesStorageClientConfig } from '../interfaces'; -import { ErrorMapper } from '../mapper/error.mapper'; @Injectable() -export class FilesStorageProducer { - private readonly timeout = 0; - +export class FilesStorageProducer extends RpcMessageProducer { constructor( + protected readonly amqpConnection: AmqpConnection, private readonly logger: LegacyLogger, - private readonly amqpConnection: AmqpConnection, - private readonly configService: ConfigService + protected readonly configService: ConfigService ) { + super(amqpConnection, FilesStorageExchange, configService.get('INCOMING_REQUEST_TIMEOUT_COPY_API')); this.logger.setContext(FilesStorageProducer.name); - this.timeout = this.configService.get('INCOMING_REQUEST_TIMEOUT_COPY_API'); } async copyFilesOfParent(payload: ICopyFilesOfParentParams): Promise { this.logger.debug({ action: 'copyFilesOfParent:started', payload }); - const response = await this.amqpConnection.request>( - this.createRequest(FilesStorageEvents.COPY_FILES_OF_PARENT, payload) - ); + const response = await this.request(FilesStorageEvents.COPY_FILES_OF_PARENT, payload); this.logger.debug({ action: 'copyFilesOfParent:finished', payload }); - this.checkError(response); - return response.message || []; + return response; } async listFilesOfParent(payload: IFileRecordParams): Promise { this.logger.debug({ action: 'listFilesOfParent:started', payload }); - const response = await this.amqpConnection.request>( - this.createRequest(FilesStorageEvents.LIST_FILES_OF_PARENT, payload) - ); + const response = await this.request(FilesStorageEvents.LIST_FILES_OF_PARENT, payload); this.logger.debug({ action: 'listFilesOfParent:finished', payload }); - this.checkError(response); - return response.message || []; + return response; } async deleteFilesOfParent(payload: EntityId): Promise { this.logger.debug({ action: 'deleteFilesOfParent:started', payload }); - const response = await this.amqpConnection.request>( - this.createRequest(FilesStorageEvents.DELETE_FILES_OF_PARENT, payload) - ); + const response = await this.request(FilesStorageEvents.DELETE_FILES_OF_PARENT, payload); this.logger.debug({ action: 'deleteFilesOfParent:finished', payload }); - this.checkError(response); - return response.message || []; - } - - // need to be fixed with https://ticketsystem.dbildungscloud.de/browse/BC-2984 - // mapRpcErrorResponseToDomainError should also removed with this ticket - private checkError(response: RpcMessage) { - const { error } = response; - if (error) { - const domainError = ErrorMapper.mapRpcErrorResponseToDomainError(error); - throw domainError; - } - } - - private createRequest(event: FilesStorageEvents, payload: IFileRecordParams | ICopyFilesOfParentParams | EntityId) { - return { - exchange: FilesStorageExchange, - routingKey: event, - payload, - timeout: this.timeout, - }; + return response; } } diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts index 22749024b31..e87aa5ddbe6 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-preview.api.spec.ts @@ -7,6 +7,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { EntityId, Permission } from '@shared/domain'; import { AntivirusService } from '@shared/infra/antivirus'; +import { PreviewProducer } from '@shared/infra/preview-generator'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import NodeClam from 'clamscan'; @@ -89,6 +90,7 @@ describe('File Controller (API) - preview', () => { let app: INestApplication; let em: EntityManager; let s3ClientAdapter: DeepMocked; + let antivirusService: DeepMocked; let currentUser: ICurrentUser; let api: API; let schoolId: EntityId; @@ -103,6 +105,8 @@ describe('File Controller (API) - preview', () => { }) .overrideProvider(AntivirusService) .useValue(createMock()) + .overrideProvider(PreviewProducer) + .useValue(createMock()) .overrideProvider(FILES_STORAGE_S3_CONNECTION) .useValue(createMock()) .overrideGuard(JwtAuthGuard) @@ -123,6 +127,7 @@ describe('File Controller (API) - preview', () => { em = module.get(EntityManager); s3ClientAdapter = module.get(FILES_STORAGE_S3_CONNECTION); + antivirusService = module.get(AntivirusService); api = new API(app); }); @@ -147,6 +152,7 @@ describe('File Controller (API) - preview', () => { uploadPath = `/file/upload/${schoolId}/schools/${schoolId}`; jest.spyOn(FileType, 'fileTypeStream').mockImplementation((readable) => Promise.resolve(readable)); + antivirusService.checkStream.mockResolvedValueOnce({ virus_detected: false }); }); const setScanStatus = async (fileRecordId: EntityId, status: ScanStatus) => { @@ -329,9 +335,8 @@ describe('File Controller (API) - preview', () => { const { result: uploadedFile } = await api.postUploadFile(uploadPath); await setScanStatus(uploadedFile.id, ScanStatus.VERIFIED); - const originalFile = TestHelper.createFile(); const previewFile = TestHelper.createFile('bytes 0-3/4'); - s3ClientAdapter.get.mockResolvedValueOnce(originalFile).mockResolvedValueOnce(previewFile); + s3ClientAdapter.get.mockResolvedValueOnce(previewFile); return { uploadedFile }; }; @@ -374,12 +379,8 @@ describe('File Controller (API) - preview', () => { await setScanStatus(uploadedFile.id, ScanStatus.VERIFIED); const error = new NotFoundException(); - const originalFile = TestHelper.createFile(); const previewFile = TestHelper.createFile('bytes 0-3/4'); - s3ClientAdapter.get - .mockRejectedValueOnce(error) - .mockResolvedValueOnce(originalFile) - .mockResolvedValueOnce(previewFile); + s3ClientAdapter.get.mockRejectedValueOnce(error).mockResolvedValueOnce(previewFile); return { uploadedFile }; }; diff --git a/apps/server/src/modules/files-storage/controller/files-storage.consumer.spec.ts b/apps/server/src/modules/files-storage/controller/files-storage.consumer.spec.ts index 124f486dc68..81fd2884347 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.consumer.spec.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.consumer.spec.ts @@ -7,6 +7,7 @@ import { courseFactory, fileRecordFactory, setupEntities } from '@shared/testing import { LegacyLogger } from '@src/core/logger'; import { FileRecord, FileRecordParentType } from '../entity'; import { FilesStorageService } from '../service/files-storage.service'; +import { PreviewService } from '../service/preview.service'; import { FileRecordResponse } from './dto'; import { FilesStorageConsumer } from './files-storage.consumer'; @@ -33,6 +34,10 @@ describe('FilesStorageConsumer', () => { provide: FilesStorageService, useValue: createMock(), }, + { + provide: PreviewService, + useValue: createMock(), + }, { provide: LegacyLogger, useValue: createMock(), @@ -165,9 +170,9 @@ describe('FilesStorageConsumer', () => { const parentId = new ObjectId().toHexString(); const fileRecords = fileRecordFactory.buildList(3); - filesStorageService.deleteFilesOfParent.mockResolvedValue([fileRecords, fileRecords.length]); + filesStorageService.getFileRecordsOfParent.mockResolvedValue([fileRecords, fileRecords.length]); - return { parentId }; + return { parentId, fileRecords }; }; it('should call filesStorageService.deleteFilesOfParent with params', async () => { @@ -175,7 +180,15 @@ describe('FilesStorageConsumer', () => { await service.deleteFilesOfParent(parentId); - expect(filesStorageService.deleteFilesOfParent).toBeCalledWith(parentId); + expect(filesStorageService.getFileRecordsOfParent).toBeCalledWith(parentId); + }); + + it('should call filesStorageService.deleteFilesOfParent with params', async () => { + const { parentId, fileRecords } = setup(); + + await service.deleteFilesOfParent(parentId); + + expect(filesStorageService.deleteFilesOfParent).toBeCalledWith(fileRecords); }); it('should return array instances of FileRecordResponse', async () => { @@ -191,7 +204,7 @@ describe('FilesStorageConsumer', () => { const setup = () => { const parentId = new ObjectId().toHexString(); - filesStorageService.deleteFilesOfParent.mockResolvedValue([[], 0]); + filesStorageService.getFileRecordsOfParent.mockResolvedValue([[], 0]); return { parentId }; }; diff --git a/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts b/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts index e7dee3c6b0d..aabefa60f16 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts @@ -7,12 +7,14 @@ import { LegacyLogger } from '@src/core/logger'; import { FilesStorageEvents, FilesStorageExchange, ICopyFileDO, IFileDO } from '@src/shared/infra/rabbitmq'; import { FilesStorageMapper } from '../mapper'; import { FilesStorageService } from '../service/files-storage.service'; +import { PreviewService } from '../service/preview.service'; import { CopyFilesOfParentPayload, FileRecordParams } from './dto'; @Injectable() export class FilesStorageConsumer { constructor( private readonly filesStorageService: FilesStorageService, + private readonly previewService: PreviewService, private logger: LegacyLogger, // eslint-disable-next-line @typescript-eslint/no-unused-vars private readonly orm: MikroORM // don't remove it, we need it for @UseRequestContext @@ -61,7 +63,11 @@ export class FilesStorageConsumer { public async deleteFilesOfParent(@RabbitPayload() payload: EntityId): Promise> { this.logger.debug({ action: 'deleteFilesOfParent', payload }); - const [fileRecords, total] = await this.filesStorageService.deleteFilesOfParent(payload); + const [fileRecords, total] = await this.filesStorageService.getFileRecordsOfParent(payload); + + await this.previewService.deletePreviews(fileRecords); + await this.filesStorageService.deleteFilesOfParent(fileRecords); + const response = FilesStorageMapper.mapToFileRecordListResponse(fileRecords, total); return { message: response.data }; diff --git a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts index 564919670e4..736d69e3e29 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.controller.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { BadRequestException, Body, @@ -22,10 +23,10 @@ import { UseInterceptors, } from '@nestjs/common'; import { ApiConsumes, ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ApiValidationError, RequestLoggingInterceptor } from '@shared/common'; +import { ApiValidationError, RequestLoggingInterceptor, RequestTimeout } from '@shared/common'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { Request, Response } from 'express'; +import { config } from '../files-storage.config'; import { GetFileResponse } from '../interface'; import { FilesStorageMapper } from '../mapper'; import { FileRecordMapper } from '../mapper/file-record.mapper'; @@ -126,6 +127,7 @@ export class FilesStorageController { @ApiResponse({ status: 500, type: InternalServerErrorException }) @ApiHeader({ name: 'Range', required: false }) @Get('/preview/:fileRecordId/:fileName') + @RequestTimeout(config().INCOMING_REQUEST_TIMEOUT) async downloadPreview( @Param() params: DownloadFileParams, @CurrentUser() currentUser: ICurrentUser, diff --git a/apps/server/src/modules/files-storage/controller/index.ts b/apps/server/src/modules/files-storage/controller/index.ts index ffd18a5db36..7aa61d2c93b 100644 --- a/apps/server/src/modules/files-storage/controller/index.ts +++ b/apps/server/src/modules/files-storage/controller/index.ts @@ -1,3 +1,3 @@ export * from './file-security.controller'; -export * from './files-storage.controller'; export * from './files-storage.consumer'; +export * from './files-storage.controller'; diff --git a/apps/server/src/modules/files-storage/dto/file.dto.ts b/apps/server/src/modules/files-storage/dto/file.dto.ts index 540e35bb7e6..9668ac3af72 100644 --- a/apps/server/src/modules/files-storage/dto/file.dto.ts +++ b/apps/server/src/modules/files-storage/dto/file.dto.ts @@ -1,6 +1,7 @@ +import { File } from '@shared/infra/s3-client'; import { Readable } from 'stream'; -export class FileDto { +export class FileDto implements File { constructor(file: FileDto) { this.name = file.name; this.data = file.data; diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts index f07d88d85fd..f497ff39a6c 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts @@ -783,4 +783,54 @@ describe('FileRecord Entity', () => { }); }); }); + + describe('fileNameWithoutExtension is called', () => { + describe('WHEN file name has extension', () => { + const setup = () => { + const fileRecord = fileRecordFactory.build({ name: 'file-name.jpg' }); + + return { fileRecord }; + }; + + it('should return the correct file name without extension', () => { + const { fileRecord } = setup(); + + const result = fileRecord.fileNameWithoutExtension; + + expect(result).toEqual('file-name'); + }); + }); + + describe('WHEN file name has not extension', () => { + const setup = () => { + const fileRecord = fileRecordFactory.build({ name: 'file-name' }); + + return { fileRecord }; + }; + + it('should return the correct file name without extension', () => { + const { fileRecord } = setup(); + + const result = fileRecord.fileNameWithoutExtension; + + expect(result).toEqual('file-name'); + }); + }); + + describe('WHEN file name starts with dot', () => { + const setup = () => { + const fileRecord = fileRecordFactory.build({ name: '.bild.123.jpg' }); + + return { fileRecord }; + }; + + it('should return the correct file name without extension', () => { + const { fileRecord } = setup(); + + const result = fileRecord.fileNameWithoutExtension; + + expect(result).toEqual('.bild.123'); + }); + }); + }); }); diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts index a87789d30fc..26f2807924c 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts @@ -2,6 +2,7 @@ import { Embeddable, Embedded, Entity, Enum, Index, Property } from '@mikro-orm/ import { ObjectId } from '@mikro-orm/mongodb'; import { BadRequestException } from '@nestjs/common'; import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; +import path from 'path'; import { v4 as uuid } from 'uuid'; import { ErrorType } from '../error'; import { PreviewInputMimeTypes } from '../interface/preview-input-mime-types.enum'; @@ -293,4 +294,10 @@ export class FileRecord extends BaseEntityWithTimestamps { return PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_ERROR; } + + public get fileNameWithoutExtension(): string { + const filenameObj = path.parse(this.name); + + return filenameObj.name; + } } diff --git a/apps/server/src/modules/files-storage/files-preview-amqp.module.ts b/apps/server/src/modules/files-storage/files-preview-amqp.module.ts new file mode 100644 index 00000000000..411a26e76d6 --- /dev/null +++ b/apps/server/src/modules/files-storage/files-preview-amqp.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { PreviewGeneratorConsumerModule } from '@shared/infra/preview-generator'; +import { defaultConfig, s3Config } from './files-storage.config'; + +@Module({ + imports: [PreviewGeneratorConsumerModule.register({ storageConfig: s3Config, serverConfig: defaultConfig })], +}) +export class PreviewGeneratorAMQPModule {} diff --git a/apps/server/src/modules/files-storage/files-storage.config.ts b/apps/server/src/modules/files-storage/files-storage.config.ts index 7dc01e9f4e1..7fac8ded763 100644 --- a/apps/server/src/modules/files-storage/files-storage.config.ts +++ b/apps/server/src/modules/files-storage/files-storage.config.ts @@ -9,13 +9,17 @@ export interface IFileStorageConfig extends ICoreModuleConfig { USE_STREAM_TO_ANTIVIRUS: boolean; } -const fileStorageConfig: IFileStorageConfig = { +export const defaultConfig = { + NEST_LOG_LEVEL: Configuration.get('NEST_LOG_LEVEL') as string, INCOMING_REQUEST_TIMEOUT: Configuration.get('FILES_STORAGE__INCOMING_REQUEST_TIMEOUT') as number, +}; + +const fileStorageConfig: IFileStorageConfig = { INCOMING_REQUEST_TIMEOUT_COPY_API: Configuration.get('INCOMING_REQUEST_TIMEOUT_COPY_API') as number, MAX_FILE_SIZE: Configuration.get('FILES_STORAGE__MAX_FILE_SIZE') as number, MAX_SECURITY_CHECK_FILE_SIZE: Configuration.get('FILES_STORAGE__MAX_FILE_SIZE') as number, - NEST_LOG_LEVEL: Configuration.get('NEST_LOG_LEVEL') as string, USE_STREAM_TO_ANTIVIRUS: Configuration.get('FILES_STORAGE__USE_STREAM_TO_ANTIVIRUS') as boolean, + ...defaultConfig, }; // The configurations lookup diff --git a/apps/server/src/modules/files-storage/files-storage.module.ts b/apps/server/src/modules/files-storage/files-storage.module.ts index 248654218ef..ccdaeb7f9fa 100644 --- a/apps/server/src/modules/files-storage/files-storage.module.ts +++ b/apps/server/src/modules/files-storage/files-storage.module.ts @@ -5,6 +5,7 @@ import { Module, NotFoundException } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain'; import { AntivirusModule } from '@shared/infra/antivirus/antivirus.module'; +import { PreviewGeneratorProducerModule } from '@shared/infra/preview-generator'; import { RabbitMQWrapperModule } from '@shared/infra/rabbitmq/rabbitmq.module'; import { S3ClientModule } from '@shared/infra/s3-client'; import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; @@ -27,6 +28,7 @@ const imports = [ port: Configuration.get('CLAMAV__SERVICE_PORT') as number, }), S3ClientModule.register([s3Config]), + PreviewGeneratorProducerModule, ]; const providers = [FilesStorageService, PreviewService, FileRecordRepo]; diff --git a/apps/server/src/modules/files-storage/helper/file-record.spec.ts b/apps/server/src/modules/files-storage/helper/file-record.spec.ts index 6d975ebe21b..a999b599936 100644 --- a/apps/server/src/modules/files-storage/helper/file-record.spec.ts +++ b/apps/server/src/modules/files-storage/helper/file-record.spec.ts @@ -1,8 +1,9 @@ import { EntityId } from '@shared/domain'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { ObjectId } from 'bson'; -import { createFileRecord, markForDelete, unmarkForDelete } from '.'; +import { createFileRecord, getFormat, getPreviewName, markForDelete, unmarkForDelete } from '.'; import { FileRecord } from '../entity'; +import { PreviewOutputMimeTypes } from '../interface'; describe('File Record Helper', () => { const setupFileRecords = () => { @@ -88,4 +89,45 @@ describe('File Record Helper', () => { expect(newFileRecord).toEqual(expect.any(FileRecord)); }); }); + + describe('getFormat is called', () => { + it('should return format', () => { + const mimeType = 'image/jpeg'; + + const result = getFormat(mimeType); + + expect(result).toEqual('jpeg'); + }); + + it('should throw error', () => { + const mimeType = 'image'; + + expect(() => getFormat(mimeType)).toThrowError(`could not get format from mime type: ${mimeType}`); + }); + }); + + describe('getPreviewName is called', () => { + const setup = () => { + const fileRecord = fileRecordFactory.buildWithId(); + const outputFormat = PreviewOutputMimeTypes.IMAGE_WEBP; + + return { fileRecord, outputFormat }; + }; + + it('should return origin file name', () => { + const { fileRecord } = setup(); + + const result = getPreviewName(fileRecord, undefined); + + expect(result).toEqual(fileRecord.name); + }); + + it('should return preview name with format', () => { + const { fileRecord, outputFormat } = setup(); + + const result = getPreviewName(fileRecord, outputFormat); + + expect(result).toEqual(`${fileRecord.name.split('.')[0]}.webp`); + }); + }); }); diff --git a/apps/server/src/modules/files-storage/helper/file-record.ts b/apps/server/src/modules/files-storage/helper/file-record.ts index c0984063420..ed291661735 100644 --- a/apps/server/src/modules/files-storage/helper/file-record.ts +++ b/apps/server/src/modules/files-storage/helper/file-record.ts @@ -1,5 +1,7 @@ +import { InternalServerErrorException } from '@nestjs/common'; import { FileRecordParams } from '../controller/dto'; import { FileRecord } from '../entity'; +import { PreviewOutputMimeTypes } from '../interface'; export function markForDelete(fileRecords: FileRecord[]): FileRecord[] { const markedFileRecords = fileRecords.map((fileRecord) => { @@ -38,3 +40,26 @@ export function createFileRecord( return entity; } + +export function getFormat(mimeType: string): string { + const format = mimeType.split('/')[1]; + + if (!format) { + throw new InternalServerErrorException(`could not get format from mime type: ${mimeType}`); + } + + return format; +} + +export function getPreviewName(fileRecord: FileRecord, outputFormat?: PreviewOutputMimeTypes): string { + const { fileNameWithoutExtension, name } = fileRecord; + + if (!outputFormat) { + return name; + } + + const format = getFormat(outputFormat); + const previewFileName = `${fileNameWithoutExtension}.${format}`; + + return previewFileName; +} diff --git a/apps/server/src/modules/files-storage/index.ts b/apps/server/src/modules/files-storage/index.ts index c22e4c2be98..6ee0883938f 100644 --- a/apps/server/src/modules/files-storage/index.ts +++ b/apps/server/src/modules/files-storage/index.ts @@ -1,5 +1,2 @@ -export * from './files-storage-amqp.module'; -export * from './files-storage-api.module'; -export * from './files-storage-test.module'; // @deprecated remove after move api tests to modules -export * from './files-storage.config'; -export * from './files-storage.const'; +// this module has no exports +// it is an isolated module, it cannot be used in other modules diff --git a/apps/server/src/modules/files-storage/interface/interfaces.ts b/apps/server/src/modules/files-storage/interface/interfaces.ts index 047c943e55a..2f288f9133b 100644 --- a/apps/server/src/modules/files-storage/interface/interfaces.ts +++ b/apps/server/src/modules/files-storage/interface/interfaces.ts @@ -1,5 +1,5 @@ import { Readable } from 'stream'; -import type { DownloadFileParams, PreviewParams } from '../controller/dto'; +import type { PreviewParams } from '../controller/dto'; import { FileRecord } from '../entity'; export interface GetFileResponse { @@ -13,9 +13,10 @@ export interface GetFileResponse { export interface PreviewFileParams { fileRecord: FileRecord; - downloadParams: DownloadFileParams; previewParams: PreviewParams; hash: string; - filePath: string; + originFilePath: string; + previewFilePath: string; + format: string; bytesRange?: string; } diff --git a/apps/server/src/modules/files-storage/mapper/index.ts b/apps/server/src/modules/files-storage/mapper/index.ts index 556e7508929..bc8af7f7f05 100644 --- a/apps/server/src/modules/files-storage/mapper/index.ts +++ b/apps/server/src/modules/files-storage/mapper/index.ts @@ -3,3 +3,4 @@ export * from './file-dto.builder'; export * from './file-record.mapper'; export * from './file-response.builder'; export * from './files-storage.mapper'; +export * from './preview.builder'; diff --git a/apps/server/src/modules/files-storage/mapper/preview.builder.spec.ts b/apps/server/src/modules/files-storage/mapper/preview.builder.spec.ts new file mode 100644 index 00000000000..1a3cc843f86 --- /dev/null +++ b/apps/server/src/modules/files-storage/mapper/preview.builder.spec.ts @@ -0,0 +1,61 @@ +import { fileRecordFactory } from '@shared/testing'; +import { PreviewOutputMimeTypes } from '../interface'; +import { PreviewBuilder } from './preview.builder'; + +describe('PreviewBuilder', () => { + describe('buildParams is called', () => { + const setup = () => { + const fileRecord = fileRecordFactory.buildWithId(); + const previewParams = { outputFormat: PreviewOutputMimeTypes.IMAGE_WEBP }; + const bytesRange = 'bytes=0-100'; + + const expectedResponse = { + fileRecord, + previewParams, + hash: expect.any(String), + previewFilePath: expect.any(String), + originFilePath: expect.any(String), + format: expect.any(String), + bytesRange, + }; + + return { fileRecord, previewParams, bytesRange, expectedResponse }; + }; + + it('should return preview file params', () => { + const { fileRecord, previewParams, bytesRange, expectedResponse } = setup(); + + const result = PreviewBuilder.buildParams(fileRecord, previewParams, bytesRange); + + expect(result).toEqual(expectedResponse); + }); + }); + + describe('buildPayload is called', () => { + const setup = () => { + const fileRecord = fileRecordFactory.buildWithId(); + const previewParams = { outputFormat: PreviewOutputMimeTypes.IMAGE_WEBP }; + const bytesRange = 'bytes=0-100'; + const previewFileParams = PreviewBuilder.buildParams(fileRecord, previewParams, bytesRange); + + const expectedResponse = { + originFilePath: previewFileParams.originFilePath, + previewFilePath: previewFileParams.previewFilePath, + previewOptions: { + format: previewFileParams.format, + width: previewFileParams.previewParams.width, + }, + }; + + return { previewFileParams, expectedResponse }; + }; + + it('should return preview payload', () => { + const { previewFileParams, expectedResponse } = setup(); + + const result = PreviewBuilder.buildPayload(previewFileParams); + + expect(result).toEqual(expectedResponse); + }); + }); +}); diff --git a/apps/server/src/modules/files-storage/mapper/preview.builder.ts b/apps/server/src/modules/files-storage/mapper/preview.builder.ts new file mode 100644 index 00000000000..83a16448a98 --- /dev/null +++ b/apps/server/src/modules/files-storage/mapper/preview.builder.ts @@ -0,0 +1,47 @@ +import { PreviewFileOptions } from '@shared/infra/preview-generator'; +import { PreviewParams } from '../controller/dto'; +import { FileRecord } from '../entity'; +import { createPath, createPreviewFilePath, createPreviewNameHash, getFormat } from '../helper'; +import { PreviewFileParams } from '../interface'; + +export class PreviewBuilder { + public static buildParams( + fileRecord: FileRecord, + previewParams: PreviewParams, + bytesRange: string | undefined + ): PreviewFileParams { + const { schoolId, id, mimeType } = fileRecord; + const originFilePath = createPath(schoolId, id); + const format = getFormat(previewParams.outputFormat ?? mimeType); + + const hash = createPreviewNameHash(id, previewParams); + const previewFilePath = createPreviewFilePath(schoolId, hash, id); + + const previewFileParams = { + fileRecord, + previewParams, + hash, + previewFilePath, + originFilePath, + format, + bytesRange, + }; + + return previewFileParams; + } + + public static buildPayload(params: PreviewFileParams): PreviewFileOptions { + const { originFilePath, previewFilePath, previewParams, format } = params; + + const payload = { + originFilePath, + previewFilePath, + previewOptions: { + format, + width: previewParams.width, + }, + }; + + return payload; + } +} diff --git a/apps/server/src/modules/files-storage/service/files-storage-delete.service.spec.ts b/apps/server/src/modules/files-storage/service/files-storage-delete.service.spec.ts index cd76b564b31..3705f93b51c 100644 --- a/apps/server/src/modules/files-storage/service/files-storage-delete.service.spec.ts +++ b/apps/server/src/modules/files-storage/service/files-storage-delete.service.spec.ts @@ -171,35 +171,13 @@ describe('FilesStorageService delete methods', () => { return { parentId, fileRecords }; }; - it('should call findBySchoolIdAndParentId once with correct params', async () => { - const { parentId } = setup(); - - await service.deleteFilesOfParent(parentId); - - expect(fileRecordRepo.findByParentId).toHaveBeenNthCalledWith(1, parentId); - }); - it('should call delete with correct params', async () => { - const { parentId, fileRecords } = setup(); + const { fileRecords } = setup(); - await service.deleteFilesOfParent(parentId); + await service.deleteFilesOfParent(fileRecords); expect(service.delete).toHaveBeenCalledWith(fileRecords); }); - - it('should return file records and count', async () => { - const { parentId, fileRecords } = setup(); - - const responseData = await service.deleteFilesOfParent(parentId); - expect(responseData[0]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ ...fileRecords[0] }), - expect.objectContaining({ ...fileRecords[1] }), - expect.objectContaining({ ...fileRecords[2] }), - ]) - ); - expect(responseData[1]).toEqual(fileRecords.length); - }); }); describe('WHEN no files exists', () => { @@ -215,43 +193,17 @@ describe('FilesStorageService delete methods', () => { const { parentId } = params; spy = jest.spyOn(service, 'delete'); - fileRecordRepo.findByParentId.mockResolvedValueOnce([fileRecords, fileRecords.length]); - return { parentId }; + return { parentId, fileRecords }; }; it('should not call delete', async () => { - const { parentId } = setup(); + const { fileRecords } = setup(); - await service.deleteFilesOfParent(parentId); + await service.deleteFilesOfParent(fileRecords); expect(service.delete).toHaveBeenCalledTimes(0); }); - - it('should return empty counted type', async () => { - const { parentId } = setup(); - - const result = await service.deleteFilesOfParent(parentId); - - expect(result).toEqual([[], 0]); - }); - }); - - describe('WHEN repository throw an error', () => { - const setup = () => { - const { params } = buildFileRecordsWithParams(); - const { parentId } = params; - - fileRecordRepo.findByParentId.mockRejectedValueOnce(new Error('bla')); - - return { parentId }; - }; - - it('should pass the error', async () => { - const { parentId } = setup(); - - await expect(service.deleteFilesOfParent(parentId)).rejects.toThrow(new Error('bla')); - }); }); describe('WHEN service.delete throw an error', () => { @@ -272,9 +224,9 @@ describe('FilesStorageService delete methods', () => { }; it('should pass the error', async () => { - const { parentId } = setup(); + const { fileRecords } = setup(); - await expect(service.deleteFilesOfParent(parentId)).rejects.toThrow(new Error('bla')); + await expect(service.deleteFilesOfParent(fileRecords)).rejects.toThrow(new Error('bla')); }); }); }); diff --git a/apps/server/src/modules/files-storage/service/files-storage.service.ts b/apps/server/src/modules/files-storage/service/files-storage.service.ts index 8c0c85630de..209f1804d3e 100644 --- a/apps/server/src/modules/files-storage/service/files-storage.service.ts +++ b/apps/server/src/modules/files-storage/service/files-storage.service.ts @@ -244,7 +244,7 @@ export class FilesStorageService { } // download - private checkFileName(fileRecord: FileRecord, params: DownloadFileParams): void | NotFoundException { + public checkFileName(fileRecord: FileRecord, params: DownloadFileParams): void | NotFoundException { if (!fileRecord.hasName(params.fileName)) { this.logger.debug(`could not find file with id: ${fileRecord.id} by filename`); throw new NotFoundException(ErrorType.FILE_NOT_FOUND); @@ -304,14 +304,10 @@ export class FilesStorageService { await this.deleteWithRollbackByError(fileRecords); } - public async deleteFilesOfParent(parentId: EntityId): Promise> { - const [fileRecords, count] = await this.getFileRecordsOfParent(parentId); - - if (count > 0) { + public async deleteFilesOfParent(fileRecords: FileRecord[]): Promise { + if (fileRecords.length > 0) { await this.delete(fileRecords); } - - return [fileRecords, count]; } // restore diff --git a/apps/server/src/modules/files-storage/service/preview.service.spec.ts b/apps/server/src/modules/files-storage/service/preview.service.spec.ts index ed4592b97da..f02f48aee21 100644 --- a/apps/server/src/modules/files-storage/service/preview.service.spec.ts +++ b/apps/server/src/modules/files-storage/service/preview.service.spec.ts @@ -2,33 +2,22 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { NotFoundException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { PreviewProducer } from '@shared/infra/preview-generator'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Readable } from 'stream'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType, ScanStatus } from '../entity'; import { ErrorType } from '../error'; import { FILES_STORAGE_S3_CONNECTION } from '../files-storage.config'; -import { createPreviewDirectoryPath, createPreviewFilePath, createPreviewNameHash } from '../helper'; +import { createPath, createPreviewDirectoryPath, createPreviewFilePath, createPreviewNameHash } from '../helper'; import { TestHelper } from '../helper/test-helper'; import { PreviewWidth } from '../interface'; import { PreviewOutputMimeTypes } from '../interface/preview-output-mime-types.enum'; -import { FileDtoBuilder, FileResponseBuilder } from '../mapper'; +import { FileResponseBuilder } from '../mapper'; import { FilesStorageService } from './files-storage.service'; import { PreviewService } from './preview.service'; -const streamMock = jest.fn(); -const resizeMock = jest.fn(); -const imageMagickMock = () => { - return { stream: streamMock, resize: resizeMock, data: Readable.from('text') }; -}; -jest.mock('gm', () => { - return { - subClass: () => imageMagickMock, - }; -}); - const buildFileRecordWithParams = (mimeType: string, scanStatus?: ScanStatus) => { const parentId = new ObjectId().toHexString(); const parentSchoolId = new ObjectId().toHexString(); @@ -62,8 +51,8 @@ const defaultPreviewParamsWithWidth = { describe('PreviewService', () => { let module: TestingModule; let previewService: PreviewService; - let fileStorageService: DeepMocked; let s3ClientAdapter: DeepMocked; + let previewProducer: DeepMocked; beforeAll(async () => { await setupEntities([FileRecord]); @@ -83,514 +72,369 @@ describe('PreviewService', () => { provide: LegacyLogger, useValue: createMock(), }, + { provide: PreviewProducer, useValue: createMock() }, ], }).compile(); previewService = module.get(PreviewService); - fileStorageService = module.get(FilesStorageService); s3ClientAdapter = module.get(FILES_STORAGE_S3_CONNECTION); - }); - - beforeEach(() => { - jest.resetAllMocks(); + previewProducer = module.get(PreviewProducer); }); afterAll(async () => { await module.close(); }); - describe('getPreview is called', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('download is called', () => { describe('WHEN preview is possbile', () => { describe('WHEN forceUpdate is true', () => { - describe('WHEN width and outputformat are not set', () => { - describe('WHEN download of original and preview file is successfull', () => { - const setup = () => { - const bytesRange = 'bytes=0-100'; - const orignalMimeType = 'image/png'; - const format = orignalMimeType.split('/')[1]; - const { fileRecord } = buildFileRecordWithParams(orignalMimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { forceUpdate: true }; - - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - - const previewFile = TestHelper.createFile(); - s3ClientAdapter.get.mockResolvedValueOnce(previewFile); - - const fileNameWithoutExtension = fileRecord.name.split('.')[0]; - const name = `${fileNameWithoutExtension}.${format}`; - const previewFileResponse = FileResponseBuilder.build(previewFile, name); - - const hash = createPreviewNameHash(fileRecord.id, {}); - const previewFileDto = FileDtoBuilder.build(hash, previewFile.data, orignalMimeType); - const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); - streamMock.mockClear(); - streamMock.mockReturnValueOnce(previewFileDto.data); - - return { - bytesRange, - fileRecord, - downloadParams, - previewParams, - format, - previewFileDto, - previewPath, - previewFileResponse, - }; + describe('WHEN first get of preview file is successfull', () => { + const setup = () => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: true, }; - - it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange); - - expect(fileStorageService.download).toHaveBeenCalledWith(fileRecord, downloadParams, bytesRange); - }); - - it('calls image magicks stream method', async () => { - const { fileRecord, downloadParams, previewParams, format } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(streamMock).toHaveBeenCalledWith(format); - expect(streamMock).toHaveBeenCalledTimes(1); - }); - - it('calls S3ClientAdapters create method', async () => { - const { fileRecord, downloadParams, previewParams, previewFileDto, previewPath } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(s3ClientAdapter.create).toHaveBeenCalledWith(previewPath, previewFileDto); - expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); - }); - - it('calls S3ClientAdapters get method', async () => { - const { fileRecord, downloadParams, previewParams, previewPath } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); - expect(s3ClientAdapter.get).toHaveBeenCalledTimes(1); - }); - - it('returns preview file response', async () => { - const { fileRecord, downloadParams, previewParams, previewFileResponse } = setup(); - - const response = await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(response).toEqual(previewFileResponse); - }); - }); - - describe('WHEN download of original file throws error', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { forceUpdate: true }; - - const error = new Error('testError'); - fileStorageService.download.mockRejectedValueOnce(error); - - return { fileRecord, downloadParams, previewParams, error }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + s3ClientAdapter.get.mockResolvedValueOnce(previewFile); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, }; + }; - it('passes error', async () => { - const { fileRecord, downloadParams, previewParams, error } = setup(); - - await expect(previewService.getPreview(fileRecord, downloadParams, previewParams)).rejects.toThrowError( - error - ); - }); - }); - - describe('WHEN create of preview file throws error', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { forceUpdate: true }; - - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - - const error = new Error('testError'); - s3ClientAdapter.create.mockRejectedValueOnce(error); - - return { fileRecord, downloadParams, previewParams, error }; - }; + it('calls previewProducer.generate with correct params', async () => { + const { fileRecord, previewParams, bytesRange, originPath, previewPath, format } = setup(); - it('passes error', async () => { - const { fileRecord, downloadParams, previewParams, error } = setup(); + await previewService.download(fileRecord, previewParams, bytesRange); - await expect(previewService.getPreview(fileRecord, downloadParams, previewParams)).rejects.toThrowError( - error - ); + expect(previewProducer.generate).toHaveBeenCalledWith({ + originFilePath: originPath, + previewFilePath: previewPath, + previewOptions: { width: previewParams.width, format }, }); }); - describe('WHEN get of preview file throws error', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { forceUpdate: true }; + it('calls S3ClientAdapters get method', async () => { + const { fileRecord, previewParams, previewPath } = setup(); - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); + await previewService.download(fileRecord, previewParams); - const error = new Error('testError'); - s3ClientAdapter.get.mockRejectedValueOnce(error); + expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); + expect(s3ClientAdapter.get).toHaveBeenCalledTimes(1); + }); - return { fileRecord, downloadParams, previewParams, error }; - }; + it('returns preview file response', async () => { + const { fileRecord, previewParams, previewFileResponse } = setup(); - it('passes error', async () => { - const { fileRecord, downloadParams, previewParams, error } = setup(); + const response = await previewService.download(fileRecord, previewParams); - await expect(previewService.getPreview(fileRecord, downloadParams, previewParams)).rejects.toThrowError( - error - ); - }); + expect(response).toEqual(previewFileResponse); }); }); - describe('WHEN width and outputFormat are set', () => { - describe('WHEN download of original and preview file is successfull', () => { - const setup = () => { - const bytesRange = 'bytes=0-100'; - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { - ...defaultPreviewParamsWithWidth, - forceUpdate: true, - }; - const format = previewParams.outputFormat.split('/')[1]; - - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - - const previewFile = TestHelper.createFile(); - s3ClientAdapter.get.mockResolvedValueOnce(previewFile); - - const fileNameWithoutExtension = fileRecord.name.split('.')[0]; - const name = `${fileNameWithoutExtension}.${format}`; - const previewFileResponse = FileResponseBuilder.build(previewFile, name); - - const hash = createPreviewNameHash(fileRecord.id, previewParams); - const previewFileDto = FileDtoBuilder.build(hash, previewFile.data, previewParams.outputFormat); - const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); - - streamMock.mockClear(); - streamMock.mockReturnValueOnce(previewFileDto.data); - - resizeMock.mockClear(); - - return { - bytesRange, - fileRecord, - downloadParams, - previewParams, - format, - previewFileDto, - previewPath, - previewFileResponse, - }; + describe('WHEN first get of preview file throws error and second is successfull', () => { + const setup = (error: Error | NotFoundException) => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: true, }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + s3ClientAdapter.get.mockRejectedValueOnce(error).mockResolvedValueOnce(previewFile); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, + }; + }; - it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange); - - expect(fileStorageService.download).toHaveBeenCalledWith(fileRecord, downloadParams, bytesRange); - }); - - it('calls image magicks resize method', async () => { - const { fileRecord, downloadParams, previewParams } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(resizeMock).toHaveBeenCalledWith(previewParams.width, undefined, '>'); - expect(resizeMock).toHaveBeenCalledTimes(1); - }); - - it('calls image magicks stream method', async () => { - const { fileRecord, downloadParams, previewParams, format } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(streamMock).toHaveBeenCalledWith(format); - expect(streamMock).toHaveBeenCalledTimes(1); - }); - - it('calls S3ClientAdapters create method', async () => { - const { fileRecord, downloadParams, previewParams, previewFileDto, previewPath } = setup(); + describe('WHEN error is a NotFoundException', () => { + it('calls previewProducer.generate with correct params', async () => { + const notFoundException = new NotFoundException(); + const { fileRecord, previewParams, bytesRange, originPath, previewPath, format } = + setup(notFoundException); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams, bytesRange); - expect(s3ClientAdapter.create).toHaveBeenCalledWith(previewPath, previewFileDto); - expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + expect(previewProducer.generate).toHaveBeenCalledWith({ + originFilePath: originPath, + previewFilePath: previewPath, + previewOptions: { width: previewParams.width, format }, + }); + expect(previewProducer.generate).toHaveBeenCalledTimes(2); }); it('calls S3ClientAdapters get method', async () => { - const { fileRecord, downloadParams, previewParams, previewPath } = setup(); + const notFoundException = new NotFoundException(); + const { fileRecord, previewParams, previewPath } = setup(notFoundException); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams); expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); - expect(s3ClientAdapter.get).toHaveBeenCalledTimes(1); + expect(s3ClientAdapter.get).toHaveBeenCalledTimes(2); }); + }); - it('returns preview file response', async () => { - const { fileRecord, downloadParams, previewParams, previewFileResponse } = setup(); - - const response = await previewService.getPreview(fileRecord, downloadParams, previewParams); + describe('WHEN error is other error', () => { + it('should pass error', async () => { + const error = new Error('testError'); + const { fileRecord, previewParams } = setup(error); - expect(response).toEqual(previewFileResponse); + await expect(previewService.download(fileRecord, previewParams)).rejects.toThrow(error); }); }); + }); - describe('WHEN download of original file throws error', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { ...defaultPreviewParams, forceUpdate: true }; - - const error = new Error('testError'); - fileStorageService.download.mockRejectedValueOnce(error); - - return { fileRecord, downloadParams, previewParams, error }; + describe('WHEN both gets of preview file throw error', () => { + const setup = () => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: true, }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + const notFoundException = new NotFoundException(); + s3ClientAdapter.get.mockRejectedValueOnce(notFoundException).mockRejectedValueOnce(notFoundException); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, + }; + }; - it('passes error', async () => { - const { fileRecord, downloadParams, previewParams, error } = setup(); + it('should pass error', async () => { + const { fileRecord, previewParams } = setup(); - await expect(previewService.getPreview(fileRecord, downloadParams, previewParams)).rejects.toThrowError( - error - ); - }); + await expect(previewService.download(fileRecord, previewParams)).rejects.toThrow(); }); }); }); describe('WHEN forceUpdate is false', () => { - describe('WHEN width and outputFormat are set', () => { - describe('WHEN S3ClientAdapter get returns already stored preview file', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { - ...defaultPreviewParamsWithWidth, - }; - const format = previewParams.outputFormat.split('/')[1]; - - const previewFile = TestHelper.createFile(); - s3ClientAdapter.get.mockResolvedValueOnce(previewFile); - - const fileNameWithoutExtension = fileRecord.name.split('.')[0]; - const name = `${fileNameWithoutExtension}.${format}`; - const previewFileResponse = FileResponseBuilder.build(previewFile, name); - - const hash = createPreviewNameHash(fileRecord.id, previewParams); - const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); - - resizeMock.mockClear(); - streamMock.mockClear(); - - return { - fileRecord, - downloadParams, - previewParams, - previewPath, - previewFileResponse, - }; + describe('WHEN first get of preview file is successfull', () => { + const setup = () => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: false, }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + s3ClientAdapter.get.mockResolvedValueOnce(previewFile); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, + }; + }; - it('calls S3ClientAdapters get method', async () => { - const { fileRecord, downloadParams, previewParams, previewPath } = setup(); + it('calls S3ClientAdapters get method', async () => { + const { fileRecord, previewParams, previewPath } = setup(); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams); - expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); - expect(s3ClientAdapter.get).toHaveBeenCalledTimes(1); - }); + expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); + expect(s3ClientAdapter.get).toHaveBeenCalledTimes(1); + }); - it('returns preview file response', async () => { - const { fileRecord, downloadParams, previewParams, previewFileResponse } = setup(); + it('returns preview file response', async () => { + const { fileRecord, previewParams, previewFileResponse } = setup(); - const response = await previewService.getPreview(fileRecord, downloadParams, previewParams); + const response = await previewService.download(fileRecord, previewParams); - expect(response).toEqual(previewFileResponse); - }); + expect(response).toEqual(previewFileResponse); + }); - it('does not call image magicks resize and stream method', async () => { - const { fileRecord, downloadParams, previewParams } = setup(); + it('does not call generate', async () => { + const { fileRecord, previewParams, bytesRange } = setup(); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams, bytesRange); - expect(resizeMock).not.toHaveBeenCalled(); - expect(streamMock).not.toHaveBeenCalled(); - }); + expect(previewProducer.generate).toHaveBeenCalledTimes(0); }); + }); - describe('WHEN S3ClientAdapter get throws NotFoundException', () => { - const setup = () => { - const bytesRange = 'bytes=0-100'; - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { - ...defaultPreviewParamsWithWidth, - }; - const format = previewParams.outputFormat.split('/')[1]; - - const error = new NotFoundException(); - s3ClientAdapter.get.mockRejectedValueOnce(error); - - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - - const previewFile = TestHelper.createFile(); - s3ClientAdapter.get.mockResolvedValueOnce(previewFile); - - const fileNameWithoutExtension = fileRecord.name.split('.')[0]; - const name = `${fileNameWithoutExtension}.${format}`; - const previewFileResponse = FileResponseBuilder.build(previewFile, name); - - const hash = createPreviewNameHash(fileRecord.id, previewParams); - const previewFileDto = FileDtoBuilder.build(hash, previewFile.data, previewParams.outputFormat); - const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); - - streamMock.mockClear(); - streamMock.mockReturnValueOnce(previewFileDto.data); - - resizeMock.mockClear(); - - return { - bytesRange, - fileRecord, - downloadParams, - previewParams, - format, - previewFileDto, - previewPath, - previewFileResponse, - }; + describe('WHEN first get of preview file throws error and second is successfull', () => { + const setup = (error: Error | NotFoundException) => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: false, }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + s3ClientAdapter.get.mockRejectedValueOnce(error).mockResolvedValueOnce(previewFile); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, + }; + }; - it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange); - - expect(fileStorageService.download).toHaveBeenCalledWith(fileRecord, downloadParams, bytesRange); - }); - - it('calls image magicks resize method', async () => { - const { fileRecord, downloadParams, previewParams } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(resizeMock).toHaveBeenCalledWith(previewParams.width, undefined, '>'); - expect(resizeMock).toHaveBeenCalledTimes(1); - }); - - it('calls image magicks stream method', async () => { - const { fileRecord, downloadParams, previewParams, format } = setup(); - - await previewService.getPreview(fileRecord, downloadParams, previewParams); - - expect(streamMock).toHaveBeenCalledWith(format); - expect(streamMock).toHaveBeenCalledTimes(1); - }); - - it('calls S3ClientAdapters create method', async () => { - const { fileRecord, downloadParams, previewParams, previewFileDto, previewPath } = setup(); + describe('WHEN error is a NotFoundException', () => { + it('calls previewProducer.generate with correct params', async () => { + const notFoundException = new NotFoundException(); + const { fileRecord, previewParams, bytesRange, originPath, previewPath, format } = + setup(notFoundException); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams, bytesRange); - expect(s3ClientAdapter.create).toHaveBeenCalledWith(previewPath, previewFileDto); - expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + expect(previewProducer.generate).toHaveBeenCalledWith({ + originFilePath: originPath, + previewFilePath: previewPath, + previewOptions: { width: previewParams.width, format }, + }); + expect(previewProducer.generate).toHaveBeenCalledTimes(1); }); it('calls S3ClientAdapters get method', async () => { - const { fileRecord, downloadParams, previewParams, previewPath } = setup(); + const notFoundException = new NotFoundException(); + const { fileRecord, previewParams, previewPath } = setup(notFoundException); - await previewService.getPreview(fileRecord, downloadParams, previewParams); + await previewService.download(fileRecord, previewParams); expect(s3ClientAdapter.get).toHaveBeenCalledWith(previewPath, undefined); expect(s3ClientAdapter.get).toHaveBeenCalledTimes(2); }); + }); - it('returns preview file response', async () => { - const { fileRecord, downloadParams, previewParams, previewFileResponse } = setup(); - - const response = await previewService.getPreview(fileRecord, downloadParams, previewParams); + describe('WHEN error is other error', () => { + it('should pass error', async () => { + const error = new Error('testError'); + const { fileRecord, previewParams } = setup(error); - expect(response).toEqual(previewFileResponse); + await expect(previewService.download(fileRecord, previewParams)).rejects.toThrow(error); }); }); + }); - describe('WHEN S3ClientAdapter get throws other than NotFoundException', () => { - const setup = () => { - const mimeType = 'image/png'; - const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { - ...defaultPreviewParamsWithWidth, - }; - const format = previewParams.outputFormat.split('/')[1]; - - const error = new Error('testError'); - s3ClientAdapter.get.mockRejectedValueOnce(error); - - return { - fileRecord, - downloadParams, - previewParams, - format, - error, - }; + describe('WHEN both gets of preview file throw error', () => { + const setup = () => { + const bytesRange = 'bytes=0-100'; + const mimeType = 'image/png'; + const { fileRecord } = buildFileRecordWithParams(mimeType); + const previewParams = { + ...defaultPreviewParamsWithWidth, + forceUpdate: true, }; + const format = previewParams.outputFormat.split('/')[1]; + + const previewFile = TestHelper.createFile(); + const notFoundException = new NotFoundException(); + s3ClientAdapter.get.mockRejectedValueOnce(notFoundException).mockRejectedValueOnce(notFoundException); + + const fileNameWithoutExtension = fileRecord.name.split('.')[0]; + const name = `${fileNameWithoutExtension}.${format}`; + const previewFileResponse = FileResponseBuilder.build(previewFile, name); + + const hash = createPreviewNameHash(fileRecord.id, previewParams); + const previewPath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const originPath = createPath(fileRecord.getSchoolId(), fileRecord.id); + + return { + bytesRange, + fileRecord, + previewParams, + format, + previewPath, + originPath, + previewFileResponse, + }; + }; - it('passes error', async () => { - const { fileRecord, downloadParams, previewParams, error } = setup(); + it('should pass error', async () => { + const { fileRecord, previewParams } = setup(); - await expect(previewService.getPreview(fileRecord, downloadParams, previewParams)).rejects.toThrow(error); - }); + await expect(previewService.download(fileRecord, previewParams)).rejects.toThrow(); }); }); }); @@ -603,33 +447,23 @@ describe('PreviewService', () => { const mimeType = 'application/zip'; const format = mimeType.split('/')[1]; const { fileRecord } = buildFileRecordWithParams(mimeType); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; const previewParams = { ...defaultPreviewParams, forceUpdate: true }; - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - const error = new UnprocessableEntityException(ErrorType.PREVIEW_NOT_POSSIBLE); return { bytesRange, fileRecord, - downloadParams, previewParams, format, error, }; }; - it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange, error } = setup(); + it('passes error', async () => { + const { fileRecord, previewParams, bytesRange, error } = setup(); - await expect( - previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange) - ).rejects.toThrowError(error); + await expect(previewService.download(fileRecord, previewParams, bytesRange)).rejects.toThrowError(error); }); }); @@ -639,33 +473,23 @@ describe('PreviewService', () => { const mimeType = 'image/png'; const format = mimeType.split('/')[1]; const { fileRecord } = buildFileRecordWithParams(mimeType, ScanStatus.PENDING); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; const previewParams = { ...defaultPreviewParams, forceUpdate: true }; - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - const error = new UnprocessableEntityException(ErrorType.PREVIEW_NOT_POSSIBLE); return { bytesRange, fileRecord, - downloadParams, previewParams, format, error, }; }; - it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange, error } = setup(); + it('passes error', async () => { + const { fileRecord, previewParams, bytesRange, error } = setup(); - await expect( - previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange) - ).rejects.toThrowError(error); + await expect(previewService.download(fileRecord, previewParams, bytesRange)).rejects.toThrowError(error); }); }); @@ -675,21 +499,14 @@ describe('PreviewService', () => { const mimeType = 'image/png'; const format = mimeType.split('/')[1]; const { fileRecord } = buildFileRecordWithParams(mimeType, ScanStatus.ERROR); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; - const previewParams = { ...defaultPreviewParams, forceUpdate: true }; - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); + const previewParams = { ...defaultPreviewParams, forceUpdate: true }; const error = new UnprocessableEntityException(ErrorType.PREVIEW_NOT_POSSIBLE); return { bytesRange, fileRecord, - downloadParams, previewParams, format, error, @@ -697,11 +514,9 @@ describe('PreviewService', () => { }; it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange, error } = setup(); + const { fileRecord, previewParams, bytesRange, error } = setup(); - await expect( - previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange) - ).rejects.toThrowError(error); + await expect(previewService.download(fileRecord, previewParams, bytesRange)).rejects.toThrowError(error); }); }); @@ -711,21 +526,13 @@ describe('PreviewService', () => { const mimeType = 'image/png'; const format = mimeType.split('/')[1]; const { fileRecord } = buildFileRecordWithParams(mimeType, ScanStatus.BLOCKED); - const downloadParams = { - fileRecordId: fileRecord.id, - fileName: fileRecord.name, - }; const previewParams = { ...defaultPreviewParams, forceUpdate: true }; - const originalFileResponse = TestHelper.createFileResponse(); - fileStorageService.download.mockResolvedValueOnce(originalFileResponse); - const error = new UnprocessableEntityException(ErrorType.PREVIEW_NOT_POSSIBLE); return { bytesRange, fileRecord, - downloadParams, previewParams, format, error, @@ -733,11 +540,9 @@ describe('PreviewService', () => { }; it('calls download with correct params', async () => { - const { fileRecord, downloadParams, previewParams, bytesRange, error } = setup(); + const { fileRecord, previewParams, bytesRange, error } = setup(); - await expect( - previewService.getPreview(fileRecord, downloadParams, previewParams, bytesRange) - ).rejects.toThrowError(error); + await expect(previewService.download(fileRecord, previewParams, bytesRange)).rejects.toThrowError(error); }); }); }); @@ -789,10 +594,10 @@ describe('PreviewService', () => { }; }; - it('does not pass error', async () => { - const { fileRecord } = setup(); + it('should throw error', async () => { + const { fileRecord, error } = setup(); - await previewService.deletePreviews([fileRecord]); + await expect(previewService.deletePreviews([fileRecord])).rejects.toThrowError(error); }); }); }); diff --git a/apps/server/src/modules/files-storage/service/preview.service.ts b/apps/server/src/modules/files-storage/service/preview.service.ts index 5ca2093351b..e27fbc0645a 100644 --- a/apps/server/src/modules/files-storage/service/preview.service.ts +++ b/apps/server/src/modules/files-storage/service/preview.service.ts @@ -1,64 +1,45 @@ import { Inject, Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'; +import { PreviewProducer } from '@shared/infra/preview-generator'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { LegacyLogger } from '@src/core/logger'; -import { subClass } from 'gm'; -import { PassThrough } from 'stream'; -import { DownloadFileParams, PreviewParams } from '../controller/dto'; +import { PreviewParams } from '../controller/dto'; import { FileRecord, PreviewStatus } from '../entity'; import { ErrorType } from '../error'; import { FILES_STORAGE_S3_CONNECTION } from '../files-storage.config'; -import { createPreviewDirectoryPath, createPreviewFilePath, createPreviewNameHash } from '../helper'; +import { createPreviewDirectoryPath, getPreviewName } from '../helper'; import { GetFileResponse, PreviewFileParams } from '../interface'; -import { PreviewOutputMimeTypes } from '../interface/preview-output-mime-types.enum'; -import { FileDtoBuilder, FileResponseBuilder } from '../mapper'; -import { FilesStorageService } from './files-storage.service'; +import { FileResponseBuilder, PreviewBuilder } from '../mapper'; @Injectable() export class PreviewService { constructor( @Inject(FILES_STORAGE_S3_CONNECTION) private readonly storageClient: S3ClientAdapter, - private readonly fileStorageService: FilesStorageService, - private logger: LegacyLogger + private logger: LegacyLogger, + private readonly previewProducer: PreviewProducer ) { this.logger.setContext(PreviewService.name); } - public async getPreview( + public async download( fileRecord: FileRecord, - downloadParams: DownloadFileParams, previewParams: PreviewParams, bytesRange?: string ): Promise { this.checkIfPreviewPossible(fileRecord); - const hash = createPreviewNameHash(fileRecord.id, previewParams); - const filePath = createPreviewFilePath(fileRecord.getSchoolId(), hash, fileRecord.id); + const previewFileParams = PreviewBuilder.buildParams(fileRecord, previewParams, bytesRange); - let response: GetFileResponse; - - const previewFileParams = { fileRecord, downloadParams, previewParams, hash, filePath, bytesRange }; - - if (previewParams.forceUpdate) { - response = await this.generatePreview(previewFileParams); - } else { - response = await this.tryGetPreviewOrGenerate(previewFileParams); - } + const response = await this.tryGetPreviewOrGenerate(previewFileParams); return response; } public async deletePreviews(fileRecords: FileRecord[]): Promise { - try { - const paths = fileRecords.map((fileRecord) => - createPreviewDirectoryPath(fileRecord.getSchoolId(), fileRecord.id) - ); + const paths = fileRecords.map((fileRecord) => createPreviewDirectoryPath(fileRecord.getSchoolId(), fileRecord.id)); - const promises = paths.map((path) => this.storageClient.deleteDirectory(path)); + const promises = paths.map((path) => this.storageClient.deleteDirectory(path)); - await Promise.all(promises); - } catch (error) { - this.logger.warn(error); - } + await Promise.all(promises); } private checkIfPreviewPossible(fileRecord: FileRecord): void | UnprocessableEntityException { @@ -72,79 +53,36 @@ export class PreviewService { let file: GetFileResponse; try { + if (params.previewParams.forceUpdate) { + await this.generatePreview(params); + } + file = await this.getPreviewFile(params); } catch (error) { if (!(error instanceof NotFoundException)) { throw error; } - file = await this.generatePreview(params); + await this.generatePreview(params); + file = await this.getPreviewFile(params); } return file; } private async getPreviewFile(params: PreviewFileParams): Promise { - const { fileRecord, filePath, bytesRange, previewParams } = params; - const name = this.getPreviewName(fileRecord, previewParams.outputFormat); - const file = await this.storageClient.get(filePath, bytesRange); + const { fileRecord, previewFilePath, bytesRange, previewParams } = params; + const name = getPreviewName(fileRecord, previewParams.outputFormat); + const file = await this.storageClient.get(previewFilePath, bytesRange); const response = FileResponseBuilder.build(file, name); return response; } - private async generatePreview(params: PreviewFileParams): Promise { - const { fileRecord, downloadParams, previewParams, hash, filePath, bytesRange } = params; - - const original = await this.fileStorageService.download(fileRecord, downloadParams, bytesRange); - const preview = this.resizeAndConvert(original, fileRecord, previewParams); - - const format = previewParams.outputFormat ?? fileRecord.mimeType; - const fileDto = FileDtoBuilder.build(hash, preview, format); - await this.storageClient.create(filePath, fileDto); - - const response = await this.getPreviewFile(params); - - return response; - } - - private resizeAndConvert( - original: GetFileResponse, - fileRecord: FileRecord, - previewParams: PreviewParams - ): PassThrough { - const mimeType = previewParams.outputFormat ?? fileRecord.mimeType; - const format = this.getFormat(mimeType); - const im = subClass({ imageMagick: '7+' }); - - const preview = im(original.data, fileRecord.name); - const { width } = previewParams; - - if (width) { - preview.resize(width, undefined, '>'); - } - - const result = preview.stream(format); - - return result; - } - - private getFormat(mimeType: string): string { - const format = mimeType.split('/')[1]; - - return format; - } - - private getPreviewName(fileRecord: FileRecord, outputFormat?: PreviewOutputMimeTypes): string { - if (!outputFormat) { - return fileRecord.name; - } - - const fileNameWithoutExtension = fileRecord.name.split('.')[0]; - const format = this.getFormat(outputFormat); - const name = `${fileNameWithoutExtension}.${format}`; + private async generatePreview(params: PreviewFileParams): Promise { + const payload = PreviewBuilder.buildPayload(params); - return name; + await this.previewProducer.generate(payload); } } diff --git a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts index a1aaf0342ee..b12006367aa 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts @@ -1,5 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { HttpService } from '@nestjs/axios'; import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -8,7 +9,6 @@ import { AntivirusService } from '@shared/infra/antivirus'; import { S3ClientAdapter } from '@shared/infra/s3-client'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -123,7 +123,7 @@ describe('FilesStorageUC delete methods', () => { const mockedResult = [[fileRecord], 0] as Counted; authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); - filesStorageService.deleteFilesOfParent.mockResolvedValueOnce(mockedResult); + filesStorageService.getFileRecordsOfParent.mockResolvedValueOnce(mockedResult); return { params, userId, mockedResult, requestParams, fileRecord }; }; @@ -143,11 +143,11 @@ describe('FilesStorageUC delete methods', () => { }); it('should call service with correct params', async () => { - const { requestParams, userId } = setup(); + const { requestParams, userId, fileRecord } = setup(); await filesStorageUC.deleteFilesOfParent(userId, requestParams); - expect(filesStorageService.deleteFilesOfParent).toHaveBeenCalledWith(requestParams.parentId); + expect(filesStorageService.deleteFilesOfParent).toHaveBeenCalledWith([fileRecord]); }); it('should call deletePreviews', async () => { @@ -189,10 +189,14 @@ describe('FilesStorageUC delete methods', () => { describe('WHEN service throws error', () => { const setup = () => { + const { fileRecords } = buildFileRecordsWithParams(); + + authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); const { requestParams, userId } = createParams(); const error = new Error('test'); authorizationReferenceService.checkPermissionByReferences.mockResolvedValueOnce(); + filesStorageService.getFileRecordsOfParent.mockResolvedValueOnce([fileRecords, fileRecords.length]); filesStorageService.deleteFilesOfParent.mockRejectedValueOnce(error); return { requestParams, userId, error }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts index 81b54553d1e..f0aa9dcc25a 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts @@ -111,7 +111,7 @@ describe('FilesStorageUC', () => { const previewFileResponse = TestHelper.createFileResponse(); filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); - previewService.getPreview.mockResolvedValueOnce(previewFileResponse); + previewService.download.mockResolvedValueOnce(previewFileResponse); return { fileDownloadParams, previewParams, userId, fileRecord, singleFileParams, previewFileResponse }; }; @@ -129,12 +129,7 @@ describe('FilesStorageUC', () => { await filesStorageUC.downloadPreview(userId, fileDownloadParams, previewParams); - expect(previewService.getPreview).toHaveBeenCalledWith( - fileRecord, - fileDownloadParams, - previewParams, - undefined - ); + expect(previewService.download).toHaveBeenCalledWith(fileRecord, previewParams, undefined); }); it('should call checkPermission with correct params', async () => { @@ -211,7 +206,7 @@ describe('FilesStorageUC', () => { filesStorageService.getFileRecord.mockResolvedValueOnce(fileRecord); const error = new Error('test'); - previewService.getPreview.mockRejectedValueOnce(error); + previewService.download.mockRejectedValueOnce(error); return { fileDownloadParams, previewParams, userId, error }; }; diff --git a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts index 833d7575bdf..47eb7c79104 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts @@ -1,9 +1,9 @@ +import { AuthorizationContext } from '@modules/authorization'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { HttpService } from '@nestjs/axios'; import { Injectable, NotFoundException } from '@nestjs/common'; import { Counted, EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext } from '@modules/authorization'; -import { AuthorizationReferenceService } from '@modules/authorization/domain'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import busboy from 'busboy'; import { Request } from 'express'; @@ -154,7 +154,9 @@ export class FilesStorageUC { await this.checkPermission(userId, parentType, parentId, FileStorageAuthorizationContext.read); - const result = this.previewService.getPreview(fileRecord, params, previewParams, bytesRange); + this.filesStorageService.checkFileName(fileRecord, params); + + const result = this.previewService.download(fileRecord, previewParams, bytesRange); return result; } @@ -162,8 +164,9 @@ export class FilesStorageUC { // delete public async deleteFilesOfParent(userId: EntityId, params: FileRecordParams): Promise> { await this.checkPermission(userId, params.parentType, params.parentId, FileStorageAuthorizationContext.delete); - const [fileRecords, count] = await this.filesStorageService.deleteFilesOfParent(params.parentId); + const [fileRecords, count] = await this.filesStorageService.getFileRecordsOfParent(params.parentId); await this.previewService.deletePreviews(fileRecords); + await this.filesStorageService.deleteFilesOfParent(fileRecords); return [fileRecords, count]; } @@ -173,8 +176,8 @@ export class FilesStorageUC { const { parentType, parentId } = fileRecord.getParentInfo(); await this.checkPermission(userId, parentType, parentId, FileStorageAuthorizationContext.delete); - await this.filesStorageService.delete([fileRecord]); await this.previewService.deletePreviews([fileRecord]); + await this.filesStorageService.delete([fileRecord]); return fileRecord; } diff --git a/apps/server/src/shared/infra/preview-generator/index.ts b/apps/server/src/shared/infra/preview-generator/index.ts new file mode 100644 index 00000000000..570b38cc9bd --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/index.ts @@ -0,0 +1,4 @@ +export * from './interface'; +export * from './preview-generator-consumer.module'; +export * from './preview-generator-producer.module'; +export * from './preview.producer'; diff --git a/apps/server/src/shared/infra/preview-generator/interface/index.ts b/apps/server/src/shared/infra/preview-generator/interface/index.ts new file mode 100644 index 00000000000..37aae418ee2 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/interface/index.ts @@ -0,0 +1 @@ +export * from './preview'; diff --git a/apps/server/src/shared/infra/preview-generator/interface/preview-consumer-config.ts b/apps/server/src/shared/infra/preview-generator/interface/preview-consumer-config.ts new file mode 100644 index 00000000000..2924fc945bc --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/interface/preview-consumer-config.ts @@ -0,0 +1,11 @@ +import { S3Config } from '@shared/infra/s3-client'; + +export interface PreviewModuleConfig { + NEST_LOG_LEVEL: string; + INCOMING_REQUEST_TIMEOUT: number; +} + +export interface PreviewConfig { + storageConfig: S3Config; + serverConfig: PreviewModuleConfig; +} diff --git a/apps/server/src/shared/infra/preview-generator/interface/preview.ts b/apps/server/src/shared/infra/preview-generator/interface/preview.ts new file mode 100644 index 00000000000..92ab2808151 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/interface/preview.ts @@ -0,0 +1,15 @@ +export interface PreviewOptions { + format: string; + width?: number; +} + +export interface PreviewFileOptions { + originFilePath: string; + previewFilePath: string; + previewOptions: PreviewOptions; +} + +export interface PreviewResponseMessage { + previewFilePath: string; + status: boolean; +} diff --git a/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.spec.ts b/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.spec.ts new file mode 100644 index 00000000000..04d56a991f1 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.spec.ts @@ -0,0 +1,37 @@ +import { PreviewActionsLoggable } from './preview-actions.loggable'; + +describe('PreviewActionsLoggable', () => { + describe('getLogMessage is called', () => { + const setup = () => { + const message = 'message'; + const payload = { + originFilePath: 'originFilePath', + previewFilePath: 'previewFilePath', + previewOptions: { + format: 'webp', + width: 100, + }, + }; + + const expectedResponse = { + message, + data: { + originFilePath: payload.originFilePath, + previewFilePath: payload.previewFilePath, + format: payload.previewOptions.format, + width: payload.previewOptions.width, + }, + }; + + return { message, payload, expectedResponse }; + }; + + it('should return log message', () => { + const { message, payload, expectedResponse } = setup(); + + const result = new PreviewActionsLoggable(message, payload).getLogMessage(); + + expect(result).toEqual(expectedResponse); + }); + }); +}); diff --git a/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.ts b/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.ts new file mode 100644 index 00000000000..e98f21d09be --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/loggable/preview-actions.loggable.ts @@ -0,0 +1,19 @@ +import { LogMessage, Loggable } from '@src/core/logger'; +import { PreviewFileOptions } from '../interface'; + +export class PreviewActionsLoggable implements Loggable { + constructor(private readonly message: string, private readonly payload: PreviewFileOptions) {} + + getLogMessage(): LogMessage { + const { originFilePath, previewFilePath, previewOptions } = this.payload; + return { + message: this.message, + data: { + originFilePath, + previewFilePath, + format: previewOptions.format, + width: previewOptions.width, + }, + }; + } +} diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator-consumer.module.ts b/apps/server/src/shared/infra/preview-generator/preview-generator-consumer.module.ts new file mode 100644 index 00000000000..9d352b81d9d --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator-consumer.module.ts @@ -0,0 +1,36 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { RabbitMQWrapperModule } from '@shared/infra/rabbitmq'; +import { S3ClientAdapter, S3ClientModule } from '@shared/infra/s3-client'; +import { createConfigModuleOptions } from '@src/config'; +import { Logger, LoggerModule } from '@src/core/logger'; +import { PreviewConfig } from './interface/preview-consumer-config'; +import { PreviewGeneratorConsumer } from './preview-generator.consumer'; +import { PreviewGeneratorService } from './preview-generator.service'; + +@Module({}) +export class PreviewGeneratorConsumerModule { + static register(config: PreviewConfig): DynamicModule { + const { storageConfig, serverConfig } = config; + const providers = [ + { + provide: PreviewGeneratorService, + useFactory: (logger: Logger, storageClient: S3ClientAdapter) => + new PreviewGeneratorService(storageClient, logger), + inject: [Logger, storageConfig.connectionName], + }, + PreviewGeneratorConsumer, + ]; + + return { + module: PreviewGeneratorConsumerModule, + imports: [ + LoggerModule, + S3ClientModule.register([storageConfig]), + RabbitMQWrapperModule, + ConfigModule.forRoot(createConfigModuleOptions(() => serverConfig)), + ], + providers, + }; + } +} diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator-producer.module.ts b/apps/server/src/shared/infra/preview-generator/preview-generator-producer.module.ts new file mode 100644 index 00000000000..d3f65299657 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator-producer.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { LoggerModule } from '@src/core/logger'; +import { RabbitMQWrapperModule } from '../rabbitmq'; +import { PreviewProducer } from './preview.producer'; + +@Module({ + imports: [LoggerModule, RabbitMQWrapperModule], + providers: [PreviewProducer], + exports: [PreviewProducer], +}) +export class PreviewGeneratorProducerModule {} diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.builder.spec.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.builder.spec.ts new file mode 100644 index 00000000000..2caaad7abe8 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.builder.spec.ts @@ -0,0 +1,28 @@ +import { PassThrough } from 'stream'; +import { PreviewGeneratorBuilder } from './preview-generator.builder'; + +describe('PreviewGeneratorBuilder', () => { + describe('buildFile is called', () => { + const setup = () => { + const preview = new PassThrough(); + const previewOptions = { + format: 'webp', + }; + + const expectedResponse = { + data: preview, + mimeType: previewOptions.format, + }; + + return { preview, previewOptions, expectedResponse }; + }; + + it('should return preview file', () => { + const { preview, previewOptions, expectedResponse } = setup(); + + const result = PreviewGeneratorBuilder.buildFile(preview, previewOptions); + + expect(result).toEqual(expectedResponse); + }); + }); +}); diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.builder.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.builder.ts new file mode 100644 index 00000000000..4c5561ed089 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.builder.ts @@ -0,0 +1,16 @@ +import { File } from '@shared/infra/s3-client'; +import { PassThrough } from 'stream'; +import { PreviewOptions } from './interface'; + +export class PreviewGeneratorBuilder { + public static buildFile(preview: PassThrough, previewOptions: PreviewOptions): File { + const { format } = previewOptions; + + const file = { + data: preview, + mimeType: format, + }; + + return file; + } +} diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.spec.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.spec.ts new file mode 100644 index 00000000000..b1a24a30a57 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.spec.ts @@ -0,0 +1,80 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Logger } from '@src/core/logger'; +import { PreviewFileOptions, PreviewResponseMessage } from './interface'; +import { PreviewGeneratorConsumer } from './preview-generator.consumer'; +import { PreviewGeneratorService } from './preview-generator.service'; + +describe('PreviewGeneratorConsumer', () => { + let module: TestingModule; + let previewGeneratorService: DeepMocked; + let service: PreviewGeneratorConsumer; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + PreviewGeneratorConsumer, + { + provide: PreviewGeneratorService, + useValue: createMock(), + }, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + + previewGeneratorService = module.get(PreviewGeneratorService); + service = module.get(PreviewGeneratorConsumer); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('generatePreview()', () => { + const setup = () => { + const payload: PreviewFileOptions = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width: 500, + }, + }; + + const response: PreviewResponseMessage = { + previewFilePath: payload.previewFilePath, + status: true, + }; + previewGeneratorService.generatePreview.mockResolvedValueOnce(response); + + return { payload, response }; + }; + + it('should call previewGeneratorService.generatePreview with payload', async () => { + const { payload } = setup(); + + await service.generatePreview(payload); + + expect(previewGeneratorService.generatePreview).toBeCalledWith(payload); + }); + + it('should return expected value', async () => { + const { payload, response } = setup(); + + const result = await service.generatePreview(payload); + + expect(result).toEqual({ message: response }); + }); + }); +}); diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.ts new file mode 100644 index 00000000000..8fc08d261f3 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.consumer.ts @@ -0,0 +1,27 @@ +import { RabbitPayload, RabbitRPC } from '@golevelup/nestjs-rabbitmq'; +import { Injectable } from '@nestjs/common'; +import { Logger } from '@src/core/logger'; +import { FilesPreviewEvents, FilesPreviewExchange } from '@src/shared/infra/rabbitmq'; +import { PreviewFileOptions } from './interface'; +import { PreviewActionsLoggable } from './loggable/preview-actions.loggable'; +import { PreviewGeneratorService } from './preview-generator.service'; + +@Injectable() +export class PreviewGeneratorConsumer { + constructor(private readonly previewGeneratorService: PreviewGeneratorService, private logger: Logger) { + this.logger.setContext(PreviewGeneratorConsumer.name); + } + + @RabbitRPC({ + exchange: FilesPreviewExchange, + routingKey: FilesPreviewEvents.GENERATE_PREVIEW, + queue: FilesPreviewEvents.GENERATE_PREVIEW, + }) + public async generatePreview(@RabbitPayload() payload: PreviewFileOptions) { + this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorConsumer.generatePreview', payload)); + + const response = await this.previewGeneratorService.generatePreview(payload); + + return { message: response }; + } +} diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.service.spec.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.service.spec.ts new file mode 100644 index 00000000000..b8eeea612f5 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.service.spec.ts @@ -0,0 +1,151 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { GetFile, S3ClientAdapter } from '@shared/infra/s3-client'; +import { Logger } from '@src/core/logger'; +import { Readable } from 'node:stream'; +import { PreviewGeneratorService } from './preview-generator.service'; + +const streamMock = jest.fn(); +const resizeMock = jest.fn(); +const imageMagickMock = () => { + return { stream: streamMock, resize: resizeMock, data: Readable.from('text') }; +}; +jest.mock('gm', () => { + return { + subClass: () => imageMagickMock, + }; +}); + +const createFile = (contentRange?: string): GetFile => { + const text = 'testText'; + const readable = Readable.from(text); + + const fileResponse = { + data: readable, + contentType: 'image/jpeg', + contentLength: text.length, + contentRange, + etag: 'testTag', + }; + + return fileResponse; +}; + +describe('PreviewGeneratorService', () => { + let module: TestingModule; + let service: PreviewGeneratorService; + let s3ClientAdapter: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + PreviewGeneratorService, + { + provide: S3ClientAdapter, + useValue: createMock(), + }, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(PreviewGeneratorService); + s3ClientAdapter = module.get(S3ClientAdapter); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('generatePreview', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); + + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; + + return { params, originFile, expectedFileData }; + }; + + describe('WHEN download of original and preview file is successful', () => { + it('should call storageClient get method with originFilePath', async () => { + const { params } = setup(); + + await service.generatePreview(params); + + expect(s3ClientAdapter.get).toBeCalledWith(params.originFilePath); + }); + + it('should call imagemagicks resize method', async () => { + const { params } = setup(); + + await service.generatePreview(params); + + expect(resizeMock).toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); + expect(resizeMock).toHaveBeenCalledTimes(1); + }); + + it('should call imagemagicks stream method', async () => { + const { params } = setup(); + + await service.generatePreview(params); + + expect(streamMock).toHaveBeenCalledWith(params.previewOptions.format); + expect(streamMock).toHaveBeenCalledTimes(1); + }); + + it('should call S3ClientAdapters create method', async () => { + const { params, expectedFileData } = setup(); + + await service.generatePreview(params); + + expect(s3ClientAdapter.create).toHaveBeenCalledWith(params.previewFilePath, expectedFileData); + expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + }); + + it('should should return values', async () => { + const { params } = setup(); + const expectedValue = { previewFilePath: params.previewFilePath, status: true }; + + const result = await service.generatePreview(params); + + expect(result).toEqual(expectedValue); + }); + }); + + describe('WHEN previewParams.width not set', () => { + it('should not call imagemagicks resize method', async () => { + const { params } = setup(0); + + await service.generatePreview(params); + + expect(resizeMock).not.toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); + expect(resizeMock).not.toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts new file mode 100644 index 00000000000..72dac25f076 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { GetFile, S3ClientAdapter } from '@shared/infra/s3-client'; +import { Logger } from '@src/core/logger'; +import { subClass } from 'gm'; +import { PassThrough } from 'stream'; +import { PreviewFileOptions, PreviewOptions, PreviewResponseMessage } from './interface'; +import { PreviewActionsLoggable } from './loggable/preview-actions.loggable'; +import { PreviewGeneratorBuilder } from './preview-generator.builder'; + +@Injectable() +export class PreviewGeneratorService { + private imageMagick = subClass({ imageMagick: '7+' }); + + constructor(private readonly storageClient: S3ClientAdapter, private logger: Logger) { + this.logger.setContext(PreviewGeneratorService.name); + } + + public async generatePreview(params: PreviewFileOptions): Promise { + this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:start', params)); + const { originFilePath, previewFilePath, previewOptions } = params; + + const original = await this.downloadOriginFile(originFilePath); + const preview = this.resizeAndConvert(original, previewOptions); + + const file = PreviewGeneratorBuilder.buildFile(preview, params.previewOptions); + + await this.storageClient.create(previewFilePath, file); + + this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:end', params)); + + return { + previewFilePath, + status: true, + }; + } + + private async downloadOriginFile(pathToFile: string): Promise { + const file = await this.storageClient.get(pathToFile); + + return file; + } + + private resizeAndConvert(original: GetFile, previewParams: PreviewOptions): PassThrough { + const { format, width } = previewParams; + + const preview = this.imageMagick(original.data); + + if (width) { + preview.resize(width, undefined, '>'); + } + + const result = preview.stream(format); + + return result; + } +} diff --git a/apps/server/src/shared/infra/preview-generator/preview.producer.spec.ts b/apps/server/src/shared/infra/preview-generator/preview.producer.spec.ts new file mode 100644 index 00000000000..47adea158a6 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview.producer.spec.ts @@ -0,0 +1,128 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { InternalServerErrorException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { setupEntities } from '@shared/testing'; +import { Logger } from '@src/core/logger'; +import { ErrorMapper, FilesPreviewEvents, FilesPreviewExchange } from '../rabbitmq'; +import { PreviewFileOptions } from './interface'; +import { PreviewProducer } from './preview.producer'; + +describe('PreviewProducer', () => { + let module: TestingModule; + let service: PreviewProducer; + let configService: DeepMocked; + let amqpConnection: DeepMocked; + + beforeAll(async () => { + await setupEntities(); + module = await Test.createTestingModule({ + providers: [ + PreviewProducer, + { + provide: Logger, + useValue: createMock(), + }, + { + provide: AmqpConnection, + useValue: createMock(), + }, + { + provide: ConfigService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(PreviewProducer); + amqpConnection = module.get(AmqpConnection); + configService = module.get(ConfigService); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('generate', () => { + describe('when valid params are passed and amqp connection return with a message', () => { + const setup = () => { + const timeout = 10000; + + const params: PreviewFileOptions = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width: 500, + }, + }; + + const message = []; + amqpConnection.request.mockResolvedValueOnce({ message }); + configService.get.mockReturnValue(timeout); + + const expectedParams = { + exchange: FilesPreviewExchange, + routingKey: FilesPreviewEvents.GENERATE_PREVIEW, + payload: params, + timeout, + }; + + return { params, expectedParams, message }; + }; + + it('should call the ampqConnection.', async () => { + const { params, expectedParams } = setup(); + + await service.generate(params); + + expect(amqpConnection.request).toHaveBeenCalledWith(expectedParams); + }); + + it('should return the response message.', async () => { + const { params, message } = setup(); + + const res = await service.generate(params); + + expect(res).toEqual(message); + }); + }); + + describe('when amqpConnection return with error in response', () => { + const setup = () => { + const params: PreviewFileOptions = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width: 500, + }, + }; + + const error = new Error('An error from called service'); + + amqpConnection.request.mockResolvedValueOnce({ error }); + const spy = jest.spyOn(ErrorMapper, 'mapRpcErrorResponseToDomainError'); + + return { params, spy, error }; + }; + + it('should call error mapper and throw with error', async () => { + const { params, spy, error } = setup(); + + await expect(service.generate(params)).rejects.toThrowError( + new InternalServerErrorException(null, { cause: error }) + ); + expect(spy).toBeCalled(); + }); + }); + }); +}); diff --git a/apps/server/src/shared/infra/preview-generator/preview.producer.ts b/apps/server/src/shared/infra/preview-generator/preview.producer.ts new file mode 100644 index 00000000000..602e2503185 --- /dev/null +++ b/apps/server/src/shared/infra/preview-generator/preview.producer.ts @@ -0,0 +1,31 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { FilesPreviewEvents, FilesPreviewExchange, RpcMessageProducer } from '@shared/infra/rabbitmq'; +import { Logger } from '@src/core/logger'; +import { PreviewFileOptions, PreviewResponseMessage } from './interface'; +import { PreviewModuleConfig } from './interface/preview-consumer-config'; +import { PreviewActionsLoggable } from './loggable/preview-actions.loggable'; + +@Injectable() +export class PreviewProducer extends RpcMessageProducer { + constructor( + protected readonly amqpConnection: AmqpConnection, + private readonly logger: Logger, + protected readonly configService: ConfigService + ) { + const timeout = configService.get('INCOMING_REQUEST_TIMEOUT'); + + super(amqpConnection, FilesPreviewExchange, timeout); + this.logger.setContext(PreviewProducer.name); + } + + async generate(payload: PreviewFileOptions): Promise { + this.logger.debug(new PreviewActionsLoggable('PreviewProducer.generate:started', payload)); + const response = await this.request(FilesPreviewEvents.GENERATE_PREVIEW, payload); + + this.logger.debug(new PreviewActionsLoggable('PreviewProducer.generate:finished', payload)); + + return response; + } +} diff --git a/apps/server/src/modules/files-storage-client/mapper/error.mapper.spec.ts b/apps/server/src/shared/infra/rabbitmq/error.mapper.spec.ts similarity index 100% rename from apps/server/src/modules/files-storage-client/mapper/error.mapper.spec.ts rename to apps/server/src/shared/infra/rabbitmq/error.mapper.spec.ts diff --git a/apps/server/src/modules/files-storage-client/mapper/error.mapper.ts b/apps/server/src/shared/infra/rabbitmq/error.mapper.ts similarity index 100% rename from apps/server/src/modules/files-storage-client/mapper/error.mapper.ts rename to apps/server/src/shared/infra/rabbitmq/error.mapper.ts diff --git a/apps/server/src/shared/infra/rabbitmq/exchange/files-preview.ts b/apps/server/src/shared/infra/rabbitmq/exchange/files-preview.ts new file mode 100644 index 00000000000..0bab7491e07 --- /dev/null +++ b/apps/server/src/shared/infra/rabbitmq/exchange/files-preview.ts @@ -0,0 +1,7 @@ +import { Configuration } from '@hpi-schul-cloud/commons/lib'; + +export const FilesPreviewExchange = Configuration.get('FILES_STORAGE__EXCHANGE') as string; + +export enum FilesPreviewEvents { + 'GENERATE_PREVIEW' = 'generate-preview', +} diff --git a/apps/server/src/shared/infra/rabbitmq/exchange/index.ts b/apps/server/src/shared/infra/rabbitmq/exchange/index.ts index f0d84c9e1a1..0cf6bd00d13 100644 --- a/apps/server/src/shared/infra/rabbitmq/exchange/index.ts +++ b/apps/server/src/shared/infra/rabbitmq/exchange/index.ts @@ -1 +1,2 @@ +export * from './files-preview'; export * from './files-storage'; diff --git a/apps/server/src/shared/infra/rabbitmq/index.ts b/apps/server/src/shared/infra/rabbitmq/index.ts index 0183c37b284..99f5887b9a8 100644 --- a/apps/server/src/shared/infra/rabbitmq/index.ts +++ b/apps/server/src/shared/infra/rabbitmq/index.ts @@ -1,3 +1,5 @@ +export * from './error.mapper'; export * from './exchange'; export * from './rabbitmq.module'; export * from './rpc-message'; +export * from './rpc-message-producer'; diff --git a/apps/server/src/shared/infra/rabbitmq/rabbitmq.module.ts b/apps/server/src/shared/infra/rabbitmq/rabbitmq.module.ts index 2d73162e6e3..946b642e779 100644 --- a/apps/server/src/shared/infra/rabbitmq/rabbitmq.module.ts +++ b/apps/server/src/shared/infra/rabbitmq/rabbitmq.module.ts @@ -1,7 +1,7 @@ import { AmqpConnectionManager, RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { Configuration } from '@hpi-schul-cloud/commons'; import { Global, Module, OnModuleDestroy } from '@nestjs/common'; -import { FilesStorageExchange } from './exchange'; +import { FilesPreviewExchange, FilesStorageExchange } from './exchange'; /** * https://www.npmjs.com/package/@golevelup/nestjs-rabbitmq#usage @@ -28,6 +28,10 @@ const imports = [ name: FilesStorageExchange, type: 'direct', }, + { + name: FilesPreviewExchange, + type: 'direct', + }, ], uri: Configuration.get('RABBITMQ_URI') as string, }), diff --git a/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.spec.ts b/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.spec.ts new file mode 100644 index 00000000000..b2e94ea676b --- /dev/null +++ b/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.spec.ts @@ -0,0 +1,130 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ErrorMapper, RpcMessageProducer } from '.'; + +interface TestPayload { + value: boolean; +} + +interface TestResponse { + value: boolean; +} + +const TestEvent = 'test-event'; +const TestExchange = 'test-exchange'; +const timeout = 1000; + +class RpcMessageProducerImp extends RpcMessageProducer { + constructor(protected readonly amqpConnection: AmqpConnection) { + super(amqpConnection, TestExchange, timeout); + } + + async testRequest(payload: TestPayload): Promise { + const response = await this.request(TestEvent, payload); + + return response; + } +} + +describe('RpcMessageProducer', () => { + let service: RpcMessageProducerImp; + let amqpConnection: DeepMocked; + + beforeAll(() => { + amqpConnection = createMock(); + + service = new RpcMessageProducerImp(amqpConnection); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('generate', () => { + describe('when valid params are passed and amqp connection return with a message', () => { + const setup = () => { + const params: TestPayload = { + value: true, + }; + + const message = []; + amqpConnection.request.mockResolvedValueOnce({ message }); + + const expectedParams = { + exchange: TestExchange, + routingKey: TestEvent, + payload: params, + timeout, + }; + + return { params, expectedParams, message }; + }; + + it('should call the ampqConnection.', async () => { + const { params, expectedParams } = setup(); + + await service.testRequest(params); + + expect(amqpConnection.request).toHaveBeenCalledWith(expectedParams); + }); + + it('should return the response message.', async () => { + const { params, message } = setup(); + + const res = await service.testRequest(params); + + expect(res).toEqual(message); + }); + }); + + describe('when amqpConnection return with error in response', () => { + const setup = () => { + const params: TestPayload = { + value: true, + }; + + const error = new Error('An error from called service'); + + amqpConnection.request.mockResolvedValueOnce({ error }); + const spy = jest.spyOn(ErrorMapper, 'mapRpcErrorResponseToDomainError'); + + return { params, spy, error }; + }; + + it('should call error mapper and throw with error', async () => { + const { params, spy, error } = setup(); + + await expect(service.testRequest(params)).rejects.toThrowError( + ErrorMapper.mapRpcErrorResponseToDomainError(error) + ); + expect(spy).toBeCalled(); + }); + }); + + describe('when amqpConnection throw an error', () => { + const setup = () => { + const params: TestPayload = { + value: true, + }; + + const error = new Error('An error from called service'); + + amqpConnection.request.mockRejectedValueOnce(error); + const spy = jest.spyOn(ErrorMapper, 'mapRpcErrorResponseToDomainError'); + + return { params, spy, error }; + }; + + it('should call error mapper and throw with error', async () => { + const { params, spy, error } = setup(); + + await expect(service.testRequest(params)).rejects.toThrowError(error); + expect(spy).not.toBeCalled(); + }); + }); + }); +}); diff --git a/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.ts b/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.ts new file mode 100644 index 00000000000..8a239b2cbcd --- /dev/null +++ b/apps/server/src/shared/infra/rabbitmq/rpc-message-producer.ts @@ -0,0 +1,37 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { ErrorMapper } from './error.mapper'; +import { RpcMessage } from './rpc-message'; + +export abstract class RpcMessageProducer { + constructor( + protected readonly amqpConnection: AmqpConnection, + protected readonly exchange: string, + protected readonly timeout: number + ) {} + + protected async request(event: string, payload: unknown) { + const response = await this.amqpConnection.request>(this.createRequest(event, payload)); + + this.checkError(response); + return response.message; + } + + // need to be fixed with https://ticketsystem.dbildungscloud.de/browse/BC-2984 + // mapRpcErrorResponseToDomainError should also removed with this ticket + protected checkError(response: RpcMessage) { + const { error } = response; + if (error) { + const domainError = ErrorMapper.mapRpcErrorResponseToDomainError(error); + throw domainError; + } + } + + protected createRequest(event: string, payload: unknown) { + return { + exchange: this.exchange, + routingKey: event, + payload, + timeout: this.timeout, + }; + } +} diff --git a/apps/server/src/shared/infra/rabbitmq/rpc-message.ts b/apps/server/src/shared/infra/rabbitmq/rpc-message.ts index 0d512e73b4a..c7e0e7de41f 100644 --- a/apps/server/src/shared/infra/rabbitmq/rpc-message.ts +++ b/apps/server/src/shared/infra/rabbitmq/rpc-message.ts @@ -1,6 +1,6 @@ export interface IError extends Error { status?: number; - message: never; + message: string; } export interface RpcMessage { message: T; diff --git a/apps/server/src/shared/infra/s3-client/interface/index.ts b/apps/server/src/shared/infra/s3-client/interface/index.ts index dc4b76ad922..ad6ed9c81da 100644 --- a/apps/server/src/shared/infra/s3-client/interface/index.ts +++ b/apps/server/src/shared/infra/s3-client/interface/index.ts @@ -24,6 +24,5 @@ export interface CopyFiles { export interface File { data: Readable; - name: string; mimeType: string; } diff --git a/config/default.schema.json b/config/default.schema.json index ef92d3f1db5..a34d8e899ad 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -1344,14 +1344,7 @@ } } }, - "required": [ - "TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION", - "STUDENT_TEAM_CREATION", - "BLOCK_DISPOSABLE_EMAIL_DOMAINS", - "HOST", - "ACTIVATION_LINK_PERIOD_OF_VALIDITY_SECONDS", - "AES_KEY" - ], + "required": [], "allOf": [ { "$ref": "#/definitions/FEATURE_ES_MERLIN_ENABLED", diff --git a/nest-cli.json b/nest-cli.json index c92fcacf1cf..73dea03c093 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -63,6 +63,15 @@ "tsConfigPath": "apps/server/tsconfig.app.json" } }, + "preview-generator-amqp": { + "type": "application", + "root": "apps/server", + "entryFile": "apps/preview-generator-consumer.app", + "sourceRoot": "apps/server/src", + "compilerOptions": { + "tsConfigPath": "apps/server/tsconfig.app.json" + } + }, "fwu-learning-contents": { "type": "application", "root": "apps/server", diff --git a/package.json b/package.json index 3c5a73df7d1..a82df72904f 100644 --- a/package.json +++ b/package.json @@ -61,11 +61,12 @@ "nest:start:management:dev": "nest start management --watch", "nest:start:management:debug": "nest start management --debug --watch", "nest:start:management:prod": "node dist/apps/server/apps/management.app", - "nest:start:files-storage": "nest start files-storage & nest start files-storage-amqp", - "nest:start:files-storage:dev": "nest start files-storage --watch & nest start files-storage-amqp --watch", - "nest:start:files-storage:debug": "nest start files-storage --debug --watch & nest start files-storage-amqp --debug --watch", + "nest:start:files-storage": "nest start files-storage & nest start files-storage-amqp & nest start preview-generator-amqp", + "nest:start:files-storage:dev": "nest start files-storage --watch & nest start files-storage-amqp --watch & nest start preview-generator-amqp --watch", + "nest:start:files-storage:debug": "nest start files-storage --debug --watch & nest start files-storage-amqp --debug --watch & nest start preview-generator-amqp --debug --watch", "nest:start:files-storage:prod": "node dist/apps/server/apps/files-storage.app", "nest:start:files-storage-amqp:prod": "node dist/apps/server/apps/files-storage-consumer.app", + "nest:start:preview-generator-amqp:prod": "node dist/apps/server/apps/preview-generator-consumer.app", "nest:start:fwu-learning-contents": "nest start fwu-learning-contents", "nest:start:fwu-learning-contents:debug": "nest start fwu-learning-contents --debug --watch", "nest:start:fwu-learning-contents:prod": "node dist/apps/server/apps/fwu-learning-contents.app", From 0a32c99a52ed29178afe2ac0bed22d0ec485c11b Mon Sep 17 00:00:00 2001 From: Martin Schuhmacher <55735359+MartinSchuhmacher@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:59:05 +0200 Subject: [PATCH 17/19] Bump crypto-js from 4.1.1 to 4.2.0 (#4508) updated-dependencies: - dependency-name: crypto-js (from 4.1.1 to 4.2.0.) - dependency-type: direct:production --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e36ecdd9873..cd0606b9fde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "connect-redis": "^6.1.3", "cors": "^2.8.1", "cross-env": "^7.0.0", - "crypto-js": "^4.0.0", + "crypto-js": "^4.2.0", "disposable-email-domains": "^1.0.56", "es6-promisify": "^7.0.0", "express": "^4.14.0", @@ -9069,9 +9069,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css-select": { "version": "5.1.0", @@ -31721,9 +31721,9 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css-select": { "version": "5.1.0", diff --git a/package.json b/package.json index a82df72904f..45e150f6668 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "connect-redis": "^6.1.3", "cors": "^2.8.1", "cross-env": "^7.0.0", - "crypto-js": "^4.0.0", + "crypto-js": "^4.2.0", "disposable-email-domains": "^1.0.56", "es6-promisify": "^7.0.0", "express": "^4.14.0", From 0cb9d54e58142979c9f7dae4bd72ea1e240eb7a6 Mon Sep 17 00:00:00 2001 From: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:28:30 +0100 Subject: [PATCH 18/19] BC-5489 - For loggables it should possible to pass unknown cause error (#4501) --- .../error/filter/global-error.filter.spec.ts | 104 +++++++++++++++++- .../core/error/filter/global-error.filter.ts | 4 +- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/apps/server/src/core/error/filter/global-error.filter.spec.ts b/apps/server/src/core/error/filter/global-error.filter.spec.ts index ca40620515f..c45c13e4bff 100644 --- a/apps/server/src/core/error/filter/global-error.filter.spec.ts +++ b/apps/server/src/core/error/filter/global-error.filter.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable promise/valid-params */ import { NotFound } from '@feathersjs/errors'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ArgumentsHost, BadRequestException, HttpStatus } from '@nestjs/common'; +import { ArgumentsHost, BadRequestException, HttpStatus, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { BusinessError } from '@shared/common'; import { ErrorLogger, ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; @@ -9,6 +9,7 @@ import { Response } from 'express'; import util from 'util'; import { ErrorResponse } from '../dto'; import { ErrorLoggable } from '../loggable/error.loggable'; +import { ErrorUtils } from '../utils'; import { GlobalErrorFilter } from './global-error.filter'; class SampleBusinessError extends BusinessError { @@ -42,6 +43,24 @@ class SampleLoggableException extends BadRequestException implements Loggable { } } +class SampleLoggableExceptionWithCause extends InternalServerErrorException implements Loggable { + constructor(private readonly testValue: string, error?: unknown) { + super(ErrorUtils.createHttpExceptionOptions(error)); + } + + getLogMessage(): ErrorLogMessage { + const message: ErrorLogMessage = { + type: 'WITH_CAUSE', + stack: this.stack, + data: { + testValue: this.testValue, + }, + }; + + return message; + } +} + describe('GlobalErrorFilter', () => { let module: TestingModule; let service: GlobalErrorFilter; @@ -304,24 +323,101 @@ describe('GlobalErrorFilter', () => { ).toBeCalledWith(expectedResponse); }); }); + + describe('when error has a cause error', () => { + const setup = () => { + const causeError = new Error('Cause error'); + const error = new SampleLoggableExceptionWithCause('test', causeError); + const expectedResponse = new ErrorResponse( + 'SAMPLE_WITH_CAUSE', + 'Sample With Cause', + 'Sample Loggable Exception With Cause', + HttpStatus.INTERNAL_SERVER_ERROR + ); + + const argumentsHost = setupHttpArgumentsHost(); + + return { error, argumentsHost, expectedResponse }; + }; + + it('should set response status appropriately', () => { + const { error, argumentsHost } = setup(); + + service.catch(error, argumentsHost); + + expect(argumentsHost.switchToHttp().getResponse().status).toBeCalledWith( + HttpStatus.INTERNAL_SERVER_ERROR + ); + }); + + it('should send appropriate error response', () => { + const { error, argumentsHost, expectedResponse } = setup(); + + service.catch(error, argumentsHost); + + expect( + argumentsHost.switchToHttp().getResponse().status(HttpStatus.INTERNAL_SERVER_ERROR).json + ).toBeCalledWith(expectedResponse); + }); + }); }); describe('when context is rmq', () => { + describe('when error is unknown error', () => { + const setup = () => { + const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('rmq'); + + const error = new Error(); + + return { error, argumentsHost }; + }; + + it('should return an RpcMessage with the error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual({ message: undefined, error }); + }); + }); + + describe('when error is a LoggableError', () => { + const setup = () => { + const causeError = new Error('Cause error'); + const error = new SampleLoggableExceptionWithCause('test', causeError); + const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('rmq'); + + return { error, argumentsHost }; + }; + + it('should return appropriate error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual({ message: undefined, error }); + }); + }); + }); + + describe('when context is other than rmq and http', () => { const setup = () => { const argumentsHost = createMock(); - argumentsHost.getType.mockReturnValueOnce('rmq'); + argumentsHost.getType.mockReturnValueOnce('other'); const error = new Error(); return { error, argumentsHost }; }; - it('should return an RpcMessage with the error', () => { + it('should return undefined', () => { const { error, argumentsHost } = setup(); const result = service.catch(error, argumentsHost); - expect(result).toEqual({ message: undefined, error }); + expect(result).toBeUndefined(); }); }); }); diff --git a/apps/server/src/core/error/filter/global-error.filter.ts b/apps/server/src/core/error/filter/global-error.filter.ts index 7e0d1dc3c3f..56760b18dd9 100644 --- a/apps/server/src/core/error/filter/global-error.filter.ts +++ b/apps/server/src/core/error/filter/global-error.filter.ts @@ -24,7 +24,9 @@ export class GlobalErrorFilter implements Exceptio if (contextType === 'http') { this.sendHttpResponse(error, host); - } else if (contextType === 'rmq') { + } + + if (contextType === 'rmq') { return { message: undefined, error }; } } From 63f38be55227c83660bb31cff110fc7cdf9f5ab3 Mon Sep 17 00:00:00 2001 From: mamutmk5 <3045922+mamutmk5@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:28:36 +0100 Subject: [PATCH 19/19] BC-5546 - Split ingress for Domains (#4495) Separate the Server Ingress to the Server Repo and for each service in an own one. Add the new ingress definitons files to the ansible roles. With the current version of nginx ingress is it possible to have more igresses with different resources for one domain. --- .../schulcloud-server-core/tasks/main.yml | 28 +++++++++++++ .../templates/api-files-ingress.yml.j2 | 41 +++++++++++++++++++ .../templates/api-fwu-ingress.yml.j2 | 41 +++++++++++++++++++ .../templates/ingress.yml.j2 | 41 +++++++++++++++++++ .../schulcloud-server-h5p/tasks/main.yml | 8 ++++ .../templates/api-h5p-ingress.yml.j2 | 41 +++++++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 ansible/roles/schulcloud-server-core/templates/api-files-ingress.yml.j2 create mode 100644 ansible/roles/schulcloud-server-core/templates/api-fwu-ingress.yml.j2 create mode 100644 ansible/roles/schulcloud-server-core/templates/ingress.yml.j2 create mode 100644 ansible/roles/schulcloud-server-h5p/templates/api-h5p-ingress.yml.j2 diff --git a/ansible/roles/schulcloud-server-core/tasks/main.yml b/ansible/roles/schulcloud-server-core/tasks/main.yml index 7f1bbeeecfe..1b58c8a5413 100644 --- a/ansible/roles/schulcloud-server-core/tasks/main.yml +++ b/ansible/roles/schulcloud-server-core/tasks/main.yml @@ -58,6 +58,13 @@ kubeconfig: ~/.kube/config namespace: "{{ NAMESPACE }}" template: deployment.yml.j2 + + - name: Ingress + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: ingress.yml.j2 + apply: yes - name: FileStorageDeployment kubernetes.core.k8s: @@ -65,6 +72,19 @@ namespace: "{{ NAMESPACE }}" template: api-files-deployment.yml.j2 + - name: FileStorageDeployment + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: api-files-deployment.yml.j2 + + - name: File Storage Ingress + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: api-files-ingress.yml.j2 + apply: yes + - name: FwuLearningContentsDeployment kubernetes.core.k8s: kubeconfig: ~/.kube/config @@ -72,6 +92,14 @@ template: api-fwu-deployment.yml.j2 when: FEATURE_FWU_CONTENT_ENABLED is defined and FEATURE_FWU_CONTENT_ENABLED|bool + - name: Fwu Learning Contents Ingress + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: api-fwu-ingress.yml.j2 + apply: yes + when: FEATURE_FWU_CONTENT_ENABLED is defined and FEATURE_FWU_CONTENT_ENABLED|bool + - name: Delete Files CronJob kubernetes.core.k8s: kubeconfig: ~/.kube/config diff --git a/ansible/roles/schulcloud-server-core/templates/api-files-ingress.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-files-ingress.yml.j2 new file mode 100644 index 00000000000..a1264b52001 --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/api-files-ingress.yml.j2 @@ -0,0 +1,41 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ NAMESPACE }}-api-files-ingress + namespace: {{ NAMESPACE }} + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "{{ TLS_ENABELD|default("false") }}" + nginx.ingress.kubernetes.io/proxy-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + nginx.org/client-max-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + # The following properties added with BC-3606. + # The header size of the request is too big. For e.g. state and the permanent growing jwt. + # Nginx throws away the Location header, resulting in the 502 Bad Gateway. + nginx.ingress.kubernetes.io/client-header-buffer-size: 100k + nginx.ingress.kubernetes.io/http2-max-header-size: 96k + nginx.ingress.kubernetes.io/large-client-header-buffers: 4 100k + nginx.ingress.kubernetes.io/proxy-buffer-size: 96k +{% if CLUSTER_ISSUER is defined %} + cert-manager.io/cluster-issuer: {{ CLUSTER_ISSUER }} +{% endif %} + +spec: + ingressClassName: nginx +{% if CLUSTER_ISSUER is defined or (TLS_ENABELD is defined and TLS_ENABELD|bool) %} + tls: + - hosts: + - {{ DOMAIN }} +{% if CLUSTER_ISSUER is defined %} + secretName: {{ DOMAIN }}-tls +{% endif %} +{% endif %} + rules: + - host: {{ DOMAIN }} + http: + paths: + - path: /api/v3/file/ + backend: + service: + name: api-files-svc + port: + number: {{ PORT_FILE_SERVICE }} + pathType: Prefix diff --git a/ansible/roles/schulcloud-server-core/templates/api-fwu-ingress.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-fwu-ingress.yml.j2 new file mode 100644 index 00000000000..f42c322e45b --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/api-fwu-ingress.yml.j2 @@ -0,0 +1,41 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ NAMESPACE }}-api-fwu-ingress + namespace: {{ NAMESPACE }} + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "{{ TLS_ENABELD|default("false") }}" + nginx.ingress.kubernetes.io/proxy-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + nginx.org/client-max-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + # The following properties added with BC-3606. + # The header size of the request is too big. For e.g. state and the permanent growing jwt. + # Nginx throws away the Location header, resulting in the 502 Bad Gateway. + nginx.ingress.kubernetes.io/client-header-buffer-size: 100k + nginx.ingress.kubernetes.io/http2-max-header-size: 96k + nginx.ingress.kubernetes.io/large-client-header-buffers: 4 100k + nginx.ingress.kubernetes.io/proxy-buffer-size: 96k +{% if CLUSTER_ISSUER is defined %} + cert-manager.io/cluster-issuer: {{ CLUSTER_ISSUER }} +{% endif %} + +spec: + ingressClassName: nginx +{% if CLUSTER_ISSUER is defined or (TLS_ENABELD is defined and TLS_ENABELD|bool) %} + tls: + - hosts: + - {{ DOMAIN }} +{% if CLUSTER_ISSUER is defined %} + secretName: {{ DOMAIN }}-tls +{% endif %} +{% endif %} + rules: + - host: {{ DOMAIN }} + http: + paths: + - path: /api/v3/fwu/ + backend: + service: + name: api-fwu-svc + port: + number: {{ PORT_FWU_LEARNING_CONTENTS }} + pathType: Prefix diff --git a/ansible/roles/schulcloud-server-core/templates/ingress.yml.j2 b/ansible/roles/schulcloud-server-core/templates/ingress.yml.j2 new file mode 100644 index 00000000000..b2dd208765f --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/ingress.yml.j2 @@ -0,0 +1,41 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ NAMESPACE }}-api-ingress + namespace: {{ NAMESPACE }} + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "{{ TLS_ENABELD|default("false") }}" + nginx.ingress.kubernetes.io/proxy-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + nginx.org/client-max-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + # The following properties added with BC-3606. + # The header size of the request is too big. For e.g. state and the permanent growing jwt. + # Nginx throws away the Location header, resulting in the 502 Bad Gateway. + nginx.ingress.kubernetes.io/client-header-buffer-size: 100k + nginx.ingress.kubernetes.io/http2-max-header-size: 96k + nginx.ingress.kubernetes.io/large-client-header-buffers: 4 100k + nginx.ingress.kubernetes.io/proxy-buffer-size: 96k +{% if CLUSTER_ISSUER is defined %} + cert-manager.io/cluster-issuer: {{ CLUSTER_ISSUER }} +{% endif %} + +spec: + ingressClassName: nginx +{% if CLUSTER_ISSUER is defined or (TLS_ENABELD is defined and TLS_ENABELD|bool) %} + tls: + - hosts: + - {{ DOMAIN }} +{% if CLUSTER_ISSUER is defined %} + secretName: {{ DOMAIN }}-tls +{% endif %} +{% endif %} + rules: + - host: {{ DOMAIN }} + http: + paths: + - path: /api/v3/ + backend: + service: + name: api-svc + port: + number: {{ PORT_SERVER }} + pathType: Prefix diff --git a/ansible/roles/schulcloud-server-h5p/tasks/main.yml b/ansible/roles/schulcloud-server-h5p/tasks/main.yml index f630b1f3671..368e97a216e 100644 --- a/ansible/roles/schulcloud-server-h5p/tasks/main.yml +++ b/ansible/roles/schulcloud-server-h5p/tasks/main.yml @@ -11,4 +11,12 @@ namespace: "{{ NAMESPACE }}" template: api-h5p-deployment.yml.j2 when: WITH_H5P_EDITOR is defined and WITH_H5P_EDITOR|bool + + - name: H5p Editor Ingress + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: api-h5p-ingress.yml.j2 + apply: yes + when: WITH_H5P_EDITOR is defined and WITH_H5P_EDITOR|bool \ No newline at end of file diff --git a/ansible/roles/schulcloud-server-h5p/templates/api-h5p-ingress.yml.j2 b/ansible/roles/schulcloud-server-h5p/templates/api-h5p-ingress.yml.j2 new file mode 100644 index 00000000000..ec68641bfa2 --- /dev/null +++ b/ansible/roles/schulcloud-server-h5p/templates/api-h5p-ingress.yml.j2 @@ -0,0 +1,41 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ NAMESPACE }}-api-h5p-ingress + namespace: {{ NAMESPACE }} + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "{{ TLS_ENABELD|default("false") }}" + nginx.ingress.kubernetes.io/proxy-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + nginx.org/client-max-body-size: "{{ INGRESS_MAX_BODY_SIZE|default("2560") }}m" + # The following properties added with BC-3606. + # The header size of the request is too big. For e.g. state and the permanent growing jwt. + # Nginx throws away the Location header, resulting in the 502 Bad Gateway. + nginx.ingress.kubernetes.io/client-header-buffer-size: 100k + nginx.ingress.kubernetes.io/http2-max-header-size: 96k + nginx.ingress.kubernetes.io/large-client-header-buffers: 4 100k + nginx.ingress.kubernetes.io/proxy-buffer-size: 96k +{% if CLUSTER_ISSUER is defined %} + cert-manager.io/cluster-issuer: {{ CLUSTER_ISSUER }} +{% endif %} + +spec: + ingressClassName: nginx +{% if CLUSTER_ISSUER is defined or (TLS_ENABELD is defined and TLS_ENABELD|bool) %} + tls: + - hosts: + - {{ DOMAIN }} +{% if CLUSTER_ISSUER is defined %} + secretName: {{ DOMAIN }}-tls +{% endif %} +{% endif %} + rules: + - host: {{ DOMAIN }} + http: + paths: + - path: /api/v3/h5p-editor/ + backend: + service: + name: api-h5p-svc + port: + number: 4448 + pathType: Prefix