Compare commits

...

35 Commits

Author SHA1 Message Date
standardci
d93916b159 chore(release): publish new version
- @standardnotes/analytics@2.25.4
 - @standardnotes/api-gateway@1.67.3
 - @standardnotes/auth-server@1.126.3
 - @standardnotes/domain-core@1.23.4
 - @standardnotes/event-store@1.11.11
 - @standardnotes/files-server@1.19.13
 - @standardnotes/home-server@1.13.11
 - @standardnotes/revisions-server@1.25.4
 - @standardnotes/scheduler-server@1.20.13
 - @standardnotes/settings@1.21.18
 - @standardnotes/syncing-server@1.70.3
 - @standardnotes/websockets-server@1.10.6
2023-07-26 10:38:44 +00:00
Karol Sójko
c34f548e45 fix(syncing-server): persisting aggregate changes from root (#674) 2023-07-26 12:23:10 +02:00
standardci
6fcd56cc86 chore(release): publish new version
- @standardnotes/home-server@1.13.10
 - @standardnotes/syncing-server@1.70.2
2023-07-25 14:07:10 +00:00
Karol Sójko
8f88a87c93 fix(syncing-server): remove notifications after adding item to vault (#672) 2023-07-25 15:51:42 +02:00
standardci
f8c2f84322 chore(release): publish new version
- @standardnotes/home-server@1.13.9
 - @standardnotes/syncing-server@1.70.1
2023-07-25 11:25:55 +00:00
Karol Sójko
46c4947871 fix(syncing-server): allow sender to decline the invite (#671) 2023-07-25 13:05:10 +02:00
standardci
64759ec2da chore(release): publish new version
- @standardnotes/home-server@1.13.8
 - @standardnotes/syncing-server@1.70.0
2023-07-25 11:03:19 +00:00
Karol Sójko
5f7e768e64 feat(syncing-server): filtering items by shared vault permissions (#670)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-25 12:45:49 +02:00
standardci
4bc189f1c5 chore(release): publish new version
- @standardnotes/home-server@1.13.7
 - @standardnotes/syncing-server@1.69.0
2023-07-24 14:29:21 +00:00
Karol Sójko
71721ab198 feat(syncing-server): determin shared vault operation type (#669)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-24 15:29:56 +02:00
standardci
5536a48966 chore(release): publish new version
- @standardnotes/home-server@1.13.6
 - @standardnotes/syncing-server@1.68.4
2023-07-24 09:15:15 +00:00
Karol Sójko
f77e29d3c9 fix(syncing-server): force remove shared vault owner when removing shared vault 2023-07-24 10:59:03 +02:00
standardci
4b1fc718a2 chore(release): publish new version
- @standardnotes/analytics@2.25.3
 - @standardnotes/api-gateway@1.67.2
 - @standardnotes/auth-server@1.126.2
 - @standardnotes/domain-core@1.23.3
 - @standardnotes/event-store@1.11.10
 - @standardnotes/files-server@1.19.12
 - @standardnotes/home-server@1.13.5
 - @standardnotes/revisions-server@1.25.3
 - @standardnotes/scheduler-server@1.20.12
 - @standardnotes/settings@1.21.17
 - @standardnotes/syncing-server@1.68.3
 - @standardnotes/websockets-server@1.10.5
2023-07-21 12:03:05 +00:00
Karol Sójko
1708c3f8a0 fix(domain-core): notification payload creation from string 2023-07-21 13:44:44 +02:00
standardci
352e02028d chore(release): publish new version
- @standardnotes/analytics@2.25.2
 - @standardnotes/api-gateway@1.67.1
 - @standardnotes/auth-server@1.126.1
 - @standardnotes/domain-core@1.23.2
 - @standardnotes/event-store@1.11.9
 - @standardnotes/files-server@1.19.11
 - @standardnotes/home-server@1.13.4
 - @standardnotes/revisions-server@1.25.2
 - @standardnotes/scheduler-server@1.20.11
 - @standardnotes/settings@1.21.16
 - @standardnotes/syncing-server@1.68.2
 - @standardnotes/websockets-server@1.10.4
2023-07-21 10:53:53 +00:00
Karol Sójko
1bbb639c83 fix: user notifications structure (#667) 2023-07-21 12:39:19 +02:00
standardci
c14265f103 chore(release): publish new version
- @standardnotes/home-server@1.13.3
 - @standardnotes/syncing-server@1.68.1
2023-07-21 07:37:04 +00:00
Karol Sójko
c030a6b3d8 fix(syncing-server): fetching items associated with shared vaults (#666) 2023-07-20 14:29:31 +02:00
standardci
af997ea658 chore(release): publish new version
- @standardnotes/api-gateway@1.67.0
 - @standardnotes/auth-server@1.126.0
 - @standardnotes/home-server@1.13.2
 - @standardnotes/syncing-server@1.68.0
2023-07-20 10:07:30 +00:00
Karol Sójko
efa4d7fc60 feat(syncing-server): add shared vaults, invites, messages and notifications to sync response (#665)
* feat(syncing-server): add shared vaults, invites, messages and notifications to sync response

* fix(syncing-server): migration timestamps

* fix: issue with migrations for notifications
2023-07-20 11:52:45 +02:00
standardci
f714aaa0e9 chore(release): publish new version
- @standardnotes/api-gateway@1.66.1
 - @standardnotes/home-server@1.13.1
 - @standardnotes/syncing-server@1.67.1
2023-07-19 07:42:57 +00:00
Karol Sójko
aee6e60583 fix: add missing imports and exports for controllers (#664) 2023-07-19 09:28:09 +02:00
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
standardci
fae4553fc8 chore(release): publish new version
- @standardnotes/analytics@2.25.0
 - @standardnotes/api-gateway@1.65.7
 - @standardnotes/auth-server@1.125.0
 - @standardnotes/domain-core@1.23.0
 - @standardnotes/event-store@1.11.7
 - @standardnotes/files-server@1.19.9
 - @standardnotes/home-server@1.12.4
 - @standardnotes/revisions-server@1.25.0
 - @standardnotes/scheduler-server@1.20.9
 - @standardnotes/settings@1.21.14
 - @standardnotes/syncing-server@1.64.0
 - @standardnotes/websockets-server@1.10.2
2023-07-17 11:46:13 +00:00
Karol Sójko
cb74b23e45 feat(syncing-server): refactor syncing to decouple getting and saving items (#659)
* feat(syncing-server): refactor syncing to decouple getting and saving items

* fix(syncing-server): item hash http representation mapping

* fix(syncing-server): remove redundant specs for inversify express controller
2023-07-17 13:28:50 +02:00
Karol Sójko
af8f12c33a fix: remove skip_paid_features flag from home server e2e testing 2023-07-14 10:30:22 +02:00
standardci
a148c4d1f6 chore(release): publish new version
- @standardnotes/auth-server@1.124.2
 - @standardnotes/home-server@1.12.3
2023-07-14 08:14:31 +00:00
Karol Sójko
f7190c0c9c fix(home-server): allow custom atributtes for activating premium features 2023-07-14 09:59:20 +02:00
Karol Sójko
c00d7765a9 fix: add missing files server url env var to e2e test suite 2023-07-14 09:34:31 +02:00
208 changed files with 6966 additions and 1854 deletions

View File

@@ -115,6 +115,7 @@ jobs:
echo "DB_TYPE=${{ matrix.db_type }}" >> packages/home-server/.env
echo "REDIS_URL=redis://cache" >> packages/home-server/.env
echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env
echo "FILES_SERVER_URL=http://localhost:3123" >> packages/home-server/.env
echo "E2E_TESTING=true" >> packages/home-server/.env
- name: Run Server
@@ -126,4 +127,4 @@ jobs:
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
- name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 1200000 -f http://localhost:9001/mocha/test.html?skip_paid_features=true
run: yarn dlx mocha-headless-chrome --timeout 1200000 -f http://localhost:9001/mocha/test.html

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.
## [2.25.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.3...@standardnotes/analytics@2.25.4) (2023-07-26)
**Note:** Version bump only for package @standardnotes/analytics
## [2.25.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.2...@standardnotes/analytics@2.25.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/analytics
## [2.25.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.1...@standardnotes/analytics@2.25.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/analytics
## [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
* **syncing-server:** refactor syncing to decouple getting and saving items ([#659](https://github.com/standardnotes/server/issues/659)) ([cb74b23](https://github.com/standardnotes/server/commit/cb74b23e45b207136e299ce8a3db2c04dc87e21e))
## [2.24.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.8...@standardnotes/analytics@2.24.9) (2023-07-12)
**Note:** Version bump only for package @standardnotes/analytics

View File

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

View File

@@ -3,10 +3,6 @@ import { Result, Entity, UniqueEntityId } from '@standardnotes/domain-core'
import { StatisticMeasureProps } from './StatisticMeasureProps'
export class StatisticMeasure extends Entity<StatisticMeasureProps> {
get id(): UniqueEntityId {
return this._id
}
get name(): string {
return this.props.name.value
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { SubscriptionProps } from './SubscriptionProps'
export class Subscription extends Entity<SubscriptionProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: SubscriptionProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { UserProps } from './UserProps'
export class User extends Entity<UserProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: UserProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.67.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.2...@standardnotes/api-gateway@1.67.3) (2023-07-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.67.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.1...@standardnotes/api-gateway@1.67.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.67.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.0...@standardnotes/api-gateway@1.67.1) (2023-07-21)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.67.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.66.1...@standardnotes/api-gateway@1.67.0) (2023-07-20)
### Features
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/api-gateway/issues/665)) ([efa4d7f](https://github.com/standardnotes/api-gateway/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
## [1.66.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.66.0...@standardnotes/api-gateway@1.66.1) (2023-07-19)
### Bug Fixes
* add missing imports and exports for controllers ([#664](https://github.com/standardnotes/api-gateway/issues/664)) ([aee6e60](https://github.com/standardnotes/api-gateway/commit/aee6e6058359e2b5231cc13387656f837699300f))
# [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
## [1.65.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.5...@standardnotes/api-gateway@1.65.6) (2023-07-12)
**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.6",
"version": "1.67.3",
"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'
@@ -13,6 +13,8 @@ export * from './v1/OfflineController'
export * from './v1/PaymentsController'
export * from './v1/RevisionsController'
export * from './v1/SessionsController'
export * from './v1/SharedVaultInvitesController'
export * from './v1/SharedVaultUsersController'
export * from './v1/SharedVaultsController'
export * from './v1/SubscriptionInvitesController'
export * from './v1/TokensController'

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 {
@@ -75,7 +100,7 @@ export class EndpointResolver implements EndpointResolverInterface {
const identifier = this.endpointToIdentifierMap.get(`[${method}]:${endpoint}`)
if (!identifier) {
throw new Error(`Endpoint ${endpoint} not found`)
throw new Error(`Endpoint [${method}]:${endpoint} not found`)
}
return identifier

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.126.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.2...@standardnotes/auth-server@1.126.3) (2023-07-26)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.126.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.1...@standardnotes/auth-server@1.126.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.126.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.0...@standardnotes/auth-server@1.126.1) (2023-07-21)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.126.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.125.1...@standardnotes/auth-server@1.126.0) (2023-07-20)
### Features
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/server/issues/665)) ([efa4d7f](https://github.com/standardnotes/server/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
## [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
* **syncing-server:** refactor syncing to decouple getting and saving items ([#659](https://github.com/standardnotes/server/issues/659)) ([cb74b23](https://github.com/standardnotes/server/commit/cb74b23e45b207136e299ce8a3db2c04dc87e21e))
## [1.124.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.124.1...@standardnotes/auth-server@1.124.2) (2023-07-14)
### Bug Fixes
* **home-server:** allow custom atributtes for activating premium features ([f7190c0](https://github.com/standardnotes/server/commit/f7190c0c9c2d105f97d1cf980ce6a4f0dae34805))
## [1.124.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.124.0...@standardnotes/auth-server@1.124.1) (2023-07-13)
### Bug Fixes

View File

@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540448427 implements MigrationInterface {
name = 'AddNotifications1688540448427'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

View File

@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540448428 implements MigrationInterface {
name = 'RemoveNotifications1688540448428'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
}

View File

@@ -1,17 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540623272 implements MigrationInterface {
name = 'AddNotifications1688540623272'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
}

View File

@@ -1,17 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540623273 implements MigrationInterface {
name = 'RemoveNotifications1688540623273'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
}

View File

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

View File

@@ -1,5 +1,9 @@
import { Result, ServiceInterface } from '@standardnotes/domain-core'
export interface AuthServiceInterface extends ServiceInterface {
activatePremiumFeatures(username: string): Promise<Result<string>>
activatePremiumFeatures(dto: {
username: string
subscriptionPlanName?: string
endsAt?: Date
}): Promise<Result<string>>
}

View File

@@ -24,14 +24,18 @@ export class Service implements AuthServiceInterface {
this.serviceContainer.register(this.getId(), this)
}
async activatePremiumFeatures(username: string): Promise<Result<string>> {
async activatePremiumFeatures(dto: {
username: string
subscriptionPlanName?: string
endsAt?: Date
}): Promise<Result<string>> {
if (!this.container) {
return Result.fail('Container not initialized')
}
const activatePremiumFeatures = this.container.get(TYPES.Auth_ActivatePremiumFeatures) as ActivatePremiumFeatures
return activatePremiumFeatures.execute({ username })
return activatePremiumFeatures.execute(dto)
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { AuthenticatorProps } from './AuthenticatorProps'
export class Authenticator extends Entity<AuthenticatorProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: AuthenticatorProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { AuthenticatorChallengeProps } from './AuthenticatorChallengeProps'
export class AuthenticatorChallenge extends Entity<AuthenticatorChallengeProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: AuthenticatorChallengeProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { EmergencyAccessInvitationProps } from './EmergencyAccessInvitationProps'
export class EmergencyAccessInvitation extends Entity<EmergencyAccessInvitationProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: EmergencyAccessInvitationProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { SessionTraceProps } from './SessionTraceProps'
export class SessionTrace extends Entity<SessionTraceProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: SessionTraceProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -75,4 +75,28 @@ describe('ActivatePremiumFeatures', () => {
expect(userSubscriptionRepository.save).toHaveBeenCalled()
expect(roleService.addUserRole).toHaveBeenCalled()
})
it('should save a subscription with custom plan name and endsAt', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
username: 'test@test.te',
subscriptionPlanName: 'PRO_PLAN',
endsAt: new Date('2024-01-01T00:00:00.000Z'),
})
expect(result.isFailed()).toBe(false)
})
it('should fail when subscription plan name is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
username: 'test@test.te',
subscriptionPlanName: 'some invalid plan name',
endsAt: new Date('2024-01-01T00:00:00.000Z'),
})
expect(result.isFailed()).toBe(true)
})
})

View File

@@ -30,22 +30,30 @@ export class ActivatePremiumFeatures implements UseCaseInterface<string> {
if (user === null) {
return Result.fail(`User not found with username: ${username.value}`)
}
const subscriptionPlanNameString = dto.subscriptionPlanName ?? SubscriptionPlanName.NAMES.ProPlan
const subscriptionPlanNameOrError = SubscriptionPlanName.create(subscriptionPlanNameString)
if (subscriptionPlanNameOrError.isFailed()) {
return Result.fail(subscriptionPlanNameOrError.getError())
}
const subscriptionPlanName = subscriptionPlanNameOrError.getValue()
const timestamp = this.timer.getTimestampInMicroseconds()
const endsAt = dto.endsAt ?? this.timer.getUTCDateNDaysAhead(365)
const subscription = new UserSubscription()
subscription.planName = SubscriptionPlanName.NAMES.ProPlan
subscription.planName = subscriptionPlanName.value
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = this.timer.convertDateToMicroseconds(this.timer.getUTCDateNDaysAhead(365))
subscription.endsAt = this.timer.convertDateToMicroseconds(endsAt)
subscription.cancelled = false
subscription.subscriptionId = 1
subscription.subscriptionType = UserSubscriptionType.Regular
await this.userSubscriptionRepository.save(subscription)
await this.roleService.addUserRole(user, SubscriptionPlanName.NAMES.ProPlan)
await this.roleService.addUserRole(user, subscriptionPlanName.value)
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(
subscription,

View File

@@ -1,3 +1,5 @@
export interface ActivatePremiumFeaturesDTO {
username: string
subscriptionPlanName?: string
endsAt?: Date
}

View File

@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.23.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.3...@standardnotes/domain-core@1.23.4) (2023-07-26)
### Bug Fixes
* **syncing-server:** persisting aggregate changes from root ([#674](https://github.com/standardnotes/server/issues/674)) ([c34f548](https://github.com/standardnotes/server/commit/c34f548e45bbd8defb8d490936e90755fd284e78))
## [1.23.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.2...@standardnotes/domain-core@1.23.3) (2023-07-21)
### Bug Fixes
* **domain-core:** notification payload creation from string ([1708c3f](https://github.com/standardnotes/server/commit/1708c3f8a00897369585e1ef8022950a7c3c610e))
## [1.23.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.1...@standardnotes/domain-core@1.23.2) (2023-07-21)
### Bug Fixes
* user notifications structure ([#667](https://github.com/standardnotes/server/issues/667)) ([1bbb639](https://github.com/standardnotes/server/commit/1bbb639c83922ec09e3778f85419d76669d36ae3))
## [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
* **syncing-server:** refactor syncing to decouple getting and saving items ([#659](https://github.com/standardnotes/server/issues/659)) ([cb74b23](https://github.com/standardnotes/server/commit/cb74b23e45b207136e299ce8a3db2c04dc87e21e))
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.21.1...@standardnotes/domain-core@1.22.0) (2023-07-12)
### Features

View File

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

View File

@@ -4,10 +4,6 @@ import { UniqueEntityId } from '../Core/UniqueEntityId'
import { CacheEntryProps } from './CacheEntryProps'
export class CacheEntry extends Entity<CacheEntryProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: CacheEntryProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -1,10 +1,20 @@
/* istanbul ignore file */
import { Change } from './Change'
import { Entity } from './Entity'
import { UniqueEntityId } from './UniqueEntityId'
export abstract class Aggregate<T> extends Entity<T> {
get id(): UniqueEntityId {
return this._id
private changesOnAggregateRoot: Change[] = []
addChange(change: Change): void {
this.changesOnAggregateRoot.push(change)
}
flushChanges(): void {
this.changesOnAggregateRoot = []
}
getChanges(): Change[] {
return this.changesOnAggregateRoot
}
}

View File

@@ -0,0 +1,26 @@
/* istanbul ignore file */
import { ChangeProps } from './ChangeProps'
import { Result } from './Result'
export class Change {
static readonly TYPES = {
Add: 'add',
Remove: 'remove',
Modify: 'modify',
}
public readonly props: ChangeProps
constructor(props: ChangeProps) {
this.props = Object.freeze(props)
}
static create(props: ChangeProps): Result<Change> {
if (!Object.values(Change.TYPES).includes(props.changeType)) {
return Result.fail('Invalid change type')
}
return Result.ok(new Change(props))
}
}

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
import { Entity } from './Entity'
export interface ChangeProps {
aggregateRootUuid: string
changeType: string
changeData: Entity<unknown>
}

View File

@@ -9,6 +9,10 @@ export abstract class Entity<T> {
this._id = id ? id : new UniqueEntityId()
}
get id(): UniqueEntityId {
return this._id
}
public equals(object?: Entity<T>): boolean {
if (object == null || object == undefined) {
return false

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

@@ -0,0 +1,70 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { NotificationPayloadProps } from './NotificationPayloadProps'
import { NotificationType } from './NotificationType'
import { Uuid } from '../Common/Uuid'
export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
private constructor(props: NotificationPayloadProps) {
super(props)
}
override toString(): string {
return JSON.stringify({
version: this.props.version,
type: this.props.type.value,
sharedVaultUuid: this.props.sharedVaultUuid.value,
itemUuid: this.props.itemUuid ? this.props.itemUuid.value : undefined,
})
}
static createFromString(jsonPayload: string): Result<NotificationPayload> {
try {
const props = JSON.parse(jsonPayload)
const typeOrError = NotificationType.create(props.type)
if (typeOrError.isFailed()) {
return Result.fail<NotificationPayload>(typeOrError.getError())
}
const type = typeOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(props.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<NotificationPayload>(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
let itemUuid: Uuid | undefined = undefined
if (props.itemUuid) {
const itemUuidOrError = Uuid.create(props.itemUuid)
if (itemUuidOrError.isFailed()) {
return Result.fail<NotificationPayload>(itemUuidOrError.getError())
}
itemUuid = itemUuidOrError.getValue()
}
return NotificationPayload.create({
version: props.version,
type,
sharedVaultUuid,
itemUuid,
})
} catch (error) {
return Result.fail<NotificationPayload>((error as Error).message)
}
}
static create(props: NotificationPayloadProps): Result<NotificationPayload> {
if (
props.itemUuid === undefined &&
props.type.equals(NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue())
) {
return Result.fail<NotificationPayload>(
`Item uuid is required for ${NotificationType.TYPES.SharedVaultItemRemoved} notification type`,
)
}
return Result.ok<NotificationPayload>(new NotificationPayload(props))
}
}

View File

@@ -0,0 +1,9 @@
import { Uuid } from '../Common/Uuid'
import { NotificationType } from './NotificationType'
export interface NotificationPayloadProps {
type: NotificationType
sharedVaultUuid: Uuid
version: string
itemUuid?: Uuid
}

View File

@@ -1,5 +1,5 @@
import { ValueObject, Result } from '@standardnotes/domain-core'
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { NotificationTypeProps } from './NotificationTypeProps'
export class NotificationType extends ValueObject<NotificationTypeProps> {
@@ -17,9 +17,9 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
}
static create(notificationType: string): Result<NotificationType> {
const isValidPermission = Object.values(this.TYPES).includes(notificationType)
if (!isValidPermission) {
return Result.fail<NotificationType>(`Invalid shared vault user permission ${notificationType}`)
const isValidType = Object.values(this.TYPES).includes(notificationType)
if (!isValidType) {
return Result.fail<NotificationType>(`Invalid notification type: ${notificationType}`)
} else {
return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
}

View File

@@ -27,6 +27,8 @@ export * from './Common/Uuid'
export * from './Common/UuidProps'
export * from './Core/Aggregate'
export * from './Core/Change'
export * from './Core/ChangeProps'
export * from './Core/Entity'
export * from './Core/Id'
export * from './Core/Result'
@@ -45,6 +47,11 @@ export * from './Env/AbstractEnv'
export * from './Mapping/MapperInterface'
export * from './Notification/NotificationPayload'
export * from './Notification/NotificationPayloadProps'
export * from './Notification/NotificationType'
export * from './Notification/NotificationTypeProps'
export * from './Service/ServiceConfiguration'
export * from './Service/ServiceContainer'
export * from './Service/ServiceContainerInterface'

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.11](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.10...@standardnotes/event-store@1.11.11) (2023-07-26)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.10](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.9...@standardnotes/event-store@1.11.10) (2023-07-21)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.9](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.8...@standardnotes/event-store@1.11.9) (2023-07-21)
**Note:** Version bump only for package @standardnotes/event-store
## [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
## [1.11.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.5...@standardnotes/event-store@1.11.6) (2023-07-12)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.13](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.12...@standardnotes/files-server@1.19.13) (2023-07-26)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.12](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.11...@standardnotes/files-server@1.19.12) (2023-07-21)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.11](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.10...@standardnotes/files-server@1.19.11) (2023-07-21)
**Note:** Version bump only for package @standardnotes/files-server
## [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
## [1.19.8](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.7...@standardnotes/files-server@1.19.8) (2023-07-13)
### Bug Fixes

View File

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

View File

@@ -3,6 +3,74 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.10...@standardnotes/home-server@1.13.11) (2023-07-26)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.9...@standardnotes/home-server@1.13.10) (2023-07-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.9](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.8...@standardnotes/home-server@1.13.9) (2023-07-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.8](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.7...@standardnotes/home-server@1.13.8) (2023-07-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.7](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.6...@standardnotes/home-server@1.13.7) (2023-07-24)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.5...@standardnotes/home-server@1.13.6) (2023-07-24)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.4...@standardnotes/home-server@1.13.5) (2023-07-21)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.3...@standardnotes/home-server@1.13.4) (2023-07-21)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.2...@standardnotes/home-server@1.13.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.1...@standardnotes/home-server@1.13.2) (2023-07-20)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.0...@standardnotes/home-server@1.13.1) (2023-07-19)
**Note:** Version bump only for package @standardnotes/home-server
# [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
## [1.12.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.2...@standardnotes/home-server@1.12.3) (2023-07-14)
### Bug Fixes
* **home-server:** allow custom atributtes for activating premium features ([f7190c0](https://github.com/standardnotes/server/commit/f7190c0c9c2d105f97d1cf980ce6a4f0dae34805))
## [1.12.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.1...@standardnotes/home-server@1.12.2) (2023-07-13)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -141,7 +141,11 @@ export class HomeServer implements HomeServerInterface {
if (env.get('E2E_TESTING', true) === 'true') {
app.post('/e2e/activate-premium', (request: Request, response: Response) => {
void this.activatePremiumFeatures(request.body.username).then((result) => {
void this.activatePremiumFeatures({
username: request.body.username,
subscriptionPlanName: request.body.subscriptionPlanName,
endsAt: request.body.endsAt ? new Date(request.body.endsAt) : undefined,
}).then((result) => {
if (result.isFailed()) {
response.status(400).send({ error: { message: result.getError() } })
} else {
@@ -175,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)
}
}
@@ -212,12 +218,16 @@ export class HomeServer implements HomeServerInterface {
return this.serverInstance.address() !== null
}
async activatePremiumFeatures(username: string): Promise<Result<string>> {
async activatePremiumFeatures(dto: {
username: string
subscriptionPlanName?: string
endsAt?: Date
}): Promise<Result<string>> {
if (!this.isRunning() || !this.authService) {
return Result.fail('Home server is not running.')
}
return this.authService.activatePremiumFeatures(username)
return this.authService.activatePremiumFeatures(dto)
}
private configureLoggers(env: Env, configuration: HomeServerConfiguration): void {

View File

@@ -3,7 +3,11 @@ import { HomeServerConfiguration } from './HomeServerConfiguration'
export interface HomeServerInterface {
start(configuration?: HomeServerConfiguration): Promise<Result<string>>
activatePremiumFeatures(username: string): Promise<Result<string>>
activatePremiumFeatures(dto: {
username: string
subscriptionPlanName?: string
endsAt?: Date
}): Promise<Result<string>>
stop(): Promise<Result<string>>
isRunning(): Promise<boolean>
}

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.25.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.3...@standardnotes/revisions-server@1.25.4) (2023-07-26)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.25.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.2...@standardnotes/revisions-server@1.25.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.1...@standardnotes/revisions-server@1.25.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/revisions-server
## [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
* **syncing-server:** refactor syncing to decouple getting and saving items ([#659](https://github.com/standardnotes/server/issues/659)) ([cb74b23](https://github.com/standardnotes/server/commit/cb74b23e45b207136e299ce8a3db2c04dc87e21e))
## [1.24.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.24.0...@standardnotes/revisions-server@1.24.1) (2023-07-12)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

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

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { RevisionProps } from './RevisionProps'
export class Revision extends Entity<RevisionProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: RevisionProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,10 +3,6 @@ import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { RevisionMetadataProps } from './RevisionMetadataProps'
export class RevisionMetadata extends Entity<RevisionMetadataProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: RevisionMetadataProps, id?: UniqueEntityId) {
super(props, id)
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.13](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.12...@standardnotes/scheduler-server@1.20.13) (2023-07-26)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.12](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.11...@standardnotes/scheduler-server@1.20.12) (2023-07-21)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.11](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.10...@standardnotes/scheduler-server@1.20.11) (2023-07-21)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [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
## [1.20.8](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.7...@standardnotes/scheduler-server@1.20.8) (2023-07-12)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

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

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.18](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.17...@standardnotes/settings@1.21.18) (2023-07-26)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.17](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.16...@standardnotes/settings@1.21.17) (2023-07-21)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.16](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.15...@standardnotes/settings@1.21.16) (2023-07-21)
**Note:** Version bump only for package @standardnotes/settings
## [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
## [1.21.13](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.12...@standardnotes/settings@1.21.13) (2023-07-12)
**Note:** Version bump only for package @standardnotes/settings

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/settings",
"version": "1.21.13",
"version": "1.21.18",
"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,98 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.70.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.70.2...@standardnotes/syncing-server@1.70.3) (2023-07-26)
### Bug Fixes
* **syncing-server:** persisting aggregate changes from root ([#674](https://github.com/standardnotes/syncing-server-js/issues/674)) ([c34f548](https://github.com/standardnotes/syncing-server-js/commit/c34f548e45bbd8defb8d490936e90755fd284e78))
## [1.70.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.70.1...@standardnotes/syncing-server@1.70.2) (2023-07-25)
### Bug Fixes
* **syncing-server:** remove notifications after adding item to vault ([#672](https://github.com/standardnotes/syncing-server-js/issues/672)) ([8f88a87](https://github.com/standardnotes/syncing-server-js/commit/8f88a87c93e21f52a029167f2408ff061e2a4e93))
## [1.70.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.70.0...@standardnotes/syncing-server@1.70.1) (2023-07-25)
### Bug Fixes
* **syncing-server:** allow sender to decline the invite ([#671](https://github.com/standardnotes/syncing-server-js/issues/671)) ([46c4947](https://github.com/standardnotes/syncing-server-js/commit/46c4947871f342f0a07c68562b0e3e77e7e114d4))
# [1.70.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.69.0...@standardnotes/syncing-server@1.70.0) (2023-07-25)
### Features
* **syncing-server:** filtering items by shared vault permissions ([#670](https://github.com/standardnotes/syncing-server-js/issues/670)) ([5f7e768](https://github.com/standardnotes/syncing-server-js/commit/5f7e768e64da0452e6efcf70e36cb5e867291456))
# [1.69.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.4...@standardnotes/syncing-server@1.69.0) (2023-07-24)
### Features
* **syncing-server:** determin shared vault operation type ([#669](https://github.com/standardnotes/syncing-server-js/issues/669)) ([71721ab](https://github.com/standardnotes/syncing-server-js/commit/71721ab1982b65feb4c84b44b267a249b573c537))
## [1.68.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.3...@standardnotes/syncing-server@1.68.4) (2023-07-24)
### Bug Fixes
* **syncing-server:** force remove shared vault owner when removing shared vault ([f77e29d](https://github.com/standardnotes/syncing-server-js/commit/f77e29d3c9c9a28be3c5624d2c9bf0ffd6348377))
## [1.68.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.2...@standardnotes/syncing-server@1.68.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.68.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.1...@standardnotes/syncing-server@1.68.2) (2023-07-21)
### Bug Fixes
* user notifications structure ([#667](https://github.com/standardnotes/syncing-server-js/issues/667)) ([1bbb639](https://github.com/standardnotes/syncing-server-js/commit/1bbb639c83922ec09e3778f85419d76669d36ae3))
## [1.68.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.0...@standardnotes/syncing-server@1.68.1) (2023-07-21)
### Bug Fixes
* **syncing-server:** fetching items associated with shared vaults ([#666](https://github.com/standardnotes/syncing-server-js/issues/666)) ([c030a6b](https://github.com/standardnotes/syncing-server-js/commit/c030a6b3d838b1f09593905d28ace65097a3a940))
# [1.68.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.67.1...@standardnotes/syncing-server@1.68.0) (2023-07-20)
### Features
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/syncing-server-js/issues/665)) ([efa4d7f](https://github.com/standardnotes/syncing-server-js/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
## [1.67.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.67.0...@standardnotes/syncing-server@1.67.1) (2023-07-19)
### Bug Fixes
* add missing imports and exports for controllers ([#664](https://github.com/standardnotes/syncing-server-js/issues/664)) ([aee6e60](https://github.com/standardnotes/syncing-server-js/commit/aee6e6058359e2b5231cc13387656f837699300f))
# [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
* **syncing-server:** refactor syncing to decouple getting and saving items ([#659](https://github.com/standardnotes/syncing-server-js/issues/659)) ([cb74b23](https://github.com/standardnotes/syncing-server-js/commit/cb74b23e45b207136e299ce8a3db2c04dc87e21e))
## [1.63.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.63.0...@standardnotes/syncing-server@1.63.1) (2023-07-12)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -2,6 +2,10 @@ import 'reflect-metadata'
import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressItemsController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressMessagesController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultInvitesController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultUsersController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultsController'
import helmet from 'helmet'
import * as cors from 'cors'

View File

@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540448427 implements MigrationInterface {
name = 'AddNotifications1688540448427'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1689671563304 implements MigrationInterface {
name = 'AddNotifications1689671563304'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE IF NOT EXISTS `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

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

@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540623272 implements MigrationInterface {
name = 'AddNotifications1688540623272'
export class AddNotifications1689672099828 implements MigrationInterface {
name = 'AddNotifications1689672099828'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
'CREATE TABLE IF NOT EXISTS "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_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.63.1",
"version": "1.70.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -23,8 +23,6 @@ import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemService } from '../Domain/Item/ItemService'
import { ItemServiceInterface } from '../Domain/Item/ItemServiceInterface'
import { ContentFilter } from '../Domain/Item/SaveRule/ContentFilter'
import { ContentTypeFilter } from '../Domain/Item/SaveRule/ContentTypeFilter'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
@@ -75,6 +73,86 @@ import { ItemBackupRepresentation } from '../Mapping/Backup/ItemBackupRepresenta
import { ItemBackupMapper } from '../Mapping/Backup/ItemBackupMapper'
import { SaveNewItem } from '../Domain/UseCase/Syncing/SaveNewItem/SaveNewItem'
import { UpdateExistingItem } from '../Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
import { GetItems } from '../Domain/UseCase/Syncing/GetItems/GetItems'
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'
import { GetUserNotifications } from '../Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications'
import { NotificationHttpMapper } from '../Mapping/Http/NotificationHttpMapper'
import { NotificationHttpRepresentation } from '../Mapping/Http/NotificationHttpRepresentation'
import { DetermineSharedVaultOperationOnItem } from '../Domain/UseCase/SharedVaults/DetermineSharedVaultOperationOnItem/DetermineSharedVaultOperationOnItem'
import { SharedVaultFilter } from '../Domain/Item/SaveRule/SharedVaultFilter'
import { RemoveNotificationsForUser } from '../Domain/UseCase/Messaging/RemoveNotificationsForUser/RemoveNotificationsForUser'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -209,6 +287,9 @@ export class ContainerConfigLoader {
container
.bind<MapperInterface<Item, TypeORMItem>>(TYPES.Sync_ItemPersistenceMapper)
.toConstantValue(new ItemPersistenceMapper())
container
.bind<MapperInterface<ItemHash, ItemHashHttpRepresentation>>(TYPES.Sync_ItemHashHttpMapper)
.toConstantValue(new ItemHashHttpMapper())
container
.bind<MapperInterface<Item, ItemHttpRepresentation>>(TYPES.Sync_ItemHttpMapper)
.toConstantValue(new ItemHttpMapper(container.get(TYPES.Sync_Timer)))
@@ -217,23 +298,149 @@ export class ContainerConfigLoader {
.toConstantValue(new SavedItemHttpMapper(container.get(TYPES.Sync_Timer)))
container
.bind<MapperInterface<ItemConflict, ItemConflictHttpRepresentation>>(TYPES.Sync_ItemConflictHttpMapper)
.toConstantValue(new ItemConflictHttpMapper(container.get(TYPES.Sync_ItemHttpMapper)))
.toConstantValue(
new ItemConflictHttpMapper(
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_ItemHashHttpMapper),
),
)
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())
container
.bind<MapperInterface<Notification, NotificationHttpRepresentation>>(TYPES.Sync_NotificationHttpMapper)
.toConstantValue(new NotificationHttpMapper())
// 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),
),
)
@@ -281,17 +488,58 @@ 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<DetermineSharedVaultOperationOnItem>(TYPES.Sync_DetermineSharedVaultOperationOnItem)
.toConstantValue(new DetermineSharedVaultOperationOnItem())
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toConstantValue(new OwnershipFilter())
container
.bind<TimeDifferenceFilter>(TYPES.Sync_TimeDifferenceFilter)
.toConstantValue(new TimeDifferenceFilter(container.get(TYPES.Sync_Timer)))
container.bind<ContentTypeFilter>(TYPES.Sync_ContentTypeFilter).toConstantValue(new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.Sync_ContentFilter).toConstantValue(new ContentFilter())
container
.bind<SharedVaultFilter>(TYPES.Sync_SharedVaultFilter)
.toConstantValue(
new SharedVaultFilter(
container.get(TYPES.Sync_DetermineSharedVaultOperationOnItem),
container.get(TYPES.Sync_SharedVaultUserRepository),
),
)
container
.bind<ItemSaveValidatorInterface>(TYPES.Sync_ItemSaveValidator)
.toConstantValue(
new ItemSaveValidator([
container.get(TYPES.Sync_OwnershipFilter),
container.get(TYPES.Sync_TimeDifferenceFilter),
container.get(TYPES.Sync_ContentTypeFilter),
container.get(TYPES.Sync_ContentFilter),
container.get(TYPES.Sync_SharedVaultFilter),
]),
)
// use cases
container.bind<SyncItems>(TYPES.Sync_SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(context.container.get(TYPES.Sync_ItemService))
})
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepository))
})
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.Sync_ItemRepository))
})
container
.bind<GetItems>(TYPES.Sync_GetItems)
.toConstantValue(
new GetItems(
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
container.get(TYPES.Sync_ItemTransferCalculator),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_MAX_ITEMS_LIMIT),
),
)
container
.bind<SaveNewItem>(TYPES.Sync_SaveNewItem)
.toConstantValue(
@@ -302,6 +550,14 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_DomainEventFactory),
),
)
container
.bind<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser)
.toConstantValue(
new AddNotificationForUser(container.get(TYPES.Sync_NotificationRepository), container.get(TYPES.Sync_Timer)),
)
container
.bind<RemoveNotificationsForUser>(TYPES.Sync_RemoveNotificationsForUser)
.toConstantValue(new RemoveNotificationsForUser(container.get(TYPES.Sync_NotificationRepository)))
container
.bind<UpdateExistingItem>(TYPES.Sync_UpdateExistingItem)
.toConstantValue(
@@ -311,43 +567,183 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_DomainEventPublisher),
container.get(TYPES.Sync_DomainEventFactory),
container.get(TYPES.Sync_REVISIONS_FREQUENCY),
container.get(TYPES.Sync_DetermineSharedVaultOperationOnItem),
container.get(TYPES.Sync_AddNotificationForUser),
container.get(TYPES.Sync_RemoveNotificationsForUser),
),
)
// Services
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toConstantValue(new OwnershipFilter())
container
.bind<TimeDifferenceFilter>(TYPES.Sync_TimeDifferenceFilter)
.toConstantValue(new TimeDifferenceFilter(container.get(TYPES.Sync_Timer)))
container.bind<ContentTypeFilter>(TYPES.Sync_ContentTypeFilter).toConstantValue(new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.Sync_ContentFilter).toConstantValue(new ContentFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.Sync_ItemSaveValidator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemSaveValidator([
context.container.get(TYPES.Sync_OwnershipFilter),
context.container.get(TYPES.Sync_TimeDifferenceFilter),
context.container.get(TYPES.Sync_ContentTypeFilter),
context.container.get(TYPES.Sync_ContentFilter),
])
})
container
.bind<ItemServiceInterface>(TYPES.Sync_ItemService)
.bind<SaveItems>(TYPES.Sync_SaveItems)
.toConstantValue(
new ItemService(
new SaveItems(
container.get(TYPES.Sync_ItemSaveValidator),
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
container.get(TYPES.Sync_ItemTransferCalculator),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_MAX_ITEMS_LIMIT),
container.get(TYPES.Sync_SaveNewItem),
container.get(TYPES.Sync_UpdateExistingItem),
container.get(TYPES.Sync_Logger),
),
)
container
.bind<GetUserNotifications>(TYPES.Sync_GetUserNotifications)
.toConstantValue(new GetUserNotifications(container.get(TYPES.Sync_NotificationRepository)))
container
.bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
.toConstantValue(
new GetSharedVaults(
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultRepository),
),
)
container
.bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
.toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
container
.bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
.toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
container
.bind<SyncItems>(TYPES.Sync_SyncItems)
.toConstantValue(
new SyncItems(
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_GetItems),
container.get(TYPES.Sync_SaveItems),
container.get(TYPES.Sync_GetSharedVaults),
container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
container.get(TYPES.Sync_GetMessagesSentToUser),
container.get(TYPES.Sync_GetUserNotifications),
),
)
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepository))
})
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<GetSharedVaultUsers>(TYPES.Sync_GetSharedVaultUsers)
.toConstantValue(
new GetSharedVaultUsers(
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_SharedVaultRepository),
),
)
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<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<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
.bind<SyncResponseFactory20161215>(TYPES.Sync_SyncResponseFactory20161215)
.toConstantValue(new SyncResponseFactory20161215(container.get(TYPES.Sync_ItemHttpMapper)))
@@ -358,6 +754,10 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_ItemConflictHttpMapper),
container.get(TYPES.Sync_SavedItemHttpMapper),
container.get(TYPES.Sync_SharedVaultHttpMapper),
container.get(TYPES.Sync_SharedVaultInviteHttpMapper),
container.get(TYPES.Sync_MessageHttpMapper),
container.get(TYPES.Sync_NotificationHttpMapper),
),
)
container
@@ -533,6 +933,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,9 @@ 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_RemoveNotificationsForUser: Symbol.for('Sync_RemoveNotificationsForUser'),
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
Sync_InviteUserToSharedVault: Symbol.for('Sync_InviteUserToSharedVault'),
Sync_UpdateSharedVaultInvite: Symbol.for('Sync_UpdateSharedVaultInvite'),
@@ -55,6 +74,10 @@ const TYPES = {
Sync_DeleteMessage: Symbol.for('Sync_DeleteMessage'),
Sync_SaveNewItem: Symbol.for('Sync_SaveNewItem'),
Sync_UpdateExistingItem: Symbol.for('Sync_UpdateExistingItem'),
Sync_GetItems: Symbol.for('Sync_GetItems'),
Sync_SaveItems: Symbol.for('Sync_SaveItems'),
Sync_GetUserNotifications: Symbol.for('Sync_GetUserNotifications'),
Sync_DetermineSharedVaultOperationOnItem: Symbol.for('Sync_DetermineSharedVaultOperationOnItem'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -67,8 +90,8 @@ const TYPES = {
Sync_DomainEventFactory: Symbol.for('Sync_DomainEventFactory'),
Sync_DomainEventMessageHandler: Symbol.for('Sync_DomainEventMessageHandler'),
Sync_HTTPClient: Symbol.for('Sync_HTTPClient'),
Sync_ItemService: Symbol.for('Sync_ItemService'),
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'),
@@ -77,22 +100,36 @@ const TYPES = {
Sync_ItemBackupService: Symbol.for('Sync_ItemBackupService'),
Sync_ItemSaveValidator: Symbol.for('Sync_ItemSaveValidator'),
Sync_OwnershipFilter: Symbol.for('Sync_OwnershipFilter'),
Sync_SharedVaultFilter: Symbol.for('Sync_SharedVaultFilter'),
Sync_TimeDifferenceFilter: Symbol.for('Sync_TimeDifferenceFilter'),
Sync_ContentTypeFilter: Symbol.for('Sync_ContentTypeFilter'),
Sync_ContentFilter: Symbol.for('Sync_ContentFilter'),
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_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),
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,6 +0,0 @@
import { Item } from './Item'
export type GetItemsResult = {
items: Array<Item>
cursorToken?: string
}

View File

@@ -0,0 +1,252 @@
import { ContentType, Dates, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { Item } from './Item'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
describe('Item', () => {
it('should create an aggregate', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
expect(entityOrError.getValue().uuid.value).toEqual(entityOrError.getValue().id.toString())
})
it('should throw an error if id cannot be cast to uuid', () => {
const entityOrError = Item.create(
{
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
},
new UniqueEntityId(1),
)
expect(entityOrError.isFailed()).toBeFalsy()
expect(() => entityOrError.getValue().uuid).toThrow()
})
it('should tell if an item is associated with a shared vault', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
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(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(
entityOrError
.getValue()
.isAssociatedWithSharedVault(Uuid.create('00000000-0000-0000-0000-000000000000').getValue()),
).toBeTruthy()
})
it('should tell that an item is not associated with a shared vault', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(
entityOrError
.getValue()
.isAssociatedWithSharedVault(Uuid.create('00000000-0000-0000-0000-000000000000').getValue()),
).toBeFalsy()
})
it('should tell if an item is associated with a key system', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
keySystemAssociation: KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().isAssociatedWithKeySystem('key-system-identifier')).toBeTruthy()
})
it('should tell that an item is not associated with a key system', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().isAssociatedWithKeySystem('key-system-identifier')).toBeFalsy()
})
it('should set shared vault association', () => {
const 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 entity = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
entity.setSharedVaultAssociation(sharedVaultAssociation)
expect(entity.props.sharedVaultAssociation).toEqual(sharedVaultAssociation)
expect(entity.getChanges()).toHaveLength(1)
})
it('should unset a shared vault association', () => {
const entity = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
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(),
}).getValue()
entity.unsetSharedVaultAssociation()
expect(entity.props.sharedVaultAssociation).toBeUndefined()
expect(entity.getChanges()).toHaveLength(1)
})
it('should set key system association', () => {
const keySystemAssociation = KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
const entity = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
entity.setKeySystemAssociation(keySystemAssociation)
expect(entity.props.keySystemAssociation).toEqual(keySystemAssociation)
expect(entity.getChanges()).toHaveLength(1)
})
it('should unset a key system association', () => {
const entity = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
keySystemAssociation: KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
}).getValue()
entity.unsetKeySystemAssociation()
expect(entity.props.keySystemAssociation).toBeUndefined()
expect(entity.getChanges()).toHaveLength(1)
})
})

View File

@@ -1,12 +1,10 @@
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { Aggregate, Change, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ItemProps } from './ItemProps'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
export class Item extends Entity<ItemProps> {
get id(): UniqueEntityId {
return this._id
}
export class Item extends Aggregate<ItemProps> {
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
}
@@ -19,4 +17,95 @@ export class Item extends Entity<ItemProps> {
return Result.ok<Item>(new Item(props, id))
}
get uuid(): Uuid {
const uuidOrError = Uuid.create(this._id.toString())
if (uuidOrError.isFailed()) {
throw new Error(uuidOrError.getError())
}
return uuidOrError.getValue()
}
get sharedVaultUuid(): Uuid | null {
if (!this.props.sharedVaultAssociation) {
return null
}
return this.props.sharedVaultAssociation.props.sharedVaultUuid
}
isAssociatedWithASharedVault(): boolean {
return this.sharedVaultUuid !== null
}
isAssociatedWithSharedVault(sharedVaultUuid: Uuid): boolean {
if (!this.isAssociatedWithASharedVault()) {
return false
}
return (this.sharedVaultUuid as Uuid).equals(sharedVaultUuid)
}
isAssociatedWithKeySystem(keySystemIdentifier: string): boolean {
if (!this.props.keySystemAssociation) {
return false
}
return this.props.keySystemAssociation.props.keySystemIdentifier === keySystemIdentifier
}
setSharedVaultAssociation(sharedVaultAssociation: SharedVaultAssociation): void {
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: this.props.sharedVaultAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
changeData: sharedVaultAssociation,
}).getValue(),
)
this.props.sharedVaultAssociation = sharedVaultAssociation
}
unsetSharedVaultAssociation(): void {
if (!this.props.sharedVaultAssociation) {
return
}
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: Change.TYPES.Remove,
changeData: this.props.sharedVaultAssociation,
}).getValue(),
)
this.props.sharedVaultAssociation = undefined
}
setKeySystemAssociation(keySystemAssociation: KeySystemAssociation): void {
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: this.props.keySystemAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
changeData: keySystemAssociation,
}).getValue(),
)
this.props.keySystemAssociation = keySystemAssociation
}
unsetKeySystemAssociation(): void {
if (!this.props.keySystemAssociation) {
return
}
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: Change.TYPES.Remove,
changeData: this.props.keySystemAssociation,
}).getValue(),
)
this.props.keySystemAssociation = undefined
}
}

View File

@@ -0,0 +1,38 @@
import { ContentType } from '@standardnotes/domain-core'
import { ItemHash } from './ItemHash'
describe('ItemHash', () => {
it('should create a value object', () => {
const valueOrError = ItemHash.create({
uuid: '00000000-0000-0000-0000-000000000000',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
})
expect(valueOrError.isFailed()).toBeFalsy()
})
it('should return error if shared vault uuid is not valid', () => {
const valueOrError = ItemHash.create({
uuid: '00000000-0000-0000-0000-000000000000',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: 'invalid',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
})

View File

@@ -1,14 +1,36 @@
export type ItemHash = {
uuid: string
content?: string
content_type: string | null
deleted?: boolean
duplicate_of?: string | null
auth_hash?: string
enc_item_key?: string
items_key_id?: string
created_at?: string
created_at_timestamp?: number
updated_at?: string
updated_at_timestamp?: number
import { Result, Uuid, ValueObject } from '@standardnotes/domain-core'
import { ItemHashProps } from './ItemHashProps'
export class ItemHash extends ValueObject<ItemHashProps> {
private constructor(props: ItemHashProps) {
super(props)
}
static create(props: ItemHashProps): Result<ItemHash> {
if (props.shared_vault_uuid) {
const sharedVaultUuidOrError = Uuid.create(props.shared_vault_uuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<ItemHash>(sharedVaultUuidOrError.getError())
}
}
return Result.ok<ItemHash>(new ItemHash(props))
}
representsASharedVaultItem(): boolean {
return this.props.shared_vault_uuid !== null
}
get sharedVaultUuid(): Uuid | null {
if (!this.representsASharedVaultItem()) {
return null
}
return Uuid.create(this.props.shared_vault_uuid as string).getValue()
}
hasDedicatedKeySystemAssociation(): boolean {
return this.props.key_system_identifier !== null
}
}

View File

@@ -0,0 +1,17 @@
export interface ItemHashProps {
uuid: string
user_uuid: string
content?: string
content_type: string | null
deleted?: boolean
duplicate_of?: string | null
auth_hash?: string
enc_item_key?: string
items_key_id?: string
key_system_identifier: string | null
shared_vault_uuid: string | null
created_at?: string
created_at_timestamp?: number
updated_at?: string
updated_at_timestamp?: number
}

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

@@ -11,4 +11,6 @@ export type ItemQuery = {
limit?: number
createdBetween?: Date[]
selectString?: string
includeSharedVaultUuids?: string[]
exclusiveSharedVaultUuids?: string[]
}

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

@@ -1,785 +0,0 @@
import 'reflect-metadata'
import { Timer, TimerInterface } from '@standardnotes/time'
import { Logger } from 'winston'
import { Item } from './Item'
import { ItemHash } from './ItemHash'
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
import { ItemService } from './ItemService'
import { ApiVersion } from '../Api/ApiVersion'
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
import { ItemConflict } from './ItemConflict'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { SaveNewItem } from '../UseCase/Syncing/SaveNewItem/SaveNewItem'
import { UpdateExistingItem } from '../UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId, Result } from '@standardnotes/domain-core'
describe('ItemService', () => {
let itemRepository: ItemRepositoryInterface
const contentSizeTransferLimit = 100
let timer: TimerInterface
let item1: Item
let item2: Item
let itemHash1: ItemHash
let itemHash2: ItemHash
let syncToken: string
let logger: Logger
let itemSaveValidator: ItemSaveValidatorInterface
let newItem: Item
let timeHelper: Timer
let itemTransferCalculator: ItemTransferCalculatorInterface
let saveNewItemUseCase: SaveNewItem
let updateExistingItemUseCase: UpdateExistingItem
const maxItemsSyncLimit = 300
const createService = () =>
new ItemService(
itemSaveValidator,
itemRepository,
contentSizeTransferLimit,
itemTransferCalculator,
timer,
maxItemsSyncLimit,
saveNewItemUseCase,
updateExistingItemUseCase,
logger,
)
beforeEach(() => {
timeHelper = new Timer()
item1 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
item2 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241312), new Date(1616164633241312)).getValue(),
timestamps: Timestamps.create(1616164633241312, 1616164633241312).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue()
itemHash1 = {
uuid: '1-2-3',
content: 'asdqwe1',
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe1',
items_key_id: 'asdasd1',
created_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(item1.props.timestamps.createdAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
updated_at: timeHelper.formatDate(
new Date(timeHelper.convertMicrosecondsToMilliseconds(item1.props.timestamps.updatedAt) + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
} as jest.Mocked<ItemHash>
itemHash2 = {
uuid: '2-3-4',
content: 'asdqwe2',
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe2',
items_key_id: 'asdasd2',
created_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(item2.props.timestamps.createdAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
updated_at: timeHelper.formatDate(
new Date(timeHelper.convertMicrosecondsToMilliseconds(item2.props.timestamps.updatedAt) + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
} as jest.Mocked<ItemHash>
itemTransferCalculator = {} as jest.Mocked<ItemTransferCalculatorInterface>
itemTransferCalculator.computeItemUuidsToFetch = jest
.fn()
.mockReturnValue([item1.id.toString(), item2.id.toString()])
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findAll = jest.fn().mockReturnValue([item1, item2])
itemRepository.countAll = jest.fn().mockReturnValue(2)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1616164633241568)
timer.getUTCDate = jest.fn().mockReturnValue(new Date())
timer.convertStringDateToDate = jest
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToDate(date))
timer.convertMicrosecondsToSeconds = jest.fn().mockReturnValue(600)
timer.convertStringDateToMicroseconds = jest
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToMicroseconds(date))
timer.convertMicrosecondsToDate = jest
.fn()
.mockImplementation((microseconds: number) => timeHelper.convertMicrosecondsToDate(microseconds))
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
logger.warn = jest.fn()
syncToken = Buffer.from('2:1616164633.241564', 'utf-8').toString('base64')
itemSaveValidator = {} as jest.Mocked<ItemSaveValidatorInterface>
itemSaveValidator.validate = jest.fn().mockReturnValue({ passed: true })
newItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241313), new Date(1616164633241313)).getValue(),
timestamps: Timestamps.create(1616164633241313, 1616164633241313).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000002'),
).getValue()
saveNewItemUseCase = {} as jest.Mocked<SaveNewItem>
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(newItem))
updateExistingItemUseCase = {} as jest.Mocked<UpdateExistingItem>
updateExistingItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(item1))
})
it('should retrieve all items for a user from last sync with sync token version 1', async () => {
syncToken = Buffer.from('1:2021-03-15 07:00:00', 'utf-8').toString('base64')
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1615791600000000,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortOrder: 'ASC',
sortBy: 'updated_at_timestamp',
})
})
it('should retrieve all items for a user from last sync', async () => {
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve all items for a user from last sync with upper bound items limit', async () => {
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.TYPES.Note,
limit: 1000,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 300,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve no items for a user if there are none from last sync', async () => {
itemTransferCalculator.computeItemUuidsToFetch = jest.fn().mockReturnValue([])
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [],
})
expect(itemRepository.findAll).not.toHaveBeenCalled()
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
})
it('should return a cursor token if there are more items than requested with limit', async () => {
itemRepository.findAll = jest.fn().mockReturnValue([item1])
const itemsResponse = await createService().getItems({
userUuid: '1-2-3',
syncToken,
limit: 1,
contentType: ContentType.TYPES.Note,
})
expect(itemsResponse).toEqual({
cursorToken: 'MjoxNjE2MTY0NjMzLjI0MTMxMQ==',
items: [item1],
})
expect(Buffer.from(<string>itemsResponse.cursorToken, 'base64').toString('utf-8')).toEqual('2:1616164633.241311')
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 1,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve all items for a user from cursor token', async () => {
const cursorToken = Buffer.from('2:1616164633.241123', 'utf-8').toString('base64')
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
cursorToken,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241123,
syncTimeComparison: '>=',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve all undeleted items for a user without cursor or sync token', async () => {
expect(
await createService().getItems({
userUuid: '1-2-3',
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
deleted: false,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
syncTimeComparison: '>',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve all items with default limit if not defined', async () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.TYPES.Note,
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortOrder: 'ASC',
sortBy: 'updated_at_timestamp',
})
})
it('should retrieve all items with non-positive limit if not defined', async () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
limit: 0,
contentType: ContentType.TYPES.Note,
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should throw an error if the sync token is missing time', async () => {
let error = null
try {
await createService().getItems({
userUuid: '1-2-3',
syncToken: '2:',
limit: 0,
contentType: ContentType.TYPES.Note,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw an error if the sync token is missing version', async () => {
let error = null
try {
await createService().getItems({
userUuid: '1-2-3',
syncToken: '1234567890',
limit: 0,
contentType: ContentType.TYPES.Note,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should front load keys items to top of the collection for better client performance', async () => {
const item3 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000003'),
).getValue()
const item4 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241312), new Date(1616164633241312)).getValue(),
timestamps: Timestamps.create(1616164633241312, 1616164633241312).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000004'),
).getValue()
itemRepository.findAll = jest.fn().mockReturnValue([item3, item4])
await createService().frontLoadKeysItemsToTop('1-2-3', [item1, item2])
})
it('should save new items', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [newItem],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxNA==',
})
expect(saveNewItemUseCase.execute).toHaveBeenCalled()
})
it('should not save new items in read only access mode', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: true,
sessionUuid: null,
})
expect(result).toEqual({
conflicts: [
{
type: 'readonly_error',
unsavedItem: itemHash1,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
expect(saveNewItemUseCase.execute).not.toHaveBeenCalled()
})
it('should save new items that are duplicates', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const duplicateItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
deleted: false,
dates: Dates.create(new Date(1616164633241570), new Date(1616164633241570)).getValue(),
timestamps: Timestamps.create(1616164633241570, 1616164633241570).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000005'),
).getValue()
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(duplicateItem))
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [duplicateItem],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU3MQ==',
})
})
it('should skip items that are conflicting on validation', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const conflict = {} as jest.Mocked<ItemConflict>
const validationResult = { passed: false, conflict }
itemSaveValidator.validate = jest.fn().mockReturnValue(validationResult)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [conflict],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should mark items as saved that are skipped on validation', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const skipped = item1
const validationResult = { passed: false, skipped }
itemSaveValidator.validate = jest.fn().mockReturnValue(validationResult)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [skipped],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxMg==',
})
})
it('should calculate the sync token based on last updated date of saved items incremented with 1 microsecond to avoid returning same object in subsequent sync', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const itemHash3 = {
uuid: '3-4-5',
content: 'asdqwe3',
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe3',
items_key_id: 'asdasd3',
created_at: '2021-02-19T11:35:45.652Z',
updated_at: '2021-03-25T09:37:37.943Z',
} as jest.Mocked<ItemHash>
const saveProcedureStartTimestamp = 1616164633241580
const item1Timestamp = 1616164633241570
const item2Timestamp = 1616164633241568
const item3Timestamp = 1616164633241569
timer.getTimestampInMicroseconds = jest.fn().mockReturnValueOnce(saveProcedureStartTimestamp)
saveNewItemUseCase.execute = jest
.fn()
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item1.props,
timestamps: Timestamps.create(item1Timestamp, item1Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue(),
),
)
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item2.props,
timestamps: Timestamps.create(item2Timestamp, item2Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000002'),
).getValue(),
),
)
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item2.props,
timestamps: Timestamps.create(item3Timestamp, item3Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000003'),
).getValue(),
),
)
const result = await createService().saveItems({
itemHashes: [itemHash1, itemHash3, itemHash2],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result.syncToken).toEqual('MjoxNjE2MTY0NjMzLjI0MTU3MQ==')
expect(Buffer.from(result.syncToken, 'base64').toString('utf-8')).toEqual('2:1616164633.241571')
})
it('should update existing items', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [item1],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxMg==',
})
})
it('should mark as skipped existing items that failed to update', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
updateExistingItemUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [
{
type: 'uuid_conflict',
unsavedItem: itemHash1,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should skip saving conflicting items and mark them as sync conflicts when saving fails', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createService().saveItems({
itemHashes: [itemHash1, itemHash2],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [
{
type: 'uuid_conflict',
unsavedItem: itemHash1,
},
{
type: 'uuid_conflict',
unsavedItem: itemHash2,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should skip saving conflicting items and mark them as sync conflicts when saving throws an error', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
saveNewItemUseCase.execute = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
const result = await createService().saveItems({
itemHashes: [itemHash1, itemHash2],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [
{
type: 'uuid_conflict',
unsavedItem: itemHash1,
},
{
type: 'uuid_conflict',
unsavedItem: itemHash2,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
})

View File

@@ -1,239 +0,0 @@
import { Time, TimerInterface } from '@standardnotes/time'
import { Logger } from 'winston'
import { GetItemsDTO } from './GetItemsDTO'
import { GetItemsResult } from './GetItemsResult'
import { Item } from './Item'
import { ItemConflict } from './ItemConflict'
import { ItemQuery } from './ItemQuery'
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
import { ItemServiceInterface } from './ItemServiceInterface'
import { SaveItemsDTO } from './SaveItemsDTO'
import { SaveItemsResult } from './SaveItemsResult'
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
import { ConflictType } from '@standardnotes/responses'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { SaveNewItem } from '../UseCase/Syncing/SaveNewItem/SaveNewItem'
import { ContentType } from '@standardnotes/domain-core'
import { UpdateExistingItem } from '../UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
private readonly SYNC_TOKEN_VERSION = 2
constructor(
private itemSaveValidator: ItemSaveValidatorInterface,
private itemRepository: ItemRepositoryInterface,
private contentSizeTransferLimit: number,
private itemTransferCalculator: ItemTransferCalculatorInterface,
private timer: TimerInterface,
private maxItemsSyncLimit: number,
private saveNewItem: SaveNewItem,
private updateExistingItem: UpdateExistingItem,
private logger: Logger,
) {}
async getItems(dto: GetItemsDTO): Promise<GetItemsResult> {
const lastSyncTime = this.getLastSyncTime(dto)
const syncTimeComparison = dto.cursorToken ? '>=' : '>'
const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
const itemQuery: ItemQuery = {
userUuid: dto.userUuid,
lastSyncTime,
syncTimeComparison,
contentType: dto.contentType,
deleted: lastSyncTime ? undefined : false,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
limit: upperBoundLimit,
}
const itemUuidsToFetch = await this.itemTransferCalculator.computeItemUuidsToFetch(
itemQuery,
this.contentSizeTransferLimit,
)
let items: Array<Item> = []
if (itemUuidsToFetch.length > 0) {
items = await this.itemRepository.findAll({
uuids: itemUuidsToFetch,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
}
const totalItemsCount = await this.itemRepository.countAll(itemQuery)
let cursorToken = undefined
if (totalItemsCount > upperBoundLimit) {
const lastSyncTime = items[items.length - 1].props.timestamps.updatedAt / Time.MicrosecondsInASecond
cursorToken = Buffer.from(`${this.SYNC_TOKEN_VERSION}:${lastSyncTime}`, 'utf-8').toString('base64')
}
return {
items,
cursorToken,
}
}
async saveItems(dto: SaveItemsDTO): Promise<SaveItemsResult> {
const savedItems: Array<Item> = []
const conflicts: Array<ItemConflict> = []
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
for (const itemHash of dto.itemHashes) {
if (dto.readOnlyAccess) {
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.ReadOnlyError,
})
continue
}
const existingItem = await this.itemRepository.findByUuid(itemHash.uuid)
const processingResult = await this.itemSaveValidator.validate({
userUuid: dto.userUuid,
apiVersion: dto.apiVersion,
itemHash,
existingItem,
})
if (!processingResult.passed) {
if (processingResult.conflict) {
conflicts.push(processingResult.conflict)
}
if (processingResult.skipped) {
savedItems.push(processingResult.skipped)
}
continue
}
if (existingItem) {
const udpatedItemOrError = await this.updateExistingItem.execute({
existingItem,
itemHash,
sessionUuid: dto.sessionUuid,
})
if (udpatedItemOrError.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Updating item ${itemHash.uuid} failed. Error: ${udpatedItemOrError.getError()}`,
)
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
const updatedItem = udpatedItemOrError.getValue()
savedItems.push(updatedItem)
} else {
try {
const newItemOrError = await this.saveNewItem.execute({
userUuid: dto.userUuid,
itemHash,
sessionUuid: dto.sessionUuid,
})
if (newItemOrError.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Saving item ${itemHash.uuid} failed. Error: ${newItemOrError.getError()}`,
)
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
const newItem = newItemOrError.getValue()
savedItems.push(newItem)
} catch (error) {
this.logger.error(`[${dto.userUuid}] Saving item ${itemHash.uuid} failed. Error: ${(error as Error).message}`)
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
}
}
const syncToken = this.calculateSyncToken(lastUpdatedTimestamp, savedItems)
return {
savedItems,
conflicts,
syncToken,
}
}
async frontLoadKeysItemsToTop(userUuid: string, retrievedItems: Array<Item>): Promise<Array<Item>> {
const itemsKeys = await this.itemRepository.findAll({
userUuid,
contentType: ContentType.TYPES.ItemsKey,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
const retrievedItemsIds: Array<string> = retrievedItems.map((item: Item) => item.id.toString())
itemsKeys.forEach((itemKey: Item) => {
if (retrievedItemsIds.indexOf(itemKey.id.toString()) === -1) {
retrievedItems.unshift(itemKey)
}
})
return retrievedItems
}
private calculateSyncToken(lastUpdatedTimestamp: number, savedItems: Array<Item>): string {
if (savedItems.length) {
const sortedItems = savedItems.sort((itemA: Item, itemB: Item) => {
return itemA.props.timestamps.updatedAt > itemB.props.timestamps.updatedAt ? 1 : -1
})
lastUpdatedTimestamp = sortedItems[sortedItems.length - 1].props.timestamps.updatedAt
}
const lastUpdatedTimestampWithMicrosecondPreventingSyncDoubles = lastUpdatedTimestamp + 1
return Buffer.from(
`${this.SYNC_TOKEN_VERSION}:${
lastUpdatedTimestampWithMicrosecondPreventingSyncDoubles / Time.MicrosecondsInASecond
}`,
'utf-8',
).toString('base64')
}
private getLastSyncTime(dto: GetItemsDTO): number | undefined {
let token = dto.syncToken
if (dto.cursorToken !== undefined && dto.cursorToken !== null) {
token = dto.cursorToken
}
if (!token) {
return undefined
}
const decodedToken = Buffer.from(token, 'base64').toString('utf-8')
const tokenParts = decodedToken.split(':')
const version = tokenParts.shift()
switch (version) {
case '1':
return this.timer.convertStringDateToMicroseconds(tokenParts.join(':'))
case '2':
return +tokenParts[0] * Time.MicrosecondsInASecond
default:
throw Error('Sync token is missing version part')
}
}
}

View File

@@ -1,11 +0,0 @@
import { GetItemsDTO } from './GetItemsDTO'
import { GetItemsResult } from './GetItemsResult'
import { Item } from './Item'
import { SaveItemsDTO } from './SaveItemsDTO'
import { SaveItemsResult } from './SaveItemsResult'
export interface ItemServiceInterface {
getItems(dto: GetItemsDTO): Promise<GetItemsResult>
saveItems(dto: SaveItemsDTO): Promise<SaveItemsResult>
frontLoadKeysItemsToTop(userUuid: string, retrievedItems: Array<Item>): Promise<Array<Item>>
}

View File

@@ -1,8 +0,0 @@
import { Item } from './Item'
import { ItemConflict } from './ItemConflict'
export type SaveItemsResult = {
savedItems: Array<Item>
conflicts: Array<ItemConflict>
syncToken: string
}

View File

@@ -5,6 +5,7 @@ import { Item } from '../Item'
import { ContentFilter } from './ContentFilter'
import { ContentType } from '@standardnotes/domain-core'
import { ItemHash } from '../ItemHash'
describe('ContentFilter', () => {
let existingItem: Item
@@ -14,25 +15,25 @@ describe('ContentFilter', () => {
const invalidContents = [[], { foo: 'bar' }, [{ foo: 'bar' }], 123, new Date(1)]
for (const invalidContent of invalidContents) {
const itemHash = ItemHash.create({
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: invalidContent as unknown as string,
content_type: ContentType.TYPES.Note,
user_uuid: '1-2-3',
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: invalidContent as unknown as string,
content_type: ContentType.TYPES.Note,
},
itemHash,
existingItem: null,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: invalidContent,
content_type: ContentType.TYPES.Note,
},
unsavedItem: itemHash,
type: 'content_error',
},
})
@@ -46,11 +47,14 @@ describe('ContentFilter', () => {
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
itemHash: ItemHash.create({
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: validContent as unknown as string,
content_type: ContentType.TYPES.Note,
},
user_uuid: '1-2-3',
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue(),
existingItem,
})

View File

@@ -5,13 +5,13 @@ import { ConflictType } from '@standardnotes/responses'
export class ContentFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (dto.itemHash.content === undefined || dto.itemHash.content === null) {
if (dto.itemHash.props.content === undefined || dto.itemHash.props.content === null) {
return {
passed: true,
}
}
const validContent = typeof dto.itemHash.content === 'string'
const validContent = typeof dto.itemHash.props.content === 'string'
if (!validContent) {
return {

View File

@@ -4,6 +4,7 @@ import { ApiVersion } from '../../Api/ApiVersion'
import { Item } from '../Item'
import { ContentTypeFilter } from './ContentTypeFilter'
import { ItemHash } from '../ItemHash'
describe('ContentTypeFilter', () => {
let existingItem: Item
@@ -22,23 +23,24 @@ describe('ContentTypeFilter', () => {
]
for (const invalidContentType of invalidContentTypes) {
const itemHash = ItemHash.create({
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: invalidContentType,
user_uuid: '1-2-3',
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: invalidContentType,
},
itemHash,
existingItem: null,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: invalidContentType,
},
unsavedItem: itemHash,
type: 'content_type_error',
},
})
@@ -49,13 +51,18 @@ describe('ContentTypeFilter', () => {
const validContentTypes = ['Note', 'SN|ItemsKey', 'SN|Component', 'SN|Editor', 'SN|ExtensionRepo', 'Tag']
for (const validContentType of validContentTypes) {
const itemHash = ItemHash.create({
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: validContentType,
user_uuid: '1-2-3',
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: validContentType,
},
itemHash,
existingItem,
})

View File

@@ -7,7 +7,7 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
export class ContentTypeFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const contentTypeOrError = ContentType.create(dto.itemHash.content_type)
const contentTypeOrError = ContentType.create(dto.itemHash.props.content_type)
if (contentTypeOrError.isFailed()) {
return {
passed: false,

View File

@@ -5,6 +5,7 @@ import { Item } from '../Item'
import { OwnershipFilter } from './OwnershipFilter'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { ItemHash } from '../ItemHash'
describe('OwnershipFilter', () => {
let existingItem: Item
@@ -30,36 +31,75 @@ describe('OwnershipFilter', () => {
})
it('should filter out items belonging to a different user', async () => {
const itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: '00000000-0000-0000-0000-000000000001',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
itemHash,
existingItem,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
unsavedItem: itemHash,
type: 'uuid_conflict',
},
})
})
it('should deffer to the shared vault filter if the item hash represents a shared vault item or existing item is a shared vault item', async () => {
const itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue()
const result = await createFilter().check({
userUuid: '00000000-0000-0000-0000-000000000001',
apiVersion: ApiVersion.v20200115,
itemHash,
existingItem,
})
expect(result).toEqual({
passed: true,
})
})
it('should leave items belonging to the same user', async () => {
const result = await createFilter().check({
userUuid: '00000000-0000-0000-0000-000000000000',
apiVersion: ApiVersion.v20200115,
itemHash: {
itemHash: ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue(),
existingItem,
})
@@ -72,10 +112,18 @@ describe('OwnershipFilter', () => {
const result = await createFilter().check({
userUuid: '00000000-0000-0000-0000-000000000000',
apiVersion: ApiVersion.v20200115,
itemHash: {
itemHash: ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue(),
existingItem: null,
})
@@ -85,23 +133,29 @@ describe('OwnershipFilter', () => {
})
it('should return an error if the user uuid is invalid', async () => {
const itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: 'invalid',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
itemHash,
existingItem,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
unsavedItem: itemHash,
type: 'uuid_error',
},
})

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