mirror of
https://github.com/standardnotes/server
synced 2026-01-25 20:01:08 -05:00
Compare commits
29 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
937ce5a157 | ||
|
|
0c1a779ef0 | ||
|
|
e01d1f44d0 | ||
|
|
cea9021c16 | ||
|
|
3039f58b5a | ||
|
|
e2326190d4 | ||
|
|
095d13f8bb | ||
|
|
1292d1d898 | ||
|
|
8bc92616d2 | ||
|
|
ae45fafaee | ||
|
|
f74227067b | ||
|
|
5f76d25ec3 | ||
|
|
ba9d3bfe46 | ||
|
|
3dc6babfca | ||
|
|
ace2b6936a | ||
|
|
712e874bfe | ||
|
|
266adda45b | ||
|
|
f5ebe4a69e | ||
|
|
15d960d47b | ||
|
|
f700b04b8f | ||
|
|
6f9683c41a | ||
|
|
0ad605c906 | ||
|
|
db4c49c57b | ||
|
|
b5c72dda8f | ||
|
|
e06cc3ba80 | ||
|
|
8a72a1a559 | ||
|
|
3f61d3163e | ||
|
|
34b3c7ce16 | ||
|
|
0ce4185379 |
138
.pnp.cjs
generated
138
.pnp.cjs
generated
@@ -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.16.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.16.0-efccf518ba-465f76dd29.zip/node_modules/@standardnotes/api/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api", "npm:1.11.0"],\
|
||||
["@standardnotes/api", "npm:1.16.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.1"],\
|
||||
["@standardnotes/models", "npm:1.27.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.16.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.1", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.17.1-f4d1330273-2b2408ffbd.zip/node_modules/@standardnotes/encryption/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/encryption", "npm:1.15.9"],\
|
||||
["@standardnotes/encryption", "npm:1.17.1"],\
|
||||
["@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.27.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,27 @@ 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"],\
|
||||
["@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"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.24.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.24.0-bf039594ac-2acbbbc062.zip/node_modules/@standardnotes/models/",\
|
||||
["npm:1.27.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.27.0-831bd645c6-263fd9e923.zip/node_modules/@standardnotes/models/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/models", "npm:1.24.0"],\
|
||||
["@standardnotes/models", "npm:1.27.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 +2912,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 +3028,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 +3138,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 +3158,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,11 +3167,13 @@ 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.16.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"],\
|
||||
["@types/express", "npm:4.17.13"],\
|
||||
["@types/ioredis", "npm:4.28.10"],\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.29.0...@standardnotes/api-gateway@1.30.0) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/api-gateway/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
|
||||
|
||||
# [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
|
||||
|
||||
## [1.27.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.27.1...@standardnotes/api-gateway@1.27.2) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.27.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.27.0...@standardnotes/api-gateway@1.27.1) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.27.1",
|
||||
"version": "1.30.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
23
packages/api-gateway/src/Controller/v1/InvitesController.ts
Normal file
23
packages/api-gateway/src/Controller/v1/InvitesController.ts
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,53 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/:workspaceUuid/users/:userUuid/keyshare')
|
||||
async initiateKeyshare(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(
|
||||
request,
|
||||
response,
|
||||
`workspaces/${request.params.workspaceUuid}/users/${request.params.userUuid}/keyshare`,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.42.0...@standardnotes/auth-server@1.43.0) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/server/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
|
||||
|
||||
# [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
|
||||
|
||||
## [1.40.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.40.1...@standardnotes/auth-server@1.40.2) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.40.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.40.0...@standardnotes/auth-server@1.40.1) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.40.1",
|
||||
"version": "1.43.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.16.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.36.1",
|
||||
"version": "1.39.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.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
|
||||
|
||||
## [1.8.21](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.20...@standardnotes/domain-events-infra@1.8.21) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.20](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.19...@standardnotes/domain-events-infra@1.8.20) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.8.20",
|
||||
"version": "1.8.25",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [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
|
||||
|
||||
* **workspace:** add publishing workspace invite created ([6f9683c](https://github.com/standardnotes/server/commit/6f9683c41a1135489832d9a854a114c82825a647))
|
||||
|
||||
# [2.65.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.64.1...@standardnotes/domain-events@2.65.0) (2022-10-10)
|
||||
|
||||
### Features
|
||||
|
||||
* add workspace invite created event ([db4c49c](https://github.com/standardnotes/server/commit/db4c49c57b81bfea6b8c6b8774c6a30e0561e154))
|
||||
|
||||
## [2.64.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.64.0...@standardnotes/domain-events@2.64.1) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.64.1",
|
||||
"version": "2.66.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -8,4 +8,5 @@ export enum DomainEventService {
|
||||
ApiGateway = 'api-gateway',
|
||||
Files = 'files',
|
||||
Scheduler = 'scheduler',
|
||||
Workspace = 'workspace',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
import { WorkspaceInviteCreatedEventPayload } from './WorkspaceInviteCreatedEventPayload'
|
||||
|
||||
export interface WorkspaceInviteCreatedEvent extends DomainEventInterface {
|
||||
type: 'WORKSPACE_INVITE_CREATED'
|
||||
payload: WorkspaceInviteCreatedEventPayload
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface WorkspaceInviteCreatedEventPayload {
|
||||
inviterUuid: string
|
||||
inviteeEmail: string
|
||||
inviteUuid: string
|
||||
workspaceUuid: string
|
||||
}
|
||||
@@ -100,6 +100,8 @@ export * from './Event/UserRolesChangedEvent'
|
||||
export * from './Event/UserRolesChangedEventPayload'
|
||||
export * from './Event/UserSignedInEvent'
|
||||
export * from './Event/UserSignedInEventPayload'
|
||||
export * from './Event/WorkspaceInviteCreatedEvent'
|
||||
export * from './Event/WorkspaceInviteCreatedEventPayload'
|
||||
|
||||
export * from './Handler/DomainEventHandlerInterface'
|
||||
export * from './Handler/DomainEventMessageHandlerInterface'
|
||||
|
||||
@@ -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.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
|
||||
|
||||
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.25...@standardnotes/event-store@1.4.0) (2022-10-10)
|
||||
|
||||
### Features
|
||||
|
||||
* add workspace invite created event ([db4c49c](https://github.com/standardnotes/server/commit/db4c49c57b81bfea6b8c6b8774c6a30e0561e154))
|
||||
|
||||
## [1.3.25](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.24...@standardnotes/event-store@1.3.25) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.3.25",
|
||||
"version": "1.4.4",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -86,6 +86,7 @@ export class ContainerConfigLoader {
|
||||
['SUBSCRIPTION_RATE_ADJUSTED', container.get(TYPES.EventHandler)],
|
||||
['REFUND_REQUESTED', container.get(TYPES.EventHandler)],
|
||||
['INVOICE_GENERATED', container.get(TYPES.EventHandler)],
|
||||
['WORKSPACE_INVITE_CREATED', container.get(TYPES.EventHandler)],
|
||||
])
|
||||
|
||||
container
|
||||
|
||||
@@ -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.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
|
||||
|
||||
## [1.6.12](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.11...@standardnotes/files-server@1.6.12) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.6.11](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.10...@standardnotes/files-server@1.6.11) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.6.11",
|
||||
"version": "1.6.16",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/predicates",
|
||||
"version": "1.4.7",
|
||||
"version": "1.4.10",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.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
|
||||
|
||||
## [1.10.40](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.39...@standardnotes/scheduler-server@1.10.40) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.10.39](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.38...@standardnotes/scheduler-server@1.10.39) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.10.39",
|
||||
"version": "1.10.44",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.4.5",
|
||||
"version": "1.4.8",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.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
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.1...@standardnotes/syncing-server@1.9.2) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.0...@standardnotes/syncing-server@1.9.1) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.6",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.12.0...@standardnotes/workspace-server@1.13.0) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/server/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
|
||||
|
||||
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.11.0...@standardnotes/workspace-server@1.12.0) (2022-10-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add initiating key share ([cea9021](https://github.com/standardnotes/server/commit/cea9021c164588969890370a2332f11749ac820e))
|
||||
|
||||
# [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
|
||||
|
||||
* **workspace:** add publishing workspace invite created ([6f9683c](https://github.com/standardnotes/server/commit/6f9683c41a1135489832d9a854a114c82825a647))
|
||||
|
||||
## [1.4.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.4.0...@standardnotes/workspace-server@1.4.1) (2022-10-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.3.0...@standardnotes/workspace-server@1.4.0) (2022-10-10)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add inviting to workspace ([e06cc3b](https://github.com/standardnotes/server/commit/e06cc3ba80fd3bbf8a5fb0e176bc76b4318a36e9))
|
||||
|
||||
# [1.3.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.2.3...@standardnotes/workspace-server@1.3.0) (2022-10-10)
|
||||
|
||||
### Features
|
||||
|
||||
* **workspace:** add creating root workspace upon user registration ([3f61d31](https://github.com/standardnotes/server/commit/3f61d3163ef91b3b94056208a41bb4858c0df259))
|
||||
|
||||
## [1.2.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.2.2...@standardnotes/workspace-server@1.2.3) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **workspace:** add optional parameters to creating workspace ([0ce4185](https://github.com/standardnotes/server/commit/0ce4185379d921cf69eb27c94d63933b8cabc2e7))
|
||||
|
||||
## [1.2.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.2.1...@standardnotes/workspace-server@1.2.2) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -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'
|
||||
|
||||
19
packages/workspace/migrations/1665390489236-optional-keys.ts
Normal file
19
packages/workspace/migrations/1665390489236-optional-keys.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class optionalKeys1665390489236 implements MigrationInterface {
|
||||
name = 'optionalKeys1665390489236'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` CHANGE `public_key` `public_key` varchar(255) NULL')
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `workspace_users` CHANGE `encrypted_private_key` `encrypted_private_key` varchar(255) NULL',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `workspace_users` CHANGE `encrypted_private_key` `encrypted_private_key` varchar(255) NOT NULL',
|
||||
)
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` CHANGE `public_key` `public_key` varchar(255) NOT NULL')
|
||||
}
|
||||
}
|
||||
27
packages/workspace/migrations/1665394559520-add-invites.ts
Normal file
27
packages/workspace/migrations/1665394559520-add-invites.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addInvites1665394559520 implements MigrationInterface {
|
||||
name = 'addInvites1665394559520'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `workspace_invites` (`uuid` varchar(36) NOT NULL, `inviter_uuid` varchar(36) NOT NULL, `invitee_email` varchar(255) NOT NULL, `status` varchar(64) NOT NULL, `accepting_user_uuid` varchar(36) NULL, `workspace_uuid` varchar(36) NOT NULL, `created_at` bigint NOT NULL, `updated_at` bigint NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
await queryRunner.query('ALTER TABLE `workspaces` ADD `created_at` bigint NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `workspaces` ADD `updated_at` bigint NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` ADD `created_at` bigint NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` ADD `updated_at` bigint NOT NULL')
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `workspace_invites` ADD CONSTRAINT `FK_782df40d03151dd3998acd0a6ba` FOREIGN KEY (`workspace_uuid`) REFERENCES `workspaces`(`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `workspace_invites` DROP FOREIGN KEY `FK_782df40d03151dd3998acd0a6ba`')
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` DROP COLUMN `updated_at`')
|
||||
await queryRunner.query('ALTER TABLE `workspace_users` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('ALTER TABLE `workspaces` DROP COLUMN `updated_at`')
|
||||
await queryRunner.query('ALTER TABLE `workspaces` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('DROP TABLE `workspace_invites`')
|
||||
}
|
||||
}
|
||||
@@ -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`')
|
||||
}
|
||||
}
|
||||
@@ -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`')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/workspace-server",
|
||||
"version": "1.2.2",
|
||||
"version": "1.13.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -25,11 +25,13 @@
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.3.0",
|
||||
"@standardnotes/api": "^1.11.0",
|
||||
"@standardnotes/api": "^1.16.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "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",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "^16.0.1",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
DomainEventMessageHandlerInterface,
|
||||
DomainEventSubscriberFactoryInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface, Timer } from '@standardnotes/time'
|
||||
import { Env } from './Env'
|
||||
import TYPES from './Types'
|
||||
import { AppDataSource } from './DataSource'
|
||||
@@ -30,6 +31,22 @@ import { Workspace } from '../Domain/Workspace/Workspace'
|
||||
import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser'
|
||||
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
|
||||
import { WorkspacesController } from '../Controller/WorkspacesController'
|
||||
import { UserRegisteredEventHandler } from '../Domain/Handler/UserRegisteredEventHandler'
|
||||
import { WorkspaceInviteRepositoryInterface } from '../Domain/Invite/WorkspaceInviteRepositoryInterface'
|
||||
import { MySQLWorkspaceInviteRepository } from '../Infra/MySQL/MySQLWorkspaceInviteRepository'
|
||||
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'
|
||||
import { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -95,6 +112,9 @@ export class ContainerConfigLoader {
|
||||
// Repositories
|
||||
container.bind<WorkspaceRepositoryInterface>(TYPES.WorkspaceRepository).to(MySQLWorkspaceRepository)
|
||||
container.bind<WorkspaceUserRepositoryInterface>(TYPES.WorkspaceUserRepository).to(MySQLWorkspaceUserRepository)
|
||||
container
|
||||
.bind<WorkspaceInviteRepositoryInterface>(TYPES.WorkspaceInviteRepository)
|
||||
.to(MySQLWorkspaceInviteRepository)
|
||||
// ORM
|
||||
container
|
||||
.bind<Repository<Workspace>>(TYPES.ORMWorkspaceRepository)
|
||||
@@ -102,6 +122,9 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<Repository<WorkspaceUser>>(TYPES.ORMWorkspaceUserRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(WorkspaceUser))
|
||||
container
|
||||
.bind<Repository<WorkspaceInvite>>(TYPES.ORMWorkspaceInviteRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(WorkspaceInvite))
|
||||
// Middleware
|
||||
container.bind<ApiGatewayAuthMiddleware>(TYPES.ApiGatewayAuthMiddleware).to(ApiGatewayAuthMiddleware)
|
||||
// env vars
|
||||
@@ -115,9 +138,22 @@ export class ContainerConfigLoader {
|
||||
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
|
||||
|
||||
// use cases
|
||||
container.bind(TYPES.CreateWorkspace).to(CreateWorkspace)
|
||||
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)
|
||||
container.bind<InitiateKeyShare>(TYPES.InitiateKeyShare).to(InitiateKeyShare)
|
||||
// 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())
|
||||
container
|
||||
.bind<TokenDecoderInterface<CrossServiceTokenData>>(TYPES.CrossServiceTokenDecoder)
|
||||
.toConstantValue(new TokenDecoder<CrossServiceTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
||||
@@ -134,7 +170,9 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
}
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([])
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||
])
|
||||
|
||||
if (env.get('SQS_QUEUE_URL', true)) {
|
||||
container
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DataSource, LoggerOptions } from 'typeorm'
|
||||
import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite'
|
||||
import { Workspace } from '../Domain/Workspace/Workspace'
|
||||
import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser'
|
||||
import { Env } from './Env'
|
||||
@@ -34,7 +35,7 @@ export const AppDataSource = new DataSource({
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
},
|
||||
entities: [Workspace, WorkspaceUser],
|
||||
entities: [Workspace, WorkspaceUser, WorkspaceInvite],
|
||||
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||
|
||||
@@ -8,9 +8,11 @@ const TYPES = {
|
||||
// Repositories
|
||||
WorkspaceRepository: Symbol.for('WorkspaceRepository'),
|
||||
WorkspaceUserRepository: Symbol.for('WorkspaceUserRepository'),
|
||||
WorkspaceInviteRepository: Symbol.for('WorkspaceInviteRepository'),
|
||||
// ORM
|
||||
ORMWorkspaceRepository: Symbol.for('ORMWorkspaceRepository'),
|
||||
ORMWorkspaceUserRepository: Symbol.for('ORMWorkspaceUserRepository'),
|
||||
ORMWorkspaceInviteRepository: Symbol.for('ORMWorkspaceInviteRepository'),
|
||||
// Middleware
|
||||
ApiGatewayAuthMiddleware: Symbol.for('ApiGatewayAuthMiddleware'),
|
||||
// env vars
|
||||
@@ -25,12 +27,23 @@ const TYPES = {
|
||||
VERSION: Symbol.for('VERSION'),
|
||||
// use cases
|
||||
CreateWorkspace: Symbol.for('CreateWorkspace'),
|
||||
InviteToWorkspace: Symbol.for('InviteToWorkspace'),
|
||||
AcceptInvitation: Symbol.for('AcceptInvitation'),
|
||||
ListWorkspaces: Symbol.for('ListWorkspaces'),
|
||||
ListWorkspaceUsers: Symbol.for('ListWorkspaceUsers'),
|
||||
InitiateKeyShare: Symbol.for('InitiateKeyShare'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
// Projection
|
||||
WorkspaceProjector: Symbol.for('WorkspaceProjector'),
|
||||
WorkspaceUserProjector: Symbol.for('WorkspaceUserProjector'),
|
||||
// Services
|
||||
Timer: Symbol.for('Timer'),
|
||||
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
|
||||
DomainEventFactory: Symbol.for('DomainEventFactory'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -1,17 +1,72 @@
|
||||
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 { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
|
||||
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 doInitiateKeyshare: InitiateKeyShare
|
||||
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,
|
||||
doInitiateKeyshare,
|
||||
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 })
|
||||
|
||||
doInitiateKeyshare = {} as jest.Mocked<InitiateKeyShare>
|
||||
doInitiateKeyshare.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 +76,7 @@ describe('WorkspacesController', () => {
|
||||
publicKey: 'buzz',
|
||||
workspaceName: 'A Team',
|
||||
ownerUuid: 'u-1-2-3',
|
||||
workspaceType: WorkspaceType.Private,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -30,4 +86,118 @@ 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,
|
||||
})
|
||||
})
|
||||
|
||||
it('should initiate keyshare', async () => {
|
||||
const result = await createController().initiateKeyshare({
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foo',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
performingUserUuid: 'p-1-2-3',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not initiate keyshare if it fails', async () => {
|
||||
doInitiateKeyshare.execute = jest.fn().mockReturnValue({ success: false })
|
||||
|
||||
const result = await createController().initiateKeyshare({
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foo',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
performingUserUuid: 'p-1-2-3',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
data: {
|
||||
error: {
|
||||
message: 'Could not initiate keyshare.',
|
||||
},
|
||||
},
|
||||
status: 400,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,16 +3,89 @@ import {
|
||||
HttpStatusCode,
|
||||
WorkspaceCreationRequestParams,
|
||||
WorkspaceCreationResponse,
|
||||
WorkspaceInvitationRequestParams,
|
||||
WorkspaceInvitationResponse,
|
||||
WorkspaceServerInterface,
|
||||
WorkspaceListRequestParams,
|
||||
WorkspaceListResponse,
|
||||
WorkspaceInvitationAcceptingRequestParams,
|
||||
WorkspaceInvitationAcceptingResponse,
|
||||
WorkspaceUserListRequestParams,
|
||||
WorkspaceKeyshareInitiatingRequestParams,
|
||||
WorkspaceKeyshareInitiatingResponse,
|
||||
} 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'
|
||||
import { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
|
||||
|
||||
@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.InitiateKeyShare) private doInitiateKeyshare: InitiateKeyShare,
|
||||
@inject(TYPES.WorkspaceProjector) private workspaceProjector: ProjectorInterface<Workspace, WorkspaceProjection>,
|
||||
@inject(TYPES.WorkspaceUserProjector)
|
||||
private workspaceUserProjector: ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>,
|
||||
) {}
|
||||
|
||||
async initiateKeyshare(
|
||||
params: WorkspaceKeyshareInitiatingRequestParams,
|
||||
): Promise<WorkspaceKeyshareInitiatingResponse> {
|
||||
const result = await this.doInitiateKeyshare.execute({
|
||||
userUuid: params.userUuid,
|
||||
workspaceUuid: params.workspaceUuid,
|
||||
encryptedWorkspaceKey: params.encryptedWorkspaceKey,
|
||||
performingUserUuid: params.performingUserUuid as Uuid,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: 'Could not initiate keyshare.',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 +102,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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { DomainEventFactory } from './DomainEventFactory'
|
||||
|
||||
describe('DomainEventFactory', () => {
|
||||
let timer: TimerInterface
|
||||
|
||||
const createFactory = () => new DomainEventFactory(timer)
|
||||
|
||||
beforeEach(() => {
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
||||
})
|
||||
|
||||
it('should create a WORKSPACE_INVITE_CREATED event', () => {
|
||||
expect(
|
||||
createFactory().createWorkspaceInviteCreatedEvent({
|
||||
inviterUuid: '1-2-3',
|
||||
inviteeEmail: 'test@test.te',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
}),
|
||||
).toEqual({
|
||||
createdAt: expect.any(Date),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: '1-2-3',
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: 'workspace',
|
||||
},
|
||||
payload: {
|
||||
inviterUuid: '1-2-3',
|
||||
inviteeEmail: 'test@test.te',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
},
|
||||
type: 'WORKSPACE_INVITE_CREATED',
|
||||
})
|
||||
})
|
||||
})
|
||||
32
packages/workspace/src/Domain/Event/DomainEventFactory.ts
Normal file
32
packages/workspace/src/Domain/Event/DomainEventFactory.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { DomainEventService, WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
|
||||
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||
|
||||
createWorkspaceInviteCreatedEvent(dto: {
|
||||
inviterUuid: string
|
||||
inviteeEmail: string
|
||||
inviteUuid: string
|
||||
workspaceUuid: string
|
||||
}): WorkspaceInviteCreatedEvent {
|
||||
return {
|
||||
type: 'WORKSPACE_INVITE_CREATED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.inviterUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.Workspace,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createWorkspaceInviteCreatedEvent(dto: {
|
||||
inviterUuid: string
|
||||
inviteeEmail: string
|
||||
inviteUuid: string
|
||||
workspaceUuid: string
|
||||
}): WorkspaceInviteCreatedEvent
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
|
||||
import { CreateWorkspace } from '../UseCase/CreateWorkspace/CreateWorkspace'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
|
||||
describe('UserRegisteredEventHandler', () => {
|
||||
let createWorkspace: CreateWorkspace
|
||||
let event: UserRegisteredEvent
|
||||
|
||||
const createHandler = () => new UserRegisteredEventHandler(createWorkspace)
|
||||
|
||||
beforeEach(() => {
|
||||
createWorkspace = {} as jest.Mocked<CreateWorkspace>
|
||||
createWorkspace.execute = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<UserRegisteredEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
protocolVersion: ProtocolVersion.V005,
|
||||
}
|
||||
})
|
||||
|
||||
it('should create a root workspace for newly registered user', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(createWorkspace.execute).toHaveBeenCalledWith({
|
||||
ownerUuid: '1-2-3',
|
||||
type: 'root',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not create a root workspace for newly registered user on legacy protocols', async () => {
|
||||
event.payload.protocolVersion = ProtocolVersion.V004
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(createWorkspace.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ProtocolVersion, WorkspaceType } from '@standardnotes/common'
|
||||
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { CreateWorkspace } from '../UseCase/CreateWorkspace/CreateWorkspace'
|
||||
|
||||
@injectable()
|
||||
export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(@inject(TYPES.CreateWorkspace) private createWorkspace: CreateWorkspace) {}
|
||||
|
||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||
if (event.payload.protocolVersion !== ProtocolVersion.V005) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.createWorkspace.execute({
|
||||
ownerUuid: event.payload.userUuid,
|
||||
type: WorkspaceType.Root,
|
||||
})
|
||||
}
|
||||
}
|
||||
76
packages/workspace/src/Domain/Invite/WorkspaceInvite.ts
Normal file
76
packages/workspace/src/Domain/Invite/WorkspaceInvite.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { WorkspaceAccessLevel } from '@standardnotes/common'
|
||||
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
import { Workspace } from '../Workspace/Workspace'
|
||||
import { WorkspaceInviteStatus } from './WorkspaceInviteStatus'
|
||||
|
||||
@Entity({ name: 'workspace_invites' })
|
||||
export class WorkspaceInvite {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'inviter_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare inviterUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'invitee_email',
|
||||
length: 255,
|
||||
})
|
||||
declare inviteeEmail: string
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
})
|
||||
declare status: WorkspaceInviteStatus
|
||||
|
||||
@Column({
|
||||
name: 'accepting_user_uuid',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
})
|
||||
declare acceptingUserUuid: string | null
|
||||
|
||||
@Column({
|
||||
name: 'workspace_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare workspaceUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'access_level',
|
||||
length: 64,
|
||||
})
|
||||
declare accessLevel: WorkspaceAccessLevel
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAt: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAt: number
|
||||
|
||||
@ManyToOne(
|
||||
/* istanbul ignore next */
|
||||
() => Workspace,
|
||||
/* istanbul ignore next */
|
||||
(workspace) => workspace.invites,
|
||||
/* istanbul ignore next */
|
||||
{ onDelete: 'CASCADE' },
|
||||
)
|
||||
@JoinColumn(
|
||||
/* istanbul ignore next */
|
||||
{ name: 'workspace_uuid' },
|
||||
)
|
||||
declare workspace: Promise<Workspace>
|
||||
}
|
||||
@@ -0,0 +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>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum WorkspaceInviteStatus {
|
||||
Created = 'created',
|
||||
Accepted = 'accepted',
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ProjectorInterface<T, E> {
|
||||
project(object: T): Promise<E>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Workspace } from '@standardnotes/models'
|
||||
|
||||
export type WorkspaceProjection = Workspace
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { WorkspaceUser } from '@standardnotes/models'
|
||||
|
||||
export type WorkspaceUserProjection = WorkspaceUser
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type AcceptInvitationDTO = {
|
||||
invitationUuid: Uuid
|
||||
acceptingUserUuid: Uuid
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type AcceptInvitationResponse = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -6,14 +6,19 @@ import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositor
|
||||
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
|
||||
|
||||
import { CreateWorkspace } from './CreateWorkspace'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
describe('CreateWorkspace', () => {
|
||||
let workspaceRepository: WorkspaceRepositoryInterface
|
||||
let workspaceUserRepository: WorkspaceUserRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createUseCase = () => new CreateWorkspace(workspaceRepository, workspaceUserRepository)
|
||||
const createUseCase = () => new CreateWorkspace(workspaceRepository, workspaceUserRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
|
||||
workspaceRepository = {} as jest.Mocked<WorkspaceRepositoryInterface>
|
||||
workspaceRepository.save = jest.fn().mockImplementation((workspace) => {
|
||||
return {
|
||||
@@ -39,6 +44,8 @@ describe('CreateWorkspace', () => {
|
||||
expect(workspaceRepository.save).toHaveBeenCalledWith({
|
||||
name: 'A Team',
|
||||
type: 'root',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
|
||||
accessLevel: 'owner',
|
||||
@@ -48,29 +55,29 @@ describe('CreateWorkspace', () => {
|
||||
status: 'active',
|
||||
userUuid: '1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a workspace without a name and owner association with it', async () => {
|
||||
it('should create a workspace without optional parameters', async () => {
|
||||
await createUseCase().execute({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
ownerUuid: '1-2-3',
|
||||
type: WorkspaceType.Private,
|
||||
})
|
||||
|
||||
expect(workspaceRepository.save).toHaveBeenCalledWith({
|
||||
type: 'private',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
|
||||
accessLevel: 'owner',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
encryptedPrivateKey: 'foo',
|
||||
publicKey: 'buzz',
|
||||
status: 'active',
|
||||
userUuid: '1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +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'
|
||||
@@ -17,6 +17,7 @@ export class CreateWorkspace implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface,
|
||||
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateWorkspaceDTO): Promise<CreateWorkspaceResponse> {
|
||||
@@ -25,17 +26,28 @@ export class CreateWorkspace implements UseCaseInterface {
|
||||
workspace.name = dto.name
|
||||
}
|
||||
workspace.type = dto.type
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
workspace.createdAt = timestamp
|
||||
workspace.updatedAt = timestamp
|
||||
|
||||
workspace = await this.workspaceRepository.save(workspace)
|
||||
|
||||
const ownerAssociation = new WorkspaceUser()
|
||||
ownerAssociation.accessLevel = WorkspaceAccessLevel.Owner
|
||||
ownerAssociation.encryptedWorkspaceKey = dto.encryptedWorkspaceKey
|
||||
ownerAssociation.encryptedPrivateKey = dto.encryptedPrivateKey
|
||||
ownerAssociation.publicKey = dto.publicKey
|
||||
if (dto.encryptedWorkspaceKey !== undefined) {
|
||||
ownerAssociation.encryptedWorkspaceKey = dto.encryptedWorkspaceKey
|
||||
}
|
||||
if (dto.encryptedPrivateKey !== undefined) {
|
||||
ownerAssociation.encryptedPrivateKey = dto.encryptedPrivateKey
|
||||
}
|
||||
if (dto.publicKey !== undefined) {
|
||||
ownerAssociation.publicKey = dto.publicKey
|
||||
}
|
||||
ownerAssociation.status = WorkspaceUserStatus.Active
|
||||
ownerAssociation.userUuid = dto.ownerUuid
|
||||
ownerAssociation.workspaceUuid = workspace.uuid
|
||||
ownerAssociation.createdAt = timestamp
|
||||
ownerAssociation.updatedAt = timestamp
|
||||
|
||||
await this.workspaceUserRepository.save(ownerAssociation)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
export type CreateWorkspaceDTO = {
|
||||
ownerUuid: Uuid
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
type: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
|
||||
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
|
||||
|
||||
import { InitiateKeyShare } from './InitiateKeyShare'
|
||||
import { WorkspaceAccessLevel } from '@standardnotes/common'
|
||||
|
||||
describe('InitiateKeyShare', () => {
|
||||
let workspaceUserRepository: WorkspaceUserRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let workspaceUser: WorkspaceUser
|
||||
let workspaceOwner: WorkspaceUser
|
||||
|
||||
const createUseCase = () => new InitiateKeyShare(workspaceUserRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
workspaceOwner = {
|
||||
accessLevel: WorkspaceAccessLevel.Owner,
|
||||
} as jest.Mocked<WorkspaceUser>
|
||||
workspaceUser = {} as jest.Mocked<WorkspaceUser>
|
||||
|
||||
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
|
||||
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(workspaceOwner)
|
||||
.mockReturnValueOnce(workspaceUser)
|
||||
workspaceUserRepository.save = jest.fn().mockImplementation((user: WorkspaceUser) => user)
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
})
|
||||
|
||||
it('should update the workspace user with a workspace key and mark as active', async () => {
|
||||
await createUseCase().execute({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
performingUserUuid: 'o-1-2-3',
|
||||
})
|
||||
|
||||
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
status: 'active',
|
||||
updatedAt: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not initiate key share if workspace is not found', async () => {
|
||||
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(workspaceOwner)
|
||||
.mockReturnValueOnce(null)
|
||||
|
||||
await createUseCase().execute({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
performingUserUuid: 'o-1-2-3',
|
||||
})
|
||||
|
||||
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not initiate key share if workspace performing user is not the owner or admin', async () => {
|
||||
workspaceOwner.accessLevel = WorkspaceAccessLevel.ReadOnly
|
||||
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(workspaceOwner)
|
||||
.mockReturnValueOnce(workspaceUser)
|
||||
|
||||
await createUseCase().execute({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
performingUserUuid: 'o-1-2-3',
|
||||
})
|
||||
|
||||
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not initiate key share if workspace performing user is found in workspace', async () => {
|
||||
workspaceOwner.accessLevel = WorkspaceAccessLevel.ReadOnly
|
||||
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce(workspaceUser)
|
||||
|
||||
await createUseCase().execute({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
performingUserUuid: 'o-1-2-3',
|
||||
})
|
||||
|
||||
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { InitiateKeyShareDTO } from './InitiateKeyShareDTO'
|
||||
import { InitiateKeyShareResponse } from './InitiateKeyShareResponse'
|
||||
|
||||
@injectable()
|
||||
export class InitiateKeyShare implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: InitiateKeyShareDTO): Promise<InitiateKeyShareResponse> {
|
||||
const workspaceOwner = await this.workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid({
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
userUuid: dto.performingUserUuid,
|
||||
})
|
||||
if (
|
||||
workspaceOwner === null ||
|
||||
![WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceOwner.accessLevel)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceUser = await this.workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid({
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
if (workspaceUser === null) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
workspaceUser.encryptedWorkspaceKey = dto.encryptedWorkspaceKey
|
||||
workspaceUser.status = WorkspaceUserStatus.Active
|
||||
workspaceUser.updatedAt = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
await this.workspaceUserRepository.save(workspaceUser)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type InitiateKeyShareDTO = {
|
||||
workspaceUuid: Uuid
|
||||
userUuid: Uuid
|
||||
performingUserUuid: Uuid
|
||||
encryptedWorkspaceKey: string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type InitiateKeyShareResponse = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'reflect-metadata'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
|
||||
|
||||
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
|
||||
let timer: TimerInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new InviteToWorkspace(workspaceInviteRepository, timer, domainEventFactory, domainEventPublisher)
|
||||
|
||||
beforeEach(() => {
|
||||
workspaceInviteRepository = {} as jest.Mocked<WorkspaceInviteRepositoryInterface>
|
||||
workspaceInviteRepository.save = jest.fn().mockImplementation((invite) => {
|
||||
return {
|
||||
...invite,
|
||||
uuid: 'i-1-2-3',
|
||||
}
|
||||
})
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createWorkspaceInviteCreatedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<WorkspaceInviteCreatedEvent>)
|
||||
})
|
||||
|
||||
it('should create an invite', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
inviteeEmail: 'test@test.te',
|
||||
inviterUuid: 'u-1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
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',
|
||||
status: 'created',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
|
||||
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
|
||||
import { WorkspaceInviteStatus } from '../../Invite/WorkspaceInviteStatus'
|
||||
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { InviteToWorkspaceDTO } from './InviteToWorkspaceDTO'
|
||||
import { InviteToWorkspaceResponse } from './InviteToWorkspaceResponse'
|
||||
|
||||
@injectable()
|
||||
export class InviteToWorkspace implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: InviteToWorkspaceDTO): Promise<InviteToWorkspaceResponse> {
|
||||
let invite = new WorkspaceInvite()
|
||||
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()
|
||||
invite.createdAt = timestamp
|
||||
invite.updatedAt = timestamp
|
||||
|
||||
invite = await this.workspaceInviteRepository.save(invite)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createWorkspaceInviteCreatedEvent({
|
||||
inviterUuid: dto.inviterUuid,
|
||||
inviteeEmail: dto.inviteeEmail,
|
||||
workspaceUuid: dto.workspaceUuid,
|
||||
inviteUuid: invite.uuid,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
invite,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid, WorkspaceAccessLevel } from '@standardnotes/common'
|
||||
|
||||
export type InviteToWorkspaceDTO = {
|
||||
workspaceUuid: Uuid
|
||||
inviterUuid: Uuid
|
||||
inviteeEmail: string
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
|
||||
|
||||
export type InviteToWorkspaceResponse = {
|
||||
invite: WorkspaceInvite
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type ListWorkspaceUsersDTO = {
|
||||
workspaceUuid: Uuid
|
||||
userUuid: Uuid
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
|
||||
|
||||
export type ListWorkspaceUsersResponse = {
|
||||
workspaceUsers: WorkspaceUser[]
|
||||
userIsOwnerOrAdmin: boolean
|
||||
}
|
||||
@@ -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],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type ListWorkspacesDTO = {
|
||||
userUuid: Uuid
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Workspace } from '../../Workspace/Workspace'
|
||||
|
||||
export type ListWorkspacesResponse = {
|
||||
ownedWorkspaces: Array<Workspace>
|
||||
joinedWorkspaces: Array<Workspace>
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { WorkspaceInvite } from '../Invite/WorkspaceInvite'
|
||||
|
||||
@Entity({ name: 'workspaces' })
|
||||
export class Workspace {
|
||||
@@ -23,4 +24,24 @@ export class Workspace {
|
||||
default: 0,
|
||||
})
|
||||
declare keyRotationIndex: number
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAt: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAt: number
|
||||
|
||||
@OneToMany(
|
||||
/* istanbul ignore next */
|
||||
() => WorkspaceInvite,
|
||||
/* istanbul ignore next */
|
||||
(workspaceInvite) => workspaceInvite.workspace,
|
||||
)
|
||||
declare invites: Promise<WorkspaceInvite[]>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -38,15 +45,17 @@ export class WorkspaceUser {
|
||||
name: 'public_key',
|
||||
length: 255,
|
||||
type: 'varchar',
|
||||
nullable: true,
|
||||
})
|
||||
declare publicKey: string
|
||||
declare publicKey: string | null
|
||||
|
||||
@Column({
|
||||
name: 'encrypted_private_key',
|
||||
length: 255,
|
||||
type: 'varchar',
|
||||
nullable: true,
|
||||
})
|
||||
declare encryptedPrivateKey: string
|
||||
declare encryptedPrivateKey: string | null
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
@@ -59,4 +68,16 @@ export class WorkspaceUser {
|
||||
default: 0,
|
||||
})
|
||||
declare keyRotationIndex: number
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAt: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAt: number
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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[]>
|
||||
findOneByUserUuidAndWorkspaceUuid(dto: { workspaceUuid: Uuid; userUuid: Uuid }): Promise<WorkspaceUser | null>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user