Skip to content

Commit

Permalink
Merge branch 'main' into BC-5424-tldraw-persistent-storage
Browse files Browse the repository at this point in the history
  • Loading branch information
davwas authored Jan 30, 2024
2 parents 31fd8cc + 01f4185 commit 0d24bb5
Show file tree
Hide file tree
Showing 80 changed files with 1,318 additions and 611 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,46 @@ describe('account repo', () => {
});

describe('deleteByUserId', () => {
it('should delete an account by user id', async () => {
const setup = async () => {
const user = userFactory.buildWithId();
const userWithoutAccount = userFactory.buildWithId();
const account = accountFactory.build({ userId: user.id });

await em.persistAndFlush([user, account]);

await expect(repo.deleteByUserId(user.id)).resolves.not.toThrow();
return {
account,
user,
userWithoutAccount,
};
};

await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError);
describe('when user has account', () => {
it('should delete an account by user id', async () => {
const { account, user } = await setup();

await expect(repo.deleteByUserId(user.id)).resolves.not.toThrow();

await expect(repo.findById(account.id)).rejects.toThrow(NotFoundError);
});

it('should return accoountId', async () => {
const { account, user } = await setup();

const result = await repo.deleteByUserId(user.id);

expect(result).toEqual([account.id]);
});
});

describe('when user has no account', () => {
it('should return null', async () => {
const { userWithoutAccount } = await setup();

const result = await repo.deleteByUserId(userWithoutAccount.id);

expect(result).toEqual([]);
});
});
});

Expand Down
9 changes: 6 additions & 3 deletions apps/server/src/modules/account/repo/account.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@ export class AccountRepo extends BaseRepo<Account> {
return this.delete(account);
}

async deleteByUserId(userId: EntityId): Promise<void> {
async deleteByUserId(userId: EntityId): Promise<EntityId[]> {
const account = await this.findByUserId(userId);
if (account) {
await this._em.removeAndFlush(account);
if (account === null) {
return [];
}
await this._em.removeAndFlush(account);

return [account.id];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class AccountServiceDb extends AbstractAccountService {
return this.accountRepo.deleteById(internalId);
}

async deleteByUserId(userId: EntityId): Promise<void> {
async deleteByUserId(userId: EntityId): Promise<EntityId[]> {
return this.accountRepo.deleteByUserId(userId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,11 @@ export class AccountServiceIdm extends AbstractAccountService {
await this.identityManager.deleteAccountById(id);
}

async deleteByUserId(userId: EntityId): Promise<void> {
async deleteByUserId(userId: EntityId): Promise<EntityId[]> {
const idmAccount = await this.identityManager.findAccountByDbcUserId(userId);
await this.identityManager.deleteAccountById(idmAccount.id);
const deletedAccountId = await this.identityManager.deleteAccountById(idmAccount.id);

return [deletedAccountId];
}

// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export abstract class AbstractAccountService {

abstract delete(id: EntityId): Promise<void>;

abstract deleteByUserId(userId: EntityId): Promise<void>;
abstract deleteByUserId(userId: EntityId): Promise<EntityId[]>;

abstract searchByUsernamePartialMatch(userName: string, skip: number, limit: number): Promise<Counted<AccountDto[]>>;

Expand Down
34 changes: 34 additions & 0 deletions apps/server/src/modules/account/services/account.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ServerConfig } from '@modules/server';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { ObjectId } from 'bson';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { DomainName, OperationType } from '@shared/domain/types';
import { LegacyLogger } from '../../../core/logger';
import { AccountServiceDb } from './account-db.service';
import { AccountServiceIdm } from './account-idm.service';
Expand Down Expand Up @@ -289,6 +292,37 @@ describe('AccountService', () => {
});
});

describe('deleteAccountByUserId', () => {
const setup = () => {
const userId = new ObjectId().toHexString();
const accountId = new ObjectId().toHexString();
const spy = jest.spyOn(accountService, 'deleteByUserId');

const expectedResult = DomainOperationBuilder.build(DomainName.ACCOUNT, OperationType.DELETE, 1, [accountId]);

return { accountId, expectedResult, spy, userId };
};

it('should call deleteByUserId in accountService', async () => {
const { spy, userId } = setup();

await accountService.deleteAccountByUserId(userId);
expect(spy).toHaveBeenCalledWith(userId);
spy.mockRestore();
});

it('should call deleteByUserId in accountService', async () => {
const { accountId, expectedResult, spy, userId } = setup();

spy.mockResolvedValueOnce([accountId]);

const result = await accountService.deleteAccountByUserId(userId);
expect(spy).toHaveBeenCalledWith(userId);
expect(result).toEqual(expectedResult);
spy.mockRestore();
});
});

describe('findMany', () => {
it('should call findMany in accountServiceDb', async () => {
await expect(accountService.findMany()).resolves.not.toThrow();
Expand Down
26 changes: 22 additions & 4 deletions apps/server/src/modules/account/services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { ObjectId } from '@mikro-orm/mongodb';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ValidationError } from '@shared/common';
import { Counted } from '@shared/domain/types';
import { Counted, DomainName, EntityId, OperationType } from '@shared/domain/types';
import { isEmail, validateOrReject } from 'class-validator';
import { DomainOperation } from '@shared/domain/interface';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { LegacyLogger } from '../../../core/logger';
import { ServerConfig } from '../../server/server.config';
import { AccountServiceDb } from './account-db.service';
Expand Down Expand Up @@ -157,13 +159,29 @@ export class AccountService extends AbstractAccountService {
});
}

async deleteByUserId(userId: string): Promise<void> {
await this.accountDb.deleteByUserId(userId);
async deleteByUserId(userId: string): Promise<EntityId[]> {
const deletedAccounts = await this.accountDb.deleteByUserId(userId);
await this.executeIdmMethod(async () => {
this.logger.debug(`Deleting account with userId ${userId} ...`);
await this.accountIdm.deleteByUserId(userId);
const deletedAccountIdm = await this.accountIdm.deleteByUserId(userId);
deletedAccounts.push(...deletedAccountIdm);
this.logger.debug(`Deleted account with userId ${userId}`);
});

return deletedAccounts;
}

async deleteAccountByUserId(userId: string): Promise<DomainOperation> {
const deletedAccounts = await this.deleteByUserId(userId);

const result = DomainOperationBuilder.build(
DomainName.ACCOUNT,
OperationType.DELETE,
deletedAccounts.length,
deletedAccounts
);

return result;
}

/**
Expand Down
13 changes: 10 additions & 3 deletions apps/server/src/modules/class/service/class.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { InternalServerErrorException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityId } from '@shared/domain/types';
import { DomainName, EntityId, OperationType } from '@shared/domain/types';
import { setupEntities } from '@shared/testing';
import { Logger } from '@src/core/logger';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { Class } from '../domain';
import { classFactory } from '../domain/testing';
import { classEntityFactory } from '../entity/testing';
Expand Down Expand Up @@ -134,7 +135,13 @@ describe(ClassService.name, () => {

classesRepo.findAllByUserId.mockResolvedValue(mappedClasses);

const expectedResult = DomainOperationBuilder.build(DomainName.CLASS, OperationType.UPDATE, 2, [
class1.id,
class2.id,
]);

return {
expectedResult,
userId1,
};
};
Expand All @@ -147,11 +154,11 @@ describe(ClassService.name, () => {
});

it('should update classes without updated user', async () => {
const { userId1 } = setup();
const { expectedResult, userId1 } = setup();

const result = await service.deleteUserDataFromClasses(userId1.toHexString());

expect(result).toEqual(2);
expect(result).toEqual(expectedResult);
});
});
});
Expand Down
24 changes: 19 additions & 5 deletions apps/server/src/modules/class/service/class.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { DomainModel, EntityId, StatusModel } from '@shared/domain/types';
import { DomainName, EntityId, OperationType, StatusModel } from '@shared/domain/types';
import { Logger } from '@src/core/logger';
import { DataDeletionDomainOperationLoggable } from '@shared/common/loggable';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { DomainOperation } from '@shared/domain/interface';
import { Class } from '../domain';
import { ClassesRepo } from '../repo';

Expand All @@ -23,11 +25,11 @@ export class ClassService {
return classes;
}

public async deleteUserDataFromClasses(userId: EntityId): Promise<number> {
public async deleteUserDataFromClasses(userId: EntityId): Promise<DomainOperation> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting data from Classes',
DomainModel.CLASS,
DomainName.CLASS,
userId,
StatusModel.PENDING
)
Expand All @@ -49,17 +51,29 @@ export class ClassService {
const numberOfUpdatedClasses = updatedClasses.length;

await this.classesRepo.updateMany(updatedClasses);

const result = DomainOperationBuilder.build(
DomainName.CLASS,
OperationType.UPDATE,
numberOfUpdatedClasses,
this.getClassesId(updatedClasses)
);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully removed user data from Classes',
DomainModel.CLASS,
DomainName.CLASS,
userId,
StatusModel.FINISHED,
numberOfUpdatedClasses,
0
)
);

return numberOfUpdatedClasses;
return result;
}

private getClassesId(classes: Class[]): EntityId[] {
return classes.map((item) => item.id);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { DomainModel } from '@shared/domain/types';
import { DomainName, OperationType } from '@shared/domain/types';
import { ObjectId } from 'bson';
import { DeletionLogStatisticBuilder } from '.';

describe(DeletionLogStatisticBuilder.name, () => {
afterAll(() => {
jest.clearAllMocks();
});
const setup = () => {
const domain = DomainName.PSEUDONYMS;
const operation = OperationType.DELETE;
const count = 2;
const refs = [new ObjectId().toHexString(), new ObjectId().toHexString()];

return { domain, operation, count, refs };
};

it('should build generic deletionLogStatistic with all attributes', () => {
// Arrange
const domain = DomainModel.PSEUDONYMS;
const modifiedCount = 0;
const deletedCount = 2;
const { domain, operation, count, refs } = setup();

const result = DeletionLogStatisticBuilder.build(domain, modifiedCount, deletedCount);
const result = DeletionLogStatisticBuilder.build(domain, operation, count, refs);

// Assert
expect(result.domain).toEqual(domain);
expect(result.modifiedCount).toEqual(modifiedCount);
expect(result.deletedCount).toEqual(deletedCount);
expect(result.operation).toEqual(operation);
expect(result.count).toEqual(count);
expect(result.refs).toEqual(refs);
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { DomainOperation } from '@shared/domain/interface';
import { DomainModel } from '@shared/domain/types';
import { DomainName, EntityId, OperationType } from '@shared/domain/types';

export class DeletionLogStatisticBuilder {
static build(
domain: DomainModel,
modifiedCount: number,
deletedCount: number,
modifiedRef?: string[],
deletedRef?: string[]
): DomainOperation {
const deletionLogStatistic = { domain, modifiedCount, deletedCount, modifiedRef, deletedRef };
static build(domain: DomainName, operation: OperationType, count: number, refs: EntityId[]): DomainOperation {
const deletionLogStatistic = { domain, operation, count, refs };

return deletionLogStatistic;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ObjectId } from 'bson';
import { DomainModel } from '@shared/domain/types';
import { DomainName } from '@shared/domain/types';
import { DeletionRequestBodyPropsBuilder } from './deletion-request-body-props.builder';

describe(DeletionRequestBodyPropsBuilder.name, () => {
Expand All @@ -8,7 +8,7 @@ describe(DeletionRequestBodyPropsBuilder.name, () => {
});
describe('when create deletionRequestBodyParams', () => {
const setup = () => {
const domain = DomainModel.PSEUDONYMS;
const domain = DomainName.PSEUDONYMS;
const refId = new ObjectId().toHexString();
const deleteInMinutes = 1000;
return { domain, refId, deleteInMinutes };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DomainModel, EntityId } from '@shared/domain/types';
import { DomainName, EntityId } from '@shared/domain/types';
import { DeletionRequestBodyProps } from '../controller/dto';

export class DeletionRequestBodyPropsBuilder {
static build(domain: DomainModel, id: EntityId, deleteInMinutes?: number): DeletionRequestBodyProps {
static build(domain: DomainName, id: EntityId, deleteInMinutes?: number): DeletionRequestBodyProps {
const deletionRequestItem = {
targetRef: { domain, id },
deleteInMinutes,
Expand Down
Loading

0 comments on commit 0d24bb5

Please sign in to comment.