chore: remove workspaces from code base
This commit is contained in:
parent
9d872008a7
commit
deec29c1b4
107 changed files with 0 additions and 3863 deletions
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
|
@ -95,11 +95,6 @@ updates:
|
|||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/workspace"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
|
47
.github/workflows/workspace.yml
vendored
47
.github/workflows/workspace.yml
vendored
|
@ -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 }}"
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export enum WorkspaceAccessLevel {
|
||||
Owner = 'owner',
|
||||
Admin = 'admin',
|
||||
ReadOnly = 'read-only',
|
||||
WriteAndRead = 'write-and-read',
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export enum WorkspaceType {
|
||||
Root = 'root',
|
||||
Team = 'team',
|
||||
Private = 'private',
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export enum WorkspaceUserStatus {
|
||||
Active = 'active',
|
||||
PendingKeyshare = 'pending-keyshare',
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -8,7 +8,6 @@ export enum DomainEventService {
|
|||
ApiGateway = 'api-gateway',
|
||||
Files = 'files',
|
||||
Scheduler = 'scheduler',
|
||||
Workspace = 'workspace',
|
||||
Analytics = 'analytics',
|
||||
Revisions = 'revisions',
|
||||
Email = 'email',
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
import { WorkspaceInviteAcceptedEventPayload } from './WorkspaceInviteAcceptedEventPayload'
|
||||
|
||||
export interface WorkspaceInviteAcceptedEvent extends DomainEventInterface {
|
||||
type: 'WORKSPACE_INVITE_ACCEPTED'
|
||||
payload: WorkspaceInviteAcceptedEventPayload
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export interface WorkspaceInviteAcceptedEventPayload {
|
||||
inviterUuid: string
|
||||
inviteeUuid: string
|
||||
workspaceUuid: string
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
dist
|
||||
test-setup.ts
|
||||
data
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"extends": "../../.eslintrc",
|
||||
"parserOptions": {
|
||||
"project": "./linter.tsconfig.json"
|
||||
}
|
||||
}
|
|
@ -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))
|
|
@ -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" ]
|
|
@ -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}`)
|
||||
})
|
|
@ -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)
|
||||
})
|
|
@ -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
|
|
@ -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
|
|
@ -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 "$@"
|
|
@ -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'],
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["dist", "test-setup.ts"]
|
||||
}
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
})
|
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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>`
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import { WorkspaceInvite } from './WorkspaceInvite'
|
||||
|
||||
export interface WorkspaceInviteRepositoryInterface {
|
||||
findOneByUuid(uuid: string): Promise<WorkspaceInvite | null>
|
||||
save(workspace: WorkspaceInvite): Promise<WorkspaceInvite>
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export enum WorkspaceInviteStatus {
|
||||
Created = 'created',
|
||||
Accepted = 'accepted',
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export interface ProjectorInterface<T, E> {
|
||||
project(object: T): Promise<E>
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import { Workspace } from '@standardnotes/api'
|
||||
|
||||
export type WorkspaceProjection = Workspace
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import { WorkspaceUser } from '@standardnotes/api'
|
||||
|
||||
export type WorkspaceUserProjection = WorkspaceUser
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export type AcceptInvitationDTO = {
|
||||
invitationUuid: string
|
||||
acceptingUserUuid: string
|
||||
publicKey: string
|
||||
encryptedPrivateKey: string
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type AcceptInvitationResponse = {
|
||||
success: boolean
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import { WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
export type CreateWorkspaceDTO = {
|
||||
ownerUuid: string
|
||||
type: WorkspaceType
|
||||
encryptedWorkspaceKey?: string
|
||||
encryptedPrivateKey?: string
|
||||
publicKey?: string
|
||||
name?: string
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { Workspace } from '../../Workspace/Workspace'
|
||||
|
||||
export type CreateWorkspaceResponse = {
|
||||
workspace: Workspace
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export type InitiateKeyShareDTO = {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
performingUserUuid: string
|
||||
encryptedWorkspaceKey: string
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type InitiateKeyShareResponse = {
|
||||
success: boolean
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { WorkspaceAccessLevel } from '@standardnotes/common'
|
||||
|
||||
export type InviteToWorkspaceDTO = {
|
||||
workspaceUuid: string
|
||||
inviterUuid: string
|
||||
inviteeEmail: string
|
||||
accessLevel: WorkspaceAccessLevel
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
|
||||
|
||||
export type InviteToWorkspaceResponse = {
|
||||
invite: WorkspaceInvite
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export type ListWorkspaceUsersDTO = {
|
||||
workspaceUuid: string
|
||||
userUuid: string
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
|
||||
|
||||
export type ListWorkspaceUsersResponse = {
|
||||
workspaceUsers: WorkspaceUser[]
|
||||
userIsOwnerOrAdmin: boolean
|
||||
}
|
|
@ -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: [],
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type ListWorkspacesDTO = {
|
||||
userUuid: string
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import { Workspace } from '../../Workspace/Workspace'
|
||||
|
||||
export type ListWorkspacesResponse = {
|
||||
ownedWorkspaces: Array<Workspace>
|
||||
joinedWorkspaces: Array<Workspace>
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export interface UseCaseInterface {
|
||||
execute(...args: any[]): Promise<Record<string, unknown>>
|
||||
}
|
|
@ -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[]>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { controller, httpGet } from 'inversify-express-utils'
|
||||
|
||||
@controller('/healthcheck')
|
||||
export class InversifyExpressHealthCheckController {
|
||||
@httpGet('/')
|
||||
public async get(): Promise<string> {
|
||||
return 'OK'
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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' })
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue