From bfec4ab549b6d0fa2d6db27cedec812095a84572 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 10:46:50 +0100 Subject: [PATCH 01/23] add service in task, modify DomainModel interface --- .../deletion-log-statistic.builder.spec.ts | 4 +- .../builder/deletion-log-statistic.builder.ts | 6 +- ...eletion-request-body-props.builder.spec.ts | 4 +- .../deletion-request-body-props.builder.ts | 5 +- ...etion-request-log-response.builder.spec.ts | 4 +- .../deletion-request-log-response.builder.ts | 5 +- .../deletion-target-ref.builder.spec.ts | 4 +- .../builder/deletion-target-ref.builder.ts | 5 +- .../deletion/domain/deletion-log.do.spec.ts | 5 +- .../deletion/domain/deletion-log.do.ts | 8 +-- .../domain/deletion-request.do.spec.ts | 5 +- .../deletion/domain/deletion-request.do.ts | 8 +-- .../testing/factory/deletion-log.factory.ts | 5 +- .../factory/deletion-request.factory.ts | 5 +- .../entity/deletion-log.entity.spec.ts | 5 +- .../deletion/entity/deletion-log.entity.ts | 8 +-- .../entity/deletion-request.entity.spec.ts | 5 +- .../entity/deletion-request.entity.ts | 8 +-- .../factory/deletion-log.entity.factory.ts | 5 +- .../deletion-request.entity.factory.ts | 5 +- .../modules/deletion/interface/interfaces.ts | 11 +--- .../services/deletion-log.service.spec.ts | 7 ++- .../deletion/services/deletion-log.service.ts | 6 +- .../services/deletion-request.service.spec.ts | 5 +- .../services/deletion-request.service.ts | 6 +- .../deletion/uc/deletion-request.uc.spec.ts | 5 +- .../deletion/uc/deletion-request.uc.ts | 62 +++++-------------- .../deletion/uc/interface/interfaces.ts | 8 +-- .../task/builder/domain-operation.builder.ts | 16 +++++ .../src/modules/task/service/task.service.ts | 26 +++++++- .../src/shared/domain/entity/task.entity.ts | 11 +++- .../domain/interface/domain-operation.ts | 9 +++ .../src/shared/domain/interface/index.ts | 1 + .../domain/types/domain.ts} | 3 +- apps/server/src/shared/domain/types/index.ts | 1 + .../server/src/shared/repo/task/task-scope.ts | 8 +++ apps/server/src/shared/repo/task/task.repo.ts | 18 ++++++ 37 files changed, 184 insertions(+), 128 deletions(-) create mode 100644 apps/server/src/modules/task/builder/domain-operation.builder.ts create mode 100644 apps/server/src/shared/domain/interface/domain-operation.ts rename apps/server/src/{modules/deletion/domain/types/deletion-domain-model.enum.ts => shared/domain/types/domain.ts} (87%) diff --git a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.spec.ts b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.spec.ts index babef08f8d9..a8a998e7a4e 100644 --- a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.spec.ts +++ b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.spec.ts @@ -1,4 +1,4 @@ -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogStatisticBuilder } from '.'; describe(DeletionLogStatisticBuilder.name, () => { @@ -8,7 +8,7 @@ describe(DeletionLogStatisticBuilder.name, () => { it('should build generic deletionLogStatistic with all attributes', () => { // Arrange - const domain = DeletionDomainModel.PSEUDONYMS; + const domain = DomainModel.PSEUDONYMS; const modifiedCount = 0; const deletedCount = 2; diff --git a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts index 2e467eed310..88037184bf6 100644 --- a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts +++ b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts @@ -1,8 +1,8 @@ -import { DeletionDomainModel } from '../domain/types'; -import { DeletionLogStatistic } from '../interface'; +import { DomainOperation } from '@shared/domain/interface'; +import { DomainModel } from '@shared/domain/types'; export class DeletionLogStatisticBuilder { - static build(domain: DeletionDomainModel, modifiedCount?: number, deletedCount?: number): DeletionLogStatistic { + static build(domain: DomainModel, modifiedCount?: number, deletedCount?: number): DomainOperation { const deletionLogStatistic = { domain, modifiedCount, deletedCount }; return deletionLogStatistic; diff --git a/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.spec.ts b/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.spec.ts index 4a363d86a40..dcde2f6adb3 100644 --- a/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.spec.ts +++ b/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.spec.ts @@ -1,5 +1,5 @@ import { ObjectId } from 'bson'; -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; import { DeletionRequestBodyPropsBuilder } from './deletion-request-body-props.builder'; describe(DeletionRequestBodyPropsBuilder.name, () => { @@ -8,7 +8,7 @@ describe(DeletionRequestBodyPropsBuilder.name, () => { }); describe('when create deletionRequestBodyParams', () => { const setup = () => { - const domain = DeletionDomainModel.PSEUDONYMS; + const domain = DomainModel.PSEUDONYMS; const refId = new ObjectId().toHexString(); const deleteInMinutes = 1000; return { domain, refId, deleteInMinutes }; diff --git a/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.ts b/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.ts index 2105f7dfc0c..21e00fb7ab0 100644 --- a/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.ts +++ b/apps/server/src/modules/deletion/builder/deletion-request-body-props.builder.ts @@ -1,9 +1,8 @@ -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { DeletionRequestBodyProps } from '../controller/dto'; -import { DeletionDomainModel } from '../domain/types'; export class DeletionRequestBodyPropsBuilder { - static build(domain: DeletionDomainModel, id: EntityId, deleteInMinutes?: number): DeletionRequestBodyProps { + static build(domain: DomainModel, id: EntityId, deleteInMinutes?: number): DeletionRequestBodyProps { const deletionRequestItem = { targetRef: { domain, id }, deleteInMinutes, diff --git a/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.spec.ts b/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.spec.ts index 6d02894b43f..718af3faf2d 100644 --- a/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.spec.ts +++ b/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.spec.ts @@ -1,4 +1,4 @@ -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogStatisticBuilder, DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '.'; describe(DeletionRequestLogResponseBuilder, () => { @@ -8,7 +8,7 @@ describe(DeletionRequestLogResponseBuilder, () => { it('should build generic deletionRequestLog with all attributes', () => { // Arrange - const targetRefDomain = DeletionDomainModel.PSEUDONYMS; + const targetRefDomain = DomainModel.PSEUDONYMS; const targetRefId = '653e4833cc39e5907a1e18d2'; const targetRef = DeletionTargetRefBuilder.build(targetRefDomain, targetRefId); const deletionPlannedAt = new Date(); diff --git a/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.ts b/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.ts index be4b0ba5a96..04dccb52162 100644 --- a/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.ts +++ b/apps/server/src/modules/deletion/builder/deletion-request-log-response.builder.ts @@ -1,11 +1,12 @@ +import { DomainOperation } from '@shared/domain/interface'; import { DeletionRequestLogResponse } from '../controller/dto'; -import { DeletionLogStatistic, DeletionTargetRef } from '../interface'; +import { DeletionTargetRef } from '../interface'; export class DeletionRequestLogResponseBuilder { static build( targetRef: DeletionTargetRef, deletionPlannedAt: Date, - statistics?: DeletionLogStatistic[] + statistics?: DomainOperation[] ): DeletionRequestLogResponse { const deletionRequestLog = { targetRef, deletionPlannedAt, statistics }; diff --git a/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.spec.ts b/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.spec.ts index 4667f290b80..762518d17bd 100644 --- a/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.spec.ts +++ b/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.spec.ts @@ -1,4 +1,4 @@ -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; import { DeletionTargetRefBuilder } from './index'; describe(DeletionTargetRefBuilder.name, () => { @@ -8,7 +8,7 @@ describe(DeletionTargetRefBuilder.name, () => { it('should build generic deletionTargetRef with all attributes', () => { // Arrange - const domain = DeletionDomainModel.PSEUDONYMS; + const domain = DomainModel.PSEUDONYMS; const refId = '653e4833cc39e5907a1e18d2'; const result = DeletionTargetRefBuilder.build(domain, refId); diff --git a/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.ts b/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.ts index d1960a5d4a4..1d1cee14a04 100644 --- a/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.ts +++ b/apps/server/src/modules/deletion/builder/deletion-target-ref.builder.ts @@ -1,9 +1,8 @@ -import { EntityId } from '@shared/domain/types'; -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { DeletionTargetRef } from '../interface'; export class DeletionTargetRefBuilder { - static build(domain: DeletionDomainModel, id: EntityId): DeletionTargetRef { + static build(domain: DomainModel, id: EntityId): DeletionTargetRef { const deletionTargetRef = { domain, id }; return deletionTargetRef; diff --git a/apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts b/apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts index fd320ff79cb..10040956627 100644 --- a/apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts @@ -1,7 +1,8 @@ import { ObjectId } from '@mikro-orm/mongodb'; +import { DomainModel } from '@shared/domain/types'; import { deletionLogFactory } from './testing/factory/deletion-log.factory'; import { DeletionLog } from './deletion-log.do'; -import { DeletionOperationModel, DeletionDomainModel } from './types'; +import { DeletionOperationModel } from './types'; describe(DeletionLog.name, () => { describe('constructor', () => { @@ -35,7 +36,7 @@ describe(DeletionLog.name, () => { const setup = () => { const props = { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, operation: DeletionOperationModel.DELETE, modifiedCount: 0, deletedCount: 1, diff --git a/apps/server/src/modules/deletion/domain/deletion-log.do.ts b/apps/server/src/modules/deletion/domain/deletion-log.do.ts index c5ca2b652d1..0a7c9d08ebf 100644 --- a/apps/server/src/modules/deletion/domain/deletion-log.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.ts @@ -1,11 +1,11 @@ -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; -import { DeletionDomainModel, DeletionOperationModel } from './types'; +import { DeletionOperationModel } from './types'; export interface DeletionLogProps extends AuthorizableObject { createdAt?: Date; updatedAt?: Date; - domain: DeletionDomainModel; + domain: DomainModel; operation?: DeletionOperationModel; modifiedCount?: number; deletedCount?: number; @@ -22,7 +22,7 @@ export class DeletionLog extends DomainObject { return this.props.updatedAt; } - get domain(): DeletionDomainModel { + get domain(): DomainModel { return this.props.domain; } diff --git a/apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts b/apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts index 1ffb7d3f906..2650e894fdc 100644 --- a/apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts @@ -1,6 +1,7 @@ import { ObjectId } from '@mikro-orm/mongodb'; +import { DomainModel } from '@shared/domain/types'; import { DeletionRequest } from './deletion-request.do'; -import { DeletionDomainModel, DeletionStatusModel } from './types'; +import { DeletionStatusModel } from './types'; import { deletionRequestFactory } from './testing/factory/deletion-request.factory'; describe(DeletionRequest.name, () => { @@ -35,7 +36,7 @@ describe(DeletionRequest.name, () => { const setup = () => { const props = { id: new ObjectId().toHexString(), - targetRefDomain: DeletionDomainModel.USER, + targetRefDomain: DomainModel.USER, deleteAfter: new Date(), targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, diff --git a/apps/server/src/modules/deletion/domain/deletion-request.do.ts b/apps/server/src/modules/deletion/domain/deletion-request.do.ts index 76b7f0371ba..92010ef2570 100644 --- a/apps/server/src/modules/deletion/domain/deletion-request.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.ts @@ -1,11 +1,11 @@ -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; -import { DeletionDomainModel, DeletionStatusModel } from './types'; +import { DeletionStatusModel } from './types'; export interface DeletionRequestProps extends AuthorizableObject { createdAt?: Date; updatedAt?: Date; - targetRefDomain: DeletionDomainModel; + targetRefDomain: DomainModel; deleteAfter: Date; targetRefId: EntityId; status: DeletionStatusModel; @@ -20,7 +20,7 @@ export class DeletionRequest extends DomainObject { return this.props.updatedAt; } - get targetRefDomain(): DeletionDomainModel { + get targetRefDomain(): DomainModel { return this.props.targetRefDomain; } diff --git a/apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts b/apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts index 2a3d0529866..2593ba5c242 100644 --- a/apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts +++ b/apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts @@ -1,12 +1,13 @@ import { DoBaseFactory } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLog, DeletionLogProps } from '../../deletion-log.do'; -import { DeletionOperationModel, DeletionDomainModel } from '../../types'; +import { DeletionOperationModel } from '../../types'; export const deletionLogFactory = DoBaseFactory.define(DeletionLog, () => { return { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, operation: DeletionOperationModel.DELETE, modifiedCount: 0, deletedCount: 1, diff --git a/apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts b/apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts index cf1f64daaec..e0ae6e7e41b 100644 --- a/apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts +++ b/apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts @@ -1,8 +1,9 @@ import { DoBaseFactory } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; import { DeepPartial } from 'fishery'; +import { DomainModel } from '@shared/domain/types'; import { DeletionRequest, DeletionRequestProps } from '../../deletion-request.do'; -import { DeletionDomainModel, DeletionStatusModel } from '../../types'; +import { DeletionStatusModel } from '../../types'; class DeletionRequestFactory extends DoBaseFactory { withUserIds(id: string): this { @@ -17,7 +18,7 @@ class DeletionRequestFactory extends DoBaseFactory { return { id: new ObjectId().toHexString(), - targetRefDomain: DeletionDomainModel.USER, + targetRefDomain: DomainModel.USER, deleteAfter: new Date(), targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, diff --git a/apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts b/apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts index c1b5f5f7184..a0524a30a52 100644 --- a/apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts @@ -1,7 +1,8 @@ import { setupEntities } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogEntity } from './deletion-log.entity'; -import { DeletionOperationModel, DeletionDomainModel } from '../domain/types'; +import { DeletionOperationModel } from '../domain/types'; describe(DeletionLogEntity.name, () => { beforeAll(async () => { @@ -13,7 +14,7 @@ describe(DeletionLogEntity.name, () => { const setup = () => { const props = { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, operation: DeletionOperationModel.DELETE, modifiedCount: 0, deletedCount: 1, diff --git a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts index 31ec5447e56..b3862fc5cc7 100644 --- a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts @@ -1,12 +1,12 @@ import { Entity, Index, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { ObjectId } from 'bson'; -import { DeletionDomainModel, DeletionOperationModel } from '../domain/types'; +import { DeletionOperationModel } from '../domain/types'; export interface DeletionLogEntityProps { id?: EntityId; - domain: DeletionDomainModel; + domain: DomainModel; operation?: DeletionOperationModel; modifiedCount?: number; deletedCount?: number; @@ -19,7 +19,7 @@ export interface DeletionLogEntityProps { @Entity({ tableName: 'deletionlogs' }) export class DeletionLogEntity extends BaseEntityWithTimestamps { @Property() - domain: DeletionDomainModel; + domain: DomainModel; @Property({ nullable: true }) operation?: DeletionOperationModel; diff --git a/apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts b/apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts index d4e8440bfa0..273679ef671 100644 --- a/apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts @@ -1,6 +1,7 @@ import { setupEntities } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; -import { DeletionDomainModel, DeletionStatusModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; +import { DeletionStatusModel } from '../domain/types'; import { DeletionRequestEntity } from '.'; describe(DeletionRequestEntity.name, () => { @@ -15,7 +16,7 @@ describe(DeletionRequestEntity.name, () => { const setup = () => { const props = { id: new ObjectId().toHexString(), - targetRefDomain: DeletionDomainModel.USER, + targetRefDomain: DomainModel.USER, deleteAfter: new Date(), targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, diff --git a/apps/server/src/modules/deletion/entity/deletion-request.entity.ts b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts index bff42fac93a..b81835641a9 100644 --- a/apps/server/src/modules/deletion/entity/deletion-request.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts @@ -1,12 +1,12 @@ import { Entity, Index, Property, Unique } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; -import { EntityId } from '@shared/domain/types'; -import { DeletionDomainModel, DeletionStatusModel } from '../domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; +import { DeletionStatusModel } from '../domain/types'; const SECONDS_OF_90_DAYS = 90 * 24 * 60 * 60; export interface DeletionRequestEntityProps { id?: EntityId; - targetRefDomain: DeletionDomainModel; + targetRefDomain: DomainModel; deleteAfter: Date; targetRefId: EntityId; status: DeletionStatusModel; @@ -25,7 +25,7 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { targetRefId!: EntityId; @Property() - targetRefDomain: DeletionDomainModel; + targetRefDomain: DomainModel; @Property() status: DeletionStatusModel; diff --git a/apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts b/apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts index 93ed4198f15..6090f14402d 100644 --- a/apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts +++ b/apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts @@ -1,14 +1,15 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogEntity, DeletionLogEntityProps } from '../../deletion-log.entity'; -import { DeletionOperationModel, DeletionDomainModel } from '../../../domain/types'; +import { DeletionOperationModel } from '../../../domain/types'; export const deletionLogEntityFactory = BaseFactory.define( DeletionLogEntity, () => { return { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, operation: DeletionOperationModel.DELETE, modifiedCount: 0, deletedCount: 1, diff --git a/apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts b/apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts index cc65bb5f4dc..8f33e0d66d6 100644 --- a/apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts +++ b/apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts @@ -1,6 +1,7 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing'; -import { DeletionStatusModel, DeletionDomainModel } from '../../../domain/types'; +import { DomainModel } from '@shared/domain/types'; +import { DeletionStatusModel } from '../../../domain/types'; import { DeletionRequestEntity, DeletionRequestEntityProps } from '../../deletion-request.entity'; export const deletionRequestEntityFactory = BaseFactory.define( @@ -8,7 +9,7 @@ export const deletionRequestEntityFactory = BaseFactory.define { return { id: new ObjectId().toHexString(), - targetRefDomain: DeletionDomainModel.USER, + targetRefDomain: DomainModel.USER, deleteAfter: new Date(), targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, diff --git a/apps/server/src/modules/deletion/interface/interfaces.ts b/apps/server/src/modules/deletion/interface/interfaces.ts index 9d75c1c0da2..7a2621d87b7 100644 --- a/apps/server/src/modules/deletion/interface/interfaces.ts +++ b/apps/server/src/modules/deletion/interface/interfaces.ts @@ -1,13 +1,6 @@ -import { EntityId } from '@shared/domain/types'; -import { DeletionDomainModel } from '../domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; export interface DeletionTargetRef { - domain: DeletionDomainModel; + domain: DomainModel; id: EntityId; } - -export interface DeletionLogStatistic { - domain: DeletionDomainModel; - modifiedCount?: number; - deletedCount?: number; -} diff --git a/apps/server/src/modules/deletion/services/deletion-log.service.spec.ts b/apps/server/src/modules/deletion/services/deletion-log.service.spec.ts index 7b63e866b14..e453ab24419 100644 --- a/apps/server/src/modules/deletion/services/deletion-log.service.spec.ts +++ b/apps/server/src/modules/deletion/services/deletion-log.service.spec.ts @@ -2,8 +2,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { setupEntities } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogRepo } from '../repo'; -import { DeletionDomainModel, DeletionOperationModel } from '../domain/types'; +import { DeletionOperationModel } from '../domain/types'; import { DeletionLogService } from './deletion-log.service'; import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; @@ -47,7 +48,7 @@ describe(DeletionLogService.name, () => { describe('when creating a deletionRequest', () => { const setup = () => { const deletionRequestId = '653e4833cc39e5907a1e18d2'; - const domain = DeletionDomainModel.USER; + const domain = DomainModel.USER; const operation = DeletionOperationModel.DELETE; const modifiedCount = 0; const deletedCount = 1; @@ -82,7 +83,7 @@ describe(DeletionLogService.name, () => { const deletionLog1 = deletionLogFactory.build({ deletionRequestId }); const deletionLog2 = deletionLogFactory.build({ deletionRequestId, - domain: DeletionDomainModel.PSEUDONYMS, + domain: DomainModel.PSEUDONYMS, }); const deletionLogs = [deletionLog1, deletionLog2]; diff --git a/apps/server/src/modules/deletion/services/deletion-log.service.ts b/apps/server/src/modules/deletion/services/deletion-log.service.ts index a06458fe748..577e76a864b 100644 --- a/apps/server/src/modules/deletion/services/deletion-log.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-log.service.ts @@ -1,8 +1,8 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { DeletionLog } from '../domain/deletion-log.do'; -import { DeletionDomainModel, DeletionOperationModel } from '../domain/types'; +import { DeletionOperationModel } from '../domain/types'; import { DeletionLogRepo } from '../repo'; @Injectable() @@ -11,7 +11,7 @@ export class DeletionLogService { async createDeletionLog( deletionRequestId: EntityId, - domain: DeletionDomainModel, + domain: DomainModel, operation: DeletionOperationModel, modifiedCount: number, deletedCount: number diff --git a/apps/server/src/modules/deletion/services/deletion-request.service.spec.ts b/apps/server/src/modules/deletion/services/deletion-request.service.spec.ts index 99763882064..d4675d62861 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.spec.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.spec.ts @@ -2,10 +2,11 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { setupEntities } from '@shared/testing'; +import { DomainModel } from '@shared/domain/types'; import { DeletionRequestService } from './deletion-request.service'; import { DeletionRequestRepo } from '../repo'; import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; -import { DeletionDomainModel, DeletionStatusModel } from '../domain/types'; +import { DeletionStatusModel } from '../domain/types'; describe(DeletionRequestService.name, () => { let module: TestingModule; @@ -47,7 +48,7 @@ describe(DeletionRequestService.name, () => { describe('when creating a deletionRequest', () => { const setup = () => { const targetRefId = '653e4833cc39e5907a1e18d2'; - const targetRefDomain = DeletionDomainModel.USER; + const targetRefDomain = DomainModel.USER; return { targetRefId, targetRefDomain }; }; diff --git a/apps/server/src/modules/deletion/services/deletion-request.service.ts b/apps/server/src/modules/deletion/services/deletion-request.service.ts index d3a41e03c12..08c282a8693 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -1,8 +1,8 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { DeletionRequest } from '../domain/deletion-request.do'; -import { DeletionDomainModel, DeletionStatusModel } from '../domain/types'; +import { DeletionStatusModel } from '../domain/types'; import { DeletionRequestRepo } from '../repo/deletion-request.repo'; @Injectable() @@ -11,7 +11,7 @@ export class DeletionRequestService { async createDeletionRequest( targetRefId: EntityId, - targetRefDomain: DeletionDomainModel, + targetRefDomain: DomainModel, deleteInMinutes = 43200 ): Promise<{ requestId: EntityId; deletionPlannedAt: Date }> { const dateOfDeletion = new Date(); diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts index bcdba26070a..2af692495d9 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -15,7 +15,8 @@ import { LegacyLogger } from '@src/core/logger'; import { ObjectId } from 'bson'; import { RegistrationPinService } from '@modules/registration-pin'; import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; -import { DeletionDomainModel, DeletionStatusModel } from '../domain/types'; +import { DomainModel } from '@shared/domain/types'; +import { DeletionStatusModel } from '../domain/types'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; import { DeletionRequestUc } from './deletion-request.uc'; @@ -148,7 +149,7 @@ describe(DeletionRequestUc.name, () => { const setup = () => { const deletionRequestToCreate: DeletionRequestBodyProps = { targetRef: { - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, id: new ObjectId().toHexString(), }, deleteInMinutes: 1440, diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts index 7d705b23b09..76f4bdc386f 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -10,14 +10,14 @@ import { RocketChatUserService } from '@modules/rocketchat-user'; import { TeamService } from '@modules/teams'; import { UserService } from '@modules/user'; import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain/types'; +import { DomainModel, EntityId } from '@shared/domain/types'; import { LegacyLogger } from '@src/core/logger'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { DeletionLogStatisticBuilder, DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '../builder'; import { DeletionRequestBodyProps, DeletionRequestLogResponse, DeletionRequestResponse } from '../controller/dto'; import { DeletionLogStatistic } from './interface/interfaces'; import { DeletionRequest, DeletionLog } from '../domain'; -import { DeletionDomainModel, DeletionOperationModel, DeletionStatusModel } from '../domain/types'; +import { DeletionOperationModel, DeletionStatusModel } from '../domain/types'; import { DeletionRequestService, DeletionLogService } from '../services'; @Injectable() @@ -120,7 +120,7 @@ export class DeletionRequestUc { private async logDeletion( deletionRequest: DeletionRequest, - domainModel: DeletionDomainModel, + domainModel: DomainModel, operationModel: DeletionOperationModel, updatedCount: number, deletedCount: number @@ -140,7 +140,7 @@ export class DeletionRequestUc { this.logger.debug({ action: 'removeAccount', deletionRequest }); await this.accountService.deleteByUserId(deletionRequest.targetRefId); - await this.logDeletion(deletionRequest, DeletionDomainModel.ACCOUNT, DeletionOperationModel.DELETE, 0, 1); + await this.logDeletion(deletionRequest, DomainModel.ACCOUNT, DeletionOperationModel.DELETE, 0, 1); } private async removeUserRegistrationPin(deletionRequest: DeletionRequest) { @@ -155,7 +155,7 @@ export class DeletionRequestUc { await this.logDeletion( deletionRequest, - DeletionDomainModel.REGISTRATIONPIN, + DomainModel.REGISTRATIONPIN, DeletionOperationModel.DELETE, 0, deletedRegistrationPin @@ -166,13 +166,7 @@ export class DeletionRequestUc { this.logger.debug({ action: 'removeUserFromClasses', deletionRequest }); const classesUpdated: number = await this.classService.deleteUserDataFromClasses(deletionRequest.targetRefId); - await this.logDeletion( - deletionRequest, - DeletionDomainModel.CLASS, - DeletionOperationModel.UPDATE, - classesUpdated, - 0 - ); + await this.logDeletion(deletionRequest, DomainModel.CLASS, DeletionOperationModel.UPDATE, classesUpdated, 0); } private async removeUserFromCourseGroup(deletionRequest: DeletionRequest) { @@ -183,7 +177,7 @@ export class DeletionRequestUc { ); await this.logDeletion( deletionRequest, - DeletionDomainModel.COURSEGROUP, + DomainModel.COURSEGROUP, DeletionOperationModel.UPDATE, courseGroupUpdated, 0 @@ -194,26 +188,14 @@ export class DeletionRequestUc { this.logger.debug({ action: 'removeUserFromCourse', deletionRequest }); const courseUpdated: number = await this.courseService.deleteUserDataFromCourse(deletionRequest.targetRefId); - await this.logDeletion( - deletionRequest, - DeletionDomainModel.COURSE, - DeletionOperationModel.UPDATE, - courseUpdated, - 0 - ); + await this.logDeletion(deletionRequest, DomainModel.COURSE, DeletionOperationModel.UPDATE, courseUpdated, 0); } private async removeUsersDashboard(deletionRequest: DeletionRequest) { this.logger.debug({ action: 'removeUsersDashboard', deletionRequest }); const dashboardDeleted: number = await this.dashboardService.deleteDashboardByUserId(deletionRequest.targetRefId); - await this.logDeletion( - deletionRequest, - DeletionDomainModel.DASHBOARD, - DeletionOperationModel.DELETE, - 0, - dashboardDeleted - ); + await this.logDeletion(deletionRequest, DomainModel.DASHBOARD, DeletionOperationModel.DELETE, 0, dashboardDeleted); } private async removeUsersFilesAndPermissions(deletionRequest: DeletionRequest) { @@ -225,7 +207,7 @@ export class DeletionRequestUc { ); await this.logDeletion( deletionRequest, - DeletionDomainModel.FILE, + DomainModel.FILE, DeletionOperationModel.UPDATE, filesDeleted + filePermissionsUpdated, 0 @@ -241,7 +223,7 @@ export class DeletionRequestUc { await this.logDeletion( deletionRequest, - DeletionDomainModel.FILERECORDS, + DomainModel.FILERECORDS, DeletionOperationModel.UPDATE, fileRecordsUpdated, 0 @@ -252,40 +234,28 @@ export class DeletionRequestUc { this.logger.debug({ action: 'removeUserFromLessons', deletionRequest }); const lessonsUpdated: number = await this.lessonService.deleteUserDataFromLessons(deletionRequest.targetRefId); - await this.logDeletion( - deletionRequest, - DeletionDomainModel.LESSONS, - DeletionOperationModel.UPDATE, - lessonsUpdated, - 0 - ); + await this.logDeletion(deletionRequest, DomainModel.LESSONS, DeletionOperationModel.UPDATE, lessonsUpdated, 0); } private async removeUsersPseudonyms(deletionRequest: DeletionRequest) { this.logger.debug({ action: 'removeUsersPseudonyms', deletionRequest }); const pseudonymDeleted: number = await this.pseudonymService.deleteByUserId(deletionRequest.targetRefId); - await this.logDeletion( - deletionRequest, - DeletionDomainModel.PSEUDONYMS, - DeletionOperationModel.DELETE, - 0, - pseudonymDeleted - ); + await this.logDeletion(deletionRequest, DomainModel.PSEUDONYMS, DeletionOperationModel.DELETE, 0, pseudonymDeleted); } private async removeUserFromTeams(deletionRequest: DeletionRequest) { this.logger.debug({ action: ' removeUserFromTeams', deletionRequest }); const teamsUpdated: number = await this.teamService.deleteUserDataFromTeams(deletionRequest.targetRefId); - await this.logDeletion(deletionRequest, DeletionDomainModel.TEAMS, DeletionOperationModel.UPDATE, teamsUpdated, 0); + await this.logDeletion(deletionRequest, DomainModel.TEAMS, DeletionOperationModel.UPDATE, teamsUpdated, 0); } private async removeUser(deletionRequest: DeletionRequest) { this.logger.debug({ action: 'removeUser', deletionRequest }); const userDeleted: number = await this.userService.deleteUser(deletionRequest.targetRefId); - await this.logDeletion(deletionRequest, DeletionDomainModel.USER, DeletionOperationModel.DELETE, 0, userDeleted); + await this.logDeletion(deletionRequest, DomainModel.USER, DeletionOperationModel.DELETE, 0, userDeleted); } private async removeUserFromRocketChat(deletionRequest: DeletionRequest) { @@ -299,7 +269,7 @@ export class DeletionRequestUc { ]); await this.logDeletion( deletionRequest, - DeletionDomainModel.ROCKETCHATUSER, + DomainModel.ROCKETCHATUSER, DeletionOperationModel.DELETE, 0, rocketChatUserDeleted diff --git a/apps/server/src/modules/deletion/uc/interface/interfaces.ts b/apps/server/src/modules/deletion/uc/interface/interfaces.ts index 57275b5b530..004484c20b2 100644 --- a/apps/server/src/modules/deletion/uc/interface/interfaces.ts +++ b/apps/server/src/modules/deletion/uc/interface/interfaces.ts @@ -1,8 +1,8 @@ import { EntityId } from '@shared/domain/types'; -import { DeletionDomainModel } from '../../domain/types/deletion-domain-model.enum'; +import { DomainModel } from '@shared/domain/types/domain'; export interface DeletionTargetRef { - targetRefDomain: DeletionDomainModel; + targetRefDomain: DomainModel; targetRefId: EntityId; } @@ -13,13 +13,13 @@ export interface DeletionRequestLog { } export interface DeletionLogStatistic { - domain: DeletionDomainModel; + domain: DomainModel; modifiedCount?: number; deletedCount?: number; } export interface DeletionRequestProps { - targetRef: { targetRefDoamin: DeletionDomainModel; targetRefId: EntityId }; + targetRef: { targetRefDoamin: DomainModel; targetRefId: EntityId }; deleteInMinutes?: number; } diff --git a/apps/server/src/modules/task/builder/domain-operation.builder.ts b/apps/server/src/modules/task/builder/domain-operation.builder.ts new file mode 100644 index 00000000000..46ffb9b3341 --- /dev/null +++ b/apps/server/src/modules/task/builder/domain-operation.builder.ts @@ -0,0 +1,16 @@ +import { DomainOperation } from '@shared/domain/interface'; +import { DomainModel } from '@shared/domain/types'; + +export class DomainOperationBuilder { + static build( + domain: DomainModel, + modifiedCount?: number, + deletedCount?: number, + modifiedRef?: string[], + deletedRef?: string[] + ): DomainOperation { + const domainOperation = { domain, modifiedCount, deletedCount, modifiedRef, deletedRef }; + + return domainOperation; + } +} diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 3af74484604..4b69f3a46ef 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -1,10 +1,11 @@ import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { Injectable } from '@nestjs/common'; import { Task } from '@shared/domain/entity'; -import { IFindOptions } from '@shared/domain/interface'; -import { Counted, EntityId } from '@shared/domain/types'; +import { DomainOperation, IFindOptions } from '@shared/domain/interface'; +import { Counted, DomainModel, EntityId } from '@shared/domain/types'; import { TaskRepo } from '@shared/repo'; import { SubmissionService } from './submission.service'; +import { DomainOperationBuilder } from '../builder/domain-operation.builder'; @Injectable() export class TaskService { @@ -23,6 +24,27 @@ export class TaskService { return this.taskRepo.findBySingleParent(creatorId, courseId, filters, options); } + async removeCreatorId(creatorId: EntityId): Promise { + const [tasksByOnlyCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); + + const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); + + await Promise.all(promiseDeletedTasks); + + const [tasksByCreatorIdWithCoursesAndLessons] = await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); + + tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); + await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); + + // TODO add array with reference od object dleteed and updated + + return DomainOperationBuilder.build( + DomainModel.TASK, + promiseDeletedTasks.length, + tasksByCreatorIdWithCoursesAndLessons.length + ); + } + async delete(task: Task): Promise { await this.filesStorageClientAdapterService.deleteFilesOfParent(task.id); diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index 88af11a0a7d..05380980fc0 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -68,7 +68,7 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, @Index() @ManyToOne('User', { fieldName: 'teacherId' }) - creator: User; + creator?: User; @Index() @ManyToOne('Course', { fieldName: 'courseId', nullable: true }) @@ -128,7 +128,7 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, return finishedIds; } - private getParent(): TaskParent | User { + private getParent(): TaskParent | User | undefined { const parent = this.lesson || this.course || this.creator; return parent; @@ -136,6 +136,9 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, private getMaxSubmissions(): number { const parent = this.getParent(); + if (parent === undefined) { + return 0; + } // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result const maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; @@ -321,6 +324,10 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, public unpublish(): void { this.private = true; } + + public removeCreatorId(): void { + this.creator = undefined; + } } export function isTask(reference: unknown): reference is Task { diff --git a/apps/server/src/shared/domain/interface/domain-operation.ts b/apps/server/src/shared/domain/interface/domain-operation.ts new file mode 100644 index 00000000000..c5288c64e6d --- /dev/null +++ b/apps/server/src/shared/domain/interface/domain-operation.ts @@ -0,0 +1,9 @@ +import { DomainModel } from '../types'; + +export interface DomainOperation { + domain: DomainModel; + modifiedCount?: number; + deletedCount?: number; + modifiedRef?: string[]; + deletedRef?: string[]; +} diff --git a/apps/server/src/shared/domain/interface/index.ts b/apps/server/src/shared/domain/interface/index.ts index 6c6b3f9d2db..72b97b4fa75 100644 --- a/apps/server/src/shared/domain/interface/index.ts +++ b/apps/server/src/shared/domain/interface/index.ts @@ -5,3 +5,4 @@ export * from './learnroom'; export * from './permission.enum'; export * from './rolename.enum'; export * from './video-conference-scope.enum'; +export * from './domain-operation'; diff --git a/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts b/apps/server/src/shared/domain/types/domain.ts similarity index 87% rename from apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts rename to apps/server/src/shared/domain/types/domain.ts index 922187e2a16..babc23631d1 100644 --- a/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts +++ b/apps/server/src/shared/domain/types/domain.ts @@ -1,4 +1,4 @@ -export const enum DeletionDomainModel { +export const enum DomainModel { ACCOUNT = 'account', CLASS = 'class', COURSEGROUP = 'courseGroup', @@ -10,6 +10,7 @@ export const enum DeletionDomainModel { PSEUDONYMS = 'pseudonyms', REGISTRATIONPIN = 'registrationPin', ROCKETCHATUSER = 'rocketChatUser', + TASK = 'task', TEAMS = 'teams', USER = 'user', } diff --git a/apps/server/src/shared/domain/types/index.ts b/apps/server/src/shared/domain/types/index.ts index b47a0d4821b..e591c94c825 100644 --- a/apps/server/src/shared/domain/types/index.ts +++ b/apps/server/src/shared/domain/types/index.ts @@ -10,3 +10,4 @@ export * from './school-purpose.enum'; export * from './system.type'; export * from './task.types'; export * from './value-of'; +export * from './domain'; diff --git a/apps/server/src/shared/repo/task/task-scope.ts b/apps/server/src/shared/repo/task/task-scope.ts index 200c40b0aad..0b6446f0953 100644 --- a/apps/server/src/shared/repo/task/task-scope.ts +++ b/apps/server/src/shared/repo/task/task-scope.ts @@ -28,6 +28,14 @@ export class TaskScope extends Scope { return this; } + byCreatorIdWithCourseAndLesson(creatorId: EntityId): TaskScope { + this.addQuery({ + $and: [{ creator: creatorId }, { $or: [{ course: { $ne: null } }, { lesson: { $ne: null } }] }], + }); + + return this; + } + byCourseIds(courseIds: EntityId[]): TaskScope { this.addQuery({ $and: [{ course: { $in: courseIds } }, { lesson: null }], diff --git a/apps/server/src/shared/repo/task/task.repo.ts b/apps/server/src/shared/repo/task/task.repo.ts index 094d21a8dc5..5afc052ef8d 100644 --- a/apps/server/src/shared/repo/task/task.repo.ts +++ b/apps/server/src/shared/repo/task/task.repo.ts @@ -187,6 +187,24 @@ export class TaskRepo extends BaseRepo { return countedTaskList; } + async findByOnlyCreatorId(creatorId: EntityId): Promise> { + const scope = new TaskScope(); + scope.byOnlyCreatorId(creatorId); + + const countedTaskList = await this.findTasksAndCount(scope.query); + + return countedTaskList; + } + + async findByCreatorIdWithCourseAndLesson(creatorId: EntityId): Promise> { + const scope = new TaskScope(); + scope.byCreatorIdWithCourseAndLesson(creatorId); + + const countedTaskList = await this.findTasksAndCount(scope.query); + + return countedTaskList; + } + private async findTasksAndCount(query: FilterQuery, options?: IFindOptions): Promise> { const pagination = options?.pagination || {}; const order = options?.order || {}; From cefceb2b0324cdacc3d9cde87523379a19a70f77 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 12:49:26 +0100 Subject: [PATCH 02/23] add test for domainOperatioNuilder, taskEntity --- .../builder/domain-operation.builder.spec.ts | 22 +++++++++++++++++++ .../src/modules/task/service/task.service.ts | 2 -- .../shared/domain/entity/task.entity.spec.ts | 19 ++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/modules/task/builder/domain-operation.builder.spec.ts diff --git a/apps/server/src/modules/task/builder/domain-operation.builder.spec.ts b/apps/server/src/modules/task/builder/domain-operation.builder.spec.ts new file mode 100644 index 00000000000..4e0eea6b9fa --- /dev/null +++ b/apps/server/src/modules/task/builder/domain-operation.builder.spec.ts @@ -0,0 +1,22 @@ +import { DomainModel } from '@shared/domain/types'; +import { DomainOperationBuilder } from './domain-operation.builder'; + +describe(DomainOperationBuilder.name, () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should build generic domainOperation with all attributes', () => { + // Arrange + const domain = DomainModel.PSEUDONYMS; + const modifiedCount = 0; + const deletedCount = 2; + + const result = DomainOperationBuilder.build(domain, modifiedCount, deletedCount); + + // Assert + expect(result.domain).toEqual(domain); + expect(result.modifiedCount).toEqual(modifiedCount); + expect(result.deletedCount).toEqual(deletedCount); + }); +}); diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 4b69f3a46ef..7eb468dbc78 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -36,8 +36,6 @@ export class TaskService { tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); - // TODO add array with reference od object dleteed and updated - return DomainOperationBuilder.build( DomainModel.TASK, promiseDeletedTasks.length, diff --git a/apps/server/src/shared/domain/entity/task.entity.spec.ts b/apps/server/src/shared/domain/entity/task.entity.spec.ts index 8f0d1ebd278..9a50aedce2a 100644 --- a/apps/server/src/shared/domain/entity/task.entity.spec.ts +++ b/apps/server/src/shared/domain/entity/task.entity.spec.ts @@ -866,4 +866,23 @@ describe('Task Entity', () => { expect(schoolId).toEqual(school.id); }); }); + + describe('removeCreatorId is called', () => { + describe('WHEN creatorId exists', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const task = taskFactory.buildWithId({ creator: user }); + + return { task }; + }; + + it('should set it to undefined', () => { + const { task } = setup(); + + const result = task.removeCreatorId(); + + expect(result).toBe(undefined); + }); + }); + }); }); From 6a1cf116f3df33b6dfbdebdc98ffcf6f019e5003 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 12:59:34 +0100 Subject: [PATCH 03/23] fix in deletion module imports&exports --- .../deletion/controller/dto/deletion-request-log.response.ts | 5 +++-- apps/server/src/modules/deletion/domain/types/index.ts | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.ts b/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.ts index 3619bebace8..e0b5d1546fe 100644 --- a/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.ts +++ b/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; -import { DeletionLogStatistic, DeletionTargetRef } from '../../interface'; +import { DomainOperation } from '@shared/domain/interface'; +import { DeletionTargetRef } from '../../interface'; export class DeletionRequestLogResponse { @ApiProperty() @@ -11,7 +12,7 @@ export class DeletionRequestLogResponse { @ApiProperty() @IsOptional() - statistics?: DeletionLogStatistic[]; + statistics?: DomainOperation[]; constructor(response: DeletionRequestLogResponse) { this.targetRef = response.targetRef; diff --git a/apps/server/src/modules/deletion/domain/types/index.ts b/apps/server/src/modules/deletion/domain/types/index.ts index d1f4de8eb6b..607e7fbe5e5 100644 --- a/apps/server/src/modules/deletion/domain/types/index.ts +++ b/apps/server/src/modules/deletion/domain/types/index.ts @@ -1,3 +1,2 @@ -export * from './deletion-domain-model.enum'; export * from './deletion-operation-model.enum'; export * from './deletion-status-model.enum'; From daa7b46eaff561f92b01490dd803d6d346cbef2e Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 13:31:43 +0100 Subject: [PATCH 04/23] fix in deletionController --- .../controller/api-test/deletion-request-create.api.spec.ts | 6 +++--- .../controller/dto/deletion-request-log.response.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/deletion/controller/api-test/deletion-request-create.api.spec.ts b/apps/server/src/modules/deletion/controller/api-test/deletion-request-create.api.spec.ts index 3616cac13dc..22814bf8590 100644 --- a/apps/server/src/modules/deletion/controller/api-test/deletion-request-create.api.spec.ts +++ b/apps/server/src/modules/deletion/controller/api-test/deletion-request-create.api.spec.ts @@ -5,8 +5,8 @@ import { AuthGuard } from '@nestjs/passport'; import { EntityManager } from '@mikro-orm/mongodb'; import { TestXApiKeyClient } from '@shared/testing'; import { AdminApiServerTestModule } from '@modules/server/admin-api.server.module'; +import { DomainModel } from '@shared/domain/types'; import { DeletionRequestBodyProps, DeletionRequestResponse } from '../dto'; -import { DeletionDomainModel } from '../../domain/types'; import { DeletionRequestEntity } from '../../entity'; const baseRouteName = '/deletionRequests'; @@ -86,14 +86,14 @@ describe(`deletionRequest create (api)`, () => { const setup = () => { const deletionRequestToCreate: DeletionRequestBodyProps = { targetRef: { - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, id: '653e4833cc39e5907a1e18d2', }, }; const deletionRequestToImmediateRemoval: DeletionRequestBodyProps = { targetRef: { - domain: DeletionDomainModel.USER, + domain: DomainModel.USER, id: '653e4833cc39e5907a1e18d2', }, deleteInMinutes: 0, diff --git a/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.spec.ts b/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.spec.ts index 5036a0d39e0..be91c23b06c 100644 --- a/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.spec.ts +++ b/apps/server/src/modules/deletion/controller/dto/deletion-request-log.response.spec.ts @@ -1,5 +1,5 @@ import { ObjectId } from 'bson'; -import { DeletionDomainModel } from '../../domain/types'; +import { DomainModel } from '@shared/domain/types'; import { DeletionLogStatisticBuilder, DeletionTargetRefBuilder } from '../../builder'; import { DeletionRequestLogResponse } from './index'; @@ -7,7 +7,7 @@ describe(DeletionRequestLogResponse.name, () => { describe('constructor', () => { describe('when passed properties', () => { const setup = () => { - const targetRefDomain = DeletionDomainModel.PSEUDONYMS; + const targetRefDomain = DomainModel.PSEUDONYMS; const targetRefId = new ObjectId().toHexString(); const targetRef = DeletionTargetRefBuilder.build(targetRefDomain, targetRefId); const deletionPlannedAt = new Date(); From f181ba4afeaed4f76cc681522086ad0ffeb2694b Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 14:01:17 +0100 Subject: [PATCH 05/23] add tests in taskRepo --- .../repo/task/task.repo.integration.spec.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/apps/server/src/shared/repo/task/task.repo.integration.spec.ts b/apps/server/src/shared/repo/task/task.repo.integration.spec.ts index ea3b486c399..6becd31c010 100644 --- a/apps/server/src/shared/repo/task/task.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/task/task.repo.integration.spec.ts @@ -2105,4 +2105,56 @@ describe('TaskRepo', () => { }).rejects.toThrow(); }); }); + + describe('findByOnlyCreatorId', () => { + describe('when searching by creatorId', () => { + const setup = async () => { + const creator = userFactory.build(); + const course = courseFactory.build({ teachers: [creator] }); + const task = taskFactory.build({ creator }); + const taskWithCourse = taskFactory.build({ course, creator }); + + await em.persistAndFlush([task, taskWithCourse]); + em.clear(); + + return { creator }; + }; + + it('should find task where is only creator', async () => { + const { creator } = await setup(); + + const [result] = await repo.findByOnlyCreatorId(creator.id); + + expect(result).toHaveLength(1); + }); + }); + }); + + describe('findByCreatorIdWithCourseAndLesson', () => { + describe('when searching by creatorId', () => { + const setup = async () => { + const creator = userFactory.build(); + const task = taskFactory.build({ creator }); + + const course = courseFactory.build({ teachers: [creator] }); + const taskWithCourse = taskFactory.build({ course, creator }); + + const lesson = lessonFactory.build({ course }); + const taskWithCourseAndLesson = taskFactory.build({ course, creator, lesson }); + + await em.persistAndFlush([task, taskWithCourse, taskWithCourseAndLesson]); + em.clear(); + + return { creator }; + }; + + it('should find task where are lesson or course', async () => { + const { creator } = await setup(); + + const [result] = await repo.findByCreatorIdWithCourseAndLesson(creator.id); + + expect(result).toHaveLength(2); + }); + }); + }); }); From 93799cf501e9ba938d4f2d861efe71c7b8ed57ab Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 27 Dec 2023 14:28:40 +0100 Subject: [PATCH 06/23] add test for taskScope --- .../src/shared/repo/task/task-scope.spec.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 apps/server/src/shared/repo/task/task-scope.spec.ts diff --git a/apps/server/src/shared/repo/task/task-scope.spec.ts b/apps/server/src/shared/repo/task/task-scope.spec.ts new file mode 100644 index 00000000000..9fe21d66fd0 --- /dev/null +++ b/apps/server/src/shared/repo/task/task-scope.spec.ts @@ -0,0 +1,47 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { FilterQuery } from '@mikro-orm/core'; +import { Task } from '@shared/domain/entity'; +import { EmptyResultQuery } from '../query'; +import { TaskScope } from './task-scope'; + +describe(TaskScope.name, () => { + describe('when build scope query', () => { + const setup = () => { + const scope = new TaskScope(); + const creatorId = new ObjectId().toHexString(); + + const expected = { + $and: [ + { + creator: creatorId, + }, + { + $or: [ + { + course: { $ne: null }, + }, + { + lesson: { $ne: null }, + }, + ], + }, + ], + } as FilterQuery; + + return { scope, creatorId, expected }; + }; + it('should create valid query returning no results for empty scope', () => { + const { scope } = setup(); + const result = scope.query; + + expect(result).toBe(EmptyResultQuery); + }); + it('should create correct query for byCreatorIdWithCourseAndLesson', () => { + const { scope, creatorId, expected } = setup(); + scope.byCreatorIdWithCourseAndLesson(creatorId); + const result = scope.query; + + expect(JSON.stringify(result)).toBe(JSON.stringify(expected)); + }); + }); +}); From a5f42885538d21cda91aa8e9d93b891a42892fdf Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 28 Dec 2023 11:26:11 +0100 Subject: [PATCH 07/23] add test for taskService --- .../modules/task/service/task.service.spec.ts | 94 ++++++++++++++++++- .../src/modules/task/service/task.service.ts | 16 ++-- 2 files changed, 101 insertions(+), 9 deletions(-) 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 4cf950964bd..bcd8c9e9b48 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -1,10 +1,12 @@ 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 { courseFactory, setupEntities, submissionFactory, taskFactory, userFactory } from '@shared/testing'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { DomainModel } from '@shared/domain/types'; import { SubmissionService } from './submission.service'; import { TaskService } from './task.service'; +import { DomainOperationBuilder } from '../builder/domain-operation.builder'; describe('TaskService', () => { let module: TestingModule; @@ -102,4 +104,94 @@ describe('TaskService', () => { expect(taskRepo.delete).toBeCalledWith(task); }); }); + + describe('removeCreatorId', () => { + describe('when task has only user as parent', () => { + const setup = () => { + const creator = userFactory.buildWithId(); + const taskWithoutCourse = taskFactory.buildWithId({ creator }); + + taskRepo.findByOnlyCreatorId.mockResolvedValue([[taskWithoutCourse], 1]); + taskRepo.findByCreatorIdWithCourseAndLesson.mockResolvedValue([[], 0]); + + const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + + return { creator, expectedResult }; + }; + + it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { + const { creator } = setup(); + + await taskService.removeCreatorId(creator.id); + + expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); + }); + + it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { + const { creator } = setup(); + + await taskService.removeCreatorId(creator.id); + + expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); + }); + + it('should return the object with information on the actions performed', async () => { + const { creator, expectedResult } = setup(); + + const result = await taskService.removeCreatorId(creator.id); + + expect(result).toEqual(expectedResult); + }); + }); + + describe('when tasks where user is parent, and when task has course', () => { + const setup = () => { + const creator = userFactory.buildWithId(); + const taskWithoutCourse = taskFactory.buildWithId({ creator }); + + const course = courseFactory.build(); + const taskWithCourse = taskFactory.buildWithId({ creator, course }); + + taskRepo.findByOnlyCreatorId.mockResolvedValue([[taskWithoutCourse], 1]); + taskRepo.findByCreatorIdWithCourseAndLesson.mockResolvedValue([[taskWithCourse], 1]); + + const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 1); + const taskWithCourseToUpdate = { ...taskWithCourse, creator: undefined }; + + return { creator, expectedResult, taskWithCourseToUpdate }; + }; + + it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { + const { creator } = setup(); + + await taskService.removeCreatorId(creator.id); + + expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); + }); + + it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { + const { creator } = setup(); + + await taskService.removeCreatorId(creator.id); + + expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); + }); + + it('should call taskRepo.save with task to update', async () => { + const { creator, taskWithCourseToUpdate } = setup(); + + await taskService.removeCreatorId(creator.id); + + expect(taskRepo.save).toBeCalledWith([taskWithCourseToUpdate]); + }); + + it('should return the object with information on the actions performed', async () => { + const { creator, expectedResult } = setup(); + + const result = await taskService.removeCreatorId(creator.id); + + expect(result).toEqual(expectedResult); + }); + }); + }); }); diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 7eb468dbc78..f0b44b0a125 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -31,16 +31,16 @@ export class TaskService { await Promise.all(promiseDeletedTasks); - const [tasksByCreatorIdWithCoursesAndLessons] = await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); + const [tasksByCreatorIdWithCoursesAndLessons, count] = await this.taskRepo.findByCreatorIdWithCourseAndLesson( + creatorId + ); - tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); - await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); + if (count > 0) { + tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); + await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); + } - return DomainOperationBuilder.build( - DomainModel.TASK, - promiseDeletedTasks.length, - tasksByCreatorIdWithCoursesAndLessons.length - ); + return DomainOperationBuilder.build(DomainModel.TASK, promiseDeletedTasks.length, count); } async delete(task: Task): Promise { From 0e9c89e36b455f61ee9e2a82970ba112541658de Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 28 Dec 2023 12:05:34 +0100 Subject: [PATCH 08/23] fixes in Task entity --- apps/server/src/shared/domain/entity/task.entity.ts | 9 ++++----- apps/server/src/shared/domain/types/task.types.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index 05380980fc0..50ffdf4e7d0 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -98,7 +98,6 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, this.dueDate = props.dueDate; if (props.private !== undefined) this.private = props.private; - this.creator = props.creator; this.course = props.course; this.school = props.school; this.lesson = props.lesson; @@ -136,11 +135,11 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, private getMaxSubmissions(): number { const parent = this.getParent(); - if (parent === undefined) { - return 0; + let maxSubmissions = 0; + if (parent) { + // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result + maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; } - // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result - const maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; return maxSubmissions; } diff --git a/apps/server/src/shared/domain/types/task.types.ts b/apps/server/src/shared/domain/types/task.types.ts index c9ce4ebb698..b10e32f5f2d 100644 --- a/apps/server/src/shared/domain/types/task.types.ts +++ b/apps/server/src/shared/domain/types/task.types.ts @@ -22,7 +22,7 @@ export interface TaskCreate extends ITask { export interface TaskProperties extends ITask { course?: Course; lesson?: LessonEntity; - creator: User; + creator?: User; school: SchoolEntity; finished?: User[]; private?: boolean; From 24acc6d84f207fdf129f982b1ada93dea3b7d79f Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 28 Dec 2023 13:05:56 +0100 Subject: [PATCH 09/23] connect deletion module with taskService --- .../builder/deletion-log-statistic.builder.ts | 10 +++- .../modules/deletion/deletion-api.module.ts | 2 + .../deletion/domain/deletion-log.do.ts | 8 ++-- .../deletion/entity/deletion-log.entity.ts | 22 ++++----- .../deletion/uc/deletion-request.uc.spec.ts | 26 +++++++++-- .../deletion/uc/deletion-request.uc.ts | 46 +++++++++++++------ .../task/builder/domain-operation.builder.ts | 4 +- apps/server/src/modules/task/builder/index.ts | 1 + .../src/modules/task/service/task.service.ts | 15 +++--- .../domain/interface/domain-operation.ts | 4 +- 10 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 apps/server/src/modules/task/builder/index.ts diff --git a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts index 88037184bf6..fa0680b8500 100644 --- a/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts +++ b/apps/server/src/modules/deletion/builder/deletion-log-statistic.builder.ts @@ -2,8 +2,14 @@ import { DomainOperation } from '@shared/domain/interface'; import { DomainModel } from '@shared/domain/types'; export class DeletionLogStatisticBuilder { - static build(domain: DomainModel, modifiedCount?: number, deletedCount?: number): DomainOperation { - const deletionLogStatistic = { domain, modifiedCount, deletedCount }; + static build( + domain: DomainModel, + modifiedCount: number, + deletedCount: number, + modifiedRef?: string[], + deletedRef?: string[] + ): DomainOperation { + const deletionLogStatistic = { domain, modifiedCount, deletedCount, modifiedRef, deletedRef }; return deletionLogStatistic; } diff --git a/apps/server/src/modules/deletion/deletion-api.module.ts b/apps/server/src/modules/deletion/deletion-api.module.ts index 8187f2fd306..d452761b86b 100644 --- a/apps/server/src/modules/deletion/deletion-api.module.ts +++ b/apps/server/src/modules/deletion/deletion-api.module.ts @@ -18,6 +18,7 @@ import { DeletionRequestsController } from './controller/deletion-requests.contr import { DeletionExecutionsController } from './controller/deletion-executions.controller'; import { DeletionRequestUc } from './uc'; import { FilesStorageClientModule } from '../files-storage-client'; +import { TaskModule } from '../task'; @Module({ imports: [ @@ -35,6 +36,7 @@ import { FilesStorageClientModule } from '../files-storage-client'; RocketChatUserModule, RegistrationPinModule, FilesStorageClientModule, + TaskModule, RocketChatModule.forRoot({ uri: Configuration.get('ROCKET_CHAT_URI') as string, adminId: Configuration.get('ROCKET_CHAT_ADMIN_ID') as string, diff --git a/apps/server/src/modules/deletion/domain/deletion-log.do.ts b/apps/server/src/modules/deletion/domain/deletion-log.do.ts index 0a7c9d08ebf..81a9c7f41cb 100644 --- a/apps/server/src/modules/deletion/domain/deletion-log.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.ts @@ -7,8 +7,8 @@ export interface DeletionLogProps extends AuthorizableObject { updatedAt?: Date; domain: DomainModel; operation?: DeletionOperationModel; - modifiedCount?: number; - deletedCount?: number; + modifiedCount: number; + deletedCount: number; deletionRequestId?: EntityId; performedAt?: Date; } @@ -30,11 +30,11 @@ export class DeletionLog extends DomainObject { return this.props.operation; } - get modifiedCount(): number | undefined { + get modifiedCount(): number { return this.props.modifiedCount; } - get deletedCount(): number | undefined { + get deletedCount(): number { return this.props.deletedCount; } diff --git a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts index b3862fc5cc7..03dfaf5123e 100644 --- a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts @@ -8,8 +8,8 @@ export interface DeletionLogEntityProps { id?: EntityId; domain: DomainModel; operation?: DeletionOperationModel; - modifiedCount?: number; - deletedCount?: number; + modifiedCount: number; + deletedCount: number; deletionRequestId?: ObjectId; performedAt?: Date; createdAt?: Date; @@ -24,11 +24,11 @@ export class DeletionLogEntity extends BaseEntityWithTimestamps { @Property({ nullable: true }) operation?: DeletionOperationModel; - @Property({ nullable: true }) - modifiedCount?: number; + @Property() + modifiedCount: number; - @Property({ nullable: true }) - deletedCount?: number; + @Property() + deletedCount: number; @Property({ nullable: true }) deletionRequestId?: ObjectId; @@ -48,14 +48,8 @@ export class DeletionLogEntity extends BaseEntityWithTimestamps { if (props.operation !== undefined) { this.operation = props.operation; } - - if (props.modifiedCount !== undefined) { - this.modifiedCount = props.modifiedCount; - } - - if (props.deletedCount !== undefined) { - this.deletedCount = props.deletedCount; - } + this.modifiedCount = props.modifiedCount; + this.deletedCount = props.deletedCount; if (props.deletionRequestId !== undefined) { this.deletionRequestId = props.deletionRequestId; diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts index 2af692495d9..a3fd49f17c3 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -16,6 +16,8 @@ import { ObjectId } from 'bson'; import { RegistrationPinService } from '@modules/registration-pin'; import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; import { DomainModel } from '@shared/domain/types'; +import { TaskService } from '@modules/task'; +import { DomainOperationBuilder } from '@src/modules/task/builder'; import { DeletionStatusModel } from '../domain/types'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; @@ -23,7 +25,7 @@ import { DeletionRequestUc } from './deletion-request.uc'; import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; import { deletionLogFactory } from '../domain/testing'; import { DeletionRequestBodyProps } from '../controller/dto'; -import { DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder, DeletionLogStatisticBuilder } from '../builder'; +import { DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '../builder'; describe(DeletionRequestUc.name, () => { let module: TestingModule; @@ -44,6 +46,7 @@ describe(DeletionRequestUc.name, () => { let registrationPinService: DeepMocked; let filesStorageClientAdapterService: DeepMocked; let dashboardService: DeepMocked; + let taskService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -117,6 +120,10 @@ describe(DeletionRequestUc.name, () => { provide: DashboardService, useValue: createMock(), }, + { + provide: TaskService, + useValue: createMock(), + }, ], }).compile(); @@ -137,6 +144,7 @@ describe(DeletionRequestUc.name, () => { registrationPinService = module.get(RegistrationPinService); filesStorageClientAdapterService = module.get(FilesStorageClientAdapterService); dashboardService = module.get(DashboardService); + taskService = module.get(TaskService); await setupEntities(); }); @@ -201,6 +209,7 @@ describe(DeletionRequestUc.name, () => { userId: deletionRequestToExecute.targetRefId, }); const parentEmail = 'parent@parent.eu'; + const tasksModified = DomainOperationBuilder.build(DomainModel.TASK, 1, 1); registrationPinService.deleteRegistrationPinByEmail.mockResolvedValueOnce(2); classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); @@ -215,6 +224,7 @@ describe(DeletionRequestUc.name, () => { rocketChatUserService.deleteByUserId.mockResolvedValueOnce(1); filesStorageClientAdapterService.removeCreatorIdFromFileRecords.mockResolvedValueOnce(5); dashboardService.deleteDashboardByUserId.mockResolvedValueOnce(1); + taskService.removeCreatorId.mockResolvedValueOnce(tasksModified); return { deletionRequestToExecute, @@ -419,6 +429,16 @@ describe(DeletionRequestUc.name, () => { expect(dashboardService.deleteDashboardByUserId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); + it('should call dashboardService.deleteDashboardByUserId to delete USERS DASHBOARD', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(taskService.removeCreatorId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + }); + it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { const { deletionRequestToExecute } = setup(); @@ -426,7 +446,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(12); + expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(13); }); }); @@ -471,7 +491,7 @@ describe(DeletionRequestUc.name, () => { deletionRequestExecuted.targetRefDomain, deletionRequestExecuted.targetRefId ); - const statistics = DeletionLogStatisticBuilder.build( + const statistics = DomainOperationBuilder.build( deletionLogExecuted.domain, deletionLogExecuted.modifiedCount, deletionLogExecuted.deletedCount diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts index 76f4bdc386f..1ae7719fd4d 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -13,9 +13,11 @@ import { Injectable } from '@nestjs/common'; import { DomainModel, EntityId } from '@shared/domain/types'; import { LegacyLogger } from '@src/core/logger'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; -import { DeletionLogStatisticBuilder, DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '../builder'; +import { TaskService } from '@modules/task'; +import { DomainOperation } from '@shared/domain/interface'; +import { DomainOperationBuilder } from '@src/modules/task/builder/domain-operation.builder'; +import { DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '../builder'; import { DeletionRequestBodyProps, DeletionRequestLogResponse, DeletionRequestResponse } from '../controller/dto'; -import { DeletionLogStatistic } from './interface/interfaces'; import { DeletionRequest, DeletionLog } from '../domain'; import { DeletionOperationModel, DeletionStatusModel } from '../domain/types'; import { DeletionRequestService, DeletionLogService } from '../services'; @@ -39,7 +41,8 @@ export class DeletionRequestUc { private readonly logger: LegacyLogger, private readonly registrationPinService: RegistrationPinService, private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService, - private readonly dashboardService: DashboardService + private readonly dashboardService: DashboardService, + private readonly taskService: TaskService ) { this.logger.setContext(DeletionRequestUc.name); } @@ -79,10 +82,10 @@ export class DeletionRequestUc { if (deletionRequest.status === DeletionStatusModel.SUCCESS) { const deletionLog: DeletionLog[] = await this.deletionLogService.findByDeletionRequestId(deletionRequestId); - const deletionLogStatistic: DeletionLogStatistic[] = deletionLog.map((log) => - DeletionLogStatisticBuilder.build(log.domain, log.modifiedCount, log.deletedCount) + const domainOperation: DomainOperation[] = deletionLog.map((log) => + DomainOperationBuilder.build(log.domain, log.modifiedCount, log.deletedCount) ); - response = { ...response, statistics: deletionLogStatistic }; + response = { ...response, statistics: domainOperation }; } return response; @@ -110,6 +113,7 @@ export class DeletionRequestUc { this.removeUserFromRocketChat(deletionRequest), this.removeUserRegistrationPin(deletionRequest), this.removeUsersDashboard(deletionRequest), + this.removeUserFromTasks(deletionRequest), ]); await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); } catch (error) { @@ -125,15 +129,13 @@ export class DeletionRequestUc { updatedCount: number, deletedCount: number ): Promise { - if (updatedCount > 0 || deletedCount > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - domainModel, - operationModel, - updatedCount, - deletedCount - ); - } + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + domainModel, + operationModel, + updatedCount, + deletedCount + ); } private async removeAccount(deletionRequest: DeletionRequest) { @@ -275,4 +277,18 @@ export class DeletionRequestUc { rocketChatUserDeleted ); } + + private async removeUserFromTasks(deletionRequest: DeletionRequest) { + this.logger.debug({ action: 'removeUserFromTasks', deletionRequest }); + + const tasksModified = await this.taskService.removeCreatorId(deletionRequest.targetRefId); + + await this.logDeletion( + deletionRequest, + DomainModel.TASK, + DeletionOperationModel.UPDATE, + tasksModified.deletedCount, + tasksModified.modifiedCount + ); + } } diff --git a/apps/server/src/modules/task/builder/domain-operation.builder.ts b/apps/server/src/modules/task/builder/domain-operation.builder.ts index 46ffb9b3341..b9c6619482f 100644 --- a/apps/server/src/modules/task/builder/domain-operation.builder.ts +++ b/apps/server/src/modules/task/builder/domain-operation.builder.ts @@ -4,8 +4,8 @@ import { DomainModel } from '@shared/domain/types'; export class DomainOperationBuilder { static build( domain: DomainModel, - modifiedCount?: number, - deletedCount?: number, + modifiedCount: number, + deletedCount: number, modifiedRef?: string[], deletedRef?: string[] ): DomainOperation { diff --git a/apps/server/src/modules/task/builder/index.ts b/apps/server/src/modules/task/builder/index.ts new file mode 100644 index 00000000000..5f9d180968d --- /dev/null +++ b/apps/server/src/modules/task/builder/index.ts @@ -0,0 +1 @@ +export * from './domain-operation.builder'; diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index f0b44b0a125..96b318c0311 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -25,22 +25,25 @@ export class TaskService { } async removeCreatorId(creatorId: EntityId): Promise { - const [tasksByOnlyCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); + const [tasksByOnlyCreatorId, counterOfTaskOnlyWithCreator] = await this.taskRepo.findByOnlyCreatorId(creatorId); const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); await Promise.all(promiseDeletedTasks); - const [tasksByCreatorIdWithCoursesAndLessons, count] = await this.taskRepo.findByCreatorIdWithCourseAndLesson( - creatorId - ); + const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCourseorLesson] = + await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); - if (count > 0) { + if (counterOfTasksWithCourseorLesson > 0) { tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); } - return DomainOperationBuilder.build(DomainModel.TASK, promiseDeletedTasks.length, count); + return DomainOperationBuilder.build( + DomainModel.TASK, + counterOfTaskOnlyWithCreator, + counterOfTasksWithCourseorLesson + ); } async delete(task: Task): Promise { diff --git a/apps/server/src/shared/domain/interface/domain-operation.ts b/apps/server/src/shared/domain/interface/domain-operation.ts index c5288c64e6d..d4900ed2314 100644 --- a/apps/server/src/shared/domain/interface/domain-operation.ts +++ b/apps/server/src/shared/domain/interface/domain-operation.ts @@ -2,8 +2,8 @@ import { DomainModel } from '../types'; export interface DomainOperation { domain: DomainModel; - modifiedCount?: number; - deletedCount?: number; + modifiedCount: number; + deletedCount: number; modifiedRef?: string[]; deletedRef?: string[]; } From 15883e4684f3bc1f6f34c47d8db53ec1df27f2e6 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 28 Dec 2023 15:44:35 +0100 Subject: [PATCH 10/23] some fixes in task entity --- apps/server/src/shared/domain/entity/task.entity.ts | 12 +++++++----- apps/server/src/shared/domain/types/task.types.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index 50ffdf4e7d0..a96139fd9bf 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -67,7 +67,7 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, teamSubmissions?: boolean; @Index() - @ManyToOne('User', { fieldName: 'teacherId' }) + @ManyToOne('User', { fieldName: 'teacherId', nullable: true }) creator?: User; @Index() @@ -98,6 +98,7 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, this.dueDate = props.dueDate; if (props.private !== undefined) this.private = props.private; + this.creator = props.creator; this.course = props.course; this.school = props.school; this.lesson = props.lesson; @@ -135,12 +136,13 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, private getMaxSubmissions(): number { const parent = this.getParent(); - let maxSubmissions = 0; - if (parent) { - // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result - maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; + if (parent === undefined) { + return 0; } + // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result + const maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; + return maxSubmissions; } diff --git a/apps/server/src/shared/domain/types/task.types.ts b/apps/server/src/shared/domain/types/task.types.ts index b10e32f5f2d..c9ce4ebb698 100644 --- a/apps/server/src/shared/domain/types/task.types.ts +++ b/apps/server/src/shared/domain/types/task.types.ts @@ -22,7 +22,7 @@ export interface TaskCreate extends ITask { export interface TaskProperties extends ITask { course?: Course; lesson?: LessonEntity; - creator?: User; + creator: User; school: SchoolEntity; finished?: User[]; private?: boolean; From 4f7f0b248b690d2bd7707edded018f129510504f Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 28 Dec 2023 16:45:25 +0100 Subject: [PATCH 11/23] change in task entity --- apps/server/src/shared/domain/entity/task.entity.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index a96139fd9bf..98c6a408382 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -136,13 +136,11 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, private getMaxSubmissions(): number { const parent = this.getParent(); - if (parent === undefined) { - return 0; + let maxSubmissions = 0; + if (parent) { + // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result + maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; } - - // For draft (user as parent) propaly user is not a student, but for maxSubmission one is valid result - const maxSubmissions = parent instanceof User ? 1 : parent.getStudentIds().length; - return maxSubmissions; } From 11fd1a67f4becc4aa4d08bd494be7470e5312560 Mon Sep 17 00:00:00 2001 From: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:40:30 +0100 Subject: [PATCH 12/23] Update apps/server/src/modules/deletion/deletion-api.module.ts Co-authored-by: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> --- apps/server/src/modules/deletion/deletion-api.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/deletion/deletion-api.module.ts b/apps/server/src/modules/deletion/deletion-api.module.ts index d452761b86b..4760524b37f 100644 --- a/apps/server/src/modules/deletion/deletion-api.module.ts +++ b/apps/server/src/modules/deletion/deletion-api.module.ts @@ -18,7 +18,7 @@ import { DeletionRequestsController } from './controller/deletion-requests.contr import { DeletionExecutionsController } from './controller/deletion-executions.controller'; import { DeletionRequestUc } from './uc'; import { FilesStorageClientModule } from '../files-storage-client'; -import { TaskModule } from '../task'; +import { TaskModule } from '@modules/task'; @Module({ imports: [ From 69aff64a24ea2d423d9ea2046b3fd8a91b2047c1 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 3 Jan 2024 10:36:11 +0100 Subject: [PATCH 13/23] move builder, fix imports --- apps/server/src/modules/deletion/deletion-api.module.ts | 2 +- .../src/modules/deletion/uc/deletion-request.uc.spec.ts | 2 +- apps/server/src/modules/deletion/uc/deletion-request.uc.ts | 2 +- apps/server/src/modules/task/service/task.service.spec.ts | 2 +- apps/server/src/modules/task/service/task.service.ts | 2 +- .../domain}/builder/domain-operation.builder.spec.ts | 7 +++++-- .../domain}/builder/domain-operation.builder.ts | 0 .../src/{modules/task => shared/domain}/builder/index.ts | 0 8 files changed, 10 insertions(+), 7 deletions(-) rename apps/server/src/{modules/task => shared/domain}/builder/domain-operation.builder.spec.ts (70%) rename apps/server/src/{modules/task => shared/domain}/builder/domain-operation.builder.ts (100%) rename apps/server/src/{modules/task => shared/domain}/builder/index.ts (100%) diff --git a/apps/server/src/modules/deletion/deletion-api.module.ts b/apps/server/src/modules/deletion/deletion-api.module.ts index 4760524b37f..94d8d59d5f6 100644 --- a/apps/server/src/modules/deletion/deletion-api.module.ts +++ b/apps/server/src/modules/deletion/deletion-api.module.ts @@ -14,11 +14,11 @@ import { RocketChatUserModule } from '@modules/rocketchat-user'; import { Configuration } from '@hpi-schul-cloud/commons'; import { RocketChatModule } from '@modules/rocketchat'; import { RegistrationPinModule } from '@modules/registration-pin'; +import { TaskModule } from '@modules/task'; import { DeletionRequestsController } from './controller/deletion-requests.controller'; import { DeletionExecutionsController } from './controller/deletion-executions.controller'; import { DeletionRequestUc } from './uc'; import { FilesStorageClientModule } from '../files-storage-client'; -import { TaskModule } from '@modules/task'; @Module({ imports: [ diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts index a3fd49f17c3..5f3010da7f2 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -17,7 +17,7 @@ import { RegistrationPinService } from '@modules/registration-pin'; import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client'; import { DomainModel } from '@shared/domain/types'; import { TaskService } from '@modules/task'; -import { DomainOperationBuilder } from '@src/modules/task/builder'; +import { DomainOperationBuilder } from '@shared/domain/builder'; import { DeletionStatusModel } from '../domain/types'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts index 1ae7719fd4d..22320e40583 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -15,7 +15,7 @@ import { LegacyLogger } from '@src/core/logger'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { TaskService } from '@modules/task'; import { DomainOperation } from '@shared/domain/interface'; -import { DomainOperationBuilder } from '@src/modules/task/builder/domain-operation.builder'; +import { DomainOperationBuilder } from '@shared/domain/builder/domain-operation.builder'; import { DeletionRequestLogResponseBuilder, DeletionTargetRefBuilder } from '../builder'; import { DeletionRequestBodyProps, DeletionRequestLogResponse, DeletionRequestResponse } from '../controller/dto'; import { DeletionRequest, DeletionLog } from '../domain'; 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 bcd8c9e9b48..a2fd038eee1 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -4,9 +4,9 @@ import { TaskRepo } from '@shared/repo'; import { courseFactory, setupEntities, submissionFactory, taskFactory, userFactory } from '@shared/testing'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { DomainModel } from '@shared/domain/types'; +import { DomainOperationBuilder } from '@shared/domain/builder'; import { SubmissionService } from './submission.service'; import { TaskService } from './task.service'; -import { DomainOperationBuilder } from '../builder/domain-operation.builder'; describe('TaskService', () => { let module: TestingModule; diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 96b318c0311..cc5ffe89b75 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -4,8 +4,8 @@ import { Task } from '@shared/domain/entity'; import { DomainOperation, IFindOptions } from '@shared/domain/interface'; import { Counted, DomainModel, EntityId } from '@shared/domain/types'; import { TaskRepo } from '@shared/repo'; +import { DomainOperationBuilder } from '@shared/domain/builder'; import { SubmissionService } from './submission.service'; -import { DomainOperationBuilder } from '../builder/domain-operation.builder'; @Injectable() export class TaskService { diff --git a/apps/server/src/modules/task/builder/domain-operation.builder.spec.ts b/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts similarity index 70% rename from apps/server/src/modules/task/builder/domain-operation.builder.spec.ts rename to apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts index 4e0eea6b9fa..72858250d1c 100644 --- a/apps/server/src/modules/task/builder/domain-operation.builder.spec.ts +++ b/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts @@ -1,5 +1,6 @@ import { DomainModel } from '@shared/domain/types'; -import { DomainOperationBuilder } from './domain-operation.builder'; +import { ObjectId } from 'bson'; +import { DomainOperationBuilder } from '.'; describe(DomainOperationBuilder.name, () => { afterAll(() => { @@ -10,9 +11,11 @@ describe(DomainOperationBuilder.name, () => { // Arrange const domain = DomainModel.PSEUDONYMS; const modifiedCount = 0; + const modifiedRef = []; + const deletedRef = [new ObjectId().toHexString(), new ObjectId().toHexString()]; const deletedCount = 2; - const result = DomainOperationBuilder.build(domain, modifiedCount, deletedCount); + const result = DomainOperationBuilder.build(domain, modifiedCount, deletedCount, modifiedRef, deletedRef); // Assert expect(result.domain).toEqual(domain); diff --git a/apps/server/src/modules/task/builder/domain-operation.builder.ts b/apps/server/src/shared/domain/builder/domain-operation.builder.ts similarity index 100% rename from apps/server/src/modules/task/builder/domain-operation.builder.ts rename to apps/server/src/shared/domain/builder/domain-operation.builder.ts diff --git a/apps/server/src/modules/task/builder/index.ts b/apps/server/src/shared/domain/builder/index.ts similarity index 100% rename from apps/server/src/modules/task/builder/index.ts rename to apps/server/src/shared/domain/builder/index.ts From e21ee86df851dfae6806b63f79b1727bd1d34efe Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 3 Jan 2024 10:49:28 +0100 Subject: [PATCH 14/23] fix in taskService --- .../src/modules/task/service/task.service.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index cc5ffe89b75..f8b18877008 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -25,25 +25,27 @@ export class TaskService { } async removeCreatorId(creatorId: EntityId): Promise { - const [tasksByOnlyCreatorId, counterOfTaskOnlyWithCreator] = await this.taskRepo.findByOnlyCreatorId(creatorId); + const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); await Promise.all(promiseDeletedTasks); - const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCourseorLesson] = + const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCoursesorLessons] = await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); - if (counterOfTasksWithCourseorLesson > 0) { + if (counterOfTasksWithCoursesorLessons > 0) { tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); } - return DomainOperationBuilder.build( + const result = DomainOperationBuilder.build( DomainModel.TASK, - counterOfTaskOnlyWithCreator, - counterOfTasksWithCourseorLesson + counterOfTasksOnlyWithCreatorId, + counterOfTasksWithCoursesorLessons ); + + return result; } async delete(task: Task): Promise { From 7df7071185969c2a9bddd0ca09132dc788ab5dd8 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 3 Jan 2024 13:53:34 +0100 Subject: [PATCH 15/23] splitting the removeCreatorId method --- .../modules/task/service/task.service.spec.ts | 14 +++--- .../src/modules/task/service/task.service.ts | 44 +++++++++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) 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 a2fd038eee1..f0aa67f86ae 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -122,7 +122,7 @@ describe('TaskService', () => { it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { const { creator } = setup(); - await taskService.removeCreatorId(creator.id); + await taskService.removeUserFromTasks(creator.id); expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); }); @@ -130,7 +130,7 @@ describe('TaskService', () => { it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { const { creator } = setup(); - await taskService.removeCreatorId(creator.id); + await taskService.removeUserFromTasks(creator.id); expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); }); @@ -138,7 +138,7 @@ describe('TaskService', () => { it('should return the object with information on the actions performed', async () => { const { creator, expectedResult } = setup(); - const result = await taskService.removeCreatorId(creator.id); + const result = await taskService.removeUserFromTasks(creator.id); expect(result).toEqual(expectedResult); }); @@ -164,7 +164,7 @@ describe('TaskService', () => { it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { const { creator } = setup(); - await taskService.removeCreatorId(creator.id); + await taskService.removeUserFromTasks(creator.id); expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); }); @@ -172,7 +172,7 @@ describe('TaskService', () => { it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { const { creator } = setup(); - await taskService.removeCreatorId(creator.id); + await taskService.removeUserFromTasks(creator.id); expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); }); @@ -180,7 +180,7 @@ describe('TaskService', () => { it('should call taskRepo.save with task to update', async () => { const { creator, taskWithCourseToUpdate } = setup(); - await taskService.removeCreatorId(creator.id); + await taskService.removeUserFromTasks(creator.id); expect(taskRepo.save).toBeCalledWith([taskWithCourseToUpdate]); }); @@ -188,7 +188,7 @@ describe('TaskService', () => { it('should return the object with information on the actions performed', async () => { const { creator, expectedResult } = setup(); - const result = await taskService.removeCreatorId(creator.id); + const result = await taskService.removeUserFromTasks(creator.id); expect(result).toEqual(expectedResult); }); diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index f8b18877008..a6d154f9e0f 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -24,26 +24,12 @@ export class TaskService { return this.taskRepo.findBySingleParent(creatorId, courseId, filters, options); } - async removeCreatorId(creatorId: EntityId): Promise { - const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); - - const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); + async removeUserFromTasks(creatorId: EntityId): Promise { + const deletedTaskWithCreatorId = await this.deleteTasksByOnlyCreator(creatorId); - await Promise.all(promiseDeletedTasks); + const updatedTasksWithCreatorId = await this.removeCreatorIdFromTasks(creatorId); - const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCoursesorLessons] = - await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); - - if (counterOfTasksWithCoursesorLessons > 0) { - tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); - await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); - } - - const result = DomainOperationBuilder.build( - DomainModel.TASK, - counterOfTasksOnlyWithCreatorId, - counterOfTasksWithCoursesorLessons - ); + const result = DomainOperationBuilder.build(DomainModel.TASK, deletedTaskWithCreatorId, updatedTasksWithCreatorId); return result; } @@ -66,4 +52,26 @@ export class TaskService { async findById(taskId: EntityId): Promise { return this.taskRepo.findById(taskId); } + + private async deleteTasksByOnlyCreator(creatorId: EntityId): Promise { + const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); + + const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); + + await Promise.all(promiseDeletedTasks); + + return counterOfTasksOnlyWithCreatorId; + } + + private async removeCreatorIdFromTasks(creatorId: EntityId): Promise { + const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCoursesorLessons] = + await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); + + if (counterOfTasksWithCoursesorLessons > 0) { + tasksByCreatorIdWithCoursesAndLessons.forEach((task: Task) => task.removeCreatorId()); + await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); + } + + return counterOfTasksWithCoursesorLessons; + } } From f6f10523e0344457c7418af0e7d6c4b6bdfb21c9 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 4 Jan 2024 07:24:00 +0100 Subject: [PATCH 16/23] fix in deletion module, delete removeUserFromTasks from taskService --- .../deletion/uc/deletion-request.uc.ts | 5 +++-- .../src/modules/task/service/task.service.ts | 22 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts index 22320e40583..7a10658073a 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -281,13 +281,14 @@ export class DeletionRequestUc { private async removeUserFromTasks(deletionRequest: DeletionRequest) { this.logger.debug({ action: 'removeUserFromTasks', deletionRequest }); - const tasksModified = await this.taskService.removeCreatorId(deletionRequest.targetRefId); + const tasksDeleted = await this.taskService.deleteTasksByOnlyCreator(deletionRequest.targetRefId); + const tasksModified = await this.taskService.removeCreatorIdFromTasks(deletionRequest.targetRefId); await this.logDeletion( deletionRequest, DomainModel.TASK, DeletionOperationModel.UPDATE, - tasksModified.deletedCount, + tasksDeleted.deletedCount, tasksModified.modifiedCount ); } diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index a6d154f9e0f..8092bd29679 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -24,16 +24,6 @@ export class TaskService { return this.taskRepo.findBySingleParent(creatorId, courseId, filters, options); } - async removeUserFromTasks(creatorId: EntityId): Promise { - const deletedTaskWithCreatorId = await this.deleteTasksByOnlyCreator(creatorId); - - const updatedTasksWithCreatorId = await this.removeCreatorIdFromTasks(creatorId); - - const result = DomainOperationBuilder.build(DomainModel.TASK, deletedTaskWithCreatorId, updatedTasksWithCreatorId); - - return result; - } - async delete(task: Task): Promise { await this.filesStorageClientAdapterService.deleteFilesOfParent(task.id); @@ -53,17 +43,19 @@ export class TaskService { return this.taskRepo.findById(taskId); } - private async deleteTasksByOnlyCreator(creatorId: EntityId): Promise { + async deleteTasksByOnlyCreator(creatorId: EntityId): Promise { const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); await Promise.all(promiseDeletedTasks); - return counterOfTasksOnlyWithCreatorId; + const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksOnlyWithCreatorId, 0); + + return result; } - private async removeCreatorIdFromTasks(creatorId: EntityId): Promise { + async removeCreatorIdFromTasks(creatorId: EntityId): Promise { const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCoursesorLessons] = await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); @@ -72,6 +64,8 @@ export class TaskService { await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); } - return counterOfTasksWithCoursesorLessons; + const result = DomainOperationBuilder.build(DomainModel.TASK, 0, counterOfTasksWithCoursesorLessons); + + return result; } } From dcda093305044d921020e56a7a691dbda5ca0a80 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 4 Jan 2024 08:03:56 +0100 Subject: [PATCH 17/23] add findByUserIdInFinished method in repo and test cases for it --- .../repo/task/task.repo.integration.spec.ts | 24 +++++++++++++++++++ apps/server/src/shared/repo/task/task.repo.ts | 9 +++++++ 2 files changed, 33 insertions(+) diff --git a/apps/server/src/shared/repo/task/task.repo.integration.spec.ts b/apps/server/src/shared/repo/task/task.repo.integration.spec.ts index 6becd31c010..7b383c427bc 100644 --- a/apps/server/src/shared/repo/task/task.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/task/task.repo.integration.spec.ts @@ -2157,4 +2157,28 @@ describe('TaskRepo', () => { }); }); }); + + describe('findByUserIdInFinished', () => { + describe('when searching by userId', () => { + const setup = async () => { + const creator = userFactory.build(); + const course = courseFactory.build({ teachers: [creator] }); + const taskWithFinished = taskFactory.build({ creator, course, finished: [creator] }); + const taskWithoutFinished = taskFactory.build({ creator, course }); + + await em.persistAndFlush([taskWithFinished, taskWithoutFinished]); + em.clear(); + + return { creator }; + }; + + it('should find task where user is in archive', async () => { + const { creator } = await setup(); + + const [result] = await repo.findByUserIdInFinished(creator.id); + + expect(result).toHaveLength(1); + }); + }); + }); }); diff --git a/apps/server/src/shared/repo/task/task.repo.ts b/apps/server/src/shared/repo/task/task.repo.ts index 5afc052ef8d..b3e9d415d64 100644 --- a/apps/server/src/shared/repo/task/task.repo.ts +++ b/apps/server/src/shared/repo/task/task.repo.ts @@ -205,6 +205,15 @@ export class TaskRepo extends BaseRepo { return countedTaskList; } + async findByUserIdInFinished(userId: EntityId): Promise> { + const scope = new TaskScope(); + scope.byFinished(userId, true); + + const countedTaskList = await this.findTasksAndCount(scope.query); + + return countedTaskList; + } + private async findTasksAndCount(query: FilterQuery, options?: IFindOptions): Promise> { const pagination = options?.pagination || {}; const order = options?.order || {}; From 2943bdfc85b9515036c3b8f0cb7aaf3cdcfcc1d0 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 4 Jan 2024 09:32:59 +0100 Subject: [PATCH 18/23] fix testcases in deletion UC --- .../deletion/uc/deletion-request.uc.spec.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts index 5f3010da7f2..3f0b3795ced 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -209,7 +209,8 @@ describe(DeletionRequestUc.name, () => { userId: deletionRequestToExecute.targetRefId, }); const parentEmail = 'parent@parent.eu'; - const tasksModified = DomainOperationBuilder.build(DomainModel.TASK, 1, 1); + const tasksModified = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + const tasksDeleted = DomainOperationBuilder.build(DomainModel.TASK, 0, 1); registrationPinService.deleteRegistrationPinByEmail.mockResolvedValueOnce(2); classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); @@ -224,7 +225,8 @@ describe(DeletionRequestUc.name, () => { rocketChatUserService.deleteByUserId.mockResolvedValueOnce(1); filesStorageClientAdapterService.removeCreatorIdFromFileRecords.mockResolvedValueOnce(5); dashboardService.deleteDashboardByUserId.mockResolvedValueOnce(1); - taskService.removeCreatorId.mockResolvedValueOnce(tasksModified); + taskService.removeCreatorIdFromTasks.mockResolvedValueOnce(tasksModified); + taskService.deleteTasksByOnlyCreator.mockResolvedValueOnce(tasksDeleted); return { deletionRequestToExecute, @@ -429,14 +431,24 @@ describe(DeletionRequestUc.name, () => { expect(dashboardService.deleteDashboardByUserId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); - it('should call dashboardService.deleteDashboardByUserId to delete USERS DASHBOARD', async () => { + it('should call taskService.deleteTasksByOnlyCreator to delete Tasks only with creator', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(taskService.deleteTasksByOnlyCreator).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + }); + + it('should call taskService.removeCreatorIdFromTasks to update Tasks without creatorId', async () => { const { deletionRequestToExecute } = setup(); deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); - expect(taskService.removeCreatorId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + expect(taskService.removeCreatorIdFromTasks).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { From e75fd0f8db0cf4eaab1c59f7d5cae5608995b936 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 4 Jan 2024 17:41:21 +0100 Subject: [PATCH 19/23] modify task services to delete user data --- .../deletion/uc/deletion-request.uc.spec.ts | 16 ++++- .../deletion/uc/deletion-request.uc.ts | 9 ++- .../modules/task/service/task.service.spec.ts | 69 +++++++++++-------- .../src/modules/task/service/task.service.ts | 22 +++++- apps/server/src/modules/task/task.module.ts | 3 +- .../shared/domain/entity/task.entity.spec.ts | 29 ++++++++ .../src/shared/domain/entity/task.entity.ts | 4 ++ 7 files changed, 115 insertions(+), 37 deletions(-) diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts index 3f0b3795ced..cd7f0a00443 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -209,7 +209,8 @@ describe(DeletionRequestUc.name, () => { userId: deletionRequestToExecute.targetRefId, }); const parentEmail = 'parent@parent.eu'; - const tasksModified = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + const tasksModifiedByRemoveCreatorId = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + const tasksModifiedByRemoveUserFromFinished = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); const tasksDeleted = DomainOperationBuilder.build(DomainModel.TASK, 0, 1); registrationPinService.deleteRegistrationPinByEmail.mockResolvedValueOnce(2); @@ -225,7 +226,8 @@ describe(DeletionRequestUc.name, () => { rocketChatUserService.deleteByUserId.mockResolvedValueOnce(1); filesStorageClientAdapterService.removeCreatorIdFromFileRecords.mockResolvedValueOnce(5); dashboardService.deleteDashboardByUserId.mockResolvedValueOnce(1); - taskService.removeCreatorIdFromTasks.mockResolvedValueOnce(tasksModified); + taskService.removeCreatorIdFromTasks.mockResolvedValueOnce(tasksModifiedByRemoveCreatorId); + taskService.removeCreatorIdFromTasks.mockResolvedValueOnce(tasksModifiedByRemoveUserFromFinished); taskService.deleteTasksByOnlyCreator.mockResolvedValueOnce(tasksDeleted); return { @@ -451,6 +453,16 @@ describe(DeletionRequestUc.name, () => { expect(taskService.removeCreatorIdFromTasks).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); + it('should call taskService.removeUserFromFinished to update Tasks without creatorId in Finished collection', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(taskService.removeUserFromFinished).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + }); + it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { const { deletionRequestToExecute } = setup(); diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts index 7a10658073a..97d2284a4cc 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -282,14 +282,17 @@ export class DeletionRequestUc { this.logger.debug({ action: 'removeUserFromTasks', deletionRequest }); const tasksDeleted = await this.taskService.deleteTasksByOnlyCreator(deletionRequest.targetRefId); - const tasksModified = await this.taskService.removeCreatorIdFromTasks(deletionRequest.targetRefId); + const tasksModifiedByRemoveCreator = await this.taskService.removeCreatorIdFromTasks(deletionRequest.targetRefId); + const tasksModifiedByRemoveUserFromFinished = await this.taskService.removeUserFromFinished( + deletionRequest.targetRefId + ); await this.logDeletion( deletionRequest, DomainModel.TASK, DeletionOperationModel.UPDATE, - tasksDeleted.deletedCount, - tasksModified.modifiedCount + tasksModifiedByRemoveCreator.modifiedCount + tasksModifiedByRemoveUserFromFinished.modifiedCount, + tasksDeleted.deletedCount ); } } 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 f0aa67f86ae..41cf5b6c61d 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -105,16 +105,15 @@ describe('TaskService', () => { }); }); - describe('removeCreatorId', () => { + describe('deleteTasksByOnlyCreator', () => { describe('when task has only user as parent', () => { const setup = () => { const creator = userFactory.buildWithId(); const taskWithoutCourse = taskFactory.buildWithId({ creator }); taskRepo.findByOnlyCreatorId.mockResolvedValue([[taskWithoutCourse], 1]); - taskRepo.findByCreatorIdWithCourseAndLesson.mockResolvedValue([[], 0]); - const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 0, 1); return { creator, expectedResult }; }; @@ -122,57 +121,40 @@ describe('TaskService', () => { it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { const { creator } = setup(); - await taskService.removeUserFromTasks(creator.id); + await taskService.deleteTasksByOnlyCreator(creator.id); expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); }); - it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { - const { creator } = setup(); - - await taskService.removeUserFromTasks(creator.id); - - expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); - }); - it('should return the object with information on the actions performed', async () => { const { creator, expectedResult } = setup(); - const result = await taskService.removeUserFromTasks(creator.id); + const result = await taskService.deleteTasksByOnlyCreator(creator.id); expect(result).toEqual(expectedResult); }); }); + }); + describe('removeCreatorIdFromTasks', () => { describe('when tasks where user is parent, and when task has course', () => { const setup = () => { const creator = userFactory.buildWithId(); - const taskWithoutCourse = taskFactory.buildWithId({ creator }); - const course = courseFactory.build(); const taskWithCourse = taskFactory.buildWithId({ creator, course }); - taskRepo.findByOnlyCreatorId.mockResolvedValue([[taskWithoutCourse], 1]); taskRepo.findByCreatorIdWithCourseAndLesson.mockResolvedValue([[taskWithCourse], 1]); - const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 1); + const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); const taskWithCourseToUpdate = { ...taskWithCourse, creator: undefined }; return { creator, expectedResult, taskWithCourseToUpdate }; }; - it('should call taskRepo.findByOnlyCreatorId with creatorId', async () => { - const { creator } = setup(); - - await taskService.removeUserFromTasks(creator.id); - - expect(taskRepo.findByOnlyCreatorId).toBeCalledWith(creator.id); - }); - it('should call taskRepo.findByCreatorIdWithCourseAndLesson with creatorId', async () => { const { creator } = setup(); - await taskService.removeUserFromTasks(creator.id); + await taskService.removeCreatorIdFromTasks(creator.id); expect(taskRepo.findByCreatorIdWithCourseAndLesson).toBeCalledWith(creator.id); }); @@ -180,7 +162,7 @@ describe('TaskService', () => { it('should call taskRepo.save with task to update', async () => { const { creator, taskWithCourseToUpdate } = setup(); - await taskService.removeUserFromTasks(creator.id); + await taskService.removeCreatorIdFromTasks(creator.id); expect(taskRepo.save).toBeCalledWith([taskWithCourseToUpdate]); }); @@ -188,7 +170,38 @@ describe('TaskService', () => { it('should return the object with information on the actions performed', async () => { const { creator, expectedResult } = setup(); - const result = await taskService.removeUserFromTasks(creator.id); + const result = await taskService.removeCreatorIdFromTasks(creator.id); + + expect(result).toEqual(expectedResult); + }); + }); + }); + + describe('removeUserFromFinished', () => { + describe('when task has user in finished array', () => { + const setup = () => { + const creator = userFactory.buildWithId(); + const finishedTask = taskFactory.finished(creator).buildWithId(); + + taskRepo.findByUserIdInFinished.mockResolvedValue([[finishedTask], 1]); + + const expectedResult = DomainOperationBuilder.build(DomainModel.TASK, 1, 0); + + return { creator, expectedResult }; + }; + + it('should call taskRepo.findByUserIdInFinished with creatorId', async () => { + const { creator } = setup(); + + await taskService.removeUserFromFinished(creator.id); + + expect(taskRepo.findByUserIdInFinished).toBeCalledWith(creator.id); + }); + + it('should return the object with information on the actions performed', async () => { + const { creator, expectedResult } = setup(); + + const result = await taskService.removeUserFromFinished(creator.id); expect(result).toEqual(expectedResult); }); diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 8092bd29679..212866dfbc2 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -12,7 +12,7 @@ export class TaskService { constructor( private readonly taskRepo: TaskRepo, private readonly submissionService: SubmissionService, - private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService + private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService, ) {} async findBySingleParent( @@ -50,7 +50,7 @@ export class TaskService { await Promise.all(promiseDeletedTasks); - const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksOnlyWithCreatorId, 0); + const result = DomainOperationBuilder.build(DomainModel.TASK, 0, counterOfTasksOnlyWithCreatorId); return result; } @@ -64,7 +64,23 @@ export class TaskService { await this.taskRepo.save(tasksByCreatorIdWithCoursesAndLessons); } - const result = DomainOperationBuilder.build(DomainModel.TASK, 0, counterOfTasksWithCoursesorLessons); + const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksWithCoursesorLessons, 0); + + return result; + } + + async removeUserFromFinished(userId: EntityId): Promise { + const [tasksWithUserInFinished, counterOfTasksWithUserInFinished] = await this.taskRepo.findByUserIdInFinished( + userId + ); + + if (counterOfTasksWithUserInFinished > 0) { + tasksWithUserInFinished.forEach((task: Task) => task.removeUserFromFinished(userId)); + + await this.taskRepo.save(tasksWithUserInFinished); + } + + const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksWithUserInFinished, 0); return result; } diff --git a/apps/server/src/modules/task/task.module.ts b/apps/server/src/modules/task/task.module.ts index 87ecf144798..1e6fdb293bd 100644 --- a/apps/server/src/modules/task/task.module.ts +++ b/apps/server/src/modules/task/task.module.ts @@ -2,10 +2,11 @@ import { CopyHelperModule } from '@modules/copy-helper'; import { FilesStorageClientModule } from '@modules/files-storage-client'; import { Module } from '@nestjs/common'; import { CourseRepo, SubmissionRepo, TaskRepo } from '@shared/repo'; +import { UserModule } from '@modules/user'; import { SubmissionService, TaskCopyService, TaskService } from './service'; @Module({ - imports: [FilesStorageClientModule, CopyHelperModule], + imports: [FilesStorageClientModule, CopyHelperModule, UserModule], providers: [TaskService, TaskCopyService, SubmissionService, TaskRepo, CourseRepo, SubmissionRepo], exports: [TaskService, TaskCopyService, SubmissionService], }) diff --git a/apps/server/src/shared/domain/entity/task.entity.spec.ts b/apps/server/src/shared/domain/entity/task.entity.spec.ts index 9a50aedce2a..a8a1007a898 100644 --- a/apps/server/src/shared/domain/entity/task.entity.spec.ts +++ b/apps/server/src/shared/domain/entity/task.entity.spec.ts @@ -885,4 +885,33 @@ describe('Task Entity', () => { }); }); }); + + describe('removeUserFromFinished', () => { + describe('when user exist in Finished array', () => { + const setup = () => { + const user1 = userFactory.buildWithId(); + const user2 = userFactory.buildWithId(); + const task = taskFactory.buildWithId({ finished: [user1, user2] }); + + return { user1, user2, task }; + }; + + it('should remove user form finished collection', () => { + const { task, user1 } = setup(); + + task.removeUserFromFinished(user1.id); + + expect(task.finished.contains(user1)).toBe(false); + }); + + it('should remove only user selected, not other users in finished collection', () => { + const { task, user1, user2 } = setup(); + + task.removeUserFromFinished(user1.id); + + expect(task.finished.contains(user1)).toBe(false); + expect(task.finished.contains(user2)).toBe(true); + }); + }); + }); }); diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index 98c6a408382..c5ae56b7286 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -327,6 +327,10 @@ export class Task extends BaseEntityWithTimestamps implements LearnroomElement, public removeCreatorId(): void { this.creator = undefined; } + + public removeUserFromFinished(userId: EntityId): void { + this.finished.remove((u) => u.id === userId); + } } export function isTask(reference: unknown): reference is Task { From 46016f2ea9c05881916a6fea0f893dd1e60e53ea Mon Sep 17 00:00:00 2001 From: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> Date: Fri, 5 Jan 2024 07:44:03 +0100 Subject: [PATCH 20/23] Update apps/server/src/modules/task/service/task.service.ts Co-authored-by: Bartosz Nowicki <116367402+bn-pass@users.noreply.github.com> --- apps/server/src/modules/task/service/task.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 212866dfbc2..e0c29b44e4f 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -12,7 +12,7 @@ export class TaskService { constructor( private readonly taskRepo: TaskRepo, private readonly submissionService: SubmissionService, - private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService, + private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService ) {} async findBySingleParent( From 9adea9a0b76c24658d4830d3f5b8ab19ea3e38f1 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 5 Jan 2024 07:51:42 +0100 Subject: [PATCH 21/23] fix after review --- .../server/src/modules/deletion/deletion-api.module.ts | 2 +- apps/server/src/modules/task/service/task.service.ts | 7 ++++--- .../domain/builder/domain-operation.builder.spec.ts | 10 +++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/server/src/modules/deletion/deletion-api.module.ts b/apps/server/src/modules/deletion/deletion-api.module.ts index 94d8d59d5f6..5e4a6cf427d 100644 --- a/apps/server/src/modules/deletion/deletion-api.module.ts +++ b/apps/server/src/modules/deletion/deletion-api.module.ts @@ -15,10 +15,10 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { RocketChatModule } from '@modules/rocketchat'; import { RegistrationPinModule } from '@modules/registration-pin'; import { TaskModule } from '@modules/task'; +import { FilesStorageClientModule } from '@modules/files-storage-client'; import { DeletionRequestsController } from './controller/deletion-requests.controller'; import { DeletionExecutionsController } from './controller/deletion-executions.controller'; import { DeletionRequestUc } from './uc'; -import { FilesStorageClientModule } from '../files-storage-client'; @Module({ imports: [ diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index e0c29b44e4f..2bb7d94f7ca 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -46,9 +46,10 @@ export class TaskService { async deleteTasksByOnlyCreator(creatorId: EntityId): Promise { const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); - const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); - - await Promise.all(promiseDeletedTasks); + if (counterOfTasksOnlyWithCreatorId > 0) { + const promiseDeletedTasks = tasksByOnlyCreatorId.map((task: Task) => this.delete(task)); + await Promise.all(promiseDeletedTasks); + } const result = DomainOperationBuilder.build(DomainModel.TASK, 0, counterOfTasksOnlyWithCreatorId); diff --git a/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts b/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts index 72858250d1c..940224c3cf7 100644 --- a/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts +++ b/apps/server/src/shared/domain/builder/domain-operation.builder.spec.ts @@ -7,17 +7,21 @@ describe(DomainOperationBuilder.name, () => { jest.clearAllMocks(); }); - it('should build generic domainOperation with all attributes', () => { - // Arrange + const setup = () => { const domain = DomainModel.PSEUDONYMS; const modifiedCount = 0; const modifiedRef = []; const deletedRef = [new ObjectId().toHexString(), new ObjectId().toHexString()]; const deletedCount = 2; + return { domain, modifiedCount, deletedCount, modifiedRef, deletedRef }; + }; + + it('should build generic domainOperation with all attributes', () => { + const { domain, modifiedCount, deletedCount, modifiedRef, deletedRef } = setup(); + const result = DomainOperationBuilder.build(domain, modifiedCount, deletedCount, modifiedRef, deletedRef); - // Assert expect(result.domain).toEqual(domain); expect(result.modifiedCount).toEqual(modifiedCount); expect(result.deletedCount).toEqual(deletedCount); From 4c22954ed843e6a228fe5be9eb7a25c04c466e0c Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 5 Jan 2024 08:57:01 +0100 Subject: [PATCH 22/23] fix imports and add logger to services in TaskService --- .../modules/task/service/task.service.spec.ts | 5 +++++ .../src/modules/task/service/task.service.ts | 19 ++++++++++++++++--- apps/server/src/modules/task/task.module.ts | 4 ++-- 3 files changed, 23 insertions(+), 5 deletions(-) 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 41cf5b6c61d..6c51e8147af 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -7,6 +7,7 @@ import { DomainModel } from '@shared/domain/types'; import { DomainOperationBuilder } from '@shared/domain/builder'; import { SubmissionService } from './submission.service'; import { TaskService } from './task.service'; +import { LegacyLogger } from '@src/core/logger'; describe('TaskService', () => { let module: TestingModule; @@ -31,6 +32,10 @@ describe('TaskService', () => { provide: FilesStorageClientAdapterService, useValue: createMock(), }, + { + provide: LegacyLogger, + useValue: createMock(), + }, ], }).compile(); diff --git a/apps/server/src/modules/task/service/task.service.ts b/apps/server/src/modules/task/service/task.service.ts index 2bb7d94f7ca..0472c23bf42 100644 --- a/apps/server/src/modules/task/service/task.service.ts +++ b/apps/server/src/modules/task/service/task.service.ts @@ -5,6 +5,7 @@ import { DomainOperation, IFindOptions } from '@shared/domain/interface'; import { Counted, DomainModel, EntityId } from '@shared/domain/types'; import { TaskRepo } from '@shared/repo'; import { DomainOperationBuilder } from '@shared/domain/builder'; +import { LegacyLogger } from '@src/core/logger'; import { SubmissionService } from './submission.service'; @Injectable() @@ -12,8 +13,11 @@ export class TaskService { constructor( private readonly taskRepo: TaskRepo, private readonly submissionService: SubmissionService, - private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService - ) {} + private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService, + private readonly logger: LegacyLogger + ) { + this.logger.setContext(TaskService.name); + } async findBySingleParent( creatorId: EntityId, @@ -44,6 +48,7 @@ export class TaskService { } async deleteTasksByOnlyCreator(creatorId: EntityId): Promise { + this.logger.log(`Deleting Tasks where creatorId ${creatorId} is only parent`); const [tasksByOnlyCreatorId, counterOfTasksOnlyWithCreatorId] = await this.taskRepo.findByOnlyCreatorId(creatorId); if (counterOfTasksOnlyWithCreatorId > 0) { @@ -52,11 +57,15 @@ export class TaskService { } const result = DomainOperationBuilder.build(DomainModel.TASK, 0, counterOfTasksOnlyWithCreatorId); + this.logger.log( + `Successfully deleted ${counterOfTasksOnlyWithCreatorId} where creatorId ${creatorId} is only parent` + ); return result; } async removeCreatorIdFromTasks(creatorId: EntityId): Promise { + this.logger.log(`Deleting creatorId ${creatorId} from Tasks`); const [tasksByCreatorIdWithCoursesAndLessons, counterOfTasksWithCoursesorLessons] = await this.taskRepo.findByCreatorIdWithCourseAndLesson(creatorId); @@ -66,11 +75,12 @@ export class TaskService { } const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksWithCoursesorLessons, 0); - + this.logger.log(`Successfully updated ${counterOfTasksWithCoursesorLessons} Tasks without creatorId ${creatorId}`); return result; } async removeUserFromFinished(userId: EntityId): Promise { + this.logger.log(`Deleting userId ${userId} from Archve collection in Tasks`); const [tasksWithUserInFinished, counterOfTasksWithUserInFinished] = await this.taskRepo.findByUserIdInFinished( userId ); @@ -82,6 +92,9 @@ export class TaskService { } const result = DomainOperationBuilder.build(DomainModel.TASK, counterOfTasksWithUserInFinished, 0); + this.logger.log( + `Successfully updated ${counterOfTasksWithUserInFinished} Tasks without userId ${userId} in archive collection in Tasks` + ); return result; } diff --git a/apps/server/src/modules/task/task.module.ts b/apps/server/src/modules/task/task.module.ts index 1e6fdb293bd..bd68fa8c5ef 100644 --- a/apps/server/src/modules/task/task.module.ts +++ b/apps/server/src/modules/task/task.module.ts @@ -2,11 +2,11 @@ import { CopyHelperModule } from '@modules/copy-helper'; import { FilesStorageClientModule } from '@modules/files-storage-client'; import { Module } from '@nestjs/common'; import { CourseRepo, SubmissionRepo, TaskRepo } from '@shared/repo'; -import { UserModule } from '@modules/user'; +import { LoggerModule } from '@src/core/logger'; import { SubmissionService, TaskCopyService, TaskService } from './service'; @Module({ - imports: [FilesStorageClientModule, CopyHelperModule, UserModule], + imports: [FilesStorageClientModule, CopyHelperModule, LoggerModule], providers: [TaskService, TaskCopyService, SubmissionService, TaskRepo, CourseRepo, SubmissionRepo], exports: [TaskService, TaskCopyService, SubmissionService], }) From aa0425b8ab5a96e4da1e98c6d37c308a222ab762 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 5 Jan 2024 13:29:46 +0100 Subject: [PATCH 23/23] fix in imports --- apps/server/src/modules/task/service/task.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6c51e8147af..fd77063c934 100644 --- a/apps/server/src/modules/task/service/task.service.spec.ts +++ b/apps/server/src/modules/task/service/task.service.spec.ts @@ -5,9 +5,9 @@ import { courseFactory, setupEntities, submissionFactory, taskFactory, userFacto import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { DomainModel } from '@shared/domain/types'; import { DomainOperationBuilder } from '@shared/domain/builder'; +import { LegacyLogger } from '@src/core/logger'; import { SubmissionService } from './submission.service'; import { TaskService } from './task.service'; -import { LegacyLogger } from '@src/core/logger'; describe('TaskService', () => { let module: TestingModule;