From 6f9683c41a1135489832d9a854a114c82825a647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 10 Oct 2022 12:22:11 +0200 Subject: [PATCH] feat(workspace): add publishing workspace invite created --- .../src/Domain/Event/DomainEventService.ts | 1 + packages/workspace/src/Bootstrap/Container.ts | 3 ++ packages/workspace/src/Bootstrap/Types.ts | 1 + .../Domain/Event/DomainEventFactory.spec.ts | 44 +++++++++++++++++++ .../src/Domain/Event/DomainEventFactory.ts | 32 ++++++++++++++ .../Event/DomainEventFactoryInterface.ts | 10 +++++ .../InviteToWorkspace.spec.ts | 17 ++++++- .../InviteToWorkspace/InviteToWorkspace.ts | 13 ++++++ 8 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 packages/workspace/src/Domain/Event/DomainEventFactory.spec.ts create mode 100644 packages/workspace/src/Domain/Event/DomainEventFactory.ts create mode 100644 packages/workspace/src/Domain/Event/DomainEventFactoryInterface.ts diff --git a/packages/domain-events/src/Domain/Event/DomainEventService.ts b/packages/domain-events/src/Domain/Event/DomainEventService.ts index b4a8c9b3a..26c60d5a6 100644 --- a/packages/domain-events/src/Domain/Event/DomainEventService.ts +++ b/packages/domain-events/src/Domain/Event/DomainEventService.ts @@ -8,4 +8,5 @@ export enum DomainEventService { ApiGateway = 'api-gateway', Files = 'files', Scheduler = 'scheduler', + Workspace = 'workspace', } diff --git a/packages/workspace/src/Bootstrap/Container.ts b/packages/workspace/src/Bootstrap/Container.ts index 210db3477..502d09628 100644 --- a/packages/workspace/src/Bootstrap/Container.ts +++ b/packages/workspace/src/Bootstrap/Container.ts @@ -36,6 +36,8 @@ import { WorkspaceInviteRepositoryInterface } from '../Domain/Invite/WorkspaceIn import { MySQLWorkspaceInviteRepository } from '../Infra/MySQL/MySQLWorkspaceInviteRepository' import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite' import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace' +import { DomainEventFactory } from '../Domain/Event/DomainEventFactory' +import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -132,6 +134,7 @@ export class ContainerConfigLoader { // Handlers container.bind(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler) // Services + container.bind(TYPES.DomainEventFactory).to(DomainEventFactory) container.bind(TYPES.Timer).toConstantValue(new Timer()) container .bind>(TYPES.CrossServiceTokenDecoder) diff --git a/packages/workspace/src/Bootstrap/Types.ts b/packages/workspace/src/Bootstrap/Types.ts index 119a7b946..ba7c9afa0 100644 --- a/packages/workspace/src/Bootstrap/Types.ts +++ b/packages/workspace/src/Bootstrap/Types.ts @@ -36,6 +36,7 @@ const TYPES = { DomainEventPublisher: Symbol.for('DomainEventPublisher'), DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'), DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'), + DomainEventFactory: Symbol.for('DomainEventFactory'), } export default TYPES diff --git a/packages/workspace/src/Domain/Event/DomainEventFactory.spec.ts b/packages/workspace/src/Domain/Event/DomainEventFactory.spec.ts new file mode 100644 index 000000000..f5cdd630d --- /dev/null +++ b/packages/workspace/src/Domain/Event/DomainEventFactory.spec.ts @@ -0,0 +1,44 @@ +import 'reflect-metadata' + +import { TimerInterface } from '@standardnotes/time' + +import { DomainEventFactory } from './DomainEventFactory' + +describe('DomainEventFactory', () => { + let timer: TimerInterface + + const createFactory = () => new DomainEventFactory(timer) + + beforeEach(() => { + timer = {} as jest.Mocked + timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) + timer.getUTCDate = jest.fn().mockReturnValue(new Date(1)) + }) + + it('should create a WORKSPACE_INVITE_CREATED event', () => { + expect( + createFactory().createWorkspaceInviteCreatedEvent({ + inviterUuid: '1-2-3', + inviteeEmail: 'test@test.te', + inviteUuid: 'i-1-2-3', + workspaceUuid: 'w-1-2-3', + }), + ).toEqual({ + createdAt: expect.any(Date), + meta: { + correlation: { + userIdentifier: '1-2-3', + userIdentifierType: 'uuid', + }, + origin: 'workspace', + }, + payload: { + inviterUuid: '1-2-3', + inviteeEmail: 'test@test.te', + inviteUuid: 'i-1-2-3', + workspaceUuid: 'w-1-2-3', + }, + type: 'WORKSPACE_INVITE_CREATED', + }) + }) +}) diff --git a/packages/workspace/src/Domain/Event/DomainEventFactory.ts b/packages/workspace/src/Domain/Event/DomainEventFactory.ts new file mode 100644 index 000000000..092262f58 --- /dev/null +++ b/packages/workspace/src/Domain/Event/DomainEventFactory.ts @@ -0,0 +1,32 @@ +import { DomainEventService, WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events' +import { TimerInterface } from '@standardnotes/time' +import { inject, injectable } from 'inversify' + +import TYPES from '../../Bootstrap/Types' + +import { DomainEventFactoryInterface } from './DomainEventFactoryInterface' + +@injectable() +export class DomainEventFactory implements DomainEventFactoryInterface { + constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} + + createWorkspaceInviteCreatedEvent(dto: { + inviterUuid: string + inviteeEmail: string + inviteUuid: string + workspaceUuid: string + }): WorkspaceInviteCreatedEvent { + return { + type: 'WORKSPACE_INVITE_CREATED', + createdAt: this.timer.getUTCDate(), + meta: { + correlation: { + userIdentifier: dto.inviterUuid, + userIdentifierType: 'uuid', + }, + origin: DomainEventService.Workspace, + }, + payload: dto, + } + } +} diff --git a/packages/workspace/src/Domain/Event/DomainEventFactoryInterface.ts b/packages/workspace/src/Domain/Event/DomainEventFactoryInterface.ts new file mode 100644 index 000000000..e857c293d --- /dev/null +++ b/packages/workspace/src/Domain/Event/DomainEventFactoryInterface.ts @@ -0,0 +1,10 @@ +import { WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events' + +export interface DomainEventFactoryInterface { + createWorkspaceInviteCreatedEvent(dto: { + inviterUuid: string + inviteeEmail: string + inviteUuid: string + workspaceUuid: string + }): WorkspaceInviteCreatedEvent +} diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts index 127f9fef6..dccb17b8a 100644 --- a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts @@ -4,12 +4,17 @@ import { TimerInterface } from '@standardnotes/time' import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface' import { InviteToWorkspace } from './InviteToWorkspace' +import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' +import { DomainEventPublisherInterface, WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events' describe('InviteToWorkspace', () => { let workspaceInviteRepository: WorkspaceInviteRepositoryInterface let timer: TimerInterface + let domainEventFactory: DomainEventFactoryInterface + let domainEventPublisher: DomainEventPublisherInterface - const createUseCase = () => new InviteToWorkspace(workspaceInviteRepository, timer) + const createUseCase = () => + new InviteToWorkspace(workspaceInviteRepository, timer, domainEventFactory, domainEventPublisher) beforeEach(() => { workspaceInviteRepository = {} as jest.Mocked @@ -22,6 +27,14 @@ describe('InviteToWorkspace', () => { timer = {} as jest.Mocked timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) + + domainEventPublisher = {} as jest.Mocked + domainEventPublisher.publish = jest.fn() + + domainEventFactory = {} as jest.Mocked + domainEventFactory.createWorkspaceInviteCreatedEvent = jest + .fn() + .mockReturnValue({} as jest.Mocked) }) it('should create an invite', async () => { @@ -41,5 +54,7 @@ describe('InviteToWorkspace', () => { createdAt: 1, updatedAt: 1, }) + + expect(domainEventPublisher.publish).toHaveBeenCalled() }) }) diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts index 60fad4b14..c3ad8aea3 100644 --- a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts @@ -1,7 +1,9 @@ +import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { TimerInterface } from '@standardnotes/time' import { inject, injectable } from 'inversify' import TYPES from '../../../Bootstrap/Types' +import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' import { WorkspaceInvite } from '../../Invite/WorkspaceInvite' import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface' import { WorkspaceInviteStatus } from '../../Invite/WorkspaceInviteStatus' @@ -15,6 +17,8 @@ export class InviteToWorkspace implements UseCaseInterface { constructor( @inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface, @inject(TYPES.Timer) private timer: TimerInterface, + @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, ) {} async execute(dto: InviteToWorkspaceDTO): Promise { @@ -30,6 +34,15 @@ export class InviteToWorkspace implements UseCaseInterface { invite = await this.workspaceInviteRepository.save(invite) + await this.domainEventPublisher.publish( + this.domainEventFactory.createWorkspaceInviteCreatedEvent({ + inviterUuid: dto.inviterUuid, + inviteeEmail: dto.inviteeEmail, + workspaceUuid: dto.workspaceUuid, + inviteUuid: invite.uuid, + }), + ) + return { uuid: invite.uuid, }