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

Commit d9478f2

Browse files
authored
feat(api): write register course user implement slice (#328) (#366)
* command * command handler * register course user function * module * env.example semicolon removal
1 parent cdfeeb2 commit d9478f2

File tree

9 files changed

+176
-2
lines changed

9 files changed

+176
-2
lines changed

packages/api/.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ COOKIE_SECRET="89YHGASP9DYFHWEOIH098yasd8rtwepuddrw902"
1212

1313
PROCESS_ST_CHECKLIST_URL="https://app.process.st/workflows/CodersCamp-Test-Checklist-kTrxJoZgP-9IabhRbohIrw/run-link"
1414
EVENT_REPOSITORY="prisma"
15-
SUBSCRIPTION_QUEUE_MAX_RETRY_COUNT=10;
16-
SUBSCRIPTION_QUEUE_WAITING_TIME_ON_RETRY_MS=100;
15+
SUBSCRIPTION_QUEUE_MAX_RETRY_COUNT=10
16+
SUBSCRIPTION_QUEUE_WAITING_TIME_ON_RETRY_MS=100

packages/api/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { env } from '@/shared/env';
1313
import { EmailConfirmationWriteModule } from '@/write/email-confirmation/email-confirmation.write-module';
1414
import { LearningMaterialsTasksModule } from '@/write/learning-materials-tasks/learning-materials-tasks.write-module';
1515
import { LearningMaterialsUrlWriteModule } from '@/write/learning-materials-url/learning-materials-url.write-module';
16+
import { RegisterCourseUserWriteModule } from '@/write/register-course-user/register-course-user.write-module';
1617
import { UserRegistrationWriteModule } from '@/write/user-registration/user-registration.write-module';
1718

1819
import { AuthModule } from './crud/auth/auth.module';
@@ -26,6 +27,7 @@ const writeModules = [
2627
UserRegistrationWriteModule,
2728
LearningMaterialsTasksModule,
2829
EmailConfirmationWriteModule,
30+
RegisterCourseUserWriteModule,
2931
];
3032
const readModules = [LearningMaterialsReadModule, CourseProgressReadModule];
3133
const automationModules = [
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { AbstractApplicationCommand } from '@/module/application-command-events';
2+
3+
export type RegisterCourseUser = {
4+
type: 'RegisterCourseUser';
5+
data: {
6+
userId: string;
7+
courseUserId: string;
8+
courseId: string;
9+
};
10+
};
11+
12+
export class RegisterCourseUserApplicationCommand extends AbstractApplicationCommand<RegisterCourseUser> {}
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 { RegisterCourseUserApplicationCommand } from '@/module/commands/register-course-user';
5+
import { CourseUserWasRegistered } from '@/module/events/course-user-was-registered.domain-event';
6+
import { APPLICATION_SERVICE, ApplicationService } from '@/write/shared/application/application-service';
7+
import { EventStreamName } from '@/write/shared/application/event-stream-name.value-object';
8+
9+
import { registerCourseUser } from '../domain/register-course-user';
10+
11+
@CommandHandler(RegisterCourseUserApplicationCommand)
12+
export class RegisterCourseUserApplicationCommandHandler
13+
implements ICommandHandler<RegisterCourseUserApplicationCommand>
14+
{
15+
constructor(
16+
@Inject(APPLICATION_SERVICE)
17+
private readonly applicationService: ApplicationService,
18+
) {}
19+
20+
async execute(command: RegisterCourseUserApplicationCommand): Promise<void> {
21+
const eventStreamName = EventStreamName.from('CourseUser', `${command.data.courseId}_${command.data.userId}`);
22+
23+
await this.applicationService.execute<CourseUserWasRegistered>(
24+
eventStreamName,
25+
{ causationId: command.id, correlationId: command.metadata.correlationId },
26+
(pastEvents) => registerCourseUser(pastEvents, command),
27+
);
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { RegisterCourseUser } from '@/module/commands/register-course-user';
2+
import { CourseUserWasRegistered } from '@/module/events/course-user-was-registered.domain-event';
3+
4+
import { registerCourseUser } from './register-course-user';
5+
6+
describe('register course user', () => {
7+
const command: RegisterCourseUser = {
8+
type: 'RegisterCourseUser',
9+
data: {
10+
userId: 'vvcwfOgoTHVrVLf4Ky5e6',
11+
courseUserId: 'F7IXr5vnCVWfe1oD9u7tz',
12+
courseId: 'iyt46Z6Mjifxmb_cZqwmb',
13+
},
14+
};
15+
16+
it('should return CourseUserWasRegistered if user is not registered', () => {
17+
// Given
18+
const pastEvents: CourseUserWasRegistered[] = [];
19+
20+
// When
21+
const events = registerCourseUser(pastEvents, command);
22+
23+
// Then
24+
expect(events).toStrictEqual([{ type: 'CourseUserWasRegistered', data: command.data }]);
25+
});
26+
27+
it('should throw error if user have been already registered', () => {
28+
// Given
29+
const pastEvents: CourseUserWasRegistered[] = [{ type: 'CourseUserWasRegistered', data: command.data }];
30+
31+
// When
32+
const events = () => registerCourseUser(pastEvents, command);
33+
34+
// Then
35+
expect(events).toThrow();
36+
});
37+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { RegisterCourseUser } from '@/module/commands/register-course-user';
2+
import { CourseUserWasRegistered } from '@/module/events/course-user-was-registered.domain-event';
3+
4+
export function registerCourseUser(
5+
pastEvents: CourseUserWasRegistered[],
6+
{ data }: RegisterCourseUser,
7+
): CourseUserWasRegistered[] {
8+
const state = pastEvents.reduce<{ registered: boolean }>(
9+
(acc, event) => {
10+
switch (event.type) {
11+
case 'CourseUserWasRegistered': {
12+
return { registered: true };
13+
}
14+
default: {
15+
return acc;
16+
}
17+
}
18+
},
19+
{ registered: false },
20+
);
21+
22+
if (state.registered) {
23+
throw new Error('Course user already registered');
24+
}
25+
26+
const newEvent: CourseUserWasRegistered = {
27+
type: 'CourseUserWasRegistered',
28+
data,
29+
};
30+
31+
return [newEvent];
32+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { initWriteTestModule } from '@/shared/test-utils';
2+
3+
export async function registerCourseUserTestModule() {
4+
return initWriteTestModule();
5+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { AsyncReturnType } from 'type-fest';
2+
3+
import { RegisterCourseUserApplicationCommand } from '@/module/commands/register-course-user';
4+
import { CourseUserWasRegistered } from '@/module/events/course-user-was-registered.domain-event';
5+
6+
import { EventStreamName } from '../shared/application/event-stream-name.value-object';
7+
import { registerCourseUserTestModule } from './register-course-user.test-module';
8+
9+
describe('registerCourseUserModule', () => {
10+
let module: AsyncReturnType<typeof registerCourseUserTestModule>;
11+
const commandBuilder = (userId = '', courseUserId = '', courseId = '') => ({
12+
class: RegisterCourseUserApplicationCommand,
13+
type: 'RegisterCourseUser',
14+
data: { userId, courseUserId, courseId },
15+
});
16+
17+
it('should register new course user', async () => {
18+
// Given
19+
const command = commandBuilder();
20+
21+
// When
22+
await module.executeCommand(() => command);
23+
24+
// Then
25+
module.expectEventPublishedLastly<CourseUserWasRegistered>({
26+
type: 'CourseUserWasRegistered',
27+
data: { userId: command.data.userId, courseUserId: command.data.courseUserId, courseId: command.data.courseId },
28+
streamName: EventStreamName.from('CourseUser', `${command.data.courseId}_${command.data.userId}`),
29+
});
30+
});
31+
32+
it('should not register new user if one already exists', async () => {
33+
// Given
34+
const command = commandBuilder();
35+
36+
// When
37+
await module.executeCommand(() => command);
38+
39+
// Then
40+
await expect(() => module.executeCommand(() => command)).rejects.toThrow();
41+
});
42+
43+
beforeEach(async () => {
44+
module = await registerCourseUserTestModule();
45+
});
46+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Module } from '@nestjs/common';
2+
3+
import { SharedModule } from '../shared/shared.module';
4+
import { RegisterCourseUserApplicationCommandHandler } from './application/register-course-user.command-handler';
5+
6+
@Module({
7+
imports: [SharedModule],
8+
providers: [RegisterCourseUserApplicationCommandHandler],
9+
exports: [],
10+
})
11+
export class RegisterCourseUserWriteModule {}

0 commit comments

Comments
 (0)