fix(syncing-server): add missing messages and key system identifier sql representations (#663)

This commit is contained in:
Karol Sójko
2023-07-19 08:31:23 +02:00
committed by GitHub
parent 3f21a358d2
commit d026152ac8
18 changed files with 171 additions and 33 deletions
@@ -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()
})
})
})
@@ -42,4 +42,18 @@ export class Validator {
return Result.ok()
}
static isNotEmptyString(value: unknown): Result<string> {
const isStringResult = Validator.isString(value)
if (isStringResult.isFailed()) {
return isStringResult
}
const isNotEmptyResult = Validator.isNotEmpty(value)
if (isNotEmptyResult.isFailed()) {
return isNotEmptyResult
}
return Result.ok()
}
}
@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddMessages1689745128577 implements MigrationInterface {
name = 'AddMessages1689745128577'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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`')
}
}
@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RenameKeyMessageIdentifier1689746180559 implements MigrationInterface {
name = 'RenameKeyMessageIdentifier1689746180559'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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`)',
)
}
}
@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddMessages1689744778643 implements MigrationInterface {
name = 'AddMessages1689744778643'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
await queryRunner.query('DROP INDEX "sender_uuid_on_messages"')
await queryRunner.query('DROP INDEX "recipient_uuid_on_messages"')
await queryRunner.query('DROP TABLE "messages"')
}
}
@@ -0,0 +1,43 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RenameKeyMessageIdentifier1689746527310 implements MigrationInterface {
name = 'RenameKeyMessageIdentifier1689746527310'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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") ',
)
}
}
@@ -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,
@@ -2,6 +2,6 @@ import { Timestamps, Uuid } from '@standardnotes/domain-core'
export interface KeySystemAssociationProps {
itemUuid: Uuid
keySystemUuid: Uuid
keySystemIdentifier: string
timestamps: Timestamps
}
@@ -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()
@@ -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({
@@ -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<Item> {
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<Item> {
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
keySystemUuid,
keySystemIdentifier,
})
if (keySystemAssociationOrError.isFailed()) {
return Result.fail(keySystemAssociationOrError.getError())
@@ -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({
@@ -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<Item> {
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<Item> {
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<Item> {
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
)
}
}
@@ -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',
@@ -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({
@@ -27,7 +27,7 @@ export class ItemHttpMapper implements MapperInterface<Item, ItemHttpRepresentat
updated_at_timestamp: domain.props.timestamps.updatedAt,
updated_with_session: domain.props.updatedWithSession ? domain.props.updatedWithSession.value : null,
key_system_identifier: domain.props.keySystemAssociation
? domain.props.keySystemAssociation.props.keySystemUuid.value
? domain.props.keySystemAssociation.props.keySystemIdentifier
: null,
shared_vault_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.sharedVaultUuid.value
@@ -23,7 +23,7 @@ export class SavedItemHttpMapper implements MapperInterface<Item, SavedItemHttpR
updated_at: this.timer.convertMicrosecondsToStringDate(domain.props.timestamps.updatedAt),
updated_at_timestamp: domain.props.timestamps.updatedAt,
key_system_identifier: domain.props.keySystemAssociation
? domain.props.keySystemAssociation.props.keySystemUuid.value
? domain.props.keySystemAssociation.props.keySystemIdentifier
: null,
shared_vault_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.sharedVaultUuid.value
@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Timestamps, UniqueEntityId, Uuid, Validator } from '@standardnotes/domain-core'
import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation'
@@ -14,11 +14,10 @@ export class KeySystemAssociationPersistenceMapper
}
const itemUuid = itemUuidOrError.getValue()
const keySystemUuidOrError = Uuid.create(projection.keySystemUuid)
if (keySystemUuidOrError.isFailed()) {
throw new Error(`Failed to create key system from projection: ${keySystemUuidOrError.getError()}`)
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(projection.keySystemIdentifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
throw new Error(`Failed to create key system from projection: ${keySystemIdentifiedValidationResult.getError()}`)
}
const keySystemUuid = keySystemUuidOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
if (timestampsOrError.isFailed()) {
@@ -30,7 +29,7 @@ export class KeySystemAssociationPersistenceMapper
{
itemUuid,
timestamps,
keySystemUuid,
keySystemIdentifier: projection.keySystemIdentifier,
},
new UniqueEntityId(projection.uuid),
)
@@ -48,7 +47,7 @@ export class KeySystemAssociationPersistenceMapper
typeorm.uuid = domain.id.toString()
typeorm.itemUuid = domain.props.itemUuid.value
typeorm.keySystemUuid = domain.props.keySystemUuid.value
typeorm.keySystemIdentifier = domain.props.keySystemIdentifier
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt