Compare commits

..

15 Commits

Author SHA1 Message Date
standardci
3039f58b5a chore(release): publish new version
- @standardnotes/api-gateway@1.29.0
 - @standardnotes/auth-server@1.42.0
 - @standardnotes/workspace-server@1.11.0
2022-10-11 13:30:43 +00:00
Karol Sójko
e2326190d4 fix: add missing dependency 2022-10-11 15:29:03 +02:00
Karol Sójko
095d13f8bb feat: add listin worspaces and workspace users 2022-10-11 15:24:21 +02:00
standardci
1292d1d898 chore(release): publish new version
- @standardnotes/api-gateway@1.28.2
 - @standardnotes/auth-server@1.41.2
 - @standardnotes/common@1.39.0
 - @standardnotes/domain-events-infra@1.8.25
 - @standardnotes/domain-events@2.66.3
 - @standardnotes/event-store@1.4.4
 - @standardnotes/files-server@1.6.16
 - @standardnotes/predicates@1.4.10
 - @standardnotes/scheduler-server@1.10.44
 - @standardnotes/security@1.4.8
 - @standardnotes/syncing-server@1.9.6
 - @standardnotes/workspace-server@1.10.0
2022-10-11 10:01:41 +00:00
Karol Sójko
8bc92616d2 feat(workspace): extract workspace user status to common 2022-10-11 12:00:01 +02:00
standardci
ae45fafaee chore(release): publish new version
- @standardnotes/api-gateway@1.28.1
 - @standardnotes/auth-server@1.41.1
 - @standardnotes/common@1.38.0
 - @standardnotes/domain-events-infra@1.8.24
 - @standardnotes/domain-events@2.66.2
 - @standardnotes/event-store@1.4.3
 - @standardnotes/files-server@1.6.15
 - @standardnotes/predicates@1.4.9
 - @standardnotes/scheduler-server@1.10.43
 - @standardnotes/security@1.4.7
 - @standardnotes/syncing-server@1.9.5
 - @standardnotes/workspace-server@1.9.0
2022-10-11 09:54:42 +00:00
Karol Sójko
f74227067b feat(workspace): add invite access level 2022-10-11 11:52:31 +02:00
standardci
5f76d25ec3 chore(release): publish new version
- @standardnotes/workspace-server@1.8.0
2022-10-11 09:33:00 +00:00
Karol Sójko
ba9d3bfe46 feat(workspace): add workspace user display name 2022-10-11 11:30:49 +02:00
standardci
3dc6babfca chore(release): publish new version
- @standardnotes/workspace-server@1.7.0
2022-10-11 08:02:58 +00:00
Karol Sójko
ace2b6936a feat(workspace): accepting invitation 2022-10-11 10:01:13 +02:00
standardci
712e874bfe chore(release): publish new version
- @standardnotes/api-gateway@1.28.0
 - @standardnotes/auth-server@1.41.0
 - @standardnotes/workspace-server@1.6.0
2022-10-11 07:44:12 +00:00
Karol Sójko
266adda45b feat(workspace): add invite to workspace endpoints 2022-10-11 09:41:45 +02:00
standardci
f5ebe4a69e chore(release): publish new version
- @standardnotes/api-gateway@1.27.4
 - @standardnotes/auth-server@1.40.4
 - @standardnotes/common@1.37.0
 - @standardnotes/domain-events-infra@1.8.23
 - @standardnotes/domain-events@2.66.1
 - @standardnotes/event-store@1.4.2
 - @standardnotes/files-server@1.6.14
 - @standardnotes/predicates@1.4.8
 - @standardnotes/scheduler-server@1.10.42
 - @standardnotes/security@1.4.6
 - @standardnotes/syncing-server@1.9.4
 - @standardnotes/workspace-server@1.5.1
2022-10-10 10:34:40 +00:00
Karol Sójko
15d960d47b feat(common): add WORKSPACE_INVITE_CREATED email message identifier 2022-10-10 12:32:35 +02:00
88 changed files with 1344 additions and 249 deletions

138
.pnp.cjs generated
View File

@@ -2521,30 +2521,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/api", [\
["npm:1.11.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.11.0-ce72fb3e14-f1134efb44.zip/node_modules/@standardnotes/api/",\
["npm:1.15.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.11.0"],\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/encryption", "npm:1.16.0"],\
["@standardnotes/models", "npm:1.24.0"],\
["@standardnotes/responses", "npm:1.10.4"],\
["@standardnotes/encryption", "npm:1.17.0"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.9.0"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.9.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.9.0-507434ff00-cc3feac393.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.9.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/encryption", "npm:1.15.9"],\
["@standardnotes/models", "npm:1.22.0"],\
["@standardnotes/responses", "npm:1.10.3"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/utils", "npm:1.10.0"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -2614,7 +2600,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/analytics", "workspace:packages/analytics"],\
["@standardnotes/api", "npm:1.9.0"],\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@@ -2739,28 +2725,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/encryption", [\
["npm:1.15.9", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.15.9-00c7fac9f6-7595ac08ce.zip/node_modules/@standardnotes/encryption/",\
["npm:1.17.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.15.9"],\
["@standardnotes/encryption", "npm:1.17.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.22.0"],\
["@standardnotes/responses", "npm:1.10.3"],\
["@standardnotes/sncrypto-common", "npm:1.12.0"],\
["@standardnotes/utils", "npm:1.9.0"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.16.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.16.0-df46ea19bc-9971b9afcc.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.16.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.24.0"],\
["@standardnotes/responses", "npm:1.10.4"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/utils", "npm:1.10.0"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -2818,10 +2791,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["npm:1.52.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.2-076ab9f511-ab345f8dc1.zip/node_modules/@standardnotes/features/",\
["npm:1.53.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.53.0-8ea4a2d559-a856e815a3.zip/node_modules/@standardnotes/features/",\
"packageDependencies": [\
["@standardnotes/features", "npm:1.52.2"],\
["@standardnotes/features", "npm:1.53.0"],\
["@standardnotes/auth", "npm:3.19.4"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/security", "workspace:packages/security"],\
@@ -2883,27 +2856,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/models", [\
["npm:1.22.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.22.0-2cc72f987b-9928246368.zip/node_modules/@standardnotes/models/",\
["npm:1.26.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.26.0-dade8919ab-f595a3de88.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.22.0"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.1"],\
["@standardnotes/responses", "npm:1.10.3"],\
["@standardnotes/utils", "npm:1.9.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.24.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.24.0-bf039594ac-2acbbbc062.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.24.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.2"],\
["@standardnotes/responses", "npm:1.10.4"],\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/features", "npm:1.53.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/utils", "npm:1.10.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
@@ -2939,23 +2899,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/responses", [\
["npm:1.10.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.3-7cdb15f83a-4a1e31eb89.zip/node_modules/@standardnotes/responses/",\
["npm:1.11.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.11.0-d066ddbbb6-46d6a47980.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.10.3"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.1"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.10.4", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.4-3af0d3ab54-41e4971144.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.10.4"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.2"],\
["@standardnotes/features", "npm:1.53.0"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
@@ -3066,14 +3015,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/sncrypto-common", [\
["npm:1.12.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.12.0-1a093ff006-b89a14bd23.zip/node_modules/@standardnotes/sncrypto-common/",\
"packageDependencies": [\
["@standardnotes/sncrypto-common", "npm:1.12.0"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.13.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.0-18cb5f8eb9-e58258f525.zip/node_modules/@standardnotes/sncrypto-common/",\
"packageDependencies": [\
@@ -3184,6 +3125,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/utils", [\
["npm:1.10.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
["@standardnotes/utils", "npm:1.10.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["dompurify", "npm:2.4.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.6.12", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.6.12-8fa8d7d09b-e177b1fa51.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
@@ -3193,17 +3145,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["lodash", "npm:4.17.21"]\
],\
"linkType": "HARD"\
}],\
["npm:1.9.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.0-da939553f6-4591aff48d.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["dompurify", "npm:2.4.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/workspace-server", [\
@@ -3213,10 +3154,11 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/api", "npm:1.11.0"],\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.12"],\

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.28.2...@standardnotes/api-gateway@1.29.0) (2022-10-11)
### Features
* add listin worspaces and workspace users ([095d13f](https://github.com/standardnotes/api-gateway/commit/095d13f8bbfe543fcf086840e1a985447a6c51ef))
## [1.28.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.28.1...@standardnotes/api-gateway@1.28.2) (2022-10-11)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.28.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.28.0...@standardnotes/api-gateway@1.28.1) (2022-10-11)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.28.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.27.4...@standardnotes/api-gateway@1.28.0) (2022-10-11)
### Features
* **workspace:** add invite to workspace endpoints ([266adda](https://github.com/standardnotes/api-gateway/commit/266adda45bd3ad84bc6605824b6be1dd912f3f9a))
## [1.27.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.27.3...@standardnotes/api-gateway@1.27.4) (2022-10-10)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.27.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.27.2...@standardnotes/api-gateway@1.27.3) (2022-10-10)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -20,6 +20,7 @@ import '../src/Controller/v1/OfflineController'
import '../src/Controller/v1/FilesController'
import '../src/Controller/v1/SubscriptionInvitesController'
import '../src/Controller/v1/WorkspacesController'
import '../src/Controller/v1/InvitesController'
import '../src/Controller/v2/PaymentsControllerV2'
import '../src/Controller/v2/ActionsControllerV2'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.27.3",
"version": "1.29.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -0,0 +1,23 @@
import { inject } from 'inversify'
import { Request, Response } from 'express'
import { controller, BaseHttpController, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/invites', TYPES.AuthMiddleware)
export class InvitesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/:inviteUuid/accept')
async accept(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(
request,
response,
`invites/${request.params.inviteUuid}/accept`,
request.body,
)
}
}

View File

@@ -1,18 +1,43 @@
import { inject } from 'inversify'
import { Request, Response } from 'express'
import { controller, BaseHttpController, httpPost } from 'inversify-express-utils'
import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/workspaces')
@controller('/v1/workspaces', TYPES.AuthMiddleware)
export class WorkspacesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
@httpPost('/')
async create(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body)
}
@httpGet('/:workspaceUuid/users')
async listWorkspaceUsers(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(
request,
response,
`workspaces/${request.params.workspaceUuid}/users`,
request.body,
)
}
@httpGet('/')
async listWorkspaces(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body)
}
@httpPost('/:workspaceUuid/invites')
async invite(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(
request,
response,
`workspaces/${request.params.workspaceUuid}/invites`,
request.body,
)
}
}

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.42.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.41.2...@standardnotes/auth-server@1.42.0) (2022-10-11)
### Features
* add listin worspaces and workspace users ([095d13f](https://github.com/standardnotes/server/commit/095d13f8bbfe543fcf086840e1a985447a6c51ef))
## [1.41.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.41.1...@standardnotes/auth-server@1.41.2) (2022-10-11)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.41.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.41.0...@standardnotes/auth-server@1.41.1) (2022-10-11)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.41.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.40.4...@standardnotes/auth-server@1.41.0) (2022-10-11)
### Features
* **workspace:** add invite to workspace endpoints ([266adda](https://github.com/standardnotes/server/commit/266adda45bd3ad84bc6605824b6be1dd912f3f9a))
## [1.40.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.40.3...@standardnotes/auth-server@1.40.4) (2022-10-10)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.40.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.40.2...@standardnotes/auth-server@1.40.3) (2022-10-10)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.40.3",
"version": "1.42.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -34,7 +34,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/analytics": "workspace:*",
"@standardnotes/api": "^1.9.0",
"@standardnotes/api": "^1.15.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.39.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.38.0...@standardnotes/common@1.39.0) (2022-10-11)
### Features
* **workspace:** extract workspace user status to common ([8bc9261](https://github.com/standardnotes/server/commit/8bc92616d2fbeb833c3fcbef6b87538745fc7f3e))
# [1.38.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.37.0...@standardnotes/common@1.38.0) (2022-10-11)
### Features
* **workspace:** add invite access level ([f742270](https://github.com/standardnotes/server/commit/f74227067b7151cb63a54e815e57f81984467bfe))
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.36.1...@standardnotes/common@1.37.0) (2022-10-10)
### Features
* **common:** add WORKSPACE_INVITE_CREATED email message identifier ([15d960d](https://github.com/standardnotes/server/commit/15d960d47b0bcf5aeddf869ac939eafb08166db7))
## [1.36.1](https://github.com/standardnotes/server/compare/@standardnotes/common@1.36.0...@standardnotes/common@1.36.1) (2022-10-10)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.36.1",
"version": "1.39.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -24,4 +24,5 @@ export enum EmailMessageIdentifier {
REFUND_NOTICE = 'REFUND_NOTICE',
REFUND_REQUESTED = 'REFUND_REQUESTED',
RATE_ADJUSTMENT_NOTICE = 'RATE_ADJUSTMENT_NOTICE',
WORKSPACE_INVITE_CREATED = 'WORKSPACE_INVITE_CREATED',
}

View File

@@ -24,4 +24,6 @@ export * from './Type/Either'
export * from './Type/Only'
export * from './Validator/UuidValidator'
export * from './Validator/ValidatorInterface'
export * from './Workspace/WorkspaceAccessLevel'
export * from './Workspace/WorkspaceType'
export * from './Workspace/WorkspaceUserStatus'

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.25](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.24...@standardnotes/domain-events-infra@1.8.25) (2022-10-11)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.24](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.23...@standardnotes/domain-events-infra@1.8.24) (2022-10-11)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.23](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.22...@standardnotes/domain-events-infra@1.8.23) (2022-10-10)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.22](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.21...@standardnotes/domain-events-infra@1.8.22) (2022-10-10)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.8.22",
"version": "1.8.25",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.66.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.66.2...@standardnotes/domain-events@2.66.3) (2022-10-11)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.66.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.66.1...@standardnotes/domain-events@2.66.2) (2022-10-11)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.66.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.66.0...@standardnotes/domain-events@2.66.1) (2022-10-10)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.66.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.65.0...@standardnotes/domain-events@2.66.0) (2022-10-10)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.66.0",
"version": "2.66.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.3...@standardnotes/event-store@1.4.4) (2022-10-11)
**Note:** Version bump only for package @standardnotes/event-store
## [1.4.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.2...@standardnotes/event-store@1.4.3) (2022-10-11)
**Note:** Version bump only for package @standardnotes/event-store
## [1.4.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.1...@standardnotes/event-store@1.4.2) (2022-10-10)
**Note:** Version bump only for package @standardnotes/event-store
## [1.4.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.0...@standardnotes/event-store@1.4.1) (2022-10-10)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.16](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.15...@standardnotes/files-server@1.6.16) (2022-10-11)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.15](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.14...@standardnotes/files-server@1.6.15) (2022-10-11)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.14](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.13...@standardnotes/files-server@1.6.14) (2022-10-10)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.13](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.12...@standardnotes/files-server@1.6.13) (2022-10-10)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.6.13",
"version": "1.6.16",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.10](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.9...@standardnotes/predicates@1.4.10) (2022-10-11)
**Note:** Version bump only for package @standardnotes/predicates
## [1.4.9](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.8...@standardnotes/predicates@1.4.9) (2022-10-11)
**Note:** Version bump only for package @standardnotes/predicates
## [1.4.8](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.7...@standardnotes/predicates@1.4.8) (2022-10-10)
**Note:** Version bump only for package @standardnotes/predicates
## [1.4.7](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.6...@standardnotes/predicates@1.4.7) (2022-10-10)
**Note:** Version bump only for package @standardnotes/predicates

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/predicates",
"version": "1.4.7",
"version": "1.4.10",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.44](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.43...@standardnotes/scheduler-server@1.10.44) (2022-10-11)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.43](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.42...@standardnotes/scheduler-server@1.10.43) (2022-10-11)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.42](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.41...@standardnotes/scheduler-server@1.10.42) (2022-10-10)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.41](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.40...@standardnotes/scheduler-server@1.10.41) (2022-10-10)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.10.41",
"version": "1.10.44",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.8](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.7...@standardnotes/security@1.4.8) (2022-10-11)
**Note:** Version bump only for package @standardnotes/security
## [1.4.7](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.6...@standardnotes/security@1.4.7) (2022-10-11)
**Note:** Version bump only for package @standardnotes/security
## [1.4.6](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.5...@standardnotes/security@1.4.6) (2022-10-10)
**Note:** Version bump only for package @standardnotes/security
## [1.4.5](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.4...@standardnotes/security@1.4.5) (2022-10-10)
**Note:** Version bump only for package @standardnotes/security

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.4.5",
"version": "1.4.8",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.5...@standardnotes/syncing-server@1.9.6) (2022-10-11)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.9.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.4...@standardnotes/syncing-server@1.9.5) (2022-10-11)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.9.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.3...@standardnotes/syncing-server@1.9.4) (2022-10-10)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.9.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.2...@standardnotes/syncing-server@1.9.3) (2022-10-10)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.9.3",
"version": "1.9.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.10.0...@standardnotes/workspace-server@1.11.0) (2022-10-11)
### Features
* add listin worspaces and workspace users ([095d13f](https://github.com/standardnotes/server/commit/095d13f8bbfe543fcf086840e1a985447a6c51ef))
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.9.0...@standardnotes/workspace-server@1.10.0) (2022-10-11)
### Features
* **workspace:** extract workspace user status to common ([8bc9261](https://github.com/standardnotes/server/commit/8bc92616d2fbeb833c3fcbef6b87538745fc7f3e))
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.8.0...@standardnotes/workspace-server@1.9.0) (2022-10-11)
### Features
* **workspace:** add invite access level ([f742270](https://github.com/standardnotes/server/commit/f74227067b7151cb63a54e815e57f81984467bfe))
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.7.0...@standardnotes/workspace-server@1.8.0) (2022-10-11)
### Features
* **workspace:** add workspace user display name ([ba9d3bf](https://github.com/standardnotes/server/commit/ba9d3bfe4632d5001b8c967860df086f103e2e35))
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.6.0...@standardnotes/workspace-server@1.7.0) (2022-10-11)
### Features
* **workspace:** accepting invitation ([ace2b69](https://github.com/standardnotes/server/commit/ace2b6936a104f3cfcad8f15d846e845917aa678))
# [1.6.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.5.1...@standardnotes/workspace-server@1.6.0) (2022-10-11)
### Features
* **workspace:** add invite to workspace endpoints ([266adda](https://github.com/standardnotes/server/commit/266adda45bd3ad84bc6605824b6be1dd912f3f9a))
## [1.5.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.5.0...@standardnotes/workspace-server@1.5.1) (2022-10-10)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.5.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.4.1...@standardnotes/workspace-server@1.5.0) (2022-10-10)
### Features

View File

@@ -5,6 +5,7 @@ import 'newrelic'
import * as Sentry from '@sentry/node'
import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressInvitesController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController'
import * as cors from 'cors'

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class addUserDisplayName1665480537103 implements MigrationInterface {
name = 'addUserDisplayName1665480537103'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `workspace_users` ADD `user_display_name` varchar(255) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `workspace_users` DROP COLUMN `user_display_name`')
}
}

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class addInviteAccessLevel1665481699781 implements MigrationInterface {
name = 'addInviteAccessLevel1665481699781'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `workspace_invites` ADD `access_level` varchar(64) NOT NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `workspace_invites` DROP COLUMN `access_level`')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.5.0",
"version": "1.11.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -25,10 +25,11 @@
"dependencies": {
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/api": "^1.11.0",
"@standardnotes/api": "^1.15.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:^",
"@standardnotes/domain-events-infra": "workspace:^",
"@standardnotes/models": "^1.26.0",
"@standardnotes/security": "workspace:*",
"@standardnotes/time": "workspace:^",
"aws-sdk": "^2.1159.0",

View File

@@ -38,6 +38,14 @@ import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection'
import { WorkspaceProjector } from '../Domain/Projection/WorkspaceProjector'
import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface'
import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection'
import { WorkspaceUserProjector } from '../Domain/Projection/WorkspaceUserProjector'
import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation'
import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -131,8 +139,16 @@ export class ContainerConfigLoader {
// use cases
container.bind<CreateWorkspace>(TYPES.CreateWorkspace).to(CreateWorkspace)
container.bind<InviteToWorkspace>(TYPES.InviteToWorkspace).to(InviteToWorkspace)
container.bind<AcceptInvitation>(TYPES.AcceptInvitation).to(AcceptInvitation)
container.bind<ListWorkspaces>(TYPES.ListWorkspaces).to(ListWorkspaces)
container.bind<ListWorkspaceUsers>(TYPES.ListWorkspaceUsers).to(ListWorkspaceUsers)
// Handlers
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
// Projection
container.bind<ProjectorInterface<Workspace, WorkspaceProjection>>(TYPES.WorkspaceProjector).to(WorkspaceProjector)
container
.bind<ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>>(TYPES.WorkspaceUserProjector)
.to(WorkspaceUserProjector)
// Services
container.bind<DomainEventFactoryInterface>(TYPES.DomainEventFactory).to(DomainEventFactory)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())

View File

@@ -28,8 +28,14 @@ const TYPES = {
// use cases
CreateWorkspace: Symbol.for('CreateWorkspace'),
InviteToWorkspace: Symbol.for('InviteToWorkspace'),
AcceptInvitation: Symbol.for('AcceptInvitation'),
ListWorkspaces: Symbol.for('ListWorkspaces'),
ListWorkspaceUsers: Symbol.for('ListWorkspaceUsers'),
// Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
// Projection
WorkspaceProjector: Symbol.for('WorkspaceProjector'),
WorkspaceUserProjector: Symbol.for('WorkspaceUserProjector'),
// Services
Timer: Symbol.for('Timer'),
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),

View File

@@ -1,17 +1,66 @@
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import 'reflect-metadata'
import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface'
import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection'
import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection'
import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation'
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
import { Workspace } from '../Domain/Workspace/Workspace'
import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser'
import { WorkspacesController } from './WorkspacesController'
describe('WorkspacesController', () => {
let doCreateWorkspace: CreateWorkspace
let doInviteToWorkspace: InviteToWorkspace
let doAcceptInvitation: AcceptInvitation
let doListWorkspaces: ListWorkspaces
let doListWorkspaceUsers: ListWorkspaceUsers
let workspacesProject: ProjectorInterface<Workspace, WorkspaceProjection>
let workspaceUsersProjector: ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>
let workspace1: Workspace
let workspace2: Workspace
let workspaceUser1: WorkspaceUser
let workspaceUser2: WorkspaceUser
const createController = () => new WorkspacesController(doCreateWorkspace)
const createController = () =>
new WorkspacesController(
doCreateWorkspace,
doInviteToWorkspace,
doListWorkspaces,
doListWorkspaceUsers,
doAcceptInvitation,
workspacesProject,
workspaceUsersProjector,
)
beforeEach(() => {
doCreateWorkspace = {} as jest.Mocked<CreateWorkspace>
doCreateWorkspace.execute = jest.fn().mockReturnValue({ workspace: { uuid: 'w-1-2-3' } })
doInviteToWorkspace = {} as jest.Mocked<InviteToWorkspace>
doInviteToWorkspace.execute = jest.fn().mockReturnValue({ invite: { uuid: 'i-1-2-3' } })
doListWorkspaces = {} as jest.Mocked<ListWorkspaces>
doListWorkspaces.execute = jest
.fn()
.mockReturnValue({ ownedWorkspaces: [workspace1], joinedWorkspaces: [workspace2] })
doListWorkspaceUsers = {} as jest.Mocked<ListWorkspaceUsers>
doListWorkspaceUsers.execute = jest.fn().mockReturnValue({ workspaceUsers: [workspaceUser1, workspaceUser2] })
doAcceptInvitation = {} as jest.Mocked<AcceptInvitation>
doAcceptInvitation.execute = jest.fn().mockReturnValue({ success: true })
workspacesProject = {} as jest.Mocked<ProjectorInterface<Workspace, WorkspaceProjection>>
workspacesProject.project = jest.fn().mockReturnValue({ foo: 'bar' })
workspaceUsersProjector = {} as jest.Mocked<ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>>
workspaceUsersProjector.project = jest.fn().mockReturnValue({ bar: 'buzz' })
})
it('should create a workspace', async () => {
@@ -21,6 +70,7 @@ describe('WorkspacesController', () => {
publicKey: 'buzz',
workspaceName: 'A Team',
ownerUuid: 'u-1-2-3',
workspaceType: WorkspaceType.Private,
})
expect(result).toEqual({
@@ -30,4 +80,82 @@ describe('WorkspacesController', () => {
status: 200,
})
})
it('should invite to a workspace', async () => {
const result = await createController().inviteToWorkspace({
inviteeEmail: 'test@test.te',
workspaceUuid: 'w-1-2-3',
accessLevel: WorkspaceAccessLevel.ReadOnly,
})
expect(result).toEqual({
data: {
uuid: 'i-1-2-3',
},
status: 200,
})
})
it('should accept an invite', async () => {
const result = await createController().acceptInvite({
userUuid: '1-2-3',
encryptedPrivateKey: 'foo',
inviteUuid: 'i-1-2-3',
publicKey: 'bar',
})
expect(result).toEqual({
data: {
success: true,
},
status: 200,
})
})
it('should not accept an invite if it fails', async () => {
doAcceptInvitation.execute = jest.fn().mockReturnValue({ success: false })
const result = await createController().acceptInvite({
userUuid: '1-2-3',
encryptedPrivateKey: 'foo',
inviteUuid: 'i-1-2-3',
publicKey: 'bar',
})
expect(result).toEqual({
data: {
error: {
message: 'Could not accept invite',
},
},
status: 400,
})
})
it('should list workspaces', async () => {
const result = await createController().listWorkspaces({
userUuid: '1-2-3',
})
expect(result).toEqual({
data: {
ownedWorkspaces: [{ foo: 'bar' }],
joinedWorkspaces: [{ foo: 'bar' }],
},
status: 200,
})
})
it('should list workspace users', async () => {
const result = await createController().listWorkspaceUsers({
userUuid: '1-2-3',
workspaceUuid: 'w-1-2-3',
})
expect(result).toEqual({
data: {
users: [{ bar: 'buzz' }, { bar: 'buzz' }],
},
status: 200,
})
})
})

View File

@@ -3,16 +3,56 @@ import {
HttpStatusCode,
WorkspaceCreationRequestParams,
WorkspaceCreationResponse,
WorkspaceInvitationRequestParams,
WorkspaceInvitationResponse,
WorkspaceServerInterface,
WorkspaceListRequestParams,
WorkspaceListResponse,
WorkspaceInvitationAcceptingRequestParams,
WorkspaceInvitationAcceptingResponse,
WorkspaceUserListRequestParams,
} from '@standardnotes/api'
import { WorkspaceType } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
import TYPES from '../Bootstrap/Types'
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface'
import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection'
import { Workspace } from '../Domain/Workspace/Workspace'
import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces'
import { WorkspaceUserListResponse } from '@standardnotes/api/dist/Domain/Response/Workspace/WorkspaceUserListResponse'
import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation'
import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser'
import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
@injectable()
export class WorkspacesController implements WorkspaceServerInterface {
constructor(@inject(TYPES.CreateWorkspace) private doCreateWorkspace: CreateWorkspace) {}
constructor(
@inject(TYPES.CreateWorkspace) private doCreateWorkspace: CreateWorkspace,
@inject(TYPES.InviteToWorkspace) private doInviteToWorkspace: InviteToWorkspace,
@inject(TYPES.ListWorkspaces) private doListWorkspaces: ListWorkspaces,
@inject(TYPES.ListWorkspaceUsers) private doListWorkspaceUsers: ListWorkspaceUsers,
@inject(TYPES.AcceptInvitation) private doAcceptInvite: AcceptInvitation,
@inject(TYPES.WorkspaceProjector) private workspaceProjector: ProjectorInterface<Workspace, WorkspaceProjection>,
@inject(TYPES.WorkspaceUserProjector)
private workspaceUserProjector: ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>,
) {}
async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse> {
const { invite } = await this.doInviteToWorkspace.execute({
inviteeEmail: params.inviteeEmail,
workspaceUuid: params.workspaceUuid,
inviterUuid: params.inviterUuid as Uuid,
accessLevel: params.accessLevel as WorkspaceAccessLevel,
})
return {
status: HttpStatusCode.Success,
data: { uuid: invite.uuid },
}
}
async createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse> {
const { workspace } = await this.doCreateWorkspace.execute({
@@ -29,4 +69,69 @@ export class WorkspacesController implements WorkspaceServerInterface {
data: { uuid: workspace.uuid },
}
}
async listWorkspaces(params: WorkspaceListRequestParams): Promise<WorkspaceListResponse> {
const { ownedWorkspaces, joinedWorkspaces } = await this.doListWorkspaces.execute({
userUuid: params.userUuid as Uuid,
})
const ownedWorkspacesProjections = []
for (const ownedWorkspace of ownedWorkspaces) {
ownedWorkspacesProjections.push(await this.workspaceProjector.project(ownedWorkspace))
}
const joinedWorkspacesProjections = []
for (const joinedWorkspace of joinedWorkspaces) {
joinedWorkspacesProjections.push(await this.workspaceProjector.project(joinedWorkspace))
}
return {
status: HttpStatusCode.Success,
data: { ownedWorkspaces: ownedWorkspacesProjections, joinedWorkspaces: joinedWorkspacesProjections },
}
}
async listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise<WorkspaceUserListResponse> {
const { workspaceUsers } = await this.doListWorkspaceUsers.execute({
userUuid: params.userUuid as Uuid,
workspaceUuid: params.workspaceUuid,
})
const workspaceUserProjections = []
for (const workspaceUser of workspaceUsers) {
workspaceUserProjections.push(await this.workspaceUserProjector.project(workspaceUser))
}
return {
status: HttpStatusCode.Success,
data: { users: workspaceUserProjections },
}
}
async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse> {
const result = await this.doAcceptInvite.execute({
acceptingUserUuid: params.userUuid,
encryptedPrivateKey: params.encryptedPrivateKey,
invitationUuid: params.inviteUuid,
publicKey: params.publicKey,
})
if (!result.success) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Could not accept invite',
},
},
}
}
return {
status: HttpStatusCode.Success,
data: {
success: true,
},
}
}
}

View File

@@ -1,4 +1,6 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
import { Workspace } from '../Workspace/Workspace'
import { WorkspaceInviteStatus } from './WorkspaceInviteStatus'
@@ -40,6 +42,12 @@ export class WorkspaceInvite {
})
declare workspaceUuid: string
@Column({
name: 'access_level',
length: 64,
})
declare accessLevel: WorkspaceAccessLevel
@Column({
name: 'created_at',
type: 'bigint',

View File

@@ -1,5 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { WorkspaceInvite } from './WorkspaceInvite'
export interface WorkspaceInviteRepositoryInterface {
findOneByUuid(uuid: Uuid): Promise<WorkspaceInvite | null>
save(workspace: WorkspaceInvite): Promise<WorkspaceInvite>
}

View File

@@ -0,0 +1,3 @@
export interface ProjectorInterface<T, E> {
project(object: T): Promise<E>
}

View File

@@ -0,0 +1,3 @@
import { Workspace } from '@standardnotes/models'
export type WorkspaceProjection = Workspace

View File

@@ -0,0 +1,30 @@
import 'reflect-metadata'
import { WorkspaceType } from '@standardnotes/common'
import { Workspace } from '../Workspace/Workspace'
import { WorkspaceProjector } from './WorkspaceProjector'
describe('WorkspaceProjector', () => {
const createProjector = () => new WorkspaceProjector()
it('should project a workspace', async () => {
expect(
await createProjector().project({
uuid: 'w-1-2-3',
type: WorkspaceType.Private,
name: 'test',
keyRotationIndex: 0,
createdAt: 1,
updatedAt: 2,
} as jest.Mocked<Workspace>),
).toEqual({
uuid: 'w-1-2-3',
type: 'private',
name: 'test',
keyRotationIndex: 0,
createdAt: 1,
updatedAt: 2,
})
})
})

View File

@@ -0,0 +1,19 @@
import { injectable } from 'inversify'
import { ProjectorInterface } from './ProjectorInterface'
import { WorkspaceProjection } from './WorkspaceProjection'
import { Workspace } from '../Workspace/Workspace'
@injectable()
export class WorkspaceProjector implements ProjectorInterface<Workspace, WorkspaceProjection> {
async project(workspace: Workspace): Promise<WorkspaceProjection> {
return {
uuid: workspace.uuid,
type: workspace.type,
name: workspace.name,
keyRotationIndex: workspace.keyRotationIndex,
createdAt: workspace.createdAt,
updatedAt: workspace.updatedAt,
}
}
}

View File

@@ -0,0 +1,3 @@
import { WorkspaceUser } from '@standardnotes/models'
export type WorkspaceUserProjection = WorkspaceUser

View File

@@ -0,0 +1,42 @@
import 'reflect-metadata'
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
import { WorkspaceUser } from '../Workspace/WorkspaceUser'
import { WorkspaceUserProjector } from './WorkspaceUserProjector'
describe('WorkspaceUserProjector', () => {
const createProjector = () => new WorkspaceUserProjector()
it('should project a workspace user', async () => {
expect(
await createProjector().project({
uuid: '1-2-3',
accessLevel: WorkspaceAccessLevel.Owner,
userUuid: 'u-1-2-3',
userDisplayName: 'foobar',
workspaceUuid: 'w-1-2-3',
encryptedWorkspaceKey: 'foo',
publicKey: 'bar',
encryptedPrivateKey: 'buzz',
status: WorkspaceUserStatus.PendingKeyshare,
keyRotationIndex: 0,
createdAt: 1,
updatedAt: 2,
} as jest.Mocked<WorkspaceUser>),
).toEqual({
uuid: '1-2-3',
accessLevel: 'owner',
userUuid: 'u-1-2-3',
userDisplayName: 'foobar',
workspaceUuid: 'w-1-2-3',
encryptedWorkspaceKey: 'foo',
publicKey: 'bar',
encryptedPrivateKey: 'buzz',
status: 'pending-keyshare',
keyRotationIndex: 0,
createdAt: 1,
updatedAt: 2,
})
})
})

View File

@@ -0,0 +1,25 @@
import { injectable } from 'inversify'
import { ProjectorInterface } from './ProjectorInterface'
import { WorkspaceUserProjection } from './WorkspaceUserProjection'
import { WorkspaceUser } from '../Workspace/WorkspaceUser'
@injectable()
export class WorkspaceUserProjector implements ProjectorInterface<WorkspaceUser, WorkspaceUserProjection> {
async project(workspaceUser: WorkspaceUser): Promise<WorkspaceUserProjection> {
return {
uuid: workspaceUser.uuid,
accessLevel: workspaceUser.accessLevel,
userUuid: workspaceUser.userUuid,
userDisplayName: workspaceUser.userDisplayName,
workspaceUuid: workspaceUser.workspaceUuid,
encryptedWorkspaceKey: workspaceUser.encryptedWorkspaceKey,
publicKey: workspaceUser.publicKey,
encryptedPrivateKey: workspaceUser.encryptedPrivateKey,
status: workspaceUser.status,
keyRotationIndex: workspaceUser.keyRotationIndex,
createdAt: workspaceUser.createdAt,
updatedAt: workspaceUser.updatedAt,
}
}
}

View File

@@ -0,0 +1,78 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { AcceptInvitation } from './AcceptInvitation'
import { WorkspaceAccessLevel } from '@standardnotes/common'
describe('AcceptInvitation', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let timer: TimerInterface
let invite: WorkspaceInvite
const createUseCase = () => new AcceptInvitation(workspaceInviteRepository, workspaceUserRepository, timer)
beforeEach(() => {
invite = {
uuid: 'i-1-2-3',
workspaceUuid: 'w-1-2-3',
inviteeEmail: 'test@test.te',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
} as jest.Mocked<WorkspaceInvite>
workspaceInviteRepository = {} as jest.Mocked<WorkspaceInviteRepositoryInterface>
workspaceInviteRepository.findOneByUuid = jest.fn().mockReturnValue(invite)
workspaceInviteRepository.save = jest.fn()
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
workspaceUserRepository.save = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
})
it('should accept an invite and assign user to workspace', async () => {
await createUseCase().execute({
acceptingUserUuid: 'u-1-2-3',
encryptedPrivateKey: 'foo',
publicKey: 'bar',
invitationUuid: 'i-1-2-3',
})
expect(workspaceInviteRepository.save).toHaveBeenCalledWith({
acceptingUserUuid: 'u-1-2-3',
status: 'accepted',
updatedAt: 1,
uuid: 'i-1-2-3',
workspaceUuid: 'w-1-2-3',
inviteeEmail: 'test@test.te',
accessLevel: 'write-and-read',
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
encryptedPrivateKey: 'foo',
publicKey: 'bar',
status: 'pending-keyshare',
userUuid: 'u-1-2-3',
workspaceUuid: 'w-1-2-3',
accessLevel: 'write-and-read',
userDisplayName: 'test@test.te',
})
})
it('should not accept an invite if it does not exist', async () => {
workspaceInviteRepository.findOneByUuid = jest.fn().mockReturnValue(null)
await createUseCase().execute({
acceptingUserUuid: 'u-1-2-3',
encryptedPrivateKey: 'foo',
publicKey: 'bar',
invitationUuid: 'i-1-2-3',
})
expect(workspaceInviteRepository.save).not.toHaveBeenCalled()
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,52 @@
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceUserStatus } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { WorkspaceInviteStatus } from '../../Invite/WorkspaceInviteStatus'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { AcceptInvitationDTO } from './AcceptInvitationDTO'
import { AcceptInvitationResponse } from './AcceptInvitationResponse'
@injectable()
export class AcceptInvitation implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
async execute(dto: AcceptInvitationDTO): Promise<AcceptInvitationResponse> {
const invite = await this.workspaceInviteRepository.findOneByUuid(dto.invitationUuid)
if (invite === null) {
return {
success: false,
}
}
invite.acceptingUserUuid = dto.acceptingUserUuid
invite.updatedAt = this.timer.getTimestampInMicroseconds()
invite.status = WorkspaceInviteStatus.Accepted
await this.workspaceInviteRepository.save(invite)
const workspaceUser = new WorkspaceUser()
workspaceUser.userUuid = dto.acceptingUserUuid
workspaceUser.userDisplayName = invite.inviteeEmail
workspaceUser.workspaceUuid = invite.workspaceUuid
workspaceUser.publicKey = dto.publicKey
workspaceUser.encryptedPrivateKey = dto.encryptedPrivateKey
workspaceUser.accessLevel = invite.accessLevel
workspaceUser.status = WorkspaceUserStatus.PendingKeyshare
await this.workspaceUserRepository.save(workspaceUser)
return {
success: true,
}
}
}

View File

@@ -0,0 +1,8 @@
import { Uuid } from '@standardnotes/common'
export type AcceptInvitationDTO = {
invitationUuid: Uuid
acceptingUserUuid: Uuid
publicKey: string
encryptedPrivateKey: string
}

View File

@@ -0,0 +1,3 @@
export type AcceptInvitationResponse = {
success: boolean
}

View File

@@ -1,13 +1,12 @@
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { Workspace } from '../../Workspace/Workspace'
import { WorkspaceAccessLevel } from '../../Workspace/WorkspaceAccessLevel'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { WorkspaceUserStatus } from '../../Workspace/WorkspaceUserStatus'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateWorkspaceDTO } from './CreateWorkspaceDTO'

View File

@@ -6,6 +6,7 @@ import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInvite
import { InviteToWorkspace } from './InviteToWorkspace'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface, WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events'
import { WorkspaceAccessLevel } from '@standardnotes/common'
describe('InviteToWorkspace', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
@@ -42,11 +43,24 @@ describe('InviteToWorkspace', () => {
inviteeEmail: 'test@test.te',
inviterUuid: 'u-1-2-3',
workspaceUuid: 'w-1-2-3',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
})
expect(result).toEqual({ uuid: 'i-1-2-3' })
expect(result).toEqual({
invite: {
uuid: 'i-1-2-3',
inviterUuid: 'u-1-2-3',
inviteeEmail: 'test@test.te',
workspaceUuid: 'w-1-2-3',
status: 'created',
accessLevel: 'write-and-read',
createdAt: 1,
updatedAt: 1,
},
})
expect(workspaceInviteRepository.save).toHaveBeenCalledWith({
accessLevel: 'write-and-read',
inviterUuid: 'u-1-2-3',
inviteeEmail: 'test@test.te',
workspaceUuid: 'w-1-2-3',

View File

@@ -26,6 +26,7 @@ export class InviteToWorkspace implements UseCaseInterface {
invite.inviterUuid = dto.inviterUuid
invite.inviteeEmail = dto.inviteeEmail
invite.workspaceUuid = dto.workspaceUuid
invite.accessLevel = dto.accessLevel
invite.status = WorkspaceInviteStatus.Created
const timestamp = this.timer.getTimestampInMicroseconds()
@@ -44,7 +45,7 @@ export class InviteToWorkspace implements UseCaseInterface {
)
return {
uuid: invite.uuid,
invite,
}
}
}

View File

@@ -1,7 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { Uuid, WorkspaceAccessLevel } from '@standardnotes/common'
export type InviteToWorkspaceDTO = {
workspaceUuid: Uuid
inviterUuid: Uuid
inviteeEmail: string
accessLevel: WorkspaceAccessLevel
}

View File

@@ -1,3 +1,5 @@
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
export type InviteToWorkspaceResponse = {
uuid: string
invite: WorkspaceInvite
}

View File

@@ -0,0 +1,84 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import 'reflect-metadata'
import { Workspace } from '../../Workspace/Workspace'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { ListWorkspaceUsers } from './ListWorkspaceUsers'
describe('ListWorkspaceUsers', () => {
let workspaceRepository: WorkspaceRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let workspace: Workspace
let workspaceUser1: WorkspaceUser
let workspaceUser2: WorkspaceUser
const createUseCase = () => new ListWorkspaceUsers(workspaceRepository, workspaceUserRepository)
beforeEach(() => {
workspace = { uuid: 'j-1-2-3' } as jest.Mocked<Workspace>
workspaceUser1 = { userUuid: 'u-1-2-3', accessLevel: WorkspaceAccessLevel.Owner } as jest.Mocked<WorkspaceUser>
workspaceUser2 = {
userUuid: 'u-2-3-4',
accessLevel: WorkspaceAccessLevel.WriteAndRead,
} as jest.Mocked<WorkspaceUser>
workspaceRepository = {} as jest.Mocked<WorkspaceRepositoryInterface>
workspaceRepository.findOneByUuid = jest.fn().mockReturnValue(workspace)
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
workspaceUserRepository.findByWorkspaceUuid = jest.fn().mockReturnValue([workspaceUser1, workspaceUser2])
})
it('should list users in a workspace where the user is owner or admin', async () => {
const result = await createUseCase().execute({
userUuid: 'u-1-2-3',
workspaceUuid: 'j-1-2-3',
})
expect(result).toEqual({
workspaceUsers: [workspaceUser1, workspaceUser2],
userIsOwnerOrAdmin: true,
})
})
it('should list users in a workspace where the user is not the owner or admin with indiciation', async () => {
const result = await createUseCase().execute({
userUuid: 'u-2-3-4',
workspaceUuid: 'j-1-2-3',
})
expect(result).toEqual({
workspaceUsers: [workspaceUser1, workspaceUser2],
userIsOwnerOrAdmin: false,
})
})
it('should not list users in a workspace where the user does not belong', async () => {
const result = await createUseCase().execute({
userUuid: 'z-1-2-3',
workspaceUuid: 'j-1-2-3',
})
expect(result).toEqual({
workspaceUsers: [],
userIsOwnerOrAdmin: false,
})
})
it('should not list users in a workspace that does not exist', async () => {
workspaceRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const result = await createUseCase().execute({
userUuid: 'u-1-2-3',
workspaceUuid: 'j-1-2-3',
})
expect(result).toEqual({
workspaceUsers: [],
userIsOwnerOrAdmin: false,
})
})
})

View File

@@ -0,0 +1,50 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { ListWorkspaceUsersDTO } from './ListWorkspaceUsersDTO'
import { ListWorkspaceUsersResponse } from './ListWorkspaceUsersResponse'
@injectable()
export class ListWorkspaceUsers implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
) {}
async execute(dto: ListWorkspaceUsersDTO): Promise<ListWorkspaceUsersResponse> {
const workspace = await this.workspaceRepository.findOneByUuid(dto.workspaceUuid)
if (workspace === null) {
return {
workspaceUsers: [],
userIsOwnerOrAdmin: false,
}
}
const workspaceUsers = await this.workspaceUserRepository.findByWorkspaceUuid(dto.workspaceUuid)
let userIsOwnerOrAdmin = false
let userIsInWorkspace = false
for (const workspaceUser of workspaceUsers) {
if (workspaceUser.userUuid === dto.userUuid) {
userIsInWorkspace = true
if ([WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceUser.accessLevel)) {
userIsOwnerOrAdmin = true
}
}
}
if (!userIsInWorkspace) {
return {
workspaceUsers: [],
userIsOwnerOrAdmin: false,
}
}
return {
workspaceUsers,
userIsOwnerOrAdmin,
}
}
}

View File

@@ -0,0 +1,6 @@
import { Uuid } from '@standardnotes/common'
export type ListWorkspaceUsersDTO = {
workspaceUuid: Uuid
userUuid: Uuid
}

View File

@@ -0,0 +1,6 @@
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
export type ListWorkspaceUsersResponse = {
workspaceUsers: WorkspaceUser[]
userIsOwnerOrAdmin: boolean
}

View File

@@ -0,0 +1,47 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import 'reflect-metadata'
import { Workspace } from '../../Workspace/Workspace'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { ListWorkspaces } from './ListWorkspaces'
describe('ListWorkspaces', () => {
let workspaceRepository: WorkspaceRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let ownedWorkspace: Workspace
let joinedWorkspace: Workspace
let workspaceUser1: WorkspaceUser
let workspaceUser2: WorkspaceUser
const createUseCase = () => new ListWorkspaces(workspaceRepository, workspaceUserRepository)
beforeEach(() => {
ownedWorkspace = { uuid: 'o-1-2-3' } as jest.Mocked<Workspace>
joinedWorkspace = { uuid: 'j-1-2-3' } as jest.Mocked<Workspace>
workspaceUser1 = { accessLevel: WorkspaceAccessLevel.Owner } as jest.Mocked<WorkspaceUser>
workspaceUser2 = { accessLevel: WorkspaceAccessLevel.WriteAndRead } as jest.Mocked<WorkspaceUser>
workspaceRepository = {} as jest.Mocked<WorkspaceRepositoryInterface>
workspaceRepository.findByUuids = jest
.fn()
.mockReturnValueOnce([ownedWorkspace])
.mockReturnValueOnce([joinedWorkspace])
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
workspaceUserRepository.findByUserUuid = jest.fn().mockReturnValue([workspaceUser1, workspaceUser2])
})
it('should list owned and joined workspaces for a user', async () => {
const result = await createUseCase().execute({
userUuid: 'u-1-2-3',
})
expect(result).toEqual({
ownedWorkspaces: [ownedWorkspace],
joinedWorkspaces: [joinedWorkspace],
})
})
})

View File

@@ -0,0 +1,40 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { ListWorkspacesDTO } from './ListWorkspacesDTO'
import { ListWorkspacesResponse } from './ListWorkspacesResponse'
@injectable()
export class ListWorkspaces implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
) {}
async execute(dto: ListWorkspacesDTO): Promise<ListWorkspacesResponse> {
const workspaceAssociations = await this.workspaceUserRepository.findByUserUuid(dto.userUuid)
const ownedWorkspacesUuids = []
const joinedWorkspacesUuids = []
for (const workspaceAssociation of workspaceAssociations) {
if ([WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceAssociation.accessLevel)) {
ownedWorkspacesUuids.push(workspaceAssociation.uuid)
} else {
joinedWorkspacesUuids.push(workspaceAssociation.uuid)
}
}
const ownedWorkspaces = await this.workspaceRepository.findByUuids(ownedWorkspacesUuids)
const joinedWorkspaces = await this.workspaceRepository.findByUuids(joinedWorkspacesUuids)
return {
ownedWorkspaces,
joinedWorkspaces,
}
}
}

View File

@@ -0,0 +1,5 @@
import { Uuid } from '@standardnotes/common'
export type ListWorkspacesDTO = {
userUuid: Uuid
}

View File

@@ -0,0 +1,6 @@
import { Workspace } from '../../Workspace/Workspace'
export type ListWorkspacesResponse = {
ownedWorkspaces: Array<Workspace>
joinedWorkspaces: Array<Workspace>
}

View File

@@ -1,5 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { Workspace } from './Workspace'
export interface WorkspaceRepositoryInterface {
save(workspace: Workspace): Promise<Workspace>
findByUuids(uuids: Uuid[]): Promise<Workspace[]>
findOneByUuid(uuid: Uuid): Promise<Workspace | null>
}

View File

@@ -1,6 +1,5 @@
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
import { WorkspaceAccessLevel } from './WorkspaceAccessLevel'
import { WorkspaceUserStatus } from './WorkspaceUserStatus'
@Entity({ name: 'workspace_users' })
@Index('index_workspace_users_on_workspace_and_user', ['userUuid', 'workspaceUuid'], { unique: true })
@@ -20,6 +19,14 @@ export class WorkspaceUser {
})
declare userUuid: string
@Column({
name: 'user_display_name',
type: 'varchar',
length: 255,
nullable: true,
})
declare userDisplayName: string | null
@Column({
name: 'workspace_uuid',
length: 36,

View File

@@ -1,5 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { WorkspaceUser } from './WorkspaceUser'
export interface WorkspaceUserRepositoryInterface {
save(workspace: WorkspaceUser): Promise<WorkspaceUser>
findByUserUuid(userUuid: Uuid): Promise<WorkspaceUser[]>
findByWorkspaceUuid(workspaceUuid: Uuid): Promise<WorkspaceUser[]>
}

View File

@@ -0,0 +1,23 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost, results } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { WorkspacesController } from '../../Controller/WorkspacesController'
@controller('/invites', TYPES.ApiGatewayAuthMiddleware)
export class InversifyExpressInvitesController extends BaseHttpController {
constructor(@inject(TYPES.WorkspacesController) private workspacesController: WorkspacesController) {
super()
}
@httpPost('/:inviteUuid/accept')
async acceptInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.acceptInvite({
...request.body,
inviteUuid: request.params.inviteUuid,
userUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
}

View File

@@ -1,16 +1,16 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost, results } from 'inversify-express-utils'
import { BaseHttpController, controller, httpGet, httpPost, results } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { WorkspacesController } from '../../Controller/WorkspacesController'
@controller('/workspaces')
@controller('/workspaces', TYPES.ApiGatewayAuthMiddleware)
export class InversifyExpressWorkspacesController extends BaseHttpController {
constructor(@inject(TYPES.WorkspacesController) private workspacesController: WorkspacesController) {
super()
}
@httpPost('/', TYPES.ApiGatewayAuthMiddleware)
@httpPost('/')
async create(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.createWorkspace({
...request.body,
@@ -19,4 +19,44 @@ export class InversifyExpressWorkspacesController extends BaseHttpController {
return this.json(result.data, result.status)
}
@httpGet('/')
async listWorkspaces(response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.listWorkspaces({
userUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
@httpGet('/:workspaceUuid/users')
async listWorkspaceUsers(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.listWorkspaceUsers({
userUuid: response.locals.user.uuid,
workspaceUuid: request.params.workspaceUuid,
})
return this.json(result.data, result.status)
}
@httpPost('/:workspaceUuid/invites')
async inviteToWorkspace(request: Request, response: Response): Promise<results.JsonResult> {
if (request.params.workspaceUuid !== request.body.workspaceUuid) {
return this.json(
{
error: {
message: 'Invalid workspace uuid.',
},
},
400,
)
}
const result = await this.workspacesController.inviteToWorkspace({
...request.body,
inviterUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
}

View File

@@ -27,4 +27,13 @@ describe('MySQLWorkspaceInviteRepository', () => {
expect(ormRepository.save).toHaveBeenCalledWith(invite)
})
it('should find one by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' })
})
})

View File

@@ -12,6 +12,10 @@ export class MySQLWorkspaceInviteRepository implements WorkspaceInviteRepository
private ormRepository: Repository<WorkspaceInvite>,
) {}
async findOneByUuid(uuid: string): Promise<WorkspaceInvite | null> {
return this.ormRepository.createQueryBuilder().where('uuid = :uuid', { uuid }).getOne()
}
async save(workspaceInvite: WorkspaceInvite): Promise<WorkspaceInvite> {
return this.ormRepository.save(workspaceInvite)
}

View File

@@ -28,4 +28,22 @@ describe('MySQLWorkspaceRepository', () => {
expect(ormRepository.save).toHaveBeenCalledWith(workspace)
})
it('should find many by uuids', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
await createRepository().findByUuids(['i-1-2-3'])
expect(queryBuilder.where).toHaveBeenCalledWith('uuid IN (:...uuids)', { uuids: ['i-1-2-3'] })
})
it('should find one by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' })
})
})

View File

@@ -11,6 +11,14 @@ export class MySQLWorkspaceRepository implements WorkspaceRepositoryInterface {
private ormRepository: Repository<Workspace>,
) {}
async findOneByUuid(uuid: string): Promise<Workspace | null> {
return this.ormRepository.createQueryBuilder().where('uuid = :uuid', { uuid }).getOne()
}
async findByUuids(uuids: string[]): Promise<Workspace[]> {
return this.ormRepository.createQueryBuilder().where('uuid IN (:...uuids)', { uuids }).getMany()
}
async save(workspace: Workspace): Promise<Workspace> {
return this.ormRepository.save(workspace)
}

View File

@@ -27,4 +27,22 @@ describe('MySQLWorkspaceUserRepository', () => {
expect(ormRepository.save).toHaveBeenCalledWith(workspace)
})
it('should find many by user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
await createRepository().findByUserUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: 'i-1-2-3' })
})
it('should find many by workspace uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
await createRepository().findByWorkspaceUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('workspace_uuid = :workspaceUuid', { workspaceUuid: 'i-1-2-3' })
})
})

View File

@@ -12,6 +12,14 @@ export class MySQLWorkspaceUserRepository implements WorkspaceUserRepositoryInte
private ormRepository: Repository<WorkspaceUser>,
) {}
async findByWorkspaceUuid(workspaceUuid: string): Promise<WorkspaceUser[]> {
return this.ormRepository.createQueryBuilder().where('workspace_uuid = :workspaceUuid', { workspaceUuid }).getMany()
}
async findByUserUuid(userUuid: string): Promise<WorkspaceUser[]> {
return this.ormRepository.createQueryBuilder().where('user_uuid = :userUuid', { userUuid }).getMany()
}
async save(workspaceUser: WorkspaceUser): Promise<WorkspaceUser> {
return this.ormRepository.save(workspaceUser)
}

177
yarn.lock
View File

@@ -1824,33 +1824,18 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/api@npm:^1.11.0":
version: 1.11.0
resolution: "@standardnotes/api@npm:1.11.0"
"@standardnotes/api@npm:^1.15.0":
version: 1.15.0
resolution: "@standardnotes/api@npm:1.15.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/encryption": 1.16.0
"@standardnotes/models": 1.24.0
"@standardnotes/responses": 1.10.4
"@standardnotes/common": ^1.39.0
"@standardnotes/encryption": 1.17.0
"@standardnotes/models": 1.26.0
"@standardnotes/responses": 1.11.0
"@standardnotes/security": ^1.1.0
"@standardnotes/utils": 1.9.0
"@standardnotes/utils": 1.10.0
reflect-metadata: ^0.1.13
checksum: f1134efb44a7a2cc8f117bc6a7826d89e11f7780d7ed6f16f654f1a96987cbe6f445ec5f5a91abb11fc79a1e621d9bab30f46fb7a746d44bb5e8b81904d9370f
languageName: node
linkType: hard
"@standardnotes/api@npm:^1.9.0":
version: 1.9.0
resolution: "@standardnotes/api@npm:1.9.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/encryption": 1.15.9
"@standardnotes/models": 1.22.0
"@standardnotes/responses": 1.10.3
"@standardnotes/security": ^1.1.0
"@standardnotes/utils": 1.9.0
reflect-metadata: ^0.1.13
checksum: cc3feac3935a382e0ce1fcaf233206a547b6c998cb99ab362d5c7030b3f4e7cbbd3a083eab40bdecbcdc9497dcd283e4513e29dbf200e815ffa46b192ed61b01
checksum: 88ae0a340e175b8251e4960f377998a9e21ec5c7967be85c813ff2103d281faea8b13faf787ac51dab4d1737521df136748c9dbd34f0eee2f61d860085bca840
languageName: node
linkType: hard
@@ -1861,7 +1846,7 @@ __metadata:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/analytics": "workspace:*"
"@standardnotes/api": ^1.9.0
"@standardnotes/api": ^1.15.0
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
@@ -1922,7 +1907,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common":
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@^1.39.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common":
version: 0.0.0-use.local
resolution: "@standardnotes/common@workspace:packages/common"
dependencies:
@@ -1987,31 +1972,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/encryption@npm:1.15.9":
version: 1.15.9
resolution: "@standardnotes/encryption@npm:1.15.9"
"@standardnotes/encryption@npm:1.17.0":
version: 1.17.0
resolution: "@standardnotes/encryption@npm:1.17.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/models": 1.22.0
"@standardnotes/responses": 1.10.3
"@standardnotes/sncrypto-common": 1.12.0
"@standardnotes/utils": 1.9.0
reflect-metadata: ^0.1.13
checksum: 7595ac08cea6e54e1456cbff3958969318d90ae237aff166bc4429da5bcf6167c5eb03aa8658a1747486a0639b80b523dae46106ab253c75d24579643cf3e948
languageName: node
linkType: hard
"@standardnotes/encryption@npm:1.16.0":
version: 1.16.0
resolution: "@standardnotes/encryption@npm:1.16.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/models": 1.24.0
"@standardnotes/responses": 1.10.4
"@standardnotes/common": ^1.39.0
"@standardnotes/models": 1.26.0
"@standardnotes/responses": 1.11.0
"@standardnotes/sncrypto-common": 1.13.0
"@standardnotes/utils": 1.9.0
"@standardnotes/utils": 1.10.0
reflect-metadata: ^0.1.13
checksum: 9971b9afcc8d32c7a6d720b43b7e098659d40212abfe1c4387f6b8aee4f410198a0e28db61bfdfdd8defe8cd336a9823e4cc565bf8f72145da2d349cb4be12a6
checksum: 587516dfed87ba0bc46fef9ddcc6edb42757c140f852096d6bf6f7911418a7aa1eb0b370c5a0c56eeab3e6b13117ec055374b6e023c64e67cdf2362a584ac06e
languageName: node
linkType: hard
@@ -2043,27 +2014,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/features@npm:1.52.1, @standardnotes/features@npm:^1.52.1":
version: 1.52.1
resolution: "@standardnotes/features@npm:1.52.1"
"@standardnotes/features@npm:1.53.0":
version: 1.53.0
resolution: "@standardnotes/features@npm:1.53.0"
dependencies:
"@standardnotes/auth": ^3.19.4
"@standardnotes/common": ^1.32.0
"@standardnotes/common": ^1.39.0
"@standardnotes/security": ^1.2.0
reflect-metadata: ^0.1.13
checksum: ff3684399e0e0c0e799f11e69dddea2e1f65f315e5a5dd3ca5640e24e836ee85faf1f5ee15fc804411bf083004527fcef08411d5c2d0b5894491bf2f28ceca68
languageName: node
linkType: hard
"@standardnotes/features@npm:1.52.2":
version: 1.52.2
resolution: "@standardnotes/features@npm:1.52.2"
dependencies:
"@standardnotes/auth": ^3.19.4
"@standardnotes/common": ^1.32.0
"@standardnotes/security": ^1.2.0
reflect-metadata: ^0.1.13
checksum: ab345f8dc11be32967e439366de0507707a95590b898d6c4332d5d42adbba7bb3fe32bca2ee1fd9948b9106d8186402439916402e8df50cf9e440996420251df
checksum: a856e815a313d42706836b1e4c8bbaeb987ee86f2e53bf4b928ed401de483ab11ecd31a8bcb13b23cb7b6a53f72a9f481da2acf68d5e0e970af4ce8c8f2e7c65
languageName: node
linkType: hard
@@ -2078,6 +2037,18 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/features@npm:^1.52.1":
version: 1.52.1
resolution: "@standardnotes/features@npm:1.52.1"
dependencies:
"@standardnotes/auth": ^3.19.4
"@standardnotes/common": ^1.32.0
"@standardnotes/security": ^1.2.0
reflect-metadata: ^0.1.13
checksum: ff3684399e0e0c0e799f11e69dddea2e1f65f315e5a5dd3ca5640e24e836ee85faf1f5ee15fc804411bf083004527fcef08411d5c2d0b5894491bf2f28ceca68
languageName: node
linkType: hard
"@standardnotes/files-server@workspace:packages/files":
version: 0.0.0-use.local
resolution: "@standardnotes/files-server@workspace:packages/files"
@@ -2128,31 +2099,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/models@npm:1.22.0":
version: 1.22.0
resolution: "@standardnotes/models@npm:1.22.0"
"@standardnotes/models@npm:1.26.0, @standardnotes/models@npm:^1.26.0":
version: 1.26.0
resolution: "@standardnotes/models@npm:1.26.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.1
"@standardnotes/responses": 1.10.3
"@standardnotes/utils": 1.9.0
"@standardnotes/common": ^1.39.0
"@standardnotes/features": 1.53.0
"@standardnotes/responses": 1.11.0
"@standardnotes/utils": 1.10.0
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 9928246368b7de7062314374219065507642ed3b6764c27f14ed8d42f0c5a9370fab8731a43a57885a000e461ea60694ba4caa7d9940350839d487cedb7079b5
languageName: node
linkType: hard
"@standardnotes/models@npm:1.24.0":
version: 1.24.0
resolution: "@standardnotes/models@npm:1.24.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.2
"@standardnotes/responses": 1.10.4
"@standardnotes/utils": 1.9.0
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 2acbbbc0629011bc9c901f2e61df936b13f349ff90409072dcfecf4899872f6d0e80d22bd45af9fc0928adc33097fd573983d0f194c4c84e7bf204a28de9943d
checksum: f595a3de88ca815d04190ff188c1355ac1848369f94b81f7fc7be3a03918eaaa964eb078bb77622efd1ba7e2db1a47595db94e3ecc90c3544a54838a3980d6b0
languageName: node
linkType: hard
@@ -2181,27 +2138,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/responses@npm:1.10.3":
version: 1.10.3
resolution: "@standardnotes/responses@npm:1.10.3"
"@standardnotes/responses@npm:1.11.0":
version: 1.11.0
resolution: "@standardnotes/responses@npm:1.11.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.1
"@standardnotes/common": ^1.39.0
"@standardnotes/features": 1.53.0
"@standardnotes/security": ^1.1.0
reflect-metadata: ^0.1.13
checksum: 4a1e31eb89342461488f00c65884839d7d935d47d4e217da83967c7cbe181fd0341ee58b744fe607def30d06cd1a8069c3b54f40b3933765303db35ee63f6a0d
languageName: node
linkType: hard
"@standardnotes/responses@npm:1.10.4":
version: 1.10.4
resolution: "@standardnotes/responses@npm:1.10.4"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.2
"@standardnotes/security": ^1.1.0
reflect-metadata: ^0.1.13
checksum: 41e4971144950e168c0de3d05a2871857d9a416ad8061849b646992f04d6590dce43e8161af0cb05c3faed1856fd4bf93fceea3fcabeebf62b5761e5376ce8c6
checksum: 46d6a479806507b15dc6f949e8863915f2cac81964aac715aab0bfc063e7bd7961767fcc6488e08397a0e1a52ccf2808429d8660b98a79415e25f601719125dc
languageName: node
linkType: hard
@@ -2301,15 +2246,6 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/sncrypto-common@npm:1.12.0":
version: 1.12.0
resolution: "@standardnotes/sncrypto-common@npm:1.12.0"
dependencies:
reflect-metadata: ^0.1.13
checksum: b89a14bd233cb781213b2e25dd8d7bfe911820d341903f2987647c168745c3afb710ad94fa42e50aaafb644ee4a7b5fd64ec137f622cfc082951fd584af0d230
languageName: node
linkType: hard
"@standardnotes/sncrypto-common@npm:1.13.0":
version: 1.13.0
resolution: "@standardnotes/sncrypto-common@npm:1.13.0"
@@ -2413,15 +2349,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/utils@npm:1.9.0":
version: 1.9.0
resolution: "@standardnotes/utils@npm:1.9.0"
"@standardnotes/utils@npm:1.10.0":
version: 1.10.0
resolution: "@standardnotes/utils@npm:1.10.0"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/common": ^1.39.0
dompurify: ^2.3.8
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 4591aff48d074b30b911f96c63eaaf521ab49563507672fbd4d7fe460e51f88a45effb002d1c82cca3513d2199c0cdb720556b03ec3e0266f593317c8efa764a
checksum: c02d54ca8a4debb62ecf6642825e1c313b3aad11767b1f0e6d36a976ec3058eba8592ab3194544042825296357676bc821e168489b638b0514bd1650dc9d0e0d
languageName: node
linkType: hard
@@ -2442,10 +2378,11 @@ __metadata:
dependencies:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/api": ^1.11.0
"@standardnotes/api": ^1.15.0
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:^"
"@standardnotes/domain-events-infra": "workspace:^"
"@standardnotes/models": ^1.26.0
"@standardnotes/security": "workspace:*"
"@standardnotes/time": "workspace:^"
"@types/cors": ^2.8.9