diff --git a/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/user-registration-was-completed.event-handler.service.ts b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/user-registration-was-completed.event-handler.service.ts new file mode 100644 index 00000000..0fcc57db --- /dev/null +++ b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/user-registration-was-completed.event-handler.service.ts @@ -0,0 +1,52 @@ +import { Injectable, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { ApplicationEvent } from '@/module/application-command-events'; +import { + RegisterCourseUserApplicationCommand, + RegisterCourseUserCommand, +} from '@/module/commands/register-course-user'; +import { UserRegistrationWasCompleted } from '@/module/events/user-registration-was-completed.domain-event'; +import { env } from '@/shared/env'; +import { ApplicationCommandFactory } from '@/write/shared/application/application-command.factory'; +import { EventsSubscription } from '@/write/shared/application/events-subscription/events-subscription'; +import { EventsSubscriptionsRegistry } from '@/write/shared/application/events-subscription/events-subscriptions-registry'; + +@Injectable() +export class UserRegistrationWasCompletedEventHandler implements OnApplicationBootstrap, OnModuleDestroy { + private eventsSubscription: EventsSubscription; + + constructor( + private readonly commandBus: CommandBus, + private readonly commandFactory: ApplicationCommandFactory, + private readonly eventsSubscriptionsFactory: EventsSubscriptionsRegistry, + ) {} + + async onApplicationBootstrap() { + this.eventsSubscription = this.eventsSubscriptionsFactory + .subscription('WhenUserRegistrationWasCompletedThenRegisterCourseUser_Automation_v1') + .onEvent('UserRegistrationWasCompleted', (event) => + this.onUserRegistrationWasCompleted(event), + ) + .build(); + await this.eventsSubscription.start(); + } + + async onModuleDestroy() { + await this.eventsSubscription.stop(); + } + + async onUserRegistrationWasCompleted(event: ApplicationEvent) { + const command = this.commandFactory.applicationCommand((idGenerator) => ({ + class: RegisterCourseUserApplicationCommand, + ...RegisterCourseUserCommand({ + userId: event.data.userId, + courseUserId: idGenerator.generate(), + courseId: env.CURRENT_COURSE_ID, + }), + metadata: { correlationId: event.metadata.correlationId, causationId: event.id }, + })); + + await this.commandBus.execute(command); + } +} diff --git a/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user-automation.module.ts b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user-automation.module.ts new file mode 100644 index 00000000..0d52cf51 --- /dev/null +++ b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user-automation.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { SharedModule } from '@/write/shared/shared.module'; + +import { UserRegistrationWasCompletedEventHandler } from './user-registration-was-completed.event-handler.service'; + +@Module({ + imports: [SharedModule], + providers: [UserRegistrationWasCompletedEventHandler], +}) +export class WhenRegistrationCompletedThenRegisterCurrentUserAutomationModule {} diff --git a/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.spec.ts b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.spec.ts new file mode 100644 index 00000000..2760b429 --- /dev/null +++ b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.spec.ts @@ -0,0 +1,42 @@ +import { AsyncReturnType } from 'type-fest'; + +import { RegisterCourseUser, RegisterCourseUserCommand } from '@/module/commands/register-course-user'; +import { userRegistrationWasCompletedEvent } from '@/module/events/user-registration-was-completed.domain-event'; +import { EventStreamName } from '@/write/shared/application/event-stream-name.value-object'; + +import { whenUserRegistrationWasCompletedThenRegisterCurrentEditionCourseUserAutomationTestModule } from './when-registration-completed-then-register-current-edition-course-user.test-module'; + +describe('RegisterCourseUser when registrationWasCompleted', () => { + let moduleUnderTest: AsyncReturnType< + typeof whenUserRegistrationWasCompletedThenRegisterCurrentEditionCourseUserAutomationTestModule + >; + + beforeEach(async () => { + moduleUnderTest = await whenUserRegistrationWasCompletedThenRegisterCurrentEditionCourseUserAutomationTestModule(); + }); + + afterEach(async () => { + await moduleUnderTest.close(); + }); + + it('publishes registerCourseUser command when userRegistrationWasCompletedEvent occurred', async () => { + // Given + + const courseId = process.env.CURRENT_COURSE_ID ?? ''; + + const userId = 'ca63d023-4cbd-40ca-9f53-f19dbb19b0ab'; + const fullName = 'test user'; + const emailAddress = 'testUser@test.pl'; + const hashedPassword = '41c2c1fc8f6cdc15.d5ee8246071726582172f83d569287951a0d727c94dfc35e291fe17abec789c2'; + + const event = userRegistrationWasCompletedEvent({ userId, fullName, emailAddress, hashedPassword }); + + // When + await moduleUnderTest.eventOccurred(EventStreamName.from('UserRegistration', userId), event); + + // Then + await moduleUnderTest.expectCommandExecutedLastly({ + ...RegisterCourseUserCommand({ courseId, userId, courseUserId: moduleUnderTest.lastGeneratedId() }), + }); + }); +}); diff --git a/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.test-module.ts b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.test-module.ts new file mode 100644 index 00000000..b0d11086 --- /dev/null +++ b/packages/api/src/module/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user.test-module.ts @@ -0,0 +1,6 @@ +import { WhenRegistrationCompletedThenRegisterCurrentUserAutomationModule } from '@/automation/when-registration-completed-then-register-current-edition-course-user/when-registration-completed-then-register-current-edition-course-user-automation.module'; +import { initAutomationTestModule } from '@/shared/test-utils'; + +export async function whenUserRegistrationWasCompletedThenRegisterCurrentEditionCourseUserAutomationTestModule() { + return initAutomationTestModule([WhenRegistrationCompletedThenRegisterCurrentUserAutomationModule]); +} diff --git a/packages/api/src/module/shared/commands/register-course-user.ts b/packages/api/src/module/shared/commands/register-course-user.ts index 7aa13fab..2a5a447b 100644 --- a/packages/api/src/module/shared/commands/register-course-user.ts +++ b/packages/api/src/module/shared/commands/register-course-user.ts @@ -9,4 +9,9 @@ export type RegisterCourseUser = { }; }; +export const RegisterCourseUserCommand = (data: RegisterCourseUser['data']): RegisterCourseUser => ({ + type: 'RegisterCourseUser', + data, +}); + export class RegisterCourseUserApplicationCommand extends AbstractApplicationCommand {} diff --git a/packages/api/src/module/write/shared/application/application-command.factory.ts b/packages/api/src/module/write/shared/application/application-command.factory.ts index 097e05f6..d6616569 100644 --- a/packages/api/src/module/write/shared/application/application-command.factory.ts +++ b/packages/api/src/module/write/shared/application/application-command.factory.ts @@ -28,14 +28,17 @@ export class ApplicationCommandFactory { const generateId = () => this.idGenerator.generate(); const currentTime = () => this.timeProvider.currentTime(); + const id = generateId(); + const correlationId = generateId(); + const command = builder(this.idGenerator); return plainToClass(command.class, { type: command.type, - id: generateId(), + id, issuedAt: currentTime(), data: command.data, - metadata: { correlationId: generateId(), ...command.metadata }, + metadata: { correlationId, ...command.metadata }, }); } }