Compare commits

...

22 Commits

Author SHA1 Message Date
standardci
3dc6babfca chore(release): publish new version
- @standardnotes/workspace-server@1.7.0
2022-10-11 08:02:58 +00:00
Karol Sójko
ace2b6936a feat(workspace): accepting invitation 2022-10-11 10:01:13 +02:00
standardci
712e874bfe chore(release): publish new version
- @standardnotes/api-gateway@1.28.0
 - @standardnotes/auth-server@1.41.0
 - @standardnotes/workspace-server@1.6.0
2022-10-11 07:44:12 +00:00
Karol Sójko
266adda45b feat(workspace): add invite to workspace endpoints 2022-10-11 09:41:45 +02:00
standardci
f5ebe4a69e chore(release): publish new version
- @standardnotes/api-gateway@1.27.4
 - @standardnotes/auth-server@1.40.4
 - @standardnotes/common@1.37.0
 - @standardnotes/domain-events-infra@1.8.23
 - @standardnotes/domain-events@2.66.1
 - @standardnotes/event-store@1.4.2
 - @standardnotes/files-server@1.6.14
 - @standardnotes/predicates@1.4.8
 - @standardnotes/scheduler-server@1.10.42
 - @standardnotes/security@1.4.6
 - @standardnotes/syncing-server@1.9.4
 - @standardnotes/workspace-server@1.5.1
2022-10-10 10:34:40 +00:00
Karol Sójko
15d960d47b feat(common): add WORKSPACE_INVITE_CREATED email message identifier 2022-10-10 12:32:35 +02:00
standardci
f700b04b8f chore(release): publish new version
- @standardnotes/api-gateway@1.27.3
 - @standardnotes/auth-server@1.40.3
 - @standardnotes/domain-events-infra@1.8.22
 - @standardnotes/domain-events@2.66.0
 - @standardnotes/event-store@1.4.1
 - @standardnotes/files-server@1.6.13
 - @standardnotes/scheduler-server@1.10.41
 - @standardnotes/syncing-server@1.9.3
 - @standardnotes/workspace-server@1.5.0
2022-10-10 10:23:57 +00:00
Karol Sójko
6f9683c41a feat(workspace): add publishing workspace invite created 2022-10-10 12:22:19 +02:00
standardci
0ad605c906 chore(release): publish new version
- @standardnotes/api-gateway@1.27.2
 - @standardnotes/auth-server@1.40.2
 - @standardnotes/domain-events-infra@1.8.21
 - @standardnotes/domain-events@2.65.0
 - @standardnotes/event-store@1.4.0
 - @standardnotes/files-server@1.6.12
 - @standardnotes/scheduler-server@1.10.40
 - @standardnotes/syncing-server@1.9.2
 - @standardnotes/workspace-server@1.4.1
2022-10-10 10:09:16 +00:00
Karol Sójko
db4c49c57b feat: add workspace invite created event 2022-10-10 12:07:23 +02:00
standardci
b5c72dda8f chore(release): publish new version
- @standardnotes/workspace-server@1.4.0
2022-10-10 09:41:14 +00:00
Karol Sójko
e06cc3ba80 feat(workspace): add inviting to workspace 2022-10-10 11:38:59 +02:00
standardci
8a72a1a559 chore(release): publish new version
- @standardnotes/workspace-server@1.3.0
2022-10-10 08:52:50 +00:00
Karol Sójko
3f61d3163e feat(workspace): add creating root workspace upon user registration 2022-10-10 10:51:16 +02:00
standardci
34b3c7ce16 chore(release): publish new version
- @standardnotes/workspace-server@1.2.3
2022-10-10 08:33:46 +00:00
Karol Sójko
0ce4185379 fix(workspace): add optional parameters to creating workspace 2022-10-10 10:31:45 +02:00
standardci
1f7989dbed chore(release): publish new version
- @standardnotes/api-gateway@1.27.1
 - @standardnotes/auth-server@1.40.1
 - @standardnotes/common@1.36.1
 - @standardnotes/domain-events-infra@1.8.20
 - @standardnotes/domain-events@2.64.1
 - @standardnotes/event-store@1.3.25
 - @standardnotes/files-server@1.6.11
 - @standardnotes/predicates@1.4.7
 - @standardnotes/scheduler-server@1.10.39
 - @standardnotes/security@1.4.5
 - @standardnotes/syncing-server@1.9.1
 - @standardnotes/workspace-server@1.2.2
2022-10-10 08:26:57 +00:00
Karol Sójko
0ea88ad202 fix(workspace): extract workspace type to common types 2022-10-10 10:25:00 +02:00
standardci
2d41742c34 chore(release): publish new version
- @standardnotes/workspace-server@1.2.1
2022-10-10 08:11:37 +00:00
Karol Sójko
447d600dbe fix(workspace): rename private key to encrypted private key 2022-10-10 10:09:49 +02:00
standardci
3f6db48f83 chore(release): publish new version
- @standardnotes/api-gateway@1.27.0
 - @standardnotes/workspace-server@1.2.0
2022-10-07 11:06:37 +00:00
Karol Sójko
156ab65272 feat: add workspaces creation 2022-10-07 13:05:00 +02:00
89 changed files with 1564 additions and 106 deletions

75
.pnp.cjs generated
View File

@@ -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.12.1", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.12.1-6b8bfe4ccf-8623fc82de.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.9.0"],\
["@standardnotes/api", "npm:1.12.1"],\
["@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.16.2"],\
["@standardnotes/models", "npm:1.24.2"],\
["@standardnotes/responses", "npm:1.10.6"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/utils", "npm:1.9.1"],\
["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.12.1"],\
["@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.16.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.16.2-9e53125abe-50efc1b201.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.15.9"],\
["@standardnotes/encryption", "npm:1.16.2"],\
["@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.24.2"],\
["@standardnotes/responses", "npm:1.10.6"],\
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
["@standardnotes/utils", "npm:1.9.1"],\
["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.52.4", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.4-05c59084e4-aea7b48627.zip/node_modules/@standardnotes/features/",\
"packageDependencies": [\
["@standardnotes/features", "npm:1.52.4"],\
["@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,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/models", [\
["npm:1.22.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.22.0-2cc72f987b-9928246368.zip/node_modules/@standardnotes/models/",\
["npm:1.24.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.24.2-8c2c157efa-17b3cfba39.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.22.0"],\
["@standardnotes/models", "npm:1.24.2"],\
["@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.52.4"],\
["@standardnotes/responses", "npm:1.10.6"],\
["@standardnotes/utils", "npm:1.9.1"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
@@ -2888,12 +2899,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/responses", [\
["npm:1.10.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.3-7cdb15f83a-4a1e31eb89.zip/node_modules/@standardnotes/responses/",\
["npm:1.10.6", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.6-f636794f47-0583e2cb77.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.10.3"],\
["@standardnotes/responses", "npm:1.10.6"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.1"],\
["@standardnotes/features", "npm:1.52.4"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
@@ -3004,10 +3015,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"\
@@ -3124,10 +3135,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["npm:1.9.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.0-da939553f6-4591aff48d.zip/node_modules/@standardnotes/utils/",\
["npm:1.9.1", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.1-e48d87ffc7-f775bb3744.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
["@standardnotes/utils", "npm:1.9.0"],\
["@standardnotes/utils", "npm:1.9.1"],\
["@standardnotes/common", "workspace:packages/common"],\
["dompurify", "npm:2.4.0"],\
["lodash", "npm:4.17.21"],\
@@ -3143,10 +3154,12 @@ 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.12.1"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@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"],\

View File

@@ -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

View File

@@ -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.
# [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

View File

@@ -19,6 +19,7 @@ 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/v2/PaymentsControllerV2'
import '../src/Controller/v2/ActionsControllerV2'

View File

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

View File

@@ -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)

View File

@@ -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'),

View File

@@ -0,0 +1,28 @@
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/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)
}
@httpPost('/:workspaceUuid/invites')
async invite(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(
request,
response,
`workspaces/${request.params.workspaceUuid}/invites`,
request.body,
)
}
}

View File

@@ -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,

View File

@@ -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>
}

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.40.0",
"version": "1.41.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.12.1",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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

View File

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

View File

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

View File

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

View File

@@ -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.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

View File

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

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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

View File

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

View File

@@ -8,4 +8,5 @@ export enum DomainEventService {
ApiGateway = 'api-gateway',
Files = 'files',
Scheduler = 'scheduler',
Workspace = 'workspace',
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { WorkspaceInviteCreatedEventPayload } from './WorkspaceInviteCreatedEventPayload'
export interface WorkspaceInviteCreatedEvent extends DomainEventInterface {
type: 'WORKSPACE_INVITE_CREATED'
payload: WorkspaceInviteCreatedEventPayload
}

View File

@@ -0,0 +1,6 @@
export interface WorkspaceInviteCreatedEventPayload {
inviterUuid: string
inviteeEmail: string
inviteUuid: string
workspaceUuid: string
}

View File

@@ -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'

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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

View File

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

View File

@@ -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

View File

@@ -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.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

View File

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

View File

@@ -3,6 +3,14 @@
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/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

View File

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

View File

@@ -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.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

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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

View File

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

View File

@@ -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.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

View File

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

View File

@@ -3,6 +3,68 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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

View File

@@ -5,6 +5,7 @@ import 'newrelic'
import * as Sentry from '@sentry/node'
import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController'
import * as cors from 'cors'
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'

View File

@@ -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',
)
}
}

View 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')
}
}

View 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`')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.1.2",
"version": "1.7.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -25,10 +25,12 @@
"dependencies": {
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/api": "^1.12.1",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/domain-events": "workspace:^",
"@standardnotes/domain-events-infra": "workspace:^",
"@standardnotes/security": "workspace:*",
"@standardnotes/time": "workspace:^",
"aws-sdk": "^2.1159.0",
"cors": "2.8.5",
"dotenv": "^16.0.1",

View File

@@ -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,22 @@ 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'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -82,8 +99,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 +129,13 @@ 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)
// Handlers
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
// 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 +152,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

View File

@@ -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'),

View File

@@ -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,17 @@ const TYPES = {
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
VERSION: Symbol.for('VERSION'),
// use cases
CreateWorkspace: Symbol.for('CreateWorkspace'),
InviteToWorkspace: Symbol.for('InviteToWorkspace'),
// Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
// 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

View File

@@ -0,0 +1,54 @@
import { WorkspaceType } from '@standardnotes/common'
import 'reflect-metadata'
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
import { WorkspacesController } from './WorkspacesController'
describe('WorkspacesController', () => {
let doCreateWorkspace: CreateWorkspace
let doInviteToWorkspace: InviteToWorkspace
const createController = () => new WorkspacesController(doCreateWorkspace, doInviteToWorkspace)
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' } })
})
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',
})
expect(result).toEqual({
data: {
uuid: 'i-1-2-3',
},
status: 200,
})
})
})

View File

@@ -0,0 +1,51 @@
import { inject, injectable } from 'inversify'
import {
HttpStatusCode,
WorkspaceCreationRequestParams,
WorkspaceCreationResponse,
WorkspaceInvitationRequestParams,
WorkspaceInvitationResponse,
WorkspaceServerInterface,
} from '@standardnotes/api'
import { Uuid, WorkspaceType } from '@standardnotes/common'
import TYPES from '../Bootstrap/Types'
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
@injectable()
export class WorkspacesController implements WorkspaceServerInterface {
constructor(
@inject(TYPES.CreateWorkspace) private doCreateWorkspace: CreateWorkspace,
@inject(TYPES.InviteToWorkspace) private doInviteToWorkspace: InviteToWorkspace,
) {}
async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse> {
const { invite } = await this.doInviteToWorkspace.execute({
inviteeEmail: params.inviteeEmail,
workspaceUuid: params.workspaceUuid,
inviterUuid: params.inviterUuid as Uuid,
})
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 },
}
}
}

View File

@@ -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',
})
})
})

View 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,
}
}
}

View File

@@ -0,0 +1,10 @@
import { WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createWorkspaceInviteCreatedEvent(dto: {
inviterUuid: string
inviteeEmail: string
inviteUuid: string
workspaceUuid: string
}): WorkspaceInviteCreatedEvent
}

View File

@@ -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()
})
})

View File

@@ -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,
})
}
}

View File

@@ -0,0 +1,68 @@
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: '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>
}

View File

@@ -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>
}

View File

@@ -0,0 +1,4 @@
export enum WorkspaceInviteStatus {
Created = 'created',
Accepted = 'accepted',
}

View File

@@ -0,0 +1,71 @@
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'
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',
} 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',
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
encryptedPrivateKey: 'foo',
publicKey: 'bar',
status: 'pending-keyshare',
userUuid: 'u-1-2-3',
workspaceUuid: 'w-1-2-3',
})
})
it('should not accept an invite if it does not exist', async () => {
workspaceInviteRepository.findOneByUuid = jest.fn().mockReturnValue(null)
await createUseCase().execute({
acceptingUserUuid: 'u-1-2-3',
encryptedPrivateKey: 'foo',
publicKey: 'bar',
invitationUuid: 'i-1-2-3',
})
expect(workspaceInviteRepository.save).not.toHaveBeenCalled()
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,50 @@
import { TimerInterface } from '@standardnotes/time'
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 { WorkspaceUserStatus } from '../../Workspace/WorkspaceUserStatus'
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.workspaceUuid = invite.workspaceUuid
workspaceUser.publicKey = dto.publicKey
workspaceUser.encryptedPrivateKey = dto.encryptedPrivateKey
workspaceUser.status = WorkspaceUserStatus.PendingKeyshare
await this.workspaceUserRepository.save(workspaceUser)
return {
success: true,
}
}
}

View File

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

View File

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

View File

@@ -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,
})
})
})

View File

@@ -0,0 +1,57 @@
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { Workspace } from '../../Workspace/Workspace'
import { WorkspaceAccessLevel } from '../../Workspace/WorkspaceAccessLevel'
import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { WorkspaceUserStatus } from '../../Workspace/WorkspaceUserStatus'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateWorkspaceDTO } from './CreateWorkspaceDTO'
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 }
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,5 @@
import { Workspace } from '../../Workspace/Workspace'
export type CreateWorkspaceResponse = {
workspace: Workspace
}

View File

@@ -0,0 +1,70 @@
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'
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',
})
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',
createdAt: 1,
updatedAt: 1,
},
})
expect(workspaceInviteRepository.save).toHaveBeenCalledWith({
inviterUuid: 'u-1-2-3',
inviteeEmail: 'test@test.te',
workspaceUuid: 'w-1-2-3',
status: 'created',
createdAt: 1,
updatedAt: 1,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,50 @@
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.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,
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
export interface UseCaseInterface {
execute(...args: any[]): Promise<Record<string, unknown>>
}

View File

@@ -1,5 +1,6 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { WorkspaceType } from './WorkspaceType'
import { WorkspaceType } from '@standardnotes/common'
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import { WorkspaceInvite } from '../Invite/WorkspaceInvite'
@Entity({ name: 'workspaces' })
export class Workspace {
@@ -23,4 +24,24 @@ export class Workspace {
default: 0,
})
declare keyRotationIndex: number
@Column({
name: 'created_at',
type: 'bigint',
})
declare createdAt: number
@Column({
name: 'updated_at',
type: 'bigint',
})
declare updatedAt: number
@OneToMany(
/* istanbul ignore next */
() => WorkspaceInvite,
/* istanbul ignore next */
(workspaceInvite) => workspaceInvite.workspace,
)
declare invites: Promise<WorkspaceInvite[]>
}

View File

@@ -0,0 +1,5 @@
import { Workspace } from './Workspace'
export interface WorkspaceRepositoryInterface {
save(workspace: Workspace): Promise<Workspace>
}

View File

@@ -38,15 +38,17 @@ export class WorkspaceUser {
name: 'public_key',
length: 255,
type: 'varchar',
nullable: true,
})
declare publicKey: string
declare publicKey: string | null
@Column({
name: 'private_key',
name: 'encrypted_private_key',
length: 255,
type: 'varchar',
nullable: true,
})
declare privateKey: string
declare encryptedPrivateKey: string | null
@Column({
name: 'status',
@@ -59,4 +61,16 @@ export class WorkspaceUser {
default: 0,
})
declare keyRotationIndex: number
@Column({
name: 'created_at',
type: 'bigint',
})
declare createdAt: number
@Column({
name: 'updated_at',
type: 'bigint',
})
declare updatedAt: number
}

View File

@@ -0,0 +1,5 @@
import { WorkspaceUser } from './WorkspaceUser'
export interface WorkspaceUserRepositoryInterface {
save(workspace: WorkspaceUser): Promise<WorkspaceUser>
}

View File

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

View File

@@ -0,0 +1,39 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { WorkspaceInvite } from '../../Domain/Invite/WorkspaceInvite'
import { MySQLWorkspaceInviteRepository } from './MySQLWorkspaceInviteRepository'
describe('MySQLWorkspaceInviteRepository', () => {
let ormRepository: Repository<WorkspaceInvite>
let invite: WorkspaceInvite
let queryBuilder: SelectQueryBuilder<WorkspaceInvite>
const createRepository = () => new MySQLWorkspaceInviteRepository(ormRepository)
beforeEach(() => {
invite = {} as jest.Mocked<WorkspaceInvite>
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<WorkspaceInvite>>
ormRepository = {} as jest.Mocked<Repository<WorkspaceInvite>>
ormRepository.save = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(invite)
expect(ormRepository.save).toHaveBeenCalledWith(invite)
})
it('should find one by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' })
})
})

View File

@@ -0,0 +1,22 @@
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import TYPES from '../../Bootstrap/Types'
import { WorkspaceInvite } from '../../Domain/Invite/WorkspaceInvite'
import { WorkspaceInviteRepositoryInterface } from '../../Domain/Invite/WorkspaceInviteRepositoryInterface'
@injectable()
export class MySQLWorkspaceInviteRepository implements WorkspaceInviteRepositoryInterface {
constructor(
@inject(TYPES.ORMWorkspaceInviteRepository)
private ormRepository: Repository<WorkspaceInvite>,
) {}
async findOneByUuid(uuid: string): Promise<WorkspaceInvite | null> {
return this.ormRepository.createQueryBuilder().where('uuid = :uuid', { uuid }).getOne()
}
async save(workspaceInvite: WorkspaceInvite): Promise<WorkspaceInvite> {
return this.ormRepository.save(workspaceInvite)
}
}

View File

@@ -0,0 +1,31 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { Workspace } from '../../Domain/Workspace/Workspace'
import { MySQLWorkspaceRepository } from './MySQLWorkspaceRepository'
describe('MySQLWorkspaceRepository', () => {
let ormRepository: Repository<Workspace>
let workspace: Workspace
let queryBuilder: SelectQueryBuilder<Workspace>
const createRepository = () => new MySQLWorkspaceRepository(ormRepository)
beforeEach(() => {
workspace = {} as jest.Mocked<Workspace>
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Workspace>>
ormRepository = {} as jest.Mocked<Repository<Workspace>>
ormRepository.save = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(workspace)
expect(ormRepository.save).toHaveBeenCalledWith(workspace)
})
})

View File

@@ -0,0 +1,17 @@
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import TYPES from '../../Bootstrap/Types'
import { Workspace } from '../../Domain/Workspace/Workspace'
import { WorkspaceRepositoryInterface } from '../../Domain/Workspace/WorkspaceRepositoryInterface'
@injectable()
export class MySQLWorkspaceRepository implements WorkspaceRepositoryInterface {
constructor(
@inject(TYPES.ORMWorkspaceRepository)
private ormRepository: Repository<Workspace>,
) {}
async save(workspace: Workspace): Promise<Workspace> {
return this.ormRepository.save(workspace)
}
}

View File

@@ -0,0 +1,30 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { WorkspaceUser } from '../../Domain/Workspace/WorkspaceUser'
import { MySQLWorkspaceUserRepository } from './MySQLWorkspaceUserRepository'
describe('MySQLWorkspaceUserRepository', () => {
let ormRepository: Repository<WorkspaceUser>
let workspace: WorkspaceUser
let queryBuilder: SelectQueryBuilder<WorkspaceUser>
const createRepository = () => new MySQLWorkspaceUserRepository(ormRepository)
beforeEach(() => {
workspace = {} as jest.Mocked<WorkspaceUser>
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<WorkspaceUser>>
ormRepository = {} as jest.Mocked<Repository<WorkspaceUser>>
ormRepository.save = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(workspace)
expect(ormRepository.save).toHaveBeenCalledWith(workspace)
})
})

View File

@@ -0,0 +1,18 @@
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import TYPES from '../../Bootstrap/Types'
import { WorkspaceUser } from '../../Domain/Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Domain/Workspace/WorkspaceUserRepositoryInterface'
@injectable()
export class MySQLWorkspaceUserRepository implements WorkspaceUserRepositoryInterface {
constructor(
@inject(TYPES.ORMWorkspaceUserRepository)
private ormRepository: Repository<WorkspaceUser>,
) {}
async save(workspaceUser: WorkspaceUser): Promise<WorkspaceUser> {
return this.ormRepository.save(workspaceUser)
}
}

120
yarn.lock
View File

@@ -1824,18 +1824,18 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/api@npm:^1.9.0":
version: 1.9.0
resolution: "@standardnotes/api@npm:1.9.0"
"@standardnotes/api@npm:^1.12.1":
version: 1.12.1
resolution: "@standardnotes/api@npm:1.12.1"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/encryption": 1.15.9
"@standardnotes/models": 1.22.0
"@standardnotes/responses": 1.10.3
"@standardnotes/common": ^1.36.1
"@standardnotes/encryption": 1.16.2
"@standardnotes/models": 1.24.2
"@standardnotes/responses": 1.10.6
"@standardnotes/security": ^1.1.0
"@standardnotes/utils": 1.9.0
"@standardnotes/utils": 1.9.1
reflect-metadata: ^0.1.13
checksum: cc3feac3935a382e0ce1fcaf233206a547b6c998cb99ab362d5c7030b3f4e7cbbd3a083eab40bdecbcdc9497dcd283e4513e29dbf200e815ffa46b192ed61b01
checksum: 8623fc82de0cbe6793691bc50bf168d1ab2535516f71ffc10ac642abe6ab9ac2faef6cfe406350c2a1b6ea31e0ad34ad29cab804721a49500f6a1d3498cdd46e
languageName: node
linkType: hard
@@ -1846,7 +1846,7 @@ __metadata:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/analytics": "workspace:*"
"@standardnotes/api": ^1.9.0
"@standardnotes/api": ^1.12.1
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
@@ -1907,7 +1907,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common":
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@^1.36.1, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common":
version: 0.0.0-use.local
resolution: "@standardnotes/common@workspace:packages/common"
dependencies:
@@ -1934,7 +1934,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/domain-events-infra@workspace:*, @standardnotes/domain-events-infra@workspace:packages/domain-events-infra":
"@standardnotes/domain-events-infra@workspace:*, @standardnotes/domain-events-infra@workspace:^, @standardnotes/domain-events-infra@workspace:packages/domain-events-infra":
version: 0.0.0-use.local
resolution: "@standardnotes/domain-events-infra@workspace:packages/domain-events-infra"
dependencies:
@@ -1955,7 +1955,7 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/domain-events@workspace:*, @standardnotes/domain-events@workspace:packages/domain-events":
"@standardnotes/domain-events@workspace:*, @standardnotes/domain-events@workspace:^, @standardnotes/domain-events@workspace:packages/domain-events":
version: 0.0.0-use.local
resolution: "@standardnotes/domain-events@workspace:packages/domain-events"
dependencies:
@@ -1972,17 +1972,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/encryption@npm:1.15.9":
version: 1.15.9
resolution: "@standardnotes/encryption@npm:1.15.9"
"@standardnotes/encryption@npm:1.16.2":
version: 1.16.2
resolution: "@standardnotes/encryption@npm:1.16.2"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/models": 1.22.0
"@standardnotes/responses": 1.10.3
"@standardnotes/sncrypto-common": 1.12.0
"@standardnotes/utils": 1.9.0
"@standardnotes/common": ^1.36.1
"@standardnotes/models": 1.24.2
"@standardnotes/responses": 1.10.6
"@standardnotes/sncrypto-common": 1.13.0
"@standardnotes/utils": 1.9.1
reflect-metadata: ^0.1.13
checksum: 7595ac08cea6e54e1456cbff3958969318d90ae237aff166bc4429da5bcf6167c5eb03aa8658a1747486a0639b80b523dae46106ab253c75d24579643cf3e948
checksum: 50efc1b20105b06be2325d17440a0952e3fd47a596f99ea937446e6da895c349cb5ae449398c390829c2379c03a7cfa160e7f6b2d9bf3339ccb53fc34fa81c86
languageName: node
linkType: hard
@@ -2014,15 +2014,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/features@npm:1.52.1, @standardnotes/features@npm:^1.52.1":
version: 1.52.1
resolution: "@standardnotes/features@npm:1.52.1"
"@standardnotes/features@npm:1.52.4":
version: 1.52.4
resolution: "@standardnotes/features@npm:1.52.4"
dependencies:
"@standardnotes/auth": ^3.19.4
"@standardnotes/common": ^1.32.0
"@standardnotes/common": ^1.36.1
"@standardnotes/security": ^1.2.0
reflect-metadata: ^0.1.13
checksum: ff3684399e0e0c0e799f11e69dddea2e1f65f315e5a5dd3ca5640e24e836ee85faf1f5ee15fc804411bf083004527fcef08411d5c2d0b5894491bf2f28ceca68
checksum: aea7b486275e9485c3d87b3db334c2b955f3ddd160054282e769aa59f026f6daffcb15edd8a3a4959a861552294df54437edb8d5b636f4e4f1c59eb74e75424b
languageName: node
linkType: hard
@@ -2037,6 +2037,18 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/features@npm:^1.52.1":
version: 1.52.1
resolution: "@standardnotes/features@npm:1.52.1"
dependencies:
"@standardnotes/auth": ^3.19.4
"@standardnotes/common": ^1.32.0
"@standardnotes/security": ^1.2.0
reflect-metadata: ^0.1.13
checksum: ff3684399e0e0c0e799f11e69dddea2e1f65f315e5a5dd3ca5640e24e836ee85faf1f5ee15fc804411bf083004527fcef08411d5c2d0b5894491bf2f28ceca68
languageName: node
linkType: hard
"@standardnotes/files-server@workspace:packages/files":
version: 0.0.0-use.local
resolution: "@standardnotes/files-server@workspace:packages/files"
@@ -2087,17 +2099,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/models@npm:1.22.0":
version: 1.22.0
resolution: "@standardnotes/models@npm:1.22.0"
"@standardnotes/models@npm:1.24.2":
version: 1.24.2
resolution: "@standardnotes/models@npm:1.24.2"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.1
"@standardnotes/responses": 1.10.3
"@standardnotes/utils": 1.9.0
"@standardnotes/common": ^1.36.1
"@standardnotes/features": 1.52.4
"@standardnotes/responses": 1.10.6
"@standardnotes/utils": 1.9.1
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 9928246368b7de7062314374219065507642ed3b6764c27f14ed8d42f0c5a9370fab8731a43a57885a000e461ea60694ba4caa7d9940350839d487cedb7079b5
checksum: 17b3cfba39c97f9e7b1960fe5cc0e6edd0ac0fbc492e266d7814972a6619465f8fb7ca6c90ac082de9592c83e2bbc2d883d9eaa076d4b283d93092e779a1af5c
languageName: node
linkType: hard
@@ -2126,15 +2138,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/responses@npm:1.10.3":
version: 1.10.3
resolution: "@standardnotes/responses@npm:1.10.3"
"@standardnotes/responses@npm:1.10.6":
version: 1.10.6
resolution: "@standardnotes/responses@npm:1.10.6"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.1
"@standardnotes/common": ^1.36.1
"@standardnotes/features": 1.52.4
"@standardnotes/security": ^1.1.0
reflect-metadata: ^0.1.13
checksum: 4a1e31eb89342461488f00c65884839d7d935d47d4e217da83967c7cbe181fd0341ee58b744fe607def30d06cd1a8069c3b54f40b3933765303db35ee63f6a0d
checksum: 0583e2cb77a23c22c7aa90e912c87b41fc8b8d236256cfa672e2a1272138974da0ebbbfa284049860adfed818e9c45a7f8ba169ff137b6b5c46a32a689320e6a
languageName: node
linkType: hard
@@ -2234,12 +2246,12 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/sncrypto-common@npm:1.12.0":
version: 1.12.0
resolution: "@standardnotes/sncrypto-common@npm:1.12.0"
"@standardnotes/sncrypto-common@npm:1.13.0":
version: 1.13.0
resolution: "@standardnotes/sncrypto-common@npm:1.13.0"
dependencies:
reflect-metadata: ^0.1.13
checksum: b89a14bd233cb781213b2e25dd8d7bfe911820d341903f2987647c168745c3afb710ad94fa42e50aaafb644ee4a7b5fd64ec137f622cfc082951fd584af0d230
checksum: e58258f52546a3f0ce2d8f76d4be79a7d23e15dfd81e687ac66cd3fc00dc91e563556da2e600dbd2f270f659b3e1f3e4301dd6e6e7e6eb4faf51c4343fc65c96
languageName: node
linkType: hard
@@ -2321,7 +2333,7 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/time@workspace:*, @standardnotes/time@workspace:packages/time":
"@standardnotes/time@workspace:*, @standardnotes/time@workspace:^, @standardnotes/time@workspace:packages/time":
version: 0.0.0-use.local
resolution: "@standardnotes/time@workspace:packages/time"
dependencies:
@@ -2337,15 +2349,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/utils@npm:1.9.0":
version: 1.9.0
resolution: "@standardnotes/utils@npm:1.9.0"
"@standardnotes/utils@npm:1.9.1":
version: 1.9.1
resolution: "@standardnotes/utils@npm:1.9.1"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/common": ^1.36.1
dompurify: ^2.3.8
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 4591aff48d074b30b911f96c63eaaf521ab49563507672fbd4d7fe460e51f88a45effb002d1c82cca3513d2199c0cdb720556b03ec3e0266f593317c8efa764a
checksum: f775bb37447300bef1c02d911f7d132538bf52d8b65573129def50b0545b1a69af785b1d09932734ecc14a37e07cf1913e5e7b25866249a059bcea9fa7e9b18c
languageName: node
linkType: hard
@@ -2366,10 +2378,12 @@ __metadata:
dependencies:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/api": ^1.12.1
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
"@standardnotes/domain-events": "workspace:^"
"@standardnotes/domain-events-infra": "workspace:^"
"@standardnotes/security": "workspace:*"
"@standardnotes/time": "workspace:^"
"@types/cors": ^2.8.9
"@types/express": ^4.17.11
"@types/ioredis": ^4.28.10