Compare commits

..

9 Commits

Author SHA1 Message Date
standardci
02639cddb2 chore(release): publish new version
- @standardnotes/analytics@2.11.15
 - @standardnotes/api-gateway@1.39.6
 - @standardnotes/auth-server@1.60.15
 - @standardnotes/domain-events-infra@1.9.36
 - @standardnotes/domain-events@2.92.0
 - @standardnotes/event-store@1.6.32
 - @standardnotes/files-server@1.8.32
 - @standardnotes/revisions-server@1.9.0
 - @standardnotes/scheduler-server@1.13.33
 - @standardnotes/syncing-server@1.19.1
 - @standardnotes/websockets-server@1.4.33
 - @standardnotes/workspace-server@1.17.32
2022-11-30 12:46:26 +00:00
Karol Sójko
0f67aa4058 feat(revisions): add updating user uuid on revisions in async processing 2022-11-30 13:44:01 +01:00
standardci
78c3403d5f chore(release): publish new version
- @standardnotes/revisions-server@1.8.2
2022-11-29 15:30:07 +00:00
Karol Sójko
fc8f8c574d fix(revisions): make user uuid nullable 2022-11-29 16:27:39 +01:00
standardci
3972ee580d chore(release): publish new version
- @standardnotes/revisions-server@1.8.1
2022-11-29 09:11:04 +00:00
Karol Sójko
b0a994d5be fix(revisions): mysql queries 2022-11-29 10:08:35 +01:00
standardci
80df28a0c4 chore(release): publish new version
- @standardnotes/analytics@2.11.14
 - @standardnotes/api-gateway@1.39.5
 - @standardnotes/auth-server@1.60.14
 - @standardnotes/domain-events-infra@1.9.35
 - @standardnotes/domain-events@2.91.0
 - @standardnotes/event-store@1.6.31
 - @standardnotes/files-server@1.8.31
 - @standardnotes/revisions-server@1.8.0
 - @standardnotes/scheduler-server@1.13.32
 - @standardnotes/syncing-server@1.19.0
 - @standardnotes/websockets-server@1.4.32
 - @standardnotes/workspace-server@1.17.31
2022-11-28 14:08:28 +00:00
Karol Sójko
1c6c6a9296 fix(revisions): binding for revisions copy request handler 2022-11-28 15:06:26 +01:00
Karol Sójko
7bb698e442 feat(revisions): add copying revisions on duplicated items 2022-11-28 15:04:33 +01:00
49 changed files with 536 additions and 29 deletions

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.13...@standardnotes/analytics@2.11.14) (2022-11-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.12...@standardnotes/analytics@2.11.13) (2022-11-25)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.11.13",
"version": "2.11.15",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.5...@standardnotes/api-gateway@1.39.6) (2022-11-30)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.4...@standardnotes/api-gateway@1.39.5) (2022-11-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.3...@standardnotes/api-gateway@1.39.4) (2022-11-25)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.39.4",
"version": "1.39.6",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.60.15](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.14...@standardnotes/auth-server@1.60.15) (2022-11-30)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.13...@standardnotes/auth-server@1.60.14) (2022-11-28)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.12...@standardnotes/auth-server@1.60.13) (2022-11-25)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.60.13",
"version": "1.60.15",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.36](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.35...@standardnotes/domain-events-infra@1.9.36) (2022-11-30)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.35](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.34...@standardnotes/domain-events-infra@1.9.35) (2022-11-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.33...@standardnotes/domain-events-infra@1.9.34) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.9.34",
"version": "1.9.36",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.92.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.91.0...@standardnotes/domain-events@2.92.0) (2022-11-30)
### Features
* **revisions:** add updating user uuid on revisions in async processing ([0f67aa4](https://github.com/standardnotes/server/commit/0f67aa4058301dfa92794b90a842966173f71b95))
# [2.91.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.90.2...@standardnotes/domain-events@2.91.0) (2022-11-28)
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/server/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [2.90.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.90.1...@standardnotes/domain-events@2.90.2) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-events

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.90.2",
"version": "2.92.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsCopyRequestedEventPayload } from './RevisionsCopyRequestedEventPayload'
export interface RevisionsCopyRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_COPY_REQUESTED'
payload: RevisionsCopyRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface RevisionsCopyRequestedEventPayload {
newItemUuid: string
originalItemUuid: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsOwnershipUpdateRequestedEventPayload } from './RevisionsOwnershipUpdateRequestedEventPayload'
export interface RevisionsOwnershipUpdateRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED'
payload: RevisionsOwnershipUpdateRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface RevisionsOwnershipUpdateRequestedEventPayload {
itemUuid: string
userUuid: string
}

View File

@@ -74,6 +74,10 @@ export * from './Event/RefundRequestedEvent'
export * from './Event/RefundRequestedEventPayload'
export * from './Event/RefundProcessedEvent'
export * from './Event/RefundProcessedEventPayload'
export * from './Event/RevisionsCopyRequestedEvent'
export * from './Event/RevisionsCopyRequestedEventPayload'
export * from './Event/RevisionsOwnershipUpdateRequestedEvent'
export * from './Event/RevisionsOwnershipUpdateRequestedEventPayload'
export * from './Event/SharedSubscriptionInvitationCanceledEvent'
export * from './Event/SharedSubscriptionInvitationCanceledEventPayload'
export * from './Event/SharedSubscriptionInvitationCreatedEvent'

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.32](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.31...@standardnotes/event-store@1.6.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.31](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.30...@standardnotes/event-store@1.6.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.30](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.29...@standardnotes/event-store@1.6.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.6.30",
"version": "1.6.32",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.32](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.31...@standardnotes/files-server@1.8.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.31](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.30...@standardnotes/files-server@1.8.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.30](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.29...@standardnotes/files-server@1.8.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.8.30",
"version": "1.8.32",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.2...@standardnotes/revisions-server@1.9.0) (2022-11-30)
### Features
* **revisions:** add updating user uuid on revisions in async processing ([0f67aa4](https://github.com/standardnotes/server/commit/0f67aa4058301dfa92794b90a842966173f71b95))
## [1.8.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.1...@standardnotes/revisions-server@1.8.2) (2022-11-29)
### Bug Fixes
* **revisions:** make user uuid nullable ([fc8f8c5](https://github.com/standardnotes/server/commit/fc8f8c574dbc8901cc4a12986154841d39abcc9b))
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.0...@standardnotes/revisions-server@1.8.1) (2022-11-29)
### Bug Fixes
* **revisions:** mysql queries ([b0a994d](https://github.com/standardnotes/server/commit/b0a994d5be3b35f054a14a6e9661232090ec11e5))
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.7.1...@standardnotes/revisions-server@1.8.0) (2022-11-28)
### Bug Fixes
* **revisions:** binding for revisions copy request handler ([1c6c6a9](https://github.com/standardnotes/server/commit/1c6c6a9296d91c35699a15b2cb4182e26233eeb2))
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/server/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [1.7.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.7.0...@standardnotes/revisions-server@1.7.1) (2022-11-28)
### Bug Fixes

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class makeUserUuidNullable1669735585016 implements MigrationInterface {
name = 'makeUserUuidNullable1669735585016'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.7.1",
"version": "1.9.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -39,6 +39,9 @@ import { FSDumpRepository } from '../Infra/FS/FSDumpRepository'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
import { RevisionsCopyRequestedEventHandler } from '../Domain/Handler/RevisionsCopyRequestedEventHandler'
import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions'
import { RevisionsOwnershipUpdateRequestedEventHandler } from '../Domain/Handler/RevisionsOwnershipUpdateRequestedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -161,6 +164,9 @@ export class ContainerConfigLoader {
container
.bind<DeleteRevision>(TYPES.DeleteRevision)
.toConstantValue(new DeleteRevision(container.get(TYPES.RevisionRepository)))
container
.bind<CopyRevisions>(TYPES.CopyRevisions)
.toConstantValue(new CopyRevisions(container.get(TYPES.RevisionRepository)))
// Controller
container
@@ -185,6 +191,14 @@ export class ContainerConfigLoader {
.toConstantValue(
new AccountDeletionRequestedEventHandler(container.get(TYPES.RevisionRepository), container.get(TYPES.Logger)),
)
container
.bind<RevisionsCopyRequestedEventHandler>(TYPES.RevisionsCopyRequestedEventHandler)
.toConstantValue(
new RevisionsCopyRequestedEventHandler(container.get(TYPES.CopyRevisions), container.get(TYPES.Logger)),
)
container
.bind<RevisionsOwnershipUpdateRequestedEventHandler>(TYPES.RevisionsOwnershipUpdateRequestedEventHandler)
.toConstantValue(new RevisionsOwnershipUpdateRequestedEventHandler(container.get(TYPES.RevisionRepository)))
// Services
container
@@ -199,6 +213,8 @@ export class ContainerConfigLoader {
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ITEM_DUMPED', container.get(TYPES.ItemDumpedEventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
['REVISIONS_COPY_REQUESTED', container.get(TYPES.RevisionsCopyRequestedEventHandler)],
['REVISIONS_OWNERSHIP_UPDATE_REQUESTED', container.get(TYPES.RevisionsOwnershipUpdateRequestedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {

View File

@@ -27,11 +27,14 @@ const TYPES = {
GetRevisionsMetada: Symbol.for('GetRevisionsMetada'),
GetRevision: Symbol.for('GetRevision'),
DeleteRevision: Symbol.for('DeleteRevision'),
CopyRevisions: Symbol.for('CopyRevisions'),
// Controller
RevisionsController: Symbol.for('RevisionsController'),
// Handlers
ItemDumpedEventHandler: Symbol.for('ItemDumpedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
RevisionsCopyRequestedEventHandler: Symbol.for('RevisionsCopyRequestedEventHandler'),
RevisionsOwnershipUpdateRequestedEventHandler: Symbol.for('RevisionsOwnershipUpdateRequestedEventHandler'),
// Services
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),

View File

@@ -0,0 +1,42 @@
import { Result } from '@standardnotes/domain-core'
import { RevisionsCopyRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions'
import { RevisionsCopyRequestedEventHandler } from './RevisionsCopyRequestedEventHandler'
describe('RevisionsCopyRequestedEventHandler', () => {
let copyRevisions: CopyRevisions
let logger: Logger
let event: RevisionsCopyRequestedEvent
const createHandler = () => new RevisionsCopyRequestedEventHandler(copyRevisions, logger)
beforeEach(() => {
copyRevisions = {} as jest.Mocked<CopyRevisions>
copyRevisions.execute = jest.fn().mockReturnValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
event = {} as jest.Mocked<RevisionsCopyRequestedEvent>
event.payload = {
newItemUuid: '1-2-3',
originalItemUuid: '2-3-4',
}
})
it('should copy revisions', async () => {
await createHandler().handle(event)
expect(copyRevisions.execute).toHaveBeenCalled()
})
it('should indicate if copying revisions fail', async () => {
copyRevisions.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
await createHandler().handle(event)
expect(logger.error).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,18 @@
import { DomainEventHandlerInterface, RevisionsCopyRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions'
export class RevisionsCopyRequestedEventHandler implements DomainEventHandlerInterface {
constructor(private copyRevisions: CopyRevisions, private logger: Logger) {}
async handle(event: RevisionsCopyRequestedEvent): Promise<void> {
const result = await this.copyRevisions.execute({
newItemUuid: event.payload.newItemUuid,
originalItemUuid: event.payload.originalItemUuid,
})
if (result.isFailed()) {
this.logger.error(`Could not copy revisions: ${result.getError()}`)
}
}
}

View File

@@ -0,0 +1,25 @@
/* istanbul ignore file */
import { Uuid } from '@standardnotes/domain-core'
import { DomainEventHandlerInterface, RevisionsOwnershipUpdateRequestedEvent } from '@standardnotes/domain-events'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
export class RevisionsOwnershipUpdateRequestedEventHandler implements DomainEventHandlerInterface {
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async handle(event: RevisionsOwnershipUpdateRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
return
}
const userUuid = userUuidOrError.getValue()
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
if (itemUuidOrError.isFailed()) {
return
}
const itemUuid = itemUuidOrError.getValue()
await this.revisionRepository.updateUserUuid(itemUuid, userUuid)
}
}

View File

@@ -0,0 +1,16 @@
import { ContentType } from './ContentType'
describe('ContentType', () => {
it('should create a value obejct', () => {
const valueOrError = ContentType.create('Note')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).not.toBeNull()
})
it('should fail to create a value obejct', () => {
const valueOrError = ContentType.create('test')
expect(valueOrError.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,22 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { Revision } from './Revision'
describe('Revision', () => {
it('should create an entity', () => {
const entityOrError = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
timestamps: Timestamps.create(new Date(1), new Date(2)).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
})
})

View File

@@ -4,7 +4,7 @@ import { ContentType } from './ContentType'
export interface RevisionProps {
itemUuid: Uuid
userUuid: Uuid
userUuid: Uuid | null
content: string | null
contentType: ContentType
itemsKeyId: string | null

View File

@@ -7,6 +7,8 @@ export interface RevisionRepositoryInterface {
removeByUserUuid(userUuid: Uuid): Promise<void>
removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void>
findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null>
findByItemUuid(itemUuid: Uuid): Promise<Array<Revision>>
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>>
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
save(revision: Revision): Promise<Revision>
}

View File

@@ -0,0 +1,59 @@
import { Result } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { CopyRevisions } from './CopyRevisions'
describe('CopyRevisions', () => {
let revisionRepository: RevisionRepositoryInterface
const createUseCase = () => new CopyRevisions(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.findByItemUuid = jest.fn().mockReturnValue([{} as jest.Mocked<Revision>])
revisionRepository.save = jest.fn()
})
it('should not copy revisions to new item if revision creation fails', async () => {
const revisionMock = jest.spyOn(Revision, 'create')
revisionMock.mockImplementation(() => Result.fail('Oops'))
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
revisionMock.mockRestore()
})
it('should copy revisions to new item', async () => {
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeFalsy()
expect(revisionRepository.save).toHaveBeenCalled()
expect(result.getValue()).toEqual('Revisions copied')
})
it('should not copy revisions for an invalid item uuid', async () => {
const result = await createUseCase().execute({
originalItemUuid: '1-2-3',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
it('should not delete revision for a an invalid new item uuid', async () => {
const result = await createUseCase().execute({
newItemUuid: '1-2-3',
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,43 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { CopyRevisionsDTO } from './CopyRevisionsDTO'
export class CopyRevisions implements UseCaseInterface<string> {
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: CopyRevisionsDTO): Promise<Result<string>> {
const orignalItemUuidOrError = Uuid.create(dto.originalItemUuid)
if (orignalItemUuidOrError.isFailed()) {
return Result.fail<string>(`Could not copy revisions: ${orignalItemUuidOrError.getError()}`)
}
const originalItemUuid = orignalItemUuidOrError.getValue()
const newItemUuidOrError = Uuid.create(dto.newItemUuid)
if (newItemUuidOrError.isFailed()) {
return Result.fail<string>(`Could not copy revisions: ${newItemUuidOrError.getError()}`)
}
const newItemUuid = newItemUuidOrError.getValue()
const revisions = await this.revisionRepository.findByItemUuid(originalItemUuid)
for (const existingRevision of revisions) {
const revisionCopyOrError = Revision.create({
...existingRevision.props,
itemUuid: newItemUuid,
})
if (revisionCopyOrError.isFailed()) {
return Result.fail<string>(`Could not create revision copy: ${revisionCopyOrError.getError()}`)
}
const revisionCopy = revisionCopyOrError.getValue()
await this.revisionRepository.save(revisionCopy)
}
return Result.ok<string>('Revisions copied')
}
}

View File

@@ -0,0 +1,4 @@
export interface CopyRevisionsDTO {
originalItemUuid: string
newItemUuid: string
}

View File

@@ -1,7 +1,7 @@
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Repository } from 'typeorm'
import { Revision } from '../../Domain/Revision/Revision'
import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
import { TypeORMRevision } from '../TypeORM/TypeORMRevision'
@@ -13,12 +13,37 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
private revisionMapper: MapperInterface<Revision, TypeORMRevision>,
) {}
async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.update()
.set({
userUuid: userUuid.value,
})
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.execute()
}
async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
const typeormRevisions = await this.ormRepository
.createQueryBuilder()
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.getMany()
const revisions = []
for (const revision of typeormRevisions) {
revisions.push(this.revisionMapper.toDomain(revision))
}
return revisions
}
async removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('user_uuid = :userUuid', { userUuid })
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.execute()
}
@@ -27,15 +52,18 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
.createQueryBuilder()
.delete()
.from('revisions')
.where('uuid = :revisionUuid AND user_uuid = :userUuid', { userUuid, revisionUuid })
.where('uuid = :revisionUuid AND user_uuid = :userUuid', {
userUuid: userUuid.value,
revisionUuid: revisionUuid.value,
})
.execute()
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
const typeormRevision = await this.ormRepository
.createQueryBuilder()
.where('uuid = :revisionUuid', { revisionUuid })
.andWhere('user_uuid = :userUuid', { userUuid })
.where('uuid = :revisionUuid', { revisionUuid: revisionUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.getOne()
if (typeormRevision === null) {
@@ -60,8 +88,8 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
.addSelect('content_type', 'contentType')
.addSelect('created_at', 'createdAt')
.addSelect('updated_at', 'updatedAt')
.where('item_uuid = :itemUuid', { itemUuid })
.andWhere('user_uuid = :userUuid', { userUuid })
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.orderBy('created_at', 'DESC')
const simplifiedRevisions = await queryBuilder.getMany()

View File

@@ -15,9 +15,11 @@ export class TypeORMRevision {
@Column({
name: 'user_uuid',
length: 36,
type: 'varchar',
nullable: true,
})
@Index('user_uuid')
declare userUuid: string
declare userUuid: string | null
@Column({
type: 'mediumtext',

View File

@@ -23,11 +23,14 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
}
const itemUuid = itemUuidOrError.getValue()
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
let userUuid = null
if (projection.userUuid !== null) {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
}
userUuid = userUuidOrError.getValue()
}
const userUuid = userUuidOrError.getValue()
const revisionOrError = Revision.create(
{
@@ -62,7 +65,7 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
typeormRevision.encItemKey = domain.props.encItemKey
typeormRevision.itemUuid = domain.props.itemUuid.value
typeormRevision.itemsKeyId = domain.props.itemsKeyId
typeormRevision.userUuid = domain.props.userUuid.value
typeormRevision.userUuid = domain.props.userUuid ? domain.props.userUuid.value : null
typeormRevision.uuid = domain.id.toString()
return typeormRevision

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.33](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.32...@standardnotes/scheduler-server@1.13.33) (2022-11-30)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.32](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.31...@standardnotes/scheduler-server@1.13.32) (2022-11-28)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.30...@standardnotes/scheduler-server@1.13.31) (2022-11-25)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.13.31",
"version": "1.13.33",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.19.0...@standardnotes/syncing-server@1.19.1) (2022-11-30)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.19.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.18.12...@standardnotes/syncing-server@1.19.0) (2022-11-28)
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/syncing-server-js/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [1.18.12](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.18.11...@standardnotes/syncing-server@1.18.12) (2022-11-25)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.18.12",
"version": "1.19.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -10,6 +10,7 @@ import {
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -21,6 +22,27 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: {
originalItemUuid: string
newItemUuid: string
},
): RevisionsCopyRequestedEvent {
return {
type: 'REVISIONS_COPY_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent {
return {
type: 'ITEM_DUMPED',

View File

@@ -8,6 +8,7 @@ import {
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
@@ -35,4 +36,8 @@ export interface DomainEventFactoryInterface {
createDuplicateItemSyncedEvent(itemUuid: string, userUuid: string): DuplicateItemSyncedEvent
createItemRevisionCreationRequested(itemUuid: string, userUuid: string): ItemRevisionCreationRequestedEvent
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: { originalItemUuid: string; newItemUuid: string },
): RevisionsCopyRequestedEvent
}

View File

@@ -1,11 +1,16 @@
import 'reflect-metadata'
import { DuplicateItemSyncedEvent } from '@standardnotes/domain-events'
import {
DomainEventPublisherInterface,
DuplicateItemSyncedEvent,
RevisionsCopyRequestedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { DuplicateItemSyncedEventHandler } from './DuplicateItemSyncedEventHandler'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
describe('DuplicateItemSyncedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
@@ -14,8 +19,17 @@ describe('DuplicateItemSyncedEventHandler', () => {
let duplicateItem: Item
let originalItem: Item
let event: DuplicateItemSyncedEvent
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createHandler = () => new DuplicateItemSyncedEventHandler(itemRepository, revisionService, logger)
const createHandler = () =>
new DuplicateItemSyncedEventHandler(
itemRepository,
revisionService,
domainEventFactory,
domainEventPublisher,
logger,
)
beforeEach(() => {
originalItem = {
@@ -45,6 +59,14 @@ describe('DuplicateItemSyncedEventHandler', () => {
userUuid: '1-2-3',
itemUuid: '2-3-4',
}
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createRevisionsCopyRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<RevisionsCopyRequestedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should copy revisions from original item to the duplicate item', async () => {

View File

@@ -1,7 +1,12 @@
import { DomainEventHandlerInterface, DuplicateItemSyncedEvent } from '@standardnotes/domain-events'
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
DuplicateItemSyncedEvent,
} from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
@@ -10,6 +15,8 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
constructor(
@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
@inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -35,6 +42,13 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
if (existingOriginalItem !== null) {
await this.revisionService.copyRevisions(existingOriginalItem.uuid, item.uuid)
await this.domainEventPublisher.publish(
this.domainEventFactory.createRevisionsCopyRequestedEvent(event.payload.userUuid, {
originalItemUuid: existingOriginalItem.uuid,
newItemUuid: item.uuid,
}),
)
}
}
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.33](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.32...@standardnotes/websockets-server@1.4.33) (2022-11-30)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.4.32](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.31...@standardnotes/websockets-server@1.4.32) (2022-11-28)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.4.31](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.30...@standardnotes/websockets-server@1.4.31) (2022-11-25)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.4.31",
"version": "1.4.33",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.17.32](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.31...@standardnotes/workspace-server@1.17.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.31](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.30...@standardnotes/workspace-server@1.17.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.30](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.29...@standardnotes/workspace-server@1.17.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/workspace-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.17.30",
"version": "1.17.32",
"engines": {
"node": ">=18.0.0 <19.0.0"
},