diff --git a/packages/domain-core/src/Domain/Core/Validator.spec.ts b/packages/domain-core/src/Domain/Core/Validator.spec.ts index 4a99bfcb0..51fdb3609 100644 --- a/packages/domain-core/src/Domain/Core/Validator.spec.ts +++ b/packages/domain-core/src/Domain/Core/Validator.spec.ts @@ -78,4 +78,18 @@ describe('Validator', () => { expect(Validator.isNotEmpty(value).isFailed()).toBeTruthy() } }) + + describe('is not empty string', () => { + it('should not validate invalid string', () => { + expect(Validator.isNotEmptyString(123 as unknown as string).isFailed()).toBeTruthy() + }) + + it('should not validate an empty string', () => { + expect(Validator.isNotEmptyString('').isFailed()).toBeTruthy() + }) + + it('should validate a string', () => { + expect(Validator.isNotEmptyString('foo').isFailed()).toBeFalsy() + }) + }) }) diff --git a/packages/domain-core/src/Domain/Core/Validator.ts b/packages/domain-core/src/Domain/Core/Validator.ts index 43dd9b218..429dcd251 100644 --- a/packages/domain-core/src/Domain/Core/Validator.ts +++ b/packages/domain-core/src/Domain/Core/Validator.ts @@ -42,4 +42,18 @@ export class Validator { return Result.ok() } + + static isNotEmptyString(value: unknown): Result { + const isStringResult = Validator.isString(value) + if (isStringResult.isFailed()) { + return isStringResult + } + + const isNotEmptyResult = Validator.isNotEmpty(value) + if (isNotEmptyResult.isFailed()) { + return isNotEmptyResult + } + + return Result.ok() + } } diff --git a/packages/syncing-server/migrations/mysql/1689745128577-add-messages.ts b/packages/syncing-server/migrations/mysql/1689745128577-add-messages.ts new file mode 100644 index 000000000..772338ab0 --- /dev/null +++ b/packages/syncing-server/migrations/mysql/1689745128577-add-messages.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddMessages1689745128577 implements MigrationInterface { + name = 'AddMessages1689745128577' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE `messages` (`uuid` varchar(36) NOT NULL, `recipient_uuid` varchar(36) NOT NULL, `sender_uuid` varchar(36) NOT NULL, `encrypted_message` text NOT NULL, `replaceability_identifier` varchar(255) NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `recipient_uuid_on_messages` (`recipient_uuid`), INDEX `sender_uuid_on_messages` (`sender_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX `sender_uuid_on_messages` ON `messages`') + await queryRunner.query('DROP INDEX `recipient_uuid_on_messages` ON `messages`') + await queryRunner.query('DROP TABLE `messages`') + } +} diff --git a/packages/syncing-server/migrations/mysql/1689746180559-rename-key-message-identifier.ts b/packages/syncing-server/migrations/mysql/1689746180559-rename-key-message-identifier.ts new file mode 100644 index 000000000..a25f1777f --- /dev/null +++ b/packages/syncing-server/migrations/mysql/1689746180559-rename-key-message-identifier.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class RenameKeyMessageIdentifier1689746180559 implements MigrationInterface { + name = 'RenameKeyMessageIdentifier1689746180559' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations`') + await queryRunner.query( + 'ALTER TABLE `key_system_associations` CHANGE `key_system_uuid` `key_system_identifier` varchar(36) NOT NULL', + ) + await queryRunner.query( + 'CREATE INDEX `key_system_identifier_on_key_system_associations` ON `key_system_associations` (`key_system_identifier`)', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'DROP INDEX `key_system_identifier_on_key_system_associations` ON `key_system_associations`', + ) + await queryRunner.query( + 'ALTER TABLE `key_system_associations` CHANGE `key_system_identifier` `key_system_uuid` varchar(36) NOT NULL', + ) + await queryRunner.query( + 'CREATE INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations` (`key_system_uuid`)', + ) + } +} diff --git a/packages/syncing-server/migrations/sqlite/1689744778643-add-messages.ts b/packages/syncing-server/migrations/sqlite/1689744778643-add-messages.ts new file mode 100644 index 000000000..b66434693 --- /dev/null +++ b/packages/syncing-server/migrations/sqlite/1689744778643-add-messages.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddMessages1689744778643 implements MigrationInterface { + name = 'AddMessages1689744778643' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "messages" ("uuid" varchar PRIMARY KEY NOT NULL, "recipient_uuid" varchar(36) NOT NULL, "sender_uuid" varchar(36) NOT NULL, "encrypted_message" text NOT NULL, "replaceability_identifier" varchar(255), "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)', + ) + await queryRunner.query('CREATE INDEX "recipient_uuid_on_messages" ON "messages" ("recipient_uuid") ') + await queryRunner.query('CREATE INDEX "sender_uuid_on_messages" ON "messages" ("sender_uuid") ') + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "sender_uuid_on_messages"') + await queryRunner.query('DROP INDEX "recipient_uuid_on_messages"') + await queryRunner.query('DROP TABLE "messages"') + } +} diff --git a/packages/syncing-server/migrations/sqlite/1689746527310-rename-key-message-identifier.ts b/packages/syncing-server/migrations/sqlite/1689746527310-rename-key-message-identifier.ts new file mode 100644 index 000000000..fa9c31e86 --- /dev/null +++ b/packages/syncing-server/migrations/sqlite/1689746527310-rename-key-message-identifier.ts @@ -0,0 +1,43 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class RenameKeyMessageIdentifier1689746527310 implements MigrationInterface { + name = 'RenameKeyMessageIdentifier1689746527310' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "key_system_uuid_on_key_system_associations"') + await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"') + await queryRunner.query( + 'CREATE TABLE "temporary_key_system_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "key_system_identifier" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)', + ) + await queryRunner.query( + 'INSERT INTO "temporary_key_system_associations"("uuid", "key_system_identifier", "item_uuid", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "key_system_uuid", "item_uuid", "created_at_timestamp", "updated_at_timestamp" FROM "key_system_associations"', + ) + await queryRunner.query('DROP TABLE "key_system_associations"') + await queryRunner.query('ALTER TABLE "temporary_key_system_associations" RENAME TO "key_system_associations"') + await queryRunner.query( + 'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ', + ) + await queryRunner.query( + 'CREATE INDEX "key_system_identifier_on_key_system_associations" ON "key_system_associations" ("key_system_identifier") ', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "key_system_identifier_on_key_system_associations"') + await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"') + await queryRunner.query('ALTER TABLE "key_system_associations" RENAME TO "temporary_key_system_associations"') + await queryRunner.query( + 'CREATE TABLE "key_system_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "key_system_uuid" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)', + ) + await queryRunner.query( + 'INSERT INTO "key_system_associations"("uuid", "key_system_uuid", "item_uuid", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "key_system_identifier", "item_uuid", "created_at_timestamp", "updated_at_timestamp" FROM "temporary_key_system_associations"', + ) + await queryRunner.query('DROP TABLE "temporary_key_system_associations"') + await queryRunner.query( + 'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ', + ) + await queryRunner.query( + 'CREATE INDEX "key_system_uuid_on_key_system_associations" ON "key_system_associations" ("key_system_uuid") ', + ) + } +} diff --git a/packages/syncing-server/src/Bootstrap/DataSource.ts b/packages/syncing-server/src/Bootstrap/DataSource.ts index c11ae758c..6da31b94d 100644 --- a/packages/syncing-server/src/Bootstrap/DataSource.ts +++ b/packages/syncing-server/src/Bootstrap/DataSource.ts @@ -9,6 +9,7 @@ import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAs import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault' import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser' import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite' +import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage' export class AppDataSource { private _dataSource: DataSource | undefined @@ -46,6 +47,7 @@ export class AppDataSource { TypeORMSharedVault, TypeORMSharedVaultUser, TypeORMSharedVaultInvite, + TypeORMMessage, ], migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`], migrationsRun: true, diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts index 4920d89d2..ac209609e 100644 --- a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts @@ -2,6 +2,6 @@ import { Timestamps, Uuid } from '@standardnotes/domain-core' export interface KeySystemAssociationProps { itemUuid: Uuid - keySystemUuid: Uuid + keySystemIdentifier: string timestamps: Timestamps } diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts index 5d05216f0..e2fdb2d7d 100644 --- a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts @@ -7,7 +7,7 @@ describe('KeySystemAssociation', () => { const entityOrError = KeySystemAssociation.create({ timestamps: Timestamps.create(123456789, 123456789).getValue(), itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), - keySystemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + keySystemIdentifier: '00000000-0000-0000-0000-000000000000', }) expect(entityOrError.isFailed()).toBeFalsy() diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts index 937fbff73..72b91637c 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts @@ -388,12 +388,12 @@ describe('SaveNewItem', () => { expect(itemRepository.save).toHaveBeenCalled() }) - it('should return a failure if the item hash has an invalid key system uuid', async () => { + it('should return a failure if the item hash has an invalid key system identifier', async () => { const useCase = createUseCase() itemHash1 = ItemHash.create({ ...itemHash1.props, - key_system_identifier: '1-2-3', + key_system_identifier: 123 as unknown as string, }).getValue() const result = await useCase.execute({ diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts index cc8f6aa4c..840f402cf 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts @@ -6,6 +6,7 @@ import { UniqueEntityId, UseCaseInterface, Uuid, + Validator, } from '@standardnotes/domain-core' import { TimerInterface } from '@standardnotes/time' import { DomainEventPublisherInterface } from '@standardnotes/domain-events' @@ -111,11 +112,11 @@ export class SaveNewItem implements UseCaseInterface { let keySystemAssociation = undefined if (dto.itemHash.hasDedicatedKeySystemAssociation()) { - const keySystemUuidOrError = Uuid.create(dto.itemHash.props.key_system_identifier as string) - if (keySystemUuidOrError.isFailed()) { - return Result.fail(keySystemUuidOrError.getError()) + const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier) + if (keySystemIdentifiedValidationResult.isFailed()) { + return Result.fail(keySystemIdentifiedValidationResult.getError()) } - const keySystemUuid = keySystemUuidOrError.getValue() + const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string const keySystemAssociationOrError = KeySystemAssociation.create({ itemUuid: uuid, @@ -123,7 +124,7 @@ export class SaveNewItem implements UseCaseInterface { this.timer.getTimestampInMicroseconds(), this.timer.getTimestampInMicroseconds(), ).getValue(), - keySystemUuid, + keySystemIdentifier, }) if (keySystemAssociationOrError.isFailed()) { return Result.fail(keySystemAssociationOrError.getError()) diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts index 9908bc756..707175534 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts @@ -385,7 +385,7 @@ describe('UpdateExistingItem', () => { }) expect(result.isFailed()).toBeFalsy() expect(item1.props.keySystemAssociation).not.toBeUndefined() - expect(item1.props.keySystemAssociation?.props.keySystemUuid.value).toBe('00000000-0000-0000-0000-000000000000') + expect(item1.props.keySystemAssociation?.props.keySystemIdentifier).toBe('00000000-0000-0000-0000-000000000000') }) it('should not add a key system association if item hash has a dedicated key system and the existing item is already associated to the key system', async () => { @@ -398,7 +398,7 @@ describe('UpdateExistingItem', () => { item1.props.keySystemAssociation = KeySystemAssociation.create({ itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), - keySystemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + keySystemIdentifier: '00000000-0000-0000-0000-000000000000', timestamps: Timestamps.create(123, 123).getValue(), }).getValue() const idBefore = item1.props.keySystemAssociation?.id.toString() @@ -416,12 +416,12 @@ describe('UpdateExistingItem', () => { expect(item1.props.keySystemAssociation.id.toString()).toEqual(idBefore) }) - it('should return error if key system uuid is invalid', async () => { + it('should return error if key system identifier is invalid', async () => { const useCase = createUseCase() const itemHash = ItemHash.create({ ...itemHash1.props, - key_system_identifier: 'invalid-uuid', + key_system_identifier: 123 as unknown as string, }).getValue() const result = await useCase.execute({ diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts index 170d22281..4628e61ec 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts @@ -1,4 +1,4 @@ -import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core' +import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core' import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { TimerInterface } from '@standardnotes/time' @@ -135,11 +135,11 @@ export class UpdateExistingItem implements UseCaseInterface { dto.itemHash.hasDedicatedKeySystemAssociation() && !this.itemIsAlreadyAssociatedWithTheKeySystem(dto.existingItem, dto.itemHash) ) { - const keySystemUuidOrError = Uuid.create(dto.itemHash.props.key_system_identifier as string) - if (keySystemUuidOrError.isFailed()) { - return Result.fail(keySystemUuidOrError.getError()) + const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier) + if (keySystemIdentifiedValidationResult.isFailed()) { + return Result.fail(keySystemIdentifiedValidationResult.getError()) } - const keySystemUuid = keySystemUuidOrError.getValue() + const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string const keySystemAssociationOrError = KeySystemAssociation.create({ itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(), @@ -147,7 +147,7 @@ export class UpdateExistingItem implements UseCaseInterface { this.timer.getTimestampInMicroseconds(), this.timer.getTimestampInMicroseconds(), ).getValue(), - keySystemUuid, + keySystemIdentifier, }) if (keySystemAssociationOrError.isFailed()) { return Result.fail(keySystemAssociationOrError.getError()) @@ -203,7 +203,7 @@ export class UpdateExistingItem implements UseCaseInterface { private itemIsAlreadyAssociatedWithTheKeySystem(item: Item, itemHash: ItemHash): boolean { return ( item.props.keySystemAssociation !== undefined && - item.props.keySystemAssociation.props.keySystemUuid.value === itemHash.props.key_system_identifier + item.props.keySystemAssociation.props.keySystemIdentifier === itemHash.props.key_system_identifier ) } } diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts index a87ff8c5b..6d1bcddc8 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts @@ -6,11 +6,11 @@ export class TypeORMKeySystemAssociation { declare uuid: string @Column({ - name: 'key_system_uuid', + name: 'key_system_identifier', length: 36, }) - @Index('key_system_uuid_on_key_system_associations') - declare keySystemUuid: string + @Index('key_system_identifier_on_key_system_associations') + declare keySystemIdentifier: string @Column({ name: 'item_uuid', diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts index 71b582db0..887b62f0d 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMMessage.ts @@ -1,4 +1,4 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm' @Entity({ name: 'messages' }) export class TypeORMMessage { @@ -9,12 +9,14 @@ export class TypeORMMessage { name: 'recipient_uuid', length: 36, }) + @Index('recipient_uuid_on_messages') declare recipientUuid: string @Column({ name: 'sender_uuid', length: 36, }) + @Index('sender_uuid_on_messages') declare senderUuid: string @Column({ diff --git a/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts b/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts index 79ae90a75..dc7350d29 100644 --- a/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts +++ b/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts @@ -27,7 +27,7 @@ export class ItemHttpMapper implements MapperInterface