Compare commits

...

9 Commits

Author SHA1 Message Date
standardci
09e1a892ca chore(release): publish new version
- @standardnotes/analytics@2.26.22
 - @standardnotes/api-gateway@1.75.2
 - @standardnotes/auth-server@1.146.2
 - @standardnotes/domain-events-infra@1.12.34
 - @standardnotes/domain-events@2.130.0
 - @standardnotes/event-store@1.11.50
 - @standardnotes/files-server@1.23.0
 - @standardnotes/home-server@1.16.4
 - @standardnotes/revisions-server@1.37.1
 - @standardnotes/scheduler-server@1.20.54
 - @standardnotes/syncing-server@1.108.0
 - @standardnotes/websockets-server@1.10.51
2023-09-25 11:53:52 +00:00
Karol Sójko
7b1eec21e5 feat: remove shared vault files upon shared vault removal (#852)
* feat: remove shared vault files upon shared vault removal

* fix: link files queue with syncing-server-js topic
2023-09-25 12:56:31 +02:00
standardci
a58262d584 chore(release): publish new version
- @standardnotes/home-server@1.16.3
 - @standardnotes/syncing-server@1.107.0
2023-09-25 09:43:23 +00:00
Karol Sójko
a8f03e157b feat(syncing-server): transfer shared vault items (#851) 2023-09-25 11:09:33 +02:00
standardci
a401962bcd chore(release): publish new version
- @standardnotes/home-server@1.16.2
 - @standardnotes/revisions-server@1.37.0
 - @standardnotes/syncing-server@1.106.0
2023-09-25 08:15:14 +00:00
Karol Sójko
9759814f63 feat: add storing paging progress in redis 2023-09-25 09:40:49 +02:00
standardci
c7cf53722c chore(release): publish new version
- @standardnotes/analytics@2.26.21
 - @standardnotes/api-gateway@1.75.1
 - @standardnotes/auth-server@1.146.1
 - @standardnotes/domain-events-infra@1.12.33
 - @standardnotes/domain-events@2.129.1
 - @standardnotes/event-store@1.11.49
 - @standardnotes/files-server@1.22.28
 - @standardnotes/home-server@1.16.1
 - @standardnotes/revisions-server@1.36.7
 - @standardnotes/scheduler-server@1.20.53
 - @standardnotes/syncing-server@1.105.1
 - @standardnotes/websockets-server@1.10.50
2023-09-25 07:30:06 +00:00
Karol Sójko
8cb33dc906 fix: add paging progress log 2023-09-25 08:51:33 +02:00
Karol Sójko
1d73e4f072 fix: remember paging progress on transitioning 2023-09-25 08:50:59 +02:00
59 changed files with 561 additions and 192 deletions

8
.pnp.cjs generated
View File

@@ -6156,6 +6156,7 @@ const RAW_RUNTIME_STATE =
["@types/cors", "npm:2.8.13"],\
["@types/dotenv", "npm:8.2.0"],\
["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\
["@types/newrelic", "npm:9.14.0"],\
["@types/node", "npm:20.5.7"],\
@@ -6168,6 +6169,7 @@ const RAW_RUNTIME_STATE =
["express", "npm:4.18.2"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\
@@ -6340,6 +6342,7 @@ const RAW_RUNTIME_STATE =
["@types/cors", "npm:2.8.13"],\
["@types/dotenv", "npm:8.2.0"],\
["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\
["@types/jsonwebtoken", "npm:9.0.2"],\
["@types/newrelic", "npm:9.14.0"],\
@@ -6359,6 +6362,7 @@ const RAW_RUNTIME_STATE =
["helmet", "npm:7.0.0"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
@@ -16772,7 +16776,7 @@ const RAW_RUNTIME_STATE =
["@types/better-sqlite3", null],\
["@types/google-cloud__spanner", null],\
["@types/hdb-pool", null],\
["@types/ioredis", null],\
["@types/ioredis", "npm:5.0.0"],\
["@types/mongodb", null],\
["@types/mssql", null],\
["@types/mysql2", null],\
@@ -16796,7 +16800,7 @@ const RAW_RUNTIME_STATE =
["dotenv", "npm:16.1.3"],\
["glob", "npm:8.1.0"],\
["hdb-pool", null],\
["ioredis", null],\
["ioredis", "npm:5.3.2"],\
["mkdirp", "npm:2.1.6"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mssql", null],\

View File

@@ -139,6 +139,11 @@ LINKING_RESULT=$(link_queue_and_topic $AUTH_TOPIC_ARN $FILES_QUEUE_ARN)
echo "linking done:"
echo "$LINKING_RESULT"
echo "linking topic $SYNCING_SERVER_TOPIC_ARN to queue $FILES_QUEUE_ARN"
LINKING_RESULT=$(link_queue_and_topic $SYNCING_SERVER_TOPIC_ARN $FILES_QUEUE_ARN)
echo "linking done:"
echo "$LINKING_RESULT"
QUEUE_NAME="syncing-server-local-queue"
echo "creating queue $QUEUE_NAME"

View File

@@ -3,8 +3,6 @@ module.exports = {
testEnvironment: 'node',
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$',
testTimeout: 20000,
coverageReporters: ['text'],
reporters: ['summary'],
coverageThreshold: {
global: {
branches: 100,

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.26.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.21...@standardnotes/analytics@2.26.22) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.20...@standardnotes/analytics@2.26.21) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.19...@standardnotes/analytics@2.26.20) (2023-09-21)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.26.20",
"version": "2.26.22",
"engines": {
"node": ">=18.0.0 <21.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.75.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.1...@standardnotes/api-gateway@1.75.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.0...@standardnotes/api-gateway@1.75.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.75.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.17...@standardnotes/api-gateway@1.75.0) (2023-09-21)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.75.0",
"version": "1.75.2",
"engines": {
"node": ">=18.0.0 <21.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.146.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.1...@standardnotes/auth-server@1.146.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.0...@standardnotes/auth-server@1.146.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.146.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.145.0...@standardnotes/auth-server@1.146.0) (2023-09-22)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.146.0",
"version": "1.146.2",
"engines": {
"node": ">=18.0.0 <21.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.12.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.33...@standardnotes/domain-events-infra@1.12.34) (2023-09-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.33](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.32...@standardnotes/domain-events-infra@1.12.33) (2023-09-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.32](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.31...@standardnotes/domain-events-infra@1.12.32) (2023-09-21)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.12.32",
"version": "1.12.34",
"engines": {
"node": ">=18.0.0 <21.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.130.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.129.1...@standardnotes/domain-events@2.130.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/server/issues/852)) ([7b1eec2](https://github.com/standardnotes/server/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
## [2.129.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.129.0...@standardnotes/domain-events@2.129.1) (2023-09-25)
### Bug Fixes
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/server/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
# [2.129.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.128.0...@standardnotes/domain-events@2.129.0) (2023-09-21)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.129.0",
"version": "2.130.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,3 +1,4 @@
export interface SharedVaultRemovedEventPayload {
sharedVaultUuid: string
vaultOwnerUuid: string
}

View File

@@ -3,4 +3,5 @@ export interface TransitionStatusUpdatedEventPayload {
transitionType: 'items' | 'revisions'
transitionTimestamp: number
status: string
page?: number
}

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.11.50](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.49...@standardnotes/event-store@1.11.50) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.49](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.48...@standardnotes/event-store@1.11.49) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.48](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.47...@standardnotes/event-store@1.11.48) (2023-09-21)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

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.23.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.28...@standardnotes/files-server@1.23.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/files/issues/852)) ([7b1eec2](https://github.com/standardnotes/files/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
## [1.22.28](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.27...@standardnotes/files-server@1.22.28) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.27](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.26...@standardnotes/files-server@1.22.27) (2023-09-21)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.22.27",
"version": "1.23.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,73 +0,0 @@
import 'reflect-metadata'
import {
AccountDeletionRequestedEvent,
AccountDeletionRequestedEventPayload,
DomainEventPublisherInterface,
FileRemovedEvent,
} from '@standardnotes/domain-events'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RemovedFileDescription } from '../File/RemovedFileDescription'
describe('AccountDeletionRequestedEventHandler', () => {
let markFilesToBeRemoved: MarkFilesToBeRemoved
let event: AccountDeletionRequestedEvent
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createHandler = () =>
new AccountDeletionRequestedEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
beforeEach(() => {
markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: true,
filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
})
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
event.payload = {
userUuid: '1-2-3',
regularSubscriptionUuid: '1-2-3',
} as jest.Mocked<AccountDeletionRequestedEventPayload>
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
})
it('should mark files to be remove for user', async () => {
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not mark files to be remove for user if user has no regular subscription', async () => {
event.payload.regularSubscriptionUuid = undefined
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not publish events if failed to mark files to be removed', async () => {
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: false,
})
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
})

View File

@@ -22,15 +22,17 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
return
}
const response = await this.markFilesToBeRemoved.execute({
const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.userUuid,
})
if (!response.success) {
if (result.isFailed()) {
return
}
for (const fileRemoved of response.filesRemoved) {
const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.regularSubscriptionUuid,

View File

@@ -1,73 +0,0 @@
import 'reflect-metadata'
import {
SharedSubscriptionInvitationCanceledEvent,
SharedSubscriptionInvitationCanceledEventPayload,
DomainEventPublisherInterface,
FileRemovedEvent,
} from '@standardnotes/domain-events'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { SharedSubscriptionInvitationCanceledEventHandler } from './SharedSubscriptionInvitationCanceledEventHandler'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RemovedFileDescription } from '../File/RemovedFileDescription'
describe('SharedSubscriptionInvitationCanceledEventHandler', () => {
let markFilesToBeRemoved: MarkFilesToBeRemoved
let event: SharedSubscriptionInvitationCanceledEvent
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createHandler = () =>
new SharedSubscriptionInvitationCanceledEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
beforeEach(() => {
markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: true,
filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
})
event = {} as jest.Mocked<SharedSubscriptionInvitationCanceledEvent>
event.payload = {
inviteeIdentifier: '1-2-3',
inviteeIdentifierType: 'uuid',
} as jest.Mocked<SharedSubscriptionInvitationCanceledEventPayload>
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
})
it('should mark files to be remove for user', async () => {
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not mark files to be remove for user if identifier is not of uuid type', async () => {
event.payload.inviteeIdentifierType = 'email'
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not publish events if failed to mark files to be removed', async () => {
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: false,
})
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
})

View File

@@ -22,15 +22,17 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
return
}
const response = await this.markFilesToBeRemoved.execute({
const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.inviteeIdentifier,
})
if (!response.success) {
if (result.isFailed()) {
return
}
for (const fileRemoved of response.filesRemoved) {
const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,

View File

@@ -0,0 +1,44 @@
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
SharedVaultRemovedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
private markFilesToBeRemoved: MarkFilesToBeRemoved,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: SharedVaultRemovedEvent): Promise<void> {
const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.sharedVaultUuid,
})
if (result.isFailed()) {
this.logger.error(
`Could not mark files to be removed for shared vault: ${event.payload.sharedVaultUuid}: ${result.getError()}`,
)
}
const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultFileRemovedEvent({
fileByteSize: fileRemoved.fileByteSize,
fileName: fileRemoved.fileName,
filePath: fileRemoved.filePath,
sharedVaultUuid: event.payload.sharedVaultUuid,
vaultOwnerUuid: event.payload.vaultOwnerUuid,
}),
)
}
}
}

View File

@@ -21,7 +21,9 @@ describe('MarkFilesToBeRemoved', () => {
})
it('should mark files for being removed', async () => {
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({ success: true })
const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
expect(result.isFailed()).toEqual(false)
expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3')
})
@@ -31,9 +33,7 @@ describe('MarkFilesToBeRemoved', () => {
throw new Error('Oops')
})
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({
success: false,
message: 'Could not mark resources for removal',
})
const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
expect(result.isFailed()).toEqual(true)
})
})

View File

@@ -1,36 +1,30 @@
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types'
import { FileRemoverInterface } from '../../Services/FileRemoverInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { MarkFilesToBeRemovedDTO } from './MarkFilesToBeRemovedDTO'
import { MarkFilesToBeRemovedResponse } from './MarkFilesToBeRemovedResponse'
import { RemovedFileDescription } from '../../File/RemovedFileDescription'
@injectable()
export class MarkFilesToBeRemoved implements UseCaseInterface {
export class MarkFilesToBeRemoved implements UseCaseInterface<RemovedFileDescription[]> {
constructor(
@inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface,
@inject(TYPES.Files_Logger) private logger: Logger,
) {}
async execute(dto: MarkFilesToBeRemovedDTO): Promise<MarkFilesToBeRemovedResponse> {
async execute(dto: MarkFilesToBeRemovedDTO): Promise<Result<RemovedFileDescription[]>> {
try {
this.logger.debug(`Marking files for later removal for user: ${dto.ownerUuid}`)
const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.ownerUuid)
return {
success: true,
filesRemoved,
}
return Result.ok(filesRemoved)
} catch (error) {
this.logger.error(`Could not mark resources for removal: ${dto.ownerUuid} - ${(error as Error).message}`)
return {
success: false,
message: 'Could not mark resources for removal',
}
return Result.fail('Could not mark resources for removal')
}
}
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.3...@standardnotes/home-server@1.16.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.2...@standardnotes/home-server@1.16.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.1...@standardnotes/home-server@1.16.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.0...@standardnotes/home-server@1.16.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.80...@standardnotes/home-server@1.16.0) (2023-09-22)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.16.0",
"version": "1.16.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.0...@standardnotes/revisions-server@1.37.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.7...@standardnotes/revisions-server@1.37.0) (2023-09-25)
### Features
* add storing paging progress in redis ([9759814](https://github.com/standardnotes/server/commit/9759814f637b8ae25b325e35bc7f5159747980b6))
## [1.36.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.6...@standardnotes/revisions-server@1.36.7) (2023-09-25)
### Bug Fixes
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/server/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
## [1.36.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.5...@standardnotes/revisions-server@1.36.6) (2023-09-22)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.36.6",
"version": "1.37.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -41,6 +41,7 @@
"express": "^4.18.2",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"mongodb": "^6.0.0",
"mysql2": "^3.0.1",
"reflect-metadata": "0.1.13",
@@ -52,6 +53,7 @@
"@types/cors": "^2.8.9",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1",
"@types/node": "^20.5.7",
"@typescript-eslint/eslint-plugin": "^6.5.0",

View File

@@ -1,4 +1,5 @@
import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify'
import { MongoRepository, Repository } from 'typeorm'
import * as winston from 'winston'
@@ -68,6 +69,8 @@ import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevision
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -88,11 +91,28 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const container = new Container({
defaultScope: 'Singleton',
})
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Revisions_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Revisions_Redis)))
}
let logger: winston.Logger
if (configuration?.logger) {
logger = configuration.logger as winston.Logger
@@ -348,6 +368,9 @@ export class ContainerConfigLoader {
isSecondaryDatabaseEnabled
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
: null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository),
container.get<TimerInterface>(TYPES.Revisions_Timer),
container.get<winston.Logger>(TYPES.Revisions_Logger),
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,

View File

@@ -1,6 +1,7 @@
const TYPES = {
Revisions_DBConnection: Symbol.for('Revisions_DBConnection'),
Revisions_Logger: Symbol.for('Revisions_Logger'),
Revisions_Redis: Symbol.for('Revisions_Redis'),
Revisions_SQS: Symbol.for('Revisions_SQS'),
Revisions_SNS: Symbol.for('Revisions_SNS'),
Revisions_S3: Symbol.for('Revisions_S3'),
@@ -27,6 +28,7 @@ const TYPES = {
Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'),
Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'),
Revisions_RevisionRepositoryResolver: Symbol.for('Revisions_RevisionRepositoryResolver'),
Revisions_TransitionStatusRepository: Symbol.for('Revisions_TransitionStatusRepository'),
// env vars
Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'),
Revisions_SQS_QUEUE_URL: Symbol.for('Revisions_SQS_QUEUE_URL'),

View File

@@ -0,0 +1,4 @@
export interface TransitionRepositoryInterface {
getPagingProgress(userUuid: string): Promise<number>
setPagingProgress(userUuid: string, progress: number): Promise<void>
}

View File

@@ -5,11 +5,13 @@ import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
import { TransitionRepositoryInterface } from '../../../Transition/TransitionRepositoryInterface'
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor(
private primaryRevisionsRepository: RevisionRepositoryInterface,
private secondRevisionsRepository: RevisionRepositoryInterface | null,
private transitionStatusRepository: TransitionRepositoryInterface | null,
private timer: TimerInterface,
private logger: Logger,
private pageSize: number,
@@ -22,6 +24,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.fail('Secondary revision repository is not set')
}
if (this.transitionStatusRepository === null) {
return Result.fail('Transition status repository is not set')
}
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
@@ -71,10 +77,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<string[]>> {
try {
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
userUuid.value,
)
this.logger.info(`[${userUuid.value}] Migrating from page ${initialPage}`)
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
const revisionsToSkipInIntegrityCheck = []
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
userUuid.value,
currentPage,
)
const query = {
userUuid: userUuid,
offset: (currentPage - 1) * this.pageSize,

View File

@@ -0,0 +1,23 @@
import * as IORedis from 'ioredis'
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
export class RedisTransitionRepository implements TransitionRepositoryInterface {
private readonly PREFIX = 'transition-revisions-paging-progress'
constructor(private redisClient: IORedis.Redis) {}
async getPagingProgress(userUuid: string): Promise<number> {
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
if (progress === null) {
return 1
}
return parseInt(progress)
}
async setPagingProgress(userUuid: string, progress: number): Promise<void> {
await this.redisClient.setex(`${this.PREFIX}:${userUuid}`, 172_800, progress.toString())
}
}

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.20.54](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.53...@standardnotes/scheduler-server@1.20.54) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.53](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.52...@standardnotes/scheduler-server@1.20.53) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.52](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.51...@standardnotes/scheduler-server@1.20.52) (2023-09-21)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.52",
"version": "1.20.54",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.108.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.107.0...@standardnotes/syncing-server@1.108.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/syncing-server-js/issues/852)) ([7b1eec2](https://github.com/standardnotes/syncing-server-js/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
# [1.107.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.106.0...@standardnotes/syncing-server@1.107.0) (2023-09-25)
### Features
* **syncing-server:** transfer shared vault items ([#851](https://github.com/standardnotes/syncing-server-js/issues/851)) ([a8f03e1](https://github.com/standardnotes/syncing-server-js/commit/a8f03e157be3d277e60d2756dd25c953775b1ba4))
# [1.106.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.105.1...@standardnotes/syncing-server@1.106.0) (2023-09-25)
### Features
* add storing paging progress in redis ([9759814](https://github.com/standardnotes/syncing-server-js/commit/9759814f637b8ae25b325e35bc7f5159747980b6))
## [1.105.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.105.0...@standardnotes/syncing-server@1.105.1) (2023-09-25)
### Bug Fixes
* add paging progress log ([8cb33dc](https://github.com/standardnotes/syncing-server-js/commit/8cb33dc906391ee8b1ebd333937045c328e4fc06))
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/syncing-server-js/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
# [1.105.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.104.0...@standardnotes/syncing-server@1.105.0) (2023-09-22)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.105.0",
"version": "1.108.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -47,6 +47,7 @@
"helmet": "^7.0.0",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.0",
"mongodb": "^6.0.0",
"mysql2": "^3.0.1",
@@ -63,6 +64,7 @@
"@types/cors": "^2.8.9",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1",
"@types/jsonwebtoken": "^9.0.1",
"@types/node": "^20.5.7",

View File

@@ -1,4 +1,5 @@
import * as winston from 'winston'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
@@ -171,6 +172,9 @@ import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRem
import { DesignateSurvivor } from '../Domain/UseCase/SharedVaults/DesignateSurvivor/DesignateSurvivor'
import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVaults/RemoveUserFromSharedVaults'
import { TransferSharedVault } from '../Domain/UseCase/SharedVaults/TransferSharedVault/TransferSharedVault'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { TransferSharedVaultItems } from '../Domain/UseCase/SharedVaults/TransferSharedVaultItems/TransferSharedVaultItems'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -228,6 +232,23 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Sync_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Sync_Redis)))
}
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
@@ -833,6 +854,9 @@ export class ContainerConfigLoader {
new TransitionItemsFromPrimaryToSecondaryDatabaseForUser(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository),
container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<Logger>(TYPES.Sync_Logger),
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
@@ -867,12 +891,22 @@ export class ContainerConfigLoader {
container.get<Logger>(TYPES.Sync_Logger),
),
)
container
.bind<TransferSharedVaultItems>(TYPES.Sync_TransferSharedVaultItems)
.toConstantValue(
new TransferSharedVaultItems(
isSecondaryDatabaseEnabled
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
: container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
),
)
container
.bind<TransferSharedVault>(TYPES.Sync_TransferSharedVault)
.toConstantValue(
new TransferSharedVault(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<TransferSharedVaultItems>(TYPES.Sync_TransferSharedVaultItems),
container.get<TimerInterface>(TYPES.Sync_Timer),
),
)

View File

@@ -15,6 +15,7 @@ const TYPES = {
Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'),
Sync_NotificationRepository: Symbol.for('Sync_NotificationRepository'),
Sync_MessageRepository: Symbol.for('Sync_MessageRepository'),
Sync_TransitionStatusRepository: Symbol.for('Sync_TransitionStatusRepository'),
// ORM
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
Sync_ORMLegacyItemRepository: Symbol.for('Sync_ORMLegacyItemRepository'),
@@ -90,6 +91,7 @@ const TYPES = {
Sync_DesignateSurvivor: Symbol.for('Sync_DesignateSurvivor'),
Sync_RemoveUserFromSharedVaults: Symbol.for('Sync_RemoveUserFromSharedVaults'),
Sync_TransferSharedVault: Symbol.for('Sync_TransferSharedVault'),
Sync_TransferSharedVaultItems: Symbol.for('Sync_TransferSharedVaultItems'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),

View File

@@ -42,7 +42,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
}
}
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent {
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent {
return {
type: 'SHARED_VAULT_REMOVED',
createdAt: this.timer.getUTCDate(),

View File

@@ -102,7 +102,7 @@ export interface DomainEventFactoryInterface {
itemUuid: string
userUuid: string
}): ItemRemovedFromSharedVaultEvent
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent
createUserDesignatedAsSurvivorInSharedVaultEvent(dto: {
sharedVaultUuid: string
userUuid: string

View File

@@ -20,4 +20,5 @@ export interface ItemRepositoryInterface {
markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
updateContentSize(itemUuid: string, contentSize: number): Promise<void>
unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void>
updateSharedVaultOwner(dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void>
}

View File

@@ -0,0 +1,4 @@
export interface TransitionRepositoryInterface {
getPagingProgress(userUuid: string): Promise<number>
setPagingProgress(userUuid: string, progress: number): Promise<void>
}

View File

@@ -101,6 +101,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultRemovedEvent({
sharedVaultUuid: sharedVaultUuid.value,
vaultOwnerUuid: sharedVault.props.userUuid.value,
}),
)

View File

@@ -1,10 +1,11 @@
import { TimerInterface } from '@standardnotes/time'
import { SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { Result, SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { TransferSharedVault } from './TransferSharedVault'
import { SharedVault } from '../../../SharedVault/SharedVault'
import { TransferSharedVaultItems } from '../TransferSharedVaultItems/TransferSharedVaultItems'
describe('TransferSharedVault', () => {
let sharedVault: SharedVault
@@ -12,8 +13,10 @@ describe('TransferSharedVault', () => {
let sharedVaultRepository: SharedVaultRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let timer: TimerInterface
let transferSharedVaultItems: TransferSharedVaultItems
const createUseCase = () => new TransferSharedVault(sharedVaultRepository, sharedVaultUserRepository, timer)
const createUseCase = () =>
new TransferSharedVault(sharedVaultRepository, sharedVaultUserRepository, transferSharedVaultItems, timer)
beforeEach(() => {
sharedVault = SharedVault.create({
@@ -40,6 +43,9 @@ describe('TransferSharedVault', () => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
transferSharedVaultItems = {} as jest.Mocked<TransferSharedVaultItems>
transferSharedVaultItems.execute = jest.fn().mockResolvedValue(Result.ok())
})
it('should transfer shared vault to another user', async () => {
@@ -145,4 +151,20 @@ describe('TransferSharedVault', () => {
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
it('should fail if transfering items fails', async () => {
const useCase = createUseCase()
transferSharedVaultItems.execute = jest.fn().mockResolvedValue(Result.fail('error'))
const result = await useCase.execute({
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
fromUserUuid: '00000000-0000-0000-0000-000000000000',
toUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -4,11 +4,13 @@ import { TimerInterface } from '@standardnotes/time'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
import { TransferSharedVaultDTO } from './TransferSharedVaultDTO'
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { TransferSharedVaultItems } from '../TransferSharedVaultItems/TransferSharedVaultItems'
export class TransferSharedVault implements UseCaseInterface<void> {
constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private transferSharedVaultItems: TransferSharedVaultItems,
private timer: TimerInterface,
) {}
@@ -48,6 +50,15 @@ export class TransferSharedVault implements UseCaseInterface<void> {
return Result.fail('New owner is not a member of this shared vault')
}
const transferingItemsResult = await this.transferSharedVaultItems.execute({
fromUserUuid: fromUserUuid.value,
toUserUuid: toUserUuid.value,
sharedVaultUuid: sharedVaultUuid.value,
})
if (transferingItemsResult.isFailed()) {
return Result.fail(`Could not transfer items: ${transferingItemsResult.getError()}`)
}
newOwner.props.isDesignatedSurvivor = false
newOwner.props.permission = SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Admin).getValue()
newOwner.props.timestamps = Timestamps.create(

View File

@@ -0,0 +1,65 @@
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { TransferSharedVaultItems } from './TransferSharedVaultItems'
describe('TransferSharedVaultItems', () => {
let itemRepository: ItemRepositoryInterface
const createUseCase = () => new TransferSharedVaultItems(itemRepository)
beforeEach(() => {
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.updateSharedVaultOwner = jest.fn()
})
it('should update shared vault owner', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
fromUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1f',
toUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1a',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(false)
expect(itemRepository.updateSharedVaultOwner).toHaveBeenCalled()
})
it('should return error when from user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
fromUserUuid: 'invalid',
toUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1a',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should return error when to user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
fromUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1f',
toUserUuid: 'invalid',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should return error when shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
fromUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1f',
toUserUuid: '0d1d1c7c-5e3e-4b0b-8b4a-8c5b1f8c5b1a',
sharedVaultUuid: 'invalid',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
})

View File

@@ -0,0 +1,36 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { TransferSharedVaultItemsDTO } from './TransferSharedVaultItemsDTO'
export class TransferSharedVaultItems implements UseCaseInterface<void> {
constructor(private itemRepository: ItemRepositoryInterface) {}
async execute(dto: TransferSharedVaultItemsDTO): Promise<Result<void>> {
const fromUserUuidOrError = Uuid.create(dto.fromUserUuid)
if (fromUserUuidOrError.isFailed()) {
return Result.fail(fromUserUuidOrError.getError())
}
const fromUserUuid = fromUserUuidOrError.getValue()
const toUserUuidOrError = Uuid.create(dto.toUserUuid)
if (toUserUuidOrError.isFailed()) {
return Result.fail(toUserUuidOrError.getError())
}
const toUserUuid = toUserUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
await this.itemRepository.updateSharedVaultOwner({
sharedVaultUuid,
fromOwnerUuid: fromUserUuid,
toOwnerUuid: toUserUuid,
})
return Result.ok()
}
}

View File

@@ -0,0 +1,5 @@
export interface TransferSharedVaultItemsDTO {
sharedVaultUuid: string
fromUserUuid: string
toUserUuid: string
}

View File

@@ -6,11 +6,13 @@ import { Logger } from 'winston'
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO'
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { ItemQuery } from '../../../Item/ItemQuery'
import { TransitionRepositoryInterface } from '../../../Transition/TransitionRepositoryInterface'
export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor(
private primaryItemRepository: ItemRepositoryInterface,
private secondaryItemRepository: ItemRepositoryInterface | null,
private transitionStatusRepository: TransitionRepositoryInterface | null,
private timer: TimerInterface,
private logger: Logger,
private pageSize: number,
@@ -23,6 +25,10 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
return Result.fail('Secondary item repository is not set')
}
if (this.transitionStatusRepository === null) {
return Result.fail('Transition status repository is not set')
}
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
@@ -77,10 +83,21 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<string[]>> {
try {
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
userUuid.value,
)
this.logger.info(`[${userUuid.value}] Migrating from page ${initialPage}`)
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
const itemsToSkipInIntegrityCheck = []
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
userUuid.value,
currentPage,
)
const query: ItemQuery = {
userUuid: userUuid.value,
offset: (currentPage - 1) * this.pageSize,

View File

@@ -0,0 +1,23 @@
import * as IORedis from 'ioredis'
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
export class RedisTransitionRepository implements TransitionRepositoryInterface {
private readonly PREFIX = 'transition-items-paging-progress'
constructor(private redisClient: IORedis.Redis) {}
async getPagingProgress(userUuid: string): Promise<number> {
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
if (progress === null) {
return 1
}
return parseInt(progress)
}
async setPagingProgress(userUuid: string, progress: number): Promise<void> {
await this.redisClient.setex(`${this.PREFIX}:${userUuid}`, 172_800, progress.toString())
}
}

View File

@@ -17,6 +17,15 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
private logger: Logger,
) {}
async updateSharedVaultOwner(dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
await this.mongoRepository.updateMany(
{
$and: [{ sharedVaultUuid: { $eq: dto.sharedVaultUuid.value } }, { userUuid: { $eq: dto.fromOwnerUuid.value } }],
},
{ $set: { userUuid: dto.toOwnerUuid.value } },
)
}
async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
await this.mongoRepository.updateMany(
{ sharedVaultUuid: { $eq: sharedVaultUuid.value } },

View File

@@ -16,6 +16,24 @@ export class SQLItemRepository extends SQLLegacyItemRepository {
super(ormRepository, mapper, logger)
}
override async updateSharedVaultOwner(dto: {
sharedVaultUuid: Uuid
fromOwnerUuid: Uuid
toOwnerUuid: Uuid
}): Promise<void> {
await this.ormRepository
.createQueryBuilder('item')
.update()
.set({
userUuid: dto.toOwnerUuid.value,
})
.where('shared_vault_uuid = :sharedVaultUuid AND user_uuid = :fromOwnerUuid', {
sharedVaultUuid: dto.sharedVaultUuid.value,
fromOwnerUuid: dto.fromOwnerUuid.value,
})
.execute()
}
override async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder('item')

View File

@@ -16,6 +16,10 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
protected logger: Logger,
) {}
async updateSharedVaultOwner(_dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
this.logger.error('Method updateSharedVaultOwner not supported.')
}
async unassignFromSharedVault(_sharedVaultUuid: Uuid): Promise<void> {
this.logger.error('Method unassignFromSharedVault not supported.')
}

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.10.51](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.50...@standardnotes/websockets-server@1.10.51) (2023-09-25)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.10.50](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.49...@standardnotes/websockets-server@1.10.50) (2023-09-25)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.10.49](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.48...@standardnotes/websockets-server@1.10.49) (2023-09-21)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.10.49",
"version": "1.10.51",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -5045,6 +5045,7 @@ __metadata:
"@types/cors": "npm:^2.8.9"
"@types/dotenv": "npm:^8.2.0"
"@types/express": "npm:^4.17.14"
"@types/ioredis": "npm:^5.0.0"
"@types/jest": "npm:^29.5.1"
"@types/newrelic": "npm:^9.14.0"
"@types/node": "npm:^20.5.7"
@@ -5057,6 +5058,7 @@ __metadata:
express: "npm:^4.18.2"
inversify: "npm:^6.0.1"
inversify-express-utils: "npm:^6.4.3"
ioredis: "npm:^5.3.2"
jest: "npm:^29.5.0"
mongodb: "npm:^6.0.0"
mysql2: "npm:^3.0.1"
@@ -5235,6 +5237,7 @@ __metadata:
"@types/cors": "npm:^2.8.9"
"@types/dotenv": "npm:^8.2.0"
"@types/express": "npm:^4.17.14"
"@types/ioredis": "npm:^5.0.0"
"@types/jest": "npm:^29.5.1"
"@types/jsonwebtoken": "npm:^9.0.1"
"@types/newrelic": "npm:^9.14.0"
@@ -5254,6 +5257,7 @@ __metadata:
helmet: "npm:^7.0.0"
inversify: "npm:^6.0.1"
inversify-express-utils: "npm:^6.4.3"
ioredis: "npm:^5.3.2"
jest: "npm:^29.5.0"
jsonwebtoken: "npm:^9.0.0"
mongodb: "npm:^6.0.0"
@@ -9531,7 +9535,7 @@ __metadata:
languageName: node
linkType: hard
"ioredis@npm:*, ioredis@npm:^5.2.4":
"ioredis@npm:*, ioredis@npm:^5.2.4, ioredis@npm:^5.3.2":
version: 5.3.2
resolution: "ioredis@npm:5.3.2"
dependencies: