Prechádzať zdrojové kódy

feat: message operations use cases. (#652)

Co-authored-by: Mo <mo@standardnotes.com>
Karol Sójko 1 rok pred
rodič
commit
55ec5970da
14 zmenil súbory, kde vykonal 348 pridanie a 0 odobranie
  1. 2 0
      packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts
  2. 60 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUser.spec.ts
  3. 30 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUser.ts
  4. 3 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUserDTO.ts
  5. 77 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessage.spec.ts
  6. 38 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessage.ts
  7. 4 0
      packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessageDTO.ts
  8. 32 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUser.spec.ts
  9. 21 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUser.ts
  10. 3 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUserDTO.ts
  11. 32 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.spec.ts
  12. 21 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.ts
  13. 3 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUserDTO.ts
  14. 22 0
      packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts

+ 2 - 0
packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts

@@ -4,6 +4,8 @@ import { Message } from './Message'
 
 export interface MessageRepositoryInterface {
   findByUuid: (uuid: Uuid) => Promise<Message | null>
+  findByRecipientUuid: (uuid: Uuid) => Promise<Message[]>
+  findBySenderUuid: (uuid: Uuid) => Promise<Message[]>
   findByRecipientUuidAndReplaceabilityIdentifier: (dto: {
     recipientUuid: Uuid
     replaceabilityIdentifier: string

+ 60 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUser.spec.ts

@@ -0,0 +1,60 @@
+import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
+import { Message } from '../../../Message/Message'
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { DeleteMessage } from '../DeleteMessage/DeleteMessage'
+import { DeleteAllMessagesSentToUser } from './DeleteAllMessagesSentToUser'
+
+describe('DeleteAllMessagesSentToUser', () => {
+  let messageRepository: MessageRepositoryInterface
+  let deleteMessageUseCase: DeleteMessage
+  let message: Message
+
+  const createUseCase = () => new DeleteAllMessagesSentToUser(messageRepository, deleteMessageUseCase)
+
+  beforeEach(() => {
+    message = Message.create({
+      senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      recipientUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      encryptedMessage: 'encryptedMessage',
+      replaceabilityIdentifier: 'replaceabilityIdentifier',
+      timestamps: Timestamps.create(123, 123).getValue(),
+    }).getValue()
+
+    messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
+    messageRepository.findByRecipientUuid = jest.fn().mockReturnValue([message])
+
+    deleteMessageUseCase = {} as jest.Mocked<DeleteMessage>
+    deleteMessageUseCase.execute = jest.fn().mockReturnValue(Result.ok())
+  })
+
+  it('should delete all messages sent to user', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+  })
+
+  it('should return error when recipient uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      recipientUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
+  })
+
+  it('should return error when delete message use case fails', async () => {
+    const useCase = createUseCase()
+    deleteMessageUseCase.execute = jest.fn().mockReturnValue(Result.fail('error'))
+
+    const result = await useCase.execute({
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('error')
+  })
+})

+ 30 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUser.ts

@@ -0,0 +1,30 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { DeleteAllMessagesSentToUserDTO } from './DeleteAllMessagesSentToUserDTO'
+import { DeleteMessage } from '../DeleteMessage/DeleteMessage'
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+
+export class DeleteAllMessagesSentToUser implements UseCaseInterface<void> {
+  constructor(private messageRepository: MessageRepositoryInterface, private deleteMessageUseCase: DeleteMessage) {}
+
+  async execute(dto: DeleteAllMessagesSentToUserDTO): Promise<Result<void>> {
+    const recipientUuidOrError = Uuid.create(dto.recipientUuid)
+    if (recipientUuidOrError.isFailed()) {
+      return Result.fail(recipientUuidOrError.getError())
+    }
+    const recipientUuid = recipientUuidOrError.getValue()
+
+    const messages = await this.messageRepository.findByRecipientUuid(recipientUuid)
+
+    for (const message of messages) {
+      const result = await this.deleteMessageUseCase.execute({
+        originatorUuid: recipientUuid.value,
+        messageUuid: message.id.toString(),
+      })
+      if (result.isFailed()) {
+        return Result.fail(result.getError())
+      }
+    }
+
+    return Result.ok()
+  }
+}

+ 3 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUserDTO.ts

@@ -0,0 +1,3 @@
+export interface DeleteAllMessagesSentToUserDTO {
+  recipientUuid: string
+}

+ 77 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessage.spec.ts

@@ -0,0 +1,77 @@
+import { Timestamps, Uuid } from '@standardnotes/domain-core'
+import { Message } from '../../../Message/Message'
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { DeleteMessage } from './DeleteMessage'
+
+describe('DeleteMessage', () => {
+  let messageRepository: MessageRepositoryInterface
+  let message: Message
+
+  const createUseCase = () => new DeleteMessage(messageRepository)
+
+  beforeEach(() => {
+    message = Message.create({
+      senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      recipientUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      encryptedMessage: 'encryptedMessage',
+      replaceabilityIdentifier: 'replaceabilityIdentifier',
+      timestamps: Timestamps.create(123, 123).getValue(),
+    }).getValue()
+
+    messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
+    messageRepository.remove = jest.fn()
+    messageRepository.findByUuid = jest.fn().mockReturnValue(message)
+  })
+
+  it('should remove message', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      messageUuid: '00000000-0000-0000-0000-000000000000',
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+  })
+
+  it('should return error when message is not found', async () => {
+    messageRepository.findByUuid = jest.fn().mockReturnValue(null)
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      messageUuid: '00000000-0000-0000-0000-000000000000',
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error if originator is neither the sender nor the recipient', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      messageUuid: '00000000-0000-0000-0000-000000000000',
+      originatorUuid: '11111111-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error when message uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      messageUuid: 'invalid',
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error when originator uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      messageUuid: '00000000-0000-0000-0000-000000000000',
+      originatorUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+})

+ 38 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessage.ts

@@ -0,0 +1,38 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { DeleteMessageDTO } from './DeleteMessageDTO'
+
+export class DeleteMessage implements UseCaseInterface<void> {
+  constructor(private messageRepository: MessageRepositoryInterface) {}
+
+  async execute(dto: DeleteMessageDTO): Promise<Result<void>> {
+    const originatorUuidOrError = Uuid.create(dto.originatorUuid)
+    if (originatorUuidOrError.isFailed()) {
+      return Result.fail(originatorUuidOrError.getError())
+    }
+    const originatorUuid = originatorUuidOrError.getValue()
+
+    const messageUuidOrError = Uuid.create(dto.messageUuid)
+    if (messageUuidOrError.isFailed()) {
+      return Result.fail(messageUuidOrError.getError())
+    }
+    const messageUuid = messageUuidOrError.getValue()
+
+    const message = await this.messageRepository.findByUuid(messageUuid)
+    if (!message) {
+      return Result.fail('Message not found')
+    }
+
+    const isSentByOriginator = message.props.senderUuid.equals(originatorUuid)
+    const isSentToOriginator = message.props.recipientUuid.equals(originatorUuid)
+
+    if (!isSentByOriginator && !isSentToOriginator) {
+      return Result.fail('Not authorized to delete this message')
+    }
+
+    await this.messageRepository.remove(message)
+
+    return Result.ok()
+  }
+}

+ 4 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/DeleteMessage/DeleteMessageDTO.ts

@@ -0,0 +1,4 @@
+export interface DeleteMessageDTO {
+  originatorUuid: string
+  messageUuid: string
+}

+ 32 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUser.spec.ts

@@ -0,0 +1,32 @@
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { GetMessagesSentByUser } from './GetMessagesSentByUser'
+
+describe('GetMessagesSentByUser', () => {
+  let messageRepository: MessageRepositoryInterface
+
+  const createUseCase = () => new GetMessagesSentByUser(messageRepository)
+
+  beforeEach(() => {
+    messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
+    messageRepository.findBySenderUuid = jest.fn().mockReturnValue([])
+  })
+
+  it('should return messages sent by user', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      senderUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.getValue()).toEqual([])
+  })
+
+  it('should return error when sender uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      senderUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
+  })
+})

+ 21 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUser.ts

@@ -0,0 +1,21 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+
+import { Message } from '../../../Message/Message'
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { GetMessagesSentByUserDTO } from './GetMessagesSentByUserDTO'
+
+export class GetMessagesSentByUser implements UseCaseInterface<Message[]> {
+  constructor(private messageRepository: MessageRepositoryInterface) {}
+
+  async execute(dto: GetMessagesSentByUserDTO): Promise<Result<Message[]>> {
+    const senderUuidOrError = Uuid.create(dto.senderUuid)
+    if (senderUuidOrError.isFailed()) {
+      return Result.fail(senderUuidOrError.getError())
+    }
+    const senderUuid = senderUuidOrError.getValue()
+
+    const messages = await this.messageRepository.findBySenderUuid(senderUuid)
+
+    return Result.ok(messages)
+  }
+}

+ 3 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUserDTO.ts

@@ -0,0 +1,3 @@
+export interface GetMessagesSentByUserDTO {
+  senderUuid: string
+}

+ 32 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.spec.ts

@@ -0,0 +1,32 @@
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { GetMessagesSentToUser } from './GetMessagesSentToUser'
+
+describe('GetMessagesSentToUser', () => {
+  let messageRepository: MessageRepositoryInterface
+
+  const createUseCase = () => new GetMessagesSentToUser(messageRepository)
+
+  beforeEach(() => {
+    messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
+    messageRepository.findByRecipientUuid = jest.fn().mockReturnValue([])
+  })
+
+  it('should return messages sent to user', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.getValue()).toEqual([])
+  })
+
+  it('should return error when recipient uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      recipientUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
+  })
+})

+ 21 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.ts

@@ -0,0 +1,21 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+
+import { Message } from '../../../Message/Message'
+import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
+import { GetMessagesSentToUserDTO } from './GetMessagesSentToUserDTO'
+
+export class GetMessagesSentToUser implements UseCaseInterface<Message[]> {
+  constructor(private messageRepository: MessageRepositoryInterface) {}
+
+  async execute(dto: GetMessagesSentToUserDTO): Promise<Result<Message[]>> {
+    const recipientUuidOrError = Uuid.create(dto.recipientUuid)
+    if (recipientUuidOrError.isFailed()) {
+      return Result.fail(recipientUuidOrError.getError())
+    }
+    const recipientUuid = recipientUuidOrError.getValue()
+
+    const messages = await this.messageRepository.findByRecipientUuid(recipientUuid)
+
+    return Result.ok(messages)
+  }
+}

+ 3 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUserDTO.ts

@@ -0,0 +1,3 @@
+export interface GetMessagesSentToUserDTO {
+  recipientUuid: string
+}

+ 22 - 0
packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts

@@ -11,6 +11,28 @@ export class TypeORMMessageRepository implements MessageRepositoryInterface {
     private mapper: MapperInterface<Message, TypeORMMessage>,
   ) {}
 
+  async findByRecipientUuid(uuid: Uuid): Promise<Message[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('message')
+      .where('message.recipient_uuid = :recipientUuid', {
+        recipientUuid: uuid.value,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
+
+  async findBySenderUuid(uuid: Uuid): Promise<Message[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('message')
+      .where('message.sender_uuid = :senderUuid', {
+        senderUuid: uuid.value,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
+
   async findByRecipientUuidAndReplaceabilityIdentifier(dto: {
     recipientUuid: Uuid
     replaceabilityIdentifier: string