mirror of
https://github.com/standardnotes/server
synced 2026-01-26 05:01:12 -05:00
Compare commits
4 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09e1a892ca | ||
|
|
7b1eec21e5 | ||
|
|
a58262d584 | ||
|
|
a8f03e157b |
@@ -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"
|
||||
|
||||
@@ -3,8 +3,6 @@ module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$',
|
||||
testTimeout: 20000,
|
||||
coverageReporters: ['text'],
|
||||
reporters: ['summary'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 100,
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.26.21",
|
||||
"version": "2.26.22",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.75.1",
|
||||
"version": "1.75.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.146.1",
|
||||
"version": "1.146.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.33",
|
||||
"version": "1.12.34",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.129.1",
|
||||
"version": "2.130.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface SharedVaultRemovedEventPayload {
|
||||
sharedVaultUuid: string
|
||||
vaultOwnerUuid: string
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.49",
|
||||
"version": "1.11.50",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.28",
|
||||
"version": "1.23.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.16.2",
|
||||
"version": "1.16.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.37.0",
|
||||
"version": "1.37.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.53",
|
||||
"version": "1.20.54",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
# [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.106.0",
|
||||
"version": "1.108.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -174,6 +174,7 @@ import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/Remov
|
||||
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
|
||||
@@ -890,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),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -91,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'),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface TransferSharedVaultItemsDTO {
|
||||
sharedVaultUuid: string
|
||||
fromUserUuid: string
|
||||
toUserUuid: string
|
||||
}
|
||||
@@ -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 } },
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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.')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.50",
|
||||
"version": "1.10.51",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user