Skip to content
This repository was archived by the owner on Mar 20, 2023. It is now read-only.

Commit 9e1015a

Browse files
feat(api): create slice UncompleteTask -> TaskWasUncompleted (#230) (#395)
1 parent cad51b7 commit 9e1015a

13 files changed

+263
-13
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ packages/ui/src/icons
5959

6060
# Database
6161
data/
62+
63+
#IDE
64+
.idea
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { UncompleteTask } from '@/commands/uncomplete-task.domain-command';
2+
import { AbstractApplicationCommand } from '@/module/application-command-events';
3+
4+
export class UncompleteTaskApplicationCommand extends AbstractApplicationCommand<UncompleteTask> {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type UncompleteTask = {
2+
type: 'UncompleteTask';
3+
data: { learningMaterialsId: string; taskId: string };
4+
};

packages/api/src/module/write/learning-materials-tasks/application/complete-task.command-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Inject } from '@nestjs/common';
22
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
33

44
import { CompleteTaskApplicationCommand } from '@/commands/complete-task.application-command';
5-
import { TaskWasCompleted } from '@/module/events/task-was-completed.domain-event';
5+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
66
import { APPLICATION_SERVICE, ApplicationService } from '@/write/shared/application/application-service';
77
import { EventStreamName } from '@/write/shared/application/event-stream-name.value-object';
88

@@ -18,7 +18,7 @@ export class CompleteTaskCommandHandler implements ICommandHandler<CompleteTaskA
1818
async execute(command: CompleteTaskApplicationCommand): Promise<void> {
1919
const eventStream = EventStreamName.from('LearningMaterialsTasks', command.data.learningMaterialsId);
2020

21-
await this.applicationService.execute<TaskWasCompleted>(
21+
await this.applicationService.execute<LearningMaterialsTasksDomainEvent>(
2222
eventStream,
2323
{
2424
causationId: command.id,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Inject } from '@nestjs/common';
2+
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
3+
4+
import { UncompleteTaskApplicationCommand } from '@/module/commands/uncomplete-task.application-command';
5+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
6+
import { uncompleteTask } from '@/write/learning-materials-tasks/domain/uncomplete-task';
7+
import { APPLICATION_SERVICE, ApplicationService } from '@/write/shared/application/application-service';
8+
import { EventStreamName } from '@/write/shared/application/event-stream-name.value-object';
9+
10+
@CommandHandler(UncompleteTaskApplicationCommand)
11+
export class UncompleteTaskCommandHandler implements ICommandHandler<UncompleteTaskApplicationCommand> {
12+
constructor(
13+
@Inject(APPLICATION_SERVICE)
14+
private readonly applicationService: ApplicationService,
15+
) {}
16+
17+
async execute(command: UncompleteTaskApplicationCommand): Promise<void> {
18+
const eventStream = EventStreamName.from('LearningMaterialsTasks', command.data.learningMaterialsId);
19+
20+
await this.applicationService.execute<LearningMaterialsTasksDomainEvent>(
21+
eventStream,
22+
{
23+
causationId: command.id,
24+
correlationId: command.metadata.correlationId,
25+
},
26+
(pastEvents) => uncompleteTask(pastEvents, command),
27+
);
28+
}
29+
}

packages/api/src/module/write/learning-materials-tasks/domain/complete-task.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { TaskWasUncompleted } from '@/events/task-was-uncompleted-event.domain-event';
12
import { CompleteTask } from '@/module/commands/complete-task.domain-command';
23
import { TaskWasCompleted } from '@/module/events/task-was-completed.domain-event';
4+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
35

46
import { completeTask } from './complete-task';
57

@@ -61,4 +63,50 @@ describe('complete task', () => {
6163
// Then
6264
expect(events).toThrowError('Task was already completed');
6365
});
66+
67+
it('should complete uncompleted task', () => {
68+
// given
69+
const pastEvents: TaskWasUncompleted[] = [
70+
{
71+
type: 'TaskWasUncompleted',
72+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
73+
},
74+
];
75+
76+
// when
77+
const events = completeTask(pastEvents, command);
78+
79+
// then
80+
expect(events).toStrictEqual([
81+
{
82+
type: 'TaskWasCompleted',
83+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
84+
},
85+
]);
86+
});
87+
88+
it('should complete task if task was completed and then uncompleted', () => {
89+
// given
90+
const pastEvents: LearningMaterialsTasksDomainEvent[] = [
91+
{
92+
type: 'TaskWasCompleted',
93+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
94+
},
95+
{
96+
type: 'TaskWasUncompleted',
97+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
98+
},
99+
];
100+
101+
// when
102+
const events = completeTask(pastEvents, command);
103+
104+
// then
105+
expect(events).toStrictEqual([
106+
{
107+
type: 'TaskWasCompleted',
108+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
109+
},
110+
]);
111+
});
64112
});

packages/api/src/module/write/learning-materials-tasks/domain/complete-task.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { TaskWasCompleted } from '@/events/task-was-completed.domain-event';
22
import { CompleteTask } from '@/module/commands/complete-task.domain-command';
3+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
34

45
export function completeTask(
5-
pastEvents: TaskWasCompleted[],
6+
pastEvents: LearningMaterialsTasksDomainEvent[],
67
{ data: { learningMaterialsId, taskId } }: CompleteTask,
78
): TaskWasCompleted[] {
89
const state = pastEvents
@@ -13,6 +14,9 @@ export function completeTask(
1314
case 'TaskWasCompleted': {
1415
return { completed: true };
1516
}
17+
case 'TaskWasUncompleted': {
18+
return { completed: false };
19+
}
1620
default: {
1721
return acc;
1822
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { TaskWasCompleted } from '@/events/task-was-completed.domain-event';
2+
import { TaskWasUncompleted } from '@/events/task-was-uncompleted-event.domain-event';
3+
4+
export type LearningMaterialsTasksDomainEvent = TaskWasCompleted | TaskWasUncompleted;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { UncompleteTask } from '@/commands/uncomplete-task.domain-command';
2+
import { TaskWasCompleted } from '@/events/task-was-completed.domain-event';
3+
import { TaskWasUncompleted } from '@/events/task-was-uncompleted-event.domain-event';
4+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
5+
import { uncompleteTask } from '@/write/learning-materials-tasks/domain/uncomplete-task';
6+
7+
describe('uncomplete task', () => {
8+
const command: UncompleteTask = {
9+
type: 'UncompleteTask',
10+
data: { learningMaterialsId: 'sbAPITNMsl2wW6j2cg1H2A', taskId: 'L9EXtwmBNBXgo_qh0uzbq' },
11+
};
12+
13+
it('should uncomplete completed task', () => {
14+
// Given
15+
const pastEvents: TaskWasCompleted[] = [
16+
{
17+
type: 'TaskWasCompleted',
18+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: 'L9EXtwmBNBXgo_qh0uzbq' },
19+
},
20+
];
21+
22+
// When
23+
const events = uncompleteTask(pastEvents, command);
24+
25+
// Then
26+
expect(events).toStrictEqual([
27+
{
28+
type: 'TaskWasUncompleted',
29+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
30+
},
31+
]);
32+
});
33+
34+
it('should throw an error if try to uncomplete uncompleted task', () => {
35+
// given
36+
const pastEvents: TaskWasUncompleted[] = [
37+
{
38+
type: 'TaskWasUncompleted',
39+
data: { learningMaterialsId: command.data.learningMaterialsId, taskId: command.data.taskId },
40+
},
41+
];
42+
43+
// when
44+
const events = () => uncompleteTask(pastEvents, command);
45+
46+
// then
47+
expect(events).toThrowError('Can not uncomplete task that was not completed yet.');
48+
});
49+
50+
it('should throw an error if try to uncomplete task that was neither completed nor uncompleted yet', () => {
51+
// given
52+
const pastEvents: LearningMaterialsTasksDomainEvent[] = [];
53+
54+
// when
55+
const events = () => uncompleteTask(pastEvents, command);
56+
57+
// then
58+
expect(events).toThrowError('Can not uncomplete task that was not completed yet.');
59+
});
60+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { UncompleteTask } from '@/commands/uncomplete-task.domain-command';
2+
import { TaskWasUncompleted } from '@/events/task-was-uncompleted-event.domain-event';
3+
import { LearningMaterialsTasksDomainEvent } from '@/write/learning-materials-tasks/domain/events';
4+
5+
export function uncompleteTask(
6+
pastEvents: LearningMaterialsTasksDomainEvent[],
7+
{ data: { learningMaterialsId, taskId } }: UncompleteTask,
8+
): TaskWasUncompleted[] {
9+
const state = pastEvents
10+
.filter(({ data }) => data.taskId === taskId)
11+
.reduce<{ completed: boolean }>(
12+
(acc, event) => {
13+
switch (event.type) {
14+
case 'TaskWasCompleted': {
15+
return { completed: true };
16+
}
17+
case 'TaskWasUncompleted': {
18+
return { completed: false };
19+
}
20+
default: {
21+
return acc;
22+
}
23+
}
24+
},
25+
{ completed: false },
26+
);
27+
28+
if (!state.completed) {
29+
throw new Error('Can not uncomplete task that was not completed yet.');
30+
}
31+
32+
const newEvent: TaskWasUncompleted = {
33+
type: 'TaskWasUncompleted',
34+
data: {
35+
taskId,
36+
learningMaterialsId,
37+
},
38+
};
39+
40+
return [newEvent];
41+
}

0 commit comments

Comments
 (0)