Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BC-5833-Add Submissions Entities Deletion to the Main User Deletion Use Case #4719

Merged
merged 27 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2c7164c
create PR
sszafGCA Jan 26, 2024
a3c6bf6
changes in submission entity
WojciechGrancow Jan 28, 2024
dec36d2
fix test cases
WojciechGrancow Jan 28, 2024
910ad0a
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Jan 29, 2024
0e81906
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 5, 2024
2469a6c
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 5, 2024
b76ac6a
implementation of main logic
sszafGCA Feb 6, 2024
9898104
Merge remote-tracking branch 'origin' into BC-5833-add-submissions-en…
sszafGCA Feb 6, 2024
e904ed0
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 8, 2024
3e53337
fix tests no.1
sszafGCA Feb 8, 2024
5ace74f
fix tests no.2
sszafGCA Feb 8, 2024
6d18a55
fix code coverage no.1
sszafGCA Feb 8, 2024
e463ee2
fix code coverage no.2
sszafGCA Feb 8, 2024
2b18f9d
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 9, 2024
738bb5e
repair logic
sszafGCA Feb 9, 2024
8b51a56
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 12, 2024
6e0abf0
PR fixes
sszafGCA Feb 12, 2024
9ce5734
fix tests
sszafGCA Feb 12, 2024
85a000a
fix lint
sszafGCA Feb 12, 2024
88716cd
fix lint
sszafGCA Feb 12, 2024
d5634ac
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 12, 2024
7260f31
change test names and move if to one method
sszafGCA Feb 13, 2024
e1e43b8
PR fixes
sszafGCA Feb 14, 2024
6c8dfb8
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 14, 2024
ddd67e6
remove not needed double questionMark
sszafGCA Feb 14, 2024
43668be
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 14, 2024
72b00e2
Merge branch 'main' into BC-5833-add-submissions-entities-deletion
sszafGCA Feb 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ObjectId } from 'bson';
import { RegistrationPinService } from '@modules/registration-pin';
import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client';
import { DomainName, OperationType } from '@shared/domain/types';
import { TaskService } from '@modules/task';
import { SubmissionService, TaskService } from '@modules/task';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { NewsService } from '@src/modules/news/service/news.service';
import { DeletionStatusModel } from '../domain/types';
Expand Down Expand Up @@ -49,6 +49,7 @@ describe(DeletionRequestUc.name, () => {
let dashboardService: DeepMocked<DashboardService>;
let taskService: DeepMocked<TaskService>;
let newsService: DeepMocked<NewsService>;
let submissionService: DeepMocked<SubmissionService>;

beforeAll(async () => {
module = await Test.createTestingModule({
Expand Down Expand Up @@ -127,6 +128,7 @@ describe(DeletionRequestUc.name, () => {
useValue: createMock<TaskService>(),
},
{ provide: NewsService, useValue: createMock<NewsService>() },
{ provide: SubmissionService, useValue: createMock<SubmissionService>() },
],
}).compile();

Expand All @@ -149,6 +151,7 @@ describe(DeletionRequestUc.name, () => {
dashboardService = module.get(DashboardService);
taskService = module.get(TaskService);
newsService = module.get(NewsService);
submissionService = module.get(SubmissionService);
await setupEntities();
});

Expand Down Expand Up @@ -295,6 +298,13 @@ describe(DeletionRequestUc.name, () => {
new ObjectId().toHexString(),
]);

const submissionsDeleted = DomainOperationBuilder.build(DomainName.SUBMISSIONS, OperationType.DELETE, 1, [
new ObjectId().toHexString(),
]);
const submissionsUpdated = DomainOperationBuilder.build(DomainName.SUBMISSIONS, OperationType.UPDATE, 1, [
new ObjectId().toHexString(),
]);

const user = userDoFactory.buildWithId();

accountService.deleteAccountByUserId.mockResolvedValueOnce(accountDeleted);
Expand All @@ -316,6 +326,8 @@ describe(DeletionRequestUc.name, () => {
taskService.removeUserFromFinished.mockResolvedValueOnce(tasksModifiedByRemoveUserFromFinished);
taskService.deleteTasksByOnlyCreator.mockResolvedValueOnce(tasksDeleted);
newsService.deleteCreatorOrUpdaterReference.mockResolvedValueOnce(newsUpdated);
submissionService.deleteSingleSubmissionsOwnedByUser.mockResolvedValueOnce(submissionsDeleted);
submissionService.removeUserReferencesFromSubmissions.mockResolvedValueOnce(submissionsUpdated);

return {
deletionRequestToExecute,
Expand Down Expand Up @@ -553,14 +565,38 @@ describe(DeletionRequestUc.name, () => {
expect(newsService.deleteCreatorOrUpdaterReference).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId);
});

it('should call submissionService.deleteSubmissionsByUserId to delete submissions', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(submissionService.deleteSingleSubmissionsOwnedByUser).toHaveBeenCalledWith(
deletionRequestToExecute.targetRefId
);
});

it('should call submissionService.updateSubmissionByUserId to update submissions', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(submissionService.removeUserReferencesFromSubmissions).toHaveBeenCalledWith(
deletionRequestToExecute.targetRefId
);
});

it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(13);
expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(15);
});
});

Expand Down
29 changes: 28 additions & 1 deletion apps/server/src/modules/deletion/uc/deletion-request.uc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Injectable } from '@nestjs/common';
import { DomainName, EntityId, OperationType } from '@shared/domain/types';
import { LegacyLogger } from '@src/core/logger';
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { TaskService } from '@modules/task';
import { SubmissionService, TaskService } from '@modules/task';
import { DomainOperation } from '@shared/domain/interface';
import { DomainOperationBuilder } from '@shared/domain/builder/domain-operation.builder';
import { NewsService } from '@src/modules/news/service/news.service';
Expand Down Expand Up @@ -43,6 +43,7 @@ export class DeletionRequestUc {
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService,
private readonly dashboardService: DashboardService,
private readonly taskService: TaskService,
private readonly submissionService: SubmissionService,
private readonly newsService: NewsService
) {
this.logger.setContext(DeletionRequestUc.name);
Expand Down Expand Up @@ -113,6 +114,7 @@ export class DeletionRequestUc {
this.removeUserFromRocketChat(deletionRequest),
this.removeUsersDashboard(deletionRequest),
this.removeUserFromTasks(deletionRequest),
this.removeUserFromSubmissions(deletionRequest),
this.removeUsersDataFromNews(deletionRequest),
]);
await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id);
Expand Down Expand Up @@ -374,6 +376,31 @@ export class DeletionRequestUc {
);
}

private async removeUserFromSubmissions(deletionRequest: DeletionRequest) {
this.logger.debug({ action: 'removeUserFromSubmissions', deletionRequest });

const [submissionsDeleted, submissionsModified] = await Promise.all([
this.submissionService.deleteSingleSubmissionsOwnedByUser(deletionRequest.targetRefId),
this.submissionService.removeUserReferencesFromSubmissions(deletionRequest.targetRefId),
]);

await this.logDeletion(
deletionRequest,
submissionsDeleted.domain,
submissionsDeleted.operation,
submissionsDeleted.count,
submissionsDeleted.refs
);

await this.logDeletion(
deletionRequest,
submissionsModified.domain,
submissionsModified.operation,
submissionsModified.count,
submissionsModified.refs
);
}

private async removeUsersDataFromNews(deletionRequest: DeletionRequest) {
this.logger.debug({ action: 'removeUsersDataFromNews', deletionRequest });
const newsModified = await this.newsService.deleteCreatorOrUpdaterReference(deletionRequest.targetRefId);
Expand Down
108 changes: 107 additions & 1 deletion apps/server/src/modules/task/service/submission.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Test, TestingModule } from '@nestjs/testing';
import { Submission } from '@shared/domain/entity';
import { Counted } from '@shared/domain/types';
import { SubmissionRepo } from '@shared/repo';
import { setupEntities, submissionFactory, taskFactory } from '@shared/testing';
import { setupEntities, submissionFactory, taskFactory, userFactory } from '@shared/testing';
import { Logger } from '@src/core/logger';
import { ObjectId } from 'bson';
import { SubmissionService } from './submission.service';

describe('Submission Service', () => {
Expand All @@ -29,6 +31,10 @@ describe('Submission Service', () => {
provide: FilesStorageClientAdapterService,
useValue: createMock<FilesStorageClientAdapterService>(),
},
{
provide: Logger,
useValue: createMock<Logger>(),
},
],
}).compile();

Expand Down Expand Up @@ -211,4 +217,104 @@ describe('Submission Service', () => {
});
});
});

describe('deleteSingleSubmissionsOwnedByUser', () => {
describe('when submission with specified userId was not found ', () => {
const setup = () => {
const submission = submissionFactory.buildWithId();

submissionRepo.findAllByUserId.mockResolvedValueOnce([[], 0]);

return { submission };
};

it('should return deletedSubmissions number of 0', async () => {
const { submission } = setup();

const result = await service.deleteSingleSubmissionsOwnedByUser(new ObjectId().toString());

expect(result.count).toEqual(0);
expect(result.refs.length).toEqual(0);
expect(submission).toBeDefined();
});
});

describe('when submission with specified userId was found ', () => {
const setup = () => {
const user = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({ student: user, teamMembers: [user] });

submissionRepo.findAllByUserId.mockResolvedValueOnce([[submission], 1]);
submissionRepo.delete.mockResolvedValueOnce();

return { submission, user };
};

it('should return deletedSubmissions number of 1', async () => {
const { submission, user } = setup();

const result = await service.deleteSingleSubmissionsOwnedByUser(user.id);

expect(result.count).toEqual(1);
expect(result.refs.length).toEqual(1);
expect(submissionRepo.delete).toBeCalledTimes(1);
expect(submissionRepo.delete).toHaveBeenCalledWith([submission]);
});
});
});

describe('removeUserReferencesFromSubmissions', () => {
describe('when submission with specified userId was not found ', () => {
const setup = () => {
const user1 = userFactory.buildWithId();
const user2 = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({ student: user1, teamMembers: [user1, user2] });

submissionRepo.findAllByUserId.mockResolvedValueOnce([[], 0]);

return { submission, user1, user2 };
};

it('should return updated submission number of 0', async () => {
const { submission, user1 } = setup();

const result = await service.removeUserReferencesFromSubmissions(new ObjectId().toString());

expect(result.count).toEqual(0);
expect(result.refs.length).toEqual(0);
expect(submission.student).toEqual(user1);
expect(submission.teamMembers.length).toEqual(2);
});
});

describe('when submission with specified userId was found ', () => {
const setup = () => {
const user1 = userFactory.buildWithId();
const user2 = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({
student: user1,
teamMembers: [user1, user2],
});

submissionRepo.findAllByUserId.mockResolvedValueOnce([[submission], 1]);
submissionRepo.delete.mockResolvedValueOnce();

return { submission, user1, user2 };
};

it('should return updated submission number of 1', async () => {
const { submission, user1, user2 } = setup();

const result = await service.removeUserReferencesFromSubmissions(user1.id);

expect(result.count).toEqual(1);
expect(result.refs.length).toEqual(1);
expect(submission.student).toBeUndefined();
expect(submission.teamMembers.length).toEqual(1);
expect(submission.teamMembers[0]).toEqual(user2);
expect(submissionRepo.save).toBeCalledTimes(1);
expect(submissionRepo.save).toHaveBeenCalledWith([submission]);
});
});
});
});
99 changes: 97 additions & 2 deletions apps/server/src/modules/task/service/submission.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { Injectable } from '@nestjs/common';
import { DataDeletionDomainOperationLoggable } from '@shared/common/loggable';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { Submission } from '@shared/domain/entity';
import { Counted, EntityId } from '@shared/domain/types';
import { DomainOperation } from '@shared/domain/interface';
import { Counted, DomainName, EntityId, OperationType, StatusModel } from '@shared/domain/types';
import { SubmissionRepo } from '@shared/repo';
import { Logger } from '@src/core/logger';

@Injectable()
export class SubmissionService {
constructor(
private readonly submissionRepo: SubmissionRepo,
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService,
private readonly logger: Logger
) {}

async findById(submissionId: EntityId): Promise<Submission> {
Expand All @@ -26,4 +31,94 @@ export class SubmissionService {

await this.submissionRepo.delete(submission);
}

async deleteSingleSubmissionsOwnedByUser(userId: EntityId): Promise<DomainOperation> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting single Submissions owned by user',
DomainName.SUBMISSIONS,
userId,
StatusModel.PENDING
)
);
let [submissionsEntities, submissionsCount] = await this.submissionRepo.findAllByUserId(userId);

if (submissionsCount > 0) {
submissionsEntities = submissionsEntities.filter((submission) => submission.isSingleSubmissionOwnedByUser());
submissionsCount = submissionsEntities.length;
}

if (submissionsCount > 0) {
await this.submissionRepo.delete(submissionsEntities);
}

const result = DomainOperationBuilder.build(
DomainName.SUBMISSIONS,
OperationType.DELETE,
submissionsCount,
this.getSubmissionsId(submissionsEntities)
);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully deleted single Submissions owned by user',
DomainName.SUBMISSIONS,
userId,
StatusModel.FINISHED,
submissionsCount,
0
)
);

return result;
}

async removeUserReferencesFromSubmissions(userId: EntityId): Promise<DomainOperation> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting user references from Submissions',
DomainName.SUBMISSIONS,
userId,
StatusModel.PENDING
)
);

let [submissionsEntities, submissionsCount] = await this.submissionRepo.findAllByUserId(userId);

if (submissionsCount > 0) {
submissionsEntities = submissionsEntities.filter((submission) => submission.isGroupSubmission());
submissionsCount = submissionsEntities.length;
}

if (submissionsCount > 0) {
submissionsEntities.forEach((submission) => {
submission.removeStudentById(userId);
submission.removeUserFromTeamMembers(userId);
});

await this.submissionRepo.save(submissionsEntities);
}
const result = DomainOperationBuilder.build(
DomainName.SUBMISSIONS,
OperationType.UPDATE,
submissionsCount,
this.getSubmissionsId(submissionsEntities)
);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully deleted references from Submissions collection',
DomainName.SUBMISSIONS,
userId,
StatusModel.FINISHED,
submissionsCount,
0
)
);
return result;
}

private getSubmissionsId(submissions: Submission[]): EntityId[] {
return submissions.map((submission) => submission.id);
}
}
Loading
Loading