diff --git a/packages/syncing-server/src/Domain/Message/Message.ts b/packages/syncing-server/src/Domain/Message/Message.ts new file mode 100644 index 000000000..9800c4d8c --- /dev/null +++ b/packages/syncing-server/src/Domain/Message/Message.ts @@ -0,0 +1,17 @@ +import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' + +import { MessageProps } from './MessageProps' + +export class Message extends Entity { + get id(): UniqueEntityId { + return this._id + } + + private constructor(props: MessageProps, id?: UniqueEntityId) { + super(props, id) + } + + static create(props: MessageProps, id?: UniqueEntityId): Result { + return Result.ok(new Message(props, id)) + } +} diff --git a/packages/syncing-server/src/Domain/Message/MessageProps.ts b/packages/syncing-server/src/Domain/Message/MessageProps.ts new file mode 100644 index 000000000..ee8bf5873 --- /dev/null +++ b/packages/syncing-server/src/Domain/Message/MessageProps.ts @@ -0,0 +1,9 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' + +export interface MessageProps { + recipientUuid: Uuid + senderUuid: Uuid + encryptedMessage: string + replaceabilityIdentifier: string | null + timestamps: Timestamps +} diff --git a/packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts b/packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts new file mode 100644 index 000000000..5f664056d --- /dev/null +++ b/packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts @@ -0,0 +1,9 @@ +import { Uuid } from '@standardnotes/domain-core' + +import { Message } from './Message' + +export interface MessageRepositoryInterface { + findByUuid: (uuid: Uuid) => Promise + save(message: Message): Promise + remove(message: Message): Promise +} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts new file mode 100644 index 000000000..71b582db0 --- /dev/null +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts @@ -0,0 +1,45 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' + +@Entity({ name: 'messages' }) +export class TypeORMMessage { + @PrimaryGeneratedColumn('uuid') + declare uuid: string + + @Column({ + name: 'recipient_uuid', + length: 36, + }) + declare recipientUuid: string + + @Column({ + name: 'sender_uuid', + length: 36, + }) + declare senderUuid: string + + @Column({ + name: 'encrypted_message', + type: 'text', + }) + declare encryptedMessage: string + + @Column({ + name: 'replaceability_identifier', + type: 'varchar', + length: 255, + nullable: true, + }) + declare replaceabilityIdentifier: string | null + + @Column({ + name: 'created_at_timestamp', + type: 'bigint', + }) + declare createdAtTimestamp: number + + @Column({ + name: 'updated_at_timestamp', + type: 'bigint', + }) + declare updatedAtTimestamp: number +} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts new file mode 100644 index 000000000..b0ec93e69 --- /dev/null +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts @@ -0,0 +1,38 @@ +import { Repository } from 'typeorm' +import { MapperInterface, Uuid } from '@standardnotes/domain-core' + +import { MessageRepositoryInterface } from '../../Domain/Message/MessageRepositoryInterface' +import { TypeORMMessage } from './TypeORMMessage' +import { Message } from '../../Domain/Message/Message' + +export class TypeORMMessageRepository implements MessageRepositoryInterface { + constructor( + private ormRepository: Repository, + private mapper: MapperInterface, + ) {} + + async findByUuid(uuid: Uuid): Promise { + const persistence = await this.ormRepository + .createQueryBuilder('message') + .where('message.uuid = :uuid', { + uuid: uuid.value, + }) + .getOne() + + if (persistence === null) { + return null + } + + return this.mapper.toDomain(persistence) + } + + async remove(message: Message): Promise { + await this.ormRepository.remove(this.mapper.toProjection(message)) + } + + async save(message: Message): Promise { + const persistence = this.mapper.toProjection(message) + + await this.ormRepository.save(persistence) + } +} diff --git a/packages/syncing-server/src/Mapping/Persistence/MessagePersistenceMapper.ts b/packages/syncing-server/src/Mapping/Persistence/MessagePersistenceMapper.ts new file mode 100644 index 000000000..1655dd555 --- /dev/null +++ b/packages/syncing-server/src/Mapping/Persistence/MessagePersistenceMapper.ts @@ -0,0 +1,63 @@ +import { Timestamps, MapperInterface, UniqueEntityId, Uuid, Validator } from '@standardnotes/domain-core' + +import { Message } from '../../Domain/Message/Message' + +import { TypeORMMessage } from '../../Infra/TypeORM/TypeORMMessage' + +export class MessagePersistenceMapper implements MapperInterface { + toDomain(projection: TypeORMMessage): Message { + const recipientUuidOrError = Uuid.create(projection.recipientUuid) + if (recipientUuidOrError.isFailed()) { + throw new Error(`Failed to create message from projection: ${recipientUuidOrError.getError()}`) + } + const recipientUuid = recipientUuidOrError.getValue() + + const senderUuidOrError = Uuid.create(projection.senderUuid) + if (senderUuidOrError.isFailed()) { + throw new Error(`Failed to create message from projection: ${senderUuidOrError.getError()}`) + } + const senderUuid = senderUuidOrError.getValue() + + const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp) + if (timestampsOrError.isFailed()) { + throw new Error(`Failed to create notification from projection: ${timestampsOrError.getError()}`) + } + const timestamps = timestampsOrError.getValue() + + const validateNotEmptyMessage = Validator.isNotEmpty(projection.encryptedMessage) + if (validateNotEmptyMessage.isFailed()) { + throw new Error(`Failed to create message from projection: ${validateNotEmptyMessage.getError()}`) + } + + const messageOrError = Message.create( + { + recipientUuid, + senderUuid, + encryptedMessage: projection.encryptedMessage, + replaceabilityIdentifier: projection.replaceabilityIdentifier, + timestamps, + }, + new UniqueEntityId(projection.uuid), + ) + if (messageOrError.isFailed()) { + throw new Error(`Failed to create message from projection: ${messageOrError.getError()}`) + } + const message = messageOrError.getValue() + + return message + } + + toProjection(domain: Message): TypeORMMessage { + const typeorm = new TypeORMMessage() + + typeorm.uuid = domain.id.toString() + typeorm.encryptedMessage = domain.props.encryptedMessage + typeorm.recipientUuid = domain.props.recipientUuid.value + typeorm.senderUuid = domain.props.senderUuid.value + typeorm.replaceabilityIdentifier = domain.props.replaceabilityIdentifier + typeorm.createdAtTimestamp = domain.props.timestamps.createdAt + typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt + + return typeorm + } +}