diff --git a/.yarn/cache/@eslint-eslintrc-npm-1.3.1-f9e7aea43c-9844dcc58a.zip b/.yarn/cache/@eslint-eslintrc-npm-1.3.1-f9e7aea43c-9844dcc58a.zip new file mode 100644 index 000000000..8bfc979a3 Binary files /dev/null and b/.yarn/cache/@eslint-eslintrc-npm-1.3.1-f9e7aea43c-9844dcc58a.zip differ diff --git a/.yarn/cache/@humanwhocodes-module-importer-npm-1.0.1-9d07ed2e4a-0fd22007db.zip b/.yarn/cache/@humanwhocodes-module-importer-npm-1.0.1-9d07ed2e4a-0fd22007db.zip new file mode 100644 index 000000000..7adb1e9f2 Binary files /dev/null and b/.yarn/cache/@humanwhocodes-module-importer-npm-1.0.1-9d07ed2e4a-0fd22007db.zip differ diff --git a/.yarn/cache/eslint-npm-8.23.0-4ce1a8504a-ff6075daa2.zip b/.yarn/cache/eslint-npm-8.23.0-4ce1a8504a-ff6075daa2.zip new file mode 100644 index 000000000..bbe836d3d Binary files /dev/null and b/.yarn/cache/eslint-npm-8.23.0-4ce1a8504a-ff6075daa2.zip differ diff --git a/.yarn/cache/espree-npm-9.4.0-0371ef3614-2e3020dde6.zip b/.yarn/cache/espree-npm-9.4.0-0371ef3614-2e3020dde6.zip new file mode 100644 index 000000000..95a79f462 Binary files /dev/null and b/.yarn/cache/espree-npm-9.4.0-0371ef3614-2e3020dde6.zip differ diff --git a/packages/api/package.json b/packages/api/package.json index 36c475f80..0cb5192fe 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -22,12 +22,14 @@ "prebuild": "yarn clean", "build": "tsc -p tsconfig.json", "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", "test": "jest spec --coverage" }, "devDependencies": { "@types/jest": "^28.1.5", "@types/lodash": "^4.14.182", "@typescript-eslint/eslint-plugin": "^5.30.0", + "eslint": "^8.23.0", "eslint-plugin-prettier": "*", "jest": "^28.1.2", "ts-jest": "^28.0.5" diff --git a/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.spec.ts b/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.spec.ts new file mode 100644 index 000000000..1467f92de --- /dev/null +++ b/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.spec.ts @@ -0,0 +1,63 @@ +import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse' +import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface' + +import { SubscriptionApiService } from './SubscriptionApiService' + +describe('SubscriptionApiService', () => { + let subscriptionServer: SubscriptionServerInterface + + const createService = () => new SubscriptionApiService(subscriptionServer) + + beforeEach(() => { + subscriptionServer = {} as jest.Mocked + subscriptionServer.invite = jest.fn().mockReturnValue({ + data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' }, + } as jest.Mocked) + }) + + it('should invite a user', async () => { + const response = await createService().invite('test@test.te') + + expect(response).toEqual({ + data: { + success: true, + sharedSubscriptionInvitationUuid: '1-2-3', + }, + }) + expect(subscriptionServer.invite).toHaveBeenCalledWith({ + api: '20200115', + identifier: 'test@test.te', + }) + }) + + it('should not invite a user if it is already inviting', async () => { + const service = createService() + Object.defineProperty(service, 'inviting', { + get: () => true, + }) + + let error = null + try { + await service.invite('test@test.te') + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should not invite a user if the server fails', async () => { + subscriptionServer.invite = jest.fn().mockImplementation(() => { + throw new Error('Oops') + }) + + let error = null + try { + await createService().invite('test@test.te') + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) +}) diff --git a/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.ts b/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.ts new file mode 100644 index 000000000..d8abf0152 --- /dev/null +++ b/packages/api/src/Domain/Client/Subscription/SubscriptionApiService.ts @@ -0,0 +1,35 @@ +import { ErrorMessage } from '../../Error/ErrorMessage' +import { ApiCallError } from '../../Error/ApiCallError' +import { ApiVersion } from '../../Api/ApiVersion' +import { ApiEndpointParam } from '../../Request/ApiEndpointParam' +import { SubscriptionApiServiceInterface } from './SubscriptionApiServiceInterface' +import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface' +import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse' + +export class SubscriptionApiService implements SubscriptionApiServiceInterface { + private inviting: boolean + + constructor(private subscriptionServer: SubscriptionServerInterface) { + this.inviting = false + } + + async invite(inviteeEmail: string): Promise { + if (this.inviting) { + throw new ApiCallError(ErrorMessage.InvitingInProgress) + } + this.inviting = true + + try { + const response = await this.subscriptionServer.invite({ + [ApiEndpointParam.ApiVersion]: ApiVersion.v0, + identifier: inviteeEmail, + }) + + this.inviting = false + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } + } +} diff --git a/packages/api/src/Domain/Client/Subscription/SubscriptionApiServiceInterface.ts b/packages/api/src/Domain/Client/Subscription/SubscriptionApiServiceInterface.ts new file mode 100644 index 000000000..32ba7b76b --- /dev/null +++ b/packages/api/src/Domain/Client/Subscription/SubscriptionApiServiceInterface.ts @@ -0,0 +1,5 @@ +import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse' + +export interface SubscriptionApiServiceInterface { + invite(inviteeEmail: string): Promise +} diff --git a/packages/api/src/Domain/Client/index.ts b/packages/api/src/Domain/Client/index.ts index 8b0797259..f79730600 100644 --- a/packages/api/src/Domain/Client/index.ts +++ b/packages/api/src/Domain/Client/index.ts @@ -1,2 +1,4 @@ +export * from './Subscription/SubscriptionApiService' +export * from './Subscription/SubscriptionApiServiceInterface' export * from './User/UserApiService' export * from './User/UserApiServiceInterface' diff --git a/packages/api/src/Domain/Error/ErrorMessage.ts b/packages/api/src/Domain/Error/ErrorMessage.ts index 292e98d2b..c11fcfbd9 100644 --- a/packages/api/src/Domain/Error/ErrorMessage.ts +++ b/packages/api/src/Domain/Error/ErrorMessage.ts @@ -1,7 +1,9 @@ export enum ErrorMessage { + InvitingInProgress = 'An existing invitation request is already in progress.', RegistrationInProgress = 'An existing registration request is already in progress.', GenericRegistrationFail = 'A server error occurred while trying to register. Please try again.', RateLimited = 'Too many successive server requests. Please wait a few minutes and try again.', InsufficientPasswordMessage = 'Your password must be at least %LENGTH% characters in length. For your security, please choose a longer password or, ideally, a passphrase, and try again.', PasscodeRequired = 'Your passcode is required in order to register for an account.', + GenericFail = 'A server error occurred. Please try again.', } diff --git a/packages/api/src/Domain/Request/Subscription/SubscriptionInviteAcceptRequestParams.ts b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteAcceptRequestParams.ts new file mode 100644 index 000000000..975efc490 --- /dev/null +++ b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteAcceptRequestParams.ts @@ -0,0 +1,10 @@ +import { Uuid } from '@standardnotes/common' + +import { ApiEndpointParam } from '../ApiEndpointParam' +import { ApiVersion } from '../../Api/ApiVersion' + +export type SubscriptionInviteAcceptRequestParams = { + [ApiEndpointParam.ApiVersion]: ApiVersion.v0 + inviteUuid: Uuid + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Subscription/SubscriptionInviteCancelRequestParams.ts b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteCancelRequestParams.ts new file mode 100644 index 000000000..f00b6912c --- /dev/null +++ b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteCancelRequestParams.ts @@ -0,0 +1,10 @@ +import { Uuid } from '@standardnotes/common' + +import { ApiEndpointParam } from '../ApiEndpointParam' +import { ApiVersion } from '../../Api/ApiVersion' + +export type SubscriptionInviteCancelRequestParams = { + [ApiEndpointParam.ApiVersion]: ApiVersion.v0 + inviteUuid: Uuid + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Subscription/SubscriptionInviteDeclineRequestParams.ts b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteDeclineRequestParams.ts new file mode 100644 index 000000000..bd771f304 --- /dev/null +++ b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteDeclineRequestParams.ts @@ -0,0 +1,10 @@ +import { Uuid } from '@standardnotes/common' + +import { ApiEndpointParam } from '../ApiEndpointParam' +import { ApiVersion } from '../../Api/ApiVersion' + +export type SubscriptionInviteDeclineRequestParams = { + [ApiEndpointParam.ApiVersion]: ApiVersion.v0 + inviteUuid: Uuid + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Subscription/SubscriptionInviteListRequestParams.ts b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteListRequestParams.ts new file mode 100644 index 000000000..bcf7fcc8d --- /dev/null +++ b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteListRequestParams.ts @@ -0,0 +1,7 @@ +import { ApiEndpointParam } from '../ApiEndpointParam' +import { ApiVersion } from '../../Api/ApiVersion' + +export type SubscriptionInviteListRequestParams = { + [ApiEndpointParam.ApiVersion]: ApiVersion.v0 + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Subscription/SubscriptionInviteRequestParams.ts b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteRequestParams.ts new file mode 100644 index 000000000..0263ec0df --- /dev/null +++ b/packages/api/src/Domain/Request/Subscription/SubscriptionInviteRequestParams.ts @@ -0,0 +1,8 @@ +import { ApiEndpointParam } from '../ApiEndpointParam' +import { ApiVersion } from '../../Api/ApiVersion' + +export type SubscriptionInviteRequestParams = { + [ApiEndpointParam.ApiVersion]: ApiVersion.v0 + identifier: string + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/index.ts b/packages/api/src/Domain/Request/index.ts index c3324f422..f1f75adf3 100644 --- a/packages/api/src/Domain/Request/index.ts +++ b/packages/api/src/Domain/Request/index.ts @@ -1,2 +1,7 @@ export * from './ApiEndpointParam' +export * from './Subscription/SubscriptionInviteAcceptRequestParams' +export * from './Subscription/SubscriptionInviteCancelRequestParams' +export * from './Subscription/SubscriptionInviteDeclineRequestParams' +export * from './Subscription/SubscriptionInviteListRequestParams' +export * from './Subscription/SubscriptionInviteRequestParams' export * from './User/UserRegistrationRequestParams' diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponse.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponse.ts new file mode 100644 index 000000000..e0453b26f --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponse.ts @@ -0,0 +1,7 @@ +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { SubscriptionInviteAcceptResponseBody } from './SubscriptionInviteAcceptResponseBody' + +export interface SubscriptionInviteAcceptResponse extends HttpResponse { + data: SubscriptionInviteAcceptResponseBody | HttpErrorResponseBody +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponseBody.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponseBody.ts new file mode 100644 index 000000000..9635a4aec --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteAcceptResponseBody.ts @@ -0,0 +1,3 @@ +export type SubscriptionInviteAcceptResponseBody = { + success: boolean +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponse.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponse.ts new file mode 100644 index 000000000..9db88e0ad --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponse.ts @@ -0,0 +1,7 @@ +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { SubscriptionInviteCancelResponseBody } from './SubscriptionInviteCancelResponseBody' + +export interface SubscriptionInviteCancelResponse extends HttpResponse { + data: SubscriptionInviteCancelResponseBody | HttpErrorResponseBody +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponseBody.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponseBody.ts new file mode 100644 index 000000000..dc8b10d9b --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteCancelResponseBody.ts @@ -0,0 +1,3 @@ +export type SubscriptionInviteCancelResponseBody = { + success: boolean +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponse.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponse.ts new file mode 100644 index 000000000..ac7c8b60f --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponse.ts @@ -0,0 +1,7 @@ +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { SubscriptionInviteDeclineResponseBody } from './SubscriptionInviteDeclineResponseBody' + +export interface SubscriptionInviteDeclineResponse extends HttpResponse { + data: SubscriptionInviteDeclineResponseBody | HttpErrorResponseBody +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponseBody.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponseBody.ts new file mode 100644 index 000000000..733d6b662 --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteDeclineResponseBody.ts @@ -0,0 +1,3 @@ +export type SubscriptionInviteDeclineResponseBody = { + success: boolean +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponse.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponse.ts new file mode 100644 index 000000000..24c89836d --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponse.ts @@ -0,0 +1,7 @@ +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { SubscriptionInviteListResponseBody } from './SubscriptionInviteListResponseBody' + +export interface SubscriptionInviteListResponse extends HttpResponse { + data: SubscriptionInviteListResponseBody | HttpErrorResponseBody +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponseBody.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponseBody.ts new file mode 100644 index 000000000..698b7dcf1 --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteListResponseBody.ts @@ -0,0 +1,5 @@ +import { Invitation } from '@standardnotes/models' + +export type SubscriptionInviteListResponseBody = { + invitations: Array +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponse.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponse.ts new file mode 100644 index 000000000..1c3170b1a --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponse.ts @@ -0,0 +1,7 @@ +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { SubscriptionInviteResponseBody } from './SubscriptionInviteResponseBody' + +export interface SubscriptionInviteResponse extends HttpResponse { + data: SubscriptionInviteResponseBody | HttpErrorResponseBody +} diff --git a/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponseBody.ts b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponseBody.ts new file mode 100644 index 000000000..7c78bf3fb --- /dev/null +++ b/packages/api/src/Domain/Response/Subscription/SubscriptionInviteResponseBody.ts @@ -0,0 +1,10 @@ +import { Uuid } from '@standardnotes/common' + +export type SubscriptionInviteResponseBody = + | { + success: true + sharedSubscriptionInvitationUuid: Uuid + } + | { + success: false + } diff --git a/packages/api/src/Domain/Response/index.ts b/packages/api/src/Domain/Response/index.ts index a2ed09315..b441a9a1d 100644 --- a/packages/api/src/Domain/Response/index.ts +++ b/packages/api/src/Domain/Response/index.ts @@ -1,2 +1,12 @@ +export * from './Subscription/SubscriptionInviteAcceptResponse' +export * from './Subscription/SubscriptionInviteAcceptResponseBody' +export * from './Subscription/SubscriptionInviteCancelResponse' +export * from './Subscription/SubscriptionInviteCancelResponseBody' +export * from './Subscription/SubscriptionInviteDeclineResponse' +export * from './Subscription/SubscriptionInviteDeclineResponseBody' +export * from './Subscription/SubscriptionInviteListResponse' +export * from './Subscription/SubscriptionInviteListResponseBody' +export * from './Subscription/SubscriptionInviteResponse' +export * from './Subscription/SubscriptionInviteResponseBody' export * from './User/UserRegistrationResponse' export * from './User/UserRegistrationResponseBody' diff --git a/packages/api/src/Domain/Server/Subscription/Paths.ts b/packages/api/src/Domain/Server/Subscription/Paths.ts new file mode 100644 index 000000000..720fc3c3c --- /dev/null +++ b/packages/api/src/Domain/Server/Subscription/Paths.ts @@ -0,0 +1,15 @@ +import { Uuid } from '@standardnotes/common' + +const SharingPaths = { + invite: '/v1/subscription-invites', + acceptInvite: (inviteUuid: Uuid) => `/v1/subscription-invites/${inviteUuid}/accept`, + declineInvite: (inviteUuid: Uuid) => `/v1/subscription-invites/${inviteUuid}/decline`, + cancelInvite: (inviteUuid: Uuid) => `/v1/subscription-invites/${inviteUuid}`, + listInvites: '/v1/subscription-invites', +} + +export const Paths = { + v1: { + ...SharingPaths, + }, +} diff --git a/packages/api/src/Domain/Server/Subscription/SubscriptionServer.spec.ts b/packages/api/src/Domain/Server/Subscription/SubscriptionServer.spec.ts new file mode 100644 index 000000000..8e7c446e3 --- /dev/null +++ b/packages/api/src/Domain/Server/Subscription/SubscriptionServer.spec.ts @@ -0,0 +1,105 @@ +import { Invitation } from '@standardnotes/models' + +import { ApiVersion } from '../../Api' +import { HttpServiceInterface } from '../../Http' +import { SubscriptionInviteResponse } from '../../Response' +import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse' +import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse' +import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse' +import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse' +import { SubscriptionServer } from './SubscriptionServer' + +describe('SubscriptionServer', () => { + let httpService: HttpServiceInterface + + const createServer = () => new SubscriptionServer(httpService) + + beforeEach(() => { + httpService = {} as jest.Mocked + }) + + it('should invite a user to a shared subscription', async () => { + httpService.post = jest.fn().mockReturnValue({ + data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' }, + } as jest.Mocked) + + const response = await createServer().invite({ + api: ApiVersion.v0, + identifier: 'test@test.te', + }) + + expect(response).toEqual({ + data: { + success: true, + sharedSubscriptionInvitationUuid: '1-2-3', + }, + }) + }) + + it('should accept an invite to a shared subscription', async () => { + httpService.get = jest.fn().mockReturnValue({ + data: { success: true }, + } as jest.Mocked) + + const response = await createServer().acceptInvite({ + api: ApiVersion.v0, + inviteUuid: '1-2-3', + }) + + expect(response).toEqual({ + data: { + success: true, + }, + }) + }) + + it('should decline an invite to a shared subscription', async () => { + httpService.get = jest.fn().mockReturnValue({ + data: { success: true }, + } as jest.Mocked) + + const response = await createServer().declineInvite({ + api: ApiVersion.v0, + inviteUuid: '1-2-3', + }) + + expect(response).toEqual({ + data: { + success: true, + }, + }) + }) + + it('should cancel an invite to a shared subscription', async () => { + httpService.delete = jest.fn().mockReturnValue({ + data: { success: true }, + } as jest.Mocked) + + const response = await createServer().cancelInvite({ + api: ApiVersion.v0, + inviteUuid: '1-2-3', + }) + + expect(response).toEqual({ + data: { + success: true, + }, + }) + }) + + it('should list invitations to a shared subscription', async () => { + httpService.get = jest.fn().mockReturnValue({ + data: { invitations: [{} as jest.Mocked] }, + } as jest.Mocked) + + const response = await createServer().listInvites({ + api: ApiVersion.v0, + }) + + expect(response).toEqual({ + data: { + invitations: [{} as jest.Mocked], + }, + }) + }) +}) diff --git a/packages/api/src/Domain/Server/Subscription/SubscriptionServer.ts b/packages/api/src/Domain/Server/Subscription/SubscriptionServer.ts new file mode 100644 index 000000000..23552df0f --- /dev/null +++ b/packages/api/src/Domain/Server/Subscription/SubscriptionServer.ts @@ -0,0 +1,48 @@ +import { HttpServiceInterface } from '../../Http/HttpServiceInterface' +import { SubscriptionInviteAcceptRequestParams } from '../../Request/Subscription/SubscriptionInviteAcceptRequestParams' +import { SubscriptionInviteCancelRequestParams } from '../../Request/Subscription/SubscriptionInviteCancelRequestParams' +import { SubscriptionInviteDeclineRequestParams } from '../../Request/Subscription/SubscriptionInviteDeclineRequestParams' +import { SubscriptionInviteListRequestParams } from '../../Request/Subscription/SubscriptionInviteListRequestParams' +import { SubscriptionInviteRequestParams } from '../../Request/Subscription/SubscriptionInviteRequestParams' +import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse' +import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse' +import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse' +import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse' +import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse' + +import { Paths } from './Paths' +import { SubscriptionServerInterface } from './SubscriptionServerInterface' + +export class SubscriptionServer implements SubscriptionServerInterface { + constructor(private httpService: HttpServiceInterface) {} + + async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise { + const response = await this.httpService.get(Paths.v1.acceptInvite(params.inviteUuid), params) + + return response as SubscriptionInviteAcceptResponse + } + + async declineInvite(params: SubscriptionInviteDeclineRequestParams): Promise { + const response = await this.httpService.get(Paths.v1.declineInvite(params.inviteUuid), params) + + return response as SubscriptionInviteDeclineResponse + } + + async cancelInvite(params: SubscriptionInviteCancelRequestParams): Promise { + const response = await this.httpService.delete(Paths.v1.cancelInvite(params.inviteUuid), params) + + return response as SubscriptionInviteCancelResponse + } + + async listInvites(params: SubscriptionInviteListRequestParams): Promise { + const response = await this.httpService.get(Paths.v1.listInvites, params) + + return response as SubscriptionInviteListResponse + } + + async invite(params: SubscriptionInviteRequestParams): Promise { + const response = await this.httpService.post(Paths.v1.invite, params) + + return response as SubscriptionInviteResponse + } +} diff --git a/packages/api/src/Domain/Server/Subscription/SubscriptionServerInterface.ts b/packages/api/src/Domain/Server/Subscription/SubscriptionServerInterface.ts new file mode 100644 index 000000000..c7c8a8359 --- /dev/null +++ b/packages/api/src/Domain/Server/Subscription/SubscriptionServerInterface.ts @@ -0,0 +1,18 @@ +import { SubscriptionInviteAcceptRequestParams } from '../../Request/Subscription/SubscriptionInviteAcceptRequestParams' +import { SubscriptionInviteCancelRequestParams } from '../../Request/Subscription/SubscriptionInviteCancelRequestParams' +import { SubscriptionInviteDeclineRequestParams } from '../../Request/Subscription/SubscriptionInviteDeclineRequestParams' +import { SubscriptionInviteListRequestParams } from '../../Request/Subscription/SubscriptionInviteListRequestParams' +import { SubscriptionInviteRequestParams } from '../../Request/Subscription/SubscriptionInviteRequestParams' +import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse' +import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse' +import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse' +import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse' +import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse' + +export interface SubscriptionServerInterface { + invite(params: SubscriptionInviteRequestParams): Promise + acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise + declineInvite(params: SubscriptionInviteDeclineRequestParams): Promise + cancelInvite(params: SubscriptionInviteCancelRequestParams): Promise + listInvites(params: SubscriptionInviteListRequestParams): Promise +} diff --git a/packages/models/src/Domain/Api/Subscription/Invitation.ts b/packages/models/src/Domain/Api/Subscription/Invitation.ts new file mode 100644 index 000000000..7f921a369 --- /dev/null +++ b/packages/models/src/Domain/Api/Subscription/Invitation.ts @@ -0,0 +1,15 @@ +import { InvitationStatus } from './InvitationStatus' +import { InviteeIdentifierType } from './InviteeIdentifierType' +import { InviterIdentifierType } from './InviterIdentifierType' + +export type Invitation = { + uuid: string + inviterIdentifier: string + inviterIdentifierType: InviterIdentifierType + inviteeIdentifier: string + inviteeIdentifierType: InviteeIdentifierType + status: InvitationStatus + subscriptionId: number + createdAt: number + updatedAt: number +} diff --git a/packages/models/src/Domain/Api/Subscription/InvitationStatus.ts b/packages/models/src/Domain/Api/Subscription/InvitationStatus.ts new file mode 100644 index 000000000..8337bb761 --- /dev/null +++ b/packages/models/src/Domain/Api/Subscription/InvitationStatus.ts @@ -0,0 +1,6 @@ +export enum InvitationStatus { + Sent = 'sent', + Canceled = 'canceled', + Accepted = 'accepted', + Declined = 'declined', +} diff --git a/packages/models/src/Domain/Api/Subscription/InviteeIdentifierType.ts b/packages/models/src/Domain/Api/Subscription/InviteeIdentifierType.ts new file mode 100644 index 000000000..65c35816e --- /dev/null +++ b/packages/models/src/Domain/Api/Subscription/InviteeIdentifierType.ts @@ -0,0 +1,5 @@ +export enum InviteeIdentifierType { + Email = 'email', + Hash = 'hash', + Uuid = 'uuid', +} diff --git a/packages/models/src/Domain/Api/Subscription/InviterIdentifierType.ts b/packages/models/src/Domain/Api/Subscription/InviterIdentifierType.ts new file mode 100644 index 000000000..147c1c3d4 --- /dev/null +++ b/packages/models/src/Domain/Api/Subscription/InviterIdentifierType.ts @@ -0,0 +1,4 @@ +export enum InviterIdentifierType { + Email = 'email', + Uuid = 'uuid', +} diff --git a/packages/models/src/Domain/index.ts b/packages/models/src/Domain/index.ts index 1b1f49f76..62018c389 100644 --- a/packages/models/src/Domain/index.ts +++ b/packages/models/src/Domain/index.ts @@ -24,6 +24,10 @@ export * from './Abstract/Contextual/SessionHistory' export * from './Abstract/Item' export * from './Abstract/Payload' export * from './Abstract/TransferPayload' +export * from './Api/Subscription/Invitation' +export * from './Api/Subscription/InvitationStatus' +export * from './Api/Subscription/InviteeIdentifierType' +export * from './Api/Subscription/InviterIdentifierType' export * from './Local/KeyParams/RootKeyParamsInterface' export * from './Local/RootKey/KeychainTypes' export * from './Local/RootKey/RootKeyContent' diff --git a/yarn.lock b/yarn.lock index e9d3e3b59..10f0263dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3463,6 +3463,23 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^1.3.1": + version: 1.3.1 + resolution: "@eslint/eslintrc@npm:1.3.1" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.4.0 + globals: ^13.15.0 + ignore: ^5.2.0 + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + minimatch: ^3.1.2 + strip-json-comments: ^3.1.1 + checksum: 9844dcc58a44399649926d5a17a2d53d529b80d3e8c3e9d0964ae198bac77ee6bb1cf44940f30cd9c2e300f7568ec82500be42ace6cacefb08aebf9905fe208e + languageName: node + linkType: hard + "@expo/react-native-action-sheet@npm:^3.13.0": version: 3.13.0 resolution: "@expo/react-native-action-sheet@npm:3.13.0" @@ -3538,6 +3555,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 0fd22007db8034a2cdf2c764b140d37d9020bbfce8a49d3ec5c05290e77d4b0263b1b972b752df8c89e5eaa94073408f2b7d977aed131faf6cf396ebb5d7fb61 + languageName: node + linkType: hard + "@humanwhocodes/object-schema@npm:^1.2.0, @humanwhocodes/object-schema@npm:^1.2.1": version: 1.2.1 resolution: "@humanwhocodes/object-schema@npm:1.2.1" @@ -6216,6 +6240,7 @@ __metadata: "@types/jest": ^28.1.5 "@types/lodash": ^4.14.182 "@typescript-eslint/eslint-plugin": ^5.30.0 + eslint: ^8.23.0 eslint-plugin-prettier: "*" jest: ^28.1.2 reflect-metadata: ^0.1.13 @@ -18026,6 +18051,55 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.23.0": + version: 8.23.0 + resolution: "eslint@npm:8.23.0" + dependencies: + "@eslint/eslintrc": ^1.3.1 + "@humanwhocodes/config-array": ^0.10.4 + "@humanwhocodes/gitignore-to-minimatch": ^1.0.2 + "@humanwhocodes/module-importer": ^1.0.1 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.1.1 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.3.0 + espree: ^9.4.0 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + find-up: ^5.0.0 + functional-red-black-tree: ^1.0.1 + glob-parent: ^6.0.1 + globals: ^13.15.0 + globby: ^11.1.0 + grapheme-splitter: ^1.0.4 + ignore: ^5.2.0 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.1.2 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + regexpp: ^3.2.0 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + bin: + eslint: bin/eslint.js + checksum: ff6075daa28d817a7ac4508f31bc108a04d9ab5056608c8651b5bf9cfea5d708ca16dea6cdab2c3c0ae99b0bf0e726af8504eaa8e17c8e12e242cb68237ead64 + languageName: node + linkType: hard + "espree@npm:^7.3.0, espree@npm:^7.3.1": version: 7.3.1 resolution: "espree@npm:7.3.1" @@ -18059,6 +18133,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.4.0": + version: 9.4.0 + resolution: "espree@npm:9.4.0" + dependencies: + acorn: ^8.8.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.3.0 + checksum: 2e3020dde67892d2ba3632413b44d0dc31d92c29ce72267d7ec24216a562f0a6494d3696e2fa39a3ec8c0e0088d773947ab2925fbb716801a11eb8dd313ac89c + languageName: node + linkType: hard + "esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1"