feat(api): add client methods for listing and canceling subscription invites (#1471)
* feat(api): add subscription server and client services and interfaces * feat(api): add subscriptions invitation operations on server side * fix(api): linter issues * feat(api): add client methods for listing and canceling subscription invites * fix(api): imports
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
export enum SubscriptionApiOperations {
|
||||||
|
Inviting,
|
||||||
|
CancelingInvite,
|
||||||
|
ListingInvites,
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Invitation } from '@standardnotes/models'
|
||||||
|
|
||||||
|
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||||
|
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||||
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
||||||
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
|
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
|
||||||
|
|
||||||
|
import { SubscriptionApiOperations } from './SubscriptionApiOperations'
|
||||||
import { SubscriptionApiService } from './SubscriptionApiService'
|
import { SubscriptionApiService } from './SubscriptionApiService'
|
||||||
|
|
||||||
describe('SubscriptionApiService', () => {
|
describe('SubscriptionApiService', () => {
|
||||||
@@ -13,6 +18,12 @@ describe('SubscriptionApiService', () => {
|
|||||||
subscriptionServer.invite = jest.fn().mockReturnValue({
|
subscriptionServer.invite = jest.fn().mockReturnValue({
|
||||||
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
|
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
|
||||||
} as jest.Mocked<SubscriptionInviteResponse>)
|
} as jest.Mocked<SubscriptionInviteResponse>)
|
||||||
|
subscriptionServer.cancelInvite = jest.fn().mockReturnValue({
|
||||||
|
data: { success: true },
|
||||||
|
} as jest.Mocked<SubscriptionInviteCancelResponse>)
|
||||||
|
subscriptionServer.listInvites = jest.fn().mockReturnValue({
|
||||||
|
data: { invitations: [{} as jest.Mocked<Invitation>] },
|
||||||
|
} as jest.Mocked<SubscriptionInviteListResponse>)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invite a user', async () => {
|
it('should invite a user', async () => {
|
||||||
@@ -32,8 +43,8 @@ describe('SubscriptionApiService', () => {
|
|||||||
|
|
||||||
it('should not invite a user if it is already inviting', async () => {
|
it('should not invite a user if it is already inviting', async () => {
|
||||||
const service = createService()
|
const service = createService()
|
||||||
Object.defineProperty(service, 'inviting', {
|
Object.defineProperty(service, 'operationsInProgress', {
|
||||||
get: () => true,
|
get: () => new Map([[SubscriptionApiOperations.Inviting, true]]),
|
||||||
})
|
})
|
||||||
|
|
||||||
let error = null
|
let error = null
|
||||||
@@ -60,4 +71,93 @@ describe('SubscriptionApiService', () => {
|
|||||||
|
|
||||||
expect(error).not.toBeNull()
|
expect(error).not.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should cancel an invite', async () => {
|
||||||
|
const response = await createService().cancelInvite('1-2-3')
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(subscriptionServer.cancelInvite).toHaveBeenCalledWith({
|
||||||
|
api: '20200115',
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not cancel an invite if it is already canceling', async () => {
|
||||||
|
const service = createService()
|
||||||
|
Object.defineProperty(service, 'operationsInProgress', {
|
||||||
|
get: () => new Map([[SubscriptionApiOperations.CancelingInvite, true]]),
|
||||||
|
})
|
||||||
|
|
||||||
|
let error = null
|
||||||
|
try {
|
||||||
|
await service.cancelInvite('1-2-3')
|
||||||
|
} catch (caughtError) {
|
||||||
|
error = caughtError
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not cancel an invite if the server fails', async () => {
|
||||||
|
subscriptionServer.cancelInvite = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('Oops')
|
||||||
|
})
|
||||||
|
|
||||||
|
let error = null
|
||||||
|
try {
|
||||||
|
await createService().cancelInvite('1-2-3')
|
||||||
|
} catch (caughtError) {
|
||||||
|
error = caughtError
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should list invites', async () => {
|
||||||
|
const response = await createService().listInvites()
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
data: {
|
||||||
|
invitations: [{} as jest.Mocked<Invitation>],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(subscriptionServer.listInvites).toHaveBeenCalledWith({
|
||||||
|
api: '20200115',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not list invitations if it is already listing', async () => {
|
||||||
|
const service = createService()
|
||||||
|
Object.defineProperty(service, 'operationsInProgress', {
|
||||||
|
get: () => new Map([[SubscriptionApiOperations.ListingInvites, true]]),
|
||||||
|
})
|
||||||
|
|
||||||
|
let error = null
|
||||||
|
try {
|
||||||
|
await service.listInvites()
|
||||||
|
} catch (caughtError) {
|
||||||
|
error = caughtError
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not list invites if the server fails', async () => {
|
||||||
|
subscriptionServer.listInvites = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('Oops')
|
||||||
|
})
|
||||||
|
|
||||||
|
let error = null
|
||||||
|
try {
|
||||||
|
await createService().listInvites()
|
||||||
|
} catch (caughtError) {
|
||||||
|
error = caughtError
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,22 +2,68 @@ import { ErrorMessage } from '../../Error/ErrorMessage'
|
|||||||
import { ApiCallError } from '../../Error/ApiCallError'
|
import { ApiCallError } from '../../Error/ApiCallError'
|
||||||
import { ApiVersion } from '../../Api/ApiVersion'
|
import { ApiVersion } from '../../Api/ApiVersion'
|
||||||
import { ApiEndpointParam } from '../../Request/ApiEndpointParam'
|
import { ApiEndpointParam } from '../../Request/ApiEndpointParam'
|
||||||
import { SubscriptionApiServiceInterface } from './SubscriptionApiServiceInterface'
|
|
||||||
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
|
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
|
||||||
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
||||||
|
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||||
|
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||||
|
|
||||||
|
import { SubscriptionApiServiceInterface } from './SubscriptionApiServiceInterface'
|
||||||
|
import { SubscriptionApiOperations } from './SubscriptionApiOperations'
|
||||||
|
|
||||||
export class SubscriptionApiService implements SubscriptionApiServiceInterface {
|
export class SubscriptionApiService implements SubscriptionApiServiceInterface {
|
||||||
private inviting: boolean
|
private operationsInProgress: Map<SubscriptionApiOperations, boolean>
|
||||||
|
|
||||||
constructor(private subscriptionServer: SubscriptionServerInterface) {
|
constructor(private subscriptionServer: SubscriptionServerInterface) {
|
||||||
this.inviting = false
|
this.operationsInProgress = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
async listInvites(): Promise<SubscriptionInviteListResponse> {
|
||||||
|
if (this.operationsInProgress.get(SubscriptionApiOperations.ListingInvites)) {
|
||||||
|
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationsInProgress.set(SubscriptionApiOperations.ListingInvites, true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.subscriptionServer.listInvites({
|
||||||
|
[ApiEndpointParam.ApiVersion]: ApiVersion.v0,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.operationsInProgress.set(SubscriptionApiOperations.ListingInvites, false)
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelInvite(inviteUuid: string): Promise<SubscriptionInviteCancelResponse> {
|
||||||
|
if (this.operationsInProgress.get(SubscriptionApiOperations.CancelingInvite)) {
|
||||||
|
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationsInProgress.set(SubscriptionApiOperations.CancelingInvite, true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.subscriptionServer.cancelInvite({
|
||||||
|
[ApiEndpointParam.ApiVersion]: ApiVersion.v0,
|
||||||
|
inviteUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.operationsInProgress.set(SubscriptionApiOperations.CancelingInvite, false)
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async invite(inviteeEmail: string): Promise<SubscriptionInviteResponse> {
|
async invite(inviteeEmail: string): Promise<SubscriptionInviteResponse> {
|
||||||
if (this.inviting) {
|
if (this.operationsInProgress.get(SubscriptionApiOperations.Inviting)) {
|
||||||
throw new ApiCallError(ErrorMessage.InvitingInProgress)
|
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||||
}
|
}
|
||||||
this.inviting = true
|
|
||||||
|
this.operationsInProgress.set(SubscriptionApiOperations.Inviting, true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.subscriptionServer.invite({
|
const response = await this.subscriptionServer.invite({
|
||||||
@@ -25,7 +71,7 @@ export class SubscriptionApiService implements SubscriptionApiServiceInterface {
|
|||||||
identifier: inviteeEmail,
|
identifier: inviteeEmail,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.inviting = false
|
this.operationsInProgress.set(SubscriptionApiOperations.Inviting, false)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
import { Uuid } from '@standardnotes/common'
|
||||||
|
|
||||||
|
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||||
|
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||||
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
||||||
|
|
||||||
export interface SubscriptionApiServiceInterface {
|
export interface SubscriptionApiServiceInterface {
|
||||||
invite(inviteeEmail: string): Promise<SubscriptionInviteResponse>
|
invite(inviteeEmail: string): Promise<SubscriptionInviteResponse>
|
||||||
|
listInvites(): Promise<SubscriptionInviteListResponse>
|
||||||
|
cancelInvite(inviteUuid: Uuid): Promise<SubscriptionInviteCancelResponse>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './Subscription/SubscriptionApiOperations'
|
||||||
export * from './Subscription/SubscriptionApiService'
|
export * from './Subscription/SubscriptionApiService'
|
||||||
export * from './Subscription/SubscriptionApiServiceInterface'
|
export * from './Subscription/SubscriptionApiServiceInterface'
|
||||||
export * from './User/UserApiService'
|
export * from './User/UserApiService'
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export enum ErrorMessage {
|
export enum ErrorMessage {
|
||||||
InvitingInProgress = 'An existing invitation request is already in progress.',
|
|
||||||
RegistrationInProgress = 'An existing registration 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.',
|
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.',
|
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.',
|
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.',
|
PasscodeRequired = 'Your passcode is required in order to register for an account.',
|
||||||
|
GenericInProgress = 'An existing request is already in progress.',
|
||||||
GenericFail = 'A server error occurred. Please try again.',
|
GenericFail = 'A server error occurred. Please try again.',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/Su
|
|||||||
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||||
import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse'
|
import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse'
|
||||||
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||||
|
|
||||||
import { SubscriptionServer } from './SubscriptionServer'
|
import { SubscriptionServer } from './SubscriptionServer'
|
||||||
|
|
||||||
describe('SubscriptionServer', () => {
|
describe('SubscriptionServer', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user