From 158ca6ac6a428e082c8f984791f80fcaadcb3e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 10 Oct 2022 11:57:44 +0200 Subject: [PATCH] feat(api): add inviting to workspace --- .../Workspace/WorkspaceApiOperations.ts | 1 + .../Workspace/WorkspaceApiService.spec.ts | 58 +++++++++++++++++++ .../Client/Workspace/WorkspaceApiService.ts | 25 +++++++- .../Workspace/WorkspaceApiServiceInterface.ts | 8 ++- .../WorkspaceInvitationRequestParams.ts | 7 +++ packages/api/src/Domain/Request/index.ts | 1 + .../Workspace/WorkspaceInvitationResponse.ts | 9 +++ .../WorkspaceInvitationResponseBody.ts | 3 + packages/api/src/Domain/Response/index.ts | 2 + .../api/src/Domain/Server/Workspace/Paths.ts | 3 + .../Server/Workspace/WorkspaceServer.spec.ts | 20 +++++++ .../Server/Workspace/WorkspaceServer.ts | 8 +++ .../Workspace/WorkspaceServerInterface.ts | 3 + .../Workspace/WorkspaceClientInterface.ts | 6 +- .../src/Domain/Workspace/WorkspaceManager.ts | 16 ++++- 15 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 packages/api/src/Domain/Request/Workspace/WorkspaceInvitationRequestParams.ts create mode 100644 packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponse.ts create mode 100644 packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponseBody.ts diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts index ac5046560..3f100c4d7 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts @@ -1,3 +1,4 @@ export enum WorkspaceApiOperations { Creating, + Inviting, } diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts index d8dd50940..2907439be 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts @@ -1,4 +1,5 @@ import { WorkspaceType } from '@standardnotes/common' +import { WorkspaceInvitationResponse } from '../../Response' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface' @@ -15,6 +16,9 @@ describe('WorkspaceApiService', () => { workspaceServer.createWorkspace = jest.fn().mockReturnValue({ data: { uuid: '1-2-3' }, } as jest.Mocked) + workspaceServer.inviteToWorkspace = jest.fn().mockReturnValue({ + data: { uuid: 'i-1-2-3' }, + } as jest.Mocked) }) it('should create a workspace', async () => { @@ -77,4 +81,58 @@ describe('WorkspaceApiService', () => { expect(error).not.toBeNull() }) + + it('should invite to a workspace', async () => { + const response = await createService().inviteToWorkspace({ + workspaceUuid: 'w-1-2-3', + inviteeEmail: 'test@test.te' + }) + + expect(response).toEqual({ + data: { + uuid: 'i-1-2-3', + }, + }) + expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({ + workspaceUuid: 'w-1-2-3', + inviteeEmail: 'test@test.te' + }) + }) + + it('should not invite to a workspace if it is already inviting', async () => { + const service = createService() + Object.defineProperty(service, 'operationsInProgress', { + get: () => new Map([[WorkspaceApiOperations.Inviting, true]]), + }) + + let error = null + try { + await service.inviteToWorkspace({ + workspaceUuid: 'w-1-2-3', + inviteeEmail: 'test@test.te' + }) + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should not invite to a workspace if the server fails', async () => { + workspaceServer.inviteToWorkspace = jest.fn().mockImplementation(() => { + throw new Error('Oops') + }) + + let error = null + try { + await createService().inviteToWorkspace({ + workspaceUuid: 'w-1-2-3', + inviteeEmail: 'test@test.te' + }) + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) }) diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts index c96b21e8a..08d1644dd 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts @@ -1,3 +1,5 @@ +import { Uuid, WorkspaceType } from '@standardnotes/common' + import { ErrorMessage } from '../../Error/ErrorMessage' import { ApiCallError } from '../../Error/ApiCallError' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' @@ -6,7 +8,7 @@ import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServer import { WorkspaceApiServiceInterface } from './WorkspaceApiServiceInterface' import { WorkspaceApiOperations } from './WorkspaceApiOperations' -import { WorkspaceType } from '@standardnotes/common' +import { WorkspaceInvitationResponse } from '../../Response' export class WorkspaceApiService implements WorkspaceApiServiceInterface { private operationsInProgress: Map @@ -15,6 +17,27 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface { this.operationsInProgress = new Map() } + async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise { + if (this.operationsInProgress.get(WorkspaceApiOperations.Inviting)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(WorkspaceApiOperations.Inviting, true) + + try { + const response = await this.workspaceServer.inviteToWorkspace({ + inviteeEmail: dto.inviteeEmail, + workspaceUuid: dto.workspaceUuid, + }) + + this.operationsInProgress.set(WorkspaceApiOperations.Inviting, false) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } + } + async createWorkspace(dto: { workspaceType: WorkspaceType, encryptedWorkspaceKey?: string diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts index ef598dc5f..712d46e5b 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts @@ -1,6 +1,6 @@ -import { WorkspaceType } from '@standardnotes/common' +import { Uuid, WorkspaceType } from '@standardnotes/common' -import { WorkspaceCreationResponse } from '../../Response' +import { WorkspaceCreationResponse, WorkspaceInvitationResponse } from '../../Response' export interface WorkspaceApiServiceInterface { createWorkspace(dto: { @@ -10,4 +10,8 @@ export interface WorkspaceApiServiceInterface { publicKey?: string workspaceName?: string }): Promise + inviteToWorkspace(dto: { + inviteeEmail: string + workspaceUuid: Uuid + }): Promise } diff --git a/packages/api/src/Domain/Request/Workspace/WorkspaceInvitationRequestParams.ts b/packages/api/src/Domain/Request/Workspace/WorkspaceInvitationRequestParams.ts new file mode 100644 index 000000000..04be64472 --- /dev/null +++ b/packages/api/src/Domain/Request/Workspace/WorkspaceInvitationRequestParams.ts @@ -0,0 +1,7 @@ +import { Uuid } from "@standardnotes/common" + +export type WorkspaceInvitationRequestParams = { + workspaceUuid: Uuid + inviteeEmail: string + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/index.ts b/packages/api/src/Domain/Request/index.ts index db5d37593..253463f0e 100644 --- a/packages/api/src/Domain/Request/index.ts +++ b/packages/api/src/Domain/Request/index.ts @@ -7,3 +7,4 @@ export * from './Subscription/SubscriptionInviteRequestParams' export * from './User/UserRegistrationRequestParams' export * from './WebSocket/WebSocketConnectionTokenRequestParams' export * from './Workspace/WorkspaceCreationRequestParams' +export * from './Workspace/WorkspaceInvitationRequestParams' diff --git a/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponse.ts b/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponse.ts new file mode 100644 index 000000000..5a50ccd96 --- /dev/null +++ b/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponse.ts @@ -0,0 +1,9 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { WorkspaceInvitationResponseBody } from './WorkspaceInvitationResponseBody' + +export interface WorkspaceInvitationResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponseBody.ts b/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponseBody.ts new file mode 100644 index 000000000..a66bbe545 --- /dev/null +++ b/packages/api/src/Domain/Response/Workspace/WorkspaceInvitationResponseBody.ts @@ -0,0 +1,3 @@ +export type WorkspaceInvitationResponseBody = { + uuid: string +} diff --git a/packages/api/src/Domain/Response/index.ts b/packages/api/src/Domain/Response/index.ts index 56b4b28a7..f2b3dbbee 100644 --- a/packages/api/src/Domain/Response/index.ts +++ b/packages/api/src/Domain/Response/index.ts @@ -14,3 +14,5 @@ export * from './WebSocket/WebSocketConnectionTokenResponse' export * from './WebSocket/WebSocketConnectionTokenResponseBody' export * from './Workspace/WorkspaceCreationResponse' export * from './Workspace/WorkspaceCreationResponseBody' +export * from './Workspace/WorkspaceInvitationResponse' +export * from './Workspace/WorkspaceInvitationResponseBody' diff --git a/packages/api/src/Domain/Server/Workspace/Paths.ts b/packages/api/src/Domain/Server/Workspace/Paths.ts index 906a9c12a..15314dc30 100644 --- a/packages/api/src/Domain/Server/Workspace/Paths.ts +++ b/packages/api/src/Domain/Server/Workspace/Paths.ts @@ -1,5 +1,8 @@ +import { Uuid } from '@standardnotes/common' + const WorkspacePaths = { createWorkspace: '/v1/workspaces', + inviteToWorkspace: (uuid: Uuid) => `/v1/workspaces/${uuid}/invites`, } export const Paths = { diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts index f203aeda9..c188fdf23 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts @@ -1,5 +1,7 @@ +import { WorkspaceType } from '@standardnotes/common' import { HttpServiceInterface } from '../../Http' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' +import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' import { WorkspaceServer } from './WorkspaceServer' @@ -17,6 +19,7 @@ describe('WorkspaceServer', () => { it('should create a workspace', async () => { const response = await createServer().createWorkspace({ + workspaceType: WorkspaceType.Private, encryptedPrivateKey: 'foo', encryptedWorkspaceKey: 'bar', publicKey: 'buzz', @@ -28,4 +31,21 @@ describe('WorkspaceServer', () => { }, }) }) + + it('should inivte to a workspace', async () => { + httpService.post = jest.fn().mockReturnValue({ + data: { uuid: 'i-1-2-3' }, + } as jest.Mocked) + + const response = await createServer().inviteToWorkspace({ + inviteeEmail: 'test@test.te', + workspaceUuid: 'w-1-2-3', + }) + + expect(response).toEqual({ + data: { + uuid: 'i-1-2-3', + }, + }) + }) }) diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts index d46f07954..0e9dcb1ae 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts @@ -1,5 +1,7 @@ import { HttpServiceInterface } from '../../Http/HttpServiceInterface' +import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/WorkspaceInvitationRequestParams' import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams' +import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { Paths } from './Paths' @@ -8,6 +10,12 @@ import { WorkspaceServerInterface } from './WorkspaceServerInterface' export class WorkspaceServer implements WorkspaceServerInterface { constructor(private httpService: HttpServiceInterface) {} + async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise { + const response = await this.httpService.post(Paths.v1.inviteToWorkspace(params.workspaceUuid), params) + + return response as WorkspaceInvitationResponse + } + async createWorkspace(params: WorkspaceCreationRequestParams): Promise { const response = await this.httpService.post(Paths.v1.createWorkspace, params) diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts index 428eb0583..d29e44373 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts @@ -1,6 +1,9 @@ +import { WorkspaceInvitationRequestParams } from '../../Request/Workspace/WorkspaceInvitationRequestParams' import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams' +import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' export interface WorkspaceServerInterface { createWorkspace(params: WorkspaceCreationRequestParams): Promise + inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise } diff --git a/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts b/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts index 3ff2b8c76..b784c801f 100644 --- a/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts +++ b/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts @@ -1,4 +1,4 @@ -import { WorkspaceType } from '@standardnotes/common' +import { Uuid, WorkspaceType } from '@standardnotes/common' export interface WorkspaceClientInterface { createWorkspace(dto: { @@ -8,4 +8,8 @@ export interface WorkspaceClientInterface { publicKey?: string workspaceName?: string }): Promise<{ uuid: string } | null> + inviteToWorkspace(dto: { + inviteeEmail: string + workspaceUuid: Uuid + }): Promise<{ uuid: string } | null> } diff --git a/packages/services/src/Domain/Workspace/WorkspaceManager.ts b/packages/services/src/Domain/Workspace/WorkspaceManager.ts index 6889476b7..cc6ee42e9 100644 --- a/packages/services/src/Domain/Workspace/WorkspaceManager.ts +++ b/packages/services/src/Domain/Workspace/WorkspaceManager.ts @@ -1,5 +1,5 @@ import { WorkspaceApiServiceInterface } from '@standardnotes/api' -import { WorkspaceType } from '@standardnotes/common' +import { Uuid, WorkspaceType } from '@standardnotes/common' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { AbstractService } from '../Service/AbstractService' @@ -13,6 +13,20 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient super(internalEventBus) } + async inviteToWorkspace(dto: { inviteeEmail: string; workspaceUuid: Uuid }): Promise<{ uuid: string } | null> { + try { + const result = await this.workspaceApiService.inviteToWorkspace(dto) + + if (result.data.error !== undefined) { + return null + } + + return result.data + } catch (error) { + return null + } + } + async createWorkspace(dto: { workspaceType: WorkspaceType, encryptedWorkspaceKey?: string