Skip to content

Commit

Permalink
Merge branch 'main' into N21-1285-launch-tool-on-board
Browse files Browse the repository at this point in the history
  • Loading branch information
arnegns authored Nov 3, 2023
2 parents 92b839b + f4e73cd commit 5a5dd87
Show file tree
Hide file tree
Showing 28 changed files with 614 additions and 167 deletions.
8 changes: 5 additions & 3 deletions ansible/roles/schulcloud-server-core/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +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
- name: Fwu Learning Contents Ingress Remove
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
template: api-fwu-ingress.yml.j2
apply: yes
state: absent
api_version: networking.k8s.io/v1
kind: Ingress
name: "{{ NAMESPACE }}-api-fwu-ingress"
when: FEATURE_FWU_CONTENT_ENABLED is defined and FEATURE_FWU_CONTENT_ENABLED|bool

- name: Delete Files CronJob
Expand Down

This file was deleted.

10 changes: 5 additions & 5 deletions apps/server/src/modules/board/board.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { FilesStorageClientModule } from '@modules/files-storage-client';
import { ContextExternalToolModule } from '@modules/tool/context-external-tool';
import { UserModule } from '@modules/user';
import { Module } from '@nestjs/common';
import { ContentElementFactory } from '@shared/domain';
import { ConsoleWriterModule } from '@shared/infra/console';
import { CourseRepo } from '@shared/repo';
import { LoggerModule } from '@src/core/logger';
import { FilesStorageClientModule } from '../files-storage-client';
import { UserModule } from '../user';
import { BoardDoRepo, BoardNodeRepo } from './repo';
import { RecursiveDeleteVisitor } from './repo/recursive-delete.vistor';
import { BoardDoRepo, BoardNodeRepo, RecursiveDeleteVisitor } from './repo';
import {
BoardDoAuthorizableService,
BoardDoService,
Expand All @@ -21,7 +21,7 @@ import { BoardDoCopyService, SchoolSpecificFileCopyServiceFactory } from './serv
import { ColumnBoardCopyService } from './service/column-board-copy.service';

@Module({
imports: [ConsoleWriterModule, FilesStorageClientModule, LoggerModule, UserModule],
imports: [ConsoleWriterModule, FilesStorageClientModule, LoggerModule, UserModule, ContextExternalToolModule],
providers: [
BoardDoAuthorizableService,
BoardDoRepo,
Expand Down
4 changes: 3 additions & 1 deletion apps/server/src/modules/board/repo/board-do.repo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createMock } from '@golevelup/ts-jest';
import { NotFoundError } from '@mikro-orm/core';
import { EntityManager } from '@mikro-orm/mongodb';
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import {
Expand All @@ -26,7 +28,6 @@ import {
richTextElementFactory,
richTextElementNodeFactory,
} from '@shared/testing';
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';
Expand All @@ -46,6 +47,7 @@ describe(BoardDoRepo.name, () => {
BoardNodeRepo,
RecursiveDeleteVisitor,
{ provide: FilesStorageClientAdapterService, useValue: createMock<FilesStorageClientAdapterService>() },
{ provide: ContextExternalToolService, useValue: createMock<ContextExternalToolService>() },
],
}).compile();
repo = module.get(BoardDoRepo);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { EntityManager } from '@mikro-orm/mongodb';
import { FileDto, FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { Test, TestingModule } from '@nestjs/testing';
import { FileRecordParentType } from '@shared/infra/rabbitmq';
import {
columnBoardFactory,
columnFactory,
contextExternalToolFactory,
externalToolElementFactory,
fileElementFactory,
linkElementFactory,
setupEntities,
submissionContainerElementFactory,
submissionItemFactory,
} from '@shared/testing';
import { FileDto, FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { RecursiveDeleteVisitor } from './recursive-delete.vistor';

describe(RecursiveDeleteVisitor.name, () => {
let module: TestingModule;
let service: RecursiveDeleteVisitor;

let em: DeepMocked<EntityManager>;
let filesStorageClientAdapterService: DeepMocked<FilesStorageClientAdapterService>;
let service: RecursiveDeleteVisitor;
let contextExternalToolService: DeepMocked<ContextExternalToolService>;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [
RecursiveDeleteVisitor,
{ provide: EntityManager, useValue: createMock<EntityManager>() },
{ provide: FilesStorageClientAdapterService, useValue: createMock<FilesStorageClientAdapterService>() },
{ provide: ContextExternalToolService, useValue: createMock<ContextExternalToolService>() },
],
}).compile();

service = module.get(RecursiveDeleteVisitor);
em = module.get(EntityManager);
filesStorageClientAdapterService = module.get(FilesStorageClientAdapterService);
service = module.get(RecursiveDeleteVisitor);
contextExternalToolService = module.get(ContextExternalToolService);

await setupEntities();
});

Expand Down Expand Up @@ -212,14 +219,30 @@ describe(RecursiveDeleteVisitor.name, () => {

describe('visitExternalToolElementAsync', () => {
const setup = () => {
const contextExternalTool = contextExternalToolFactory.buildWithId();
const childExternalToolElement = externalToolElementFactory.build();
const externalToolElement = externalToolElementFactory.build({
children: [childExternalToolElement],
contextExternalToolId: contextExternalTool.id,
});

return { externalToolElement, childExternalToolElement };
contextExternalToolService.findById.mockResolvedValue(contextExternalTool);

return {
externalToolElement,
childExternalToolElement,
contextExternalTool,
};
};

it('should delete the context external tool that is linked to the element', async () => {
const { externalToolElement, contextExternalTool } = setup();

await service.visitExternalToolElementAsync(externalToolElement);

expect(contextExternalToolService.deleteContextExternalTool).toHaveBeenCalledWith(contextExternalTool);
});

it('should call entity remove', async () => {
const { externalToolElement, childExternalToolElement } = setup();

Expand Down
16 changes: 13 additions & 3 deletions apps/server/src/modules/board/repo/recursive-delete.vistor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { EntityManager } from '@mikro-orm/mongodb';
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { ContextExternalTool } from '@modules/tool/context-external-tool/domain';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { Injectable } from '@nestjs/common';
import {
AnyBoardDo,
Expand All @@ -14,13 +17,13 @@ import {
SubmissionItem,
} from '@shared/domain';
import { LinkElement } from '@shared/domain/domainobject/board/link-element.do';
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';

@Injectable()
export class RecursiveDeleteVisitor implements BoardCompositeVisitorAsync {
constructor(
private readonly em: EntityManager,
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService,
private readonly contextExternalToolService: ContextExternalToolService
) {}

async visitColumnBoardAsync(columnBoard: ColumnBoard): Promise<void> {
Expand Down Expand Up @@ -67,7 +70,14 @@ export class RecursiveDeleteVisitor implements BoardCompositeVisitorAsync {
}

async visitExternalToolElementAsync(externalToolElement: ExternalToolElement): Promise<void> {
// TODO N21-1296: Delete linked ContextExternalTool
if (externalToolElement.contextExternalToolId) {
const linkedTool: ContextExternalTool = await this.contextExternalToolService.findById(
externalToolElement.contextExternalToolId
);

await this.contextExternalToolService.deleteContextExternalTool(linkedTool);
}

this.deleteNode(externalToolElement);

await this.visitChildrenAsync(externalToolElement);
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/modules/class/domain/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './factory/class.factory';
1 change: 1 addition & 0 deletions apps/server/src/modules/class/entity/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './factory/class.entity.factory';
38 changes: 35 additions & 3 deletions apps/server/src/modules/class/repo/classes.repo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory';
import { Test } from '@nestjs/testing';
import { TestingModule } from '@nestjs/testing/testing-module';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { SchoolEntity } from '@shared/domain';
import { MongoMemoryDatabaseModule } from '@shared/infra/database';
import { cleanupCollections, schoolFactory } from '@shared/testing';
import { classEntityFactory } from '@modules/class/entity/testing/factory/class.entity.factory';
import { Class } from '../domain';
import { ClassEntity } from '../entity';
import { ClassesRepo } from './classes.repo';
Expand Down Expand Up @@ -48,6 +49,7 @@ describe(ClassesRepo.name, () => {
const classes: ClassEntity[] = classEntityFactory.buildListWithId(3, { schoolId: school.id });

await em.persistAndFlush(classes);
em.clear();

return {
school,
Expand Down Expand Up @@ -78,9 +80,13 @@ describe(ClassesRepo.name, () => {
const setup = async () => {
const testUser = new ObjectId();
const class1: ClassEntity = classEntityFactory.withUserIds([testUser, new ObjectId()]).buildWithId();
const class2: ClassEntity = classEntityFactory.withUserIds([testUser, new ObjectId()]).buildWithId();
const class2: ClassEntity = classEntityFactory
.withUserIds([new ObjectId()])
.buildWithId({ teacherIds: [testUser] });
const class3: ClassEntity = classEntityFactory.withUserIds([new ObjectId(), new ObjectId()]).buildWithId();

await em.persistAndFlush([class1, class2, class3]);
em.clear();

return {
class1,
Expand All @@ -104,15 +110,17 @@ describe(ClassesRepo.name, () => {
});

describe('updateMany', () => {
describe('When deleting user data from classes', () => {
describe('when deleting user data from classes', () => {
const setup = async () => {
const testUser1 = new ObjectId();
const testUser2 = new ObjectId();
const testUser3 = new ObjectId();
const class1: ClassEntity = classEntityFactory.withUserIds([testUser1, testUser2]).buildWithId();
const class2: ClassEntity = classEntityFactory.withUserIds([testUser1, testUser3]).buildWithId();
const class3: ClassEntity = classEntityFactory.withUserIds([testUser2, testUser3]).buildWithId();

await em.persistAndFlush([class1, class2, class3]);
em.clear();

return {
class1,
Expand Down Expand Up @@ -144,5 +152,29 @@ describe(ClassesRepo.name, () => {
expect(result3).toHaveLength(2);
});
});

describe('when updating a class that does not exist', () => {
const setup = async () => {
const class1: ClassEntity = classEntityFactory.buildWithId();
const class2: ClassEntity = classEntityFactory.buildWithId();

await em.persistAndFlush([class1]);
em.clear();

return {
class1,
class2,
};
};

it('should throw an error', async () => {
const { class1, class2 } = await setup();

const updatedArray: ClassEntity[] = [class1, class2];
const domainObjectsArray: Class[] = ClassMapper.mapToDOs(updatedArray);

await expect(repo.updateMany(domainObjectsArray)).rejects.toThrow(NotFoundLoggableException);
});
});
});
});
32 changes: 28 additions & 4 deletions apps/server/src/modules/class/repo/classes.repo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { Injectable } from '@nestjs/common';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { EntityId } from '@shared/domain';
import { Class } from '../domain';
import { ClassEntity } from '../entity';
Expand All @@ -18,17 +19,40 @@ export class ClassesRepo {
}

async findAllByUserId(userId: EntityId): Promise<Class[]> {
const classes: ClassEntity[] = await this.em.find(ClassEntity, { userIds: new ObjectId(userId) });
const classes: ClassEntity[] = await this.em.find(ClassEntity, {
$or: [{ userIds: new ObjectId(userId) }, { teacherIds: new ObjectId(userId) }],
});

const mapped: Class[] = ClassMapper.mapToDOs(classes);

return mapped;
}

async updateMany(classes: Class[]): Promise<void> {
const classesEntities = ClassMapper.mapToEntities(classes);
const referencedEntities = classesEntities.map((classEntity) => this.em.getReference(ClassEntity, classEntity.id));
const classMap: Map<string, Class> = new Map<string, Class>(
classes.map((clazz: Class): [string, Class] => [clazz.id, clazz])
);

await this.em.persistAndFlush(referencedEntities);
const existingEntities: ClassEntity[] = await this.em.find(ClassEntity, {
id: { $in: Array.from(classMap.keys()) },
});

if (existingEntities.length < classes.length) {
const missingEntityIds: string[] = Array.from(classMap.keys()).filter(
(classId) => !existingEntities.find((entity) => entity.id === classId)
);

throw new NotFoundLoggableException(Class.name, 'id', missingEntityIds.toString());
}

existingEntities.forEach((entity) => {
const updatedDomainObject: Class | undefined = classMap.get(entity.id);

const updatedEntity: ClassEntity = ClassMapper.mapToEntity(updatedDomainObject as Class);

this.em.assign(entity, updatedEntity);
});

await this.em.persistAndFlush(existingEntities);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Class } from '../../domain';
import { ClassSourceOptions } from '../../domain/class-source-options.do';
import { classFactory } from '../../domain/testing/factory/class.factory';
import { ClassEntity } from '../../entity';
import { classEntityFactory } from '../../entity/testing/factory/class.entity.factory';
import { classEntityFactory } from '../../entity/testing';
import { ClassMapper } from './class.mapper';

describe(ClassMapper.name, () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/modules/class/repo/mapper/class.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ClassMapper {
});
}

private static mapToEntity(domainObject: Class): ClassEntity {
static mapToEntity(domainObject: Class): ClassEntity {
return new ClassEntity({
id: domainObject.id,
name: domainObject.name,
Expand Down
Loading

0 comments on commit 5a5dd87

Please sign in to comment.