mirror of
https://github.com/standardnotes/server
synced 2026-01-27 08:01:11 -05:00
Compare commits
35 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 | ||
|
|
1f7989dbed | ||
|
|
0ea88ad202 | ||
|
|
2d41742c34 | ||
|
|
447d600dbe | ||
|
|
3f6db48f83 | ||
|
|
156ab65272 |
105
.pnp.cjs
generated
105
.pnp.cjs
generated
@@ -2521,16 +2521,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/api", [\
|
||||
["npm:1.9.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.9.0-507434ff00-cc3feac393.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.9.0"],\
|
||||
["@standardnotes/api", "npm:1.16.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/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"],\
|
||||
["@standardnotes/utils", "npm:1.10.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -2600,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"],\
|
||||
@@ -2725,15 +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"],\
|
||||
["@standardnotes/models", "npm:1.27.0"],\
|
||||
["@standardnotes/responses", "npm:1.11.0"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||
["@standardnotes/utils", "npm:1.10.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -2790,6 +2790,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["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.53.0"],\
|
||||
["@standardnotes/auth", "npm:3.19.4"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/files-server", [\
|
||||
@@ -2845,14 +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.27.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.27.0-831bd645c6-263fd9e923.zip/node_modules/@standardnotes/models/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/models", "npm:1.27.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@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"]\
|
||||
],\
|
||||
@@ -2888,12 +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/features", "npm:1.53.0"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
@@ -3004,10 +3028,10 @@ 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/",\
|
||||
["npm:1.13.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.0-18cb5f8eb9-e58258f525.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/sncrypto-common", "npm:1.12.0"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -3114,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": [\
|
||||
@@ -3123,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", [\
|
||||
@@ -3143,10 +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.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.
BIN
.yarn/cache/@standardnotes-features-npm-1.53.0-8ea4a2d559-a856e815a3.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-features-npm-1.53.0-8ea4a2d559-a856e815a3.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@standardnotes-models-npm-1.27.0-831bd645c6-263fd9e923.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-models-npm-1.27.0-831bd645c6-263fd9e923.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@ PORT=3000
|
||||
|
||||
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
|
||||
AUTH_SERVER_URL=http://auth:3000
|
||||
WORKSPACE_SERVER_URL=http://workspace:3000
|
||||
PAYMENTS_SERVER_URL=http://payments:3000
|
||||
FILES_SERVER_URL=http://files:3000
|
||||
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
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
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.26.2...@standardnotes/api-gateway@1.27.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* add workspaces creation ([156ab65](https://github.com/standardnotes/api-gateway/commit/156ab6527265351b13797394cbd34da037ab5a38))
|
||||
|
||||
## [1.26.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.26.1...@standardnotes/api-gateway@1.26.2) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -19,6 +19,8 @@ import '../src/Controller/v1/TokensController'
|
||||
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.26.2",
|
||||
"version": "1.30.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -75,6 +75,7 @@ export class ContainerConfigLoader {
|
||||
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
||||
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
||||
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL'))
|
||||
container
|
||||
.bind(TYPES.HTTP_CALL_TIMEOUT)
|
||||
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
|
||||
|
||||
@@ -8,6 +8,7 @@ const TYPES = {
|
||||
AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
|
||||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
||||
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
||||
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
|
||||
VERSION: Symbol.for('VERSION'),
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/workspaces', TYPES.AuthMiddleware)
|
||||
export class WorkspacesController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
@inject(TYPES.SYNCING_SERVER_JS_URL) private syncingServerJsUrl: string,
|
||||
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
|
||||
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
|
||||
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
||||
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@@ -48,6 +49,15 @@ export class HttpService implements HttpServiceInterface {
|
||||
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWorkspaceServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callPaymentsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
||||
@@ -31,4 +31,10 @@ export interface HttpServiceInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callWorkspaceServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
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.40.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.39.2...@standardnotes/auth-server@1.40.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.40.0",
|
||||
"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,30 @@
|
||||
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
|
||||
|
||||
* **workspace:** extract workspace type to common types ([0ea88ad](https://github.com/standardnotes/server/commit/0ea88ad202d54de79d1433183c1706823639da93))
|
||||
|
||||
# [1.36.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.35.1...@standardnotes/common@1.36.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.36.0",
|
||||
"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,3 +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,30 @@
|
||||
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.8.19](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.18...@standardnotes/domain-events-infra@1.8.19) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.8.19",
|
||||
"version": "1.8.25",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
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
|
||||
|
||||
# [2.64.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.63.1...@standardnotes/domain-events@2.64.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.64.0",
|
||||
"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,32 @@
|
||||
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.3.24](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.23...@standardnotes/event-store@1.3.24) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.3.24",
|
||||
"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,30 @@
|
||||
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.10](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.9...@standardnotes/files-server@1.6.10) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.6.10",
|
||||
"version": "1.6.16",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
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.4.6](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.5...@standardnotes/predicates@1.4.6) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/predicates
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/predicates",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.10",
|
||||
"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.
|
||||
|
||||
## [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.10.38](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.37...@standardnotes/scheduler-server@1.10.38) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.10.38",
|
||||
"version": "1.10.44",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
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.4.4](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.3...@standardnotes/security@1.4.4) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/security
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.8",
|
||||
"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.
|
||||
|
||||
## [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.9.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.21...@standardnotes/syncing-server@1.9.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.6",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,104 @@
|
||||
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
|
||||
|
||||
* **workspace:** extract workspace type to common types ([0ea88ad](https://github.com/standardnotes/server/commit/0ea88ad202d54de79d1433183c1706823639da93))
|
||||
|
||||
## [1.2.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.2.0...@standardnotes/workspace-server@1.2.1) (2022-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **workspace:** rename private key to encrypted private key ([447d600](https://github.com/standardnotes/server/commit/447d600dbe0ee101a7579409adc9da6cd3e309de))
|
||||
|
||||
# [1.2.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.1.2...@standardnotes/workspace-server@1.2.0) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* add workspaces creation ([156ab65](https://github.com/standardnotes/server/commit/156ab6527265351b13797394cbd34da037ab5a38))
|
||||
|
||||
## [1.1.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.1.1...@standardnotes/workspace-server@1.1.2) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
@@ -5,6 +5,8 @@ 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'
|
||||
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class renamePrivateKey1665389240189 implements MigrationInterface {
|
||||
name = 'renamePrivateKey1665389240189'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `workspace_users` CHANGE `private_key` `encrypted_private_key` varchar(255) NOT NULL',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `workspace_users` CHANGE `encrypted_private_key` `private_key` varchar(255) NOT NULL',
|
||||
)
|
||||
}
|
||||
}
|
||||
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.1.2",
|
||||
"version": "1.13.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -25,10 +25,13 @@
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.3.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'
|
||||
@@ -21,6 +22,31 @@ import {
|
||||
} from '@standardnotes/domain-events-infra'
|
||||
import { ApiGatewayAuthMiddleware } from '../Controller/ApiGatewayAuthMiddleware'
|
||||
import { CrossServiceTokenData, TokenDecoder, TokenDecoderInterface } from '@standardnotes/security'
|
||||
import { WorkspaceRepositoryInterface } from '../Domain/Workspace/WorkspaceRepositoryInterface'
|
||||
import { MySQLWorkspaceRepository } from '../Infra/MySQL/MySQLWorkspaceRepository'
|
||||
import { WorkspaceUserRepositoryInterface } from '../Domain/Workspace/WorkspaceUserRepositoryInterface'
|
||||
import { MySQLWorkspaceUserRepository } from '../Infra/MySQL/MySQLWorkspaceUserRepository'
|
||||
import { Repository } from 'typeorm'
|
||||
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')
|
||||
@@ -82,8 +108,23 @@ export class ContainerConfigLoader {
|
||||
}
|
||||
|
||||
// Controller
|
||||
container.bind<WorkspacesController>(TYPES.WorkspacesController).to(WorkspacesController)
|
||||
// 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)
|
||||
.toConstantValue(AppDataSource.getRepository(Workspace))
|
||||
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
|
||||
@@ -97,8 +138,22 @@ export class ContainerConfigLoader {
|
||||
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
|
||||
|
||||
// use cases
|
||||
container.bind<CreateWorkspace>(TYPES.CreateWorkspace).to(CreateWorkspace)
|
||||
container.bind<InviteToWorkspace>(TYPES.InviteToWorkspace).to(InviteToWorkspace)
|
||||
container.bind<AcceptInvitation>(TYPES.AcceptInvitation).to(AcceptInvitation)
|
||||
container.bind<ListWorkspaces>(TYPES.ListWorkspaces).to(ListWorkspaces)
|
||||
container.bind<ListWorkspaceUsers>(TYPES.ListWorkspaceUsers).to(ListWorkspaceUsers)
|
||||
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)))
|
||||
@@ -115,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'),
|
||||
|
||||
@@ -4,8 +4,15 @@ const TYPES = {
|
||||
SNS: Symbol.for('SNS'),
|
||||
SQS: Symbol.for('SQS'),
|
||||
// Controller
|
||||
WorkspacesController: Symbol.for('WorkspacesController'),
|
||||
// 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
|
||||
@@ -19,12 +26,24 @@ const TYPES = {
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
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
|
||||
|
||||
203
packages/workspace/src/Controller/WorkspacesController.spec.ts
Normal file
203
packages/workspace/src/Controller/WorkspacesController.spec.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
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,
|
||||
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 () => {
|
||||
const result = await createController().createWorkspace({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
workspaceName: 'A Team',
|
||||
ownerUuid: 'u-1-2-3',
|
||||
workspaceType: WorkspaceType.Private,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
data: {
|
||||
uuid: 'w-1-2-3',
|
||||
},
|
||||
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,
|
||||
})
|
||||
})
|
||||
})
|
||||
170
packages/workspace/src/Controller/WorkspacesController.ts
Normal file
170
packages/workspace/src/Controller/WorkspacesController.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
WorkspaceCreationRequestParams,
|
||||
WorkspaceCreationResponse,
|
||||
WorkspaceInvitationRequestParams,
|
||||
WorkspaceInvitationResponse,
|
||||
WorkspaceServerInterface,
|
||||
WorkspaceListRequestParams,
|
||||
WorkspaceListResponse,
|
||||
WorkspaceInvitationAcceptingRequestParams,
|
||||
WorkspaceInvitationAcceptingResponse,
|
||||
WorkspaceUserListRequestParams,
|
||||
WorkspaceKeyshareInitiatingRequestParams,
|
||||
WorkspaceKeyshareInitiatingResponse,
|
||||
} from '@standardnotes/api'
|
||||
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,
|
||||
@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({
|
||||
encryptedPrivateKey: params.encryptedPrivateKey,
|
||||
encryptedWorkspaceKey: params.encryptedWorkspaceKey,
|
||||
publicKey: params.publicKey,
|
||||
name: params.workspaceName,
|
||||
type: WorkspaceType.Root,
|
||||
ownerUuid: params.ownerUuid as string,
|
||||
})
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
|
||||
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, 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 {
|
||||
...workspace,
|
||||
uuid: 'w-1-2-3',
|
||||
}
|
||||
})
|
||||
|
||||
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
|
||||
workspaceUserRepository.save = jest.fn()
|
||||
})
|
||||
|
||||
it('should create a workspace and owner association with it', async () => {
|
||||
await createUseCase().execute({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
name: 'A Team',
|
||||
ownerUuid: '1-2-3',
|
||||
type: WorkspaceType.Root,
|
||||
})
|
||||
|
||||
expect(workspaceRepository.save).toHaveBeenCalledWith({
|
||||
name: 'A Team',
|
||||
type: 'root',
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a workspace without optional parameters', async () => {
|
||||
await createUseCase().execute({
|
||||
ownerUuid: '1-2-3',
|
||||
type: WorkspaceType.Private,
|
||||
})
|
||||
|
||||
expect(workspaceRepository.save).toHaveBeenCalledWith({
|
||||
type: 'private',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
|
||||
accessLevel: 'owner',
|
||||
status: 'active',
|
||||
userUuid: '1-2-3',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
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 { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
|
||||
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
|
||||
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
|
||||
import { CreateWorkspaceDTO } from './CreateWorkspaceDTO'
|
||||
import { CreateWorkspaceResponse } from './CreateWorkspaceResponse'
|
||||
|
||||
@injectable()
|
||||
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> {
|
||||
let workspace = new Workspace()
|
||||
if (dto.name !== undefined) {
|
||||
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
|
||||
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)
|
||||
|
||||
return { workspace }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Uuid, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
export type CreateWorkspaceDTO = {
|
||||
ownerUuid: Uuid
|
||||
type: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
name?: string
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Workspace } from '../../Workspace/Workspace'
|
||||
|
||||
export type CreateWorkspaceResponse = {
|
||||
workspace: Workspace
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user