diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts index d3fb6acc9..8f1ff5681 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiOperations.ts @@ -2,4 +2,5 @@ export enum WorkspaceApiOperations { Creating, Inviting, Accepting, + ListingWorkspaces, } diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts index e21301cd1..7ded2d50d 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.spec.ts @@ -1,7 +1,9 @@ import { WorkspaceType } from '@standardnotes/common' +import { HttpStatusCode } from '../../Http' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface' import { WorkspaceApiOperations } from './WorkspaceApiOperations' @@ -23,6 +25,10 @@ describe('WorkspaceApiService', () => { workspaceServer.acceptInvite = jest.fn().mockReturnValue({ data: { success: true }, } as jest.Mocked) + workspaceServer.listWorkspaces = jest.fn().mockReturnValue({ + status: HttpStatusCode.Success, + data: { ownedWorkspaces: [], joinedWorkspaces: [] }, + } as jest.Mocked) }) it('should create a workspace', async () => { @@ -202,4 +208,48 @@ describe('WorkspaceApiService', () => { expect(error).not.toBeNull() }) + + it('should list workspaces', async () => { + const response = await createService().listWorkspaces() + + expect(response).toEqual({ + status: 200, + data: { + ownedWorkspaces: [], + joinedWorkspaces: [], + }, + }) + expect(workspaceServer.listWorkspaces).toHaveBeenCalled() + }) + + it('should not list workspaces if it is already listing them', async () => { + const service = createService() + Object.defineProperty(service, 'operationsInProgress', { + get: () => new Map([[WorkspaceApiOperations.ListingWorkspaces, true]]), + }) + + let error = null + try { + await service.listWorkspaces() + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should not list workspaces if the server fails', async () => { + workspaceServer.listWorkspaces = jest.fn().mockImplementation(() => { + throw new Error('Oops') + }) + + let error = null + try { + await createService().listWorkspaces() + } 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 cef2ffa2e..caef6b4cd 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiService.ts @@ -6,6 +6,7 @@ import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCre import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' import { WorkspaceApiServiceInterface } from './WorkspaceApiServiceInterface' import { WorkspaceApiOperations } from './WorkspaceApiOperations' @@ -17,6 +18,20 @@ export class WorkspaceApiService implements WorkspaceApiServiceInterface { this.operationsInProgress = new Map() } + async listWorkspaces(): Promise { + this.lockOperation(WorkspaceApiOperations.ListingWorkspaces) + + try { + const response = await this.workspaceServer.listWorkspaces({}) + + this.unlockOperation(WorkspaceApiOperations.ListingWorkspaces) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } + } + async acceptInvite(dto: { inviteUuid: string userUuid: string diff --git a/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts b/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts index ff7cbc52d..77312b7a5 100644 --- a/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts +++ b/packages/api/src/Domain/Client/Workspace/WorkspaceApiServiceInterface.ts @@ -3,6 +3,7 @@ import { Uuid, WorkspaceType } from '@standardnotes/common' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' export interface WorkspaceApiServiceInterface { createWorkspace(dto: { @@ -19,4 +20,5 @@ export interface WorkspaceApiServiceInterface { publicKey: string encryptedPrivateKey: string }): Promise + listWorkspaces(): Promise } diff --git a/packages/api/src/Domain/Request/Workspace/WorkspaceListRequestParams.ts b/packages/api/src/Domain/Request/Workspace/WorkspaceListRequestParams.ts new file mode 100644 index 000000000..f3bf4a4d7 --- /dev/null +++ b/packages/api/src/Domain/Request/Workspace/WorkspaceListRequestParams.ts @@ -0,0 +1,3 @@ +export type WorkspaceListRequestParams = { + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/index.ts b/packages/api/src/Domain/Request/index.ts index b079056ea..6819f74c2 100644 --- a/packages/api/src/Domain/Request/index.ts +++ b/packages/api/src/Domain/Request/index.ts @@ -9,3 +9,4 @@ export * from './WebSocket/WebSocketConnectionTokenRequestParams' export * from './Workspace/WorkspaceCreationRequestParams' export * from './Workspace/WorkspaceInvitationAcceptingRequestParams' export * from './Workspace/WorkspaceInvitationRequestParams' +export * from './Workspace/WorkspaceListRequestParams' diff --git a/packages/api/src/Domain/Response/Workspace/WorkspaceListResponse.ts b/packages/api/src/Domain/Response/Workspace/WorkspaceListResponse.ts new file mode 100644 index 000000000..dd2bcff25 --- /dev/null +++ b/packages/api/src/Domain/Response/Workspace/WorkspaceListResponse.ts @@ -0,0 +1,9 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { WorkspaceListResponseBody } from './WorkspaceListResponseBody' + +export interface WorkspaceListResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Workspace/WorkspaceListResponseBody.ts b/packages/api/src/Domain/Response/Workspace/WorkspaceListResponseBody.ts new file mode 100644 index 000000000..d48824e11 --- /dev/null +++ b/packages/api/src/Domain/Response/Workspace/WorkspaceListResponseBody.ts @@ -0,0 +1,6 @@ +import { Workspace } from '@standardnotes/models' + +export type WorkspaceListResponseBody = { + ownedWorkspaces: Array + joinedWorkspaces: Array +} diff --git a/packages/api/src/Domain/Response/index.ts b/packages/api/src/Domain/Response/index.ts index 8de8d117e..b00245892 100644 --- a/packages/api/src/Domain/Response/index.ts +++ b/packages/api/src/Domain/Response/index.ts @@ -18,3 +18,5 @@ export * from './Workspace/WorkspaceInvitationAcceptingResponse' export * from './Workspace/WorkspaceInvitationAcceptingResponseBody' export * from './Workspace/WorkspaceInvitationResponse' export * from './Workspace/WorkspaceInvitationResponseBody' +export * from './Workspace/WorkspaceListResponse' +export * from './Workspace/WorkspaceListResponseBody' diff --git a/packages/api/src/Domain/Server/Workspace/Paths.ts b/packages/api/src/Domain/Server/Workspace/Paths.ts index ca9b42d94..1da981566 100644 --- a/packages/api/src/Domain/Server/Workspace/Paths.ts +++ b/packages/api/src/Domain/Server/Workspace/Paths.ts @@ -2,6 +2,7 @@ import { Uuid } from '@standardnotes/common' const WorkspacePaths = { createWorkspace: '/v1/workspaces', + listWorkspaces: '/v1/workspaces', inviteToWorkspace: (uuid: Uuid) => `/v1/workspaces/${uuid}/invites`, acceptInvite: (uuid: Uuid) => `/v1/invites/${uuid}/accept`, } diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts index 6f12e023d..1ca14646c 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.spec.ts @@ -1,8 +1,9 @@ import { WorkspaceType } from '@standardnotes/common' -import { HttpServiceInterface } from '../../Http' +import { HttpServiceInterface, HttpStatusCode } from '../../Http' import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' import { WorkspaceServer } from './WorkspaceServer' @@ -68,4 +69,18 @@ describe('WorkspaceServer', () => { }, }) }) + + it('should list workspaces', async () => { + httpService.get = jest.fn().mockReturnValue({ + status: HttpStatusCode.Success, + data: { ownedWorkspaces: [], joinedWorkspaces: [] }, + } as jest.Mocked) + + const response = await createServer().listWorkspaces({}) + + expect(response).toEqual({ + status: 200, + data: { ownedWorkspaces: [], joinedWorkspaces: [] }, + }) + }) }) diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts index fc0afa013..a50ae1add 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServer.ts @@ -5,6 +5,8 @@ import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceI import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams' import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' +import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' import { Paths } from './Paths' import { WorkspaceServerInterface } from './WorkspaceServerInterface' @@ -12,6 +14,12 @@ import { WorkspaceServerInterface } from './WorkspaceServerInterface' export class WorkspaceServer implements WorkspaceServerInterface { constructor(private httpService: HttpServiceInterface) {} + async listWorkspaces(params: WorkspaceListRequestParams): Promise { + const response = await this.httpService.get(Paths.v1.listWorkspaces, params) + + return response as WorkspaceListResponse + } + async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise { const response = await this.httpService.post(Paths.v1.acceptInvite(params.inviteUuid), params) diff --git a/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts b/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts index f765de2c0..1cf263b90 100644 --- a/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts +++ b/packages/api/src/Domain/Server/Workspace/WorkspaceServerInterface.ts @@ -4,9 +4,12 @@ import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceI import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse' import { WorkspaceInvitationAcceptingRequestParams } from '../../Request/Workspace/WorkspaceInvitationAcceptingRequestParams' import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse' +import { WorkspaceListRequestParams } from '../../Request/Workspace/WorkspaceListRequestParams' +import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse' export interface WorkspaceServerInterface { createWorkspace(params: WorkspaceCreationRequestParams): Promise + listWorkspaces(params: WorkspaceListRequestParams): Promise inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise } diff --git a/packages/models/src/Domain/Api/Workspace/Workspace.ts b/packages/models/src/Domain/Api/Workspace/Workspace.ts new file mode 100644 index 000000000..47ec1fe2b --- /dev/null +++ b/packages/models/src/Domain/Api/Workspace/Workspace.ts @@ -0,0 +1,10 @@ +import { WorkspaceType } from '@standardnotes/common' + +export type Workspace = { + uuid: string + type: WorkspaceType + name: string | null + keyRotationIndex: number + createdAt: number + updatedAt: number +} diff --git a/packages/models/src/Domain/index.ts b/packages/models/src/Domain/index.ts index 48ee7bd51..de83ff3d9 100644 --- a/packages/models/src/Domain/index.ts +++ b/packages/models/src/Domain/index.ts @@ -28,6 +28,7 @@ export * from './Api/Subscription/Invitation' export * from './Api/Subscription/InvitationStatus' export * from './Api/Subscription/InviteeIdentifierType' export * from './Api/Subscription/InviterIdentifierType' +export * from './Api/Workspace/Workspace' export * from './Device/Environment' export * from './Device/Platform' export * from './Local/KeyParams/RootKeyParamsInterface' diff --git a/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts b/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts index 573c1bc44..7c4ec1a2c 100644 --- a/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts +++ b/packages/services/src/Domain/Workspace/WorkspaceClientInterface.ts @@ -1,4 +1,5 @@ import { Uuid, WorkspaceType } from '@standardnotes/common' +import { Workspace } from '@standardnotes/models' export interface WorkspaceClientInterface { createWorkspace(dto: { @@ -15,4 +16,5 @@ export interface WorkspaceClientInterface { publicKey: string encryptedPrivateKey: string }): Promise<{ success: boolean }> + listWorkspaces(): Promise<{ ownedWorkspaces: Array; joinedWorkspaces: Array }> } diff --git a/packages/services/src/Domain/Workspace/WorkspaceManager.ts b/packages/services/src/Domain/Workspace/WorkspaceManager.ts index d18504b14..f8f542e4c 100644 --- a/packages/services/src/Domain/Workspace/WorkspaceManager.ts +++ b/packages/services/src/Domain/Workspace/WorkspaceManager.ts @@ -1,5 +1,6 @@ import { WorkspaceApiServiceInterface } from '@standardnotes/api' import { Uuid, WorkspaceType } from '@standardnotes/common' +import { Workspace } from '@standardnotes/models' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { AbstractService } from '../Service/AbstractService' @@ -13,6 +14,20 @@ export class WorkspaceManager extends AbstractService implements WorkspaceClient super(internalEventBus) } + async listWorkspaces(): Promise<{ ownedWorkspaces: Workspace[]; joinedWorkspaces: Workspace[] }> { + try { + const result = await this.workspaceApiService.listWorkspaces() + + if (result.data.error !== undefined) { + return { ownedWorkspaces: [], joinedWorkspaces: [] } + } + + return result.data + } catch (error) { + return { ownedWorkspaces: [], joinedWorkspaces: [] } + } + } + async acceptInvite(dto: { inviteUuid: string userUuid: string