feat: add sending user requests to process (#1908)
* feat: add sending user requests to process * fix(snjs): yarn lock * fix(snjs): imports * fix: specs
This commit is contained in:
5
packages/api/src/Domain/Client/User/UserApiOperations.ts
Normal file
5
packages/api/src/Domain/Client/User/UserApiOperations.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum UserApiOperations {
|
||||
Registering,
|
||||
SubmittingRequest,
|
||||
DeletingAccount,
|
||||
}
|
||||
@@ -1,20 +1,35 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { ProtocolVersion, UserRequestType } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
import { UserServerInterface } from '../../Server'
|
||||
import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface'
|
||||
|
||||
import { UserApiOperations } from './UserApiOperations'
|
||||
import { UserApiService } from './UserApiService'
|
||||
|
||||
describe('UserApiService', () => {
|
||||
let userServer: UserServerInterface
|
||||
let userRequestServer: UserRequestServerInterface
|
||||
let keyParams: RootKeyParamsInterface
|
||||
|
||||
const createService = () => new UserApiService(userServer)
|
||||
const createService = () => new UserApiService(userServer, userRequestServer)
|
||||
|
||||
beforeEach(() => {
|
||||
userServer = {} as jest.Mocked<UserServerInterface>
|
||||
userServer.register = jest.fn().mockReturnValue({
|
||||
data: { user: { email: 'test@test.te', uuid: '1-2-3' } },
|
||||
} as jest.Mocked<UserRegistrationResponse>)
|
||||
userServer.deleteAccount = jest.fn().mockReturnValue({
|
||||
data: { message: 'Success' },
|
||||
} as jest.Mocked<UserDeletionResponse>)
|
||||
|
||||
userRequestServer = {} as jest.Mocked<UserRequestServerInterface>
|
||||
userRequestServer.submitUserRequest = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<UserRequestResponse>)
|
||||
|
||||
keyParams = {} as jest.Mocked<RootKeyParamsInterface>
|
||||
keyParams.getPortableValue = jest.fn().mockReturnValue({
|
||||
@@ -51,8 +66,8 @@ describe('UserApiService', () => {
|
||||
|
||||
it('should not register a user if it is already registering', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'registering', {
|
||||
get: () => true,
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.Registering, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
@@ -84,4 +99,99 @@ describe('UserApiService', () => {
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should submit a user request', async () => {
|
||||
const response = await createService().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(userRequestServer.submitUserRequest).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not submit a user request if it is already submitting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.SubmittingRequest, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.submitUserRequest({ userUuid: '1-2-3', requestType: UserRequestType.ExitDiscount })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not submit a user request if the server fails', async () => {
|
||||
userRequestServer.submitUserRequest = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should delete a user', async () => {
|
||||
const response = await createService().deleteAccount('1-2-3')
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
message: 'Success',
|
||||
},
|
||||
})
|
||||
expect(userServer.deleteAccount).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not delete a user if it is already deleting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.DeletingAccount, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.deleteAccount('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not delete a user if the server fails', async () => {
|
||||
userServer.deleteAccount = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().deleteAccount('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
import { UserServerInterface } from '../../Server/User/UserServerInterface'
|
||||
import { ApiVersion } from '../../Api/ApiVersion'
|
||||
import { ApiEndpointParam } from '../../Request/ApiEndpointParam'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface'
|
||||
|
||||
import { UserApiOperations } from './UserApiOperations'
|
||||
import { UserApiServiceInterface } from './UserApiServiceInterface'
|
||||
|
||||
export class UserApiService implements UserApiServiceInterface {
|
||||
private registering: boolean
|
||||
private operationsInProgress: Map<UserApiOperations, boolean>
|
||||
|
||||
constructor(private userServer: UserServerInterface) {
|
||||
this.registering = false
|
||||
constructor(private userServer: UserServerInterface, private userRequestServer: UserRequestServerInterface) {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async deleteAccount(userUuid: string): Promise<UserDeletionResponse> {
|
||||
this.lockOperation(UserApiOperations.DeletingAccount)
|
||||
|
||||
try {
|
||||
const response = await this.userServer.deleteAccount({
|
||||
userUuid: userUuid,
|
||||
})
|
||||
|
||||
this.unlockOperation(UserApiOperations.DeletingAccount)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericRegistrationFail)
|
||||
}
|
||||
}
|
||||
|
||||
async submitUserRequest(dto: { userUuid: string; requestType: UserRequestType }): Promise<UserRequestResponse> {
|
||||
this.lockOperation(UserApiOperations.SubmittingRequest)
|
||||
|
||||
try {
|
||||
const response = await this.userRequestServer.submitUserRequest({
|
||||
userUuid: dto.userUuid,
|
||||
requestType: dto.requestType,
|
||||
})
|
||||
|
||||
this.unlockOperation(UserApiOperations.SubmittingRequest)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericRegistrationFail)
|
||||
}
|
||||
}
|
||||
|
||||
async register(registerDTO: {
|
||||
@@ -20,10 +60,7 @@ export class UserApiService implements UserApiServiceInterface {
|
||||
keyParams: RootKeyParamsInterface
|
||||
ephemeral: boolean
|
||||
}): Promise<UserRegistrationResponse> {
|
||||
if (this.registering) {
|
||||
throw new ApiCallError(ErrorMessage.RegistrationInProgress)
|
||||
}
|
||||
this.registering = true
|
||||
this.lockOperation(UserApiOperations.Registering)
|
||||
|
||||
try {
|
||||
const response = await this.userServer.register({
|
||||
@@ -34,11 +71,23 @@ export class UserApiService implements UserApiServiceInterface {
|
||||
...registerDTO.keyParams.getPortableValue(),
|
||||
})
|
||||
|
||||
this.registering = false
|
||||
this.unlockOperation(UserApiOperations.Registering)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericRegistrationFail)
|
||||
}
|
||||
}
|
||||
|
||||
private lockOperation(operation: UserApiOperations): void {
|
||||
if (this.operationsInProgress.get(operation)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(operation, true)
|
||||
}
|
||||
|
||||
private unlockOperation(operation: UserApiOperations): void {
|
||||
this.operationsInProgress.set(operation, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { UserRequestType, Uuid } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
|
||||
export interface UserApiServiceInterface {
|
||||
register(registerDTO: {
|
||||
@@ -8,4 +12,6 @@ export interface UserApiServiceInterface {
|
||||
keyParams: RootKeyParamsInterface
|
||||
ephemeral: boolean
|
||||
}): Promise<UserRegistrationResponse>
|
||||
submitUserRequest(dto: { userUuid: Uuid; requestType: UserRequestType }): Promise<UserRequestResponse>
|
||||
deleteAccount(userUuid: string): Promise<UserDeletionResponse>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './Subscription/SubscriptionApiOperations'
|
||||
export * from './Subscription/SubscriptionApiService'
|
||||
export * from './Subscription/SubscriptionApiServiceInterface'
|
||||
export * from './User/UserApiOperations'
|
||||
export * from './User/UserApiService'
|
||||
export * from './User/UserApiServiceInterface'
|
||||
export * from './WebSocket/WebSocketApiService'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
export type UserDeletionRequestParams = {
|
||||
userUuid: Uuid
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { UserRequestType, Uuid } from '@standardnotes/common'
|
||||
|
||||
export type UserRequestRequestParams = {
|
||||
userUuid: Uuid
|
||||
requestType: UserRequestType
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export * from './Subscription/SubscriptionInviteDeclineRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteListRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteRequestParams'
|
||||
export * from './User/UserRegistrationRequestParams'
|
||||
export * from './UserRequest/UserRequestRequestParams'
|
||||
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
|
||||
export * from './Workspace/WorkspaceCreationRequestParams'
|
||||
export * from './Workspace/WorkspaceInvitationAcceptingRequestParams'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { UserDeletionResponseBody } from './UserDeletionResponseBody'
|
||||
|
||||
export interface UserDeletionResponse extends HttpResponse {
|
||||
data: Either<UserDeletionResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type UserDeletionResponseBody = {
|
||||
message: string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { UserRequestResponseBody } from './UserRequestResponseBody'
|
||||
|
||||
export interface UserRequestResponse extends HttpResponse {
|
||||
data: Either<UserRequestResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type UserRequestResponseBody = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -8,8 +8,12 @@ export * from './Subscription/SubscriptionInviteListResponse'
|
||||
export * from './Subscription/SubscriptionInviteListResponseBody'
|
||||
export * from './Subscription/SubscriptionInviteResponse'
|
||||
export * from './Subscription/SubscriptionInviteResponseBody'
|
||||
export * from './User/UserDeletionResponse'
|
||||
export * from './User/UserDeletionResponseBody'
|
||||
export * from './User/UserRegistrationResponse'
|
||||
export * from './User/UserRegistrationResponseBody'
|
||||
export * from './UserRequest/UserRequestResponse'
|
||||
export * from './UserRequest/UserRequestResponseBody'
|
||||
export * from './WebSocket/WebSocketConnectionTokenResponse'
|
||||
export * from './WebSocket/WebSocketConnectionTokenResponseBody'
|
||||
export * from './Workspace/WorkspaceCreationResponse'
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
const UserPaths = {
|
||||
register: '/v1/users',
|
||||
deleteAccount: (userUuid: Uuid) => `/v1/users/${userUuid}`,
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { UserRegistrationResponse } from '../../Response'
|
||||
import { UserDeletionResponse, UserRegistrationResponse } from '../../Response'
|
||||
import { UserServer } from './UserServer'
|
||||
|
||||
describe('UserServer', () => {
|
||||
@@ -14,6 +14,9 @@ describe('UserServer', () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { user: { email: 'test@test.te', uuid: '1-2-3' } },
|
||||
} as jest.Mocked<UserRegistrationResponse>)
|
||||
httpService.delete = jest.fn().mockReturnValue({
|
||||
data: { message: 'Success' },
|
||||
} as jest.Mocked<UserDeletionResponse>)
|
||||
})
|
||||
|
||||
it('should register a user', async () => {
|
||||
@@ -36,4 +39,16 @@ describe('UserServer', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should delete a user', async () => {
|
||||
const response = await createServer().deleteAccount({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
message: 'Success',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { UserDeletionRequestParams } from '../../Request/User/UserDeletionRequestParams'
|
||||
import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
import { Paths } from './Paths'
|
||||
import { UserServerInterface } from './UserServerInterface'
|
||||
@@ -7,6 +9,12 @@ import { UserServerInterface } from './UserServerInterface'
|
||||
export class UserServer implements UserServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async deleteAccount(params: UserDeletionRequestParams): Promise<UserDeletionResponse> {
|
||||
const response = await this.httpService.delete(Paths.v1.deleteAccount(params.userUuid), params)
|
||||
|
||||
return response as UserDeletionResponse
|
||||
}
|
||||
|
||||
async register(params: UserRegistrationRequestParams): Promise<UserRegistrationResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.register, params)
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { UserDeletionRequestParams } from '../../Request/User/UserDeletionRequestParams'
|
||||
import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
export interface UserServerInterface {
|
||||
register(params: UserRegistrationRequestParams): Promise<UserRegistrationResponse>
|
||||
deleteAccount(params: UserDeletionRequestParams): Promise<UserDeletionResponse>
|
||||
}
|
||||
|
||||
11
packages/api/src/Domain/Server/UserRequest/Paths.ts
Normal file
11
packages/api/src/Domain/Server/UserRequest/Paths.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
|
||||
const UserRequestPaths = {
|
||||
submitUserRequest: (userUuid: Uuid) => `/v1/users/${userUuid}/requests`,
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
v1: {
|
||||
...UserRequestPaths,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
|
||||
import { UserRequestServer } from './UserRequestServer'
|
||||
|
||||
describe('UserRequestServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new UserRequestServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<UserRequestResponse>)
|
||||
})
|
||||
|
||||
it('should submit a user request', async () => {
|
||||
const response = await createServer().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { UserRequestRequestParams } from '../../Request/UserRequest/UserRequestRequestParams'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
|
||||
import { Paths } from './Paths'
|
||||
import { UserRequestServerInterface } from './UserRequestServerInterface'
|
||||
|
||||
export class UserRequestServer implements UserRequestServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async submitUserRequest(params: UserRequestRequestParams): Promise<UserRequestResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.submitUserRequest(params.userUuid), params)
|
||||
|
||||
return response as UserRequestResponse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { UserRequestRequestParams } from '../../Request/UserRequest/UserRequestRequestParams'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
|
||||
export interface UserRequestServerInterface {
|
||||
submitUserRequest(params: UserRequestRequestParams): Promise<UserRequestResponse>
|
||||
}
|
||||
@@ -2,6 +2,8 @@ export * from './Subscription/SubscriptionServer'
|
||||
export * from './Subscription/SubscriptionServerInterface'
|
||||
export * from './User/UserServer'
|
||||
export * from './User/UserServerInterface'
|
||||
export * from './UserRequest/UserRequestServer'
|
||||
export * from './UserRequest/UserRequestServerInterface'
|
||||
export * from './WebSocket/WebSocketServer'
|
||||
export * from './WebSocket/WebSocketServerInterface'
|
||||
export * from './Workspace/WorkspaceServer'
|
||||
|
||||
Reference in New Issue
Block a user