From ca59c48adc5c3efaf8cef24a9f9ef93e05852dd3 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 19 Oct 2023 23:19:24 +0200 Subject: [PATCH 01/22] first commit --- apps/server/src/apps/deletion.app.ts | 49 ++++++++++++++++ apps/server/src/config/database.config.ts | 3 +- .../src/modules/deletion/deletion.module.ts | 37 ++++++++++++ .../deletion/domain/deletion-log.do.ts | 37 ++++++++++++ .../deletion/domain/deletion-request.do.ts | 32 ++++++++++ .../deletion/entities/deletion-log.entity.ts | 58 +++++++++++++++++++ .../entities/deletion-request.entity.ts | 38 ++++++++++++ .../src/modules/deletion/entities/index.ts | 1 + apps/server/src/modules/deletion/index.ts | 1 + .../deletion/repo/deletion-log.repo.ts | 25 ++++++++ .../deletion/repo/deletion-request.repo.ts | 45 ++++++++++++++ .../server/src/modules/deletion/repo/index.ts | 1 + .../repo/mapper/deletion-log.mapper.ts | 37 ++++++++++++ .../repo/mapper/deletion-request.mapper.ts | 33 +++++++++++ .../services/deletion-request.service.ts | 32 ++++++++++ .../modules/deletion/uc/deletion-worker.us.ts | 16 +++++ 16 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/apps/deletion.app.ts create mode 100644 apps/server/src/modules/deletion/deletion.module.ts create mode 100644 apps/server/src/modules/deletion/domain/deletion-log.do.ts create mode 100644 apps/server/src/modules/deletion/domain/deletion-request.do.ts create mode 100644 apps/server/src/modules/deletion/entities/deletion-log.entity.ts create mode 100644 apps/server/src/modules/deletion/entities/deletion-request.entity.ts create mode 100644 apps/server/src/modules/deletion/entities/index.ts create mode 100644 apps/server/src/modules/deletion/index.ts create mode 100644 apps/server/src/modules/deletion/repo/deletion-log.repo.ts create mode 100644 apps/server/src/modules/deletion/repo/deletion-request.repo.ts create mode 100644 apps/server/src/modules/deletion/repo/index.ts create mode 100644 apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts create mode 100644 apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts create mode 100644 apps/server/src/modules/deletion/services/deletion-request.service.ts create mode 100644 apps/server/src/modules/deletion/uc/deletion-worker.us.ts diff --git a/apps/server/src/apps/deletion.app.ts b/apps/server/src/apps/deletion.app.ts new file mode 100644 index 00000000000..902831ba391 --- /dev/null +++ b/apps/server/src/apps/deletion.app.ts @@ -0,0 +1,49 @@ +/* istanbul ignore file */ +/* eslint-disable no-console */ +import { NestFactory } from '@nestjs/core'; +import { ExpressAdapter } from '@nestjs/platform-express'; +import express from 'express'; + +// register source-map-support for debugging +import { install as sourceMapInstall } from 'source-map-support'; + +// application imports +import { LegacyLogger } from '@src/core/logger'; +import { DeletionModule } from '@src/modules/deletion'; +import { enableOpenApiDocs } from '@src/shared/controller/swagger'; + +async function bootstrap() { + sourceMapInstall(); + + // create the NestJS application on a seperate express instance + const nestExpress = express(); + + const nestExpressAdapter = new ExpressAdapter(nestExpress); + const nestApp = await NestFactory.create(DeletionModule, nestExpressAdapter); + // WinstonLogger + nestApp.useLogger(await nestApp.resolve(LegacyLogger)); + + // customize nest app settings + nestApp.enableCors({ exposedHeaders: ['Content-Disposition'] }); + enableOpenApiDocs(nestApp, 'docs'); + + await nestApp.init(); + + // mount instances + const rootExpress = express(); + + const port = 4450; + const basePath = '/api/v3'; + + // exposed alias mounts + rootExpress.use(basePath, nestExpress); + rootExpress.listen(port); + + console.log('##########################################'); + console.log(`### Start KNL Deletion Server ###`); + console.log(`### Port: ${port} ###`); + console.log(`### Base path: ${basePath} ###`); + console.log('##########################################'); +} + +void bootstrap(); diff --git a/apps/server/src/config/database.config.ts b/apps/server/src/config/database.config.ts index ad97e4c3d66..ed0b3767e21 100644 --- a/apps/server/src/config/database.config.ts +++ b/apps/server/src/config/database.config.ts @@ -4,9 +4,10 @@ interface GlobalConstants { DB_URL: string; DB_PASSWORD?: string; DB_USERNAME?: string; + DEL_DB_URL: string; } const usedGlobals: GlobalConstants = globals; /** Database URL */ -export const { DB_URL, DB_PASSWORD, DB_USERNAME } = usedGlobals; +export const { DB_URL, DB_PASSWORD, DB_USERNAME, DEL_DB_URL } = usedGlobals; diff --git a/apps/server/src/modules/deletion/deletion.module.ts b/apps/server/src/modules/deletion/deletion.module.ts new file mode 100644 index 00000000000..4481d7ce492 --- /dev/null +++ b/apps/server/src/modules/deletion/deletion.module.ts @@ -0,0 +1,37 @@ +import { Module, NotFoundException } from '@nestjs/common'; +import { DB_PASSWORD, DB_USERNAME, DEL_DB_URL } from '@src/config'; +import { CoreModule } from '@src/core'; +import { Logger } from '@src/core/logger'; +import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; +import { AuthorizationModule } from '@src/modules'; +import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; +import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; +import { DeletionRequestService } from './services/deletion-request.service'; +import { DeletionRequestRepo } from './repo/deletion-request.repo'; + +const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { + findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), +}; + +@Module({ + imports: [ + AuthorizationModule, + AuthenticationModule, + CoreModule, + RabbitMQWrapperTestModule, + MikroOrmModule.forRoot({ + ...defaultMikroOrmOptions, + type: 'mongo', + clientUrl: DEL_DB_URL, + password: DB_PASSWORD, + user: DB_USERNAME, + entities: [], + }), + // ConfigModule.forRoot(createConfigModuleOptions(config)), + ], + providers: [Logger, DeletionRequestService, DeletionRequestRepo], +}) +export class DeletionModule {} diff --git a/apps/server/src/modules/deletion/domain/deletion-log.do.ts b/apps/server/src/modules/deletion/domain/deletion-log.do.ts new file mode 100644 index 00000000000..a5ae8db4b89 --- /dev/null +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.ts @@ -0,0 +1,37 @@ +import { EntityId } from '@shared/domain/types'; +import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; + +export interface DeletionLogProps extends AuthorizableObject { + createdAt: Date; + updatedAt: Date; + scope?: string; + operation?: string; + docIds?: EntityId[]; + deletionRequestId?: EntityId; +} + +export class DeletionLog extends DomainObject { + get createdAt(): Date { + return this.props.createdAt; + } + + get updatedAt(): Date { + return this.props.updatedAt; + } + + get scope(): string | undefined { + return this.props.scope; + } + + get operation(): string | undefined { + return this.props.operation; + } + + get deletionRequestId(): EntityId | undefined { + return this.props.deletionRequestId; + } + + get docIds(): EntityId[] | undefined { + return this.props.docIds; + } +} diff --git a/apps/server/src/modules/deletion/domain/deletion-request.do.ts b/apps/server/src/modules/deletion/domain/deletion-request.do.ts new file mode 100644 index 00000000000..3a85ecd345c --- /dev/null +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.ts @@ -0,0 +1,32 @@ +import { EntityId } from '@shared/domain/types'; +import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; + +export interface DeletionRequestProps extends AuthorizableObject { + createdAt?: Date; + updatedAt?: Date; + source?: string; + deleteAfter?: Date; + userId?: EntityId; +} + +export class DeletionRequest extends DomainObject { + get createdAt(): Date | undefined { + return this.props.createdAt; + } + + get updatedAt(): Date | undefined { + return this.props.updatedAt; + } + + get source(): string | undefined { + return this.props.source; + } + + get deleteAfter(): Date | undefined { + return this.props.deleteAfter; + } + + get userId(): EntityId | undefined { + return this.props.userId; + } +} diff --git a/apps/server/src/modules/deletion/entities/deletion-log.entity.ts b/apps/server/src/modules/deletion/entities/deletion-log.entity.ts new file mode 100644 index 00000000000..839655016fa --- /dev/null +++ b/apps/server/src/modules/deletion/entities/deletion-log.entity.ts @@ -0,0 +1,58 @@ +import { Entity, Property } from '@mikro-orm/core'; +import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; +import { ObjectId } from '@mikro-orm/mongodb'; + +export interface DeletionLogEntityProps { + id: EntityId; + createdAt?: Date; + updatedAt?: Date; + scope?: string; + operation?: string; + docIds?: ObjectId[]; + deletionRequestId?: ObjectId; +} + +@Entity({ tableName: 'deletionlogs' }) +export class DeletionLogEntity extends BaseEntityWithTimestamps { + @Property({ nullable: true }) + scope?: string; + + @Property({ nullable: true }) + operation?: string; + + @Property({ nullable: true }) + docIds?: ObjectId[]; + + @Property({ nullable: true }) + deletionRequestId?: ObjectId; + + constructor(props: DeletionLogEntityProps) { + super(); + + if (props.id !== undefined) { + this.id = props.id; + } + + if (props.createdAt !== undefined) { + this.createdAt = props.createdAt; + } + + if (props.scope !== undefined) { + this.scope = props.scope; + } + + if (props.operation !== undefined) { + this.operation = props.operation; + } + + if (props.updatedAt !== undefined) { + this.updatedAt = props.updatedAt; + } + + if (props.deletionRequestId !== undefined) { + this.deletionRequestId = props.deletionRequestId; + } + + this.docIds = props.docIds; + } +} diff --git a/apps/server/src/modules/deletion/entities/deletion-request.entity.ts b/apps/server/src/modules/deletion/entities/deletion-request.entity.ts new file mode 100644 index 00000000000..dd4ac0a2258 --- /dev/null +++ b/apps/server/src/modules/deletion/entities/deletion-request.entity.ts @@ -0,0 +1,38 @@ +import { Entity, Property } from '@mikro-orm/core'; +import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; +import { ObjectId } from '@mikro-orm/mongodb'; + +export interface DeletionRequestEntityProps { + id?: EntityId; + source?: string; + deleteAfter?: Date; + userId?: ObjectId; +} + +@Entity({ tableName: 'deletionrequests' }) +export class DeletionRequestEntity extends BaseEntityWithTimestamps { + @Property({ nullable: true }) + deleteAfter?: Date; + + @Property({ fieldName: 'userToDeletion', nullable: true }) + userId?: ObjectId; + + @Property({ nullable: true }) + source?: string; + + constructor(props: DeletionRequestEntityProps) { + super(); + + if (props.source !== undefined) { + this.source = props.source; + } + + if (props.deleteAfter !== undefined) { + this.deleteAfter = props.deleteAfter; + } + + if (props.userId !== undefined) { + this.userId = props.userId; + } + } +} diff --git a/apps/server/src/modules/deletion/entities/index.ts b/apps/server/src/modules/deletion/entities/index.ts new file mode 100644 index 00000000000..221c543f2a2 --- /dev/null +++ b/apps/server/src/modules/deletion/entities/index.ts @@ -0,0 +1 @@ +export * from './deletion-request.entity'; \ No newline at end of file diff --git a/apps/server/src/modules/deletion/index.ts b/apps/server/src/modules/deletion/index.ts new file mode 100644 index 00000000000..a2fbd9ee46b --- /dev/null +++ b/apps/server/src/modules/deletion/index.ts @@ -0,0 +1 @@ +export * from './deletion.module'; diff --git a/apps/server/src/modules/deletion/repo/deletion-log.repo.ts b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts new file mode 100644 index 00000000000..5d412f5ee67 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts @@ -0,0 +1,25 @@ +import { EntityManager } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { DeletionLog } from '../domain/deletion-log.do'; +import { DeletionLogEntity } from '../entities/deletion-log.entity'; +import { DeletionLogMapper } from './mapper/deletion-log.mapper'; + +@Injectable() +export class DeletionLogRepo { + constructor(private readonly em: EntityManager) {} + + async findById(id: EntityId): Promise { + const deletionRequest: DeletionLogEntity = await this.em.findOneOrFail(DeletionLogEntity, { id }); + + const mapped: DeletionLog = DeletionLogMapper.mapToDO(deletionRequest); + + return mapped; + } + + // create + + // update + + // delete +} diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts new file mode 100644 index 00000000000..fdd9729b1d2 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -0,0 +1,45 @@ +import { EntityManager } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { DeletionRequest } from '../domain/deletion-request.do'; +import { DeletionRequestEntity } from '../entities'; +import { DeletionRequestMapper } from './mapper/deletion-request.mapper'; + +@Injectable() +export class DeletionRequestRepo { + constructor(private readonly em: EntityManager) {} + + async findById(id: EntityId): Promise { + const deletionRequest: DeletionRequestEntity = await this.em.findOneOrFail(DeletionRequestEntity, { id }); + + const mapped: DeletionRequest = DeletionRequestMapper.mapToDO(deletionRequest); + + return mapped; + } + + // create + async create(deletionRequest: DeletionRequest): Promise { + const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); + await this.em.persistAndFlush(deletionRequestEntity); + } + + // update + async update(deletionRequest: DeletionRequest): Promise { + const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); + await this.em.persistAndFlush(deletionRequestEntity); + } + + // find + async findAllItemsByDeletionDate(): Promise { + const currentDate = new Date(); + const itemsToDelete: DeletionRequestEntity[] = await this.em.find(DeletionRequestEntity, { + deleteAfter: { $lt: currentDate }, + }); + + const mapped: DeletionRequest[] = itemsToDelete.map((entity) => DeletionRequestMapper.mapToDO(entity)); + + return mapped; + } + + // delete +} diff --git a/apps/server/src/modules/deletion/repo/index.ts b/apps/server/src/modules/deletion/repo/index.ts new file mode 100644 index 00000000000..d93472feca7 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/index.ts @@ -0,0 +1 @@ +export * from './classes.repo'; diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts new file mode 100644 index 00000000000..681497d9e43 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts @@ -0,0 +1,37 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionLogEntity } from '../../entities/deletion-log.entity'; +import { DeletionLog } from '../../domain/deletion-log.do'; + +export class DeletionLogMapper { + static mapToDO(entity: DeletionLogEntity): DeletionLog { + return new DeletionLog({ + id: entity.id, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + scope: entity.scope, + operation: entity.operation, + docIds: entity.docIds?.map((docId) => docId.toHexString()), + deletionRequestId: entity.deletionRequestId?.toHexString(), + }); + } + + static mapToEntity(domainObject: DeletionLog): DeletionLogEntity { + return new DeletionLogEntity({ + id: domainObject.id, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + scope: domainObject.scope, + operation: domainObject.operation, + docIds: domainObject.docIds?.map((docId) => new ObjectId(docId)), + deletionRequestId: new ObjectId(domainObject.deletionRequestId), + }); + } + + // static mapToDOs(entities: DeletionLogEntity[]): DeletionLog[] { + // return entities.map((entity) => this.mapToDO(entity)); + // } + + // static mapToEntities(domainObjects: DeletionLog[]): DeletionLogEntity[] { + // return domainObjects.map((domainObject) => this.mapToEntity(domainObject)); + // } +} diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts new file mode 100644 index 00000000000..6262ff7a6df --- /dev/null +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts @@ -0,0 +1,33 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionRequest } from '../../domain/deletion-request.do'; +import { DeletionRequestEntity } from '../../entities'; + +export class DeletionRequestMapper { + static mapToDO(entity: DeletionRequestEntity): DeletionRequest { + return new DeletionRequest({ + id: entity.id, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + source: entity.source, + deleteAfter: entity.deleteAfter, + userId: entity.userId?.toHexString(), + }); + } + + static mapToEntity(domainObject: DeletionRequest): DeletionRequestEntity { + return new DeletionRequestEntity({ + id: domainObject.id, + source: domainObject.source, + deleteAfter: domainObject.deleteAfter, + userId: new ObjectId(domainObject.userId), + }); + } + + // static mapToDOs(entities: DeletionRequestEntity[]): DeletionRequest[] { + // return entities.map((entity) => this.mapToDOs(entity)); + // } + + // static mapToEntities(domainObjects: DeletionRequest[]): DeletionRequestEntity[] { + // return domainObjects.map((domainObject) => this.mapToEntities(domainObject)); + // } +} diff --git a/apps/server/src/modules/deletion/services/deletion-request.service.ts b/apps/server/src/modules/deletion/services/deletion-request.service.ts new file mode 100644 index 00000000000..1f59221ecbe --- /dev/null +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionRequestRepo } from '../repo/deletion-request.repo'; +import { DeletionRequest } from '../domain/deletion-request.do'; + +@Injectable() +export class DeletionRequestService { + constructor(private readonly deletionRequestRepo: DeletionRequestRepo) {} + + async createDeletionRequest(userId: EntityId): Promise { + const newDeletionRequest = new DeletionRequest({ + id: new ObjectId().toHexString(), + source: 'shd', + deleteAfter: new Date(), + userId, + }); + + await this.deletionRequestRepo.create(newDeletionRequest); + } + + // updateService + async updateDeletionRequest(deletionRequest: DeletionRequest): Promise { + await this.deletionRequestRepo.update(deletionRequest); + } + + // findAll + async findAllItemsByDeletionDate(): Promise { + const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsByDeletionDate(); + return itemsToDelete; + } +} diff --git a/apps/server/src/modules/deletion/uc/deletion-worker.us.ts b/apps/server/src/modules/deletion/uc/deletion-worker.us.ts new file mode 100644 index 00000000000..9dbb19b41aa --- /dev/null +++ b/apps/server/src/modules/deletion/uc/deletion-worker.us.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { AuthorizationService } from '@src/modules/authorization'; +import { DeletionRequestService } from '../services/deletion-request.service'; +import { DeletionRequest } from '../domain/deletion-request.do'; + +@Injectable() +export class DeletionWorkerUc { + constructor( + private readonly deletionRequestService: DeletionRequestService, + private readonly authorizationService: AuthorizationService + ) {} + + async findAllItemsByDeletionDate(): Promise { + return this.deletionRequestService.findAllItemsByDeletionDate(); + } +} From 5838b81e9a45ea4191f865000cba03385d228fca Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 20 Oct 2023 22:31:08 +0200 Subject: [PATCH 02/22] add some tests --- .../src/modules/deletion/entities/index.ts | 1 - .../deletion-log.entity.ts | 0 .../deletion-request.entity.ts | 0 .../src/modules/deletion/entity/index.ts | 2 ++ .../testing/deletion-request.entity.spec.ts | 19 +++++++++++++++++++ .../deletion-request.entity.factory.ts | 14 ++++++++++++++ .../deletion/repo/deletion-log.repo.ts | 2 +- .../deletion/repo/deletion-request.repo.ts | 2 +- .../server/src/modules/deletion/repo/index.ts | 3 ++- .../repo/mapper/deletion-log.mapper.ts | 2 +- .../repo/mapper/deletion-request.mapper.ts | 2 +- 11 files changed, 41 insertions(+), 6 deletions(-) delete mode 100644 apps/server/src/modules/deletion/entities/index.ts rename apps/server/src/modules/deletion/{entities => entity}/deletion-log.entity.ts (100%) rename apps/server/src/modules/deletion/{entities => entity}/deletion-request.entity.ts (100%) create mode 100644 apps/server/src/modules/deletion/entity/index.ts create mode 100644 apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts create mode 100644 apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts diff --git a/apps/server/src/modules/deletion/entities/index.ts b/apps/server/src/modules/deletion/entities/index.ts deleted file mode 100644 index 221c543f2a2..00000000000 --- a/apps/server/src/modules/deletion/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './deletion-request.entity'; \ No newline at end of file diff --git a/apps/server/src/modules/deletion/entities/deletion-log.entity.ts b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts similarity index 100% rename from apps/server/src/modules/deletion/entities/deletion-log.entity.ts rename to apps/server/src/modules/deletion/entity/deletion-log.entity.ts diff --git a/apps/server/src/modules/deletion/entities/deletion-request.entity.ts b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts similarity index 100% rename from apps/server/src/modules/deletion/entities/deletion-request.entity.ts rename to apps/server/src/modules/deletion/entity/deletion-request.entity.ts diff --git a/apps/server/src/modules/deletion/entity/index.ts b/apps/server/src/modules/deletion/entity/index.ts new file mode 100644 index 00000000000..7e3e31dcd19 --- /dev/null +++ b/apps/server/src/modules/deletion/entity/index.ts @@ -0,0 +1,2 @@ +export * from './deletion-request.entity'; +export * from './deletion-log.entity'; diff --git a/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts b/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts new file mode 100644 index 00000000000..ef279ee6a08 --- /dev/null +++ b/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts @@ -0,0 +1,19 @@ +import { setupEntities } from '@shared/testing'; +import { DeletionRequestEntity } from '../deletion-request.entity'; +import { deletionRequestEntityFactory } from './factory/deletion-request.entity.factory'; + +describe(DeletionRequestEntity.name, () => { + beforeAll(async () => { + await setupEntities(); + }); + + describe('constructor', () => { + describe('When constructor is called', () => { + it('should create a deletionRequest by passing required properties', () => { + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build(); + + expect(entity instanceof DeletionRequestEntity).toEqual(true); + }); + }); + }); +}); 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 new file mode 100644 index 00000000000..82cc7290c54 --- /dev/null +++ b/apps/server/src/modules/deletion/entity/testing/factory/deletion-request.entity.factory.ts @@ -0,0 +1,14 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseFactory } from '@shared/testing'; +import { DeletionRequestEntity, DeletionRequestEntityProps } from '../..'; + +export const deletionRequestEntityFactory = BaseFactory.define( + DeletionRequestEntity, + () => { + return { + source: 'shd', + deleteAfter: new Date(), + userId: new ObjectId(), + }; + } +); diff --git a/apps/server/src/modules/deletion/repo/deletion-log.repo.ts b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts index 5d412f5ee67..0c0ace43c6d 100644 --- a/apps/server/src/modules/deletion/repo/deletion-log.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts @@ -2,7 +2,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { DeletionLog } from '../domain/deletion-log.do'; -import { DeletionLogEntity } from '../entities/deletion-log.entity'; +import { DeletionLogEntity } from '../entity/deletion-log.entity'; import { DeletionLogMapper } from './mapper/deletion-log.mapper'; @Injectable() diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts index fdd9729b1d2..b386596733c 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -2,7 +2,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { DeletionRequest } from '../domain/deletion-request.do'; -import { DeletionRequestEntity } from '../entities'; +import { DeletionRequestEntity } from '../entity'; import { DeletionRequestMapper } from './mapper/deletion-request.mapper'; @Injectable() diff --git a/apps/server/src/modules/deletion/repo/index.ts b/apps/server/src/modules/deletion/repo/index.ts index d93472feca7..68860c00a79 100644 --- a/apps/server/src/modules/deletion/repo/index.ts +++ b/apps/server/src/modules/deletion/repo/index.ts @@ -1 +1,2 @@ -export * from './classes.repo'; +export * from './deletion-log.repo'; +export * from './deletion-request.repo'; diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts index 681497d9e43..9c3656e4d34 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts @@ -1,5 +1,5 @@ import { ObjectId } from '@mikro-orm/mongodb'; -import { DeletionLogEntity } from '../../entities/deletion-log.entity'; +import { DeletionLogEntity } from '../../entity/deletion-log.entity'; import { DeletionLog } from '../../domain/deletion-log.do'; export class DeletionLogMapper { diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts index 6262ff7a6df..879352117b6 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequest } from '../../domain/deletion-request.do'; -import { DeletionRequestEntity } from '../../entities'; +import { DeletionRequestEntity } from '../../entity'; export class DeletionRequestMapper { static mapToDO(entity: DeletionRequestEntity): DeletionRequest { From 7df7c23f85f0752d8ef6fad4d52becb031f855e2 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 27 Oct 2023 08:51:31 +0200 Subject: [PATCH 03/22] add test cases and services --- apps/server/src/apps/deletion.app.ts | 49 ---- apps/server/src/config/database.config.ts | 3 +- .../src/modules/deletion/deletion.module.ts | 57 ++--- .../deletion/domain/deletion-log.do.spec.ts | 64 +++++ .../deletion/domain/deletion-log.do.ts | 27 ++- .../domain/deletion-request.do.spec.ts | 62 +++++ .../deletion/domain/deletion-request.do.ts | 19 +- .../testing/factory/deletion-log.factory.ts | 18 ++ .../factory/deletion-request.factory.ts | 28 +++ .../types/deletion-domain-model.enum.ts | 7 + .../types/deletion-operation-model.enum.ts | 4 + .../types/deletion-status-model.enum.ts | 4 + .../entity/deletion-log.entity.spec.ts | 60 +++++ .../deletion/entity/deletion-log.entity.ts | 47 ++-- .../entity/deletion-request.entity.spec.ts | 59 +++++ .../entity/deletion-request.entity.ts | 51 +++- .../testing/deletion-request.entity.spec.ts | 19 -- .../factory/deletion-log.entity.factory.ts | 21 ++ .../deletion-request.entity.factory.ts | 12 +- .../deletion/repo/deletion-log.repo.spec.ts | 186 +++++++++++++++ .../deletion/repo/deletion-log.repo.ts | 30 ++- .../repo/deletion-request.repo.spec.ts | 218 ++++++++++++++++++ .../deletion/repo/deletion-request.repo.ts | 40 +++- .../repo/mapper/deletion-log.mapper.spec.ts | 139 +++++++++++ .../repo/mapper/deletion-log.mapper.ts | 22 +- .../mapper/deletion-request.mapper.spec.ts | 60 +++++ .../repo/mapper/deletion-request.mapper.ts | 20 +- .../src/modules/deletion/repo/mapper/index.ts | 2 + .../deletion/services/deletion-log.service.ts | 40 ++++ .../services/deletion-request.service.spec.ts | 133 +++++++++++ .../services/deletion-request.service.ts | 33 ++- .../src/modules/deletion/services/index.ts | 1 + 32 files changed, 1340 insertions(+), 195 deletions(-) delete mode 100644 apps/server/src/apps/deletion.app.ts create mode 100644 apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts create mode 100644 apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts create mode 100644 apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts create mode 100644 apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts create mode 100644 apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts create mode 100644 apps/server/src/modules/deletion/domain/types/deletion-operation-model.enum.ts create mode 100644 apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts create mode 100644 apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts create mode 100644 apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts delete mode 100644 apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts create mode 100644 apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts create mode 100644 apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts create mode 100644 apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts create mode 100644 apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts create mode 100644 apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts create mode 100644 apps/server/src/modules/deletion/repo/mapper/index.ts create mode 100644 apps/server/src/modules/deletion/services/deletion-log.service.ts create mode 100644 apps/server/src/modules/deletion/services/deletion-request.service.spec.ts create mode 100644 apps/server/src/modules/deletion/services/index.ts diff --git a/apps/server/src/apps/deletion.app.ts b/apps/server/src/apps/deletion.app.ts deleted file mode 100644 index 902831ba391..00000000000 --- a/apps/server/src/apps/deletion.app.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* istanbul ignore file */ -/* eslint-disable no-console */ -import { NestFactory } from '@nestjs/core'; -import { ExpressAdapter } from '@nestjs/platform-express'; -import express from 'express'; - -// register source-map-support for debugging -import { install as sourceMapInstall } from 'source-map-support'; - -// application imports -import { LegacyLogger } from '@src/core/logger'; -import { DeletionModule } from '@src/modules/deletion'; -import { enableOpenApiDocs } from '@src/shared/controller/swagger'; - -async function bootstrap() { - sourceMapInstall(); - - // create the NestJS application on a seperate express instance - const nestExpress = express(); - - const nestExpressAdapter = new ExpressAdapter(nestExpress); - const nestApp = await NestFactory.create(DeletionModule, nestExpressAdapter); - // WinstonLogger - nestApp.useLogger(await nestApp.resolve(LegacyLogger)); - - // customize nest app settings - nestApp.enableCors({ exposedHeaders: ['Content-Disposition'] }); - enableOpenApiDocs(nestApp, 'docs'); - - await nestApp.init(); - - // mount instances - const rootExpress = express(); - - const port = 4450; - const basePath = '/api/v3'; - - // exposed alias mounts - rootExpress.use(basePath, nestExpress); - rootExpress.listen(port); - - console.log('##########################################'); - console.log(`### Start KNL Deletion Server ###`); - console.log(`### Port: ${port} ###`); - console.log(`### Base path: ${basePath} ###`); - console.log('##########################################'); -} - -void bootstrap(); diff --git a/apps/server/src/config/database.config.ts b/apps/server/src/config/database.config.ts index ed0b3767e21..ad97e4c3d66 100644 --- a/apps/server/src/config/database.config.ts +++ b/apps/server/src/config/database.config.ts @@ -4,10 +4,9 @@ interface GlobalConstants { DB_URL: string; DB_PASSWORD?: string; DB_USERNAME?: string; - DEL_DB_URL: string; } const usedGlobals: GlobalConstants = globals; /** Database URL */ -export const { DB_URL, DB_PASSWORD, DB_USERNAME, DEL_DB_URL } = usedGlobals; +export const { DB_URL, DB_PASSWORD, DB_USERNAME } = usedGlobals; diff --git a/apps/server/src/modules/deletion/deletion.module.ts b/apps/server/src/modules/deletion/deletion.module.ts index 4481d7ce492..7d508b1650a 100644 --- a/apps/server/src/modules/deletion/deletion.module.ts +++ b/apps/server/src/modules/deletion/deletion.module.ts @@ -1,37 +1,38 @@ -import { Module, NotFoundException } from '@nestjs/common'; -import { DB_PASSWORD, DB_USERNAME, DEL_DB_URL } from '@src/config'; -import { CoreModule } from '@src/core'; +import { Module } from '@nestjs/common'; +// import { Module, NotFoundException } from '@nestjs/common'; +// import { DB_PASSWORD, DB_USERNAME, DEL_DB_URL } from '@src/config'; +// import { CoreModule } from '@src/core'; import { Logger } from '@src/core/logger'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -import { AuthorizationModule } from '@src/modules'; -import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; +// import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +// import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; +// import { AuthorizationModule } from '@src/modules'; +// import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; +// import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; import { DeletionRequestService } from './services/deletion-request.service'; import { DeletionRequestRepo } from './repo/deletion-request.repo'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; +// const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { +// findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => +// // eslint-disable-next-line @typescript-eslint/restrict-template-expressions +// new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), +// }; @Module({ - imports: [ - AuthorizationModule, - AuthenticationModule, - CoreModule, - RabbitMQWrapperTestModule, - MikroOrmModule.forRoot({ - ...defaultMikroOrmOptions, - type: 'mongo', - clientUrl: DEL_DB_URL, - password: DB_PASSWORD, - user: DB_USERNAME, - entities: [], - }), - // ConfigModule.forRoot(createConfigModuleOptions(config)), - ], + // imports: [ + // AuthorizationModule, + // AuthenticationModule, + // CoreModule, + // RabbitMQWrapperTestModule, + // MikroOrmModule.forRoot({ + // ...defaultMikroOrmOptions, + // type: 'mongo', + // clientUrl: DEL_DB_URL, + // password: DB_PASSWORD, + // user: DB_USERNAME, + // entities: [], + // }), + // ConfigModule.forRoot(createConfigModuleOptions(config)), + // ], providers: [Logger, DeletionRequestService, DeletionRequestRepo], }) export class DeletionModule {} 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 new file mode 100644 index 00000000000..b18e4759af5 --- /dev/null +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.spec.ts @@ -0,0 +1,64 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { deletionLogFactory } from './testing/factory/deletion-log.factory'; +import { DeletionLog } from './deletion-log.do'; +import { DeletionOperationModel } from './types/deletion-operation-model.enum'; +import { DeletionDomainModel } from './types/deletion-domain-model.enum'; + +describe(DeletionLog.name, () => { + describe('constructor', () => { + describe('When constructor is called', () => { + it('should create a deletionRequest by passing required properties', () => { + const domainObject: DeletionLog = deletionLogFactory.build(); + + expect(domainObject instanceof DeletionLog).toEqual(true); + }); + }); + + describe('when passed a valid id', () => { + const setup = () => { + const domainObject: DeletionLog = deletionLogFactory.buildWithId(); + + return { domainObject }; + }; + + it('should set the id', () => { + const { domainObject } = setup(); + + const deletionLogDomainObject: DeletionLog = new DeletionLog(domainObject); + + expect(deletionLogDomainObject.id).toEqual(domainObject.id); + }); + }); + }); + + describe('getters', () => { + describe('When getters are used', () => { + it('getters should return proper values', () => { + const props = { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + operation: DeletionOperationModel.DELETE, + modifiedCounter: 0, + deletedCounter: 1, + deletionRequestId: new ObjectId().toHexString(), + createdAt: new Date(), + updatedAt: new Date(), + }; + + const deletionLogDo = new DeletionLog(props); + const gettersValues = { + id: deletionLogDo.id, + domain: deletionLogDo.domain, + operation: deletionLogDo.operation, + modifiedCounter: deletionLogDo.modifiedCounter, + deletedCounter: deletionLogDo.deletedCounter, + deletionRequestId: deletionLogDo.deletionRequestId, + createdAt: deletionLogDo.createdAt, + updatedAt: deletionLogDo.updatedAt, + }; + + expect(gettersValues).toEqual(props); + }); + }); + }); +}); 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 a5ae8db4b89..89391ca65a8 100644 --- a/apps/server/src/modules/deletion/domain/deletion-log.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.ts @@ -1,12 +1,15 @@ import { EntityId } from '@shared/domain/types'; import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; +import { DeletionDomainModel } from './types/deletion-domain-model.enum'; +import { DeletionOperationModel } from './types/deletion-operation-model.enum'; export interface DeletionLogProps extends AuthorizableObject { createdAt: Date; updatedAt: Date; - scope?: string; - operation?: string; - docIds?: EntityId[]; + domain?: DeletionDomainModel; + operation?: DeletionOperationModel; + modifiedCounter?: number; + deletedCounter?: number; deletionRequestId?: EntityId; } @@ -19,19 +22,23 @@ export class DeletionLog extends DomainObject { return this.props.updatedAt; } - get scope(): string | undefined { - return this.props.scope; + get domain(): DeletionDomainModel | undefined { + return this.props.domain; } - get operation(): string | undefined { + get operation(): DeletionOperationModel | undefined { return this.props.operation; } - get deletionRequestId(): EntityId | undefined { - return this.props.deletionRequestId; + get modifiedCounter(): number | undefined { + return this.props.modifiedCounter; + } + + get deletedCounter(): number | undefined { + return this.props.deletedCounter; } - get docIds(): EntityId[] | undefined { - return this.props.docIds; + get deletionRequestId(): EntityId | undefined { + return this.props.deletionRequestId; } } 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 new file mode 100644 index 00000000000..a2262f0b614 --- /dev/null +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.spec.ts @@ -0,0 +1,62 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionRequest } from './deletion-request.do'; +import { DeletionDomainModel } from './types/deletion-domain-model.enum'; +import { deletionRequestFactory } from './testing/factory/deletion-request.factory'; +import { DeletionStatusModel } from './types/deletion-status-model.enum'; + +describe(DeletionRequest.name, () => { + describe('constructor', () => { + describe('When constructor is called', () => { + it('should create a deletionRequest by passing required properties', () => { + const domainObject: DeletionRequest = deletionRequestFactory.build(); + + expect(domainObject instanceof DeletionRequest).toEqual(true); + }); + }); + + describe('when passed a valid id', () => { + const setup = () => { + const domainObject: DeletionRequest = deletionRequestFactory.buildWithId(); + + return { domainObject }; + }; + + it('should set the id', () => { + const { domainObject } = setup(); + + const deletionRequestDomainObject: DeletionRequest = new DeletionRequest(domainObject); + + expect(deletionRequestDomainObject.id).toEqual(domainObject.id); + }); + }); + }); + + describe('getters', () => { + describe('When getters are used', () => { + it('getters should return proper values', () => { + const props = { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + deleteAfter: new Date(), + itemId: new ObjectId().toHexString(), + status: DeletionStatusModel.REGISTERED, + createdAt: new Date(), + updatedAt: new Date(), + }; + + const deletionRequestDo = new DeletionRequest(props); + const gettersValues = { + id: deletionRequestDo.id, + domain: deletionRequestDo.domain, + deleteAfter: deletionRequestDo.deleteAfter, + itemId: deletionRequestDo.itemId, + status: deletionRequestDo.status, + createdAt: deletionRequestDo.createdAt, + updatedAt: deletionRequestDo.updatedAt, + }; + + expect(gettersValues).toEqual(props); + }); + }); + }); +}); 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 3a85ecd345c..881560359bc 100644 --- a/apps/server/src/modules/deletion/domain/deletion-request.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.ts @@ -1,12 +1,15 @@ import { EntityId } from '@shared/domain/types'; import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; +import { DeletionDomainModel } from './types/deletion-domain-model.enum'; +import { DeletionStatusModel } from './types/deletion-status-model.enum'; export interface DeletionRequestProps extends AuthorizableObject { createdAt?: Date; updatedAt?: Date; - source?: string; + domain?: DeletionDomainModel; deleteAfter?: Date; - userId?: EntityId; + itemId?: EntityId; + status?: DeletionStatusModel; } export class DeletionRequest extends DomainObject { @@ -18,15 +21,19 @@ export class DeletionRequest extends DomainObject { return this.props.updatedAt; } - get source(): string | undefined { - return this.props.source; + get domain(): DeletionDomainModel | undefined { + return this.props.domain; } get deleteAfter(): Date | undefined { return this.props.deleteAfter; } - get userId(): EntityId | undefined { - return this.props.userId; + get itemId(): EntityId | undefined { + return this.props.itemId; + } + + get status(): DeletionStatusModel | undefined { + return this.props.status; } } 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 new file mode 100644 index 00000000000..25249fa3518 --- /dev/null +++ b/apps/server/src/modules/deletion/domain/testing/factory/deletion-log.factory.ts @@ -0,0 +1,18 @@ +import { DoBaseFactory } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionLog, DeletionLogProps } from '../../deletion-log.do'; +import { DeletionOperationModel } from '../../types/deletion-operation-model.enum'; +import { DeletionDomainModel } from '../../types/deletion-domain-model.enum'; + +export const deletionLogFactory = DoBaseFactory.define(DeletionLog, () => { + return { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + operation: DeletionOperationModel.DELETE, + modifiedCounter: 0, + deletedCounter: 1, + deletionRequestId: new ObjectId().toHexString(), + createdAt: new Date(), + updatedAt: new Date(), + }; +}); 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 new file mode 100644 index 00000000000..36cad2e8aa0 --- /dev/null +++ b/apps/server/src/modules/deletion/domain/testing/factory/deletion-request.factory.ts @@ -0,0 +1,28 @@ +import { DoBaseFactory } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeepPartial } from 'fishery'; +import { DeletionRequest, DeletionRequestProps } from '../../deletion-request.do'; +import { DeletionDomainModel } from '../../types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '../../types/deletion-status-model.enum'; + +class DeletionRequestFactory extends DoBaseFactory { + withUserIds(itemId: string): this { + const params: DeepPartial = { + itemId, + }; + + return this.params(params); + } +} + +export const deletionRequestFactory = DeletionRequestFactory.define(DeletionRequest, () => { + return { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + deleteAfter: new Date(), + itemId: new ObjectId().toHexString(), + status: DeletionStatusModel.REGISTERED, + createdAt: new Date(), + updatedAt: new Date(), + }; +}); diff --git a/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts new file mode 100644 index 00000000000..7df1a28e4fe --- /dev/null +++ b/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts @@ -0,0 +1,7 @@ +export const enum DeletionDomainModel { + USER = 'user', + TEAMS = 'teams', + LESSONS = 'lessons', + PSEUDONYMS = 'pseudonyms', + ACCOUNT = 'account', +} diff --git a/apps/server/src/modules/deletion/domain/types/deletion-operation-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-operation-model.enum.ts new file mode 100644 index 00000000000..675189e634b --- /dev/null +++ b/apps/server/src/modules/deletion/domain/types/deletion-operation-model.enum.ts @@ -0,0 +1,4 @@ +export const enum DeletionOperationModel { + DELETE = 'delete', + UPDATE = 'update', +} diff --git a/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts new file mode 100644 index 00000000000..884cccdf68f --- /dev/null +++ b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts @@ -0,0 +1,4 @@ +export enum DeletionStatusModel { + 'REGISTERED' = 'registered', + 'SUCCESS' = 'success', +} 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 new file mode 100644 index 00000000000..022582d34c2 --- /dev/null +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.spec.ts @@ -0,0 +1,60 @@ +import { setupEntities } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionLogEntity } from './deletion-log.entity'; +import { DeletionOperationModel } from '../domain/types/deletion-operation-model.enum'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; + +describe(DeletionLogEntity.name, () => { + beforeAll(async () => { + await setupEntities(); + }); + + describe('constructor', () => { + describe('When constructor is called', () => { + const setup = () => { + const props = { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + operation: DeletionOperationModel.DELETE, + modifiedCounter: 0, + deletedCounter: 1, + deletionRequestId: new ObjectId(), + createdAt: new Date(), + updatedAt: new Date(), + }; + + return { props }; + }; + it('should throw an error by empty constructor', () => { + // @ts-expect-error: Test case + const test = () => new DeletionLogEntity(); + expect(test).toThrow(); + }); + + it('should create a deletionLog by passing required properties', () => { + const { props } = setup(); + const entity: DeletionLogEntity = new DeletionLogEntity(props); + + expect(entity instanceof DeletionLogEntity).toEqual(true); + }); + + it(`should return a valid object with fields values set from the provided complete props object`, () => { + const { props } = setup(); + const entity: DeletionLogEntity = new DeletionLogEntity(props); + + const entityProps = { + id: entity.id, + domain: entity.domain, + operation: entity.operation, + modifiedCounter: entity.modifiedCounter, + deletedCounter: entity.deletedCounter, + deletionRequestId: entity.deletionRequestId, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + expect(entityProps).toEqual(props); + }); + }); + }); +}); 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 839655016fa..f2139ca6aaa 100644 --- a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts @@ -1,58 +1,69 @@ import { Entity, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionOperationModel } from '../domain/types/deletion-operation-model.enum'; export interface DeletionLogEntityProps { - id: EntityId; + id?: EntityId; + domain?: DeletionDomainModel; + operation?: DeletionOperationModel; + modifiedCounter?: number; + deletedCounter?: number; + deletionRequestId?: ObjectId; createdAt?: Date; updatedAt?: Date; - scope?: string; - operation?: string; - docIds?: ObjectId[]; - deletionRequestId?: ObjectId; } @Entity({ tableName: 'deletionlogs' }) export class DeletionLogEntity extends BaseEntityWithTimestamps { @Property({ nullable: true }) - scope?: string; + domain?: DeletionDomainModel; @Property({ nullable: true }) - operation?: string; + operation?: DeletionOperationModel; @Property({ nullable: true }) - docIds?: ObjectId[]; + modifiedCounter?: number; + + @Property({ nullable: true }) + deletedCounter?: number; @Property({ nullable: true }) deletionRequestId?: ObjectId; constructor(props: DeletionLogEntityProps) { super(); - if (props.id !== undefined) { this.id = props.id; } - if (props.createdAt !== undefined) { - this.createdAt = props.createdAt; - } - - if (props.scope !== undefined) { - this.scope = props.scope; + if (props.domain !== undefined) { + this.domain = props.domain; } if (props.operation !== undefined) { this.operation = props.operation; } - if (props.updatedAt !== undefined) { - this.updatedAt = props.updatedAt; + if (props.modifiedCounter !== undefined) { + this.modifiedCounter = props.modifiedCounter; + } + + if (props.deletedCounter !== undefined) { + this.deletedCounter = props.deletedCounter; } if (props.deletionRequestId !== undefined) { this.deletionRequestId = props.deletionRequestId; } - this.docIds = props.docIds; + if (props.createdAt !== undefined) { + this.createdAt = props.createdAt; + } + + if (props.updatedAt !== undefined) { + this.updatedAt = props.updatedAt; + } } } 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 new file mode 100644 index 00000000000..fca7bb87b57 --- /dev/null +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.spec.ts @@ -0,0 +1,59 @@ +import { setupEntities } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DeletionRequestEntity } from '@src/modules/deletion/entity/deletion-request.entity'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; + +describe(DeletionRequestEntity.name, () => { + beforeAll(async () => { + await setupEntities(); + }); + + describe('constructor', () => { + describe('When constructor is called', () => { + const setup = () => { + const props = { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + deleteAfter: new Date(), + itemId: new ObjectId().toHexString(), + status: DeletionStatusModel.REGISTERED, + createdAt: new Date(), + updatedAt: new Date(), + }; + + return { props }; + }; + + it('should throw an error by empty constructor', () => { + // @ts-expect-error: Test case + const test = () => new DeletionRequestEntity(); + expect(test).toThrow(); + }); + + it('should create a deletionRequest by passing required properties', () => { + const { props } = setup(); + const entity: DeletionRequestEntity = new DeletionRequestEntity(props); + + expect(entity instanceof DeletionRequestEntity).toEqual(true); + }); + + it(`should return a valid object with fields values set from the provided complete props object`, () => { + const { props } = setup(); + const entity: DeletionRequestEntity = new DeletionRequestEntity(props); + + const entityProps = { + id: entity.id, + domain: entity.domain, + deleteAfter: entity.deleteAfter, + itemId: entity.itemId, + status: entity.status, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + expect(entityProps).toEqual(props); + }); + }); + }); +}); 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 dd4ac0a2258..f76dbedcb8b 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,18 @@ -import { Entity, Property } from '@mikro-orm/core'; -import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; +import { Entity, Index, Property } from '@mikro-orm/core'; +import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { ObjectId } from '@mikro-orm/mongodb'; +import { EntityId } from '@shared/domain'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; export interface DeletionRequestEntityProps { id?: EntityId; - source?: string; + domain?: DeletionDomainModel; deleteAfter?: Date; - userId?: ObjectId; + itemId?: EntityId; + status?: DeletionStatusModel; + createdAt?: Date; + updatedAt?: Date; } @Entity({ tableName: 'deletionrequests' }) @@ -14,25 +20,48 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { @Property({ nullable: true }) deleteAfter?: Date; - @Property({ fieldName: 'userToDeletion', nullable: true }) - userId?: ObjectId; + @Property({ fieldName: 'itemToDeletion', nullable: true }) + @Index() + _itemId?: ObjectId; + + get itemId(): EntityId | undefined { + return this._itemId?.toHexString(); + } + + @Property({ nullable: true }) + domain?: DeletionDomainModel; @Property({ nullable: true }) - source?: string; + status?: DeletionStatusModel; constructor(props: DeletionRequestEntityProps) { super(); + if (props.id !== undefined) { + this.id = props.id; + } - if (props.source !== undefined) { - this.source = props.source; + if (props.domain !== undefined) { + this.domain = props.domain; } if (props.deleteAfter !== undefined) { this.deleteAfter = props.deleteAfter; } - if (props.userId !== undefined) { - this.userId = props.userId; + if (props.itemId !== undefined) { + this._itemId = new ObjectId(props.itemId); + } + + if (props.status !== undefined) { + this.status = props.status; + } + + if (props.createdAt !== undefined) { + this.createdAt = props.createdAt; + } + + if (props.updatedAt !== undefined) { + this.updatedAt = props.updatedAt; } } } diff --git a/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts b/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts deleted file mode 100644 index ef279ee6a08..00000000000 --- a/apps/server/src/modules/deletion/entity/testing/deletion-request.entity.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { setupEntities } from '@shared/testing'; -import { DeletionRequestEntity } from '../deletion-request.entity'; -import { deletionRequestEntityFactory } from './factory/deletion-request.entity.factory'; - -describe(DeletionRequestEntity.name, () => { - beforeAll(async () => { - await setupEntities(); - }); - - describe('constructor', () => { - describe('When constructor is called', () => { - it('should create a deletionRequest by passing required properties', () => { - const entity: DeletionRequestEntity = deletionRequestEntityFactory.build(); - - expect(entity instanceof DeletionRequestEntity).toEqual(true); - }); - }); - }); -}); 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 new file mode 100644 index 00000000000..ed7f975cc0f --- /dev/null +++ b/apps/server/src/modules/deletion/entity/testing/factory/deletion-log.entity.factory.ts @@ -0,0 +1,21 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseFactory } from '@shared/testing'; +import { DeletionLogEntity, DeletionLogEntityProps } from '@src/modules/deletion/entity'; +import { DeletionOperationModel } from '@src/modules/deletion/domain/types/deletion-operation-model.enum'; +import { DeletionDomainModel } from '@src/modules/deletion/domain/types/deletion-domain-model.enum'; + +export const deletionLogEntityFactory = BaseFactory.define( + DeletionLogEntity, + () => { + return { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + operation: DeletionOperationModel.DELETE, + modifiedCounter: 0, + deletedCounter: 1, + deletionRequestId: new ObjectId(), + createdAt: new Date(), + updatedAt: new Date(), + }; + } +); 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 82cc7290c54..6e1a0f326ec 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,14 +1,20 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing'; -import { DeletionRequestEntity, DeletionRequestEntityProps } from '../..'; +import { DeletionDomainModel } from '@src/modules/deletion/domain/types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '@src/modules/deletion/domain/types/deletion-status-model.enum'; +import { DeletionRequestEntity, DeletionRequestEntityProps } from '@src/modules/deletion/entity'; export const deletionRequestEntityFactory = BaseFactory.define( DeletionRequestEntity, () => { return { - source: 'shd', + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, deleteAfter: new Date(), - userId: new ObjectId(), + itemId: new ObjectId().toHexString(), + status: DeletionStatusModel.REGISTERED, + createdAt: new Date(), + updatedAt: new Date(), }; } ); diff --git a/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts new file mode 100644 index 00000000000..c36a00753a7 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts @@ -0,0 +1,186 @@ +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Test } from '@nestjs/testing'; +import { TestingModule } from '@nestjs/testing/testing-module'; +import { MongoMemoryDatabaseModule } from '@shared/infra/database'; +import { cleanupCollections } from '@shared/testing'; +import { DeletionLogMapper } from './mapper'; +import { DeletionLogEntity } from '../entity'; +import { DeletionLogRepo } from './deletion-log.repo'; +import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; +import { DeletionLog } from '../domain/deletion-log.do'; +import { deletionLogEntityFactory } from '../entity/testing/factory/deletion-log.entity.factory'; + +describe(DeletionLogRepo.name, () => { + let module: TestingModule; + let repo: DeletionLogRepo; + let em: EntityManager; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ + MongoMemoryDatabaseModule.forRoot({ + entities: [DeletionLogEntity], + }), + ], + providers: [DeletionLogRepo, DeletionLogMapper], + }).compile(); + + repo = module.get(DeletionLogRepo); + em = module.get(EntityManager); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(async () => { + await cleanupCollections(em); + }); + + describe('defined', () => { + it('repo should be defined', () => { + expect(repo).toBeDefined(); + }); + + it('entity manager should be defined', () => { + expect(em).toBeDefined(); + }); + + it('should implement entityName getter', () => { + expect(repo.entityName).toBe(DeletionLogEntity); + }); + }); + + describe('create deletionLog', () => { + describe('when deletionLog is new', () => { + it('should create a new deletionLog', async () => { + const domainObject: DeletionLog = deletionLogFactory.build(); + const deletionLogId = domainObject.id; + await repo.create(domainObject); + + const expectedDomainObject = { + id: domainObject.id, + domain: domainObject.domain, + operation: domainObject.operation, + modifiedCounter: domainObject.modifiedCounter, + deletedCounter: domainObject.deletedCounter, + deletionRequestId: domainObject.deletionRequestId, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }; + + const result = await repo.findById(deletionLogId); + + // expect(result).toEqual(domainObject); + expect(result).toEqual(expect.objectContaining(expectedDomainObject)); + }); + }); + }); + + describe('findById', () => { + describe('when searching by Id', () => { + const setup = async () => { + // Test deletionLog entity + const entity: DeletionLogEntity = deletionLogEntityFactory.build(); + await em.persistAndFlush(entity); + + const expectedDeletionLog = { + id: entity.id, + domain: entity.domain, + operation: entity.operation, + modifiedCounter: entity.modifiedCounter, + deletedCounter: entity.deletedCounter, + deletionRequestId: entity.deletionRequestId?.toHexString(), + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + return { + entity, + expectedDeletionLog, + }; + }; + + it('should find the deletionRequest', async () => { + const { entity, expectedDeletionLog } = await setup(); + + const result: DeletionLog = await repo.findById(entity.id); + + // Verify explicit fields. + expect(result).toEqual(expect.objectContaining(expectedDeletionLog)); + }); + }); + }); + + describe('findAllByDeletionRequestId', () => { + describe('when there is no deletionLog for deletionRequestId', () => { + it('should return empty array', async () => { + const deletionRequestId = new ObjectId().toHexString(); + const result = await repo.findAllByDeletionRequestId(deletionRequestId); + + expect(result).toEqual([]); + }); + }); + + describe('when searching by deletionRequestId', () => { + const setup = async () => { + const deletionRequest1Id = new ObjectId(); + const deletionRequest2Id = new ObjectId(); + const deletionLogEntity1: DeletionLogEntity = deletionLogEntityFactory.build({ + deletionRequestId: deletionRequest1Id, + }); + const deletionLogEntity2: DeletionLogEntity = deletionLogEntityFactory.build({ + deletionRequestId: deletionRequest1Id, + }); + const deletionLogEntity3: DeletionLogEntity = deletionLogEntityFactory.build({ + deletionRequestId: deletionRequest2Id, + }); + + await em.persistAndFlush([deletionLogEntity1, deletionLogEntity2, deletionLogEntity3]); + em.clear(); + + return { deletionLogEntity1, deletionLogEntity2, deletionLogEntity3, deletionRequest1Id }; + }; + + it('should find deletionRequests with deleteAfter smaller then today', async () => { + const { deletionLogEntity1, deletionLogEntity2, deletionLogEntity3, deletionRequest1Id } = await setup(); + + const results = await repo.findAllByDeletionRequestId(deletionRequest1Id.toHexString()); + + const expectedArray = [ + { + id: deletionLogEntity1.id, + domain: deletionLogEntity1.domain, + operation: deletionLogEntity1.operation, + deletionRequestId: deletionLogEntity1.deletionRequestId?.toHexString(), + modifiedCounter: deletionLogEntity1.modifiedCounter, + deletedCounter: deletionLogEntity1.deletedCounter, + createdAt: deletionLogEntity1.createdAt, + updatedAt: deletionLogEntity1.updatedAt, + }, + { + id: deletionLogEntity2.id, + domain: deletionLogEntity2.domain, + operation: deletionLogEntity2.operation, + deletionRequestId: deletionLogEntity2.deletionRequestId?.toHexString(), + modifiedCounter: deletionLogEntity2.modifiedCounter, + deletedCounter: deletionLogEntity2.deletedCounter, + createdAt: deletionLogEntity2.createdAt, + updatedAt: deletionLogEntity2.updatedAt, + }, + ]; + + expect(results.length).toEqual(2); + + // Verify explicit fields. + expect(results).toEqual( + expect.arrayContaining([expect.objectContaining(expectedArray[0]), expect.objectContaining(expectedArray[1])]) + ); + + const result: DeletionLog = await repo.findById(deletionLogEntity3.id); + + expect(result.id).toEqual(deletionLogEntity3.id); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/repo/deletion-log.repo.ts b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts index 0c0ace43c6d..d71032eb124 100644 --- a/apps/server/src/modules/deletion/repo/deletion-log.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-log.repo.ts @@ -1,4 +1,4 @@ -import { EntityManager } from '@mikro-orm/mongodb'; +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { DeletionLog } from '../domain/deletion-log.do'; @@ -9,17 +9,33 @@ import { DeletionLogMapper } from './mapper/deletion-log.mapper'; export class DeletionLogRepo { constructor(private readonly em: EntityManager) {} - async findById(id: EntityId): Promise { - const deletionRequest: DeletionLogEntity = await this.em.findOneOrFail(DeletionLogEntity, { id }); + get entityName() { + return DeletionLogEntity; + } + + async findById(deletionLogId: EntityId): Promise { + const deletionLog: DeletionLogEntity = await this.em.findOneOrFail(DeletionLogEntity, { + id: deletionLogId, + }); - const mapped: DeletionLog = DeletionLogMapper.mapToDO(deletionRequest); + const mapped: DeletionLog = DeletionLogMapper.mapToDO(deletionLog); return mapped; } - // create + async findAllByDeletionRequestId(deletionRequestId: EntityId): Promise { + const deletionLogEntities: DeletionLogEntity[] = await this.em.find(DeletionLogEntity, { + deletionRequestId: new ObjectId(deletionRequestId), + }); + + const mapped: DeletionLog[] = DeletionLogMapper.mapToDOs(deletionLogEntities); - // update + return mapped; + } - // delete + async create(deletionLog: DeletionLog): Promise { + const deletionLogEntity: DeletionLogEntity = DeletionLogMapper.mapToEntity(deletionLog); + this.em.persist(deletionLogEntity); + await this.em.flush(); + } } diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts new file mode 100644 index 00000000000..d52b43407aa --- /dev/null +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts @@ -0,0 +1,218 @@ +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Test } from '@nestjs/testing'; +import { TestingModule } from '@nestjs/testing/testing-module'; +import { MongoMemoryDatabaseModule } from '@shared/infra/database'; +import { cleanupCollections } from '@shared/testing'; +import { DeletionRequestMapper } from './mapper'; +import { DeletionRequestRepo } from './deletion-request.repo'; +import { DeletionRequestEntity } from '../entity'; +import { DeletionRequest } from '../domain/deletion-request.do'; +import { deletionRequestEntityFactory } from '../entity/testing/factory/deletion-request.entity.factory'; +import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; + +describe(DeletionRequestRepo.name, () => { + let module: TestingModule; + let repo: DeletionRequestRepo; + let em: EntityManager; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ + MongoMemoryDatabaseModule.forRoot({ + entities: [DeletionRequestEntity], + }), + ], + providers: [DeletionRequestRepo, DeletionRequestMapper], + }).compile(); + + repo = module.get(DeletionRequestRepo); + em = module.get(EntityManager); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(async () => { + await cleanupCollections(em); + }); + + describe('defined', () => { + it('repo should be defined', () => { + expect(repo).toBeDefined(); + }); + + it('entity manager should be defined', () => { + expect(em).toBeDefined(); + }); + + it('should implement entityName getter', () => { + expect(repo.entityName).toBe(DeletionRequestEntity); + }); + }); + + describe('create deletionRequest', () => { + describe('when deletionRequest is new', () => { + it('should create a new deletionRequest', async () => { + const domainObject: DeletionRequest = deletionRequestFactory.build(); + const deletionRequestId = domainObject.id; + await repo.create(domainObject); + + const result = await repo.findById(deletionRequestId); + + expect(result).toEqual(domainObject); + }); + }); + }); + + describe('findById', () => { + describe('when searching by Id', () => { + const setup = async () => { + const userId = new ObjectId().toHexString(); + + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + await em.persistAndFlush(entity); + + const expectedDeletionRequest = { + id: entity.id, + domain: entity.domain, + deleteAfter: entity.deleteAfter, + itemId: entity.itemId, + status: entity.status, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + return { + entity, + expectedDeletionRequest, + }; + }; + + it('should find the deletionRequest', async () => { + const { entity, expectedDeletionRequest } = await setup(); + + const result: DeletionRequest = await repo.findById(entity.id); + + // Verify explicit fields. + expect(result).toEqual(expect.objectContaining(expectedDeletionRequest)); + }); + }); + }); + + describe('findAllItemsByDeletionDate', () => { + describe('when there is no deletionRequest for execution', () => { + it('should return empty array', async () => { + const result = await repo.findAllItemsByDeletionDate(); + + expect(result).toEqual([]); + }); + }); + + describe('when there are deletionRequests for execution', () => { + const setup = async () => { + const dateInFuture = new Date(); + dateInFuture.setDate(dateInFuture.getDate() + 30); + const deletionRequestEntity1: DeletionRequestEntity = deletionRequestEntityFactory.build({ + deleteAfter: new Date(2023, 9, 1), + }); + const deletionRequestEntity2: DeletionRequestEntity = deletionRequestEntityFactory.build({ + deleteAfter: new Date(2023, 9, 1), + }); + const deletionRequestEntity3: DeletionRequestEntity = deletionRequestEntityFactory.build({ + deleteAfter: dateInFuture, + }); + + await em.persistAndFlush([deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3]); + em.clear(); + + return { deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3 }; + }; + + it('should find deletionRequests with deleteAfter smaller then today', async () => { + const { deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3 } = await setup(); + + const results = await repo.findAllItemsByDeletionDate(); + + const expectedArray = [ + { + id: deletionRequestEntity1.id, + domain: deletionRequestEntity1.domain, + deleteAfter: deletionRequestEntity1.deleteAfter, + itemId: deletionRequestEntity1.itemId, + status: deletionRequestEntity1.status, + createdAt: deletionRequestEntity1.createdAt, + updatedAt: deletionRequestEntity1.updatedAt, + }, + { + id: deletionRequestEntity2.id, + domain: deletionRequestEntity2.domain, + deleteAfter: deletionRequestEntity2.deleteAfter, + itemId: deletionRequestEntity2.itemId, + status: deletionRequestEntity2.status, + createdAt: deletionRequestEntity2.createdAt, + updatedAt: deletionRequestEntity2.updatedAt, + }, + ]; + + expect(results.length).toEqual(2); + + // Verify explicit fields. + expect(results).toEqual( + expect.arrayContaining([expect.objectContaining(expectedArray[0]), expect.objectContaining(expectedArray[1])]) + ); + + const result: DeletionRequest = await repo.findById(deletionRequestEntity3.id); + + expect(result.id).toEqual(deletionRequestEntity3.id); + }); + }); + }); + + describe('deleteById', () => { + describe('when deleting deletionRequest exists', () => { + const setup = async () => { + const userId = new ObjectId().toHexString(); + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + const deletionRequestId = entity.id; + await em.persistAndFlush(entity); + em.clear(); + + return { deletionRequestId }; + }; + + it('should delete the deletionRequest with deletionRequestId', async () => { + const { deletionRequestId } = await setup(); + + await repo.deleteById(deletionRequestId); + + expect(await em.findOne(DeletionRequestEntity, { id: deletionRequestId })).toBeNull(); + }); + + it('should return true', async () => { + const { deletionRequestId } = await setup(); + + const result: boolean = await repo.deleteById(deletionRequestId); + + expect(result).toEqual(true); + }); + }); + + describe('when no deletionRequestEntity exists', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + + return { deletionRequestId }; + }; + + it('should return false', async () => { + const { deletionRequestId } = setup(); + + const result: boolean = await repo.deleteById(deletionRequestId); + + expect(result).toEqual(false); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts index b386596733c..a2f9a491504 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -9,27 +9,26 @@ import { DeletionRequestMapper } from './mapper/deletion-request.mapper'; export class DeletionRequestRepo { constructor(private readonly em: EntityManager) {} - async findById(id: EntityId): Promise { - const deletionRequest: DeletionRequestEntity = await this.em.findOneOrFail(DeletionRequestEntity, { id }); + get entityName() { + return DeletionRequestEntity; + } + + async findById(deletionRequestId: EntityId): Promise { + const deletionRequest: DeletionRequestEntity = await this.em.findOneOrFail(DeletionRequestEntity, { + id: deletionRequestId, + }); const mapped: DeletionRequest = DeletionRequestMapper.mapToDO(deletionRequest); return mapped; } - // create async create(deletionRequest: DeletionRequest): Promise { const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); - await this.em.persistAndFlush(deletionRequestEntity); - } - - // update - async update(deletionRequest: DeletionRequest): Promise { - const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); - await this.em.persistAndFlush(deletionRequestEntity); + this.em.persist(deletionRequestEntity); + await this.em.flush(); } - // find async findAllItemsByDeletionDate(): Promise { const currentDate = new Date(); const itemsToDelete: DeletionRequestEntity[] = await this.em.find(DeletionRequestEntity, { @@ -41,5 +40,22 @@ export class DeletionRequestRepo { return mapped; } - // delete + async update(deletionRequest: DeletionRequest): Promise { + const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); + await this.em.persistAndFlush(deletionRequestEntity); + } + + async deleteById(deletionRequestId: EntityId): Promise { + const entity: DeletionRequestEntity | null = await this.em.findOne(DeletionRequestEntity, { + id: deletionRequestId, + }); + + if (!entity) { + return false; + } + + await this.em.removeAndFlush(entity); + + return true; + } } diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts new file mode 100644 index 00000000000..c82acbb2e8e --- /dev/null +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts @@ -0,0 +1,139 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { deletionLogEntityFactory } from '../../entity/testing/factory/deletion-log.entity.factory'; +import { DeletionLogMapper } from './deletion-log.mapper'; +import { DeletionLog } from '../../domain/deletion-log.do'; +import { deletionLogFactory } from '../../domain/testing/factory/deletion-log.factory'; +import { DeletionLogEntity } from '../../entity'; + +describe(DeletionLogMapper.name, () => { + describe('mapToDO', () => { + describe('When entity is mapped for domainObject', () => { + it('should properly map the entity to the domain object', () => { + const entity = deletionLogEntityFactory.build(); + + const domainObject = DeletionLogMapper.mapToDO(entity); + + const expectedDomainObject = new DeletionLog({ + id: entity.id, + domain: entity.domain, + operation: entity.operation, + deletionRequestId: entity.deletionRequestId?.toHexString(), + modifiedCounter: entity.modifiedCounter, + deletedCounter: entity.deletedCounter, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }); + + expect(domainObject).toEqual(expectedDomainObject); + }); + }); + }); + + describe('mapToDOs', () => { + describe('When empty entities array is mapped for an empty domainObjects array', () => { + it('should return empty domain objects array for an empty entities array', () => { + const domainObjects = DeletionLogMapper.mapToDOs([]); + + expect(domainObjects).toEqual([]); + }); + }); + + describe('When entities array is mapped for domainObjects array', () => { + it('should properly map the entities to the domain objects', () => { + const entities = [deletionLogEntityFactory.build()]; + + const domainObjects = DeletionLogMapper.mapToDOs(entities); + + const expectedDomainObjects = entities.map( + (entity) => + new DeletionLog({ + id: entity.id, + domain: entity.domain, + operation: entity.operation, + deletionRequestId: entity.deletionRequestId?.toHexString(), + modifiedCounter: entity.modifiedCounter, + deletedCounter: entity.deletedCounter, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }) + ); + + expect(domainObjects).toEqual(expectedDomainObjects); + }); + }); + }); + + describe('mapToEntity', () => { + describe('When domainObject is mapped for entity', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date()); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should properly map the domainObject to the entity', () => { + const domainObject = deletionLogFactory.build(); + + const entities = DeletionLogMapper.mapToEntity(domainObject); + + const expectedEntities = new DeletionLogEntity({ + id: domainObject.id, + domain: domainObject.domain, + operation: domainObject.operation, + deletionRequestId: new ObjectId(domainObject.deletionRequestId), + modifiedCounter: domainObject.modifiedCounter, + deletedCounter: domainObject.deletedCounter, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }); + + expect(entities).toEqual(expectedEntities); + }); + }); + }); + + describe('mapToEntities', () => { + describe('When empty domainObjects array is mapped for an entities array', () => { + it('should return empty entities array for an empty domain objects array', () => { + const entities = DeletionLogMapper.mapToEntities([]); + + expect(entities).toEqual([]); + }); + }); + + describe('When domainObjects array is mapped for entities array', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date()); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should properly map the domainObjects to the entities', () => { + const domainObjects = [deletionLogFactory.build()]; + + const entities = DeletionLogMapper.mapToEntities(domainObjects); + + const expectedEntities = domainObjects.map( + (domainObject) => + new DeletionLogEntity({ + id: domainObject.id, + domain: domainObject.domain, + operation: domainObject.operation, + deletionRequestId: new ObjectId(domainObject.deletionRequestId), + modifiedCounter: domainObject.modifiedCounter, + deletedCounter: domainObject.deletedCounter, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }) + ); + expect(entities).toEqual(expectedEntities); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts index 9c3656e4d34..506e7369c84 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts @@ -8,9 +8,10 @@ export class DeletionLogMapper { id: entity.id, createdAt: entity.createdAt, updatedAt: entity.updatedAt, - scope: entity.scope, + domain: entity.domain, operation: entity.operation, - docIds: entity.docIds?.map((docId) => docId.toHexString()), + modifiedCounter: entity.modifiedCounter, + deletedCounter: entity.deletedCounter, deletionRequestId: entity.deletionRequestId?.toHexString(), }); } @@ -20,18 +21,19 @@ export class DeletionLogMapper { id: domainObject.id, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, - scope: domainObject.scope, + domain: domainObject.domain, operation: domainObject.operation, - docIds: domainObject.docIds?.map((docId) => new ObjectId(docId)), + modifiedCounter: domainObject.modifiedCounter, + deletedCounter: domainObject.deletedCounter, deletionRequestId: new ObjectId(domainObject.deletionRequestId), }); } - // static mapToDOs(entities: DeletionLogEntity[]): DeletionLog[] { - // return entities.map((entity) => this.mapToDO(entity)); - // } + static mapToDOs(entities: DeletionLogEntity[]): DeletionLog[] { + return entities.map((entity) => this.mapToDO(entity)); + } - // static mapToEntities(domainObjects: DeletionLog[]): DeletionLogEntity[] { - // return domainObjects.map((domainObject) => this.mapToEntity(domainObject)); - // } + static mapToEntities(domainObjects: DeletionLog[]): DeletionLogEntity[] { + return domainObjects.map((domainObject) => this.mapToEntity(domainObject)); + } } diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts new file mode 100644 index 00000000000..a4bbedc9946 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts @@ -0,0 +1,60 @@ +import { DeletionRequest } from '../../domain/deletion-request.do'; +import { deletionRequestFactory } from '../../domain/testing/factory/deletion-request.factory'; +import { DeletionRequestEntity } from '../../entity'; +import { deletionRequestEntityFactory } from '../../entity/testing/factory/deletion-request.entity.factory'; +import { DeletionRequestMapper } from './deletion-request.mapper'; + +describe(DeletionRequestMapper.name, () => { + describe('mapToDO', () => { + describe('When entity is mapped for domainObject', () => { + it('should properly map the entity to the domain object', () => { + const entity = deletionRequestEntityFactory.build(); + + const domainObject = DeletionRequestMapper.mapToDO(entity); + + const expectedDomainObject = new DeletionRequest({ + id: entity.id, + domain: entity.domain, + deleteAfter: entity.deleteAfter, + itemId: entity.itemId, + status: entity.status, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }); + + expect(domainObject).toEqual(expectedDomainObject); + }); + }); + }); + + describe('mapToEntity', () => { + describe('When domainObject is mapped for entity', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date()); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should properly map the domainObject to the entity', () => { + const domainObject = deletionRequestFactory.build(); + + const entity = DeletionRequestMapper.mapToEntity(domainObject); + + const expectedEntity = new DeletionRequestEntity({ + id: domainObject.id, + domain: domainObject.domain, + deleteAfter: domainObject.deleteAfter, + itemId: domainObject.itemId, + status: domainObject.status, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }); + + expect(entity).toEqual(expectedEntity); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts index 879352117b6..c757606ce69 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts @@ -8,26 +8,22 @@ export class DeletionRequestMapper { id: entity.id, createdAt: entity.createdAt, updatedAt: entity.updatedAt, - source: entity.source, + domain: entity.domain, deleteAfter: entity.deleteAfter, - userId: entity.userId?.toHexString(), + itemId: entity.itemId, + status: entity.status, }); } static mapToEntity(domainObject: DeletionRequest): DeletionRequestEntity { return new DeletionRequestEntity({ id: domainObject.id, - source: domainObject.source, + domain: domainObject.domain, deleteAfter: domainObject.deleteAfter, - userId: new ObjectId(domainObject.userId), + itemId: new ObjectId(domainObject.itemId).toHexString(), + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + status: domainObject.status, }); } - - // static mapToDOs(entities: DeletionRequestEntity[]): DeletionRequest[] { - // return entities.map((entity) => this.mapToDOs(entity)); - // } - - // static mapToEntities(domainObjects: DeletionRequest[]): DeletionRequestEntity[] { - // return domainObjects.map((domainObject) => this.mapToEntities(domainObject)); - // } } diff --git a/apps/server/src/modules/deletion/repo/mapper/index.ts b/apps/server/src/modules/deletion/repo/mapper/index.ts new file mode 100644 index 00000000000..0407135b228 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/mapper/index.ts @@ -0,0 +1,2 @@ +export * from './deletion-request.mapper'; +export * from './deletion-log.mapper'; diff --git a/apps/server/src/modules/deletion/services/deletion-log.service.ts b/apps/server/src/modules/deletion/services/deletion-log.service.ts new file mode 100644 index 00000000000..70290bc0c25 --- /dev/null +++ b/apps/server/src/modules/deletion/services/deletion-log.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import { DeletionLogRepo } from '../repo'; + +@Injectable() +export class DeletionLogService { + constructor(private readonly deletionLogRepo: DeletionLogRepo) {} + + // async createLogRequest(userId: EntityId): Promise { + // const dateInFuture = new Date(); + // dateInFuture.setDate(dateInFuture.getDate() + 30); + + // const newDeletionLog = new DeletionLog({ + // id: new ObjectId().toHexString(), + // scope: 'scope', + // operation: DeletionOperationModel.DELETE, + // deletionRequestId: new ObjectId(), + // docIds: [new ObjectId(), new ObjectId()], + // }); + + // await this.deletionLogRepo.create(newDeletionLog); + + // return newDeletionLof.id; + // } + + // async findById(deletionRequestId: EntityId): Promise { + // const deletionRequest: DeletionRequest = await this.deletionRequestRepo.findById(deletionRequestId); + + // return deletionRequest; + // } + + // async findAllItemsByDeletionDate(): Promise { + // const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsByDeletionDate(); + + // return itemsToDelete; + // } + + // async deleteById(deletionRequestId: EntityId): Promise { + // await this.deletionRequestRepo.deleteById(deletionRequestId); + // } +} 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 new file mode 100644 index 00000000000..4013f3adcad --- /dev/null +++ b/apps/server/src/modules/deletion/services/deletion-request.service.spec.ts @@ -0,0 +1,133 @@ +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 { DeletionRequestService } from './deletion-request.service'; +import { DeletionRequestRepo } from '../repo'; +import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; + +describe(DeletionRequestService.name, () => { + let module: TestingModule; + let service: DeletionRequestService; + let deletionRequestRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + DeletionRequestService, + { + provide: DeletionRequestRepo, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(DeletionRequestService); + deletionRequestRepo = module.get(DeletionRequestRepo); + + await setupEntities(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('defined', () => { + it('should be defined', () => { + expect(service).toBeDefined(); + }); + }); + + // TODO createDeletionRequest + // describe('createDeletionRequest', () => { + // describe('when creating a deletionRequest', () => { + // const setup = () => { + // const deletionRequest = deletionRequestFactory.build(); + + // return { deletionRequest }; + // }; + + // it('should '); + // }); + // }); + + describe('findById', () => { + describe('when finding by deletionRequestId', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + const deletionRequest = deletionRequestFactory.build({ id: deletionRequestId }); + + deletionRequestRepo.findById.mockResolvedValue(deletionRequest); + + return { deletionRequestId, deletionRequest }; + }; + + it('should call deletionRequestRepo.findById', async () => { + const { deletionRequestId } = setup(); + + await service.findById(deletionRequestId); + + expect(deletionRequestRepo.findById).toBeCalledWith(deletionRequestId); + }); + + it('should return deletionRequest', async () => { + const { deletionRequestId, deletionRequest } = setup(); + + const result = await service.findById(deletionRequestId); + + expect(result).toEqual(deletionRequest); + }); + }); + }); + + describe('findAllItemsByDeletionDate', () => { + describe('when finding all deletionRequests for execution', () => { + const setup = () => { + const dateInPast = new Date(); + dateInPast.setDate(dateInPast.getDate() - 1); + const deletionRequest1 = deletionRequestFactory.build({ deleteAfter: dateInPast }); + const deletionRequest2 = deletionRequestFactory.build({ deleteAfter: dateInPast }); + + deletionRequestRepo.findAllItemsByDeletionDate.mockResolvedValue([deletionRequest1, deletionRequest2]); + + const deletionRequests = [deletionRequest1, deletionRequest2]; + return { deletionRequests }; + }; + + it('should call deletionRequestRepo.findAllItemsByDeletionDate', async () => { + await service.findAllItemsByDeletionDate(); + + expect(deletionRequestRepo.findAllItemsByDeletionDate).toBeCalled(); + }); + + it('should return array of two deletionRequests with date smaller than today', async () => { + const { deletionRequests } = setup(); + const result = await service.findAllItemsByDeletionDate(); + + expect(result).toHaveLength(2); + expect(result).toEqual(deletionRequests); + }); + }); + }); + + describe('deleteById', () => { + describe('when deleting deletionRequest', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + + return { deletionRequestId }; + }; + + it('should call deletionRequestRepo.findAllItemsByDeletionDate', async () => { + const { deletionRequestId } = setup(); + await service.deleteById(deletionRequestId); + + expect(deletionRequestRepo.deleteById).toBeCalledWith(deletionRequestId); + }); + }); + }); +}); 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 1f59221ecbe..38889fe37d2 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -3,30 +3,47 @@ import { EntityId } from '@shared/domain'; import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestRepo } from '../repo/deletion-request.repo'; import { DeletionRequest } from '../domain/deletion-request.do'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; @Injectable() export class DeletionRequestService { constructor(private readonly deletionRequestRepo: DeletionRequestRepo) {} - async createDeletionRequest(userId: EntityId): Promise { + async createDeletionRequest( + itemId: EntityId, + domain: DeletionDomainModel, + deleteInMinutes?: number + ): Promise { + deleteInMinutes = deleteInMinutes === undefined ? 43200 : deleteInMinutes; + + const dateOfDeletion = new Date(); + dateOfDeletion.setDate(dateOfDeletion.getDate() + deleteInMinutes * 1000); + const newDeletionRequest = new DeletionRequest({ id: new ObjectId().toHexString(), - source: 'shd', - deleteAfter: new Date(), - userId, + domain, + deleteAfter: dateOfDeletion, + itemId, }); await this.deletionRequestRepo.create(newDeletionRequest); + + return newDeletionRequest.id; } - // updateService - async updateDeletionRequest(deletionRequest: DeletionRequest): Promise { - await this.deletionRequestRepo.update(deletionRequest); + async findById(deletionRequestId: EntityId): Promise { + const deletionRequest: DeletionRequest = await this.deletionRequestRepo.findById(deletionRequestId); + + return deletionRequest; } - // findAll async findAllItemsByDeletionDate(): Promise { const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsByDeletionDate(); + return itemsToDelete; } + + async deleteById(deletionRequestId: EntityId): Promise { + await this.deletionRequestRepo.deleteById(deletionRequestId); + } } diff --git a/apps/server/src/modules/deletion/services/index.ts b/apps/server/src/modules/deletion/services/index.ts new file mode 100644 index 00000000000..9661354718c --- /dev/null +++ b/apps/server/src/modules/deletion/services/index.ts @@ -0,0 +1 @@ +export * from './deletion-request.service'; From a853ea748f390f2b646f44d0cf24c55915fc2b41 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 1 Nov 2023 23:01:22 +0100 Subject: [PATCH 04/22] add usecases and test cases --- .../src/modules/deletion/deletion.module.ts | 30 +- .../deletion/domain/deletion-log.do.spec.ts | 8 +- .../deletion/domain/deletion-log.do.ts | 24 +- .../deletion/domain/deletion-request.do.ts | 16 +- .../testing/factory/deletion-log.factory.ts | 4 +- .../types/deletion-domain-model.enum.ts | 10 +- .../entity/deletion-log.entity.spec.ts | 8 +- .../deletion/entity/deletion-log.entity.ts | 26 +- .../entity/deletion-request.entity.spec.ts | 42 +- .../entity/deletion-request.entity.ts | 53 +-- .../factory/deletion-log.entity.factory.ts | 4 +- apps/server/src/modules/deletion/index.ts | 1 + .../deletion/repo/deletion-log.repo.spec.ts | 16 +- .../repo/deletion-request.repo.spec.ts | 61 +++ .../deletion/repo/deletion-request.repo.ts | 15 +- .../repo/mapper/deletion-log.mapper.spec.ts | 16 +- .../repo/mapper/deletion-log.mapper.ts | 8 +- .../services/deletion-log.service.spec.ts | 110 +++++ .../deletion/services/deletion-log.service.ts | 61 ++- .../services/deletion-request.service.spec.ts | 70 +++- .../services/deletion-request.service.ts | 16 +- .../deletion/uc/deletion-request.uc.spec.ts | 396 ++++++++++++++++++ .../deletion/uc/deletion-request.uc.ts | 219 ++++++++++ .../modules/deletion/uc/deletion-worker.us.ts | 16 - 24 files changed, 1026 insertions(+), 204 deletions(-) create mode 100644 apps/server/src/modules/deletion/services/deletion-log.service.spec.ts create mode 100644 apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts create mode 100644 apps/server/src/modules/deletion/uc/deletion-request.uc.ts delete mode 100644 apps/server/src/modules/deletion/uc/deletion-worker.us.ts diff --git a/apps/server/src/modules/deletion/deletion.module.ts b/apps/server/src/modules/deletion/deletion.module.ts index 7d508b1650a..a32f57e2db3 100644 --- a/apps/server/src/modules/deletion/deletion.module.ts +++ b/apps/server/src/modules/deletion/deletion.module.ts @@ -1,38 +1,10 @@ import { Module } from '@nestjs/common'; -// import { Module, NotFoundException } from '@nestjs/common'; -// import { DB_PASSWORD, DB_USERNAME, DEL_DB_URL } from '@src/config'; -// import { CoreModule } from '@src/core'; import { Logger } from '@src/core/logger'; -// import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -// import { AuthenticationModule } from '@src/modules/authentication/authentication.module'; -// import { AuthorizationModule } from '@src/modules'; -// import { RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; -// import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; import { DeletionRequestService } from './services/deletion-request.service'; import { DeletionRequestRepo } from './repo/deletion-request.repo'; -// const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { -// findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => -// // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -// new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -// }; - @Module({ - // imports: [ - // AuthorizationModule, - // AuthenticationModule, - // CoreModule, - // RabbitMQWrapperTestModule, - // MikroOrmModule.forRoot({ - // ...defaultMikroOrmOptions, - // type: 'mongo', - // clientUrl: DEL_DB_URL, - // password: DB_PASSWORD, - // user: DB_USERNAME, - // entities: [], - // }), - // ConfigModule.forRoot(createConfigModuleOptions(config)), - // ], providers: [Logger, DeletionRequestService, DeletionRequestRepo], + exports: [DeletionRequestService], }) export class DeletionModule {} 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 b18e4759af5..860e07f56de 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 @@ -38,8 +38,8 @@ describe(DeletionLog.name, () => { id: new ObjectId().toHexString(), domain: DeletionDomainModel.USER, operation: DeletionOperationModel.DELETE, - modifiedCounter: 0, - deletedCounter: 1, + modifiedCount: 0, + deletedCount: 1, deletionRequestId: new ObjectId().toHexString(), createdAt: new Date(), updatedAt: new Date(), @@ -50,8 +50,8 @@ describe(DeletionLog.name, () => { id: deletionLogDo.id, domain: deletionLogDo.domain, operation: deletionLogDo.operation, - modifiedCounter: deletionLogDo.modifiedCounter, - deletedCounter: deletionLogDo.deletedCounter, + modifiedCount: deletionLogDo.modifiedCount, + deletedCount: deletionLogDo.deletedCount, deletionRequestId: deletionLogDo.deletionRequestId, createdAt: deletionLogDo.createdAt, updatedAt: deletionLogDo.updatedAt, 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 89391ca65a8..73e62b46055 100644 --- a/apps/server/src/modules/deletion/domain/deletion-log.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-log.do.ts @@ -4,25 +4,25 @@ import { DeletionDomainModel } from './types/deletion-domain-model.enum'; import { DeletionOperationModel } from './types/deletion-operation-model.enum'; export interface DeletionLogProps extends AuthorizableObject { - createdAt: Date; - updatedAt: Date; - domain?: DeletionDomainModel; + createdAt?: Date; + updatedAt?: Date; + domain: DeletionDomainModel; operation?: DeletionOperationModel; - modifiedCounter?: number; - deletedCounter?: number; + modifiedCount?: number; + deletedCount?: number; deletionRequestId?: EntityId; } export class DeletionLog extends DomainObject { - get createdAt(): Date { + get createdAt(): Date | undefined { return this.props.createdAt; } - get updatedAt(): Date { + get updatedAt(): Date | undefined { return this.props.updatedAt; } - get domain(): DeletionDomainModel | undefined { + get domain(): DeletionDomainModel { return this.props.domain; } @@ -30,12 +30,12 @@ export class DeletionLog extends DomainObject { return this.props.operation; } - get modifiedCounter(): number | undefined { - return this.props.modifiedCounter; + get modifiedCount(): number | undefined { + return this.props.modifiedCount; } - get deletedCounter(): number | undefined { - return this.props.deletedCounter; + get deletedCount(): number | undefined { + return this.props.deletedCount; } get deletionRequestId(): EntityId | undefined { 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 881560359bc..6d1349a65f2 100644 --- a/apps/server/src/modules/deletion/domain/deletion-request.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.ts @@ -6,10 +6,10 @@ import { DeletionStatusModel } from './types/deletion-status-model.enum'; export interface DeletionRequestProps extends AuthorizableObject { createdAt?: Date; updatedAt?: Date; - domain?: DeletionDomainModel; - deleteAfter?: Date; - itemId?: EntityId; - status?: DeletionStatusModel; + domain: DeletionDomainModel; + deleteAfter: Date; + itemId: EntityId; + status: DeletionStatusModel; } export class DeletionRequest extends DomainObject { @@ -21,19 +21,19 @@ export class DeletionRequest extends DomainObject { return this.props.updatedAt; } - get domain(): DeletionDomainModel | undefined { + get domain(): DeletionDomainModel { return this.props.domain; } - get deleteAfter(): Date | undefined { + get deleteAfter(): Date { return this.props.deleteAfter; } - get itemId(): EntityId | undefined { + get itemId(): EntityId { return this.props.itemId; } - get status(): DeletionStatusModel | undefined { + get status(): DeletionStatusModel { return this.props.status; } } 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 25249fa3518..d83b2f44c8a 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 @@ -9,8 +9,8 @@ export const deletionLogFactory = DoBaseFactory.define { id: new ObjectId().toHexString(), domain: DeletionDomainModel.USER, operation: DeletionOperationModel.DELETE, - modifiedCounter: 0, - deletedCounter: 1, + modifiedCount: 0, + deletedCount: 1, deletionRequestId: new ObjectId(), createdAt: new Date(), updatedAt: new Date(), @@ -46,8 +46,8 @@ describe(DeletionLogEntity.name, () => { id: entity.id, domain: entity.domain, operation: entity.operation, - modifiedCounter: entity.modifiedCounter, - deletedCounter: entity.deletedCounter, + modifiedCount: entity.modifiedCount, + deletedCount: entity.deletedCount, deletionRequestId: entity.deletionRequestId, createdAt: entity.createdAt, updatedAt: entity.updatedAt, 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 f2139ca6aaa..8a9d2bab025 100644 --- a/apps/server/src/modules/deletion/entity/deletion-log.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-log.entity.ts @@ -6,10 +6,10 @@ import { DeletionOperationModel } from '../domain/types/deletion-operation-model export interface DeletionLogEntityProps { id?: EntityId; - domain?: DeletionDomainModel; + domain: DeletionDomainModel; operation?: DeletionOperationModel; - modifiedCounter?: number; - deletedCounter?: number; + modifiedCount?: number; + deletedCount?: number; deletionRequestId?: ObjectId; createdAt?: Date; updatedAt?: Date; @@ -17,17 +17,17 @@ export interface DeletionLogEntityProps { @Entity({ tableName: 'deletionlogs' }) export class DeletionLogEntity extends BaseEntityWithTimestamps { - @Property({ nullable: true }) - domain?: DeletionDomainModel; + @Property() + domain: DeletionDomainModel; @Property({ nullable: true }) operation?: DeletionOperationModel; @Property({ nullable: true }) - modifiedCounter?: number; + modifiedCount?: number; @Property({ nullable: true }) - deletedCounter?: number; + deletedCount?: number; @Property({ nullable: true }) deletionRequestId?: ObjectId; @@ -38,20 +38,18 @@ export class DeletionLogEntity extends BaseEntityWithTimestamps { this.id = props.id; } - if (props.domain !== undefined) { - this.domain = props.domain; - } + this.domain = props.domain; if (props.operation !== undefined) { this.operation = props.operation; } - if (props.modifiedCounter !== undefined) { - this.modifiedCounter = props.modifiedCounter; + if (props.modifiedCount !== undefined) { + this.modifiedCount = props.modifiedCount; } - if (props.deletedCounter !== undefined) { - this.deletedCounter = props.deletedCounter; + if (props.deletedCount !== undefined) { + this.deletedCount = props.deletedCount; } if (props.deletionRequestId !== undefined) { 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 fca7bb87b57..1552360e2fd 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 @@ -3,28 +3,31 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestEntity } from '@src/modules/deletion/entity/deletion-request.entity'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; +// import { deletionRequestEntityFactory } from './testing/factory/deletion-request.entity.factory'; describe(DeletionRequestEntity.name, () => { beforeAll(async () => { await setupEntities(); }); - describe('constructor', () => { - describe('When constructor is called', () => { - const setup = () => { - const props = { - id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, - deleteAfter: new Date(), - itemId: new ObjectId().toHexString(), - status: DeletionStatusModel.REGISTERED, - createdAt: new Date(), - updatedAt: new Date(), - }; + const setup = () => { + jest.clearAllMocks(); - return { props }; - }; + const props = { + id: new ObjectId().toHexString(), + domain: DeletionDomainModel.USER, + deleteAfter: new Date(), + itemId: new ObjectId().toHexString(), + status: DeletionStatusModel.REGISTERED, + createdAt: new Date(), + updatedAt: new Date(), + }; + return { props }; + }; + + describe('constructor', () => { + describe('When constructor is called', () => { it('should throw an error by empty constructor', () => { // @ts-expect-error: Test case const test = () => new DeletionRequestEntity(); @@ -56,4 +59,15 @@ describe(DeletionRequestEntity.name, () => { }); }); }); + + describe('executed', () => { + it('should update status', () => { + const { props } = setup(); + const entity: DeletionRequestEntity = new DeletionRequestEntity(props); + + entity.executed(); + + expect(entity.status).toEqual(DeletionStatusModel.SUCCESS); + }); + }); }); 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 f76dbedcb8b..6494edca17f 100644 --- a/apps/server/src/modules/deletion/entity/deletion-request.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts @@ -7,32 +7,32 @@ import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum' export interface DeletionRequestEntityProps { id?: EntityId; - domain?: DeletionDomainModel; - deleteAfter?: Date; - itemId?: EntityId; - status?: DeletionStatusModel; + domain: DeletionDomainModel; + deleteAfter: Date; + itemId: EntityId; + status: DeletionStatusModel; createdAt?: Date; updatedAt?: Date; } @Entity({ tableName: 'deletionrequests' }) +@Index({ properties: ['_itemId', 'domain'] }) export class DeletionRequestEntity extends BaseEntityWithTimestamps { - @Property({ nullable: true }) - deleteAfter?: Date; + @Property() + deleteAfter: Date; - @Property({ fieldName: 'itemToDeletion', nullable: true }) - @Index() - _itemId?: ObjectId; + @Property() + _itemId: ObjectId; - get itemId(): EntityId | undefined { - return this._itemId?.toHexString(); + get itemId(): EntityId { + return this._itemId.toHexString(); } - @Property({ nullable: true }) - domain?: DeletionDomainModel; + @Property() + domain: DeletionDomainModel; - @Property({ nullable: true }) - status?: DeletionStatusModel; + @Property() + status: DeletionStatusModel; constructor(props: DeletionRequestEntityProps) { super(); @@ -40,21 +40,10 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { this.id = props.id; } - if (props.domain !== undefined) { - this.domain = props.domain; - } - - if (props.deleteAfter !== undefined) { - this.deleteAfter = props.deleteAfter; - } - - if (props.itemId !== undefined) { - this._itemId = new ObjectId(props.itemId); - } - - if (props.status !== undefined) { - this.status = props.status; - } + this.domain = props.domain; + this.deleteAfter = props.deleteAfter; + this._itemId = new ObjectId(props.itemId); + this.status = props.status; if (props.createdAt !== undefined) { this.createdAt = props.createdAt; @@ -64,4 +53,8 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { this.updatedAt = props.updatedAt; } } + + public executed(): void { + this.status = DeletionStatusModel.SUCCESS; + } } 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 ed7f975cc0f..a8b40c381db 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 @@ -11,8 +11,8 @@ export const deletionLogEntityFactory = BaseFactory.define { id: domainObject.id, domain: domainObject.domain, operation: domainObject.operation, - modifiedCounter: domainObject.modifiedCounter, - deletedCounter: domainObject.deletedCounter, + modifiedCount: domainObject.modifiedCount, + deletedCount: domainObject.deletedCount, deletionRequestId: domainObject.deletionRequestId, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, @@ -88,8 +88,8 @@ describe(DeletionLogRepo.name, () => { id: entity.id, domain: entity.domain, operation: entity.operation, - modifiedCounter: entity.modifiedCounter, - deletedCounter: entity.deletedCounter, + modifiedCount: entity.modifiedCount, + deletedCount: entity.deletedCount, deletionRequestId: entity.deletionRequestId?.toHexString(), createdAt: entity.createdAt, updatedAt: entity.updatedAt, @@ -153,8 +153,8 @@ describe(DeletionLogRepo.name, () => { domain: deletionLogEntity1.domain, operation: deletionLogEntity1.operation, deletionRequestId: deletionLogEntity1.deletionRequestId?.toHexString(), - modifiedCounter: deletionLogEntity1.modifiedCounter, - deletedCounter: deletionLogEntity1.deletedCounter, + modifiedCount: deletionLogEntity1.modifiedCount, + deletedCount: deletionLogEntity1.deletedCount, createdAt: deletionLogEntity1.createdAt, updatedAt: deletionLogEntity1.updatedAt, }, @@ -163,8 +163,8 @@ describe(DeletionLogRepo.name, () => { domain: deletionLogEntity2.domain, operation: deletionLogEntity2.operation, deletionRequestId: deletionLogEntity2.deletionRequestId?.toHexString(), - modifiedCounter: deletionLogEntity2.modifiedCounter, - deletedCounter: deletionLogEntity2.deletedCounter, + modifiedCount: deletionLogEntity2.modifiedCount, + deletedCount: deletionLogEntity2.deletedCount, createdAt: deletionLogEntity2.createdAt, updatedAt: deletionLogEntity2.updatedAt, }, diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts index d52b43407aa..fd28755a6b1 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts @@ -170,6 +170,67 @@ describe(DeletionRequestRepo.name, () => { }); }); + describe('update', () => { + describe('when updating deletionRequest', () => { + const setup = async () => { + const userId = new ObjectId().toHexString(); + + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + await em.persistAndFlush(entity); + + // Arrange expected DeletionRequestEntity after changing status + entity.status = DeletionStatusModel.SUCCESS; + const deletionRequestToUpdate = DeletionRequestMapper.mapToDO(entity); + + return { + entity, + deletionRequestToUpdate, + }; + }; + + it('should update the deletionRequest', async () => { + const { entity, deletionRequestToUpdate } = await setup(); + + await repo.update(deletionRequestToUpdate); + + const result: DeletionRequest = await repo.findById(entity.id); + + expect(result.status).toEqual(entity.status); + }); + }); + }); + + describe('markDeletionRequestAsExecuted', () => { + describe('when mark deletionRequest as executed', () => { + const setup = async () => { + const userId = new ObjectId().toHexString(); + + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + await em.persistAndFlush(entity); + + return { entity }; + }; + + it('should update the deletionRequest', async () => { + const { entity } = await setup(); + + const result = await repo.markDeletionRequestAsExecuted(entity.id); + + expect(result).toBe(true); + }); + + it('should update the deletionRequest', async () => { + const { entity } = await setup(); + + await repo.markDeletionRequestAsExecuted(entity.id); + + const result: DeletionRequest = await repo.findById(entity.id); + + expect(result.status).toEqual(DeletionStatusModel.SUCCESS); + }); + }); + }); + describe('deleteById', () => { describe('when deleting deletionRequest exists', () => { const setup = async () => { diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts index a2f9a491504..8ab31672f60 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -42,7 +42,20 @@ export class DeletionRequestRepo { async update(deletionRequest: DeletionRequest): Promise { const deletionRequestEntity = DeletionRequestMapper.mapToEntity(deletionRequest); - await this.em.persistAndFlush(deletionRequestEntity); + const referencedEntity = this.em.getReference(DeletionRequestEntity, deletionRequestEntity.id); + + await this.em.persistAndFlush(referencedEntity); + } + + async markDeletionRequestAsExecuted(deletionRequestId: EntityId): Promise { + const deletionRequest: DeletionRequestEntity = await this.em.findOneOrFail(DeletionRequestEntity, { + id: deletionRequestId, + }); + + deletionRequest.executed(); + await this.em.persistAndFlush(deletionRequest); + + return true; } async deleteById(deletionRequestId: EntityId): Promise { diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts index c82acbb2e8e..008c54502c2 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts @@ -18,8 +18,8 @@ describe(DeletionLogMapper.name, () => { domain: entity.domain, operation: entity.operation, deletionRequestId: entity.deletionRequestId?.toHexString(), - modifiedCounter: entity.modifiedCounter, - deletedCounter: entity.deletedCounter, + modifiedCount: entity.modifiedCount, + deletedCount: entity.deletedCount, createdAt: entity.createdAt, updatedAt: entity.updatedAt, }); @@ -51,8 +51,8 @@ describe(DeletionLogMapper.name, () => { domain: entity.domain, operation: entity.operation, deletionRequestId: entity.deletionRequestId?.toHexString(), - modifiedCounter: entity.modifiedCounter, - deletedCounter: entity.deletedCounter, + modifiedCount: entity.modifiedCount, + deletedCount: entity.deletedCount, createdAt: entity.createdAt, updatedAt: entity.updatedAt, }) @@ -84,8 +84,8 @@ describe(DeletionLogMapper.name, () => { domain: domainObject.domain, operation: domainObject.operation, deletionRequestId: new ObjectId(domainObject.deletionRequestId), - modifiedCounter: domainObject.modifiedCounter, - deletedCounter: domainObject.deletedCounter, + modifiedCount: domainObject.modifiedCount, + deletedCount: domainObject.deletedCount, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, }); @@ -126,8 +126,8 @@ describe(DeletionLogMapper.name, () => { domain: domainObject.domain, operation: domainObject.operation, deletionRequestId: new ObjectId(domainObject.deletionRequestId), - modifiedCounter: domainObject.modifiedCounter, - deletedCounter: domainObject.deletedCounter, + modifiedCount: domainObject.modifiedCount, + deletedCount: domainObject.deletedCount, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, }) diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts index 506e7369c84..820cd9d87c0 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.ts @@ -10,8 +10,8 @@ export class DeletionLogMapper { updatedAt: entity.updatedAt, domain: entity.domain, operation: entity.operation, - modifiedCounter: entity.modifiedCounter, - deletedCounter: entity.deletedCounter, + modifiedCount: entity.modifiedCount, + deletedCount: entity.deletedCount, deletionRequestId: entity.deletionRequestId?.toHexString(), }); } @@ -23,8 +23,8 @@ export class DeletionLogMapper { updatedAt: domainObject.updatedAt, domain: domainObject.domain, operation: domainObject.operation, - modifiedCounter: domainObject.modifiedCounter, - deletedCounter: domainObject.deletedCounter, + modifiedCount: domainObject.modifiedCount, + deletedCount: domainObject.deletedCount, deletionRequestId: new ObjectId(domainObject.deletionRequestId), }); } 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 new file mode 100644 index 00000000000..21522e5e924 --- /dev/null +++ b/apps/server/src/modules/deletion/services/deletion-log.service.spec.ts @@ -0,0 +1,110 @@ +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 { DeletionLogRepo } from '../repo'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionLogService } from './deletion-log.service'; +import { DeletionOperationModel } from '../domain/types/deletion-operation-model.enum'; +import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; + +describe(DeletionLogService.name, () => { + let module: TestingModule; + let service: DeletionLogService; + let deletionLogRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + DeletionLogService, + { + provide: DeletionLogRepo, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(DeletionLogService); + deletionLogRepo = module.get(DeletionLogRepo); + + await setupEntities(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('defined', () => { + it('should be defined', () => { + expect(service).toBeDefined(); + }); + }); + + describe('createDeletionRequest', () => { + describe('when creating a deletionRequest', () => { + const setup = () => { + const deletionRequestId = '653e4833cc39e5907a1e18d2'; + const domain = DeletionDomainModel.USER; + const operation = DeletionOperationModel.DELETE; + const modifiedCount = 0; + const deletedCount = 1; + + return { deletionRequestId, domain, operation, modifiedCount, deletedCount }; + }; + + it('should call deletionRequestRepo.create', async () => { + const { deletionRequestId, domain, operation, modifiedCount, deletedCount } = setup(); + + await service.createDeletionLog(deletionRequestId, domain, operation, modifiedCount, deletedCount); + + expect(deletionLogRepo.create).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.any(String), + deletionRequestId, + domain, + operation, + modifiedCount, + deletedCount, + }) + ); + }); + }); + }); + + describe('findByDeletionRequestId', () => { + describe('when finding all logs for deletionRequestId', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + const deletionLog1 = deletionLogFactory.build({ deletionRequestId }); + const deletionLog2 = deletionLogFactory.build({ + deletionRequestId, + domain: DeletionDomainModel.PSEUDONYMS, + }); + const deletionLogs = [deletionLog1, deletionLog2]; + + deletionLogRepo.findAllByDeletionRequestId.mockResolvedValue(deletionLogs); + + return { deletionRequestId, deletionLogs }; + }; + + it('should call deletionLogRepo.findAllByDeletionRequestId', async () => { + const { deletionRequestId } = setup(); + await service.findByDeletionRequestId(deletionRequestId); + + expect(deletionLogRepo.findAllByDeletionRequestId).toBeCalledWith(deletionRequestId); + }); + + it('should return array of two deletionLogs with deletionRequestId', async () => { + const { deletionRequestId, deletionLogs } = setup(); + const result = await service.findByDeletionRequestId(deletionRequestId); + + expect(result).toHaveLength(2); + expect(result).toEqual(deletionLogs); + }); + }); + }); +}); 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 70290bc0c25..d37f9b9c4a9 100644 --- a/apps/server/src/modules/deletion/services/deletion-log.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-log.service.ts @@ -1,40 +1,37 @@ import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionLogRepo } from '../repo'; +import { DeletionLog } from '../domain/deletion-log.do'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionOperationModel } from '../domain/types/deletion-operation-model.enum'; @Injectable() export class DeletionLogService { constructor(private readonly deletionLogRepo: DeletionLogRepo) {} - // async createLogRequest(userId: EntityId): Promise { - // const dateInFuture = new Date(); - // dateInFuture.setDate(dateInFuture.getDate() + 30); - - // const newDeletionLog = new DeletionLog({ - // id: new ObjectId().toHexString(), - // scope: 'scope', - // operation: DeletionOperationModel.DELETE, - // deletionRequestId: new ObjectId(), - // docIds: [new ObjectId(), new ObjectId()], - // }); - - // await this.deletionLogRepo.create(newDeletionLog); - - // return newDeletionLof.id; - // } - - // async findById(deletionRequestId: EntityId): Promise { - // const deletionRequest: DeletionRequest = await this.deletionRequestRepo.findById(deletionRequestId); - - // return deletionRequest; - // } - - // async findAllItemsByDeletionDate(): Promise { - // const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsByDeletionDate(); - - // return itemsToDelete; - // } - - // async deleteById(deletionRequestId: EntityId): Promise { - // await this.deletionRequestRepo.deleteById(deletionRequestId); - // } + async createDeletionLog( + deletionRequestId: EntityId, + domain: DeletionDomainModel, + operation: DeletionOperationModel, + modifiedCounter: number, + deletedCounter: number + ): Promise { + const newDeletionLog = new DeletionLog({ + id: new ObjectId().toHexString(), + domain, + deletionRequestId, + operation, + modifiedCount: modifiedCounter, + deletedCount: deletedCounter, + }); + + await this.deletionLogRepo.create(newDeletionLog); + } + + async findByDeletionRequestId(deletionRequestId: EntityId): Promise { + const deletionLogs: DeletionLog[] = await this.deletionLogRepo.findAllByDeletionRequestId(deletionRequestId); + + return deletionLogs; + } } 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 4013f3adcad..e0396d6e81b 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 @@ -5,6 +5,8 @@ import { setupEntities } from '@shared/testing'; import { DeletionRequestService } from './deletion-request.service'; import { DeletionRequestRepo } from '../repo'; import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; describe(DeletionRequestService.name, () => { let module: TestingModule; @@ -42,18 +44,32 @@ describe(DeletionRequestService.name, () => { }); }); - // TODO createDeletionRequest - // describe('createDeletionRequest', () => { - // describe('when creating a deletionRequest', () => { - // const setup = () => { - // const deletionRequest = deletionRequestFactory.build(); + describe('createDeletionRequest', () => { + describe('when creating a deletionRequest', () => { + const setup = () => { + const itemId = '653e4833cc39e5907a1e18d2'; + const domain = DeletionDomainModel.USER; + + return { itemId, domain }; + }; + + it('should call deletionRequestRepo.create', async () => { + const { itemId, domain } = setup(); - // return { deletionRequest }; - // }; + await service.createDeletionRequest(itemId, domain); - // it('should '); - // }); - // }); + expect(deletionRequestRepo.create).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.any(String), + domain, + deleteAfter: expect.any(Date), + itemId, + status: DeletionStatusModel.REGISTERED, + }) + ); + }); + }); + }); describe('findById', () => { describe('when finding by deletionRequestId', () => { @@ -114,6 +130,40 @@ describe(DeletionRequestService.name, () => { }); }); + describe('update', () => { + describe('when updating deletionRequest', () => { + const setup = () => { + const deletionRequest = deletionRequestFactory.buildWithId(); + + return { deletionRequest }; + }; + + it('should call deletionRequestRepo.update', async () => { + const { deletionRequest } = setup(); + await service.update(deletionRequest); + + expect(deletionRequestRepo.update).toBeCalledWith(deletionRequest); + }); + }); + }); + + describe('markDeletionRequestAsExecuted', () => { + describe('when mark deletionRequest as executed', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + + return { deletionRequestId }; + }; + + it('should call deletionRequestRepo.markDeletionRequestAsExecuted', async () => { + const { deletionRequestId } = setup(); + await service.markDeletionRequestAsExecuted(deletionRequestId); + + expect(deletionRequestRepo.markDeletionRequestAsExecuted).toBeCalledWith(deletionRequestId); + }); + }); + }); + describe('deleteById', () => { describe('when deleting deletionRequest', () => { const setup = () => { 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 38889fe37d2..570661d4f92 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -4,6 +4,7 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestRepo } from '../repo/deletion-request.repo'; import { DeletionRequest } from '../domain/deletion-request.do'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; @Injectable() export class DeletionRequestService { @@ -13,22 +14,23 @@ export class DeletionRequestService { itemId: EntityId, domain: DeletionDomainModel, deleteInMinutes?: number - ): Promise { + ): Promise<{ requestId: EntityId; deletionPlannedAt: Date }> { deleteInMinutes = deleteInMinutes === undefined ? 43200 : deleteInMinutes; const dateOfDeletion = new Date(); - dateOfDeletion.setDate(dateOfDeletion.getDate() + deleteInMinutes * 1000); + dateOfDeletion.setMinutes(dateOfDeletion.getMinutes() + deleteInMinutes); const newDeletionRequest = new DeletionRequest({ id: new ObjectId().toHexString(), domain, deleteAfter: dateOfDeletion, itemId, + status: DeletionStatusModel.REGISTERED, }); await this.deletionRequestRepo.create(newDeletionRequest); - return newDeletionRequest.id; + return { requestId: newDeletionRequest.id, deletionPlannedAt: newDeletionRequest.deleteAfter }; } async findById(deletionRequestId: EntityId): Promise { @@ -43,6 +45,14 @@ export class DeletionRequestService { return itemsToDelete; } + async update(deletionRequestToUpdate: DeletionRequest): Promise { + await this.deletionRequestRepo.update(deletionRequestToUpdate); + } + + async markDeletionRequestAsExecuted(deletionRequestId: EntityId): Promise { + return this.deletionRequestRepo.markDeletionRequestAsExecuted(deletionRequestId); + } + async deleteById(deletionRequestId: EntityId): Promise { await this.deletionRequestRepo.deleteById(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 new file mode 100644 index 00000000000..6525e71e97f --- /dev/null +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts @@ -0,0 +1,396 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { setupEntities } from '@shared/testing'; +import { AccountService } from '@src/modules/account/services/account.service'; +import { ClassService } from '@src/modules/class'; +import { CourseGroupService } from '@src/modules/learnroom/service/coursegroup.service'; +import { CourseService } from '@src/modules/learnroom/service'; +import { FilesService } from '@src/modules/files/service'; +import { LessonService } from '@src/modules/lesson/service'; +import { PseudonymService } from '@src/modules/pseudonym'; +import { TeamService } from '@src/modules/teams'; +import { UserService } from '@src/modules'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionLogService } from '../services/deletion-log.service'; +import { DeletionRequestService } from '../services'; +import { DeletionRequestLog, DeletionRequestProps, DeletionRequestUc } from './deletion-request.uc'; +import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; +import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; + +describe(DeletionRequestUc.name, () => { + let module: TestingModule; + let uc: DeletionRequestUc; + let deletionRequestService: DeepMocked; + let deletionLogService: DeepMocked; + let accountService: DeepMocked; + let classService: DeepMocked; + let courseGroupService: DeepMocked; + let courseService: DeepMocked; + let filesService: DeepMocked; + let lessonService: DeepMocked; + let pseudonymService: DeepMocked; + let teamService: DeepMocked; + let userService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + DeletionRequestUc, + { + provide: DeletionRequestService, + useValue: createMock(), + }, + { + provide: DeletionLogService, + useValue: createMock(), + }, + { + provide: AccountService, + useValue: createMock(), + }, + { + provide: ClassService, + useValue: createMock(), + }, + { + provide: CourseGroupService, + useValue: createMock(), + }, + { + provide: CourseService, + useValue: createMock(), + }, + { + provide: FilesService, + useValue: createMock(), + }, + { + provide: LessonService, + useValue: createMock(), + }, + { + provide: PseudonymService, + useValue: createMock(), + }, + { + provide: TeamService, + useValue: createMock(), + }, + { + provide: UserService, + useValue: createMock(), + }, + ], + }).compile(); + + uc = module.get(DeletionRequestUc); + deletionRequestService = module.get(DeletionRequestService); + deletionLogService = module.get(DeletionLogService); + accountService = module.get(AccountService); + classService = module.get(ClassService); + courseGroupService = module.get(CourseGroupService); + courseService = module.get(CourseService); + filesService = module.get(FilesService); + lessonService = module.get(LessonService); + pseudonymService = module.get(PseudonymService); + teamService = module.get(TeamService); + userService = module.get(UserService); + await setupEntities(); + }); + + const setup = () => { + jest.clearAllMocks(); + const deletionRequestToCreate: DeletionRequestProps = { + targetRef: { + domain: DeletionDomainModel.USER, + itemId: '653e4833cc39e5907a1e18d2', + }, + deleteInMinutes: 1440, + }; + + const deletionRequest = deletionRequestFactory.build(); + const deletionRequestToExecute = deletionRequestFactory.build({ deleteAfter: new Date('2023-01-01') }); + const deletionRequestExecuted = deletionRequestFactory.build({ status: DeletionStatusModel.SUCCESS }); + const deletionLogExecuted1 = deletionLogFactory.build({ deletionRequestId: deletionRequestExecuted.id }); + const deletionLogExecuted2 = deletionLogFactory.build({ + deletionRequestId: deletionRequestExecuted.id, + domain: DeletionDomainModel.ACCOUNT, + modifiedCount: 0, + deletedCount: 1, + }); + + const executedDeletionRequestSummary: DeletionRequestLog = { + targetRef: { + domain: deletionRequestExecuted.domain, + itemId: deletionRequestExecuted.itemId, + }, + deletionPlannedAt: deletionRequestExecuted.deleteAfter, + statistics: [ + { + domain: deletionLogExecuted1.domain, + modifiedCount: deletionLogExecuted1.modifiedCount, + deletedCount: deletionLogExecuted1.deletedCount, + }, + { + domain: deletionLogExecuted2.domain, + modifiedCount: deletionLogExecuted2.modifiedCount, + deletedCount: deletionLogExecuted2.deletedCount, + }, + ], + }; + + const notExecutedDeletionRequestSummary: DeletionRequestLog = { + targetRef: { + domain: deletionRequest.domain, + itemId: deletionRequest.itemId, + }, + deletionPlannedAt: deletionRequest.deleteAfter, + }; + + classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); + courseGroupService.deleteUserDataFromCourseGroup.mockResolvedValueOnce(2); + courseService.deleteUserDataFromCourse.mockResolvedValueOnce(2); + filesService.markFilesOwnedByUserForDeletion.mockResolvedValueOnce(2); + filesService.removeUserPermissionsToAnyFiles.mockResolvedValueOnce(2); + lessonService.deleteUserDataFromLessons.mockResolvedValueOnce(2); + pseudonymService.deleteByUserId.mockResolvedValueOnce(2); + teamService.deleteUserDataFromTeams.mockResolvedValueOnce(2); + userService.deleteUser.mockResolvedValueOnce(1); + + return { + deletionRequestToCreate, + deletionRequest, + deletionRequestToExecute, + deletionRequestExecuted, + notExecutedDeletionRequestSummary, + executedDeletionRequestSummary, + deletionLogExecuted1, + deletionLogExecuted2, + }; + }; + + describe('createDeletionRequest', () => { + describe('when creating a deletionRequest', () => { + it('should call the service to create the deletionRequest', async () => { + const { deletionRequestToCreate } = setup(); + + await uc.createDeletionRequest(deletionRequestToCreate); + + expect(deletionRequestService.createDeletionRequest).toHaveBeenCalledWith( + deletionRequestToCreate.targetRef.itemId, + deletionRequestToCreate.targetRef.domain, + deletionRequestToCreate.deleteInMinutes + ); + }); + + it('should return the deletionRequestID and deletionPlannedAt', async () => { + const { deletionRequestToCreate, deletionRequest } = setup(); + + deletionRequestService.createDeletionRequest.mockResolvedValueOnce({ + requestId: deletionRequest.id, + deletionPlannedAt: deletionRequest.deleteAfter, + }); + + const result = await uc.createDeletionRequest(deletionRequestToCreate); + + expect(result).toEqual({ + requestId: deletionRequest.id, + deletionPlannedAt: deletionRequest.deleteAfter, + }); + }); + }); + }); + + describe('executeDeletionRequests', () => { + describe('when executing deletionRequests', () => { + it('should call deletionRequestService.findAllItemsByDeletionDate', async () => { + await uc.executeDeletionRequests(); + + expect(deletionRequestService.findAllItemsByDeletionDate).toHaveBeenCalled(); + }); + + it('should call deletionRequestService.markDeletionRequestAsExecuted to update status of deletionRequests', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(deletionRequestService.markDeletionRequestAsExecuted).toHaveBeenCalledWith(deletionRequestToExecute.id); + }); + + it('should call accountService.deleteByUserId to delete user data in account module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(accountService.deleteByUserId).toHaveBeenCalled(); + }); + + it('should call classService.deleteUserDataFromClasses to delete user data in class module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(classService.deleteUserDataFromClasses).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call courseGroupService.deleteUserDataFromCourseGroup to delete user data in courseGroup module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(courseGroupService.deleteUserDataFromCourseGroup).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call courseService.deleteUserDataFromCourse to delete user data in course module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(courseService.deleteUserDataFromCourse).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call filesService.markFilesOwnedByUserForDeletion to mark users files to delete in file module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(filesService.markFilesOwnedByUserForDeletion).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call filesService.removeUserPermissionsToAnyFiles to remove users permissions to any files in file module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(filesService.removeUserPermissionsToAnyFiles).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call lessonService.deleteUserDataFromLessons to delete users data in lesson module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(lessonService.deleteUserDataFromLessons).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call pseudonymService.deleteByUserId to delete users data in pseudonym module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(pseudonymService.deleteByUserId).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call teamService.deleteUserDataFromTeams to delete users data in teams module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(teamService.deleteUserDataFromTeams).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call userService.deleteUsers to delete user in user module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(userService.deleteUser).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + }); + + it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(9); + }); + }); + }); + + describe('findById', () => { + describe('when searching for logs for deletionRequest which was executed', () => { + it('should call to deletionRequestService and deletionLogService', async () => { + const { deletionRequestExecuted } = setup(); + + deletionRequestService.findById.mockResolvedValueOnce(deletionRequestExecuted); + + await uc.findById(deletionRequestExecuted.id); + + expect(deletionRequestService.findById).toHaveBeenCalledWith(deletionRequestExecuted.id); + expect(deletionLogService.findByDeletionRequestId).toHaveBeenCalledWith(deletionRequestExecuted.id); + }); + + it('should return object with summary of deletionRequest', async () => { + const { deletionRequestExecuted, deletionLogExecuted1, deletionLogExecuted2, executedDeletionRequestSummary } = + setup(); + + deletionRequestService.findById.mockResolvedValueOnce(deletionRequestExecuted); + deletionLogService.findByDeletionRequestId.mockResolvedValueOnce([deletionLogExecuted1, deletionLogExecuted2]); + + const result = await uc.findById(deletionRequestExecuted.id); + + expect(result).toEqual(executedDeletionRequestSummary); + }); + }); + + describe('when searching for logs for deletionRequest which was not executed', () => { + it('should call to deletionRequestService', async () => { + const { deletionRequest } = setup(); + + deletionRequestService.findById.mockResolvedValueOnce(deletionRequest); + + await uc.findById(deletionRequest.id); + + expect(deletionRequestService.findById).toHaveBeenCalledWith(deletionRequest.id); + expect(deletionLogService.findByDeletionRequestId).not.toHaveBeenCalled(); + }); + + it('should return object with summary of deletionRequest', async () => { + const { deletionRequest, notExecutedDeletionRequestSummary } = setup(); + + deletionRequestService.findById.mockResolvedValueOnce(deletionRequest); + + const result = await uc.findById(deletionRequest.id); + + expect(result).toEqual(notExecutedDeletionRequestSummary); + }); + }); + }); + + describe('deleteDeletionRequestById', () => { + describe('when deleting a deletionRequestId', () => { + it('should call the service deletionRequestService.deleteById', async () => { + const { deletionRequest } = setup(); + + await uc.deleteDeletionRequestById(deletionRequest.id); + + expect(deletionRequestService.deleteById).toHaveBeenCalledWith(deletionRequest.id); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts new file mode 100644 index 00000000000..982c80e88ae --- /dev/null +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -0,0 +1,219 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { PseudonymService } from '@src/modules/pseudonym'; +import { UserService } from '@src/modules'; +import { TeamService } from '@src/modules/teams'; +import { ClassService } from '@src/modules/class'; +import { LessonService } from '@src/modules/lesson/service'; +import { CourseService } from '@src/modules/learnroom/service'; +import { CourseGroupService } from '@src/modules/learnroom/service/coursegroup.service'; +import { FilesService } from '@src/modules/files/service'; +import { AccountService } from '@src/modules/account/services/account.service'; +import { DeletionRequestService } from '../services/deletion-request.service'; +import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; +import { DeletionLogService } from '../services/deletion-log.service'; +import { DeletionRequest } from '../domain/deletion-request.do'; +import { DeletionOperationModel } from '../domain/types/deletion-operation-model.enum'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; +import { DeletionLog } from '../domain/deletion-log.do'; + +export interface DeletionRequestLog { + targetRef: { domain: DeletionDomainModel; itemId: EntityId }; + deletionPlannedAt: Date; + statistics?: DeletionLogStatistic[]; +} + +export interface DeletionLogStatistic { + domain: DeletionDomainModel; + modifiedCount?: number; + deletedCount?: number; +} + +export interface DeletionRequestProps { + targetRef: { domain: DeletionDomainModel; itemId: EntityId }; + deleteInMinutes?: number; +} + +@Injectable() +export class DeletionRequestUc { + constructor( + private readonly deletionRequestService: DeletionRequestService, + private readonly deletionLogService: DeletionLogService, + private readonly accountService: AccountService, + private readonly classService: ClassService, + private readonly courseGroupService: CourseGroupService, + private readonly courseService: CourseService, + private readonly filesService: FilesService, + private readonly lessonService: LessonService, + private readonly pseudonymService: PseudonymService, + private readonly teamService: TeamService, + private readonly userService: UserService + ) {} + + async createDeletionRequest( + deletionRequest: DeletionRequestProps + ): Promise<{ requestId: EntityId; deletionPlannedAt: Date }> { + const result = await this.deletionRequestService.createDeletionRequest( + deletionRequest.targetRef.itemId, + deletionRequest.targetRef.domain, + deletionRequest.deleteInMinutes + ); + + return result; + } + + async executeDeletionRequests(): Promise { + const deletionRequestToExecution: DeletionRequest[] = + await this.deletionRequestService.findAllItemsByDeletionDate(); + + for (const req of deletionRequestToExecution) { + // eslint-disable-next-line no-await-in-loop + await this.executeDeletionRequest(req); + } + } + + async findById(deletionRequestId: EntityId): Promise { + const deletionRequest = await this.deletionRequestService.findById(deletionRequestId); + let response: DeletionRequestLog = { + targetRef: { + domain: deletionRequest.domain, + itemId: deletionRequest.itemId, + }, + deletionPlannedAt: deletionRequest.deleteAfter, + }; + + if (deletionRequest.status === DeletionStatusModel.SUCCESS) { + const deletionLog: DeletionLog[] = await this.deletionLogService.findByDeletionRequestId(deletionRequestId); + const deletionLogStatistic: DeletionLogStatistic[] = deletionLog.map((log) => { + return { + domain: log.domain, + modifiedCount: log.modifiedCount, + deletedCount: log.deletedCount, + }; + }); + response = { ...response, statistics: deletionLogStatistic }; + } + + return response; + } + + async deleteDeletionRequestById(deletionRequestId: EntityId): Promise { + await this.deletionRequestService.deleteById(deletionRequestId); + } + + private async executeDeletionRequest(deletionRequest: DeletionRequest): Promise { + await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); + + const [ + , + classesUpdated, + courseGroupUpdated, + courseUpdated, + fileDeleted, + filesPermisionUpdated, + lessonUpdated, + pseudonymDeleted, + teamsUpdated, + userDeleted, + ] = await Promise.all([ + this.accountService.deleteByUserId(deletionRequest.itemId), + this.classService.deleteUserDataFromClasses(deletionRequest.itemId), + this.courseGroupService.deleteUserDataFromCourseGroup(deletionRequest.itemId), + this.courseService.deleteUserDataFromCourse(deletionRequest.itemId), + this.filesService.markFilesOwnedByUserForDeletion(deletionRequest.itemId), + this.filesService.removeUserPermissionsToAnyFiles(deletionRequest.itemId), + this.lessonService.deleteUserDataFromLessons(deletionRequest.itemId), + this.pseudonymService.deleteByUserId(deletionRequest.itemId), + this.teamService.deleteUserDataFromTeams(deletionRequest.itemId), + this.userService.deleteUser(deletionRequest.itemId), + ]); + + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.ACCOUNT, + DeletionOperationModel.DELETE, + 0, + 1 + ); + + if (classesUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.CLASS, + DeletionOperationModel.UPDATE, + classesUpdated, + 0 + ); + } + + if (courseGroupUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.COURSEGROUP, + DeletionOperationModel.UPDATE, + courseGroupUpdated, + 0 + ); + } + + if (courseUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.COURSE, + DeletionOperationModel.UPDATE, + courseUpdated, + 0 + ); + } + + if (fileDeleted > 0 || filesPermisionUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.FILE, + DeletionOperationModel.UPDATE, + fileDeleted + filesPermisionUpdated, + 0 + ); + } + + if (lessonUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.LESSONS, + DeletionOperationModel.UPDATE, + lessonUpdated, + 0 + ); + } + + if (pseudonymDeleted > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.PSEUDONYMS, + DeletionOperationModel.DELETE, + 0, + pseudonymDeleted + ); + } + + if (teamsUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.TEAMS, + DeletionOperationModel.UPDATE, + teamsUpdated, + 0 + ); + } + + if (userDeleted > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.USER, + DeletionOperationModel.DELETE, + 0, + userDeleted + ); + } + } +} diff --git a/apps/server/src/modules/deletion/uc/deletion-worker.us.ts b/apps/server/src/modules/deletion/uc/deletion-worker.us.ts deleted file mode 100644 index 9dbb19b41aa..00000000000 --- a/apps/server/src/modules/deletion/uc/deletion-worker.us.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AuthorizationService } from '@src/modules/authorization'; -import { DeletionRequestService } from '../services/deletion-request.service'; -import { DeletionRequest } from '../domain/deletion-request.do'; - -@Injectable() -export class DeletionWorkerUc { - constructor( - private readonly deletionRequestService: DeletionRequestService, - private readonly authorizationService: AuthorizationService - ) {} - - async findAllItemsByDeletionDate(): Promise { - return this.deletionRequestService.findAllItemsByDeletionDate(); - } -} From c56ae3d7dbca30bea3d1b237a9fee7fa8b061fe1 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 1 Nov 2023 23:18:55 +0100 Subject: [PATCH 05/22] fix importing --- apps/server/src/modules/deletion/uc/deletion-request.uc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 982c80e88ae..4cfeb6f9c30 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { PseudonymService } from '@src/modules/pseudonym'; -import { UserService } from '@src/modules'; +import { UserService } from '@src/modules/user'; import { TeamService } from '@src/modules/teams'; import { ClassService } from '@src/modules/class'; import { LessonService } from '@src/modules/lesson/service'; From cad449129d004edf2f5914a0f385e82a7a61686c Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 2 Nov 2023 07:21:28 +0100 Subject: [PATCH 06/22] add type in uc --- apps/server/src/modules/deletion/uc/deletion-request.uc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4cfeb6f9c30..8cd1d3d0025 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -73,7 +73,7 @@ export class DeletionRequestUc { } async findById(deletionRequestId: EntityId): Promise { - const deletionRequest = await this.deletionRequestService.findById(deletionRequestId); + const deletionRequest: DeletionRequest = await this.deletionRequestService.findById(deletionRequestId); let response: DeletionRequestLog = { targetRef: { domain: deletionRequest.domain, From b80ff0e7a98475dd26a0c1c6a6dac834a77d3e08 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Thu, 2 Nov 2023 08:00:26 +0100 Subject: [PATCH 07/22] fix import --- apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6525e71e97f..87f2cf0db39 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 @@ -9,7 +9,7 @@ import { FilesService } from '@src/modules/files/service'; import { LessonService } from '@src/modules/lesson/service'; import { PseudonymService } from '@src/modules/pseudonym'; import { TeamService } from '@src/modules/teams'; -import { UserService } from '@src/modules'; +import { UserService } from '@src/modules/user'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; From 44f1b436987c79f28d31e1c091d8a4caf6ff74cb Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 3 Nov 2023 07:02:53 +0100 Subject: [PATCH 08/22] implement entity, domain, mapper and repo for rocketChat --- .../domain/rocket-chat-user.do.spec.ts | 60 ++++++++ .../rocketchat/domain/rocket-chat-user.do.ts | 37 +++++ .../testing/rocket-chat-user.factory.ts | 18 +++ .../src/modules/rocketchat/entity/index.ts | 1 + .../entity/rocket-chat-user.entity.spec.ts | 70 +++++++++ .../entity/rocket-chat-user.entity.ts | 59 ++++++++ .../rocket-chat-user.entity.factory.ts | 18 +++ .../src/modules/rocketchat/repo/index.ts | 1 + .../modules/rocketchat/repo/mapper/index.ts | 1 + .../mapper/rocket-chat-user.mapper.spec.ts | 60 ++++++++ .../repo/mapper/rocket-chat-user.mapper.ts | 28 ++++ .../repo/rocket-chat-user.repo.spec.ts | 134 ++++++++++++++++++ .../rocketchat/repo/rocket-chat-user.repo.ts | 39 +++++ 13 files changed, 526 insertions(+) create mode 100644 apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts create mode 100644 apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts create mode 100644 apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts create mode 100644 apps/server/src/modules/rocketchat/entity/index.ts create mode 100644 apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts create mode 100644 apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts create mode 100644 apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts create mode 100644 apps/server/src/modules/rocketchat/repo/index.ts create mode 100644 apps/server/src/modules/rocketchat/repo/mapper/index.ts create mode 100644 apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts create mode 100644 apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts create mode 100644 apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts create mode 100644 apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts diff --git a/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts b/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts new file mode 100644 index 00000000000..4c86eb0ff20 --- /dev/null +++ b/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts @@ -0,0 +1,60 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { RocketChatUser } from './rocket-chat-user.do'; +import { rocketChatUserFactory } from './testing/rocket-chat-user.factory'; + +describe(RocketChatUser.name, () => { + describe('constructor', () => { + describe('When constructor is called', () => { + it('should create a rocketChatUser by passing required properties', () => { + const domainObject: RocketChatUser = rocketChatUserFactory.build(); + + expect(domainObject instanceof RocketChatUser).toEqual(true); + }); + }); + + describe('when passed a valid id', () => { + const setup = () => { + const domainObject: RocketChatUser = rocketChatUserFactory.build(); + + return { domainObject }; + }; + + it('should set the id', () => { + const { domainObject } = setup(); + + const rocketChatUserObject: RocketChatUser = new RocketChatUser(domainObject); + + expect(rocketChatUserObject.id).toEqual(domainObject.id); + }); + }); + }); + + describe('getters', () => { + describe('When getters are used', () => { + it('getters should return proper values', () => { + const props = { + id: new ObjectId().toHexString(), + userId: new ObjectId().toHexString(), + username: 'Test.User.shls', + rcId: 'JfMJXua6t29KYXdDc', + authToken: 'OL8e5YCZHy3agGnLS-gHAx1wU4ZCG8-DXU_WZnUxUu6', + createdAt: new Date(), + updatedAt: new Date(), + }; + + const rocketChatUserDo = new RocketChatUser(props); + const gettersValues = { + id: rocketChatUserDo.id, + userId: rocketChatUserDo.userId, + username: rocketChatUserDo.username, + rcId: rocketChatUserDo.rcId, + authToken: rocketChatUserDo.authToken, + createdAt: rocketChatUserDo.createdAt, + updatedAt: rocketChatUserDo.updatedAt, + }; + + expect(gettersValues).toEqual(props); + }); + }); + }); +}); diff --git a/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts b/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts new file mode 100644 index 00000000000..8dfd830f3eb --- /dev/null +++ b/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts @@ -0,0 +1,37 @@ +import { EntityId } from '@shared/domain/types'; +import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; + +export interface RocketChatUserProps extends AuthorizableObject { + userId: EntityId; + username: string; + rcId: string; + authToken?: string; + createdAt?: Date; + updatedAt?: Date; +} + +export class RocketChatUser extends DomainObject { + get userId(): EntityId { + return this.props.userId; + } + + get username(): string { + return this.props.username; + } + + get rcId(): string { + return this.props.rcId; + } + + get authToken(): string | undefined { + return this.props.authToken; + } + + get createdAt(): Date | undefined { + return this.props.createdAt; + } + + get updatedAt(): Date | undefined { + return this.props.updatedAt; + } +} diff --git a/apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts b/apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts new file mode 100644 index 00000000000..3ad6432d1d5 --- /dev/null +++ b/apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts @@ -0,0 +1,18 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseFactory } from '@shared/testing'; +import { RocketChatUser, RocketChatUserProps } from '../rocket-chat-user.do'; + +export const rocketChatUserFactory = BaseFactory.define( + RocketChatUser, + ({ sequence }) => { + return { + id: new ObjectId().toHexString(), + userId: new ObjectId().toHexString(), + username: `username-${sequence}`, + rcId: `rcId-${sequence}`, + authToken: `aythToken-${sequence}`, + createdAt: new Date(), + updatedAt: new Date(), + }; + } +); diff --git a/apps/server/src/modules/rocketchat/entity/index.ts b/apps/server/src/modules/rocketchat/entity/index.ts new file mode 100644 index 00000000000..9528e8da500 --- /dev/null +++ b/apps/server/src/modules/rocketchat/entity/index.ts @@ -0,0 +1 @@ +export * from './rocket-chat-user.entity'; diff --git a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts b/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts new file mode 100644 index 00000000000..798e36d59f1 --- /dev/null +++ b/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts @@ -0,0 +1,70 @@ +import { setupEntities } from '@shared/testing'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { RocketChatUserEntity } from '@src/modules/rocketchat/entity'; + +describe(RocketChatUserEntity.name, () => { + beforeAll(async () => { + await setupEntities(); + }); + + const setup = () => { + jest.clearAllMocks(); + + const props = { + id: new ObjectId().toHexString(), + userId: new ObjectId().toHexString(), + username: 'Test.User.shls', + rcId: 'JfMJXua6t29KYXdDc', + authToken: 'OL8e5YCZHy3agGnLS-gHAx1wU4ZCG8-DXU_WZnUxUu6', + createdAt: new Date(), + updatedAt: new Date(), + }; + + return { props }; + }; + + describe('constructor', () => { + describe('When constructor is called', () => { + it('should throw an error by empty constructor', () => { + // @ts-expect-error: Test case + const test = () => new RocketChatUserEntity(); + expect(test).toThrow(); + }); + + it('should create a rocketChatUser by passing required properties', () => { + const { props } = setup(); + const entity: RocketChatUserEntity = new RocketChatUserEntity(props); + + expect(entity instanceof RocketChatUserEntity).toEqual(true); + }); + + it(`should return a valid object with fields values set from the provided complete props object`, () => { + const { props } = setup(); + const entity: RocketChatUserEntity = new RocketChatUserEntity(props); + + const entityProps = { + id: entity.id, + userId: entity.userId, + username: entity.username, + rcId: entity.rcId, + authToken: entity.authToken, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + expect(entityProps).toEqual(props); + }); + }); + }); + + // describe('executed', () => { + // it('should update status', () => { + // const { props } = setup(); + // const entity: DeletionRequestEntity = new DeletionRequestEntity(props); + + // entity.executed(); + + // expect(entity.status).toEqual(DeletionStatusModel.SUCCESS); + // }); + // }); +}); diff --git a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts b/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts new file mode 100644 index 00000000000..77629868a27 --- /dev/null +++ b/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts @@ -0,0 +1,59 @@ +import { Entity, Index, Property } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; +import { EntityId } from '@shared/domain'; + +export interface RocketChatUserEntityProps { + id?: EntityId; + userId: EntityId; + username: string; + rcId: string; + authToken?: string; + createdAt?: Date; + updatedAt?: Date; +} + +@Entity({ tableName: 'rocketchatuser' }) +export class RocketChatUserEntity extends BaseEntityWithTimestamps { + @Property() + username: string; + + @Property() + @Index() + _userId: ObjectId; + + get userId(): EntityId { + return this._userId.toHexString(); + } + + @Property() + @Index() + rcId: string; + + @Property({ nullable: true }) + authToken?: string; + + constructor(props: RocketChatUserEntityProps) { + super(); + + if (props.id !== undefined) { + this.id = props.id; + } + + this._userId = new ObjectId(props.userId); + this.username = props.username; + this.rcId = props.rcId; + + if (props.authToken !== undefined) { + this.authToken = props.authToken; + } + + if (props.createdAt !== undefined) { + this.createdAt = props.createdAt; + } + + if (props.updatedAt !== undefined) { + this.updatedAt = props.updatedAt; + } + } +} diff --git a/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts b/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts new file mode 100644 index 00000000000..43bc99dffea --- /dev/null +++ b/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts @@ -0,0 +1,18 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseFactory } from '@shared/testing'; +import { RocketChatUserEntity, RocketChatUserEntityProps } from '../rocket-chat-user.entity'; + +export const rocketChatUserEntityFactory = BaseFactory.define( + RocketChatUserEntity, + ({ sequence }) => { + return { + id: new ObjectId().toHexString(), + userId: new ObjectId().toHexString(), + username: `username-${sequence}`, + rcId: `rcId-${sequence}`, + authToken: `aythToken-${sequence}`, + createdAt: new Date(), + updatedAt: new Date(), + }; + } +); diff --git a/apps/server/src/modules/rocketchat/repo/index.ts b/apps/server/src/modules/rocketchat/repo/index.ts new file mode 100644 index 00000000000..b05b92fc380 --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/index.ts @@ -0,0 +1 @@ +export * from './rocket-chat-user.repo'; diff --git a/apps/server/src/modules/rocketchat/repo/mapper/index.ts b/apps/server/src/modules/rocketchat/repo/mapper/index.ts new file mode 100644 index 00000000000..7a33e93289e --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/mapper/index.ts @@ -0,0 +1 @@ +export * from './rocket-chat-user.mapper'; diff --git a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts b/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts new file mode 100644 index 00000000000..06808e7e00a --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts @@ -0,0 +1,60 @@ +import { RocketChatUser } from '../../domain/rocket-chat-user.do'; +import { rocketChatUserFactory } from '../../domain/testing/rocket-chat-user.factory'; +import { RocketChatUserEntity } from '../../entity'; +import { rocketChatUserEntityFactory } from '../../entity/testing/rocket-chat-user.entity.factory'; +import { RocketChatUserMapper } from './rocket-chat-user.mapper'; + +describe(RocketChatUserMapper.name, () => { + describe('mapToDO', () => { + describe('When entity is mapped for domainObject', () => { + it('should properly map the entity to the domain object', () => { + const entity = rocketChatUserEntityFactory.build(); + + const domainObject = RocketChatUserMapper.mapToDO(entity); + + const expectedDomainObject = new RocketChatUser({ + id: entity.id, + userId: entity.userId, + username: entity.username, + rcId: entity.rcId, + authToken: entity.authToken, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }); + + expect(domainObject).toEqual(expectedDomainObject); + }); + }); + }); + + describe('mapToEntity', () => { + describe('When domainObject is mapped for entity', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date()); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should properly map the domainObject to the entity', () => { + const domainObject = rocketChatUserFactory.build(); + + const entity = RocketChatUserMapper.mapToEntity(domainObject); + + const expectedEntity = new RocketChatUserEntity({ + id: domainObject.id, + userId: domainObject.userId, + username: domainObject.username, + rcId: domainObject.rcId, + authToken: domainObject.authToken, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }); + + expect(entity).toEqual(expectedEntity); + }); + }); + }); +}); diff --git a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts b/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts new file mode 100644 index 00000000000..761e459e64d --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts @@ -0,0 +1,28 @@ +import { RocketChatUserEntity } from '../../entity'; +import { RocketChatUser } from '../../domain/rocket-chat-user.do'; + +export class RocketChatUserMapper { + static mapToDO(entity: RocketChatUserEntity): RocketChatUser { + return new RocketChatUser({ + id: entity.id, + userId: entity.userId, + username: entity.username, + rcId: entity.rcId, + authToken: entity.authToken, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }); + } + + static mapToEntity(domainObject: RocketChatUser): RocketChatUserEntity { + return new RocketChatUserEntity({ + id: domainObject.id, + userId: domainObject.userId, + username: domainObject.username, + rcId: domainObject.rcId, + authToken: domainObject.authToken, + createdAt: domainObject.createdAt, + updatedAt: domainObject.updatedAt, + }); + } +} diff --git a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts b/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts new file mode 100644 index 00000000000..84dab676cf8 --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts @@ -0,0 +1,134 @@ +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Test } from '@nestjs/testing'; +import { TestingModule } from '@nestjs/testing/testing-module'; +import { MongoMemoryDatabaseModule } from '@shared/infra/database'; +import { cleanupCollections } from '@shared/testing'; +import { RocketChatUserMapper } from './mapper'; +import { RocketChatUserEntity } from '../entity'; +import { RocketChatUserRepo } from './rocket-chat-user.repo'; +import { rocketChatUserEntityFactory } from '../entity/testing/rocket-chat-user.entity.factory'; +import { RocketChatUser } from '../domain/rocket-chat-user.do'; + +describe(RocketChatUserRepo.name, () => { + let module: TestingModule; + let repo: RocketChatUserRepo; + let em: EntityManager; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ + MongoMemoryDatabaseModule.forRoot({ + entities: [RocketChatUserEntity], + }), + ], + providers: [RocketChatUserRepo, RocketChatUserMapper], + }).compile(); + + repo = module.get(RocketChatUserRepo); + em = module.get(EntityManager); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(async () => { + await cleanupCollections(em); + await em.nativeDelete(RocketChatUserEntity, {}); + }); + + describe('defined', () => { + it('repo should be defined', () => { + expect(repo).toBeDefined(); + }); + + it('entity manager should be defined', () => { + expect(em).toBeDefined(); + }); + + it('should implement entityName getter', () => { + expect(repo.entityName).toBe(RocketChatUserEntity); + }); + }); + + describe('findByUserId', () => { + describe('when searching by userId', () => { + const setup = async () => { + // const rocketChatUserId = new ObjectId().toHexString(); + + // const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build({ userId: rocketChatUserId }); + const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build(); + await em.persistAndFlush(entity); + em.clear(); + const expectedRocketChatUser = { + id: entity.id, + userId: entity.userId, + username: entity.username, + rcId: entity.rcId, + authToken: entity.authToken, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + }; + + return { + entity, + expectedRocketChatUser, + }; + }; + + it('should find the rocketChatUser', async () => { + const { entity, expectedRocketChatUser } = await setup(); + + const result: RocketChatUser = await repo.findByUserId(entity.userId); + + // Verify explicit fields. + expect(result).toEqual(expect.objectContaining(expectedRocketChatUser)); + }); + }); + }); + + describe('deleteById', () => { + describe('when deleting deletionRequest exists', () => { + const setup = async () => { + const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build(); + const rocketChatUserId = entity.userId; + await em.persistAndFlush(entity); + em.clear(); + + return { rocketChatUserId }; + }; + + it('should delete the deletionRequest with deletionRequestId', async () => { + const { rocketChatUserId } = await setup(); + + await repo.deleteByUserId(rocketChatUserId); + + expect(await em.findOne(RocketChatUserEntity, { userId: rocketChatUserId })).toBeNull(); + }); + + it('should return true', async () => { + const { rocketChatUserId } = await setup(); + + const result: boolean = await repo.deleteByUserId(rocketChatUserId); + + expect(result).toEqual(true); + }); + }); + + describe('when no deletionRequestEntity exists', () => { + const setup = () => { + const rocketChatUserId = new ObjectId().toHexString(); + + return { rocketChatUserId }; + }; + + it('should return false', async () => { + const { rocketChatUserId } = setup(); + + const result: boolean = await repo.deleteByUserId(rocketChatUserId); + + expect(result).toEqual(false); + }); + }); + }); +}); diff --git a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts b/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts new file mode 100644 index 00000000000..e32e63dc3f3 --- /dev/null +++ b/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts @@ -0,0 +1,39 @@ +import { EntityManager } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { RocketChatUserEntity } from '../entity'; +import { RocketChatUser } from '../domain/rocket-chat-user.do'; +import { RocketChatUserMapper } from './mapper'; + +@Injectable() +export class RocketChatUserRepo { + constructor(private readonly em: EntityManager) {} + + get entityName() { + return RocketChatUserEntity; + } + + async findByUserId(id: EntityId): Promise { + const entity: RocketChatUserEntity = await this.em.findOneOrFail(RocketChatUserEntity, { + userId: id, + }); + + const mapped: RocketChatUser = RocketChatUserMapper.mapToDO(entity); + + return mapped; + } + + async deleteByUserId(id: EntityId): Promise { + const entity: RocketChatUserEntity | null = await this.em.findOne(RocketChatUserEntity, { + userId: id, + }); + + if (!entity) { + return false; + } + + await this.em.removeAndFlush(entity); + + return true; + } +} From c226c6df042013069919ae619232783cd1033fba Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Fri, 3 Nov 2023 09:40:39 +0100 Subject: [PATCH 09/22] fix most of issue form review --- .../deletion/domain/deletion-log.do.spec.ts | 8 +++- .../domain/deletion-request.do.spec.ts | 17 +++++-- .../deletion/domain/deletion-request.do.ts | 12 ++--- .../factory/deletion-request.factory.ts | 8 ++-- .../types/deletion-status-model.enum.ts | 6 +-- .../entity/deletion-request.entity.spec.ts | 9 ++-- .../entity/deletion-request.entity.ts | 19 +++----- .../factory/deletion-log.entity.factory.ts | 6 +-- .../deletion-request.entity.factory.ts | 10 ++-- .../deletion/repo/deletion-log.repo.spec.ts | 26 +++++----- .../repo/deletion-request.repo.spec.ts | 36 +++++++------- .../repo/mapper/deletion-log.mapper.spec.ts | 47 ++++++++++++++----- .../mapper/deletion-request.mapper.spec.ts | 33 ++++++++----- .../repo/mapper/deletion-request.mapper.ts | 9 ++-- .../deletion/services/deletion-log.service.ts | 8 ++-- .../services/deletion-request.service.spec.ts | 14 +++--- .../services/deletion-request.service.ts | 8 ++-- .../deletion/uc/deletion-request.uc.spec.ts | 36 +++++++------- .../deletion/uc/deletion-request.uc.ts | 32 ++++++------- 19 files changed, 195 insertions(+), 149 deletions(-) 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 860e07f56de..9117ded29c5 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 @@ -33,7 +33,7 @@ describe(DeletionLog.name, () => { describe('getters', () => { describe('When getters are used', () => { - it('getters should return proper values', () => { + const setup = () => { const props = { id: new ObjectId().toHexString(), domain: DeletionDomainModel.USER, @@ -46,6 +46,12 @@ describe(DeletionLog.name, () => { }; const deletionLogDo = new DeletionLog(props); + + return { props, deletionLogDo }; + }; + it('getters should return proper values', () => { + const { props, deletionLogDo } = setup(); + const gettersValues = { id: deletionLogDo.id, domain: deletionLogDo.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 a2262f0b614..3c0eb608c87 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 @@ -33,23 +33,30 @@ describe(DeletionRequest.name, () => { describe('getters', () => { describe('When getters are used', () => { - it('getters should return proper values', () => { + const setup = () => { const props = { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + targetRefDomain: DeletionDomainModel.USER, deleteAfter: new Date(), - itemId: new ObjectId().toHexString(), + targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, createdAt: new Date(), updatedAt: new Date(), }; const deletionRequestDo = new DeletionRequest(props); + + return { props, deletionRequestDo }; + }; + + it('getters should return proper values', () => { + const { props, deletionRequestDo } = setup(); + const gettersValues = { id: deletionRequestDo.id, - domain: deletionRequestDo.domain, + targetRefDomain: deletionRequestDo.targetRefDomain, deleteAfter: deletionRequestDo.deleteAfter, - itemId: deletionRequestDo.itemId, + targetRefId: deletionRequestDo.targetRefId, status: deletionRequestDo.status, createdAt: deletionRequestDo.createdAt, updatedAt: deletionRequestDo.updatedAt, 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 6d1349a65f2..e1a8b289ef0 100644 --- a/apps/server/src/modules/deletion/domain/deletion-request.do.ts +++ b/apps/server/src/modules/deletion/domain/deletion-request.do.ts @@ -6,9 +6,9 @@ import { DeletionStatusModel } from './types/deletion-status-model.enum'; export interface DeletionRequestProps extends AuthorizableObject { createdAt?: Date; updatedAt?: Date; - domain: DeletionDomainModel; + targetRefDomain: DeletionDomainModel; deleteAfter: Date; - itemId: EntityId; + targetRefId: EntityId; status: DeletionStatusModel; } @@ -21,16 +21,16 @@ export class DeletionRequest extends DomainObject { return this.props.updatedAt; } - get domain(): DeletionDomainModel { - return this.props.domain; + get targetRefDomain(): DeletionDomainModel { + return this.props.targetRefDomain; } get deleteAfter(): Date { return this.props.deleteAfter; } - get itemId(): EntityId { - return this.props.itemId; + get targetRefId(): EntityId { + return this.props.targetRefId; } get status(): DeletionStatusModel { 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 36cad2e8aa0..9f87bbc1cbf 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 @@ -6,9 +6,9 @@ import { DeletionDomainModel } from '../../types/deletion-domain-model.enum'; import { DeletionStatusModel } from '../../types/deletion-status-model.enum'; class DeletionRequestFactory extends DoBaseFactory { - withUserIds(itemId: string): this { + withUserIds(id: string): this { const params: DeepPartial = { - itemId, + targetRefId: id, }; return this.params(params); @@ -18,9 +18,9 @@ class DeletionRequestFactory extends DoBaseFactory { return { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + targetRefDomain: DeletionDomainModel.USER, deleteAfter: new Date(), - itemId: new ObjectId().toHexString(), + targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts index 884cccdf68f..4bce8af628c 100644 --- a/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts +++ b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts @@ -1,4 +1,4 @@ -export enum DeletionStatusModel { - 'REGISTERED' = 'registered', - 'SUCCESS' = 'success', +export const enum DeletionStatusModel { + REGISTERED = 'registered', + SUCCESS = 'success', } 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 1552360e2fd..2097f53a9c2 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 @@ -3,7 +3,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestEntity } from '@src/modules/deletion/entity/deletion-request.entity'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; -// import { deletionRequestEntityFactory } from './testing/factory/deletion-request.entity.factory'; describe(DeletionRequestEntity.name, () => { beforeAll(async () => { @@ -15,9 +14,9 @@ describe(DeletionRequestEntity.name, () => { const props = { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + targetRefDomain: DeletionDomainModel.USER, deleteAfter: new Date(), - itemId: new ObjectId().toHexString(), + targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, createdAt: new Date(), updatedAt: new Date(), @@ -47,9 +46,9 @@ describe(DeletionRequestEntity.name, () => { const entityProps = { id: entity.id, - domain: entity.domain, + targetRefDomain: entity.targetRefDomain, deleteAfter: entity.deleteAfter, - itemId: entity.itemId, + targetRefId: entity.targetRefId, status: entity.status, createdAt: entity.createdAt, updatedAt: entity.updatedAt, 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 6494edca17f..5a37d1b71b7 100644 --- a/apps/server/src/modules/deletion/entity/deletion-request.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts @@ -1,35 +1,30 @@ import { Entity, Index, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; -import { ObjectId } from '@mikro-orm/mongodb'; import { EntityId } from '@shared/domain'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; export interface DeletionRequestEntityProps { id?: EntityId; - domain: DeletionDomainModel; + targetRefDomain: DeletionDomainModel; deleteAfter: Date; - itemId: EntityId; + targetRefId: EntityId; status: DeletionStatusModel; createdAt?: Date; updatedAt?: Date; } @Entity({ tableName: 'deletionrequests' }) -@Index({ properties: ['_itemId', 'domain'] }) +@Index({ properties: ['targetRefId', 'targetRefDomain'] }) export class DeletionRequestEntity extends BaseEntityWithTimestamps { @Property() deleteAfter: Date; @Property() - _itemId: ObjectId; - - get itemId(): EntityId { - return this._itemId.toHexString(); - } + targetRefId: EntityId; @Property() - domain: DeletionDomainModel; + targetRefDomain: DeletionDomainModel; @Property() status: DeletionStatusModel; @@ -40,9 +35,9 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { this.id = props.id; } - this.domain = props.domain; + this.targetRefDomain = props.targetRefDomain; this.deleteAfter = props.deleteAfter; - this._itemId = new ObjectId(props.itemId); + this.targetRefId = props.targetRefId; this.status = props.status; if (props.createdAt !== undefined) { 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 a8b40c381db..897fba6820a 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,8 +1,8 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing'; -import { DeletionLogEntity, DeletionLogEntityProps } from '@src/modules/deletion/entity'; -import { DeletionOperationModel } from '@src/modules/deletion/domain/types/deletion-operation-model.enum'; -import { DeletionDomainModel } from '@src/modules/deletion/domain/types/deletion-domain-model.enum'; +import { DeletionLogEntity, DeletionLogEntityProps } from '../../deletion-log.entity'; +import { DeletionOperationModel } from '../../../domain/types/deletion-operation-model.enum'; +import { DeletionDomainModel } from '../../../domain/types/deletion-domain-model.enum'; export const deletionLogEntityFactory = BaseFactory.define( DeletionLogEntity, 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 6e1a0f326ec..3ccba779e3e 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,17 +1,17 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { BaseFactory } from '@shared/testing'; -import { DeletionDomainModel } from '@src/modules/deletion/domain/types/deletion-domain-model.enum'; -import { DeletionStatusModel } from '@src/modules/deletion/domain/types/deletion-status-model.enum'; -import { DeletionRequestEntity, DeletionRequestEntityProps } from '@src/modules/deletion/entity'; +import { DeletionStatusModel } from '../../../domain/types/deletion-status-model.enum'; +import { DeletionRequestEntity, DeletionRequestEntityProps } from '../../deletion-request.entity'; +import { DeletionDomainModel } from '../../../domain/types/deletion-domain-model.enum'; export const deletionRequestEntityFactory = BaseFactory.define( DeletionRequestEntity, () => { return { id: new ObjectId().toHexString(), - domain: DeletionDomainModel.USER, + targetRefDomain: DeletionDomainModel.USER, deleteAfter: new Date(), - itemId: new ObjectId().toHexString(), + targetRefId: new ObjectId().toHexString(), status: DeletionStatusModel.REGISTERED, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts index 94d451023b3..5bc151c3541 100644 --- a/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts +++ b/apps/server/src/modules/deletion/repo/deletion-log.repo.spec.ts @@ -53,10 +53,9 @@ describe(DeletionLogRepo.name, () => { describe('create deletionLog', () => { describe('when deletionLog is new', () => { - it('should create a new deletionLog', async () => { + const setup = () => { const domainObject: DeletionLog = deletionLogFactory.build(); const deletionLogId = domainObject.id; - await repo.create(domainObject); const expectedDomainObject = { id: domainObject.id, @@ -69,9 +68,14 @@ describe(DeletionLogRepo.name, () => { updatedAt: domainObject.updatedAt, }; + return { domainObject, deletionLogId, expectedDomainObject }; + }; + it('should create a new deletionLog', async () => { + const { domainObject, deletionLogId, expectedDomainObject } = setup(); + await repo.create(domainObject); + const result = await repo.findById(deletionLogId); - // expect(result).toEqual(domainObject); expect(result).toEqual(expect.objectContaining(expectedDomainObject)); }); }); @@ -139,14 +143,6 @@ describe(DeletionLogRepo.name, () => { await em.persistAndFlush([deletionLogEntity1, deletionLogEntity2, deletionLogEntity3]); em.clear(); - return { deletionLogEntity1, deletionLogEntity2, deletionLogEntity3, deletionRequest1Id }; - }; - - it('should find deletionRequests with deleteAfter smaller then today', async () => { - const { deletionLogEntity1, deletionLogEntity2, deletionLogEntity3, deletionRequest1Id } = await setup(); - - const results = await repo.findAllByDeletionRequestId(deletionRequest1Id.toHexString()); - const expectedArray = [ { id: deletionLogEntity1.id, @@ -170,6 +166,14 @@ describe(DeletionLogRepo.name, () => { }, ]; + return { deletionLogEntity3, deletionRequest1Id, expectedArray }; + }; + + it('should find deletionRequests with deleteAfter smaller then today', async () => { + const { deletionLogEntity3, deletionRequest1Id, expectedArray } = await setup(); + + const results = await repo.findAllByDeletionRequestId(deletionRequest1Id.toHexString()); + expect(results.length).toEqual(2); // Verify explicit fields. diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts index fd28755a6b1..4abb324dece 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts @@ -71,14 +71,14 @@ describe(DeletionRequestRepo.name, () => { const setup = async () => { const userId = new ObjectId().toHexString(); - const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ targetRefId: userId }); await em.persistAndFlush(entity); const expectedDeletionRequest = { id: entity.id, - domain: entity.domain, + targetRefDomain: entity.targetRefDomain, deleteAfter: entity.deleteAfter, - itemId: entity.itemId, + targetRefId: entity.targetRefId, status: entity.status, createdAt: entity.createdAt, updatedAt: entity.updatedAt, @@ -127,35 +127,35 @@ describe(DeletionRequestRepo.name, () => { await em.persistAndFlush([deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3]); em.clear(); - return { deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3 }; - }; - - it('should find deletionRequests with deleteAfter smaller then today', async () => { - const { deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3 } = await setup(); - - const results = await repo.findAllItemsByDeletionDate(); - const expectedArray = [ { id: deletionRequestEntity1.id, - domain: deletionRequestEntity1.domain, + targetRefDomain: deletionRequestEntity1.targetRefDomain, deleteAfter: deletionRequestEntity1.deleteAfter, - itemId: deletionRequestEntity1.itemId, + targetRefId: deletionRequestEntity1.targetRefId, status: deletionRequestEntity1.status, createdAt: deletionRequestEntity1.createdAt, updatedAt: deletionRequestEntity1.updatedAt, }, { id: deletionRequestEntity2.id, - domain: deletionRequestEntity2.domain, + targetRefDomain: deletionRequestEntity2.targetRefDomain, deleteAfter: deletionRequestEntity2.deleteAfter, - itemId: deletionRequestEntity2.itemId, + targetRefId: deletionRequestEntity2.targetRefId, status: deletionRequestEntity2.status, createdAt: deletionRequestEntity2.createdAt, updatedAt: deletionRequestEntity2.updatedAt, }, ]; + return { deletionRequestEntity3, expectedArray }; + }; + + it('should find deletionRequests with deleteAfter smaller then today', async () => { + const { deletionRequestEntity3, expectedArray } = await setup(); + + const results = await repo.findAllItemsByDeletionDate(); + expect(results.length).toEqual(2); // Verify explicit fields. @@ -175,7 +175,7 @@ describe(DeletionRequestRepo.name, () => { const setup = async () => { const userId = new ObjectId().toHexString(); - const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ targetRefId: userId }); await em.persistAndFlush(entity); // Arrange expected DeletionRequestEntity after changing status @@ -205,7 +205,7 @@ describe(DeletionRequestRepo.name, () => { const setup = async () => { const userId = new ObjectId().toHexString(); - const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ targetRefId: userId }); await em.persistAndFlush(entity); return { entity }; @@ -235,7 +235,7 @@ describe(DeletionRequestRepo.name, () => { describe('when deleting deletionRequest exists', () => { const setup = async () => { const userId = new ObjectId().toHexString(); - const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ itemId: userId }); + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ targetRefId: userId }); const deletionRequestId = entity.id; await em.persistAndFlush(entity); em.clear(); diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts index 008c54502c2..a5823f5ce32 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-log.mapper.spec.ts @@ -8,11 +8,9 @@ import { DeletionLogEntity } from '../../entity'; describe(DeletionLogMapper.name, () => { describe('mapToDO', () => { describe('When entity is mapped for domainObject', () => { - it('should properly map the entity to the domain object', () => { + const setup = () => { const entity = deletionLogEntityFactory.build(); - const domainObject = DeletionLogMapper.mapToDO(entity); - const expectedDomainObject = new DeletionLog({ id: entity.id, domain: entity.domain, @@ -24,6 +22,13 @@ describe(DeletionLogMapper.name, () => { updatedAt: entity.updatedAt, }); + return { entity, expectedDomainObject }; + }; + it('should properly map the entity to the domain object', () => { + const { entity, expectedDomainObject } = setup(); + + const domainObject = DeletionLogMapper.mapToDO(entity); + expect(domainObject).toEqual(expectedDomainObject); }); }); @@ -39,11 +44,9 @@ describe(DeletionLogMapper.name, () => { }); describe('When entities array is mapped for domainObjects array', () => { - it('should properly map the entities to the domain objects', () => { + const setup = () => { const entities = [deletionLogEntityFactory.build()]; - const domainObjects = DeletionLogMapper.mapToDOs(entities); - const expectedDomainObjects = entities.map( (entity) => new DeletionLog({ @@ -58,6 +61,13 @@ describe(DeletionLogMapper.name, () => { }) ); + return { entities, expectedDomainObjects }; + }; + it('should properly map the entities to the domain objects', () => { + const { entities, expectedDomainObjects } = setup(); + + const domainObjects = DeletionLogMapper.mapToDOs(entities); + expect(domainObjects).toEqual(expectedDomainObjects); }); }); @@ -74,11 +84,9 @@ describe(DeletionLogMapper.name, () => { jest.useRealTimers(); }); - it('should properly map the domainObject to the entity', () => { + const setup = () => { const domainObject = deletionLogFactory.build(); - const entities = DeletionLogMapper.mapToEntity(domainObject); - const expectedEntities = new DeletionLogEntity({ id: domainObject.id, domain: domainObject.domain, @@ -90,6 +98,14 @@ describe(DeletionLogMapper.name, () => { updatedAt: domainObject.updatedAt, }); + return { domainObject, expectedEntities }; + }; + + it('should properly map the domainObject to the entity', () => { + const { domainObject, expectedEntities } = setup(); + + const entities = DeletionLogMapper.mapToEntity(domainObject); + expect(entities).toEqual(expectedEntities); }); }); @@ -114,11 +130,9 @@ describe(DeletionLogMapper.name, () => { jest.useRealTimers(); }); - it('should properly map the domainObjects to the entities', () => { + const setup = () => { const domainObjects = [deletionLogFactory.build()]; - const entities = DeletionLogMapper.mapToEntities(domainObjects); - const expectedEntities = domainObjects.map( (domainObject) => new DeletionLogEntity({ @@ -132,6 +146,15 @@ describe(DeletionLogMapper.name, () => { updatedAt: domainObject.updatedAt, }) ); + + return { domainObjects, expectedEntities }; + }; + + it('should properly map the domainObjects to the entities', () => { + const { domainObjects, expectedEntities } = setup(); + + const entities = DeletionLogMapper.mapToEntities(domainObjects); + expect(entities).toEqual(expectedEntities); }); }); diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts index a4bbedc9946..4e880aab54e 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.spec.ts @@ -7,21 +7,27 @@ import { DeletionRequestMapper } from './deletion-request.mapper'; describe(DeletionRequestMapper.name, () => { describe('mapToDO', () => { describe('When entity is mapped for domainObject', () => { - it('should properly map the entity to the domain object', () => { + const setup = () => { const entity = deletionRequestEntityFactory.build(); - const domainObject = DeletionRequestMapper.mapToDO(entity); - const expectedDomainObject = new DeletionRequest({ id: entity.id, - domain: entity.domain, + targetRefDomain: entity.targetRefDomain, deleteAfter: entity.deleteAfter, - itemId: entity.itemId, + targetRefId: entity.targetRefId, status: entity.status, createdAt: entity.createdAt, updatedAt: entity.updatedAt, }); + return { entity, expectedDomainObject }; + }; + + it('should properly map the entity to the domain object', () => { + const { entity, expectedDomainObject } = setup(); + + const domainObject = DeletionRequestMapper.mapToDO(entity); + expect(domainObject).toEqual(expectedDomainObject); }); }); @@ -37,22 +43,27 @@ describe(DeletionRequestMapper.name, () => { afterAll(() => { jest.useRealTimers(); }); - - it('should properly map the domainObject to the entity', () => { + const setup = () => { const domainObject = deletionRequestFactory.build(); - const entity = DeletionRequestMapper.mapToEntity(domainObject); - const expectedEntity = new DeletionRequestEntity({ id: domainObject.id, - domain: domainObject.domain, + targetRefDomain: domainObject.targetRefDomain, deleteAfter: domainObject.deleteAfter, - itemId: domainObject.itemId, + targetRefId: domainObject.targetRefId, status: domainObject.status, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, }); + return { domainObject, expectedEntity }; + }; + + it('should properly map the domainObject to the entity', () => { + const { domainObject, expectedEntity } = setup(); + + const entity = DeletionRequestMapper.mapToEntity(domainObject); + expect(entity).toEqual(expectedEntity); }); }); diff --git a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts index c757606ce69..fd6c273011f 100644 --- a/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts +++ b/apps/server/src/modules/deletion/repo/mapper/deletion-request.mapper.ts @@ -1,4 +1,3 @@ -import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequest } from '../../domain/deletion-request.do'; import { DeletionRequestEntity } from '../../entity'; @@ -8,9 +7,9 @@ export class DeletionRequestMapper { id: entity.id, createdAt: entity.createdAt, updatedAt: entity.updatedAt, - domain: entity.domain, + targetRefDomain: entity.targetRefDomain, deleteAfter: entity.deleteAfter, - itemId: entity.itemId, + targetRefId: entity.targetRefId, status: entity.status, }); } @@ -18,9 +17,9 @@ export class DeletionRequestMapper { static mapToEntity(domainObject: DeletionRequest): DeletionRequestEntity { return new DeletionRequestEntity({ id: domainObject.id, - domain: domainObject.domain, + targetRefDomain: domainObject.targetRefDomain, deleteAfter: domainObject.deleteAfter, - itemId: new ObjectId(domainObject.itemId).toHexString(), + targetRefId: domainObject.targetRefId, createdAt: domainObject.createdAt, updatedAt: domainObject.updatedAt, status: domainObject.status, 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 d37f9b9c4a9..937d422ebb3 100644 --- a/apps/server/src/modules/deletion/services/deletion-log.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-log.service.ts @@ -14,16 +14,16 @@ export class DeletionLogService { deletionRequestId: EntityId, domain: DeletionDomainModel, operation: DeletionOperationModel, - modifiedCounter: number, - deletedCounter: number + modifiedCount: number, + deletedCount: number ): Promise { const newDeletionLog = new DeletionLog({ id: new ObjectId().toHexString(), domain, deletionRequestId, operation, - modifiedCount: modifiedCounter, - deletedCount: deletedCounter, + modifiedCount, + deletedCount, }); await this.deletionLogRepo.create(newDeletionLog); 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 e0396d6e81b..bbe46aef49f 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 @@ -47,23 +47,23 @@ describe(DeletionRequestService.name, () => { describe('createDeletionRequest', () => { describe('when creating a deletionRequest', () => { const setup = () => { - const itemId = '653e4833cc39e5907a1e18d2'; - const domain = DeletionDomainModel.USER; + const targetRefId = '653e4833cc39e5907a1e18d2'; + const targetRefDomain = DeletionDomainModel.USER; - return { itemId, domain }; + return { targetRefId, targetRefDomain }; }; it('should call deletionRequestRepo.create', async () => { - const { itemId, domain } = setup(); + const { targetRefId, targetRefDomain } = setup(); - await service.createDeletionRequest(itemId, domain); + await service.createDeletionRequest(targetRefId, targetRefDomain); expect(deletionRequestRepo.create).toHaveBeenCalledWith( expect.objectContaining({ id: expect.any(String), - domain, + targetRefDomain, deleteAfter: expect.any(Date), - itemId, + targetRefId, status: DeletionStatusModel.REGISTERED, }) ); 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 570661d4f92..51b1a266e4e 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -11,8 +11,8 @@ export class DeletionRequestService { constructor(private readonly deletionRequestRepo: DeletionRequestRepo) {} async createDeletionRequest( - itemId: EntityId, - domain: DeletionDomainModel, + targetRefId: EntityId, + targetRefDomain: DeletionDomainModel, deleteInMinutes?: number ): Promise<{ requestId: EntityId; deletionPlannedAt: Date }> { deleteInMinutes = deleteInMinutes === undefined ? 43200 : deleteInMinutes; @@ -22,9 +22,9 @@ export class DeletionRequestService { const newDeletionRequest = new DeletionRequest({ id: new ObjectId().toHexString(), - domain, + targetRefDomain, deleteAfter: dateOfDeletion, - itemId, + targetRefId, status: DeletionStatusModel.REGISTERED, }); 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 87f2cf0db39..902e5d079a8 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 @@ -103,8 +103,8 @@ describe(DeletionRequestUc.name, () => { jest.clearAllMocks(); const deletionRequestToCreate: DeletionRequestProps = { targetRef: { - domain: DeletionDomainModel.USER, - itemId: '653e4833cc39e5907a1e18d2', + targetRefDoamin: DeletionDomainModel.USER, + targetRefId: '653e4833cc39e5907a1e18d2', }, deleteInMinutes: 1440, }; @@ -122,8 +122,8 @@ describe(DeletionRequestUc.name, () => { const executedDeletionRequestSummary: DeletionRequestLog = { targetRef: { - domain: deletionRequestExecuted.domain, - itemId: deletionRequestExecuted.itemId, + targetRefDoamin: deletionRequestExecuted.targetRefDomain, + targetRefId: deletionRequestExecuted.targetRefId, }, deletionPlannedAt: deletionRequestExecuted.deleteAfter, statistics: [ @@ -142,8 +142,8 @@ describe(DeletionRequestUc.name, () => { const notExecutedDeletionRequestSummary: DeletionRequestLog = { targetRef: { - domain: deletionRequest.domain, - itemId: deletionRequest.itemId, + targetRefDoamin: deletionRequest.targetRefDomain, + targetRefId: deletionRequest.targetRefId, }, deletionPlannedAt: deletionRequest.deleteAfter, }; @@ -178,8 +178,8 @@ describe(DeletionRequestUc.name, () => { await uc.createDeletionRequest(deletionRequestToCreate); expect(deletionRequestService.createDeletionRequest).toHaveBeenCalledWith( - deletionRequestToCreate.targetRef.itemId, - deletionRequestToCreate.targetRef.domain, + deletionRequestToCreate.targetRef.targetRefId, + deletionRequestToCreate.targetRef.targetRefDoamin, deletionRequestToCreate.deleteInMinutes ); }); @@ -237,7 +237,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(classService.deleteUserDataFromClasses).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(classService.deleteUserDataFromClasses).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call courseGroupService.deleteUserDataFromCourseGroup to delete user data in courseGroup module', async () => { @@ -247,7 +247,9 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(courseGroupService.deleteUserDataFromCourseGroup).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(courseGroupService.deleteUserDataFromCourseGroup).toHaveBeenCalledWith( + deletionRequestToExecute.targetRefId + ); }); it('should call courseService.deleteUserDataFromCourse to delete user data in course module', async () => { @@ -257,7 +259,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(courseService.deleteUserDataFromCourse).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(courseService.deleteUserDataFromCourse).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call filesService.markFilesOwnedByUserForDeletion to mark users files to delete in file module', async () => { @@ -267,7 +269,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(filesService.markFilesOwnedByUserForDeletion).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(filesService.markFilesOwnedByUserForDeletion).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call filesService.removeUserPermissionsToAnyFiles to remove users permissions to any files in file module', async () => { @@ -277,7 +279,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(filesService.removeUserPermissionsToAnyFiles).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(filesService.removeUserPermissionsToAnyFiles).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call lessonService.deleteUserDataFromLessons to delete users data in lesson module', async () => { @@ -287,7 +289,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(lessonService.deleteUserDataFromLessons).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(lessonService.deleteUserDataFromLessons).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call pseudonymService.deleteByUserId to delete users data in pseudonym module', async () => { @@ -297,7 +299,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(pseudonymService.deleteByUserId).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(pseudonymService.deleteByUserId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call teamService.deleteUserDataFromTeams to delete users data in teams module', async () => { @@ -307,7 +309,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(teamService.deleteUserDataFromTeams).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(teamService.deleteUserDataFromTeams).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call userService.deleteUsers to delete user in user module', async () => { @@ -317,7 +319,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(userService.deleteUser).toHaveBeenCalledWith(deletionRequestToExecute.itemId); + expect(userService.deleteUser).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { 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 8cd1d3d0025..be201054f79 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -18,7 +18,7 @@ import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum' import { DeletionLog } from '../domain/deletion-log.do'; export interface DeletionRequestLog { - targetRef: { domain: DeletionDomainModel; itemId: EntityId }; + targetRef: { targetRefDoamin: DeletionDomainModel; targetRefId: EntityId }; deletionPlannedAt: Date; statistics?: DeletionLogStatistic[]; } @@ -30,7 +30,7 @@ export interface DeletionLogStatistic { } export interface DeletionRequestProps { - targetRef: { domain: DeletionDomainModel; itemId: EntityId }; + targetRef: { targetRefDoamin: DeletionDomainModel; targetRefId: EntityId }; deleteInMinutes?: number; } @@ -54,8 +54,8 @@ export class DeletionRequestUc { deletionRequest: DeletionRequestProps ): Promise<{ requestId: EntityId; deletionPlannedAt: Date }> { const result = await this.deletionRequestService.createDeletionRequest( - deletionRequest.targetRef.itemId, - deletionRequest.targetRef.domain, + deletionRequest.targetRef.targetRefId, + deletionRequest.targetRef.targetRefDoamin, deletionRequest.deleteInMinutes ); @@ -76,8 +76,8 @@ export class DeletionRequestUc { const deletionRequest: DeletionRequest = await this.deletionRequestService.findById(deletionRequestId); let response: DeletionRequestLog = { targetRef: { - domain: deletionRequest.domain, - itemId: deletionRequest.itemId, + targetRefDoamin: deletionRequest.targetRefDomain, + targetRefId: deletionRequest.targetRefId, }, deletionPlannedAt: deletionRequest.deleteAfter, }; @@ -116,16 +116,16 @@ export class DeletionRequestUc { teamsUpdated, userDeleted, ] = await Promise.all([ - this.accountService.deleteByUserId(deletionRequest.itemId), - this.classService.deleteUserDataFromClasses(deletionRequest.itemId), - this.courseGroupService.deleteUserDataFromCourseGroup(deletionRequest.itemId), - this.courseService.deleteUserDataFromCourse(deletionRequest.itemId), - this.filesService.markFilesOwnedByUserForDeletion(deletionRequest.itemId), - this.filesService.removeUserPermissionsToAnyFiles(deletionRequest.itemId), - this.lessonService.deleteUserDataFromLessons(deletionRequest.itemId), - this.pseudonymService.deleteByUserId(deletionRequest.itemId), - this.teamService.deleteUserDataFromTeams(deletionRequest.itemId), - this.userService.deleteUser(deletionRequest.itemId), + this.accountService.deleteByUserId(deletionRequest.targetRefId), + this.classService.deleteUserDataFromClasses(deletionRequest.targetRefId), + this.courseGroupService.deleteUserDataFromCourseGroup(deletionRequest.targetRefId), + this.courseService.deleteUserDataFromCourse(deletionRequest.targetRefId), + this.filesService.markFilesOwnedByUserForDeletion(deletionRequest.targetRefId), + this.filesService.removeUserPermissionsToAnyFiles(deletionRequest.targetRefId), + this.lessonService.deleteUserDataFromLessons(deletionRequest.targetRefId), + this.pseudonymService.deleteByUserId(deletionRequest.targetRefId), + this.teamService.deleteUserDataFromTeams(deletionRequest.targetRefId), + this.userService.deleteUser(deletionRequest.targetRefId), ]); await this.deletionLogService.createDeletionLog( From 7f2dd4f719b89ea3de33fd4a7bed29b54092e836 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Sun, 5 Nov 2023 08:39:06 +0100 Subject: [PATCH 10/22] add some test,additional status, limit ... --- .../types/deletion-status-model.enum.ts | 1 + .../entity/deletion-request.entity.spec.ts | 13 +- .../entity/deletion-request.entity.ts | 5 + .../deletion/repo/deletion-request-scope.ts | 17 ++ .../repo/deletion-request.repo.spec.ts | 119 +++++++-- .../deletion/repo/deletion-request.repo.ts | 28 ++- .../services/deletion-request.service.spec.ts | 29 ++- .../services/deletion-request.service.ts | 10 +- .../deletion/uc/deletion-request.uc.spec.ts | 233 +++++++++++------- .../deletion/uc/deletion-request.uc.ts | 233 ++++++++++-------- 10 files changed, 460 insertions(+), 228 deletions(-) create mode 100644 apps/server/src/modules/deletion/repo/deletion-request-scope.ts diff --git a/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts index 4bce8af628c..5681d1be214 100644 --- a/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts +++ b/apps/server/src/modules/deletion/domain/types/deletion-status-model.enum.ts @@ -1,4 +1,5 @@ export const enum DeletionStatusModel { + FAILED = 'failed', REGISTERED = 'registered', SUCCESS = 'success', } 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 2097f53a9c2..5ba3c15ce53 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 @@ -60,7 +60,7 @@ describe(DeletionRequestEntity.name, () => { }); describe('executed', () => { - it('should update status', () => { + it('should update status with value success', () => { const { props } = setup(); const entity: DeletionRequestEntity = new DeletionRequestEntity(props); @@ -69,4 +69,15 @@ describe(DeletionRequestEntity.name, () => { expect(entity.status).toEqual(DeletionStatusModel.SUCCESS); }); }); + + describe('failed', () => { + it('should update status with value failed', () => { + const { props } = setup(); + const entity: DeletionRequestEntity = new DeletionRequestEntity(props); + + entity.failed(); + + expect(entity.status).toEqual(DeletionStatusModel.FAILED); + }); + }); }); 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 5a37d1b71b7..150fed4d91e 100644 --- a/apps/server/src/modules/deletion/entity/deletion-request.entity.ts +++ b/apps/server/src/modules/deletion/entity/deletion-request.entity.ts @@ -27,6 +27,7 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { targetRefDomain: DeletionDomainModel; @Property() + @Index() status: DeletionStatusModel; constructor(props: DeletionRequestEntityProps) { @@ -52,4 +53,8 @@ export class DeletionRequestEntity extends BaseEntityWithTimestamps { public executed(): void { this.status = DeletionStatusModel.SUCCESS; } + + public failed(): void { + this.status = DeletionStatusModel.FAILED; + } } diff --git a/apps/server/src/modules/deletion/repo/deletion-request-scope.ts b/apps/server/src/modules/deletion/repo/deletion-request-scope.ts new file mode 100644 index 00000000000..202bc09a887 --- /dev/null +++ b/apps/server/src/modules/deletion/repo/deletion-request-scope.ts @@ -0,0 +1,17 @@ +import { Scope } from '@shared/repo'; +import { DeletionRequestEntity } from '../entity'; +import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; + +export class DeletionRequestScope extends Scope { + byDeleteAfter(currentDate: Date): DeletionRequestScope { + this.addQuery({ deleteAfter: { $lt: currentDate } }); + + return this; + } + + byStatus(): DeletionRequestScope { + this.addQuery({ status: [DeletionStatusModel.REGISTERED, DeletionStatusModel.FAILED] }); + + return this; + } +} diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts index 4abb324dece..0e296a3406d 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts @@ -101,10 +101,10 @@ describe(DeletionRequestRepo.name, () => { }); }); - describe('findAllItemsByDeletionDate', () => { + describe('findAllItemsToExecution', () => { describe('when there is no deletionRequest for execution', () => { it('should return empty array', async () => { - const result = await repo.findAllItemsByDeletionDate(); + const result = await repo.findAllItemsToExecution(); expect(result).toEqual([]); }); @@ -115,27 +115,54 @@ describe(DeletionRequestRepo.name, () => { const dateInFuture = new Date(); dateInFuture.setDate(dateInFuture.getDate() + 30); const deletionRequestEntity1: DeletionRequestEntity = deletionRequestEntityFactory.build({ - deleteAfter: new Date(2023, 9, 1), + createdAt: new Date(2023, 7, 1), + deleteAfter: new Date(2023, 8, 1), + status: DeletionStatusModel.SUCCESS, }); const deletionRequestEntity2: DeletionRequestEntity = deletionRequestEntityFactory.build({ - deleteAfter: new Date(2023, 9, 1), + createdAt: new Date(2023, 7, 1), + deleteAfter: new Date(2023, 8, 1), + status: DeletionStatusModel.FAILED, }); const deletionRequestEntity3: DeletionRequestEntity = deletionRequestEntityFactory.build({ + createdAt: new Date(2023, 8, 1), + deleteAfter: new Date(2023, 9, 1), + }); + const deletionRequestEntity4: DeletionRequestEntity = deletionRequestEntityFactory.build({ + createdAt: new Date(2023, 9, 1), + deleteAfter: new Date(2023, 10, 1), + }); + const deletionRequestEntity5: DeletionRequestEntity = deletionRequestEntityFactory.build({ deleteAfter: dateInFuture, }); - await em.persistAndFlush([deletionRequestEntity1, deletionRequestEntity2, deletionRequestEntity3]); + await em.persistAndFlush([ + deletionRequestEntity1, + deletionRequestEntity2, + deletionRequestEntity3, + deletionRequestEntity4, + deletionRequestEntity5, + ]); em.clear(); const expectedArray = [ { - id: deletionRequestEntity1.id, - targetRefDomain: deletionRequestEntity1.targetRefDomain, - deleteAfter: deletionRequestEntity1.deleteAfter, - targetRefId: deletionRequestEntity1.targetRefId, - status: deletionRequestEntity1.status, - createdAt: deletionRequestEntity1.createdAt, - updatedAt: deletionRequestEntity1.updatedAt, + id: deletionRequestEntity4.id, + targetRefDomain: deletionRequestEntity4.targetRefDomain, + deleteAfter: deletionRequestEntity4.deleteAfter, + targetRefId: deletionRequestEntity4.targetRefId, + status: deletionRequestEntity4.status, + createdAt: deletionRequestEntity4.createdAt, + updatedAt: deletionRequestEntity4.updatedAt, + }, + { + id: deletionRequestEntity3.id, + targetRefDomain: deletionRequestEntity3.targetRefDomain, + deleteAfter: deletionRequestEntity3.deleteAfter, + targetRefId: deletionRequestEntity3.targetRefId, + status: deletionRequestEntity3.status, + createdAt: deletionRequestEntity3.createdAt, + updatedAt: deletionRequestEntity3.updatedAt, }, { id: deletionRequestEntity2.id, @@ -148,24 +175,45 @@ describe(DeletionRequestRepo.name, () => { }, ]; - return { deletionRequestEntity3, expectedArray }; + return { deletionRequestEntity1, deletionRequestEntity5, expectedArray }; }; - it('should find deletionRequests with deleteAfter smaller then today', async () => { - const { deletionRequestEntity3, expectedArray } = await setup(); + it('should find deletionRequests with deleteAfter smaller then today and status with value registered or failed', async () => { + const { deletionRequestEntity1, deletionRequestEntity5, expectedArray } = await setup(); - const results = await repo.findAllItemsByDeletionDate(); + const results = await repo.findAllItemsToExecution(); - expect(results.length).toEqual(2); + expect(results.length).toEqual(3); // Verify explicit fields. expect(results).toEqual( - expect.arrayContaining([expect.objectContaining(expectedArray[0]), expect.objectContaining(expectedArray[1])]) + expect.arrayContaining([ + expect.objectContaining(expectedArray[0]), + expect.objectContaining(expectedArray[1]), + expect.objectContaining(expectedArray[2]), + ]) ); - const result: DeletionRequest = await repo.findById(deletionRequestEntity3.id); + const result1: DeletionRequest = await repo.findById(deletionRequestEntity1.id); + + expect(result1.id).toEqual(deletionRequestEntity1.id); + + const result5: DeletionRequest = await repo.findById(deletionRequestEntity5.id); + + expect(result5.id).toEqual(deletionRequestEntity5.id); + }); + + it('should find deletionRequests to execute with limit = 2', async () => { + const { expectedArray } = await setup(); + + const results = await repo.findAllItemsToExecution({ pagination: { limit: 2 } }); - expect(result.id).toEqual(deletionRequestEntity3.id); + expect(results.length).toEqual(2); + + // Verify explicit fields. + expect(results).toEqual( + expect.arrayContaining([expect.objectContaining(expectedArray[0]), expect.objectContaining(expectedArray[1])]) + ); }); }); }); @@ -200,6 +248,37 @@ describe(DeletionRequestRepo.name, () => { }); }); + describe('markDeletionRequestAsFailed', () => { + describe('when mark deletionRequest as failed', () => { + const setup = async () => { + const userId = new ObjectId().toHexString(); + + const entity: DeletionRequestEntity = deletionRequestEntityFactory.build({ targetRefId: userId }); + await em.persistAndFlush(entity); + + return { entity }; + }; + + it('should update the deletionRequest', async () => { + const { entity } = await setup(); + + const result = await repo.markDeletionRequestAsFailed(entity.id); + + expect(result).toBe(true); + }); + + it('should update the deletionRequest', async () => { + const { entity } = await setup(); + + await repo.markDeletionRequestAsFailed(entity.id); + + const result: DeletionRequest = await repo.findById(entity.id); + + expect(result.status).toEqual(DeletionStatusModel.FAILED); + }); + }); + }); + describe('markDeletionRequestAsExecuted', () => { describe('when mark deletionRequest as executed', () => { const setup = async () => { diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts index 8ab31672f60..d762d19f67f 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -1,9 +1,10 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain'; +import { EntityId, IFindOptions, SortOrder } from '@shared/domain'; import { DeletionRequest } from '../domain/deletion-request.do'; import { DeletionRequestEntity } from '../entity'; import { DeletionRequestMapper } from './mapper/deletion-request.mapper'; +import { DeletionRequestScope } from './deletion-request-scope'; @Injectable() export class DeletionRequestRepo { @@ -29,13 +30,19 @@ export class DeletionRequestRepo { await this.em.flush(); } - async findAllItemsByDeletionDate(): Promise { + async findAllItemsToExecution(options?: IFindOptions): Promise { const currentDate = new Date(); - const itemsToDelete: DeletionRequestEntity[] = await this.em.find(DeletionRequestEntity, { - deleteAfter: { $lt: currentDate }, + const scope = new DeletionRequestScope().byDeleteAfter(currentDate).byStatus(); + const { pagination } = options || { limit: 100 }; + const order = { createdAt: SortOrder.desc }; + + const [deletionRequestEntities] = await this.em.findAndCount(DeletionRequestEntity, scope.query, { + offset: pagination?.skip, + limit: pagination?.limit, + orderBy: order, }); - const mapped: DeletionRequest[] = itemsToDelete.map((entity) => DeletionRequestMapper.mapToDO(entity)); + const mapped: DeletionRequest[] = deletionRequestEntities.map((entity) => DeletionRequestMapper.mapToDO(entity)); return mapped; } @@ -58,6 +65,17 @@ export class DeletionRequestRepo { return true; } + async markDeletionRequestAsFailed(deletionRequestId: EntityId): Promise { + const deletionRequest: DeletionRequestEntity = await this.em.findOneOrFail(DeletionRequestEntity, { + id: deletionRequestId, + }); + + deletionRequest.failed(); + await this.em.persistAndFlush(deletionRequest); + + return true; + } + async deleteById(deletionRequestId: EntityId): Promise { const entity: DeletionRequestEntity | null = await this.em.findOne(DeletionRequestEntity, { id: deletionRequestId, 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 bbe46aef49f..fcccfc433db 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 @@ -100,7 +100,7 @@ describe(DeletionRequestService.name, () => { }); }); - describe('findAllItemsByDeletionDate', () => { + describe('findAllItemsToExecute', () => { describe('when finding all deletionRequests for execution', () => { const setup = () => { const dateInPast = new Date(); @@ -108,21 +108,21 @@ describe(DeletionRequestService.name, () => { const deletionRequest1 = deletionRequestFactory.build({ deleteAfter: dateInPast }); const deletionRequest2 = deletionRequestFactory.build({ deleteAfter: dateInPast }); - deletionRequestRepo.findAllItemsByDeletionDate.mockResolvedValue([deletionRequest1, deletionRequest2]); + deletionRequestRepo.findAllItemsToExecution.mockResolvedValue([deletionRequest1, deletionRequest2]); const deletionRequests = [deletionRequest1, deletionRequest2]; return { deletionRequests }; }; it('should call deletionRequestRepo.findAllItemsByDeletionDate', async () => { - await service.findAllItemsByDeletionDate(); + await service.findAllItemsToExecute(); - expect(deletionRequestRepo.findAllItemsByDeletionDate).toBeCalled(); + expect(deletionRequestRepo.findAllItemsToExecution).toBeCalled(); }); - it('should return array of two deletionRequests with date smaller than today', async () => { + it('should return array of two deletionRequests to execute', async () => { const { deletionRequests } = setup(); - const result = await service.findAllItemsByDeletionDate(); + const result = await service.findAllItemsToExecute(); expect(result).toHaveLength(2); expect(result).toEqual(deletionRequests); @@ -164,6 +164,23 @@ describe(DeletionRequestService.name, () => { }); }); + describe('markDeletionRequestAsFailed', () => { + describe('when mark deletionRequest as failed', () => { + const setup = () => { + const deletionRequestId = new ObjectId().toHexString(); + + return { deletionRequestId }; + }; + + it('should call deletionRequestRepo.markDeletionRequestAsExecuted', async () => { + const { deletionRequestId } = setup(); + await service.markDeletionRequestAsFailed(deletionRequestId); + + expect(deletionRequestRepo.markDeletionRequestAsFailed).toBeCalledWith(deletionRequestId); + }); + }); + }); + describe('deleteById', () => { describe('when deleting deletionRequest', () => { const setup = () => { 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 51b1a266e4e..b04a29f68e4 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain'; +import { EntityId, IFindOptions } from '@shared/domain'; import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestRepo } from '../repo/deletion-request.repo'; import { DeletionRequest } from '../domain/deletion-request.do'; @@ -39,8 +39,8 @@ export class DeletionRequestService { return deletionRequest; } - async findAllItemsByDeletionDate(): Promise { - const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsByDeletionDate(); + async findAllItemsToExecute(options?: IFindOptions): Promise { + const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsToExecution(options); return itemsToDelete; } @@ -53,6 +53,10 @@ export class DeletionRequestService { return this.deletionRequestRepo.markDeletionRequestAsExecuted(deletionRequestId); } + async markDeletionRequestAsFailed(deletionRequestId: EntityId): Promise { + return this.deletionRequestRepo.markDeletionRequestAsFailed(deletionRequestId); + } + async deleteById(deletionRequestId: EntityId): Promise { await this.deletionRequestRepo.deleteById(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 902e5d079a8..340df09200f 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 @@ -99,79 +99,25 @@ describe(DeletionRequestUc.name, () => { await setupEntities(); }); - const setup = () => { - jest.clearAllMocks(); - const deletionRequestToCreate: DeletionRequestProps = { - targetRef: { - targetRefDoamin: DeletionDomainModel.USER, - targetRefId: '653e4833cc39e5907a1e18d2', - }, - deleteInMinutes: 1440, - }; - - const deletionRequest = deletionRequestFactory.build(); - const deletionRequestToExecute = deletionRequestFactory.build({ deleteAfter: new Date('2023-01-01') }); - const deletionRequestExecuted = deletionRequestFactory.build({ status: DeletionStatusModel.SUCCESS }); - const deletionLogExecuted1 = deletionLogFactory.build({ deletionRequestId: deletionRequestExecuted.id }); - const deletionLogExecuted2 = deletionLogFactory.build({ - deletionRequestId: deletionRequestExecuted.id, - domain: DeletionDomainModel.ACCOUNT, - modifiedCount: 0, - deletedCount: 1, - }); - - const executedDeletionRequestSummary: DeletionRequestLog = { - targetRef: { - targetRefDoamin: deletionRequestExecuted.targetRefDomain, - targetRefId: deletionRequestExecuted.targetRefId, - }, - deletionPlannedAt: deletionRequestExecuted.deleteAfter, - statistics: [ - { - domain: deletionLogExecuted1.domain, - modifiedCount: deletionLogExecuted1.modifiedCount, - deletedCount: deletionLogExecuted1.deletedCount, - }, - { - domain: deletionLogExecuted2.domain, - modifiedCount: deletionLogExecuted2.modifiedCount, - deletedCount: deletionLogExecuted2.deletedCount, - }, - ], - }; - - const notExecutedDeletionRequestSummary: DeletionRequestLog = { - targetRef: { - targetRefDoamin: deletionRequest.targetRefDomain, - targetRefId: deletionRequest.targetRefId, - }, - deletionPlannedAt: deletionRequest.deleteAfter, - }; - - classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); - courseGroupService.deleteUserDataFromCourseGroup.mockResolvedValueOnce(2); - courseService.deleteUserDataFromCourse.mockResolvedValueOnce(2); - filesService.markFilesOwnedByUserForDeletion.mockResolvedValueOnce(2); - filesService.removeUserPermissionsToAnyFiles.mockResolvedValueOnce(2); - lessonService.deleteUserDataFromLessons.mockResolvedValueOnce(2); - pseudonymService.deleteByUserId.mockResolvedValueOnce(2); - teamService.deleteUserDataFromTeams.mockResolvedValueOnce(2); - userService.deleteUser.mockResolvedValueOnce(1); - - return { - deletionRequestToCreate, - deletionRequest, - deletionRequestToExecute, - deletionRequestExecuted, - notExecutedDeletionRequestSummary, - executedDeletionRequestSummary, - deletionLogExecuted1, - deletionLogExecuted2, - }; - }; - describe('createDeletionRequest', () => { describe('when creating a deletionRequest', () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequestToCreate: DeletionRequestProps = { + targetRef: { + targetRefDoamin: DeletionDomainModel.USER, + targetRefId: '653e4833cc39e5907a1e18d2', + }, + deleteInMinutes: 1440, + }; + const deletionRequest = deletionRequestFactory.build(); + + return { + deletionRequestToCreate, + deletionRequest, + }; + }; + it('should call the service to create the deletionRequest', async () => { const { deletionRequestToCreate } = setup(); @@ -204,16 +150,35 @@ describe(DeletionRequestUc.name, () => { describe('executeDeletionRequests', () => { describe('when executing deletionRequests', () => { - it('should call deletionRequestService.findAllItemsByDeletionDate', async () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequestToExecute = deletionRequestFactory.build({ deleteAfter: new Date('2023-01-01') }); + + classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); + courseGroupService.deleteUserDataFromCourseGroup.mockResolvedValueOnce(2); + courseService.deleteUserDataFromCourse.mockResolvedValueOnce(2); + filesService.markFilesOwnedByUserForDeletion.mockResolvedValueOnce(2); + filesService.removeUserPermissionsToAnyFiles.mockResolvedValueOnce(2); + lessonService.deleteUserDataFromLessons.mockResolvedValueOnce(2); + pseudonymService.deleteByUserId.mockResolvedValueOnce(2); + teamService.deleteUserDataFromTeams.mockResolvedValueOnce(2); + userService.deleteUser.mockResolvedValueOnce(1); + + return { + deletionRequestToExecute, + }; + }; + + it('should call deletionRequestService.findAllItemsToExecute', async () => { await uc.executeDeletionRequests(); - expect(deletionRequestService.findAllItemsByDeletionDate).toHaveBeenCalled(); + expect(deletionRequestService.findAllItemsToExecute).toHaveBeenCalled(); }); it('should call deletionRequestService.markDeletionRequestAsExecuted to update status of deletionRequests', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -223,7 +188,7 @@ describe(DeletionRequestUc.name, () => { it('should call accountService.deleteByUserId to delete user data in account module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -233,7 +198,7 @@ describe(DeletionRequestUc.name, () => { it('should call classService.deleteUserDataFromClasses to delete user data in class module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -243,7 +208,7 @@ describe(DeletionRequestUc.name, () => { it('should call courseGroupService.deleteUserDataFromCourseGroup to delete user data in courseGroup module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -255,7 +220,7 @@ describe(DeletionRequestUc.name, () => { it('should call courseService.deleteUserDataFromCourse to delete user data in course module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -265,7 +230,7 @@ describe(DeletionRequestUc.name, () => { it('should call filesService.markFilesOwnedByUserForDeletion to mark users files to delete in file module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -275,7 +240,7 @@ describe(DeletionRequestUc.name, () => { it('should call filesService.removeUserPermissionsToAnyFiles to remove users permissions to any files in file module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -285,7 +250,7 @@ describe(DeletionRequestUc.name, () => { it('should call lessonService.deleteUserDataFromLessons to delete users data in lesson module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -295,7 +260,7 @@ describe(DeletionRequestUc.name, () => { it('should call pseudonymService.deleteByUserId to delete users data in pseudonym module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -305,7 +270,7 @@ describe(DeletionRequestUc.name, () => { it('should call teamService.deleteUserDataFromTeams to delete users data in teams module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -315,7 +280,7 @@ describe(DeletionRequestUc.name, () => { it('should call userService.deleteUsers to delete user in user module', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); @@ -325,17 +290,87 @@ describe(DeletionRequestUc.name, () => { it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { const { deletionRequestToExecute } = setup(); - deletionRequestService.findAllItemsByDeletionDate.mockResolvedValueOnce([deletionRequestToExecute]); + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); await uc.executeDeletionRequests(); - expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(9); + expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(10); + }); + }); + + describe('when an error occurred', () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequestToExecute = deletionRequestFactory.build({ deleteAfter: new Date('2023-01-01') }); + + classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); + courseGroupService.deleteUserDataFromCourseGroup.mockResolvedValueOnce(2); + courseService.deleteUserDataFromCourse.mockResolvedValueOnce(2); + filesService.markFilesOwnedByUserForDeletion.mockResolvedValueOnce(2); + filesService.removeUserPermissionsToAnyFiles.mockResolvedValueOnce(2); + lessonService.deleteUserDataFromLessons.mockResolvedValueOnce(2); + pseudonymService.deleteByUserId.mockResolvedValueOnce(2); + teamService.deleteUserDataFromTeams.mockResolvedValueOnce(2); + userService.deleteUser.mockRejectedValueOnce(new Error()); + + return { + deletionRequestToExecute, + }; + }; + + it('should throw an arror', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(deletionRequestService.markDeletionRequestAsFailed).toHaveBeenCalledWith(deletionRequestToExecute.id); }); }); }); describe('findById', () => { describe('when searching for logs for deletionRequest which was executed', () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequestExecuted = deletionRequestFactory.build({ status: DeletionStatusModel.SUCCESS }); + const deletionLogExecuted1 = deletionLogFactory.build({ deletionRequestId: deletionRequestExecuted.id }); + const deletionLogExecuted2 = deletionLogFactory.build({ + deletionRequestId: deletionRequestExecuted.id, + domain: DeletionDomainModel.ACCOUNT, + modifiedCount: 0, + deletedCount: 1, + }); + + const executedDeletionRequestSummary: DeletionRequestLog = { + targetRef: { + targetRefDoamin: deletionRequestExecuted.targetRefDomain, + targetRefId: deletionRequestExecuted.targetRefId, + }, + deletionPlannedAt: deletionRequestExecuted.deleteAfter, + statistics: [ + { + domain: deletionLogExecuted1.domain, + modifiedCount: deletionLogExecuted1.modifiedCount, + deletedCount: deletionLogExecuted1.deletedCount, + }, + { + domain: deletionLogExecuted2.domain, + modifiedCount: deletionLogExecuted2.modifiedCount, + deletedCount: deletionLogExecuted2.deletedCount, + }, + ], + }; + + return { + deletionRequestExecuted, + executedDeletionRequestSummary, + deletionLogExecuted1, + deletionLogExecuted2, + }; + }; + it('should call to deletionRequestService and deletionLogService', async () => { const { deletionRequestExecuted } = setup(); @@ -361,6 +396,23 @@ describe(DeletionRequestUc.name, () => { }); describe('when searching for logs for deletionRequest which was not executed', () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequest = deletionRequestFactory.build(); + const notExecutedDeletionRequestSummary: DeletionRequestLog = { + targetRef: { + targetRefDoamin: deletionRequest.targetRefDomain, + targetRefId: deletionRequest.targetRefId, + }, + deletionPlannedAt: deletionRequest.deleteAfter, + }; + + return { + deletionRequest, + notExecutedDeletionRequestSummary, + }; + }; + it('should call to deletionRequestService', async () => { const { deletionRequest } = setup(); @@ -386,6 +438,15 @@ describe(DeletionRequestUc.name, () => { describe('deleteDeletionRequestById', () => { describe('when deleting a deletionRequestId', () => { + const setup = () => { + jest.clearAllMocks(); + const deletionRequest = deletionRequestFactory.build(); + + return { + deletionRequest, + }; + }; + it('should call the service deletionRequestService.deleteById', async () => { const { deletionRequest } = 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 be201054f79..0b3ed0bd2e1 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { EntityId } from '@shared/domain'; +import { EntityId, IFindOptions } from '@shared/domain'; import { PseudonymService } from '@src/modules/pseudonym'; import { UserService } from '@src/modules/user'; import { TeamService } from '@src/modules/teams'; @@ -62,9 +62,10 @@ export class DeletionRequestUc { return result; } - async executeDeletionRequests(): Promise { - const deletionRequestToExecution: DeletionRequest[] = - await this.deletionRequestService.findAllItemsByDeletionDate(); + async executeDeletionRequests(options?: IFindOptions): Promise { + const deletionRequestToExecution: DeletionRequest[] = await this.deletionRequestService.findAllItemsToExecute( + options + ); for (const req of deletionRequestToExecution) { // eslint-disable-next-line no-await-in-loop @@ -102,20 +103,7 @@ export class DeletionRequestUc { } private async executeDeletionRequest(deletionRequest: DeletionRequest): Promise { - await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); - - const [ - , - classesUpdated, - courseGroupUpdated, - courseUpdated, - fileDeleted, - filesPermisionUpdated, - lessonUpdated, - pseudonymDeleted, - teamsUpdated, - userDeleted, - ] = await Promise.all([ + await Promise.all([ this.accountService.deleteByUserId(deletionRequest.targetRefId), this.classService.deleteUserDataFromClasses(deletionRequest.targetRefId), this.courseGroupService.deleteUserDataFromCourseGroup(deletionRequest.targetRefId), @@ -126,94 +114,125 @@ export class DeletionRequestUc { this.pseudonymService.deleteByUserId(deletionRequest.targetRefId), this.teamService.deleteUserDataFromTeams(deletionRequest.targetRefId), this.userService.deleteUser(deletionRequest.targetRefId), - ]); - - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.ACCOUNT, - DeletionOperationModel.DELETE, - 0, - 1 - ); - - if (classesUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.CLASS, - DeletionOperationModel.UPDATE, - classesUpdated, - 0 - ); - } - - if (courseGroupUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.COURSEGROUP, - DeletionOperationModel.UPDATE, - courseGroupUpdated, - 0 - ); - } - - if (courseUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.COURSE, - DeletionOperationModel.UPDATE, - courseUpdated, - 0 - ); - } - - if (fileDeleted > 0 || filesPermisionUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.FILE, - DeletionOperationModel.UPDATE, - fileDeleted + filesPermisionUpdated, - 0 - ); - } - - if (lessonUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.LESSONS, - DeletionOperationModel.UPDATE, - lessonUpdated, - 0 - ); - } - - if (pseudonymDeleted > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.PSEUDONYMS, - DeletionOperationModel.DELETE, - 0, - pseudonymDeleted - ); - } - - if (teamsUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.TEAMS, - DeletionOperationModel.UPDATE, - teamsUpdated, - 0 - ); - } - - if (userDeleted > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.USER, - DeletionOperationModel.DELETE, - 0, - userDeleted - ); - } + ]) + .then( + async ([ + , + classesUpdated, + courseGroupUpdated, + courseUpdated, + fileDeleted, + filesPermisionUpdated, + lessonUpdated, + pseudonymDeleted, + teamsUpdated, + userDeleted, + ]) => { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.ACCOUNT, + DeletionOperationModel.DELETE, + 0, + 1 + ); + + if (classesUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.CLASS, + DeletionOperationModel.UPDATE, + classesUpdated, + 0 + ); + } + + if (classesUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.CLASS, + DeletionOperationModel.UPDATE, + classesUpdated, + 0 + ); + } + + if (courseGroupUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.COURSEGROUP, + DeletionOperationModel.UPDATE, + courseGroupUpdated, + 0 + ); + } + + if (courseUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.COURSE, + DeletionOperationModel.UPDATE, + courseUpdated, + 0 + ); + } + + if (fileDeleted > 0 || filesPermisionUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.FILE, + DeletionOperationModel.UPDATE, + fileDeleted + filesPermisionUpdated, + 0 + ); + } + + if (lessonUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.LESSONS, + DeletionOperationModel.UPDATE, + lessonUpdated, + 0 + ); + } + + if (pseudonymDeleted > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.PSEUDONYMS, + DeletionOperationModel.DELETE, + 0, + pseudonymDeleted + ); + } + + if (teamsUpdated > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.TEAMS, + DeletionOperationModel.UPDATE, + teamsUpdated, + 0 + ); + } + + if (userDeleted > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.USER, + DeletionOperationModel.DELETE, + 0, + userDeleted + ); + } + + await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); + + return true; + } + ) + .catch(async () => { + await this.deletionRequestService.markDeletionRequestAsFailed(deletionRequest.id); + }); } } From aba10b427e31f6b25a2914bcc993c6930d515da6 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 08:21:29 +0100 Subject: [PATCH 11/22] implement rocketChat User deletion --- .../modules/rocketchat-user/domain/index.ts | 1 + .../domain/rocket-chat-user.do.spec.ts | 0 .../domain/rocket-chat-user.do.ts | 0 .../testing/rocket-chat-user.factory.ts | 0 .../entity/index.ts | 0 .../entity/rocket-chat-user.entity.spec.ts | 4 +- .../entity/rocket-chat-user.entity.ts | 15 ++- .../rocketchat-user/entity/testing/index.ts | 1 + .../rocket-chat-user.entity.factory.ts | 20 ++++ .../src/modules/rocketchat-user/index.ts | 1 + .../repo/index.ts | 0 .../repo/mapper/index.ts | 0 .../mapper/rocket-chat-user.mapper.spec.ts | 5 +- .../repo/mapper/rocket-chat-user.mapper.ts | 5 +- .../repo/rocket-chat-user.repo.spec.ts | 44 +++++---- .../repo/rocket-chat-user.repo.ts | 20 ++-- .../rocketchat-user/rocketchat-user.module.ts | 8 ++ .../service/rocket-chat-user.service.spec.ts | 94 +++++++++++++++++++ .../service/rocket-chat-user.service.ts | 19 ++++ .../rocket-chat-user.entity.factory.ts | 18 ---- 20 files changed, 186 insertions(+), 69 deletions(-) create mode 100644 apps/server/src/modules/rocketchat-user/domain/index.ts rename apps/server/src/modules/{rocketchat => rocketchat-user}/domain/rocket-chat-user.do.spec.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/domain/rocket-chat-user.do.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/domain/testing/rocket-chat-user.factory.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/entity/index.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/entity/rocket-chat-user.entity.spec.ts (94%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/entity/rocket-chat-user.entity.ts (82%) create mode 100644 apps/server/src/modules/rocketchat-user/entity/testing/index.ts create mode 100644 apps/server/src/modules/rocketchat-user/entity/testing/rocket-chat-user.entity.factory.ts create mode 100644 apps/server/src/modules/rocketchat-user/index.ts rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/index.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/mapper/index.ts (100%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/mapper/rocket-chat-user.mapper.spec.ts (92%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/mapper/rocket-chat-user.mapper.ts (84%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/rocket-chat-user.repo.spec.ts (68%) rename apps/server/src/modules/{rocketchat => rocketchat-user}/repo/rocket-chat-user.repo.ts (61%) create mode 100644 apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts create mode 100644 apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.spec.ts create mode 100644 apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.ts delete mode 100644 apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts diff --git a/apps/server/src/modules/rocketchat-user/domain/index.ts b/apps/server/src/modules/rocketchat-user/domain/index.ts new file mode 100644 index 00000000000..0246dd0f0f9 --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/domain/index.ts @@ -0,0 +1 @@ +export * from './rocket-chat-user.do'; diff --git a/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts b/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts similarity index 100% rename from apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.spec.ts rename to apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts diff --git a/apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts b/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.ts similarity index 100% rename from apps/server/src/modules/rocketchat/domain/rocket-chat-user.do.ts rename to apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.ts diff --git a/apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts b/apps/server/src/modules/rocketchat-user/domain/testing/rocket-chat-user.factory.ts similarity index 100% rename from apps/server/src/modules/rocketchat/domain/testing/rocket-chat-user.factory.ts rename to apps/server/src/modules/rocketchat-user/domain/testing/rocket-chat-user.factory.ts diff --git a/apps/server/src/modules/rocketchat/entity/index.ts b/apps/server/src/modules/rocketchat-user/entity/index.ts similarity index 100% rename from apps/server/src/modules/rocketchat/entity/index.ts rename to apps/server/src/modules/rocketchat-user/entity/index.ts diff --git a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts similarity index 94% rename from apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts rename to apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts index 798e36d59f1..0d10bb44a04 100644 --- a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.spec.ts +++ b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts @@ -1,6 +1,6 @@ import { setupEntities } from '@shared/testing'; import { ObjectId } from '@mikro-orm/mongodb'; -import { RocketChatUserEntity } from '@src/modules/rocketchat/entity'; +import { RocketChatUserEntity } from '@src/modules/rocketchat-user/entity'; describe(RocketChatUserEntity.name, () => { beforeAll(async () => { @@ -12,7 +12,7 @@ describe(RocketChatUserEntity.name, () => { const props = { id: new ObjectId().toHexString(), - userId: new ObjectId().toHexString(), + userId: new ObjectId(), username: 'Test.User.shls', rcId: 'JfMJXua6t29KYXdDc', authToken: 'OL8e5YCZHy3agGnLS-gHAx1wU4ZCG8-DXU_WZnUxUu6', diff --git a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.ts similarity index 82% rename from apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts rename to apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.ts index 77629868a27..6df469e0ddb 100644 --- a/apps/server/src/modules/rocketchat/entity/rocket-chat-user.entity.ts +++ b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.ts @@ -1,11 +1,11 @@ -import { Entity, Index, Property } from '@mikro-orm/core'; +import { Entity, Index, Property, Unique } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { EntityId } from '@shared/domain'; export interface RocketChatUserEntityProps { id?: EntityId; - userId: EntityId; + userId: ObjectId; username: string; rcId: string; authToken?: string; @@ -16,15 +16,12 @@ export interface RocketChatUserEntityProps { @Entity({ tableName: 'rocketchatuser' }) export class RocketChatUserEntity extends BaseEntityWithTimestamps { @Property() + @Unique() username: string; @Property() - @Index() - _userId: ObjectId; - - get userId(): EntityId { - return this._userId.toHexString(); - } + @Unique() + userId: ObjectId; @Property() @Index() @@ -40,7 +37,7 @@ export class RocketChatUserEntity extends BaseEntityWithTimestamps { this.id = props.id; } - this._userId = new ObjectId(props.userId); + this.userId = props.userId; this.username = props.username; this.rcId = props.rcId; diff --git a/apps/server/src/modules/rocketchat-user/entity/testing/index.ts b/apps/server/src/modules/rocketchat-user/entity/testing/index.ts new file mode 100644 index 00000000000..f19ebd8c74a --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/entity/testing/index.ts @@ -0,0 +1 @@ +export * from './rocket-chat-user.entity.factory'; diff --git a/apps/server/src/modules/rocketchat-user/entity/testing/rocket-chat-user.entity.factory.ts b/apps/server/src/modules/rocketchat-user/entity/testing/rocket-chat-user.entity.factory.ts new file mode 100644 index 00000000000..302459a4eb6 --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/entity/testing/rocket-chat-user.entity.factory.ts @@ -0,0 +1,20 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BaseFactory } from '@shared/testing'; +import { RocketChatUserEntity, RocketChatUserEntityProps } from '../rocket-chat-user.entity'; + +class RocketChatUserFactory extends BaseFactory {} + +export const rocketChatUserEntityFactory = RocketChatUserFactory.define< + RocketChatUserEntity, + RocketChatUserEntityProps +>(RocketChatUserEntity, ({ sequence }) => { + return { + id: new ObjectId().toHexString(), + userId: new ObjectId(), + username: `username-${sequence}`, + rcId: `rcId-${sequence}`, + authToken: `aythToken-${sequence}`, + createdAt: new Date(), + updatedAt: new Date(), + }; +}); diff --git a/apps/server/src/modules/rocketchat-user/index.ts b/apps/server/src/modules/rocketchat-user/index.ts new file mode 100644 index 00000000000..dc61a422c20 --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/index.ts @@ -0,0 +1 @@ +export * from './rocketchat-user.module'; diff --git a/apps/server/src/modules/rocketchat/repo/index.ts b/apps/server/src/modules/rocketchat-user/repo/index.ts similarity index 100% rename from apps/server/src/modules/rocketchat/repo/index.ts rename to apps/server/src/modules/rocketchat-user/repo/index.ts diff --git a/apps/server/src/modules/rocketchat/repo/mapper/index.ts b/apps/server/src/modules/rocketchat-user/repo/mapper/index.ts similarity index 100% rename from apps/server/src/modules/rocketchat/repo/mapper/index.ts rename to apps/server/src/modules/rocketchat-user/repo/mapper/index.ts diff --git a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts b/apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.spec.ts similarity index 92% rename from apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts rename to apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.spec.ts index 06808e7e00a..bd5a07abb5c 100644 --- a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.spec.ts +++ b/apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.spec.ts @@ -1,3 +1,4 @@ +import { ObjectId } from '@mikro-orm/mongodb'; import { RocketChatUser } from '../../domain/rocket-chat-user.do'; import { rocketChatUserFactory } from '../../domain/testing/rocket-chat-user.factory'; import { RocketChatUserEntity } from '../../entity'; @@ -14,7 +15,7 @@ describe(RocketChatUserMapper.name, () => { const expectedDomainObject = new RocketChatUser({ id: entity.id, - userId: entity.userId, + userId: entity.userId.toHexString(), username: entity.username, rcId: entity.rcId, authToken: entity.authToken, @@ -45,7 +46,7 @@ describe(RocketChatUserMapper.name, () => { const expectedEntity = new RocketChatUserEntity({ id: domainObject.id, - userId: domainObject.userId, + userId: new ObjectId(domainObject.userId), username: domainObject.username, rcId: domainObject.rcId, authToken: domainObject.authToken, diff --git a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts b/apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.ts similarity index 84% rename from apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts rename to apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.ts index 761e459e64d..3d45c9c34ac 100644 --- a/apps/server/src/modules/rocketchat/repo/mapper/rocket-chat-user.mapper.ts +++ b/apps/server/src/modules/rocketchat-user/repo/mapper/rocket-chat-user.mapper.ts @@ -1,3 +1,4 @@ +import { ObjectId } from '@mikro-orm/mongodb'; import { RocketChatUserEntity } from '../../entity'; import { RocketChatUser } from '../../domain/rocket-chat-user.do'; @@ -5,7 +6,7 @@ export class RocketChatUserMapper { static mapToDO(entity: RocketChatUserEntity): RocketChatUser { return new RocketChatUser({ id: entity.id, - userId: entity.userId, + userId: entity.userId.toHexString(), username: entity.username, rcId: entity.rcId, authToken: entity.authToken, @@ -17,7 +18,7 @@ export class RocketChatUserMapper { static mapToEntity(domainObject: RocketChatUser): RocketChatUserEntity { return new RocketChatUserEntity({ id: domainObject.id, - userId: domainObject.userId, + userId: new ObjectId(domainObject.userId), username: domainObject.username, rcId: domainObject.rcId, authToken: domainObject.authToken, diff --git a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts similarity index 68% rename from apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts rename to apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts index 84dab676cf8..dc5c0a38855 100644 --- a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.spec.ts +++ b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts @@ -1,13 +1,12 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; -import { Test } from '@nestjs/testing'; -import { TestingModule } from '@nestjs/testing/testing-module'; +import { Test, TestingModule } from '@nestjs/testing'; import { MongoMemoryDatabaseModule } from '@shared/infra/database'; import { cleanupCollections } from '@shared/testing'; import { RocketChatUserMapper } from './mapper'; import { RocketChatUserEntity } from '../entity'; import { RocketChatUserRepo } from './rocket-chat-user.repo'; -import { rocketChatUserEntityFactory } from '../entity/testing/rocket-chat-user.entity.factory'; -import { RocketChatUser } from '../domain/rocket-chat-user.do'; +import { RocketChatUser } from '../domain'; +import { rocketChatUserEntityFactory } from '../entity/testing'; describe(RocketChatUserRepo.name, () => { let module: TestingModule; @@ -34,12 +33,13 @@ describe(RocketChatUserRepo.name, () => { afterEach(async () => { await cleanupCollections(em); - await em.nativeDelete(RocketChatUserEntity, {}); + // await em.nativeDelete(RocketChatUserEntity, {}); }); describe('defined', () => { it('repo should be defined', () => { expect(repo).toBeDefined(); + expect(typeof repo.findByUserId).toEqual('function'); }); it('entity manager should be defined', () => { @@ -52,17 +52,15 @@ describe(RocketChatUserRepo.name, () => { }); describe('findByUserId', () => { - describe('when searching by userId', () => { + describe('when searching rocketChatUser by userId', () => { const setup = async () => { - // const rocketChatUserId = new ObjectId().toHexString(); - - // const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build({ userId: rocketChatUserId }); - const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build(); + const userId = new ObjectId(); + const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build({ userId }); await em.persistAndFlush(entity); em.clear(); const expectedRocketChatUser = { id: entity.id, - userId: entity.userId, + userId: entity.userId.toHexString(), username: entity.username, rcId: entity.rcId, authToken: entity.authToken, @@ -79,7 +77,7 @@ describe(RocketChatUserRepo.name, () => { it('should find the rocketChatUser', async () => { const { entity, expectedRocketChatUser } = await setup(); - const result: RocketChatUser = await repo.findByUserId(entity.userId); + const result: RocketChatUser = await repo.findByUserId(entity.userId.toHexString()); // Verify explicit fields. expect(result).toEqual(expect.objectContaining(expectedRocketChatUser)); @@ -87,35 +85,35 @@ describe(RocketChatUserRepo.name, () => { }); }); - describe('deleteById', () => { - describe('when deleting deletionRequest exists', () => { + describe('deleteByUserId', () => { + describe('when deleting rocketChatUser exists', () => { const setup = async () => { const entity: RocketChatUserEntity = rocketChatUserEntityFactory.build(); - const rocketChatUserId = entity.userId; + const rocketChatUserId = entity.userId.toHexString(); await em.persistAndFlush(entity); em.clear(); return { rocketChatUserId }; }; - it('should delete the deletionRequest with deletionRequestId', async () => { + it('should delete the rocketChatUSer with userId', async () => { const { rocketChatUserId } = await setup(); await repo.deleteByUserId(rocketChatUserId); - expect(await em.findOne(RocketChatUserEntity, { userId: rocketChatUserId })).toBeNull(); + expect(await em.findOne(RocketChatUserEntity, { userId: new ObjectId(rocketChatUserId) })).toBeNull(); }); - it('should return true', async () => { + it('should return number equal 1', async () => { const { rocketChatUserId } = await setup(); - const result: boolean = await repo.deleteByUserId(rocketChatUserId); + const result: number = await repo.deleteByUserId(rocketChatUserId); - expect(result).toEqual(true); + expect(result).toEqual(1); }); }); - describe('when no deletionRequestEntity exists', () => { + describe('when no rocketChatUser exists', () => { const setup = () => { const rocketChatUserId = new ObjectId().toHexString(); @@ -125,9 +123,9 @@ describe(RocketChatUserRepo.name, () => { it('should return false', async () => { const { rocketChatUserId } = setup(); - const result: boolean = await repo.deleteByUserId(rocketChatUserId); + const result: number = await repo.deleteByUserId(rocketChatUserId); - expect(result).toEqual(false); + expect(result).toEqual(0); }); }); }); diff --git a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.ts similarity index 61% rename from apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts rename to apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.ts index e32e63dc3f3..741f297f804 100644 --- a/apps/server/src/modules/rocketchat/repo/rocket-chat-user.repo.ts +++ b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.ts @@ -1,4 +1,4 @@ -import { EntityManager } from '@mikro-orm/mongodb'; +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { RocketChatUserEntity } from '../entity'; @@ -13,9 +13,9 @@ export class RocketChatUserRepo { return RocketChatUserEntity; } - async findByUserId(id: EntityId): Promise { + async findByUserId(userId: EntityId): Promise { const entity: RocketChatUserEntity = await this.em.findOneOrFail(RocketChatUserEntity, { - userId: id, + userId: new ObjectId(userId), }); const mapped: RocketChatUser = RocketChatUserMapper.mapToDO(entity); @@ -23,17 +23,11 @@ export class RocketChatUserRepo { return mapped; } - async deleteByUserId(id: EntityId): Promise { - const entity: RocketChatUserEntity | null = await this.em.findOne(RocketChatUserEntity, { - userId: id, + async deleteByUserId(userId: EntityId): Promise { + const promise: Promise = this.em.nativeDelete(RocketChatUserEntity, { + userId: new ObjectId(userId), }); - if (!entity) { - return false; - } - - await this.em.removeAndFlush(entity); - - return true; + return promise; } } diff --git a/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts b/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts new file mode 100644 index 00000000000..053e134f82c --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { RocketChatUserRepo } from './repo'; + +@Module({ + providers: [RocketChatUserRepo], + exports: [], +}) +export class RocketChatUserModule {} diff --git a/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.spec.ts b/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.spec.ts new file mode 100644 index 00000000000..dd8ae17667c --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.spec.ts @@ -0,0 +1,94 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { setupEntities } from '@shared/testing'; +import { RocketChatUserService } from './rocket-chat-user.service'; +import { RocketChatUserRepo } from '../repo'; +import { rocketChatUserFactory } from '../domain/testing/rocket-chat-user.factory'; +import { RocketChatUser } from '../domain'; + +describe(RocketChatUserService.name, () => { + let module: TestingModule; + let service: RocketChatUserService; + let rocketChatUserRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + RocketChatUserService, + { + provide: RocketChatUserRepo, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(RocketChatUserService); + rocketChatUserRepo = module.get(RocketChatUserRepo); + + await setupEntities(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('findByUserId', () => { + describe('when searching rocketChatUser', () => { + const setup = () => { + const userId: string = new ObjectId().toHexString(); + + const rocketChatUser: RocketChatUser = rocketChatUserFactory.build(); + + rocketChatUserRepo.findByUserId.mockResolvedValueOnce(rocketChatUser); + + return { + userId, + rocketChatUser, + }; + }; + + it('should return the rocketChatUser', async () => { + const { userId, rocketChatUser } = setup(); + + const result: RocketChatUser = await service.findByUserId(userId); + + expect(result).toEqual(rocketChatUser); + }); + }); + }); + + describe('deleteUserDataFromClasses', () => { + describe('when deleting rocketChatUser', () => { + const setup = () => { + const userId = new ObjectId().toHexString(); + + rocketChatUserRepo.deleteByUserId.mockResolvedValueOnce(1); + + return { + userId, + }; + }; + + it('should call rocketChatUserRepo', async () => { + const { userId } = setup(); + + await service.deleteByUserId(userId); + + expect(rocketChatUserRepo.deleteByUserId).toBeCalledWith(userId); + }); + + it('should delete rocketChatUser by userId', async () => { + const { userId } = setup(); + + const result: number = await service.deleteByUserId(userId); + + expect(result).toEqual(1); + }); + }); + }); +}); diff --git a/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.ts b/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.ts new file mode 100644 index 00000000000..32a600c0f75 --- /dev/null +++ b/apps/server/src/modules/rocketchat-user/service/rocket-chat-user.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { RocketChatUserRepo } from '../repo'; +import { RocketChatUser } from '../domain'; + +@Injectable() +export class RocketChatUserService { + constructor(private readonly rocketChatUserRepo: RocketChatUserRepo) {} + + public async findByUserId(userId: EntityId): Promise { + const user: RocketChatUser = await this.rocketChatUserRepo.findByUserId(userId); + + return user; + } + + public deleteByUserId(userId: EntityId): Promise { + return this.rocketChatUserRepo.deleteByUserId(userId); + } +} diff --git a/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts b/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts deleted file mode 100644 index 43bc99dffea..00000000000 --- a/apps/server/src/modules/rocketchat/entity/testing/rocket-chat-user.entity.factory.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ObjectId } from '@mikro-orm/mongodb'; -import { BaseFactory } from '@shared/testing'; -import { RocketChatUserEntity, RocketChatUserEntityProps } from '../rocket-chat-user.entity'; - -export const rocketChatUserEntityFactory = BaseFactory.define( - RocketChatUserEntity, - ({ sequence }) => { - return { - id: new ObjectId().toHexString(), - userId: new ObjectId().toHexString(), - username: `username-${sequence}`, - rcId: `rcId-${sequence}`, - authToken: `aythToken-${sequence}`, - createdAt: new Date(), - updatedAt: new Date(), - }; - } -); From 65c7b5a35ae7ea7ad7fa107ece87e772eecf961b Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 08:30:47 +0100 Subject: [PATCH 12/22] add service to module --- .../src/modules/rocketchat-user/rocketchat-user.module.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts b/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts index 053e134f82c..798b2276a4d 100644 --- a/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts +++ b/apps/server/src/modules/rocketchat-user/rocketchat-user.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { RocketChatUserRepo } from './repo'; +import { RocketChatUserService } from './service/rocket-chat-user.service'; +import { RocketChatService } from '../rocketchat/rocket-chat.service'; @Module({ - providers: [RocketChatUserRepo], - exports: [], + providers: [RocketChatUserService, RocketChatUserRepo], + exports: [RocketChatService], }) export class RocketChatUserModule {} From 972dffc26ff2486d5e0e956b7abf93d4bed9ea42 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 11:32:01 +0100 Subject: [PATCH 13/22] small fixes during review --- .../domain/rocket-chat-user.do.spec.ts | 9 ++++++++- .../entity/rocket-chat-user.entity.spec.ts | 11 ----------- .../repo/rocket-chat-user.repo.spec.ts | 1 - 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts b/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts index 4c86eb0ff20..a1be448f80c 100644 --- a/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts +++ b/apps/server/src/modules/rocketchat-user/domain/rocket-chat-user.do.spec.ts @@ -31,7 +31,7 @@ describe(RocketChatUser.name, () => { describe('getters', () => { describe('When getters are used', () => { - it('getters should return proper values', () => { + const setup = () => { const props = { id: new ObjectId().toHexString(), userId: new ObjectId().toHexString(), @@ -43,6 +43,13 @@ describe(RocketChatUser.name, () => { }; const rocketChatUserDo = new RocketChatUser(props); + + return { props, rocketChatUserDo }; + }; + + it('getters should return proper values', () => { + const { props, rocketChatUserDo } = setup(); + const gettersValues = { id: rocketChatUserDo.id, userId: rocketChatUserDo.userId, diff --git a/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts index 0d10bb44a04..9348be5cd84 100644 --- a/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts +++ b/apps/server/src/modules/rocketchat-user/entity/rocket-chat-user.entity.spec.ts @@ -56,15 +56,4 @@ describe(RocketChatUserEntity.name, () => { }); }); }); - - // describe('executed', () => { - // it('should update status', () => { - // const { props } = setup(); - // const entity: DeletionRequestEntity = new DeletionRequestEntity(props); - - // entity.executed(); - - // expect(entity.status).toEqual(DeletionStatusModel.SUCCESS); - // }); - // }); }); diff --git a/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts index dc5c0a38855..75e7d0f64e4 100644 --- a/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts +++ b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts @@ -33,7 +33,6 @@ describe(RocketChatUserRepo.name, () => { afterEach(async () => { await cleanupCollections(em); - // await em.nativeDelete(RocketChatUserEntity, {}); }); describe('defined', () => { From 550f16f90c040b29089a0d8bf99feb78f07efff1 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 14:34:21 +0100 Subject: [PATCH 14/22] implement deletion rocketChat user in KNL deletion module --- .../types/deletion-domain-model.enum.ts | 1 + .../deletion/uc/deletion-request.uc.spec.ts | 55 ++++++++++++++++++- .../deletion/uc/deletion-request.uc.ts | 33 ++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts b/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts index dbfc2e06d8d..1a4f3bcf425 100644 --- a/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts +++ b/apps/server/src/modules/deletion/domain/types/deletion-domain-model.enum.ts @@ -6,6 +6,7 @@ export const enum DeletionDomainModel { FILE = 'file', LESSONS = 'lessons', PSEUDONYMS = 'pseudonyms', + ROCKETCHATUSER = 'rocketChatUser', TEAMS = 'teams', USER = 'user', } 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 340df09200f..f75c051dddb 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 @@ -10,6 +10,8 @@ import { LessonService } from '@src/modules/lesson/service'; import { PseudonymService } from '@src/modules/pseudonym'; import { TeamService } from '@src/modules/teams'; import { UserService } from '@src/modules/user'; +import { RocketChatUserService } from '@src/modules/rocketchat-user/service/rocket-chat-user.service'; +import { RocketChatService } from '@src/modules/rocketchat'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; @@ -17,6 +19,8 @@ import { DeletionRequestLog, DeletionRequestProps, DeletionRequestUc } from './d import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; +import { rocketChatUserFactory } from '@src/modules/rocketchat-user/domain/testing/rocket-chat-user.factory'; +import { RocketChatUser } from '@src/modules/rocketchat-user/domain'; describe(DeletionRequestUc.name, () => { let module: TestingModule; @@ -32,6 +36,8 @@ describe(DeletionRequestUc.name, () => { let pseudonymService: DeepMocked; let teamService: DeepMocked; let userService: DeepMocked; + let rocketChatUserService: DeepMocked; + let rocketChatService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -81,6 +87,14 @@ describe(DeletionRequestUc.name, () => { provide: UserService, useValue: createMock(), }, + { + provide: RocketChatUserService, + useValue: createMock(), + }, + { + provide: RocketChatService, + useValue: createMock(), + }, ], }).compile(); @@ -96,6 +110,8 @@ describe(DeletionRequestUc.name, () => { pseudonymService = module.get(PseudonymService); teamService = module.get(TeamService); userService = module.get(UserService); + rocketChatUserService = module.get(RocketChatUserService); + rocketChatService = module.get(RocketChatService); await setupEntities(); }); @@ -153,6 +169,9 @@ describe(DeletionRequestUc.name, () => { const setup = () => { jest.clearAllMocks(); const deletionRequestToExecute = deletionRequestFactory.build({ deleteAfter: new Date('2023-01-01') }); + const rocketChatUser: RocketChatUser = rocketChatUserFactory.build({ + userId: deletionRequestToExecute.targetRefId, + }); classService.deleteUserDataFromClasses.mockResolvedValueOnce(1); courseGroupService.deleteUserDataFromCourseGroup.mockResolvedValueOnce(2); @@ -163,9 +182,11 @@ describe(DeletionRequestUc.name, () => { pseudonymService.deleteByUserId.mockResolvedValueOnce(2); teamService.deleteUserDataFromTeams.mockResolvedValueOnce(2); userService.deleteUser.mockResolvedValueOnce(1); + rocketChatUserService.deleteByUserId.mockResolvedValueOnce(1); return { deletionRequestToExecute, + rocketChatUser, }; }; @@ -287,6 +308,38 @@ describe(DeletionRequestUc.name, () => { expect(userService.deleteUser).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); }); + it('should call rocketChatUserService.findByUserId to find rocketChatUser in rocketChatUser module', async () => { + const { deletionRequestToExecute } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + + await uc.executeDeletionRequests(); + + expect(rocketChatUserService.findByUserId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + }); + + it('should call rocketChatUserService.deleteByUserId to delete rocketChatUser in rocketChatUser module', async () => { + const { deletionRequestToExecute, rocketChatUser } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + rocketChatUserService.findByUserId.mockResolvedValueOnce(rocketChatUser); + + await uc.executeDeletionRequests(); + + expect(rocketChatUserService.deleteByUserId).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId); + }); + + it('should call rocketChatService.deleteUser to delete rocketChatUser in rocketChat external module', async () => { + const { deletionRequestToExecute, rocketChatUser } = setup(); + + deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]); + rocketChatUserService.findByUserId.mockResolvedValueOnce(rocketChatUser); + + await uc.executeDeletionRequests(); + + expect(rocketChatService.deleteUser).toHaveBeenCalledWith(rocketChatUser.username); + }); + it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => { const { deletionRequestToExecute } = setup(); @@ -294,7 +347,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(10); + expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(11); }); }); 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 0b3ed0bd2e1..418d434d44a 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -9,6 +9,8 @@ import { CourseService } from '@src/modules/learnroom/service'; import { CourseGroupService } from '@src/modules/learnroom/service/coursegroup.service'; import { FilesService } from '@src/modules/files/service'; import { AccountService } from '@src/modules/account/services/account.service'; +import { RocketChatService } from '@src/modules/rocketchat'; +import { RocketChatUserService } from '@src/modules/rocketchat-user/service/rocket-chat-user.service'; import { DeletionRequestService } from '../services/deletion-request.service'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; @@ -47,7 +49,9 @@ export class DeletionRequestUc { private readonly lessonService: LessonService, private readonly pseudonymService: PseudonymService, private readonly teamService: TeamService, - private readonly userService: UserService + private readonly userService: UserService, + private readonly rocketChatUserService: RocketChatUserService, + private readonly rocketChatService: RocketChatService ) {} async createDeletionRequest( @@ -114,6 +118,7 @@ export class DeletionRequestUc { this.pseudonymService.deleteByUserId(deletionRequest.targetRefId), this.teamService.deleteUserDataFromTeams(deletionRequest.targetRefId), this.userService.deleteUser(deletionRequest.targetRefId), + this.removeUserFromRocketChatByUserId(deletionRequest.targetRefId), ]) .then( async ([ @@ -127,6 +132,7 @@ export class DeletionRequestUc { pseudonymDeleted, teamsUpdated, userDeleted, + rocketChatUserDeleted, ]) => { await this.deletionLogService.createDeletionLog( deletionRequest.id, @@ -226,6 +232,16 @@ export class DeletionRequestUc { ); } + if (rocketChatUserDeleted > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + DeletionDomainModel.ROCKETCHATUSER, + DeletionOperationModel.DELETE, + 0, + rocketChatUserDeleted + ); + } + await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); return true; @@ -235,4 +251,19 @@ export class DeletionRequestUc { await this.deletionRequestService.markDeletionRequestAsFailed(deletionRequest.id); }); } + + private async removeUserFromRocketChatByUserId(userId: EntityId): Promise { + const rocketChatUser = await this.rocketChatUserService.findByUserId(userId); + + if (!rocketChatUser) { + return 0; + } + + const [, rocketChatUserDeleted] = await Promise.all([ + this.rocketChatService.deleteUser(rocketChatUser.username), + this.rocketChatUserService.deleteByUserId(rocketChatUser.userId), + ]); + + return rocketChatUserDeleted; + } } From ef3941a0433115e55f4409d7cd1f1a5fc742d4d2 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 14:40:49 +0100 Subject: [PATCH 15/22] small fixes --- .../src/modules/deletion/uc/deletion-request.uc.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 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 f75c051dddb..f808c9f9caf 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 @@ -12,6 +12,8 @@ import { TeamService } from '@src/modules/teams'; import { UserService } from '@src/modules/user'; import { RocketChatUserService } from '@src/modules/rocketchat-user/service/rocket-chat-user.service'; import { RocketChatService } from '@src/modules/rocketchat'; +import { rocketChatUserFactory } from '@src/modules/rocketchat-user/domain/testing/rocket-chat-user.factory'; +import { RocketChatUser } from '@src/modules/rocketchat-user/domain'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; @@ -19,8 +21,6 @@ import { DeletionRequestLog, DeletionRequestProps, DeletionRequestUc } from './d import { deletionRequestFactory } from '../domain/testing/factory/deletion-request.factory'; import { DeletionStatusModel } from '../domain/types/deletion-status-model.enum'; import { deletionLogFactory } from '../domain/testing/factory/deletion-log.factory'; -import { rocketChatUserFactory } from '@src/modules/rocketchat-user/domain/testing/rocket-chat-user.factory'; -import { RocketChatUser } from '@src/modules/rocketchat-user/domain'; describe(DeletionRequestUc.name, () => { let module: TestingModule; From a1ca1556ba0bf9911d737a1342f57d3214a375e6 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Mon, 6 Nov 2023 16:32:53 +0100 Subject: [PATCH 16/22] changing limit parameter and changing in useCases --- .../repo/deletion-request.repo.spec.ts | 2 +- .../deletion/repo/deletion-request.repo.ts | 8 +- .../services/deletion-request.service.ts | 6 +- .../deletion/uc/deletion-request.uc.spec.ts | 2 +- .../deletion/uc/deletion-request.uc.ts | 172 ++++++++---------- 5 files changed, 83 insertions(+), 107 deletions(-) diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts index 0e296a3406d..1883a581b2e 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.spec.ts @@ -206,7 +206,7 @@ describe(DeletionRequestRepo.name, () => { it('should find deletionRequests to execute with limit = 2', async () => { const { expectedArray } = await setup(); - const results = await repo.findAllItemsToExecution({ pagination: { limit: 2 } }); + const results = await repo.findAllItemsToExecution(2); expect(results.length).toEqual(2); diff --git a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts index d762d19f67f..5964115c852 100644 --- a/apps/server/src/modules/deletion/repo/deletion-request.repo.ts +++ b/apps/server/src/modules/deletion/repo/deletion-request.repo.ts @@ -1,6 +1,6 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId, IFindOptions, SortOrder } from '@shared/domain'; +import { EntityId, SortOrder } from '@shared/domain'; import { DeletionRequest } from '../domain/deletion-request.do'; import { DeletionRequestEntity } from '../entity'; import { DeletionRequestMapper } from './mapper/deletion-request.mapper'; @@ -30,15 +30,13 @@ export class DeletionRequestRepo { await this.em.flush(); } - async findAllItemsToExecution(options?: IFindOptions): Promise { + async findAllItemsToExecution(limit?: number): Promise { const currentDate = new Date(); const scope = new DeletionRequestScope().byDeleteAfter(currentDate).byStatus(); - const { pagination } = options || { limit: 100 }; const order = { createdAt: SortOrder.desc }; const [deletionRequestEntities] = await this.em.findAndCount(DeletionRequestEntity, scope.query, { - offset: pagination?.skip, - limit: pagination?.limit, + limit, orderBy: order, }); 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 b04a29f68e4..d3f8da4c708 100644 --- a/apps/server/src/modules/deletion/services/deletion-request.service.ts +++ b/apps/server/src/modules/deletion/services/deletion-request.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { EntityId, IFindOptions } from '@shared/domain'; +import { EntityId } from '@shared/domain'; import { ObjectId } from '@mikro-orm/mongodb'; import { DeletionRequestRepo } from '../repo/deletion-request.repo'; import { DeletionRequest } from '../domain/deletion-request.do'; @@ -39,8 +39,8 @@ export class DeletionRequestService { return deletionRequest; } - async findAllItemsToExecute(options?: IFindOptions): Promise { - const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsToExecution(options); + async findAllItemsToExecute(limit?: number): Promise { + const itemsToDelete: DeletionRequest[] = await this.deletionRequestRepo.findAllItemsToExecution(limit); return itemsToDelete; } 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 340df09200f..e328c831ac1 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 @@ -294,7 +294,7 @@ describe(DeletionRequestUc.name, () => { await uc.executeDeletionRequests(); - expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(10); + expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(9); }); }); 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 0b3ed0bd2e1..3a1b5d4b07b 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { EntityId, IFindOptions } from '@shared/domain'; +import { EntityId } from '@shared/domain'; import { PseudonymService } from '@src/modules/pseudonym'; import { UserService } from '@src/modules/user'; import { TeamService } from '@src/modules/teams'; @@ -62,9 +62,9 @@ export class DeletionRequestUc { return result; } - async executeDeletionRequests(options?: IFindOptions): Promise { + async executeDeletionRequests(limit?: number): Promise { const deletionRequestToExecution: DeletionRequest[] = await this.deletionRequestService.findAllItemsToExecute( - options + limit ); for (const req of deletionRequestToExecution) { @@ -128,103 +128,63 @@ export class DeletionRequestUc { teamsUpdated, userDeleted, ]) => { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.ACCOUNT, + await this.logDeletion(deletionRequest, DeletionDomainModel.ACCOUNT, DeletionOperationModel.DELETE, 0, 1); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.CLASS, + DeletionOperationModel.UPDATE, + classesUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.COURSEGROUP, + DeletionOperationModel.UPDATE, + courseGroupUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.COURSE, + DeletionOperationModel.UPDATE, + courseUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.FILE, + DeletionOperationModel.UPDATE, + fileDeleted + filesPermisionUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.LESSONS, + DeletionOperationModel.UPDATE, + lessonUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.PSEUDONYMS, DeletionOperationModel.DELETE, 0, - 1 + pseudonymDeleted + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.TEAMS, + DeletionOperationModel.UPDATE, + teamsUpdated, + 0 + ); + await this.logDeletion( + deletionRequest, + DeletionDomainModel.USER, + DeletionOperationModel.DELETE, + 0, + userDeleted ); - - if (classesUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.CLASS, - DeletionOperationModel.UPDATE, - classesUpdated, - 0 - ); - } - - if (classesUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.CLASS, - DeletionOperationModel.UPDATE, - classesUpdated, - 0 - ); - } - - if (courseGroupUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.COURSEGROUP, - DeletionOperationModel.UPDATE, - courseGroupUpdated, - 0 - ); - } - - if (courseUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.COURSE, - DeletionOperationModel.UPDATE, - courseUpdated, - 0 - ); - } - - if (fileDeleted > 0 || filesPermisionUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.FILE, - DeletionOperationModel.UPDATE, - fileDeleted + filesPermisionUpdated, - 0 - ); - } - - if (lessonUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.LESSONS, - DeletionOperationModel.UPDATE, - lessonUpdated, - 0 - ); - } - - if (pseudonymDeleted > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.PSEUDONYMS, - DeletionOperationModel.DELETE, - 0, - pseudonymDeleted - ); - } - - if (teamsUpdated > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.TEAMS, - DeletionOperationModel.UPDATE, - teamsUpdated, - 0 - ); - } - - if (userDeleted > 0) { - await this.deletionLogService.createDeletionLog( - deletionRequest.id, - DeletionDomainModel.USER, - DeletionOperationModel.DELETE, - 0, - userDeleted - ); - } await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id); @@ -235,4 +195,22 @@ export class DeletionRequestUc { await this.deletionRequestService.markDeletionRequestAsFailed(deletionRequest.id); }); } + + private async logDeletion( + deletionRequest: DeletionRequest, + domainModel: DeletionDomainModel, + operationModel: DeletionOperationModel, + updatedCount: number, + deletedCount: number + ): Promise { + if (updatedCount > 0 || deletedCount > 0) { + await this.deletionLogService.createDeletionLog( + deletionRequest.id, + domainModel, + operationModel, + updatedCount, + deletedCount + ); + } + } } From 051162af36552636984d715b8799f0021a3d31d7 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 8 Nov 2023 10:45:45 +0100 Subject: [PATCH 17/22] delete 3 lines of code --- apps/server/src/modules/deletion/uc/deletion-request.uc.ts | 4 ---- 1 file changed, 4 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 f723ea3fc00..b6a0bdd130c 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -215,10 +215,6 @@ export class DeletionRequestUc { private async removeUserFromRocketChat(deletionRequest: DeletionRequest): Promise { const rocketChatUser = await this.rocketChatUserService.findByUserId(deletionRequest.targetRefId); - if (!rocketChatUser) { - return 0; - } - const [, rocketChatUserDeleted] = await Promise.all([ this.rocketChatService.deleteUser(rocketChatUser.username), this.rocketChatUserService.deleteByUserId(rocketChatUser.userId), From 7842199dc764874aa72c7549c5c8927a7b3f3777 Mon Sep 17 00:00:00 2001 From: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:56:05 +0100 Subject: [PATCH 18/22] Update apps/server/src/modules/deletion/uc/deletion-request.uc.ts Co-authored-by: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> --- apps/server/src/modules/deletion/uc/deletion-request.uc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b6a0bdd130c..abea56fda96 100644 --- a/apps/server/src/modules/deletion/uc/deletion-request.uc.ts +++ b/apps/server/src/modules/deletion/uc/deletion-request.uc.ts @@ -8,7 +8,7 @@ import { LessonService } from '@modules/lesson/service'; import { CourseGroupService, CourseService } from '@modules/learnroom/service'; import { FilesService } from '@modules/files/service'; import { AccountService } from '@modules/account/services'; -import { RocketChatUserService } from '@modules/rocketchat-user/service/rocket-chat-user.service'; +import { RocketChatUserService } from '@modules/rocketchat-user'; import { RocketChatService } from '@modules/rocketchat'; import { DeletionRequestService } from '../services/deletion-request.service'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; From 4de1e5ec047db68612e9eb8903fd289a706eecbc Mon Sep 17 00:00:00 2001 From: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:56:25 +0100 Subject: [PATCH 19/22] Update apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts Co-authored-by: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> --- apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a47ab100ce2..ef2185bbd0d 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 @@ -12,7 +12,7 @@ import { UserService } from '@modules/user'; import { RocketChatUserService } from '@modules/rocketchat-user/service'; import { RocketChatService } from '@modules/rocketchat'; import { rocketChatUserFactory } from '@modules/rocketchat-user/domain/testing'; -import { RocketChatUser } from '@modules/rocketchat-user/domain'; +import { RocketChatUser } from '@modules/rocketchat-user'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; From 5d91b72cd3f93a2f1ce8efe1af4368bb645549e9 Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 8 Nov 2023 11:05:03 +0100 Subject: [PATCH 20/22] small fixes --- apps/server/src/modules/rocketchat-user/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/server/src/modules/rocketchat-user/index.ts b/apps/server/src/modules/rocketchat-user/index.ts index dc61a422c20..34ae0f25f87 100644 --- a/apps/server/src/modules/rocketchat-user/index.ts +++ b/apps/server/src/modules/rocketchat-user/index.ts @@ -1 +1,3 @@ export * from './rocketchat-user.module'; +export * from './service'; +export * from './domain'; From 29a5d4ebf1c5802dad93e9d6218d7ea80610fb41 Mon Sep 17 00:00:00 2001 From: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:18:53 +0100 Subject: [PATCH 21/22] Update apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts Co-authored-by: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> --- .../server/src/modules/deletion/uc/deletion-request.uc.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 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 ef2185bbd0d..34c34e302f5 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 @@ -9,10 +9,9 @@ import { LessonService } from '@modules/lesson/service'; import { PseudonymService } from '@modules/pseudonym'; import { TeamService } from '@modules/teams'; import { UserService } from '@modules/user'; -import { RocketChatUserService } from '@modules/rocketchat-user/service'; import { RocketChatService } from '@modules/rocketchat'; import { rocketChatUserFactory } from '@modules/rocketchat-user/domain/testing'; -import { RocketChatUser } from '@modules/rocketchat-user'; +import { RocketChatUser, RocketChatUserService } from '@modules/rocketchat-user'; import { DeletionDomainModel } from '../domain/types/deletion-domain-model.enum'; import { DeletionLogService } from '../services/deletion-log.service'; import { DeletionRequestService } from '../services'; From decb0fa83db084124034a6adee26d74ddbe6729f Mon Sep 17 00:00:00 2001 From: WojciechGrancow Date: Wed, 8 Nov 2023 11:55:34 +0100 Subject: [PATCH 22/22] import fix --- .../modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts index 75e7d0f64e4..d58e5fc42d1 100644 --- a/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts +++ b/apps/server/src/modules/rocketchat-user/repo/rocket-chat-user.repo.spec.ts @@ -1,6 +1,6 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { MongoMemoryDatabaseModule } from '@shared/infra/database'; +import { MongoMemoryDatabaseModule } from '@infra/database'; import { cleanupCollections } from '@shared/testing'; import { RocketChatUserMapper } from './mapper'; import { RocketChatUserEntity } from '../entity';