diff --git a/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts b/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts index fd02fa644..10be95f12 100644 --- a/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts +++ b/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts @@ -7,5 +7,9 @@ export interface SharedSubscriptionInvitationRepositoryInterface { findOneByUuidAndStatus(uuid: Uuid, status: InvitationStatus): Promise findOneByUuid(uuid: Uuid): Promise findByInviterEmail(inviterEmail: string): Promise + findOneByInviteeAndInviterEmail( + inviteeEmail: string, + inviterEmail: string, + ): Promise countByInviterEmailAndStatus(inviterEmail: Uuid, statuses: InvitationStatus[]): Promise } diff --git a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts index f26d5644b..cfa25f6d2 100644 --- a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts +++ b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts @@ -10,6 +10,7 @@ import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubs import { UserSubscription } from '../../Subscription/UserSubscription' import { RoleName } from '@standardnotes/common' import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType' +import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSubscriptionInvitation' describe('InviteToSharedSubscription', () => { let userSubscriptionRepository: UserSubscriptionRepositoryInterface @@ -40,6 +41,7 @@ describe('InviteToSharedSubscription', () => { sharedSubscriptionInvitationRepository = {} as jest.Mocked sharedSubscriptionInvitationRepository.save = jest.fn().mockImplementation((same) => ({ ...same, uuid: '1-2-3' })) sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus = jest.fn().mockReturnValue(2) + sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail = jest.fn().mockReturnValue(null) domainEventPublisher = {} as jest.Mocked domainEventPublisher.publish = jest.fn() @@ -181,4 +183,26 @@ describe('InviteToSharedSubscription', () => { expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) + + it('should not create an invitation if it already exists', async () => { + sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail = jest + .fn() + .mockReturnValue({} as jest.Mocked) + + expect( + await createUseCase().execute({ + inviteeIdentifier: 'invitee@test.te', + inviterUuid: '1-2-3', + inviterEmail: 'inviter@test.te', + inviterRoles: [RoleName.ProUser], + }), + ).toEqual({ + success: false, + }) + + expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled() + + expect(domainEventFactory.createSharedSubscriptionInvitationCreatedEvent).not.toHaveBeenCalled() + expect(domainEventPublisher.publish).not.toHaveBeenCalled() + }) }) diff --git a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts index c9f15c4db..21e336ad1 100644 --- a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts +++ b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts @@ -53,6 +53,16 @@ export class InviteToSharedSubscription implements UseCaseInterface { } } + const existingInvitation = await this.sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail( + dto.inviteeIdentifier, + dto.inviterEmail, + ) + if (existingInvitation !== null) { + return { + success: false, + } + } + const sharedSubscriptionInvition = new SharedSubscriptionInvitation() sharedSubscriptionInvition.inviterIdentifier = dto.inviterEmail sharedSubscriptionInvition.inviterIdentifierType = InviterIdentifierType.Email diff --git a/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.spec.ts b/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.spec.ts index 647ea41a2..b450f8714 100644 --- a/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.spec.ts +++ b/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.spec.ts @@ -70,6 +70,23 @@ describe('MySQLSharedSubscriptionInvitationRepository', () => { expect(result).toEqual(invitation) }) + it('should find one invitation by invitee and inviter email', async () => { + queryBuilder.where = jest.fn().mockReturnThis() + queryBuilder.getOne = jest.fn().mockReturnValue(invitation) + + const result = await createRepository().findOneByInviteeAndInviterEmail('invitee@test.te', 'inviter@test.te') + + expect(queryBuilder.where).toHaveBeenCalledWith( + 'invitation.inviter_identifier = :inviterEmail AND invitation.invitee_identifier = :inviteeEmail', + { + inviterEmail: 'inviter@test.te', + inviteeEmail: 'invitee@test.te', + }, + ) + + expect(result).toEqual(invitation) + }) + it('should find one invitation by uuid', async () => { queryBuilder.where = jest.fn().mockReturnThis() queryBuilder.getOne = jest.fn().mockReturnValue(invitation) diff --git a/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.ts b/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.ts index 9807d2395..8cfd094af 100644 --- a/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.ts +++ b/packages/auth/src/Infra/MySQL/MySQLSharedSubscriptionInvitationRepository.ts @@ -13,6 +13,19 @@ export class MySQLSharedSubscriptionInvitationRepository implements SharedSubscr private ormRepository: Repository, ) {} + async findOneByInviteeAndInviterEmail( + inviteeEmail: string, + inviterEmail: string, + ): Promise { + return this.ormRepository + .createQueryBuilder('invitation') + .where('invitation.inviter_identifier = :inviterEmail AND invitation.invitee_identifier = :inviteeEmail', { + inviterEmail, + inviteeEmail, + }) + .getOne() + } + async save(sharedSubscriptionInvitation: SharedSubscriptionInvitation): Promise { return this.ormRepository.save(sharedSubscriptionInvitation) }