Compare commits

...

7 Commits

Author SHA1 Message Date
standardci
4e602687d5 chore(release): publish new version
- @standardnotes/analytics@2.25.1
 - @standardnotes/api-gateway@1.66.0
 - @standardnotes/auth-server@1.125.1
 - @standardnotes/domain-core@1.23.1
 - @standardnotes/event-store@1.11.8
 - @standardnotes/files-server@1.19.10
 - @standardnotes/home-server@1.13.0
 - @standardnotes/revisions-server@1.25.1
 - @standardnotes/scheduler-server@1.20.10
 - @standardnotes/settings@1.21.15
 - @standardnotes/syncing-server@1.67.0
 - @standardnotes/websockets-server@1.10.3
2023-07-19 06:47:26 +00:00
Karol Sójko
d026152ac8 fix(syncing-server): add missing messages and key system identifier sql representations (#663) 2023-07-19 08:31:23 +02:00
Karol Sójko
3f21a358d2 feat(syncing-server): add persistence of shared vaults with users and invites + controllers (#662)
* feat(syncing-server): associating existing items with key systems and shared vaults

* fix(syncing-server): find item query

* feat(syncing-server): add persistence of shared vaults with users and invites

* feat: shared vault controllers
2023-07-19 07:28:03 +02:00
standardci
dc55e47c98 chore(release): publish new version
- @standardnotes/home-server@1.12.6
 - @standardnotes/syncing-server@1.66.0
2023-07-18 11:39:37 +00:00
Karol Sójko
3b804e2321 feat(syncing-server): associating existing items with key systems and shared vaults (#661)
* feat(syncing-server): associating existing items with key systems and shared vaults

* fix(syncing-server): find item query

* feat(syncing-server): add persistence of shared vaults with users and invites
2023-07-18 13:21:30 +02:00
standardci
b32f851a90 chore(release): publish new version
- @standardnotes/home-server@1.12.5
 - @standardnotes/syncing-server@1.65.0
2023-07-18 09:55:28 +00:00
Karol Sójko
479d20e76f feat(syncing-server): persisting shared vault and key system associations (#660) 2023-07-18 11:39:02 +02:00
101 changed files with 2113 additions and 175 deletions

View File

@@ -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.25.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.0...@standardnotes/analytics@2.25.1) (2023-07-19)
**Note:** Version bump only for package @standardnotes/analytics
# [2.25.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.9...@standardnotes/analytics@2.25.0) (2023-07-17)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.25.0",
"version": "2.25.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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.66.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.7...@standardnotes/api-gateway@1.66.0) (2023-07-19)
### Features
* **syncing-server:** add persistence of shared vaults with users and invites + controllers ([#662](https://github.com/standardnotes/api-gateway/issues/662)) ([3f21a35](https://github.com/standardnotes/api-gateway/commit/3f21a358d24d70daf541aa62dc86cd9e29500e62))
## [1.65.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.6...@standardnotes/api-gateway@1.65.7) (2023-07-17)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -16,8 +16,10 @@ import '../src/Controller/v1/OfflineController'
import '../src/Controller/v1/FilesController'
import '../src/Controller/v1/SubscriptionInvitesController'
import '../src/Controller/v1/AuthenticatorsController'
import '../src/Controller/v1/AsymmetricMessagesController'
import '../src/Controller/v1/MessagesController'
import '../src/Controller/v1/SharedVaultsController'
import '../src/Controller/v1/SharedVaultInvitesController'
import '../src/Controller/v1/SharedVaultUsersController'
import '../src/Controller/v2/PaymentsControllerV2'
import '../src/Controller/v2/ActionsControllerV2'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.65.7",
"version": "1.66.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -4,7 +4,7 @@ export * from './SubscriptionTokenAuthMiddleware'
export * from './TokenAuthenticationMethod'
export * from './WebSocketAuthMiddleware'
export * from './v1/ActionsController'
export * from './v1/AsymmetricMessagesController'
export * from './v1/MessagesController'
export * from './v1/AuthenticatorsController'
export * from './v1/FilesController'
export * from './v1/InvoicesController'

View File

@@ -1,17 +0,0 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, all } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
@controller('/v1/asymmetric-messages')
export class AsymmetricMessagesController extends BaseHttpController {
constructor(@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface) {
super()
}
@all('*', TYPES.RequiredCrossServiceTokenMiddleware)
async subscriptions(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callSyncingServer(request, response, request.path.replace('/v1/', ''), request.body)
}
}

View File

@@ -0,0 +1,70 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/messages', TYPES.RequiredCrossServiceTokenMiddleware)
export class MessagesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpGet('/')
async getMessages(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'messages/'),
request.body,
)
}
@httpGet('/outbound')
async getMessagesSent(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'messages/outbound'),
request.body,
)
}
@httpPost('/')
async sendMessage(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'messages/'),
request.body,
)
}
@httpDelete('/inbound')
async deleteMessagesSentToUser(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('DELETE', 'messages/inbound'),
request.body,
)
}
@httpDelete('/:messageUuid')
async deleteMessage(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'DELETE',
'messages/:messageUuid',
request.params.messageUuid,
),
request.body,
)
}
}

View File

@@ -0,0 +1,148 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.RequiredCrossServiceTokenMiddleware)
export class SharedVaultInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpPost('/:sharedVaultUuid/invites')
async createSharedVaultInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'POST',
'shared-vaults/:sharedVaultUuid/invites',
request.params.sharedVaultUuid,
),
request.body,
)
}
@httpPatch('/:sharedVaultUuid/invites/:inviteUuid')
async updateSharedVaultInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'PATCH',
'shared-vaults/:sharedVaultUuid/invites/:inviteUuid',
request.params.sharedVaultUuid,
request.params.inviteUuid,
),
request.body,
)
}
@httpPost('/:sharedVaultUuid/invites/:inviteUuid/accept')
async acceptSharedVaultInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'POST',
'shared-vaults/:sharedVaultUuid/invites/:inviteUuid/accept',
request.params.sharedVaultUuid,
request.params.inviteUuid,
),
request.body,
)
}
@httpPost('/:sharedVaultUuid/invites/:inviteUuid/decline')
async declineSharedVaultInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'POST',
'shared-vaults/:sharedVaultUuid/invites/:inviteUuid/decline',
request.params.sharedVaultUuid,
request.params.inviteUuid,
),
request.body,
)
}
@httpDelete('/invites/inbound')
async deleteInboundUserInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('DELETE', 'shared-vaults/invites/inbound'),
request.body,
)
}
@httpGet('/invites/outbound')
async getOutboundUserInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'shared-vaults/invites/outbound'),
request.body,
)
}
@httpGet('/invites')
async getUserInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'shared-vaults/invites'),
request.body,
)
}
@httpGet('/:sharedVaultUuid/invites')
async getSharedVaultInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'GET',
'shared-vaults/:sharedVaultUuid/invites',
request.params.sharedVaultUuid,
),
request.body,
)
}
@httpDelete('/:sharedVaultUuid/invites/:inviteUuid')
async deleteSharedVaultInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'DELETE',
'shared-vaults/:sharedVaultUuid/invites/:inviteUuid',
request.params.sharedVaultUuid,
request.params.inviteUuid,
),
request.body,
)
}
@httpDelete('/:sharedVaultUuid/invites')
async deleteAllSharedVaultInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'DELETE',
'shared-vaults/:sharedVaultUuid/invites',
request.params.sharedVaultUuid,
),
request.body,
)
}
}

View File

@@ -0,0 +1,45 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.RequiredCrossServiceTokenMiddleware)
export class SharedVaultUsersController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpGet('/')
async getSharedVaultUsers(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'GET',
'/shared-vaults/:sharedVaultUuid/users',
request.params.sharedVaultUuid,
),
request.body,
)
}
@httpDelete('/:userUuid')
async removeUserFromSharedVault(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'DELETE',
'/shared-vaults/:sharedVaultUuid/users/:userUuid',
request.params.sharedVaultUuid,
request.params.userUuid,
),
request.body,
)
}
}

View File

@@ -1,17 +1,64 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, all } from 'inversify-express-utils'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults')
@controller('/v1/shared-vaults', TYPES.RequiredCrossServiceTokenMiddleware)
export class SharedVaultsController extends BaseHttpController {
constructor(@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface) {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@all('*', TYPES.RequiredCrossServiceTokenMiddleware)
async subscriptions(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callSyncingServer(request, response, request.path.replace('/v1/', ''), request.body)
@httpGet('/')
async getSharedVaults(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'shared-vaults/'),
request.body,
)
}
@httpPost('/')
async createSharedVault(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'shared-vaults/'),
request.body,
)
}
@httpDelete('/:sharedVaultUuid')
async deleteSharedVault(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'DELETE',
'shared-vaults/:sharedVaultUuid',
request.params.sharedVaultUuid,
),
request.body,
)
}
@httpPost('/:sharedVaultUuid/valet-tokens')
async createValetTokenForSharedVaultFile(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'POST',
'shared-vaults/:sharedVaultUuid/valet-tokens',
request.params.sharedVaultUuid,
),
request.body,
)
}
}

View File

@@ -62,6 +62,31 @@ export class EndpointResolver implements EndpointResolverInterface {
['[GET]:items/:itemUuid/revisions', 'revisions.revisions.getRevisions'],
['[GET]:items/:itemUuid/revisions/:id', 'revisions.revisions.getRevision'],
['[DELETE]:items/:itemUuid/revisions/:id', 'revisions.revisions.deleteRevision'],
// Messages Controller
['[GET]:messages/', 'sync.messages.get-received'],
['[GET]:messages/outbound', 'sync.messages.get-sent'],
['[POST]:messages/', 'sync.messages.send'],
['[DELETE]:messages/inbound', 'sync.messages.delete-all'],
['[DELETE]:messages/:messageUuid', 'sync.messages.delete'],
// Shared Vaults Controller
['[GET]:shared-vaults/', 'sync.shared-vaults.get-vaults'],
['[POST]:shared-vaults/', 'sync.shared-vaults.create-vault'],
['[DELETE]:shared-vaults/:sharedVaultUuid', 'sync.shared-vaults.delete-vault'],
['[POST]:shared-vaults/:sharedVaultUuid/valet-tokens', 'sync.shared-vaults.create-file-valet-token'],
// Shared Vault Invites Controller
['[POST]:shared-vaults/:sharedVaultUuid/invites', 'sync.shared-vault-invites.create'],
['[PATCH]:shared-vaults/:sharedVaultUuid/invites/:inviteUuid', 'sync.shared-vault-invites.update'],
['[POST]:shared-vaults/:sharedVaultUuid/invites/:inviteUuid/accept', 'sync.shared-vault-invites.accept'],
['[POST]:shared-vaults/:sharedVaultUuid/invites/:inviteUuid/decline', 'sync.shared-vault-invites.decline'],
['[DELETE]:shared-vaults/invites/inbound', 'sync.shared-vault-invites.delete-inbound'],
['[GET]:shared-vaults/invites/outbound', 'sync.shared-vault-invites.get-outbound'],
['[GET]:shared-vaults/invites', 'sync.shared-vault-invites.get-user-invites'],
['[GET]:shared-vaults/:sharedVaultUuid/invites', 'sync.shared-vault-invites.get-vault-invites'],
['[DELETE]:shared-vaults/:sharedVaultUuid/invites/:inviteUuid', 'sync.shared-vault-invites.delete-invite'],
['[DELETE]:shared-vaults/:sharedVaultUuid/invites', 'sync.shared-vault-invites.delete-all'],
// Shared Vault Users Controller
['[GET]:shared-vaults/:sharedVaultUuid/users', 'sync.shared-vault-users.get-users'],
['[DELETE]:shared-vaults/:sharedVaultUuid/users/:userUuid', 'sync.shared-vault-users.remove-user'],
])
resolveEndpointOrMethodIdentifier(method: string, endpoint: string, ...params: string[]): string {

View File

@@ -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.125.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.125.0...@standardnotes/auth-server@1.125.1) (2023-07-19)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.125.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.124.2...@standardnotes/auth-server@1.125.0) (2023-07-17)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.125.0",
"version": "1.125.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.0...@standardnotes/domain-core@1.23.1) (2023-07-19)
### Bug Fixes
* **syncing-server:** add missing messages and key system identifier sql representations ([#663](https://github.com/standardnotes/server/issues/663)) ([d026152](https://github.com/standardnotes/server/commit/d026152ac8cb2ecda2eee8d3f7385d655b210938))
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.22.0...@standardnotes/domain-core@1.23.0) (2023-07-17)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.23.0",
"version": "1.23.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -78,4 +78,18 @@ describe('Validator', () => {
expect(Validator.isNotEmpty(value).isFailed()).toBeTruthy()
}
})
describe('is not empty string', () => {
it('should not validate invalid string', () => {
expect(Validator.isNotEmptyString(123 as unknown as string).isFailed()).toBeTruthy()
})
it('should not validate an empty string', () => {
expect(Validator.isNotEmptyString('').isFailed()).toBeTruthy()
})
it('should validate a string', () => {
expect(Validator.isNotEmptyString('foo').isFailed()).toBeFalsy()
})
})
})

View File

@@ -42,4 +42,18 @@ export class Validator {
return Result.ok()
}
static isNotEmptyString(value: unknown): Result<string> {
const isStringResult = Validator.isString(value)
if (isStringResult.isFailed()) {
return isStringResult
}
const isNotEmptyResult = Validator.isNotEmpty(value)
if (isNotEmptyResult.isFailed()) {
return isNotEmptyResult
}
return Result.ok()
}
}

View File

@@ -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.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.7...@standardnotes/event-store@1.11.8) (2023-07-19)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.7](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.6...@standardnotes/event-store@1.11.7) (2023-07-17)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -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.19.10](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.9...@standardnotes/files-server@1.19.10) (2023-07-19)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.9](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.8...@standardnotes/files-server@1.19.9) (2023-07-17)
**Note:** Version bump only for package @standardnotes/files-server

View File

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

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.6...@standardnotes/home-server@1.13.0) (2023-07-19)
### Features
* **syncing-server:** add persistence of shared vaults with users and invites + controllers ([#662](https://github.com/standardnotes/server/issues/662)) ([3f21a35](https://github.com/standardnotes/server/commit/3f21a358d24d70daf541aa62dc86cd9e29500e62))
## [1.12.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.5...@standardnotes/home-server@1.12.6) (2023-07-18)
**Note:** Version bump only for package @standardnotes/home-server
## [1.12.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.4...@standardnotes/home-server@1.12.5) (2023-07-18)
**Note:** Version bump only for package @standardnotes/home-server
## [1.12.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.3...@standardnotes/home-server@1.12.4) (2023-07-17)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -179,6 +179,8 @@ export class HomeServer implements HomeServerInterface {
return Result.ok('Server started.')
} catch (error) {
console.error((error as Error).stack)
return Result.fail((error as Error).message)
}
}

View File

@@ -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.25.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.0...@standardnotes/revisions-server@1.25.1) (2023-07-19)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.24.1...@standardnotes/revisions-server@1.25.0) (2023-07-17)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.25.0",
"version": "1.25.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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.10](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.9...@standardnotes/scheduler-server@1.20.10) (2023-07-19)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.9](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.8...@standardnotes/scheduler-server@1.20.9) (2023-07-17)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

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

View File

@@ -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.21.15](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.14...@standardnotes/settings@1.21.15) (2023-07-19)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.14](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.13...@standardnotes/settings@1.21.14) (2023-07-17)
**Note:** Version bump only for package @standardnotes/settings

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/settings",
"version": "1.21.14",
"version": "1.21.15",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -49,3 +49,6 @@ NEW_RELIC_LOG_LEVEL=info
# (Optional) Revision Dumps
FILE_UPLOAD_PATH=
VALET_TOKEN_SECRET=change-me-!
VALET_TOKEN_TTL=7200

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.67.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.66.0...@standardnotes/syncing-server@1.67.0) (2023-07-19)
### Bug Fixes
* **syncing-server:** add missing messages and key system identifier sql representations ([#663](https://github.com/standardnotes/syncing-server-js/issues/663)) ([d026152](https://github.com/standardnotes/syncing-server-js/commit/d026152ac8cb2ecda2eee8d3f7385d655b210938))
### Features
* **syncing-server:** add persistence of shared vaults with users and invites + controllers ([#662](https://github.com/standardnotes/syncing-server-js/issues/662)) ([3f21a35](https://github.com/standardnotes/syncing-server-js/commit/3f21a358d24d70daf541aa62dc86cd9e29500e62))
# [1.66.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.65.0...@standardnotes/syncing-server@1.66.0) (2023-07-18)
### Features
* **syncing-server:** associating existing items with key systems and shared vaults ([#661](https://github.com/standardnotes/syncing-server-js/issues/661)) ([3b804e2](https://github.com/standardnotes/syncing-server-js/commit/3b804e2321f9d8444722f1d336689e12598121ca))
# [1.65.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.64.0...@standardnotes/syncing-server@1.65.0) (2023-07-18)
### Features
* **syncing-server:** persisting shared vault and key system associations ([#660](https://github.com/standardnotes/syncing-server-js/issues/660)) ([479d20e](https://github.com/standardnotes/syncing-server-js/commit/479d20e76f7766006dfa34a0d14401b090dec775))
# [1.64.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.63.1...@standardnotes/syncing-server@1.64.0) (2023-07-17)
### Features

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultAndKeySystemAssociations1689671563305 implements MigrationInterface {
name = 'AddSharedVaultAndKeySystemAssociations1689671563305'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `shared_vault_associations` (`uuid` varchar(36) NOT NULL, `shared_vault_uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `last_edited_by` varchar(36) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `shared_vault_uuid_on_shared_vault_associations` (`shared_vault_uuid`), INDEX `item_uuid_on_shared_vault_associations` (`item_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
await queryRunner.query(
'CREATE TABLE `key_system_associations` (`uuid` varchar(36) NOT NULL, `key_system_uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `key_system_uuid_on_key_system_associations` (`key_system_uuid`), INDEX `item_uuid_on_key_system_associations` (`item_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `item_uuid_on_key_system_associations` ON `key_system_associations`')
await queryRunner.query('DROP INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations`')
await queryRunner.query('DROP TABLE `key_system_associations`')
await queryRunner.query('DROP INDEX `item_uuid_on_shared_vault_associations` ON `shared_vault_associations`')
await queryRunner.query(
'DROP INDEX `shared_vault_uuid_on_shared_vault_associations` ON `shared_vault_associations`',
)
await queryRunner.query('DROP TABLE `shared_vault_associations`')
}
}

View File

@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultsWithUsersAndInvites1689677728282 implements MigrationInterface {
name = 'AddSharedVaultsWithUsersAndInvites1689677728282'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `shared_vaults` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `file_upload_bytes_used` int NOT NULL, `file_upload_bytes_limit` int NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `user_uuid_on_shared_vaults` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
await queryRunner.query(
'CREATE TABLE `shared_vault_users` (`uuid` varchar(36) NOT NULL, `shared_vault_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `permission` varchar(24) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `shared_vault_uuid_on_shared_vault_users` (`shared_vault_uuid`), INDEX `user_uuid_on_shared_vault_users` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
await queryRunner.query(
'CREATE TABLE `shared_vault_invites` (`uuid` varchar(36) NOT NULL, `shared_vault_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `sender_uuid` varchar(36) NOT NULL, `encrypted_message` text NOT NULL, `permission` varchar(24) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `shared_vault_uuid_on_shared_vault_invites` (`shared_vault_uuid`), INDEX `user_uuid_on_shared_vault_invites` (`user_uuid`), INDEX `sender_uuid_on_shared_vault_invites` (`sender_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `sender_uuid_on_shared_vault_invites` ON `shared_vault_invites`')
await queryRunner.query('DROP INDEX `user_uuid_on_shared_vault_invites` ON `shared_vault_invites`')
await queryRunner.query('DROP INDEX `shared_vault_uuid_on_shared_vault_invites` ON `shared_vault_invites`')
await queryRunner.query('DROP TABLE `shared_vault_invites`')
await queryRunner.query('DROP INDEX `user_uuid_on_shared_vault_users` ON `shared_vault_users`')
await queryRunner.query('DROP INDEX `shared_vault_uuid_on_shared_vault_users` ON `shared_vault_users`')
await queryRunner.query('DROP TABLE `shared_vault_users`')
await queryRunner.query('DROP INDEX `user_uuid_on_shared_vaults` ON `shared_vaults`')
await queryRunner.query('DROP TABLE `shared_vaults`')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddMessages1689745128577 implements MigrationInterface {
name = 'AddMessages1689745128577'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `messages` (`uuid` varchar(36) NOT NULL, `recipient_uuid` varchar(36) NOT NULL, `sender_uuid` varchar(36) NOT NULL, `encrypted_message` text NOT NULL, `replaceability_identifier` varchar(255) NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `recipient_uuid_on_messages` (`recipient_uuid`), INDEX `sender_uuid_on_messages` (`sender_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `sender_uuid_on_messages` ON `messages`')
await queryRunner.query('DROP INDEX `recipient_uuid_on_messages` ON `messages`')
await queryRunner.query('DROP TABLE `messages`')
}
}

View File

@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RenameKeyMessageIdentifier1689746180559 implements MigrationInterface {
name = 'RenameKeyMessageIdentifier1689746180559'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations`')
await queryRunner.query(
'ALTER TABLE `key_system_associations` CHANGE `key_system_uuid` `key_system_identifier` varchar(36) NOT NULL',
)
await queryRunner.query(
'CREATE INDEX `key_system_identifier_on_key_system_associations` ON `key_system_associations` (`key_system_identifier`)',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'DROP INDEX `key_system_identifier_on_key_system_associations` ON `key_system_associations`',
)
await queryRunner.query(
'ALTER TABLE `key_system_associations` CHANGE `key_system_identifier` `key_system_uuid` varchar(36) NOT NULL',
)
await queryRunner.query(
'CREATE INDEX `key_system_uuid_on_key_system_associations` ON `key_system_associations` (`key_system_uuid`)',
)
}
}

View File

@@ -0,0 +1,35 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultAndKeySystemAssociations1689672099829 implements MigrationInterface {
name = 'AddSharedVaultAndKeySystemAssociations1689672099829'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "shared_vault_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "last_edited_by" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'CREATE INDEX "shared_vault_uuid_on_shared_vault_associations" ON "shared_vault_associations" ("shared_vault_uuid") ',
)
await queryRunner.query(
'CREATE INDEX "item_uuid_on_shared_vault_associations" ON "shared_vault_associations" ("item_uuid") ',
)
await queryRunner.query(
'CREATE TABLE "key_system_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "key_system_uuid" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'CREATE INDEX "key_system_uuid_on_key_system_associations" ON "key_system_associations" ("key_system_uuid") ',
)
await queryRunner.query(
'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"')
await queryRunner.query('DROP INDEX "key_system_uuid_on_key_system_associations"')
await queryRunner.query('DROP TABLE "key_system_associations"')
await queryRunner.query('DROP INDEX "item_uuid_on_shared_vault_associations"')
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_shared_vault_associations"')
await queryRunner.query('DROP TABLE "shared_vault_associations"')
}
}

View File

@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultsWithUsersAndInvites1689677867175 implements MigrationInterface {
name = 'AddSharedVaultsWithUsersAndInvites1689677867175'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "shared_vaults" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "file_upload_bytes_used" integer NOT NULL, "file_upload_bytes_limit" integer NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "user_uuid_on_shared_vaults" ON "shared_vaults" ("user_uuid") ')
await queryRunner.query(
'CREATE TABLE "shared_vault_users" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36) NOT NULL, "permission" varchar(24) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'CREATE INDEX "shared_vault_uuid_on_shared_vault_users" ON "shared_vault_users" ("shared_vault_uuid") ',
)
await queryRunner.query('CREATE INDEX "user_uuid_on_shared_vault_users" ON "shared_vault_users" ("user_uuid") ')
await queryRunner.query(
'CREATE TABLE "shared_vault_invites" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36) NOT NULL, "sender_uuid" varchar(36) NOT NULL, "encrypted_message" text NOT NULL, "permission" varchar(24) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'CREATE INDEX "shared_vault_uuid_on_shared_vault_invites" ON "shared_vault_invites" ("shared_vault_uuid") ',
)
await queryRunner.query('CREATE INDEX "user_uuid_on_shared_vault_invites" ON "shared_vault_invites" ("user_uuid") ')
await queryRunner.query(
'CREATE INDEX "sender_uuid_on_shared_vault_invites" ON "shared_vault_invites" ("sender_uuid") ',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "sender_uuid_on_shared_vault_invites"')
await queryRunner.query('DROP INDEX "user_uuid_on_shared_vault_invites"')
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_shared_vault_invites"')
await queryRunner.query('DROP TABLE "shared_vault_invites"')
await queryRunner.query('DROP INDEX "user_uuid_on_shared_vault_users"')
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_shared_vault_users"')
await queryRunner.query('DROP TABLE "shared_vault_users"')
await queryRunner.query('DROP INDEX "user_uuid_on_shared_vaults"')
await queryRunner.query('DROP TABLE "shared_vaults"')
}
}

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddMessages1689744778643 implements MigrationInterface {
name = 'AddMessages1689744778643'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "messages" ("uuid" varchar PRIMARY KEY NOT NULL, "recipient_uuid" varchar(36) NOT NULL, "sender_uuid" varchar(36) NOT NULL, "encrypted_message" text NOT NULL, "replaceability_identifier" varchar(255), "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "recipient_uuid_on_messages" ON "messages" ("recipient_uuid") ')
await queryRunner.query('CREATE INDEX "sender_uuid_on_messages" ON "messages" ("sender_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "sender_uuid_on_messages"')
await queryRunner.query('DROP INDEX "recipient_uuid_on_messages"')
await queryRunner.query('DROP TABLE "messages"')
}
}

View File

@@ -0,0 +1,43 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RenameKeyMessageIdentifier1689746527310 implements MigrationInterface {
name = 'RenameKeyMessageIdentifier1689746527310'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "key_system_uuid_on_key_system_associations"')
await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"')
await queryRunner.query(
'CREATE TABLE "temporary_key_system_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "key_system_identifier" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'INSERT INTO "temporary_key_system_associations"("uuid", "key_system_identifier", "item_uuid", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "key_system_uuid", "item_uuid", "created_at_timestamp", "updated_at_timestamp" FROM "key_system_associations"',
)
await queryRunner.query('DROP TABLE "key_system_associations"')
await queryRunner.query('ALTER TABLE "temporary_key_system_associations" RENAME TO "key_system_associations"')
await queryRunner.query(
'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ',
)
await queryRunner.query(
'CREATE INDEX "key_system_identifier_on_key_system_associations" ON "key_system_associations" ("key_system_identifier") ',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "key_system_identifier_on_key_system_associations"')
await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"')
await queryRunner.query('ALTER TABLE "key_system_associations" RENAME TO "temporary_key_system_associations"')
await queryRunner.query(
'CREATE TABLE "key_system_associations" ("uuid" varchar PRIMARY KEY NOT NULL, "key_system_uuid" varchar(36) NOT NULL, "item_uuid" varchar(36) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'INSERT INTO "key_system_associations"("uuid", "key_system_uuid", "item_uuid", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "key_system_identifier", "item_uuid", "created_at_timestamp", "updated_at_timestamp" FROM "temporary_key_system_associations"',
)
await queryRunner.query('DROP TABLE "temporary_key_system_associations"')
await queryRunner.query(
'CREATE INDEX "item_uuid_on_key_system_associations" ON "key_system_associations" ("item_uuid") ',
)
await queryRunner.query(
'CREATE INDEX "key_system_uuid_on_key_system_associations" ON "key_system_associations" ("key_system_uuid") ',
)
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.64.0",
"version": "1.67.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -78,6 +78,75 @@ import { SaveItems } from '../Domain/UseCase/Syncing/SaveItems/SaveItems'
import { ItemHashHttpMapper } from '../Mapping/Http/ItemHashHttpMapper'
import { ItemHash } from '../Domain/Item/ItemHash'
import { ItemHashHttpRepresentation } from '../Mapping/Http/ItemHashHttpRepresentation'
import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation'
import { SharedVaultAssociation } from '../Domain/SharedVault/SharedVaultAssociation'
import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation'
import { SharedVaultAssociationPersistenceMapper } from '../Mapping/Persistence/SharedVaultAssociationPersistenceMapper'
import { TypeORMKeySystemAssociationRepository } from '../Infra/TypeORM/TypeORMKeySystemAssociationRepository'
import { SharedVaultAssociationRepositoryInterface } from '../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
import { TypeORMSharedVaultAssociationRepository } from '../Infra/TypeORM/TypeORMSharedVaultAssociationRepository'
import { KeySystemAssociation } from '../Domain/KeySystem/KeySystemAssociation'
import { KeySystemAssociationRepositoryInterface } from '../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
import { KeySystemAssociationPersistenceMapper } from '../Mapping/Persistence/KeySystemAssociationPersistenceMapper'
import { HomeServerSharedVaultInvitesController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSharedVaultInvitesController'
import { InviteUserToSharedVault } from '../Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault'
import { TypeORMSharedVaultRepository } from '../Infra/TypeORM/TypeORMSharedVaultRepository'
import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
import { SharedVaultRepositoryInterface } from '../Domain/SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultPersistenceMapper } from '../Mapping/Persistence/SharedVaultPersistenceMapper'
import { SharedVault } from '../Domain/SharedVault/SharedVault'
import { SharedVaultUser } from '../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserPersistenceMapper } from '../Mapping/Persistence/SharedVaultUserPersistenceMapper'
import { SharedVaultInvite } from '../Domain/SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultInvitePersistenceMapper } from '../Mapping/Persistence/SharedVaultInvitePersistenceMapper'
import { SharedVaultUserRepositoryInterface } from '../Domain/SharedVault/User/SharedVaultUserRepositoryInterface'
import { TypeORMSharedVaultUserRepository } from '../Infra/TypeORM/TypeORMSharedVaultUserRepository'
import { SharedVaultInviteRepositoryInterface } from '../Domain/SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { TypeORMSharedVaultInviteRepository } from '../Infra/TypeORM/TypeORMSharedVaultInviteRepository'
import { UpdateSharedVaultInvite } from '../Domain/UseCase/SharedVaults/UpdateSharedVaultInvite/UpdateSharedVaultInvite'
import { AcceptInviteToSharedVault } from '../Domain/UseCase/SharedVaults/AcceptInviteToSharedVault/AcceptInviteToSharedVault'
import { AddUserToSharedVault } from '../Domain/UseCase/SharedVaults/AddUserToSharedVault/AddUserToSharedVault'
import { DeclineInviteToSharedVault } from '../Domain/UseCase/SharedVaults/DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser'
import { DeleteSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser'
import { GetSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser'
import { GetSharedVaultInvitesSentToUser } from '../Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
import { HomeServerSharedVaultUsersController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSharedVaultUsersController'
import { GetSharedVaultUsers } from '../Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers'
import { RemoveUserFromSharedVault } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault'
import { AddNotificationForUser } from '../Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser'
import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
import { NotificationRepositoryInterface } from '../Domain/Notifications/NotificationRepositoryInterface'
import { TypeORMNotificationRepository } from '../Infra/TypeORM/TypeORMNotificationRepository'
import { NotificationPersistenceMapper } from '../Mapping/Persistence/NotificationPersistenceMapper'
import { Notification } from '../Domain/Notifications/Notification'
import { SharedVaultUserHttpRepresentation } from '../Mapping/Http/SharedVaultUserHttpRepresentation'
import { SharedVaultUserHttpMapper } from '../Mapping/Http/SharedVaultUserHttpMapper'
import { HomeServerSharedVaultsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSharedVaultsController'
import { GetSharedVaults } from '../Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults'
import { CreateSharedVault } from '../Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault'
import { DeleteSharedVault } from '../Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault'
import { CreateSharedVaultFileValetToken } from '../Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken'
import { SharedVaultValetTokenData, TokenEncoder, TokenEncoderInterface } from '@standardnotes/security'
import { SharedVaultHttpRepresentation } from '../Mapping/Http/SharedVaultHttpRepresentation'
import { SharedVaultHttpMapper } from '../Mapping/Http/SharedVaultHttpMapper'
import { SharedVaultInviteHttpRepresentation } from '../Mapping/Http/SharedVaultInviteHttpRepresentation'
import { SharedVaultInviteHttpMapper } from '../Mapping/Http/SharedVaultInviteHttpMapper'
import { HomeServerMessagesController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerMessagesController'
import { GetMessagesSentToUser } from '../Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage'
import { MessageRepositoryInterface } from '../Domain/Message/MessageRepositoryInterface'
import { TypeORMMessageRepository } from '../Infra/TypeORM/TypeORMMessageRepository'
import { Message } from '../Domain/Message/Message'
import { MessagePersistenceMapper } from '../Mapping/Persistence/MessagePersistenceMapper'
import { GetMessagesSentByUser } from '../Domain/UseCase/Messaging/GetMessagesSentByUser/GetMessagesSentByUser'
import { SendMessageToUser } from '../Domain/UseCase/Messaging/SendMessageToUser/SendMessageToUser'
import { DeleteAllMessagesSentToUser } from '../Domain/UseCase/Messaging/DeleteAllMessagesSentToUser/DeleteAllMessagesSentToUser'
import { DeleteMessage } from '../Domain/UseCase/Messaging/DeleteMessage/DeleteMessage'
import { MessageHttpRepresentation } from '../Mapping/Http/MessageHttpRepresentation'
import { MessageHttpMapper } from '../Mapping/Http/MessageHttpMapper'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -232,19 +301,137 @@ export class ContainerConfigLoader {
container
.bind<MapperInterface<Item, ItemBackupRepresentation>>(TYPES.Sync_ItemBackupMapper)
.toConstantValue(new ItemBackupMapper(container.get(TYPES.Sync_Timer)))
container
.bind<MapperInterface<KeySystemAssociation, TypeORMKeySystemAssociation>>(
TYPES.Sync_KeySystemAssociationPersistenceMapper,
)
.toConstantValue(new KeySystemAssociationPersistenceMapper())
container
.bind<MapperInterface<SharedVaultAssociation, TypeORMSharedVaultAssociation>>(
TYPES.Sync_SharedVaultAssociationPersistenceMapper,
)
.toConstantValue(new SharedVaultAssociationPersistenceMapper())
container
.bind<MapperInterface<SharedVault, TypeORMSharedVault>>(TYPES.Sync_SharedVaultPersistenceMapper)
.toConstantValue(new SharedVaultPersistenceMapper())
container
.bind<MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>>(TYPES.Sync_SharedVaultUserPersistenceMapper)
.toConstantValue(new SharedVaultUserPersistenceMapper())
container
.bind<MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>>(TYPES.Sync_SharedVaultInvitePersistenceMapper)
.toConstantValue(new SharedVaultInvitePersistenceMapper())
container
.bind<MapperInterface<Notification, TypeORMNotification>>(TYPES.Sync_NotificationPersistenceMapper)
.toConstantValue(new NotificationPersistenceMapper())
container
.bind<MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>>(TYPES.Sync_SharedVaultUserHttpMapper)
.toConstantValue(new SharedVaultUserHttpMapper())
container
.bind<MapperInterface<SharedVault, SharedVaultHttpRepresentation>>(TYPES.Sync_SharedVaultHttpMapper)
.toConstantValue(new SharedVaultHttpMapper())
container
.bind<MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>>(
TYPES.Sync_SharedVaultInviteHttpMapper,
)
.toConstantValue(new SharedVaultInviteHttpMapper())
container
.bind<MapperInterface<Message, TypeORMMessage>>(TYPES.Sync_MessagePersistenceMapper)
.toConstantValue(new MessagePersistenceMapper())
container
.bind<MapperInterface<Message, MessageHttpRepresentation>>(TYPES.Sync_MessageHttpMapper)
.toConstantValue(new MessageHttpMapper())
// ORM
container
.bind<Repository<TypeORMItem>>(TYPES.Sync_ORMItemRepository)
.toDynamicValue(() => appDataSource.getRepository(TypeORMItem))
container
.bind<Repository<TypeORMSharedVaultAssociation>>(TYPES.Sync_ORMSharedVaultAssociationRepository)
.toConstantValue(appDataSource.getRepository(TypeORMSharedVaultAssociation))
container
.bind<Repository<TypeORMKeySystemAssociation>>(TYPES.Sync_ORMKeySystemAssociationRepository)
.toConstantValue(appDataSource.getRepository(TypeORMKeySystemAssociation))
container
.bind<Repository<TypeORMSharedVault>>(TYPES.Sync_ORMSharedVaultRepository)
.toConstantValue(appDataSource.getRepository(TypeORMSharedVault))
container
.bind<Repository<TypeORMSharedVaultInvite>>(TYPES.Sync_ORMSharedVaultInviteRepository)
.toConstantValue(appDataSource.getRepository(TypeORMSharedVaultInvite))
container
.bind<Repository<TypeORMSharedVaultUser>>(TYPES.Sync_ORMSharedVaultUserRepository)
.toConstantValue(appDataSource.getRepository(TypeORMSharedVaultUser))
container
.bind<Repository<TypeORMNotification>>(TYPES.Sync_ORMNotificationRepository)
.toConstantValue(appDataSource.getRepository(TypeORMNotification))
container
.bind<Repository<TypeORMMessage>>(TYPES.Sync_ORMMessageRepository)
.toConstantValue(appDataSource.getRepository(TypeORMMessage))
// Repositories
container
.bind<KeySystemAssociationRepositoryInterface>(TYPES.Sync_KeySystemAssociationRepository)
.toConstantValue(
new TypeORMKeySystemAssociationRepository(
container.get(TYPES.Sync_ORMKeySystemAssociationRepository),
container.get(TYPES.Sync_KeySystemAssociationPersistenceMapper),
),
)
container
.bind<SharedVaultAssociationRepositoryInterface>(TYPES.Sync_SharedVaultAssociationRepository)
.toConstantValue(
new TypeORMSharedVaultAssociationRepository(
container.get(TYPES.Sync_ORMSharedVaultAssociationRepository),
container.get(TYPES.Sync_SharedVaultAssociationPersistenceMapper),
),
)
container
.bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository)
.toConstantValue(
new TypeORMItemRepository(
container.get(TYPES.Sync_ORMItemRepository),
container.get(TYPES.Sync_ItemPersistenceMapper),
container.get(TYPES.Sync_KeySystemAssociationRepository),
container.get(TYPES.Sync_SharedVaultAssociationRepository),
),
)
container
.bind<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository)
.toConstantValue(
new TypeORMSharedVaultRepository(
container.get(TYPES.Sync_ORMSharedVaultRepository),
container.get(TYPES.Sync_SharedVaultPersistenceMapper),
),
)
container
.bind<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository)
.toConstantValue(
new TypeORMSharedVaultUserRepository(
container.get(TYPES.Sync_ORMSharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultUserPersistenceMapper),
),
)
container
.bind<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository)
.toConstantValue(
new TypeORMSharedVaultInviteRepository(
container.get(TYPES.Sync_ORMSharedVaultInviteRepository),
container.get(TYPES.Sync_SharedVaultInvitePersistenceMapper),
),
)
container
.bind<NotificationRepositoryInterface>(TYPES.Sync_NotificationRepository)
.toConstantValue(
new TypeORMNotificationRepository(
container.get(TYPES.Sync_ORMNotificationRepository),
container.get(TYPES.Sync_NotificationPersistenceMapper),
),
)
container
.bind<MessageRepositoryInterface>(TYPES.Sync_MessageRepository)
.toConstantValue(
new TypeORMMessageRepository(
container.get(TYPES.Sync_ORMMessageRepository),
container.get(TYPES.Sync_MessagePersistenceMapper),
),
)
@@ -292,6 +479,14 @@ export class ContainerConfigLoader {
.toConstantValue(
env.get('MAX_ITEMS_LIMIT', true) ? +env.get('MAX_ITEMS_LIMIT', true) : this.DEFAULT_MAX_ITEMS_LIMIT,
)
container.bind(TYPES.Sync_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
container
.bind(TYPES.Sync_VALET_TOKEN_TTL)
.toConstantValue(env.get('VALET_TOKEN_TTL', true) ? +env.get('VALET_TOKEN_TTL', true) : 7200)
container
.bind<TokenEncoderInterface<SharedVaultValetTokenData>>(TYPES.Sync_SharedVaultValetTokenEncoder)
.toConstantValue(new TokenEncoder<SharedVaultValetTokenData>(container.get(TYPES.Sync_VALET_TOKEN_SECRET)))
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toConstantValue(new OwnershipFilter())
container
@@ -370,6 +565,146 @@ export class ContainerConfigLoader {
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.Sync_ItemRepository))
})
container
.bind<InviteUserToSharedVault>(TYPES.Sync_InviteUserToSharedVault)
.toConstantValue(
new InviteUserToSharedVault(
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_SharedVaultInviteRepository),
container.get(TYPES.Sync_Timer),
),
)
container
.bind<UpdateSharedVaultInvite>(TYPES.Sync_UpdateSharedVaultInvite)
.toConstantValue(
new UpdateSharedVaultInvite(
container.get(TYPES.Sync_SharedVaultInviteRepository),
container.get(TYPES.Sync_Timer),
),
)
container
.bind<AddUserToSharedVault>(TYPES.Sync_AddUserToSharedVault)
.toConstantValue(
new AddUserToSharedVault(
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_Timer),
),
)
container
.bind<AcceptInviteToSharedVault>(TYPES.Sync_AcceptInviteToSharedVault)
.toConstantValue(
new AcceptInviteToSharedVault(
container.get(TYPES.Sync_AddUserToSharedVault),
container.get(TYPES.Sync_SharedVaultInviteRepository),
),
)
container
.bind<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault)
.toConstantValue(new DeclineInviteToSharedVault(container.get(TYPES.Sync_SharedVaultInviteRepository)))
container
.bind<DeleteSharedVaultInvitesToUser>(TYPES.Sync_DeleteSharedVaultInvitesToUser)
.toConstantValue(
new DeleteSharedVaultInvitesToUser(
container.get(TYPES.Sync_SharedVaultInviteRepository),
container.get(TYPES.Sync_DeclineInviteToSharedVault),
),
)
container
.bind<DeleteSharedVaultInvitesSentByUser>(TYPES.Sync_DeleteSharedVaultInvitesSentByUser)
.toConstantValue(
new DeleteSharedVaultInvitesSentByUser(
container.get(TYPES.Sync_SharedVaultInviteRepository),
container.get(TYPES.Sync_DeclineInviteToSharedVault),
),
)
container
.bind<GetSharedVaultInvitesSentByUser>(TYPES.Sync_GetSharedVaultInvitesSentByUser)
.toConstantValue(new GetSharedVaultInvitesSentByUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
container
.bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
.toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
container
.bind<GetSharedVaultUsers>(TYPES.Sync_GetSharedVaultUsers)
.toConstantValue(
new GetSharedVaultUsers(
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultRepository),
),
)
container
.bind<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser)
.toConstantValue(
new AddNotificationForUser(container.get(TYPES.Sync_NotificationRepository), container.get(TYPES.Sync_Timer)),
)
container
.bind<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser)
.toConstantValue(
new RemoveUserFromSharedVault(
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_AddNotificationForUser),
),
)
container
.bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
.toConstantValue(
new GetSharedVaults(
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultRepository),
),
)
container
.bind<CreateSharedVault>(TYPES.Sync_CreateSharedVault)
.toConstantValue(
new CreateSharedVault(
container.get(TYPES.Sync_AddUserToSharedVault),
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_Timer),
),
)
container
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
.toConstantValue(
new DeleteSharedVault(
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultInviteRepository),
container.get(TYPES.Sync_RemoveSharedVaultUser),
),
)
container
.bind<CreateSharedVaultFileValetToken>(TYPES.Sync_CreateSharedVaultFileValetToken)
.toConstantValue(
new CreateSharedVaultFileValetToken(
container.get(TYPES.Sync_SharedVaultRepository),
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultValetTokenEncoder),
container.get(TYPES.Sync_VALET_TOKEN_TTL),
),
)
container
.bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
.toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
container
.bind<GetMessagesSentByUser>(TYPES.Sync_GetMessagesSentByUser)
.toConstantValue(new GetMessagesSentByUser(container.get(TYPES.Sync_MessageRepository)))
container
.bind<SendMessageToUser>(TYPES.Sync_SendMessageToUser)
.toConstantValue(
new SendMessageToUser(container.get(TYPES.Sync_MessageRepository), container.get(TYPES.Sync_Timer)),
)
container
.bind<DeleteMessage>(TYPES.Sync_DeleteMessage)
.toConstantValue(new DeleteMessage(container.get(TYPES.Sync_MessageRepository)))
container
.bind<DeleteAllMessagesSentToUser>(TYPES.Sync_DeleteAllMessagesSentToUser)
.toConstantValue(
new DeleteAllMessagesSentToUser(
container.get(TYPES.Sync_MessageRepository),
container.get(TYPES.Sync_DeleteMessage),
),
)
// Services
container
@@ -557,6 +892,58 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_ControllerContainer),
),
)
container
.bind<HomeServerSharedVaultInvitesController>(TYPES.Sync_HomeServerSharedVaultInvitesController)
.toConstantValue(
new HomeServerSharedVaultInvitesController(
container.get(TYPES.Sync_InviteUserToSharedVault),
container.get(TYPES.Sync_UpdateSharedVaultInvite),
container.get(TYPES.Sync_AcceptInviteToSharedVault),
container.get(TYPES.Sync_DeclineInviteToSharedVault),
container.get(TYPES.Sync_DeleteSharedVaultInvitesToUser),
container.get(TYPES.Sync_DeleteSharedVaultInvitesSentByUser),
container.get(TYPES.Sync_GetSharedVaultInvitesSentByUser),
container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
container.get(TYPES.Sync_SharedVaultInviteHttpMapper),
container.get(TYPES.Sync_ControllerContainer),
),
)
container
.bind<HomeServerSharedVaultUsersController>(TYPES.Sync_HomeServerSharedVaultUsersController)
.toConstantValue(
new HomeServerSharedVaultUsersController(
container.get(TYPES.Sync_GetSharedVaultUsers),
container.get(TYPES.Sync_RemoveSharedVaultUser),
container.get(TYPES.Sync_SharedVaultUserHttpMapper),
container.get(TYPES.Sync_ControllerContainer),
),
)
container
.bind<HomeServerSharedVaultsController>(TYPES.Sync_HomeServerSharedVaultsController)
.toConstantValue(
new HomeServerSharedVaultsController(
container.get(TYPES.Sync_GetSharedVaults),
container.get(TYPES.Sync_CreateSharedVault),
container.get(TYPES.Sync_DeleteSharedVault),
container.get(TYPES.Sync_CreateSharedVaultFileValetToken),
container.get(TYPES.Sync_SharedVaultHttpMapper),
container.get(TYPES.Sync_SharedVaultUserHttpMapper),
container.get(TYPES.Sync_ControllerContainer),
),
)
container
.bind<HomeServerMessagesController>(TYPES.Sync_HomeServerMessagesController)
.toConstantValue(
new HomeServerMessagesController(
container.get(TYPES.Sync_GetMessagesSentToUser),
container.get(TYPES.Sync_GetMessagesSentByUser),
container.get(TYPES.Sync_SendMessageToUser),
container.get(TYPES.Sync_DeleteAllMessagesSentToUser),
container.get(TYPES.Sync_DeleteMessage),
container.get(TYPES.Sync_MessageHttpMapper),
container.get(TYPES.Sync_ControllerContainer),
),
)
}
logger.debug('Configuration complete')

View File

@@ -4,6 +4,12 @@ import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { TypeORMItem } from '../Infra/TypeORM/TypeORMItem'
import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation'
import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation'
import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage'
export class AppDataSource {
private _dataSource: DataSource | undefined
@@ -33,7 +39,16 @@ export class AppDataSource {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [TypeORMItem, TypeORMNotification],
entities: [
TypeORMItem,
TypeORMNotification,
TypeORMSharedVaultAssociation,
TypeORMKeySystemAssociation,
TypeORMSharedVault,
TypeORMSharedVaultUser,
TypeORMSharedVaultInvite,
TypeORMMessage,
],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',

View File

@@ -8,8 +8,22 @@ const TYPES = {
Sync_Env: Symbol.for('Sync_Env'),
// Repositories
Sync_ItemRepository: Symbol.for('Sync_ItemRepository'),
Sync_KeySystemAssociationRepository: Symbol.for('Sync_KeySystemAssociationRepository'),
Sync_SharedVaultAssociationRepository: Symbol.for('Sync_SharedVaultAssociationRepository'),
Sync_SharedVaultRepository: Symbol.for('Sync_SharedVaultRepository'),
Sync_SharedVaultInviteRepository: Symbol.for('Sync_SharedVaultInviteRepository'),
Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'),
Sync_NotificationRepository: Symbol.for('Sync_NotificationRepository'),
Sync_MessageRepository: Symbol.for('Sync_MessageRepository'),
// ORM
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
Sync_ORMSharedVaultAssociationRepository: Symbol.for('Sync_ORMSharedVaultAssociationRepository'),
Sync_ORMKeySystemAssociationRepository: Symbol.for('Sync_ORMKeySystemAssociationRepository'),
Sync_ORMSharedVaultRepository: Symbol.for('Sync_ORMSharedVaultRepository'),
Sync_ORMSharedVaultInviteRepository: Symbol.for('Sync_ORMSharedVaultInviteRepository'),
Sync_ORMSharedVaultUserRepository: Symbol.for('Sync_ORMSharedVaultUserRepository'),
Sync_ORMNotificationRepository: Symbol.for('Sync_ORMNotificationRepository'),
Sync_ORMMessageRepository: Symbol.for('Sync_ORMMessageRepository'),
// Middleware
Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
// env vars
@@ -30,6 +44,8 @@ const TYPES = {
Sync_CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('Sync_CONTENT_SIZE_TRANSFER_LIMIT'),
Sync_MAX_ITEMS_LIMIT: Symbol.for('Sync_MAX_ITEMS_LIMIT'),
Sync_FILE_UPLOAD_PATH: Symbol.for('Sync_FILE_UPLOAD_PATH'),
Sync_VALET_TOKEN_SECRET: Symbol.for('Sync_VALET_TOKEN_SECRET'),
Sync_VALET_TOKEN_TTL: Symbol.for('Sync_VALET_TOKEN_TTL'),
// use cases
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
@@ -39,6 +55,8 @@ const TYPES = {
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
Sync_AddUserToSharedVault: Symbol.for('Sync_AddUserToSharedVault'),
Sync_AddNotificationForUser: Symbol.for('Sync_AddNotificationForUser'),
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
Sync_InviteUserToSharedVault: Symbol.for('Sync_InviteUserToSharedVault'),
Sync_UpdateSharedVaultInvite: Symbol.for('Sync_UpdateSharedVaultInvite'),
@@ -70,6 +88,7 @@ const TYPES = {
Sync_DomainEventMessageHandler: Symbol.for('Sync_DomainEventMessageHandler'),
Sync_HTTPClient: Symbol.for('Sync_HTTPClient'),
Sync_Timer: Symbol.for('Sync_Timer'),
Sync_SharedVaultValetTokenEncoder: Symbol.for('Sync_SharedVaultValetTokenEncoder'),
Sync_SyncResponseFactory20161215: Symbol.for('Sync_SyncResponseFactory20161215'),
Sync_SyncResponseFactory20200115: Symbol.for('Sync_SyncResponseFactory20200115'),
Sync_SyncResponseFactoryResolver: Symbol.for('Sync_SyncResponseFactoryResolver'),
@@ -84,10 +103,15 @@ const TYPES = {
Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'),
Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'),
Sync_HomeServerItemsController: Symbol.for('Sync_HomeServerItemsController'),
Sync_HomeServerSharedVaultInvitesController: Symbol.for('Sync_HomeServerSharedVaultInvitesController'),
Sync_HomeServerSharedVaultUsersController: Symbol.for('Sync_HomeServerSharedVaultUsersController'),
Sync_HomeServerSharedVaultsController: Symbol.for('Sync_HomeServerSharedVaultsController'),
Sync_HomeServerMessagesController: Symbol.for('Sync_HomeServerMessagesController'),
// Mapping
Sync_SharedVaultHttpMapper: Symbol.for('Sync_SharedVaultHttpMapper'),
Sync_SharedVaultUserHttpMapper: Symbol.for('Sync_SharedVaultUserHttpMapper'),
Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
Sync_MessagePersistenceMapper: Symbol.for('Sync_MessagePersistenceMapper'),
Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
@@ -95,6 +119,12 @@ const TYPES = {
Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'),
Sync_ItemConflictHttpMapper: Symbol.for('Sync_ItemConflictHttpMapper'),
Sync_ItemBackupMapper: Symbol.for('Sync_ItemBackupMapper'),
Sync_KeySystemAssociationPersistenceMapper: Symbol.for('Sync_KeySystemAssociationPersistenceMapper'),
Sync_SharedVaultAssociationPersistenceMapper: Symbol.for('Sync_SharedVaultAssociationPersistenceMapper'),
Sync_SharedVaultPersistenceMapper: Symbol.for('Sync_SharedVaultPersistenceMapper'),
Sync_SharedVaultUserPersistenceMapper: Symbol.for('Sync_SharedVaultUserPersistenceMapper'),
Sync_SharedVaultInvitePersistenceMapper: Symbol.for('Sync_SharedVaultInvitePersistenceMapper'),
Sync_NotificationPersistenceMapper: Symbol.for('Sync_NotificationPersistenceMapper'),
}
export default TYPES

View File

@@ -53,7 +53,7 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
event = {} as jest.Mocked<ItemRevisionCreationRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
itemUuid: '2-3-4',
itemUuid: '00000000-0000-0000-0000-000000000000',
}
event.meta = {
correlation: {
@@ -96,4 +96,13 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
it('should not create a revision if the item uuid is invalid', async () => {
event.payload.itemUuid = 'invalid-uuid'
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
})

View File

@@ -3,6 +3,7 @@ import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { Uuid } from '@standardnotes/domain-core'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
@@ -17,7 +18,13 @@ export class ItemRevisionCreationRequestedEventHandler implements DomainEventHan
) {}
async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {
const item = await this.itemRepository.findByUuid(event.payload.itemUuid)
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
if (itemUuidOrError.isFailed()) {
return
}
const itemUuid = itemUuidOrError.getValue()
const item = await this.itemRepository.findByUuid(itemUuid)
if (item === null) {
return
}

View File

@@ -1,8 +1,8 @@
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { ItemProps } from './ItemProps'
export class Item extends Entity<ItemProps> {
export class Item extends Aggregate<ItemProps> {
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -10,4 +10,12 @@ export class ItemHash extends ValueObject<ItemHashProps> {
static create(props: ItemHashProps): Result<ItemHash> {
return Result.ok<ItemHash>(new ItemHash(props))
}
representsASharedVaultItem(): boolean {
return this.props.shared_vault_uuid !== null
}
hasDedicatedKeySystemAssociation(): boolean {
return this.props.key_system_identifier !== null
}
}

View File

@@ -1,5 +1,8 @@
import { ContentType, Dates, Timestamps, Uuid } from '@standardnotes/domain-core'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
export interface ItemProps {
duplicateOf: Uuid | null
itemsKeyId: string | null
@@ -13,4 +16,6 @@ export interface ItemProps {
dates: Dates
timestamps: Timestamps
contentSize?: number
sharedVaultAssociation?: SharedVaultAssociation
keySystemAssociation?: KeySystemAssociation
}

View File

@@ -1,6 +1,8 @@
import { Uuid } from '@standardnotes/domain-core'
import { ReadStream } from 'fs'
import { Item } from './Item'
import { ItemQuery } from './ItemQuery'
import { ReadStream } from 'fs'
import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
export interface ItemRepositoryInterface {
@@ -15,7 +17,7 @@ export interface ItemRepositoryInterface {
findDatesForComputingIntegrityHash(userUuid: string): Promise<Array<{ updated_at_timestamp: number }>>
findItemsForComputingIntegrityPayloads(userUuid: string): Promise<ExtendedIntegrityPayload[]>
findByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Item | null>
findByUuid(uuid: string): Promise<Item | null>
findByUuid(uuid: Uuid): Promise<Item | null>
remove(item: Item): Promise<void>
save(item: Item): Promise<void>
markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>

View File

@@ -0,0 +1,7 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
export interface KeySystemAssociationProps {
itemUuid: Uuid
keySystemIdentifier: string
timestamps: Timestamps
}

View File

@@ -0,0 +1,16 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { KeySystemAssociation } from './KeySystemAssociation'
describe('KeySystemAssociation', () => {
it('should create an entity', () => {
const entityOrError = KeySystemAssociation.create({
timestamps: Timestamps.create(123456789, 123456789).getValue(),
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
})
})

View File

@@ -0,0 +1,13 @@
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { KeySystemAssociationProps } from './KeySystemAssocationProps'
export class KeySystemAssociation extends Entity<KeySystemAssociationProps> {
private constructor(props: KeySystemAssociationProps, id?: UniqueEntityId) {
super(props, id)
}
static create(props: KeySystemAssociationProps, id?: UniqueEntityId): Result<KeySystemAssociation> {
return Result.ok<KeySystemAssociation>(new KeySystemAssociation(props, id))
}
}

View File

@@ -0,0 +1,9 @@
import { Uuid } from '@standardnotes/domain-core'
import { KeySystemAssociation } from './KeySystemAssociation'
export interface KeySystemAssociationRepositoryInterface {
save(keySystem: KeySystemAssociation): Promise<void>
remove(keySystem: KeySystemAssociation): Promise<void>
findByItemUuid(itemUuid: Uuid): Promise<KeySystemAssociation | null>
}

View File

@@ -1,18 +0,0 @@
import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { SharedVaultItem } from './SharedVaultItem'
describe('SharedVaultItem', () => {
it('should create an entity', () => {
const entityOrError = SharedVaultItem.create({
itemId: new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
sharedVaultId: new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
keySystemIdentifier: 'key-system-identifier',
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123456789, 123456789).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
})
})

View File

@@ -1,13 +0,0 @@
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { SharedVaultItemProps } from './SharedVaultItemProps'
export class SharedVaultItem extends Entity<SharedVaultItemProps> {
private constructor(props: SharedVaultItemProps, id?: UniqueEntityId) {
super(props, id)
}
static create(props: SharedVaultItemProps, id?: UniqueEntityId): Result<SharedVaultItem> {
return Result.ok<SharedVaultItem>(new SharedVaultItem(props, id))
}
}

View File

@@ -1,9 +0,0 @@
import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
export interface SharedVaultItemProps {
sharedVaultId: UniqueEntityId
itemId: UniqueEntityId
keySystemIdentifier: string
lastEditedBy: Uuid
timestamps: Timestamps
}

View File

@@ -1,8 +0,0 @@
import { UniqueEntityId } from '@standardnotes/domain-core'
import { SharedVaultItem } from './SharedVaultItem'
export interface SharedVaultItemRepositoryInterface {
save(sharedVaultItem: SharedVaultItem): Promise<void>
findBySharedVaultId(sharedVaultId: UniqueEntityId): Promise<SharedVaultItem[]>
}

View File

@@ -3,13 +3,12 @@ import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVault } from './SharedVault'
describe('SharedVault', () => {
it('should create an aggregate', () => {
it('should create an entity', () => {
const entityOrError = SharedVault.create({
fileUploadBytesLimit: 1_000_000,
fileUploadBytesUsed: 0,
timestamps: Timestamps.create(123456789, 123456789).getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultItems: [],
})
expect(entityOrError.isFailed()).toBeFalsy()

View File

@@ -1,8 +1,8 @@
import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { SharedVaultProps } from './SharedVaultProps'
export class SharedVault extends Aggregate<SharedVaultProps> {
export class SharedVault extends Entity<SharedVaultProps> {
private constructor(props: SharedVaultProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -0,0 +1,17 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from './SharedVaultAssociation'
describe('SharedVaultAssociation', () => {
it('should create an entity', () => {
const entityOrError = SharedVaultAssociation.create({
timestamps: Timestamps.create(123456789, 123456789).getValue(),
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
})
})

View File

@@ -0,0 +1,13 @@
import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { SharedVaultAssociationProps } from './SharedVaultAssociationProps'
export class SharedVaultAssociation extends Aggregate<SharedVaultAssociationProps> {
private constructor(props: SharedVaultAssociationProps, id?: UniqueEntityId) {
super(props, id)
}
static create(props: SharedVaultAssociationProps, id?: UniqueEntityId): Result<SharedVaultAssociation> {
return Result.ok<SharedVaultAssociation>(new SharedVaultAssociation(props, id))
}
}

View File

@@ -0,0 +1,8 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
export interface SharedVaultAssociationProps {
lastEditedBy: Uuid
sharedVaultUuid: Uuid
itemUuid: Uuid
timestamps: Timestamps
}

View File

@@ -0,0 +1,9 @@
import { Uuid } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from './SharedVaultAssociation'
export interface SharedVaultAssociationRepositoryInterface {
save(sharedVaultAssociation: SharedVaultAssociation): Promise<void>
remove(sharedVaultAssociation: SharedVaultAssociation): Promise<void>
findByItemUuid(itemUuid: Uuid): Promise<SharedVaultAssociation | null>
}

View File

@@ -1,11 +1,8 @@
import { Uuid, Timestamps } from '@standardnotes/domain-core'
import { SharedVaultItem } from './Item/SharedVaultItem'
export interface SharedVaultProps {
userUuid: Uuid
fileUploadBytesUsed: number
fileUploadBytesLimit: number
timestamps: Timestamps
sharedVaultItems: SharedVaultItem[]
}

View File

@@ -32,7 +32,6 @@ export class CreateSharedVault implements UseCaseInterface<CreateSharedVaultResu
fileUploadBytesUsed: 0,
userUuid,
timestamps,
sharedVaultItems: [],
})
if (sharedVaultOrError.isFailed()) {
return Result.fail(sharedVaultOrError.getError())

View File

@@ -24,7 +24,6 @@ describe('CreateSharedVaultFileValetToken', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>

View File

@@ -31,7 +31,6 @@ describe('DeleteSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
@@ -116,7 +115,6 @@ describe('DeleteSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
const useCase = createUseCase()

View File

@@ -20,7 +20,6 @@ describe('GetSharedVaultUsers', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultUser = SharedVaultUser.create({
@@ -67,7 +66,6 @@ describe('GetSharedVaultUsers', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)

View File

@@ -29,7 +29,6 @@ describe('GetSharedVaults', () => {
timestamps: Timestamps.create(123, 123).getValue(),
fileUploadBytesLimit: 123,
fileUploadBytesUsed: 123,
sharedVaultItems: [],
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuids = jest.fn().mockResolvedValue([sharedVault])

View File

@@ -21,7 +21,6 @@ describe('InviteUserToSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
@@ -153,7 +152,6 @@ describe('InviteUserToSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('10000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)

View File

@@ -24,7 +24,6 @@ describe('RemoveUserFromSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
@@ -89,7 +88,6 @@ describe('RemoveUserFromSharedVault', () => {
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultItems: [],
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)

View File

@@ -56,7 +56,7 @@ describe('SaveItems', () => {
logger.error = jest.fn()
itemHash1 = ItemHash.create({
uuid: 'item-uuid',
uuid: '00000000-0000-0000-0000-000000000000',
user_uuid: 'user-uuid',
content: 'content',
content_type: ContentType.TYPES.Note,
@@ -197,7 +197,7 @@ describe('SaveItems', () => {
const result = await useCase.execute({
itemHashes: [itemHash1],
userUuid: 'user-uuid',
userUuid: '00000000-0000-0000-0000-000000000000',
apiVersion: '1',
readOnlyAccess: false,
sessionUuid: 'session-uuid',
@@ -208,6 +208,7 @@ describe('SaveItems', () => {
itemHash: itemHash1,
existingItem: savedItem,
sessionUuid: 'session-uuid',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
})
@@ -234,6 +235,29 @@ describe('SaveItems', () => {
])
})
it('should mark items as conflict if the item uuid is invalid', async () => {
const useCase = createUseCase()
itemRepository.findByUuid = jest.fn().mockResolvedValue(savedItem)
updateExistingItem.execute = jest.fn().mockResolvedValue(Result.fail('error'))
const result = await useCase.execute({
itemHashes: [ItemHash.create({ ...itemHash1.props, uuid: 'invalid-uuid' }).getValue()],
userUuid: 'user-uuid',
apiVersion: '1',
readOnlyAccess: false,
sessionUuid: 'session-uuid',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().conflicts).toEqual([
{
unsavedItem: ItemHash.create({ ...itemHash1.props, uuid: 'invalid-uuid' }).getValue(),
type: 'uuid_conflict',
},
])
})
it('should calculate the sync token based on existing and new items saved', async () => {
const useCase = createUseCase()
@@ -260,8 +284,8 @@ describe('SaveItems', () => {
const result = await useCase.execute({
itemHashes: [
itemHash1,
ItemHash.create({ ...itemHash1.props, uuid: 'item-uuid-2' }).getValue(),
ItemHash.create({ ...itemHash1.props, uuid: 'item-uuid-2' }).getValue(),
ItemHash.create({ ...itemHash1.props, uuid: '00000000-0000-0000-0000-000000000002' }).getValue(),
ItemHash.create({ ...itemHash1.props, uuid: '00000000-0000-0000-0000-000000000003' }).getValue(),
],
userUuid: 'user-uuid',
apiVersion: '2',

View File

@@ -1,4 +1,4 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SaveItemsResult } from './SaveItemsResult'
import { SaveItemsDTO } from './SaveItemsDTO'
@@ -40,7 +40,18 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
continue
}
const existingItem = await this.itemRepository.findByUuid(itemHash.props.uuid)
const itemUuidOrError = Uuid.create(itemHash.props.uuid)
if (itemUuidOrError.isFailed()) {
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
const itemUuid = itemUuidOrError.getValue()
const existingItem = await this.itemRepository.findByUuid(itemUuid)
const processingResult = await this.itemSaveValidator.validate({
userUuid: dto.userUuid,
apiVersion: dto.apiVersion,
@@ -63,6 +74,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
existingItem,
itemHash,
sessionUuid: dto.sessionUuid,
performingUserUuid: dto.userUuid,
})
if (udpatedItemOrError.isFailed()) {
this.logger.error(

View File

@@ -6,6 +6,8 @@ import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryIn
import { ItemHash } from '../../../Item/ItemHash'
import { ContentType, Dates, Result, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { Item } from '../../../Item/Item'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
describe('SaveNewItem', () => {
let itemRepository: ItemRepositoryInterface
@@ -38,7 +40,7 @@ describe('SaveNewItem', () => {
).getValue()
itemHash1 = ItemHash.create({
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
user_uuid: '00000000-0000-0000-0000-000000000000',
key_system_identifier: null,
shared_vault_uuid: null,
@@ -282,4 +284,149 @@ describe('SaveNewItem', () => {
mock.mockRestore()
})
it('returns a failure if the item hash has an invalid uuid', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
uuid: '1-2-3',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-00000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
})
describe('when item hash represents a shared vault item', () => {
it('returns a failure if the shared vault uuid is invalid', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '1-2-3',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
})
it('should create a shared vault association between the item and the shared vault', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.sharedVaultAssociation?.props.lastEditedBy.value).toEqual(
'00000000-0000-0000-0000-000000000000',
)
expect(itemRepository.save).toHaveBeenCalled()
})
it('should return a failure if it fails to create a shared vault association', async () => {
const mock = jest.spyOn(SharedVaultAssociation, 'create')
mock.mockImplementation(() => {
return Result.fail('Oops')
})
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})
describe('when item hash has a dedicated key system', () => {
it('should create a key system for the item if the item hash has information about a key system used for encryption', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
key_system_identifier: '00000000-0000-0000-0000-000000000001',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.keySystemAssociation?.props.itemUuid.value).toEqual(
'00000000-0000-0000-0000-000000000000',
)
expect(itemRepository.save).toHaveBeenCalled()
})
it('should return a failure if the item hash has an invalid key system identifier', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
key_system_identifier: 123 as unknown as string,
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
})
it('should return a failure if it fails to create a key system', async () => {
const mock = jest.spyOn(KeySystemAssociation, 'create')
mock.mockImplementation(() => {
return Result.fail('Oops')
})
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
key_system_identifier: '00000000-0000-0000-0000-000000000001',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})
})

View File

@@ -6,6 +6,7 @@ import {
UniqueEntityId,
UseCaseInterface,
Uuid,
Validator,
} from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
@@ -14,6 +15,8 @@ import { Item } from '../../../Item/Item'
import { SaveNewItemDTO } from './SaveNewItemDTO'
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
export class SaveNewItem implements UseCaseInterface<Item> {
constructor(
@@ -24,6 +27,12 @@ export class SaveNewItem implements UseCaseInterface<Item> {
) {}
async execute(dto: SaveNewItemDTO): Promise<Result<Item>> {
const uuidOrError = Uuid.create(dto.itemHash.props.uuid)
if (uuidOrError.isFailed()) {
return Result.fail(uuidOrError.getError())
}
const uuid = uuidOrError.getValue()
let updatedWithSession = null
if (dto.sessionUuid) {
const sessionUuidOrError = Uuid.create(dto.sessionUuid)
@@ -78,6 +87,51 @@ export class SaveNewItem implements UseCaseInterface<Item> {
}
const timestamps = timestampsOrError.getValue()
let sharedVaultAssociation = undefined
if (dto.itemHash.representsASharedVaultItem()) {
const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
lastEditedBy: userUuid,
sharedVaultUuid,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
itemUuid: uuid,
})
if (sharedVaultAssociationOrError.isFailed()) {
return Result.fail(sharedVaultAssociationOrError.getError())
}
sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
}
let keySystemAssociation = undefined
if (dto.itemHash.hasDedicatedKeySystemAssociation()) {
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
return Result.fail(keySystemIdentifiedValidationResult.getError())
}
const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
const keySystemAssociationOrError = KeySystemAssociation.create({
itemUuid: uuid,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
keySystemIdentifier,
})
if (keySystemAssociationOrError.isFailed()) {
return Result.fail(keySystemAssociationOrError.getError())
}
keySystemAssociation = keySystemAssociationOrError.getValue()
}
const itemOrError = Item.create(
{
updatedWithSession,
@@ -91,8 +145,10 @@ export class SaveNewItem implements UseCaseInterface<Item> {
deleted: dto.itemHash.props.deleted ?? false,
dates,
timestamps,
keySystemAssociation,
sharedVaultAssociation,
},
new UniqueEntityId(dto.itemHash.props.uuid),
new UniqueEntityId(uuid.value),
)
if (itemOrError.isFailed()) {
return Result.fail(itemOrError.getError())

View File

@@ -6,6 +6,8 @@ import { ItemHash } from '../../../Item/ItemHash'
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { UpdateExistingItem } from './UpdateExistingItem'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId, Result } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
describe('UpdateExistingItem', () => {
let itemRepository: ItemRepositoryInterface
@@ -87,6 +89,7 @@ describe('UpdateExistingItem', () => {
existingItem: item1,
itemHash: itemHash1,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
@@ -100,6 +103,7 @@ describe('UpdateExistingItem', () => {
existingItem: item1,
itemHash: itemHash1,
sessionUuid: 'invalid-uuid',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
@@ -115,6 +119,7 @@ describe('UpdateExistingItem', () => {
content_type: 'invalid',
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
@@ -130,6 +135,7 @@ describe('UpdateExistingItem', () => {
deleted: true,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
@@ -152,6 +158,7 @@ describe('UpdateExistingItem', () => {
duplicate_of: '00000000-0000-0000-0000-000000000001',
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
@@ -169,6 +176,7 @@ describe('UpdateExistingItem', () => {
duplicate_of: 'invalid-uuid',
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
@@ -185,6 +193,7 @@ describe('UpdateExistingItem', () => {
created_at_timestamp: 123,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
@@ -202,6 +211,7 @@ describe('UpdateExistingItem', () => {
created_at_timestamp: undefined,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
@@ -223,6 +233,7 @@ describe('UpdateExistingItem', () => {
updated_at_timestamp: 123,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
@@ -246,9 +257,203 @@ describe('UpdateExistingItem', () => {
updated_at_timestamp: 123,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
it('should return error if performing user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
existingItem: item1,
itemHash: itemHash1,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
describe('when item is associated to a shared vault', () => {
it('should add a shared vault association if item hash represents a shared vault item and the existing item is not already associated to the shared vault', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
expect(item1.props.sharedVaultAssociation?.props.sharedVaultUuid.value).toBe(
'00000000-0000-0000-0000-000000000000',
)
})
it('should not add a shared vault association if item hash represents a shared vault item and the existing item is already associated to the shared vault', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue()
item1.props.sharedVaultAssociation = SharedVaultAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
const idBefore = item1.props.sharedVaultAssociation?.id.toString()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
expect(item1.props.sharedVaultAssociation.id.toString()).toEqual(idBefore)
})
it('should return error if shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: 'invalid-uuid',
}).getValue()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if shared vault association could not be created', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue()
const mock = jest.spyOn(SharedVaultAssociation, 'create')
mock.mockImplementation(() => {
return Result.fail('Oops')
})
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})
describe('when item is associated to a key system', () => {
it('should add a key system association if item hash has a dedicated key system and the existing item is not already associated to the key system', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
key_system_identifier: '00000000-0000-0000-0000-000000000000',
}).getValue()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(item1.props.keySystemAssociation).not.toBeUndefined()
expect(item1.props.keySystemAssociation?.props.keySystemIdentifier).toBe('00000000-0000-0000-0000-000000000000')
})
it('should not add a key system association if item hash has a dedicated key system and the existing item is already associated to the key system', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
key_system_identifier: '00000000-0000-0000-0000-000000000000',
}).getValue()
item1.props.keySystemAssociation = KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
const idBefore = item1.props.keySystemAssociation?.id.toString()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(item1.props.keySystemAssociation).not.toBeUndefined()
expect(item1.props.keySystemAssociation.id.toString()).toEqual(idBefore)
})
it('should return error if key system identifier is invalid', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
key_system_identifier: 123 as unknown as string,
}).getValue()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if key system association could not be created', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
key_system_identifier: '00000000-0000-0000-0000-000000000000',
}).getValue()
const mock = jest.spyOn(KeySystemAssociation, 'create')
mock.mockImplementation(() => {
return Result.fail('Oops')
})
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})
})

View File

@@ -1,4 +1,4 @@
import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -6,6 +6,9 @@ import { Item } from '../../../Item/Item'
import { UpdateExistingItemDTO } from './UpdateExistingItemDTO'
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
import { ItemHash } from '../../../Item/ItemHash'
export class UpdateExistingItem implements UseCaseInterface<Item> {
constructor(
@@ -27,6 +30,12 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
}
dto.existingItem.props.updatedWithSession = sessionUuid
const userUuidOrError = Uuid.create(dto.performingUserUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
if (dto.itemHash.props.content) {
dto.existingItem.props.content = dto.itemHash.props.content
}
@@ -96,6 +105,57 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
dto.existingItem.props.contentSize = Buffer.byteLength(JSON.stringify(dto.existingItem))
if (
dto.itemHash.representsASharedVaultItem() &&
!this.itemIsAlreadyAssociatedWithTheSharedVault(dto.existingItem, dto.itemHash)
) {
const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
lastEditedBy: userUuid,
sharedVaultUuid,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
})
if (sharedVaultAssociationOrError.isFailed()) {
return Result.fail(sharedVaultAssociationOrError.getError())
}
dto.existingItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
}
if (
dto.itemHash.hasDedicatedKeySystemAssociation() &&
!this.itemIsAlreadyAssociatedWithTheKeySystem(dto.existingItem, dto.itemHash)
) {
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
return Result.fail(keySystemIdentifiedValidationResult.getError())
}
const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
const keySystemAssociationOrError = KeySystemAssociation.create({
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
keySystemIdentifier,
})
if (keySystemAssociationOrError.isFailed()) {
return Result.fail(keySystemAssociationOrError.getError())
}
dto.existingItem.props.keySystemAssociation = keySystemAssociationOrError.getValue()
}
if (dto.itemHash.props.deleted === true) {
dto.existingItem.props.deleted = true
dto.existingItem.props.content = null
@@ -132,4 +192,18 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
return Result.ok(dto.existingItem)
}
private itemIsAlreadyAssociatedWithTheSharedVault(item: Item, itemHash: ItemHash): boolean {
return (
item.props.sharedVaultAssociation !== undefined &&
item.props.sharedVaultAssociation.props.sharedVaultUuid.value === itemHash.props.shared_vault_uuid
)
}
private itemIsAlreadyAssociatedWithTheKeySystem(item: Item, itemHash: ItemHash): boolean {
return (
item.props.keySystemAssociation !== undefined &&
item.props.keySystemAssociation.props.keySystemIdentifier === itemHash.props.key_system_identifier
)
}
}

View File

@@ -5,4 +5,5 @@ export interface UpdateExistingItemDTO {
existingItem: Item
itemHash: ItemHash
sessionUuid: string | null
performingUserUuid: string
}

View File

@@ -43,6 +43,10 @@ export class HomeServerSharedVaultInvitesController extends BaseHttpController {
this.getOutboundUserInvites.bind(this),
)
this.controllerContainer.register('sync.shared-vault-invites.get-user-invites', this.getUserInvites.bind(this))
this.controllerContainer.register(
'sync.shared-vault-invites.get-vault-invites',
this.getSharedVaultInvites.bind(this),
)
this.controllerContainer.register(
'sync.shared-vault-invites.delete-invite',
this.deleteSharedVaultInvite.bind(this),

View File

@@ -1,23 +1,46 @@
import { ReadStream } from 'fs'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { MapperInterface } from '@standardnotes/domain-core'
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Item } from '../../Domain/Item/Item'
import { ItemQuery } from '../../Domain/Item/ItemQuery'
import { ItemRepositoryInterface } from '../../Domain/Item/ItemRepositoryInterface'
import { ExtendedIntegrityPayload } from '../../Domain/Item/ExtendedIntegrityPayload'
import { TypeORMItem } from './TypeORMItem'
import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
export class TypeORMItemRepository implements ItemRepositoryInterface {
constructor(private ormRepository: Repository<TypeORMItem>, private mapper: MapperInterface<Item, TypeORMItem>) {}
constructor(
private ormRepository: Repository<TypeORMItem>,
private mapper: MapperInterface<Item, TypeORMItem>,
private keySystemAssociationRepository: KeySystemAssociationRepositoryInterface,
private sharedVaultAssociationRepository: SharedVaultAssociationRepositoryInterface,
) {}
async save(item: Item): Promise<void> {
const persistence = this.mapper.toProjection(item)
await this.ormRepository.save(persistence)
if (item.props.sharedVaultAssociation) {
await this.sharedVaultAssociationRepository.save(item.props.sharedVaultAssociation)
}
if (item.props.keySystemAssociation) {
await this.keySystemAssociationRepository.save(item.props.keySystemAssociation)
}
}
async remove(item: Item): Promise<void> {
if (item.props.keySystemAssociation) {
await this.keySystemAssociationRepository.remove(item.props.keySystemAssociation)
}
if (item.props.sharedVaultAssociation) {
await this.sharedVaultAssociationRepository.remove(item.props.sharedVaultAssociation)
}
await this.ormRepository.remove(this.mapper.toProjection(item))
}
@@ -55,11 +78,11 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
.execute()
}
async findByUuid(uuid: string): Promise<Item | null> {
async findByUuid(uuid: Uuid): Promise<Item | null> {
const persistence = await this.ormRepository
.createQueryBuilder('item')
.where('item.uuid = :uuid', {
uuid,
uuid: uuid.value,
})
.getOne()
@@ -67,7 +90,19 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
return null
}
return this.mapper.toDomain(persistence)
const item = this.mapper.toDomain(persistence)
const keySystemAssociation = await this.keySystemAssociationRepository.findByItemUuid(uuid)
if (keySystemAssociation) {
item.props.keySystemAssociation = keySystemAssociation
}
const sharedVaultAssociation = await this.sharedVaultAssociationRepository.findByItemUuid(uuid)
if (sharedVaultAssociation) {
item.props.sharedVaultAssociation = sharedVaultAssociation
}
return item
}
async findDatesForComputingIntegrityHash(userUuid: string): Promise<Array<{ updated_at_timestamp: number }>> {

View File

@@ -0,0 +1,33 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'key_system_associations' })
export class TypeORMKeySystemAssociation {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'key_system_identifier',
length: 36,
})
@Index('key_system_identifier_on_key_system_associations')
declare keySystemIdentifier: string
@Column({
name: 'item_uuid',
length: 36,
})
@Index('item_uuid_on_key_system_associations')
declare itemUuid: string
@Column({
name: 'created_at_timestamp',
type: 'bigint',
})
declare createdAtTimestamp: number
@Column({
name: 'updated_at_timestamp',
type: 'bigint',
})
declare updatedAtTimestamp: number
}

View File

@@ -0,0 +1,36 @@
import { Repository } from 'typeorm'
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation'
import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
import { TypeORMKeySystemAssociation } from './TypeORMKeySystemAssociation'
export class TypeORMKeySystemAssociationRepository implements KeySystemAssociationRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMKeySystemAssociation>,
private mapper: MapperInterface<KeySystemAssociation, TypeORMKeySystemAssociation>,
) {}
async findByItemUuid(itemUuid: Uuid): Promise<KeySystemAssociation | null> {
const persistence = await this.ormRepository
.createQueryBuilder('key_system_association')
.where('key_system_association.item_uuid = :itemUuid', {
itemUuid: itemUuid.value,
})
.getOne()
if (persistence === null) {
return null
}
return this.mapper.toDomain(persistence)
}
async save(keySystemAssociation: KeySystemAssociation): Promise<void> {
await this.ormRepository.save(this.mapper.toProjection(keySystemAssociation))
}
async remove(keySystemAssociation: KeySystemAssociation): Promise<void> {
await this.ormRepository.remove(this.mapper.toProjection(keySystemAssociation))
}
}

View File

@@ -1,4 +1,4 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'messages' })
export class TypeORMMessage {
@@ -9,12 +9,14 @@ export class TypeORMMessage {
name: 'recipient_uuid',
length: 36,
})
@Index('recipient_uuid_on_messages')
declare recipientUuid: string
@Column({
name: 'sender_uuid',
length: 36,
})
@Index('sender_uuid_on_messages')
declare senderUuid: string
@Column({

View File

@@ -9,7 +9,7 @@ export class TypeORMSharedVault {
name: 'user_uuid',
length: 36,
})
@Index('index_shared_vaults_on_user_uuid')
@Index('user_uuid_on_shared_vaults')
declare userUuid: string
@Column({

View File

@@ -1,7 +1,7 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'shared_vault_items' })
export class TypeORMSharedVaultItem {
@Entity({ name: 'shared_vault_associations' })
export class TypeORMSharedVaultAssociation {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@@ -9,21 +9,16 @@ export class TypeORMSharedVaultItem {
name: 'shared_vault_uuid',
length: 36,
})
@Index('shared_vault_uuid_on_shared_vault_associations')
declare sharedVaultUuid: string
@Column({
name: 'item_uuid',
length: 36,
})
@Index('item_uuid_on_shared_vault_associations')
declare itemUuid: string
@Column({
name: 'key_system_identifier',
type: 'varchar',
length: 36,
})
declare keySystemIdentifier: string
@Column({
name: 'last_edited_by',
type: 'varchar',

View File

@@ -0,0 +1,36 @@
import { Repository } from 'typeorm'
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation'
import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
import { TypeORMSharedVaultAssociation } from './TypeORMSharedVaultAssociation'
export class TypeORMSharedVaultAssociationRepository implements SharedVaultAssociationRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMSharedVaultAssociation>,
private mapper: MapperInterface<SharedVaultAssociation, TypeORMSharedVaultAssociation>,
) {}
async findByItemUuid(itemUuid: Uuid): Promise<SharedVaultAssociation | null> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_association')
.where('shared_vault_association.item_uuid = :itemUuid', {
itemUuid: itemUuid.value,
})
.getOne()
if (persistence === null) {
return null
}
return this.mapper.toDomain(persistence)
}
async save(sharedVaultAssociation: SharedVaultAssociation): Promise<void> {
await this.ormRepository.save(this.mapper.toProjection(sharedVaultAssociation))
}
async remove(sharedVaultAssociation: SharedVaultAssociation): Promise<void> {
await this.ormRepository.remove(this.mapper.toProjection(sharedVaultAssociation))
}
}

View File

@@ -1,4 +1,4 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'shared_vault_invites' })
export class TypeORMSharedVaultInvite {
@@ -9,18 +9,21 @@ export class TypeORMSharedVaultInvite {
name: 'shared_vault_uuid',
length: 36,
})
@Index('shared_vault_uuid_on_shared_vault_invites')
declare sharedVaultUuid: string
@Column({
name: 'user_uuid',
length: 36,
})
@Index('user_uuid_on_shared_vault_invites')
declare userUuid: string
@Column({
name: 'sender_uuid',
length: 36,
})
@Index('sender_uuid_on_shared_vault_invites')
declare senderUuid: string
@Column({

View File

@@ -1,30 +0,0 @@
import { Repository } from 'typeorm'
import { MapperInterface, UniqueEntityId } from '@standardnotes/domain-core'
import { SharedVaultItem } from '../../Domain/SharedVault/Item/SharedVaultItem'
import { SharedVaultItemRepositoryInterface } from '../../Domain/SharedVault/Item/SharedVaultItemRepositoryInterface'
import { TypeORMSharedVaultItem } from './TypeORMSharedVaultItem'
export class TypeORMSharedVaultItemRepository implements SharedVaultItemRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMSharedVaultItem>,
private mapper: MapperInterface<SharedVaultItem, TypeORMSharedVaultItem>,
) {}
async findBySharedVaultId(sharedVaultId: UniqueEntityId): Promise<SharedVaultItem[]> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_item')
.where('shared_vault_item.shared_vault_uuid = :sharedVaultUuid', {
sharedVaultUuid: sharedVaultId.toString(),
})
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async save(sharedVaultItem: SharedVaultItem): Promise<void> {
const persistence = this.mapper.toProjection(sharedVaultItem)
await this.ormRepository.save(persistence)
}
}

View File

@@ -4,12 +4,10 @@ import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultRepositoryInterface } from '../../Domain/SharedVault/SharedVaultRepositoryInterface'
import { TypeORMSharedVault } from './TypeORMSharedVault'
import { SharedVault } from '../../Domain/SharedVault/SharedVault'
import { SharedVaultItemRepositoryInterface } from '../../Domain/SharedVault/Item/SharedVaultItemRepositoryInterface'
export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMSharedVault>,
private sharedVaultItemRepository: SharedVaultItemRepositoryInterface,
private mapper: MapperInterface<SharedVault, TypeORMSharedVault>,
) {}
@@ -28,10 +26,6 @@ export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterf
}
async save(sharedVault: SharedVault): Promise<void> {
for (const item of sharedVault.props.sharedVaultItems) {
await this.sharedVaultItemRepository.save(item)
}
const persistence = this.mapper.toProjection(sharedVault)
await this.ormRepository.save(persistence)

View File

@@ -1,4 +1,4 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'shared_vault_users' })
export class TypeORMSharedVaultUser {
@@ -9,12 +9,14 @@ export class TypeORMSharedVaultUser {
name: 'shared_vault_uuid',
length: 36,
})
@Index('shared_vault_uuid_on_shared_vault_users')
declare sharedVaultUuid: string
@Column({
name: 'user_uuid',
length: 36,
})
@Index('user_uuid_on_shared_vault_users')
declare userUuid: string
@Column({

View File

@@ -26,6 +26,16 @@ export class ItemHttpMapper implements MapperInterface<Item, ItemHttpRepresentat
updated_at: this.timer.convertMicrosecondsToStringDate(domain.props.timestamps.updatedAt),
updated_at_timestamp: domain.props.timestamps.updatedAt,
updated_with_session: domain.props.updatedWithSession ? domain.props.updatedWithSession.value : null,
key_system_identifier: domain.props.keySystemAssociation
? domain.props.keySystemAssociation.props.keySystemIdentifier
: null,
shared_vault_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.sharedVaultUuid.value
: null,
user_uuid: domain.props.userUuid.value,
last_edited_by_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.lastEditedBy.value
: null,
}
}
}

View File

@@ -12,4 +12,8 @@ export interface ItemHttpRepresentation {
updated_at: string
updated_at_timestamp: number
updated_with_session: string | null
key_system_identifier: string | null
shared_vault_uuid: string | null
user_uuid: string | null
last_edited_by_uuid: string | null
}

View File

@@ -22,6 +22,16 @@ export class SavedItemHttpMapper implements MapperInterface<Item, SavedItemHttpR
created_at_timestamp: domain.props.timestamps.createdAt,
updated_at: this.timer.convertMicrosecondsToStringDate(domain.props.timestamps.updatedAt),
updated_at_timestamp: domain.props.timestamps.updatedAt,
key_system_identifier: domain.props.keySystemAssociation
? domain.props.keySystemAssociation.props.keySystemIdentifier
: null,
shared_vault_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.sharedVaultUuid.value
: null,
user_uuid: domain.props.userUuid.value,
last_edited_by_uuid: domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.lastEditedBy.value
: null,
}
}
}

View File

@@ -8,4 +8,8 @@ export interface SavedItemHttpRepresentation {
created_at_timestamp: number
updated_at: string
updated_at_timestamp: number
key_system_identifier: string | null
shared_vault_uuid: string | null
user_uuid: string | null
last_edited_by_uuid: string | null
}

View File

@@ -0,0 +1,56 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid, Validator } from '@standardnotes/domain-core'
import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation'
import { TypeORMKeySystemAssociation } from '../../Infra/TypeORM/TypeORMKeySystemAssociation'
export class KeySystemAssociationPersistenceMapper
implements MapperInterface<KeySystemAssociation, TypeORMKeySystemAssociation>
{
toDomain(projection: TypeORMKeySystemAssociation): KeySystemAssociation {
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
throw new Error(`Failed to create key system from projection: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(projection.keySystemIdentifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
throw new Error(`Failed to create key system from projection: ${keySystemIdentifiedValidationResult.getError()}`)
}
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
if (timestampsOrError.isFailed()) {
throw new Error(`Failed to create key system from projection: ${timestampsOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const keySystemOrError = KeySystemAssociation.create(
{
itemUuid,
timestamps,
keySystemIdentifier: projection.keySystemIdentifier,
},
new UniqueEntityId(projection.uuid),
)
if (keySystemOrError.isFailed()) {
throw new Error(`Failed to create key system from projection: ${keySystemOrError.getError()}`)
}
const keySystem = keySystemOrError.getValue()
return keySystem
}
toProjection(domain: KeySystemAssociation): TypeORMKeySystemAssociation {
const typeorm = new TypeORMKeySystemAssociation()
typeorm.uuid = domain.id.toString()
typeorm.itemUuid = domain.props.itemUuid.value
typeorm.keySystemIdentifier = domain.props.keySystemIdentifier
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
return typeorm
}
}

View File

@@ -0,0 +1,65 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { TypeORMSharedVaultAssociation } from '../../Infra/TypeORM/TypeORMSharedVaultAssociation'
import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation'
export class SharedVaultAssociationPersistenceMapper
implements MapperInterface<SharedVaultAssociation, TypeORMSharedVaultAssociation>
{
toDomain(projection: TypeORMSharedVaultAssociation): SharedVaultAssociation {
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
throw new Error(`Failed to create shared vault association from projection: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
throw new Error(`Failed to create shared vault association from projection: ${sharedVaultUuidOrError.getError()}`)
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const lastEditedByOrError = Uuid.create(projection.lastEditedBy)
if (lastEditedByOrError.isFailed()) {
throw new Error(`Failed to create shared vault association from projection: ${lastEditedByOrError.getError()}`)
}
const lastEditedBy = lastEditedByOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
if (timestampsOrError.isFailed()) {
throw new Error(`Failed to create shared vault association from projection: ${timestampsOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create(
{
itemUuid,
lastEditedBy,
sharedVaultUuid,
timestamps,
},
new UniqueEntityId(projection.uuid),
)
if (sharedVaultAssociationOrError.isFailed()) {
throw new Error(
`Failed to create shared vault association from projection: ${sharedVaultAssociationOrError.getError()}`,
)
}
const sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
return sharedVaultAssociation
}
toProjection(domain: SharedVaultAssociation): TypeORMSharedVaultAssociation {
const typeorm = new TypeORMSharedVaultAssociation()
typeorm.uuid = domain.id.toString()
typeorm.sharedVaultUuid = domain.props.sharedVaultUuid.value
typeorm.itemUuid = domain.props.itemUuid.value
typeorm.lastEditedBy = domain.props.lastEditedBy.value
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
return typeorm
}
}

View File

@@ -23,7 +23,6 @@ export class SharedVaultPersistenceMapper implements MapperInterface<SharedVault
fileUploadBytesUsed: projection.fileUploadBytesUsed,
fileUploadBytesLimit: projection.fileUploadBytesLimit,
timestamps,
sharedVaultItems: [],
},
new UniqueEntityId(projection.uuid),
)

View File

@@ -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.3](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.2...@standardnotes/websockets-server@1.10.3) (2023-07-19)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.10.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.1...@standardnotes/websockets-server@1.10.2) (2023-07-17)
**Note:** Version bump only for package @standardnotes/websockets-server

Some files were not shown because too many files have changed in this diff Show More