diff --git a/packages/syncing-server/migrations/mysql/1689671563305-add-shared-vault-and-key-system-associations.ts b/packages/syncing-server/migrations/mysql/1689671563305-add-shared-vault-and-key-system-associations.ts new file mode 100644 index 000000000..1b7219d59 --- /dev/null +++ b/packages/syncing-server/migrations/mysql/1689671563305-add-shared-vault-and-key-system-associations.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddSharedVaultAndKeySystemAssociations1689671563305 implements MigrationInterface { + name = 'AddSharedVaultAndKeySystemAssociations1689671563305' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE `shared_vault_associations` (`uuid` varchar(36) NOT NULL, `shared_vault_uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `last_edited_by` varchar(36) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `shared_vault_uuid_on_shared_vault_associations` (`shared_vault_uuid`), INDEX `item_uuid_on_shared_vault_associations` (`item_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB', + ) + await queryRunner.query( + 'CREATE TABLE `key_system_associations` (`uuid` varchar(36) 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, INDEX `key_system_uuid_on_key_system_associations` (`key_system_uuid`), INDEX `item_uuid_on_key_system_associations` (`item_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX `item_uuid_on_key_system_associations` ON `key_system_associations`') + await queryRunner.query('DROP INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations`') + await queryRunner.query('DROP TABLE `key_system_associations`') + await queryRunner.query('DROP INDEX `item_uuid_on_shared_vault_associations` ON `shared_vault_associations`') + await queryRunner.query( + 'DROP INDEX `shared_vault_uuid_on_shared_vault_associations` ON `shared_vault_associations`', + ) + await queryRunner.query('DROP TABLE `shared_vault_associations`') + } +} diff --git a/packages/syncing-server/migrations/sqlite/1689672099829-add-shared-vault-and-key-system-associations.ts b/packages/syncing-server/migrations/sqlite/1689672099829-add-shared-vault-and-key-system-associations.ts new file mode 100644 index 000000000..0008e9d51 --- /dev/null +++ b/packages/syncing-server/migrations/sqlite/1689672099829-add-shared-vault-and-key-system-associations.ts @@ -0,0 +1,35 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddSharedVaultAndKeySystemAssociations1689672099829 implements MigrationInterface { + name = 'AddSharedVaultAndKeySystemAssociations1689672099829' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "shared_vault_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "last_edited_by" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)', + ) + await queryRunner.query( + 'CREATE INDEX "shared_vault_uuid_on_shared_vault_associations" ON "shared_vault_associations" ("shared_vault_uuid") ', + ) + await queryRunner.query( + 'CREATE INDEX "item_uuid_on_shared_vault_associations" ON "shared_vault_associations" ("item_uuid") ', + ) + 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( + 'CREATE INDEX "key_system_uuid_on_key_system_associations" ON "key_system_associations" ("key_system_uuid") ', + ) + await queryRunner.query( + 'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"') + await queryRunner.query('DROP INDEX "key_system_uuid_on_key_system_associations"') + await queryRunner.query('DROP TABLE "key_system_associations"') + await queryRunner.query('DROP INDEX "item_uuid_on_shared_vault_associations"') + await queryRunner.query('DROP INDEX "shared_vault_uuid_on_shared_vault_associations"') + await queryRunner.query('DROP TABLE "shared_vault_associations"') + } +} diff --git a/packages/syncing-server/src/Bootstrap/Container.ts b/packages/syncing-server/src/Bootstrap/Container.ts index 84bd34708..b25165262 100644 --- a/packages/syncing-server/src/Bootstrap/Container.ts +++ b/packages/syncing-server/src/Bootstrap/Container.ts @@ -78,6 +78,16 @@ import { SaveItems } from '../Domain/UseCase/Syncing/SaveItems/SaveItems' import { ItemHashHttpMapper } from '../Mapping/Http/ItemHashHttpMapper' import { ItemHash } from '../Domain/Item/ItemHash' import { ItemHashHttpRepresentation } from '../Mapping/Http/ItemHashHttpRepresentation' +import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation' +import { SharedVaultAssociation } from '../Domain/SharedVault/SharedVaultAssociation' +import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation' +import { SharedVaultAssociationPersistenceMapper } from '../Mapping/Persistence/SharedVaultAssociationPersistenceMapper' +import { TypeORMKeySystemAssociationRepository } from '../Infra/TypeORM/TypeORMKeySystemAssociationRepository' +import { SharedVaultAssociationRepositoryInterface } from '../Domain/SharedVault/SharedVaultAssociationRepositoryInterface' +import { TypeORMSharedVaultAssociationRepository } from '../Infra/TypeORM/TypeORMSharedVaultAssociationRepository' +import { KeySystemAssociation } from '../Domain/KeySystem/KeySystemAssociation' +import { KeySystemAssociationRepositoryInterface } from '../Domain/KeySystem/KeySystemAssociationRepositoryInterface' +import { KeySystemAssociationPersistenceMapper } from '../Mapping/Persistence/KeySystemAssociationPersistenceMapper' export class ContainerConfigLoader { private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 @@ -232,19 +242,54 @@ export class ContainerConfigLoader { container .bind>(TYPES.Sync_ItemBackupMapper) .toConstantValue(new ItemBackupMapper(container.get(TYPES.Sync_Timer))) + container + .bind>( + TYPES.Sync_KeySystemAssociationPersistenceMapper, + ) + .toConstantValue(new KeySystemAssociationPersistenceMapper()) + container + .bind>( + TYPES.Sync_SharedVaultAssociationPersistenceMapper, + ) + .toConstantValue(new SharedVaultAssociationPersistenceMapper()) // ORM container .bind>(TYPES.Sync_ORMItemRepository) .toDynamicValue(() => appDataSource.getRepository(TypeORMItem)) + container + .bind>(TYPES.Sync_ORMSharedVaultAssociationRepository) + .toConstantValue(appDataSource.getRepository(TypeORMSharedVaultAssociation)) + container + .bind>(TYPES.Sync_ORMKeySystemAssociationRepository) + .toConstantValue(appDataSource.getRepository(TypeORMKeySystemAssociation)) // Repositories + container + .bind(TYPES.Sync_KeySystemAssociationRepository) + .toConstantValue( + new TypeORMKeySystemAssociationRepository( + container.get(TYPES.Sync_ORMKeySystemAssociationRepository), + container.get(TYPES.Sync_KeySystemAssociationPersistenceMapper), + ), + ) + container + .bind(TYPES.Sync_SharedVaultAssociationRepository) + .toConstantValue( + new TypeORMSharedVaultAssociationRepository( + container.get(TYPES.Sync_ORMSharedVaultAssociationRepository), + container.get(TYPES.Sync_SharedVaultAssociationPersistenceMapper), + ), + ) + container .bind(TYPES.Sync_ItemRepository) .toConstantValue( new TypeORMItemRepository( container.get(TYPES.Sync_ORMItemRepository), container.get(TYPES.Sync_ItemPersistenceMapper), + container.get(TYPES.Sync_KeySystemAssociationRepository), + container.get(TYPES.Sync_SharedVaultAssociationRepository), ), ) diff --git a/packages/syncing-server/src/Bootstrap/DataSource.ts b/packages/syncing-server/src/Bootstrap/DataSource.ts index acaf07252..1a3d95df3 100644 --- a/packages/syncing-server/src/Bootstrap/DataSource.ts +++ b/packages/syncing-server/src/Bootstrap/DataSource.ts @@ -4,6 +4,8 @@ import { Env } from './Env' import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions' import { TypeORMItem } from '../Infra/TypeORM/TypeORMItem' import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification' +import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation' +import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation' export class AppDataSource { private _dataSource: DataSource | undefined @@ -33,7 +35,7 @@ export class AppDataSource { const commonDataSourceOptions = { maxQueryExecutionTime, - entities: [TypeORMItem, TypeORMNotification], + entities: [TypeORMItem, TypeORMNotification, TypeORMSharedVaultAssociation, TypeORMKeySystemAssociation], migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`], migrationsRun: true, logging: this.env.get('DB_DEBUG_LEVEL', true) ?? 'info', diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index 603917b0c..545ec9fe6 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -8,8 +8,12 @@ const TYPES = { Sync_Env: Symbol.for('Sync_Env'), // Repositories Sync_ItemRepository: Symbol.for('Sync_ItemRepository'), + Sync_KeySystemAssociationRepository: Symbol.for('Sync_KeySystemAssociationRepository'), + Sync_SharedVaultAssociationRepository: Symbol.for('Sync_SharedVaultAssociationRepository'), // ORM Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'), + Sync_ORMSharedVaultAssociationRepository: Symbol.for('Sync_ORMSharedVaultAssociationRepository'), + Sync_ORMKeySystemAssociationRepository: Symbol.for('Sync_ORMKeySystemAssociationRepository'), // Middleware Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'), // env vars @@ -95,6 +99,8 @@ const TYPES = { Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'), Sync_ItemConflictHttpMapper: Symbol.for('Sync_ItemConflictHttpMapper'), Sync_ItemBackupMapper: Symbol.for('Sync_ItemBackupMapper'), + Sync_KeySystemAssociationPersistenceMapper: Symbol.for('Sync_KeySystemAssociationPersistenceMapper'), + Sync_SharedVaultAssociationPersistenceMapper: Symbol.for('Sync_SharedVaultAssociationPersistenceMapper'), } export default TYPES diff --git a/packages/syncing-server/src/Domain/Item/Item.ts b/packages/syncing-server/src/Domain/Item/Item.ts index 26df940b3..ce1d28af9 100644 --- a/packages/syncing-server/src/Domain/Item/Item.ts +++ b/packages/syncing-server/src/Domain/Item/Item.ts @@ -1,8 +1,8 @@ -import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' +import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core' import { ItemProps } from './ItemProps' -export class Item extends Entity { +export class Item extends Aggregate { private constructor(props: ItemProps, id?: UniqueEntityId) { super(props, id) } diff --git a/packages/syncing-server/src/Domain/Item/ItemHash.ts b/packages/syncing-server/src/Domain/Item/ItemHash.ts index d8d886d0c..664ada970 100644 --- a/packages/syncing-server/src/Domain/Item/ItemHash.ts +++ b/packages/syncing-server/src/Domain/Item/ItemHash.ts @@ -10,4 +10,12 @@ export class ItemHash extends ValueObject { static create(props: ItemHashProps): Result { return Result.ok(new ItemHash(props)) } + + representsASharedVaultItem(): boolean { + return this.props.shared_vault_uuid !== null + } + + hasDedicatedKeySystemAssociation(): boolean { + return this.props.key_system_identifier !== null + } } diff --git a/packages/syncing-server/src/Domain/Item/ItemProps.ts b/packages/syncing-server/src/Domain/Item/ItemProps.ts index 1601f23ca..636369a41 100644 --- a/packages/syncing-server/src/Domain/Item/ItemProps.ts +++ b/packages/syncing-server/src/Domain/Item/ItemProps.ts @@ -1,5 +1,8 @@ import { ContentType, Dates, Timestamps, Uuid } from '@standardnotes/domain-core' +import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation' +import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation' + export interface ItemProps { duplicateOf: Uuid | null itemsKeyId: string | null @@ -13,4 +16,6 @@ export interface ItemProps { dates: Dates timestamps: Timestamps contentSize?: number + sharedVaultAssociation?: SharedVaultAssociation + keySystemAssociation?: KeySystemAssociation } diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts new file mode 100644 index 000000000..4920d89d2 --- /dev/null +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssocationProps.ts @@ -0,0 +1,7 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' + +export interface KeySystemAssociationProps { + itemUuid: Uuid + keySystemUuid: Uuid + timestamps: Timestamps +} diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts new file mode 100644 index 000000000..5d05216f0 --- /dev/null +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts @@ -0,0 +1,16 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' + +import { KeySystemAssociation } from './KeySystemAssociation' + +describe('KeySystemAssociation', () => { + it('should create an entity', () => { + 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(), + }) + + expect(entityOrError.isFailed()).toBeFalsy() + expect(entityOrError.getValue().id).not.toBeNull() + }) +}) diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.ts new file mode 100644 index 000000000..b8f7f2015 --- /dev/null +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.ts @@ -0,0 +1,13 @@ +import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' + +import { KeySystemAssociationProps } from './KeySystemAssocationProps' + +export class KeySystemAssociation extends Entity { + private constructor(props: KeySystemAssociationProps, id?: UniqueEntityId) { + super(props, id) + } + + static create(props: KeySystemAssociationProps, id?: UniqueEntityId): Result { + return Result.ok(new KeySystemAssociation(props, id)) + } +} diff --git a/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociationRepositoryInterface.ts b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociationRepositoryInterface.ts new file mode 100644 index 000000000..4e5be90ae --- /dev/null +++ b/packages/syncing-server/src/Domain/KeySystem/KeySystemAssociationRepositoryInterface.ts @@ -0,0 +1,6 @@ +import { KeySystemAssociation } from './KeySystemAssociation' + +export interface KeySystemAssociationRepositoryInterface { + save(keySystem: KeySystemAssociation): Promise + remove(keySystem: KeySystemAssociation): Promise +} diff --git a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.spec.ts b/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.spec.ts deleted file mode 100644 index 60315022f..000000000 --- a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core' - -import { SharedVaultItem } from './SharedVaultItem' - -describe('SharedVaultItem', () => { - it('should create an entity', () => { - const entityOrError = SharedVaultItem.create({ - itemId: new UniqueEntityId('00000000-0000-0000-0000-000000000000'), - sharedVaultId: new UniqueEntityId('00000000-0000-0000-0000-000000000000'), - keySystemIdentifier: 'key-system-identifier', - lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), - timestamps: Timestamps.create(123456789, 123456789).getValue(), - }) - - expect(entityOrError.isFailed()).toBeFalsy() - expect(entityOrError.getValue().id).not.toBeNull() - }) -}) diff --git a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.ts b/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.ts deleted file mode 100644 index 95b03e851..000000000 --- a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItem.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' - -import { SharedVaultItemProps } from './SharedVaultItemProps' - -export class SharedVaultItem extends Entity { - private constructor(props: SharedVaultItemProps, id?: UniqueEntityId) { - super(props, id) - } - - static create(props: SharedVaultItemProps, id?: UniqueEntityId): Result { - return Result.ok(new SharedVaultItem(props, id)) - } -} diff --git a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemProps.ts b/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemProps.ts deleted file mode 100644 index 566dd2849..000000000 --- a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemProps.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core' - -export interface SharedVaultItemProps { - sharedVaultId: UniqueEntityId - itemId: UniqueEntityId - keySystemIdentifier: string - lastEditedBy: Uuid - timestamps: Timestamps -} diff --git a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemRepositoryInterface.ts b/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemRepositoryInterface.ts deleted file mode 100644 index 4b83e5f42..000000000 --- a/packages/syncing-server/src/Domain/SharedVault/Item/SharedVaultItemRepositoryInterface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UniqueEntityId } from '@standardnotes/domain-core' - -import { SharedVaultItem } from './SharedVaultItem' - -export interface SharedVaultItemRepositoryInterface { - save(sharedVaultItem: SharedVaultItem): Promise - findBySharedVaultId(sharedVaultId: UniqueEntityId): Promise -} diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts index 91a38a481..11bb99707 100644 --- a/packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts @@ -3,13 +3,12 @@ import { Timestamps, Uuid } from '@standardnotes/domain-core' import { SharedVault } from './SharedVault' describe('SharedVault', () => { - it('should create an aggregate', () => { + it('should create an entity', () => { const entityOrError = SharedVault.create({ fileUploadBytesLimit: 1_000_000, fileUploadBytesUsed: 0, timestamps: Timestamps.create(123456789, 123456789).getValue(), userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), - sharedVaultItems: [], }) expect(entityOrError.isFailed()).toBeFalsy() diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVault.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVault.ts index e26f188f1..a4a91545f 100644 --- a/packages/syncing-server/src/Domain/SharedVault/SharedVault.ts +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVault.ts @@ -1,8 +1,8 @@ -import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core' +import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' import { SharedVaultProps } from './SharedVaultProps' -export class SharedVault extends Aggregate { +export class SharedVault extends Entity { private constructor(props: SharedVaultProps, id?: UniqueEntityId) { super(props, id) } diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.spec.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.spec.ts new file mode 100644 index 000000000..3cef050f3 --- /dev/null +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.spec.ts @@ -0,0 +1,17 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' + +import { SharedVaultAssociation } from './SharedVaultAssociation' + +describe('SharedVaultAssociation', () => { + it('should create an entity', () => { + const entityOrError = SharedVaultAssociation.create({ + timestamps: Timestamps.create(123456789, 123456789).getValue(), + itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + }) + + expect(entityOrError.isFailed()).toBeFalsy() + expect(entityOrError.getValue().id).not.toBeNull() + }) +}) diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.ts new file mode 100644 index 000000000..b7ad1064b --- /dev/null +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.ts @@ -0,0 +1,13 @@ +import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core' + +import { SharedVaultAssociationProps } from './SharedVaultAssociationProps' + +export class SharedVaultAssociation extends Aggregate { + private constructor(props: SharedVaultAssociationProps, id?: UniqueEntityId) { + super(props, id) + } + + static create(props: SharedVaultAssociationProps, id?: UniqueEntityId): Result { + return Result.ok(new SharedVaultAssociation(props, id)) + } +} diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationProps.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationProps.ts new file mode 100644 index 000000000..3086af142 --- /dev/null +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationProps.ts @@ -0,0 +1,8 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' + +export interface SharedVaultAssociationProps { + lastEditedBy: Uuid + sharedVaultUuid: Uuid + itemUuid: Uuid + timestamps: Timestamps +} diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationRepositoryInterface.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationRepositoryInterface.ts new file mode 100644 index 000000000..562d2977a --- /dev/null +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociationRepositoryInterface.ts @@ -0,0 +1,6 @@ +import { SharedVaultAssociation } from './SharedVaultAssociation' + +export interface SharedVaultAssociationRepositoryInterface { + save(sharedVaultAssociation: SharedVaultAssociation): Promise + remove(sharedVaultAssociation: SharedVaultAssociation): Promise +} diff --git a/packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts b/packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts index 7d9882b68..678f1146c 100644 --- a/packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts +++ b/packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts @@ -1,11 +1,8 @@ import { Uuid, Timestamps } from '@standardnotes/domain-core' -import { SharedVaultItem } from './Item/SharedVaultItem' - export interface SharedVaultProps { userUuid: Uuid fileUploadBytesUsed: number fileUploadBytesLimit: number timestamps: Timestamps - sharedVaultItems: SharedVaultItem[] } diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts index 3b9eae5d2..ec09ea17a 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts @@ -32,7 +32,6 @@ export class CreateSharedVault implements UseCaseInterface { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository = {} as jest.Mocked diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts index 470cba766..bef949e37 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts @@ -31,7 +31,6 @@ describe('DeleteSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository = {} as jest.Mocked sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) @@ -116,7 +115,6 @@ describe('DeleteSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) const useCase = createUseCase() diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts index 02b9389db..896cc3f89 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts @@ -20,7 +20,6 @@ describe('GetSharedVaultUsers', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultUser = SharedVaultUser.create({ @@ -67,7 +66,6 @@ describe('GetSharedVaultUsers', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts index 9c85ef460..053d590ed 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts @@ -29,7 +29,6 @@ describe('GetSharedVaults', () => { timestamps: Timestamps.create(123, 123).getValue(), fileUploadBytesLimit: 123, fileUploadBytesUsed: 123, - sharedVaultItems: [], }).getValue() sharedVaultRepository = {} as jest.Mocked sharedVaultRepository.findByUuids = jest.fn().mockResolvedValue([sharedVault]) diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts index 6d95c5b33..610b3c76f 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts @@ -21,7 +21,6 @@ describe('InviteUserToSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository = {} as jest.Mocked sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) @@ -153,7 +152,6 @@ describe('InviteUserToSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('10000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts index 7a5e99577..03c4d7e89 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts @@ -24,7 +24,6 @@ describe('RemoveUserFromSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository = {} as jest.Mocked sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) @@ -89,7 +88,6 @@ describe('RemoveUserFromSharedVault', () => { fileUploadBytesUsed: 2, userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(), timestamps: Timestamps.create(123, 123).getValue(), - sharedVaultItems: [], }).getValue() sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) 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 9dc48394d..937fbff73 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 @@ -6,6 +6,8 @@ import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryIn import { ItemHash } from '../../../Item/ItemHash' import { ContentType, Dates, Result, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core' import { Item } from '../../../Item/Item' +import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation' +import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation' describe('SaveNewItem', () => { let itemRepository: ItemRepositoryInterface @@ -38,7 +40,7 @@ describe('SaveNewItem', () => { ).getValue() itemHash1 = ItemHash.create({ - uuid: '1-2-3', + uuid: '00000000-0000-0000-0000-000000000000', user_uuid: '00000000-0000-0000-0000-000000000000', key_system_identifier: null, shared_vault_uuid: null, @@ -282,4 +284,149 @@ describe('SaveNewItem', () => { mock.mockRestore() }) + + it('returns a failure if the item hash has an invalid uuid', async () => { + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + uuid: '1-2-3', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-00000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeTruthy() + }) + + describe('when item hash represents a shared vault item', () => { + it('returns a failure if the shared vault uuid is invalid', async () => { + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + shared_vault_uuid: '1-2-3', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeTruthy() + }) + + it('should create a shared vault association between the item and the shared vault', async () => { + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + shared_vault_uuid: '00000000-0000-0000-0000-000000000001', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeFalsy() + expect(result.getValue().props.sharedVaultAssociation?.props.lastEditedBy.value).toEqual( + '00000000-0000-0000-0000-000000000000', + ) + expect(itemRepository.save).toHaveBeenCalled() + }) + + it('should return a failure if it fails to create a shared vault association', async () => { + const mock = jest.spyOn(SharedVaultAssociation, 'create') + mock.mockImplementation(() => { + return Result.fail('Oops') + }) + + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + shared_vault_uuid: '00000000-0000-0000-0000-000000000001', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeTruthy() + + mock.mockRestore() + }) + }) + + describe('when item hash has a dedicated key system', () => { + it('should create a key system for the item if the item hash has information about a key system used for encryption', async () => { + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + key_system_identifier: '00000000-0000-0000-0000-000000000001', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeFalsy() + expect(result.getValue().props.keySystemAssociation?.props.itemUuid.value).toEqual( + '00000000-0000-0000-0000-000000000000', + ) + expect(itemRepository.save).toHaveBeenCalled() + }) + + it('should return a failure if the item hash has an invalid key system uuid', async () => { + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + key_system_identifier: '1-2-3', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeTruthy() + }) + + it('should return a failure if it fails to create a key system', async () => { + const mock = jest.spyOn(KeySystemAssociation, 'create') + mock.mockImplementation(() => { + return Result.fail('Oops') + }) + + const useCase = createUseCase() + + itemHash1 = ItemHash.create({ + ...itemHash1.props, + key_system_identifier: '00000000-0000-0000-0000-000000000001', + }).getValue() + + const result = await useCase.execute({ + userUuid: '00000000-0000-0000-0000-000000000000', + sessionUuid: '00000000-0000-0000-0000-000000000001', + itemHash: itemHash1, + }) + + expect(result.isFailed()).toBeTruthy() + + mock.mockRestore() + }) + }) }) 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 c7dff06e5..cc8f6aa4c 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts @@ -14,6 +14,8 @@ import { Item } from '../../../Item/Item' import { SaveNewItemDTO } from './SaveNewItemDTO' import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface' import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface' +import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation' +import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation' export class SaveNewItem implements UseCaseInterface { constructor( @@ -24,6 +26,12 @@ export class SaveNewItem implements UseCaseInterface { ) {} async execute(dto: SaveNewItemDTO): Promise> { + const uuidOrError = Uuid.create(dto.itemHash.props.uuid) + if (uuidOrError.isFailed()) { + return Result.fail(uuidOrError.getError()) + } + const uuid = uuidOrError.getValue() + let updatedWithSession = null if (dto.sessionUuid) { const sessionUuidOrError = Uuid.create(dto.sessionUuid) @@ -78,6 +86,51 @@ export class SaveNewItem implements UseCaseInterface { } const timestamps = timestampsOrError.getValue() + let sharedVaultAssociation = undefined + if (dto.itemHash.representsASharedVaultItem()) { + const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string) + if (sharedVaultUuidOrError.isFailed()) { + return Result.fail(sharedVaultUuidOrError.getError()) + } + const sharedVaultUuid = sharedVaultUuidOrError.getValue() + + const sharedVaultAssociationOrError = SharedVaultAssociation.create({ + lastEditedBy: userUuid, + sharedVaultUuid, + timestamps: Timestamps.create( + this.timer.getTimestampInMicroseconds(), + this.timer.getTimestampInMicroseconds(), + ).getValue(), + itemUuid: uuid, + }) + if (sharedVaultAssociationOrError.isFailed()) { + return Result.fail(sharedVaultAssociationOrError.getError()) + } + sharedVaultAssociation = sharedVaultAssociationOrError.getValue() + } + + 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 keySystemUuid = keySystemUuidOrError.getValue() + + const keySystemAssociationOrError = KeySystemAssociation.create({ + itemUuid: uuid, + timestamps: Timestamps.create( + this.timer.getTimestampInMicroseconds(), + this.timer.getTimestampInMicroseconds(), + ).getValue(), + keySystemUuid, + }) + if (keySystemAssociationOrError.isFailed()) { + return Result.fail(keySystemAssociationOrError.getError()) + } + keySystemAssociation = keySystemAssociationOrError.getValue() + } + const itemOrError = Item.create( { updatedWithSession, @@ -91,8 +144,10 @@ export class SaveNewItem implements UseCaseInterface { deleted: dto.itemHash.props.deleted ?? false, dates, timestamps, + keySystemAssociation, + sharedVaultAssociation, }, - new UniqueEntityId(dto.itemHash.props.uuid), + new UniqueEntityId(uuid.value), ) if (itemOrError.isFailed()) { return Result.fail(itemOrError.getError()) diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts index 44c7caec0..f736b110e 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts @@ -7,17 +7,40 @@ import { ItemQuery } from '../../Domain/Item/ItemQuery' import { ItemRepositoryInterface } from '../../Domain/Item/ItemRepositoryInterface' import { ExtendedIntegrityPayload } from '../../Domain/Item/ExtendedIntegrityPayload' import { TypeORMItem } from './TypeORMItem' +import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface' +import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface' export class TypeORMItemRepository implements ItemRepositoryInterface { - constructor(private ormRepository: Repository, private mapper: MapperInterface) {} + constructor( + private ormRepository: Repository, + private mapper: MapperInterface, + private keySystemAssociationRepository: KeySystemAssociationRepositoryInterface, + private sharedVaultAssociationRepository: SharedVaultAssociationRepositoryInterface, + ) {} async save(item: Item): Promise { const persistence = this.mapper.toProjection(item) await this.ormRepository.save(persistence) + + if (item.props.sharedVaultAssociation) { + await this.sharedVaultAssociationRepository.save(item.props.sharedVaultAssociation) + } + + if (item.props.keySystemAssociation) { + await this.keySystemAssociationRepository.save(item.props.keySystemAssociation) + } } async remove(item: Item): Promise { + if (item.props.keySystemAssociation) { + await this.keySystemAssociationRepository.remove(item.props.keySystemAssociation) + } + + if (item.props.sharedVaultAssociation) { + await this.sharedVaultAssociationRepository.remove(item.props.sharedVaultAssociation) + } + await this.ormRepository.remove(this.mapper.toProjection(item)) } diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts new file mode 100644 index 000000000..a87ff8c5b --- /dev/null +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts @@ -0,0 +1,33 @@ +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm' + +@Entity({ name: 'key_system_associations' }) +export class TypeORMKeySystemAssociation { + @PrimaryGeneratedColumn('uuid') + declare uuid: string + + @Column({ + name: 'key_system_uuid', + length: 36, + }) + @Index('key_system_uuid_on_key_system_associations') + declare keySystemUuid: string + + @Column({ + name: 'item_uuid', + length: 36, + }) + @Index('item_uuid_on_key_system_associations') + declare itemUuid: string + + @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/TypeORMKeySystemAssociationRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociationRepository.ts new file mode 100644 index 000000000..ea205ca01 --- /dev/null +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociationRepository.ts @@ -0,0 +1,21 @@ +import { Repository } from 'typeorm' +import { MapperInterface } from '@standardnotes/domain-core' + +import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation' +import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface' +import { TypeORMKeySystemAssociation } from './TypeORMKeySystemAssociation' + +export class TypeORMKeySystemAssociationRepository implements KeySystemAssociationRepositoryInterface { + constructor( + private ormRepository: Repository, + private mapper: MapperInterface, + ) {} + + async save(keySystemAssociation: KeySystemAssociation): Promise { + await this.ormRepository.save(this.mapper.toProjection(keySystemAssociation)) + } + + async remove(keySystemAssociation: KeySystemAssociation): Promise { + await this.ormRepository.remove(this.mapper.toProjection(keySystemAssociation)) + } +} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItem.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociation.ts similarity index 67% rename from packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItem.ts rename to packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociation.ts index 92ab74595..d9f3b34a6 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItem.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociation.ts @@ -1,7 +1,7 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm' -@Entity({ name: 'shared_vault_items' }) -export class TypeORMSharedVaultItem { +@Entity({ name: 'shared_vault_associations' }) +export class TypeORMSharedVaultAssociation { @PrimaryGeneratedColumn('uuid') declare uuid: string @@ -9,21 +9,16 @@ export class TypeORMSharedVaultItem { name: 'shared_vault_uuid', length: 36, }) + @Index('shared_vault_uuid_on_shared_vault_associations') declare sharedVaultUuid: string @Column({ name: 'item_uuid', length: 36, }) + @Index('item_uuid_on_shared_vault_associations') declare itemUuid: string - @Column({ - name: 'key_system_identifier', - type: 'varchar', - length: 36, - }) - declare keySystemIdentifier: string - @Column({ name: 'last_edited_by', type: 'varchar', diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociationRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociationRepository.ts new file mode 100644 index 000000000..b0b831e28 --- /dev/null +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociationRepository.ts @@ -0,0 +1,21 @@ +import { Repository } from 'typeorm' +import { MapperInterface } from '@standardnotes/domain-core' + +import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation' +import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface' +import { TypeORMSharedVaultAssociation } from './TypeORMSharedVaultAssociation' + +export class TypeORMSharedVaultAssociationRepository implements SharedVaultAssociationRepositoryInterface { + constructor( + private ormRepository: Repository, + private mapper: MapperInterface, + ) {} + + async save(sharedVaultAssociation: SharedVaultAssociation): Promise { + await this.ormRepository.save(this.mapper.toProjection(sharedVaultAssociation)) + } + + async remove(sharedVaultAssociation: SharedVaultAssociation): Promise { + await this.ormRepository.remove(this.mapper.toProjection(sharedVaultAssociation)) + } +} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItemRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItemRepository.ts deleted file mode 100644 index 0348bf474..000000000 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultItemRepository.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Repository } from 'typeorm' -import { MapperInterface, UniqueEntityId } from '@standardnotes/domain-core' - -import { SharedVaultItem } from '../../Domain/SharedVault/Item/SharedVaultItem' -import { SharedVaultItemRepositoryInterface } from '../../Domain/SharedVault/Item/SharedVaultItemRepositoryInterface' -import { TypeORMSharedVaultItem } from './TypeORMSharedVaultItem' - -export class TypeORMSharedVaultItemRepository implements SharedVaultItemRepositoryInterface { - constructor( - private ormRepository: Repository, - private mapper: MapperInterface, - ) {} - - async findBySharedVaultId(sharedVaultId: UniqueEntityId): Promise { - const persistence = await this.ormRepository - .createQueryBuilder('shared_vault_item') - .where('shared_vault_item.shared_vault_uuid = :sharedVaultUuid', { - sharedVaultUuid: sharedVaultId.toString(), - }) - .getMany() - - return persistence.map((p) => this.mapper.toDomain(p)) - } - - async save(sharedVaultItem: SharedVaultItem): Promise { - const persistence = this.mapper.toProjection(sharedVaultItem) - - await this.ormRepository.save(persistence) - } -} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts index 4fcb77dc5..667c7c0f3 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts @@ -4,12 +4,10 @@ import { MapperInterface, Uuid } from '@standardnotes/domain-core' import { SharedVaultRepositoryInterface } from '../../Domain/SharedVault/SharedVaultRepositoryInterface' import { TypeORMSharedVault } from './TypeORMSharedVault' import { SharedVault } from '../../Domain/SharedVault/SharedVault' -import { SharedVaultItemRepositoryInterface } from '../../Domain/SharedVault/Item/SharedVaultItemRepositoryInterface' export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterface { constructor( private ormRepository: Repository, - private sharedVaultItemRepository: SharedVaultItemRepositoryInterface, private mapper: MapperInterface, ) {} @@ -28,10 +26,6 @@ export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterf } async save(sharedVault: SharedVault): Promise { - for (const item of sharedVault.props.sharedVaultItems) { - await this.sharedVaultItemRepository.save(item) - } - const persistence = this.mapper.toProjection(sharedVault) await this.ormRepository.save(persistence) diff --git a/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts b/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts index b015d7fcc..79ae90a75 100644 --- a/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts +++ b/packages/syncing-server/src/Mapping/Http/ItemHttpMapper.ts @@ -26,6 +26,16 @@ export class ItemHttpMapper implements MapperInterface +{ + toDomain(projection: TypeORMKeySystemAssociation): KeySystemAssociation { + const itemUuidOrError = Uuid.create(projection.itemUuid) + if (itemUuidOrError.isFailed()) { + throw new Error(`Failed to create key system from projection: ${itemUuidOrError.getError()}`) + } + 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 keySystemUuid = keySystemUuidOrError.getValue() + + const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp) + if (timestampsOrError.isFailed()) { + throw new Error(`Failed to create key system from projection: ${timestampsOrError.getError()}`) + } + const timestamps = timestampsOrError.getValue() + + const keySystemOrError = KeySystemAssociation.create( + { + itemUuid, + timestamps, + keySystemUuid, + }, + new UniqueEntityId(projection.uuid), + ) + if (keySystemOrError.isFailed()) { + throw new Error(`Failed to create key system from projection: ${keySystemOrError.getError()}`) + } + + const keySystem = keySystemOrError.getValue() + + return keySystem + } + + toProjection(domain: KeySystemAssociation): TypeORMKeySystemAssociation { + const typeorm = new TypeORMKeySystemAssociation() + + typeorm.uuid = domain.id.toString() + typeorm.itemUuid = domain.props.itemUuid.value + typeorm.keySystemUuid = domain.props.keySystemUuid.value + typeorm.createdAtTimestamp = domain.props.timestamps.createdAt + typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt + + return typeorm + } +} diff --git a/packages/syncing-server/src/Mapping/Persistence/SharedVaultAssociationPersistenceMapper.ts b/packages/syncing-server/src/Mapping/Persistence/SharedVaultAssociationPersistenceMapper.ts new file mode 100644 index 000000000..acada738f --- /dev/null +++ b/packages/syncing-server/src/Mapping/Persistence/SharedVaultAssociationPersistenceMapper.ts @@ -0,0 +1,65 @@ +import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core' + +import { TypeORMSharedVaultAssociation } from '../../Infra/TypeORM/TypeORMSharedVaultAssociation' +import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation' + +export class SharedVaultAssociationPersistenceMapper + implements MapperInterface +{ + toDomain(projection: TypeORMSharedVaultAssociation): SharedVaultAssociation { + const itemUuidOrError = Uuid.create(projection.itemUuid) + if (itemUuidOrError.isFailed()) { + throw new Error(`Failed to create shared vault association from projection: ${itemUuidOrError.getError()}`) + } + const itemUuid = itemUuidOrError.getValue() + + const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid) + if (sharedVaultUuidOrError.isFailed()) { + throw new Error(`Failed to create shared vault association from projection: ${sharedVaultUuidOrError.getError()}`) + } + const sharedVaultUuid = sharedVaultUuidOrError.getValue() + + const lastEditedByOrError = Uuid.create(projection.lastEditedBy) + if (lastEditedByOrError.isFailed()) { + throw new Error(`Failed to create shared vault association from projection: ${lastEditedByOrError.getError()}`) + } + const lastEditedBy = lastEditedByOrError.getValue() + + const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp) + if (timestampsOrError.isFailed()) { + throw new Error(`Failed to create shared vault association from projection: ${timestampsOrError.getError()}`) + } + const timestamps = timestampsOrError.getValue() + + const sharedVaultAssociationOrError = SharedVaultAssociation.create( + { + itemUuid, + lastEditedBy, + sharedVaultUuid, + timestamps, + }, + new UniqueEntityId(projection.uuid), + ) + if (sharedVaultAssociationOrError.isFailed()) { + throw new Error( + `Failed to create shared vault association from projection: ${sharedVaultAssociationOrError.getError()}`, + ) + } + const sharedVaultAssociation = sharedVaultAssociationOrError.getValue() + + return sharedVaultAssociation + } + + toProjection(domain: SharedVaultAssociation): TypeORMSharedVaultAssociation { + const typeorm = new TypeORMSharedVaultAssociation() + + typeorm.uuid = domain.id.toString() + typeorm.sharedVaultUuid = domain.props.sharedVaultUuid.value + typeorm.itemUuid = domain.props.itemUuid.value + typeorm.lastEditedBy = domain.props.lastEditedBy.value + typeorm.createdAtTimestamp = domain.props.timestamps.createdAt + typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt + + return typeorm + } +} diff --git a/packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts b/packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts index 064e0a104..eced5d7c4 100644 --- a/packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts +++ b/packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts @@ -23,7 +23,6 @@ export class SharedVaultPersistenceMapper implements MapperInterface