chore: remove workspaces from code base

This commit is contained in:
Karol Sójko 2023-02-23 13:30:39 +01:00
parent 9d872008a7
commit deec29c1b4
No known key found for this signature in database
GPG key ID: D966F68E8A92F649
107 changed files with 0 additions and 3863 deletions

View file

@ -95,11 +95,6 @@ updates:
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/packages/workspace"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:

View file

@ -1,47 +0,0 @@
name: Workspace Server
concurrency:
group: workspace
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/workspace-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: workspace
workspace_name: "@standardnotes/workspace-server"
package_path: packages/workspace
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WORKSPACE_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WORKSPACE_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View file

@ -349,7 +349,6 @@ export API_GATEWAY_NEW_RELIC_NO_CONFIG_FILE=true
export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT
export API_GATEWAY_AUTH_SERVER_URL=http://localhost:$AUTH_SERVER_PORT
export API_GATEWAY_WORKSPACE_SERVER_URL=http://localhost:3004
export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:3005
if [ -z "$PUBLIC_FILES_SERVER_URL" ]; then
export PUBLIC_FILES_SERVER_URL=http://localhost:3125

View file

@ -91,13 +91,6 @@ TOPIC_CREATED_RESULT=$(create_topic ${SCHEDULER_TOPIC_NAME})
echo "created topic: $TOPIC_CREATED_RESULT"
SCHEDULER_TOPIC_ARN=$(get_topic_arn_from_name $SCHEDULER_TOPIC_NAME)
WORKSPACE_TOPIC_NAME="workspace-local-topic"
echo "creating topic $WORKSPACE_TOPIC_NAME"
TOPIC_CREATED_RESULT=$(create_topic ${WORKSPACE_TOPIC_NAME})
echo "created topic: $TOPIC_CREATED_RESULT"
WORKSPACE_TOPIC_ARN=$(get_topic_arn_from_name $WORKSPACE_TOPIC_NAME)
QUEUE_NAME="analytics-local-queue"
echo "creating queue $QUEUE_NAME"
@ -182,13 +175,6 @@ QUEUE_URL=$(create_queue ${QUEUE_NAME})
echo "created queue: $QUEUE_URL"
SCHEDULER_QUEUE_ARN=$(get_queue_arn_from_name $QUEUE_NAME)
QUEUE_NAME="workspace-local-queue"
echo "creating queue $QUEUE_NAME"
QUEUE_URL=$(create_queue ${QUEUE_NAME})
echo "created queue: $QUEUE_URL"
WORKSPACE_QUEUE_ARN=$(get_queue_arn_from_name $QUEUE_NAME)
echo "all topics are:"
echo "$(get_all_topics)"

View file

@ -6,7 +6,6 @@ PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
AUTH_SERVER_URL=http://auth:3000
WORKSPACE_SERVER_URL=http://workspace:3000
WEB_SOCKET_SERVER_URL=http://websockets:3000
PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000

View file

@ -19,7 +19,6 @@ 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/v1/AuthenticatorsController'
import '../src/Controller/v1/ProxyController'

View file

@ -59,7 +59,6 @@ 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', true))
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
container.bind(TYPES.PROXY_SERVER_URL).toConstantValue(env.get('PROXY_SERVER_URL', true))
container

View file

@ -9,7 +9,6 @@ const TYPES = {
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
PROXY_SERVER_URL: Symbol.for('PROXY_SERVER_URL'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),

View file

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

View file

@ -1,53 +0,0 @@
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,
)
}
}

View file

@ -16,7 +16,6 @@ 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.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
@ -82,21 +81,6 @@ export class HttpService implements HttpServiceInterface {
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
}
async callWorkspaceServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.workspaceServerUrl) {
response.status(400).send({ message: 'Workspace Server not configured' })
return
}
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
}
async callWebSocketServer(
request: Request,
response: Response,

View file

@ -43,12 +43,6 @@ 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>
callWebSocketServer(
request: Request,
response: Response,

View file

@ -26,7 +26,6 @@ export enum EmailMessageIdentifier {
REFUND_NOTICE = 'REFUND_NOTICE',
REFUND_REQUESTED = 'REFUND_REQUESTED',
RATE_ADJUSTMENT_NOTICE = 'RATE_ADJUSTMENT_NOTICE',
WORKSPACE_INVITE_CREATED = 'WORKSPACE_INVITE_CREATED',
EXIT_DISCOUNT = 'EXIT_DISCOUNT',
SUBSCRIPTION_CANCELLED = 'SUBSCRIPTION_CANCELLED',
}

View file

@ -1,6 +0,0 @@
export enum WorkspaceAccessLevel {
Owner = 'owner',
Admin = 'admin',
ReadOnly = 'read-only',
WriteAndRead = 'write-and-read',
}

View file

@ -1,5 +0,0 @@
export enum WorkspaceType {
Root = 'root',
Team = 'team',
Private = 'private',
}

View file

@ -1,4 +0,0 @@
export enum WorkspaceUserStatus {
Active = 'active',
PendingKeyshare = 'pending-keyshare',
}

View file

@ -20,6 +20,3 @@ export * from './Subscription/SubscriptionName'
export * from './Type/Either'
export * from './Type/Only'
export * from './User/UserRequestType'
export * from './Workspace/WorkspaceAccessLevel'
export * from './Workspace/WorkspaceType'
export * from './Workspace/WorkspaceUserStatus'

View file

@ -8,7 +8,6 @@ export enum DomainEventService {
ApiGateway = 'api-gateway',
Files = 'files',
Scheduler = 'scheduler',
Workspace = 'workspace',
Analytics = 'analytics',
Revisions = 'revisions',
Email = 'email',

View file

@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { WorkspaceInviteAcceptedEventPayload } from './WorkspaceInviteAcceptedEventPayload'
export interface WorkspaceInviteAcceptedEvent extends DomainEventInterface {
type: 'WORKSPACE_INVITE_ACCEPTED'
payload: WorkspaceInviteAcceptedEventPayload
}

View file

@ -1,5 +0,0 @@
export interface WorkspaceInviteAcceptedEventPayload {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}

View file

@ -92,8 +92,6 @@ export * from './Event/UserRolesChangedEvent'
export * from './Event/UserRolesChangedEventPayload'
export * from './Event/WebSocketMessageRequestedEvent'
export * from './Event/WebSocketMessageRequestedEventPayload'
export * from './Event/WorkspaceInviteAcceptedEvent'
export * from './Event/WorkspaceInviteAcceptedEventPayload'
export * from './Handler/DomainEventHandlerInterface'
export * from './Handler/DomainEventMessageHandlerInterface'

View file

@ -1,32 +0,0 @@
LOG_LEVEL=debug
NODE_ENV=development
VERSION=development
AUTH_JWT_SECRET=auth_jwt_secret
PORT=3000
DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=workspace
DB_PASSWORD=changeme123
DB_DATABASE=workspace
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
DB_MIGRATIONS_PATH=dist/migrations/*.js
REDIS_URL=redis://cache
SNS_TOPIC_ARN=
SNS_AWS_REGION=
SQS_QUEUE_URL=
SQS_AWS_REGION=
# (Optional) New Relic Setup
NEW_RELIC_ENABLED=false
NEW_RELIC_APP_NAME=Workspace
NEW_RELIC_LICENSE_KEY=
NEW_RELIC_NO_CONFIG_FILE=true
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_LEVEL=info

View file

@ -1,3 +0,0 @@
dist
test-setup.ts
data

View file

@ -1,6 +0,0 @@
{
"extends": "../../.eslintrc",
"parserOptions": {
"project": "./linter.tsconfig.json"
}
}

View file

@ -1,558 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.20.3...@standardnotes/workspace-server@1.20.4) (2023-02-23)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.20.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.20.2...@standardnotes/workspace-server@1.20.3) (2023-02-21)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.20.1...@standardnotes/workspace-server@1.20.2) (2023-02-20)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.20.0...@standardnotes/workspace-server@1.20.1) (2023-02-15)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.23...@standardnotes/workspace-server@1.20.0) (2023-02-15)
### Features
* optimize memory on server utilities ([881a696](https://github.com/standardnotes/server/commit/881a6967aca57d68795af0792114f848ddddf120))
## [1.19.23](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.22...@standardnotes/workspace-server@1.19.23) (2023-02-06)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.22](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.21...@standardnotes/workspace-server@1.19.22) (2023-01-30)
### Bug Fixes
* sqs configuration for aws sdk v3 ([b54c331](https://github.com/standardnotes/server/commit/b54c331bef0d4ad1ba1111700dc9f1bf64c1ea51))
## [1.19.21](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.20...@standardnotes/workspace-server@1.19.21) (2023-01-25)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.20](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.19...@standardnotes/workspace-server@1.19.20) (2023-01-24)
### Bug Fixes
* **auth:** add pseudo u2f params on non existing accounts ([e4c65ca](https://github.com/standardnotes/server/commit/e4c65ca631938d8b1d635a40e463d5544051e4a1))
## [1.19.19](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.18...@standardnotes/workspace-server@1.19.19) (2023-01-24)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.18](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.17...@standardnotes/workspace-server@1.19.18) (2023-01-23)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.17](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.16...@standardnotes/workspace-server@1.19.17) (2023-01-20)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.16](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.15...@standardnotes/workspace-server@1.19.16) (2023-01-20)
### Bug Fixes
* dependency issues ([589f8e6](https://github.com/standardnotes/server/commit/589f8e62f4753b9c6fab21bd675114d373d89f2d))
## [1.19.15](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.14...@standardnotes/workspace-server@1.19.15) (2023-01-20)
### Reverts
* Revert "chore: upgrade @standardnotes/* dependencies" ([5bf3ecd](https://github.com/standardnotes/server/commit/5bf3ecdf42e1e5b9cb538cad08a18fb6e4054129))
## [1.19.14](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.13...@standardnotes/workspace-server@1.19.14) (2023-01-20)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.13](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.12...@standardnotes/workspace-server@1.19.13) (2023-01-19)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.12](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.11...@standardnotes/workspace-server@1.19.12) (2023-01-19)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.11](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.10...@standardnotes/workspace-server@1.19.11) (2023-01-19)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.10](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.9...@standardnotes/workspace-server@1.19.10) (2023-01-19)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.9](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.8...@standardnotes/workspace-server@1.19.9) (2023-01-18)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.8](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.7...@standardnotes/workspace-server@1.19.8) (2023-01-17)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.7](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.6...@standardnotes/workspace-server@1.19.7) (2023-01-17)
### Bug Fixes
* allow to run typeorm in non-replica mode ([f73129c](https://github.com/standardnotes/server/commit/f73129cd7e7d6a9b8a63e5c80284467597557982))
## [1.19.6](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.5...@standardnotes/workspace-server@1.19.6) (2023-01-16)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.5](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.4...@standardnotes/workspace-server@1.19.5) (2023-01-13)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.3...@standardnotes/workspace-server@1.19.4) (2022-12-28)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.2...@standardnotes/workspace-server@1.19.3) (2022-12-20)
### Bug Fixes
* **workspace:** specs ([c8203cf](https://github.com/standardnotes/server/commit/c8203cf04cb93cc65d30b69f10fb275f5e6be449))
## [1.19.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.1...@standardnotes/workspace-server@1.19.2) (2022-12-20)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.19.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.19.0...@standardnotes/workspace-server@1.19.1) (2022-12-19)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.6...@standardnotes/workspace-server@1.19.0) (2022-12-19)
### Features
* **auth:** add session traces ([8bcb552](https://github.com/standardnotes/server/commit/8bcb552783b2d12f3296b3195752168482790bc8))
## [1.18.6](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.5...@standardnotes/workspace-server@1.18.6) (2022-12-15)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.18.5](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.4...@standardnotes/workspace-server@1.18.5) (2022-12-15)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.18.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.3...@standardnotes/workspace-server@1.18.4) (2022-12-12)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.18.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.2...@standardnotes/workspace-server@1.18.3) (2022-12-12)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.18.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.1...@standardnotes/workspace-server@1.18.2) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.0...@standardnotes/workspace-server@1.18.1) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.47...@standardnotes/workspace-server@1.18.0) (2022-12-09)
### Features
* **workspace:** replace workspace invite created event with email requested ([61c1cff](https://github.com/standardnotes/server/commit/61c1cfff4bcee09e1f933cb3e085412b6f07cc42))
## [1.17.47](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.46...@standardnotes/workspace-server@1.17.47) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.46](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.45...@standardnotes/workspace-server@1.17.46) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.45](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.44...@standardnotes/workspace-server@1.17.45) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.44](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.43...@standardnotes/workspace-server@1.17.44) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.43](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.42...@standardnotes/workspace-server@1.17.43) (2022-12-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.42](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.41...@standardnotes/workspace-server@1.17.42) (2022-12-08)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.41](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.40...@standardnotes/workspace-server@1.17.41) (2022-12-08)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.40](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.39...@standardnotes/workspace-server@1.17.40) (2022-12-08)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.39](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.38...@standardnotes/workspace-server@1.17.39) (2022-12-08)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.38](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.37...@standardnotes/workspace-server@1.17.38) (2022-12-08)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.37](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.36...@standardnotes/workspace-server@1.17.37) (2022-12-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.36](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.35...@standardnotes/workspace-server@1.17.36) (2022-12-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.35](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.34...@standardnotes/workspace-server@1.17.35) (2022-12-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.34](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.33...@standardnotes/workspace-server@1.17.34) (2022-12-06)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.33](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.32...@standardnotes/workspace-server@1.17.33) (2022-12-05)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.32](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.31...@standardnotes/workspace-server@1.17.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.31](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.30...@standardnotes/workspace-server@1.17.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.30](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.29...@standardnotes/workspace-server@1.17.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.29](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.28...@standardnotes/workspace-server@1.17.29) (2022-11-24)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.28](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.27...@standardnotes/workspace-server@1.17.28) (2022-11-23)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.27](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.26...@standardnotes/workspace-server@1.17.27) (2022-11-23)
### Bug Fixes
* binding of sns and sqs with additional config ([74bc791](https://github.com/standardnotes/server/commit/74bc79116bc50d9a5af1a558db1b7108dcda6d0e))
## [1.17.26](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.25...@standardnotes/workspace-server@1.17.26) (2022-11-22)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.25](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.24...@standardnotes/workspace-server@1.17.25) (2022-11-21)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.24](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.23...@standardnotes/workspace-server@1.17.24) (2022-11-18)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.23](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.22...@standardnotes/workspace-server@1.17.23) (2022-11-16)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.22](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.21...@standardnotes/workspace-server@1.17.22) (2022-11-14)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.21](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.18...@standardnotes/workspace-server@1.17.21) (2022-11-14)
### Bug Fixes
* versioning issue ([7ca377f](https://github.com/standardnotes/server/commit/7ca377f1b889379e6a43a66c0134bf266763516d))
## [1.17.18](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.17...@standardnotes/workspace-server@1.17.18) (2022-11-14)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.17](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.16...@standardnotes/workspace-server@1.17.17) (2022-11-11)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.16](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.15...@standardnotes/workspace-server@1.17.16) (2022-11-11)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.15](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.14...@standardnotes/workspace-server@1.17.15) (2022-11-10)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.14](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.13...@standardnotes/workspace-server@1.17.14) (2022-11-10)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.13](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.12...@standardnotes/workspace-server@1.17.13) (2022-11-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.12](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.11...@standardnotes/workspace-server@1.17.12) (2022-11-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.11](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.10...@standardnotes/workspace-server@1.17.11) (2022-11-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.10](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.9...@standardnotes/workspace-server@1.17.10) (2022-11-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.9](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.8...@standardnotes/workspace-server@1.17.9) (2022-11-09)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.8](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.7...@standardnotes/workspace-server@1.17.8) (2022-11-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.7](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.6...@standardnotes/workspace-server@1.17.7) (2022-11-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.6](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.5...@standardnotes/workspace-server@1.17.6) (2022-11-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.5](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.4...@standardnotes/workspace-server@1.17.5) (2022-11-07)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.3...@standardnotes/workspace-server@1.17.4) (2022-11-04)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.2...@standardnotes/workspace-server@1.17.3) (2022-11-04)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.1...@standardnotes/workspace-server@1.17.2) (2022-11-04)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.0...@standardnotes/workspace-server@1.17.1) (2022-11-03)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.6...@standardnotes/workspace-server@1.17.0) (2022-11-02)
### Features
* **auth:** add processing user requests ([2255f85](https://github.com/standardnotes/server/commit/2255f856f928e855ac94f8aca4e1fb81047f58f7))
## [1.16.6](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.5...@standardnotes/workspace-server@1.16.6) (2022-11-02)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.16.5](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.4...@standardnotes/workspace-server@1.16.5) (2022-11-01)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.16.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.3...@standardnotes/workspace-server@1.16.4) (2022-11-01)
### Bug Fixes
* force utf8mb4 charset on typeorm ([5160cc3](https://github.com/standardnotes/server/commit/5160cc36ddc9e30551d5ad40a9e210d87091eec3))
## [1.16.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.2...@standardnotes/workspace-server@1.16.3) (2022-10-31)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.1...@standardnotes/workspace-server@1.16.2) (2022-10-31)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.16.0...@standardnotes/workspace-server@1.16.1) (2022-10-26)
**Note:** Version bump only for package @standardnotes/workspace-server
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.15.0...@standardnotes/workspace-server@1.16.0) (2022-10-24)
### Features
* **auth:** change accepting invitations to be an authorized endpoint ([771a555](https://github.com/standardnotes/server/commit/771a555b4f33452311cd5bf0b8cfcbc4f2f1c4dd))
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.6...@standardnotes/workspace-server@1.15.0) (2022-10-19)
### Features
* building server applications in ARM64 architecture for Docker ([fd92866](https://github.com/standardnotes/server/commit/fd92866ba1a86b22769b23cc4c8387a83f87979a))
## [1.14.6](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.5...@standardnotes/workspace-server@1.14.6) (2022-10-17)
### Bug Fixes
* **workspaces:** accepting invitations ([b6f3954](https://github.com/standardnotes/server/commit/b6f3954e24794d6ab97ab61f39c4d91c39e5f558))
## [1.14.5](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.4...@standardnotes/workspace-server@1.14.5) (2022-10-17)
### Bug Fixes
* **workspaces:** allow type of workspace to come from parameters ([6af8ff7](https://github.com/standardnotes/server/commit/6af8ff700ea795252d5f8d3a955f27eef9240ae2))
## [1.14.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.3...@standardnotes/workspace-server@1.14.4) (2022-10-17)
### Bug Fixes
* **workspaces:** listing workspace uuids ([365d316](https://github.com/standardnotes/server/commit/365d316878d8d76256efc102e53ff9868789cbc8))
## [1.14.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.2...@standardnotes/workspace-server@1.14.3) (2022-10-17)
### Bug Fixes
* **workspaces:** add debug logs for listing workspaces ([d232e71](https://github.com/standardnotes/server/commit/d232e71683d553ddaa41dd2f75716cdf8e62a19f))
## [1.14.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.1...@standardnotes/workspace-server@1.14.2) (2022-10-14)
### Bug Fixes
* **workspace:** fetching users workspaces list ([0cacc8e](https://github.com/standardnotes/server/commit/0cacc8efa06d6dde0e8cc4486c303082a5ee4211))
## [1.14.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.14.0...@standardnotes/workspace-server@1.14.1) (2022-10-14)
### Bug Fixes
* **workspaces:** reading response locals when listing workspaces ([d2fcc76](https://github.com/standardnotes/server/commit/d2fcc761ad30786c7245f379ded996da383c5cfe))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.2...@standardnotes/workspace-server@1.14.0) (2022-10-13)
### Features
* publish workspace invite accepted event for websockets ([86379eb](https://github.com/standardnotes/server/commit/86379eb96d7231d6a76ee91350accef2d44a941d))
## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.1...@standardnotes/workspace-server@1.13.2) (2022-10-13)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.0...@standardnotes/workspace-server@1.13.1) (2022-10-12)
### Bug Fixes
* **workspace:** add workspace to workspace user foreign keys ([4f6a2a8](https://github.com/standardnotes/server/commit/4f6a2a83d3d7b57d176e169f33780730eeae6919))
# [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
## [1.1.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.1.0...@standardnotes/workspace-server@1.1.1) (2022-10-06)
**Note:** Version bump only for package @standardnotes/workspace-server
# 1.1.0 (2022-10-06)
### Features
* add workspace microservice ([44a9ade](https://github.com/standardnotes/server/commit/44a9ade3fc0935d24733327c6b2de05b52496b1c))

View file

@ -1,17 +0,0 @@
FROM node:18.13.0-alpine
RUN apk add --update \
curl \
&& rm -rf /var/cache/apk/*
ENV NODE_ENV production
RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace/packages/workspace
ENTRYPOINT [ "/workspace/packages/workspace/docker/entrypoint.sh" ]
CMD [ "start-web" ]

View file

@ -1,71 +0,0 @@
import 'reflect-metadata'
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'
import * as winston from 'winston'
import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const server = new InversifyExpressServer(container)
server.setConfig((app) => {
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-Auth-Version', container.get(TYPES.VERSION))
next()
})
app.use(json())
app.use(urlencoded({ extended: true }))
app.use(cors())
if (env.get('SENTRY_DSN', true)) {
Sentry.init({
dsn: env.get('SENTRY_DSN'),
integrations: [new Sentry.Integrations.Http({ tracing: false, breadcrumbs: true })],
tracesSampleRate: 0,
})
app.use(Sentry.Handlers.requestHandler() as RequestHandler)
}
})
const logger: winston.Logger = container.get(TYPES.Logger)
server.setErrorConfig((app) => {
if (env.get('SENTRY_DSN', true)) {
app.use(Sentry.Handlers.errorHandler() as ErrorRequestHandler)
}
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
response.status(500).send({
error: {
message:
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
},
})
})
})
const serverInstance = server.build()
serverInstance.listen(env.get('PORT'))
logger.info(`Server started on port ${process.env.PORT}`)
})

View file

@ -1,25 +0,0 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View file

@ -1,11 +0,0 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/server.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index

View file

@ -1,11 +0,0 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/worker.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index

View file

@ -1,22 +0,0 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
;;
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
;;
* )
echo "Unknown command"
;;
esac
exec "$@"

View file

@ -1,12 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../jest.config')
const { defaults: tsjPreset } = require('ts-jest/presets')
module.exports = {
...base,
transform: {
...tsjPreset.transform,
},
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', '/Domain/Email/', '/Domain/Event'],
setupFilesAfterEnv: ['./test-setup.ts'],
}

View file

@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "test-setup.ts"]
}

View file

@ -1,20 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class initial1665049971623 implements MigrationInterface {
name = 'initial1665049971623'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `workspaces` (`uuid` varchar(36) NOT NULL, `type` varchar(64) NOT NULL, `name` varchar(255) NULL, `key_rotation_index` int NOT NULL DEFAULT 0, PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
await queryRunner.query(
'CREATE TABLE `workspace_users` (`uuid` varchar(36) NOT NULL, `access_level` varchar(64) NOT NULL, `user_uuid` varchar(36) NOT NULL, `workspace_uuid` varchar(36) NOT NULL, `encrypted_workspace_key` varchar(255) NULL, `public_key` varchar(255) NOT NULL, `private_key` varchar(255) NOT NULL, `status` varchar(64) NOT NULL, `key_rotation_index` int NOT NULL DEFAULT 0, UNIQUE INDEX `index_workspace_users_on_workspace_and_user` (`user_uuid`, `workspace_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_workspace_users_on_workspace_and_user` ON `workspace_users`')
await queryRunner.query('DROP TABLE `workspace_users`')
await queryRunner.query('DROP TABLE `workspaces`')
}
}

View file

@ -1,17 +0,0 @@
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

@ -1,19 +0,0 @@
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

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

View file

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

View file

@ -1,15 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class addWorkspaceUserForeignKey1665580464598 implements MigrationInterface {
name = 'addWorkspaceUserForeignKey1665580464598'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'ALTER TABLE `workspace_users` ADD CONSTRAINT `FK_cd407e5f2c4f3156ad2015aed41` 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_users` DROP FOREIGN KEY `FK_cd407e5f2c4f3156ad2015aed41`')
}
}

View file

@ -1,62 +0,0 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.20.4",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
"private": true,
"description": "Workspace Server",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"author": "Karol Sójko <karol@standardnotes.com>",
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"setup:env": "cp .env.sample .env",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"typeorm": "typeorm-ts-node-commonjs"
},
"dependencies": {
"@aws-sdk/client-sns": "^3.259.0",
"@aws-sdk/client-sqs": "^3.259.0",
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.28.1",
"@standardnotes/api": "^1.24.10",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:^",
"@standardnotes/domain-events-infra": "workspace:^",
"@standardnotes/models": "^1.42.11",
"@standardnotes/security": "workspace:*",
"@standardnotes/time": "workspace:^",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.2",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.4",
"mysql2": "^3.0.1",
"newrelic": "^9.8.0",
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.10",
"winston": "^3.8.1"
},
"devDependencies": {
"@types/cors": "^2.8.9",
"@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.1.1",
"@types/newrelic": "^9.4.0",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"eslint": "^8.32.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^29.1.2",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
}
}

View file

@ -1,195 +0,0 @@
import * as winston from 'winston'
import Redis from 'ioredis'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { Container } from 'inversify'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { TimerInterface, Timer } from '@standardnotes/time'
import { Env } from './Env'
import TYPES from './Types'
import { AppDataSource } from './DataSource'
import {
SNSDomainEventPublisher,
SQSDomainEventSubscriberFactory,
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} 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')
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
await AppDataSource.initialize()
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Redis).toConstantValue(redis)
const newrelicWinstonFormatter = newrelicFormatter(winston)
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonFormatter())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
if (env.get('SNS_TOPIC_ARN', true)) {
const snsConfig: SNSClientConfig = {
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}
if (env.get('SNS_ENDPOINT', true)) {
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
}
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
snsConfig.credentials = {
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
}
}
container.bind<SNSClient>(TYPES.SNS).toConstantValue(new SNSClient(snsConfig))
}
if (env.get('SQS_QUEUE_URL', true)) {
const sqsConfig: SQSClientConfig = {
region: env.get('SQS_AWS_REGION', true),
}
if (env.get('SQS_ENDPOINT', true)) {
sqsConfig.endpoint = env.get('SQS_ENDPOINT', true)
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
container.bind<SQSClient>(TYPES.SQS).toConstantValue(new SQSClient(sqsConfig))
}
// 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
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
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)))
container
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
])
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toConstantValue(
env.get('NEW_RELIC_ENABLED', true) === 'true'
? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Logger))
: new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)),
)
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toConstantValue(
new SQSDomainEventSubscriberFactory(
container.get(TYPES.SQS),
container.get(TYPES.SQS_QUEUE_URL),
container.get(TYPES.DomainEventMessageHandler),
),
)
return container
}
}

View file

@ -1,53 +0,0 @@
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'
const env: Env = new Env()
env.load()
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
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

@ -1,24 +0,0 @@
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View file

@ -1,48 +0,0 @@
const TYPES = {
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
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
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
REDIS_URL: Symbol.for('REDIS_URL'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
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

View file

@ -1,99 +0,0 @@
import 'reflect-metadata'
import { ApiGatewayAuthMiddleware } from './ApiGatewayAuthMiddleware'
import { NextFunction, Request, Response } from 'express'
import { Logger } from 'winston'
import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
import { RoleName } from '@standardnotes/domain-core'
describe('ApiGatewayAuthMiddleware', () => {
let tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>
let request: Request
let response: Response
let next: NextFunction
const logger = {
debug: jest.fn(),
} as unknown as jest.Mocked<Logger>
const createMiddleware = () => new ApiGatewayAuthMiddleware(tokenDecoder, logger)
beforeEach(() => {
tokenDecoder = {} as jest.Mocked<TokenDecoderInterface<CrossServiceTokenData>>
tokenDecoder.decodeToken = jest.fn().mockReturnValue({
user: {
uuid: '1-2-3',
email: 'test@test.te',
},
roles: [
{
uuid: 'a-b-c',
name: RoleName.NAMES.CoreUser,
},
],
})
request = {
headers: {},
} as jest.Mocked<Request>
response = {
locals: {},
} as jest.Mocked<Response>
response.status = jest.fn().mockReturnThis()
response.send = jest.fn()
next = jest.fn()
})
it('should authorize user', async () => {
request.headers['x-auth-token'] = 'auth-jwt-token'
await createMiddleware().handler(request, response, next)
expect(response.locals.user).toEqual({
uuid: '1-2-3',
email: 'test@test.te',
})
expect(response.locals.roles).toEqual([
{
uuid: 'a-b-c',
name: RoleName.NAMES.CoreUser,
},
])
expect(next).toHaveBeenCalled()
})
it('should not authorize if request is missing auth jwt token in headers', async () => {
await createMiddleware().handler(request, response, next)
expect(response.status).toHaveBeenCalledWith(401)
expect(next).not.toHaveBeenCalled()
})
it('should not authorize if auth jwt token is malformed', async () => {
request.headers['x-auth-token'] = 'auth-jwt-token'
tokenDecoder.decodeToken = jest.fn().mockReturnValue(undefined)
await createMiddleware().handler(request, response, next)
expect(response.status).toHaveBeenCalledWith(401)
expect(next).not.toHaveBeenCalled()
})
it('should pass the error to next middleware if one occurres', async () => {
request.headers['x-auth-token'] = 'auth-jwt-token'
const error = new Error('Ooops')
tokenDecoder.decodeToken = jest.fn().mockImplementation(() => {
throw error
})
await createMiddleware().handler(request, response, next)
expect(response.status).not.toHaveBeenCalled()
expect(next).toHaveBeenCalledWith(error)
})
})

View file

@ -1,59 +0,0 @@
import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
@injectable()
export class ApiGatewayAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.CrossServiceTokenDecoder) private tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
try {
if (!request.headers['x-auth-token']) {
this.logger.debug('ApiGatewayAuthMiddleware missing x-auth-token header.')
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
const token: CrossServiceTokenData | undefined = this.tokenDecoder.decodeToken(
request.headers['x-auth-token'] as string,
)
if (token === undefined) {
this.logger.debug('ApiGatewayAuthMiddleware authentication failure.')
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
response.locals.user = token.user
response.locals.roles = token.roles
response.locals.session = token.session
response.locals.readOnlyAccess = token.session?.readonly_access ?? false
return next()
} catch (error) {
return next(error)
}
}
}

View file

@ -1,204 +0,0 @@
import 'reflect-metadata'
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
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,
})
})
})

View file

@ -1,170 +0,0 @@
import { inject, injectable } from 'inversify'
import {
HttpStatusCode,
WorkspaceCreationRequestParams,
WorkspaceCreationResponse,
WorkspaceInvitationRequestParams,
WorkspaceInvitationResponse,
WorkspaceServerInterface,
WorkspaceListRequestParams,
WorkspaceListResponse,
WorkspaceInvitationAcceptingRequestParams,
WorkspaceInvitationAcceptingResponse,
WorkspaceUserListRequestParams,
WorkspaceKeyshareInitiatingRequestParams,
WorkspaceKeyshareInitiatingResponse,
} from '@standardnotes/api'
import { WorkspaceAccessLevel } 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 string,
})
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 string,
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: params.workspaceType,
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 string,
})
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 string,
workspaceUuid: params.workspaceUuid,
})
const workspaceUserProjections = []
for (const workspaceUser of workspaceUsers) {
workspaceUserProjections.push(await this.workspaceUserProjector.project(workspaceUser))
}
return {
status: HttpStatusCode.Success,
data: { users: workspaceUserProjections },
}
}
async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise<WorkspaceInvitationAcceptingResponse> {
const result = await this.doAcceptInvite.execute({
acceptingUserUuid: params.userUuid,
encryptedPrivateKey: params.encryptedPrivateKey,
invitationUuid: params.inviteUuid,
publicKey: params.publicKey,
})
if (!result.success) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Could not accept invite',
},
},
}
}
return {
status: HttpStatusCode.Success,
data: {
success: true,
},
}
}
}

View file

@ -1,9 +0,0 @@
import { html } from './workspace-invite-created.html'
export function getSubject(): string {
return 'You have been invited to a Standard Notes workspace'
}
export function getBody(inviteUuid: string): string {
return html(inviteUuid)
}

View file

@ -1,11 +0,0 @@
export const html = (inviteUuid: string) => `<p>Hello,</p>
<p>We are happy to inform that you have been invited to a shared workspace in Standard Notes.</p>
<p>
Please
<a href='https://app.standardnotes.com/?accept_workspace_invite=${inviteUuid}'>accept the invitation</a>
to see the shared content.
</p>
<p>
Thanks,
<br>SN</br>
</p>`

View file

@ -1,72 +0,0 @@
import {
DomainEventService,
EmailRequestedEvent,
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
} 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) {}
createWorkspaceInviteAcceptedEvent(dto: {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}): WorkspaceInviteAcceptedEvent {
return {
type: 'WORKSPACE_INVITE_ACCEPTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.inviteeUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Workspace,
},
payload: dto,
}
}
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent {
return {
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Workspace,
},
payload: dto,
}
}
createEmailRequestedEvent(dto: {
userEmail: string
messageIdentifier: string
level: string
body: string
subject: string
}): EmailRequestedEvent {
return {
type: 'EMAIL_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userEmail,
userIdentifierType: 'email',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
}

View file

@ -1,22 +0,0 @@
import { JSONString } from '@standardnotes/common'
import {
EmailRequestedEvent,
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
} from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createEmailRequestedEvent(dto: {
userEmail: string
messageIdentifier: string
level: string
body: string
subject: string
}): EmailRequestedEvent
createWorkspaceInviteAcceptedEvent(dto: {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}): WorkspaceInviteAcceptedEvent
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
}

View file

@ -1,44 +0,0 @@
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

@ -1,22 +0,0 @@
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

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

View file

@ -1,6 +0,0 @@
import { WorkspaceInvite } from './WorkspaceInvite'
export interface WorkspaceInviteRepositoryInterface {
findOneByUuid(uuid: string): Promise<WorkspaceInvite | null>
save(workspace: WorkspaceInvite): Promise<WorkspaceInvite>
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,106 +0,0 @@
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'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import {
DomainEventPublisherInterface,
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
} from '@standardnotes/domain-events'
describe('AcceptInvitation', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let timer: TimerInterface
let invite: WorkspaceInvite
const createUseCase = () =>
new AcceptInvitation(
workspaceInviteRepository,
workspaceUserRepository,
domainEventFactory,
domainEventPublisher,
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)
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createWebSocketMessageRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
domainEventFactory.createWorkspaceInviteAcceptedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WorkspaceInviteAcceptedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
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',
createdAt: 1,
updatedAt: 1,
})
})
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

@ -1,72 +0,0 @@
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'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
@injectable()
export class AcceptInvitation implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@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,
}
}
const timestamp = this.timer.getTimestampInMicroseconds()
invite.acceptingUserUuid = dto.acceptingUserUuid
invite.updatedAt = timestamp
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
workspaceUser.createdAt = timestamp
workspaceUser.updatedAt = timestamp
await this.workspaceUserRepository.save(workspaceUser)
const event = this.domainEventFactory.createWorkspaceInviteAcceptedEvent({
inviteeUuid: invite.acceptingUserUuid,
inviterUuid: invite.inviterUuid,
workspaceUuid: invite.workspaceUuid,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createWebSocketMessageRequestedEvent({
userUuid: invite.inviterUuid,
message: JSON.stringify(event),
}),
)
return {
success: true,
}
}
}

View file

@ -1,6 +0,0 @@
export type AcceptInvitationDTO = {
invitationUuid: string
acceptingUserUuid: string
publicKey: string
encryptedPrivateKey: string
}

View file

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

View file

@ -1,83 +0,0 @@
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

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

View file

@ -1,10 +0,0 @@
import { WorkspaceType } from '@standardnotes/common'
export type CreateWorkspaceDTO = {
ownerUuid: string
type: WorkspaceType
encryptedWorkspaceKey?: string
encryptedPrivateKey?: string
publicKey?: string
name?: string
}

View file

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

View file

@ -1,100 +0,0 @@
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()
})
})

View file

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

View file

@ -1,6 +0,0 @@
export type InitiateKeyShareDTO = {
workspaceUuid: string
userUuid: string
performingUserUuid: string
encryptedWorkspaceKey: string
}

View file

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

View file

@ -1,72 +0,0 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { InviteToWorkspace } from './InviteToWorkspace'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface, EmailRequestedEvent } 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.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
})
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()
})
})

View file

@ -1,54 +0,0 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { EmailLevel } from '@standardnotes/domain-core'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { getBody, getSubject } from '../../Email/WorkspaceInviteCreated'
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.createEmailRequestedEvent({
body: getBody(invite.uuid),
subject: getSubject(),
level: EmailLevel.LEVELS.System,
messageIdentifier: 'WORKSPACE_INVITE_CREATED',
userEmail: dto.inviteeEmail,
}),
)
return {
invite,
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +0,0 @@
export type ListWorkspaceUsersDTO = {
workspaceUuid: string
userUuid: string
}

View file

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

View file

@ -1,66 +0,0 @@
import 'reflect-metadata'
import { WorkspaceAccessLevel } from '@standardnotes/common'
import { Logger } from 'winston'
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
let logger: Logger
const createUseCase = () => new ListWorkspaces(workspaceRepository, workspaceUserRepository, logger)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
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],
})
})
it('should list empty owned and joined workspaces for a user that does not have any', async () => {
workspaceUserRepository.findByUserUuid = jest.fn().mockReturnValue([])
const result = await createUseCase().execute({
userUuid: 'u-1-2-3',
})
expect(result).toEqual({
ownedWorkspaces: [],
joinedWorkspaces: [],
})
})
})

View file

@ -1,54 +0,0 @@
import { WorkspaceAccessLevel } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../../Bootstrap/Types'
import { Workspace } from '../../Workspace/Workspace'
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,
@inject(TYPES.Logger) private logger: Logger,
) {}
async execute(dto: ListWorkspacesDTO): Promise<ListWorkspacesResponse> {
this.logger.debug(`Listing workspaces for user ${dto.userUuid}`)
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.workspaceUuid)
} else {
joinedWorkspacesUuids.push(workspaceAssociation.workspaceUuid)
}
}
this.logger.debug(`Owned workspaces uuids: ${JSON.stringify(ownedWorkspacesUuids)}`)
this.logger.debug(`Joined workspaces uuids: ${JSON.stringify(joinedWorkspacesUuids)}`)
let ownedWorkspaces: Array<Workspace> = []
if (ownedWorkspacesUuids.length > 0) {
ownedWorkspaces = await this.workspaceRepository.findByUuids(ownedWorkspacesUuids)
}
let joinedWorkspaces: Array<Workspace> = []
if (joinedWorkspacesUuids.length > 0) {
joinedWorkspaces = await this.workspaceRepository.findByUuids(joinedWorkspacesUuids)
}
return {
ownedWorkspaces,
joinedWorkspaces,
}
}
}

View file

@ -1,3 +0,0 @@
export type ListWorkspacesDTO = {
userUuid: string
}

View file

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

View file

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

View file

@ -1,56 +0,0 @@
import { WorkspaceType } from '@standardnotes/common'
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import { WorkspaceInvite } from '../Invite/WorkspaceInvite'
import { WorkspaceUser } from './WorkspaceUser'
@Entity({ name: 'workspaces' })
export class Workspace {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
length: 64,
})
declare type: WorkspaceType
@Column({
length: 255,
nullable: true,
type: 'varchar',
})
declare name: string | null
@Column({
name: 'key_rotation_index',
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[]>
@OneToMany(
/* istanbul ignore next */
() => WorkspaceUser,
/* istanbul ignore next */
(workspaceUser) => workspaceUser.workspace,
)
declare users: Promise<WorkspaceUser[]>
}

View file

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

View file

@ -1,98 +0,0 @@
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
import { Workspace } from './Workspace'
@Entity({ name: 'workspace_users' })
@Index('index_workspace_users_on_workspace_and_user', ['userUuid', 'workspaceUuid'], { unique: true })
export class WorkspaceUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'access_level',
length: 64,
})
declare accessLevel: WorkspaceAccessLevel
@Column({
name: 'user_uuid',
length: 36,
})
declare userUuid: string
@Column({
name: 'user_display_name',
type: 'varchar',
length: 255,
nullable: true,
})
declare userDisplayName: string | null
@Column({
name: 'workspace_uuid',
length: 36,
})
declare workspaceUuid: string
@Column({
name: 'encrypted_workspace_key',
length: 255,
type: 'varchar',
nullable: true,
})
declare encryptedWorkspaceKey: string | null
@Column({
name: 'public_key',
length: 255,
type: 'varchar',
nullable: true,
})
declare publicKey: string | null
@Column({
name: 'encrypted_private_key',
length: 255,
type: 'varchar',
nullable: true,
})
declare encryptedPrivateKey: string | null
@Column({
name: 'status',
length: 64,
})
declare status: WorkspaceUserStatus
@Column({
name: 'key_rotation_index',
default: 0,
})
declare keyRotationIndex: number
@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.users,
/* istanbul ignore next */
{ onDelete: 'CASCADE' },
)
@JoinColumn(
/* istanbul ignore next */
{ name: 'workspace_uuid' },
)
declare workspace: Promise<Workspace>
}

View file

@ -1,8 +0,0 @@
import { WorkspaceUser } from './WorkspaceUser'
export interface WorkspaceUserRepositoryInterface {
save(workspace: WorkspaceUser): Promise<WorkspaceUser>
findByUserUuid(userUuid: string): Promise<WorkspaceUser[]>
findByWorkspaceUuid(workspaceUuid: string): Promise<WorkspaceUser[]>
findOneByUserUuidAndWorkspaceUuid(dto: { workspaceUuid: string; userUuid: string }): Promise<WorkspaceUser | null>
}

View file

@ -1,9 +0,0 @@
import { controller, httpGet } from 'inversify-express-utils'
@controller('/healthcheck')
export class InversifyExpressHealthCheckController {
@httpGet('/')
public async get(): Promise<string> {
return 'OK'
}
}

View file

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

View file

@ -1,74 +0,0 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, 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)
}
@httpGet('/')
async listWorkspaces(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.listWorkspaces({
userUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
@httpGet('/:workspaceUuid/users')
async listWorkspaceUsers(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.listWorkspaceUsers({
userUuid: response.locals.user.uuid,
workspaceUuid: request.params.workspaceUuid,
})
return this.json(result.data, result.status)
}
@httpPost('/:workspaceUuid/users/:userUuid/keyshare')
async initiateKeyshare(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.initiateKeyshare({
userUuid: request.params.userUuid,
workspaceUuid: request.params.workspaceUuid,
encryptedWorkspaceKey: request.body.encryptedWorkspaceKey,
performingUserUuid: 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

@ -1,39 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,49 +0,0 @@
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)
})
it('should find many by uuids', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
await createRepository().findByUuids(['i-1-2-3'])
expect(queryBuilder.where).toHaveBeenCalledWith('uuid IN (:...uuids)', { uuids: ['i-1-2-3'] })
})
it('should find one by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' })
})
})

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