feat: implement collections syncing for selfhosted (#42)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hoppscotch/common": "workspace:^",
|
"@hoppscotch/common": "workspace:^",
|
||||||
|
"@hoppscotch/data": "workspace:^",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"firebase": "^9.8.4",
|
"firebase": "^9.8.4",
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
"vite-plugin-static-copy": "^0.12.0",
|
"vite-plugin-static-copy": "^0.12.0",
|
||||||
"vite-plugin-vue-layouts": "^0.7.0",
|
"vite-plugin-vue-layouts": "^0.7.0",
|
||||||
"vite-plugin-windicss": "^1.8.8",
|
"vite-plugin-windicss": "^1.8.8",
|
||||||
|
"vitest": "^0.29.3",
|
||||||
"vue-tsc": "^1.0.9",
|
"vue-tsc": "^1.0.9",
|
||||||
"windicss": "^3.5.6"
|
"windicss": "^3.5.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
mutation CreateGQLChildUserCollection(
|
||||||
|
$title: String!
|
||||||
|
$parentUserCollectionID: ID!
|
||||||
|
) {
|
||||||
|
createGQLChildUserCollection(
|
||||||
|
title: $title
|
||||||
|
parentUserCollectionID: $parentUserCollectionID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mutation CreateGQLRootUserCollection($title: String!) {
|
||||||
|
createGQLRootUserCollection(title: $title) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
mutation CreateGQLUserRequest(
|
||||||
|
$title: String!
|
||||||
|
$request: String!
|
||||||
|
$collectionID: ID!
|
||||||
|
) {
|
||||||
|
createGQLUserRequest(
|
||||||
|
title: $title
|
||||||
|
request: $request
|
||||||
|
collectionID: $collectionID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
mutation CreateRESTChildUserCollection(
|
||||||
|
$title: String!
|
||||||
|
$parentUserCollectionID: ID!
|
||||||
|
) {
|
||||||
|
createRESTChildUserCollection(
|
||||||
|
title: $title
|
||||||
|
parentUserCollectionID: $parentUserCollectionID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mutation CreateRESTRootUserCollection($title: String!) {
|
||||||
|
createRESTRootUserCollection(title: $title) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
mutation CreateRESTUserRequest(
|
||||||
|
$collectionID: ID!
|
||||||
|
$title: String!
|
||||||
|
$request: String!
|
||||||
|
) {
|
||||||
|
createRESTUserRequest(
|
||||||
|
collectionID: $collectionID
|
||||||
|
title: $title
|
||||||
|
request: $request
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation DeleteUserCollection($userCollectionID: ID!) {
|
||||||
|
deleteUserCollection(userCollectionID: $userCollectionID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation DeleteUserRequest($requestID: ID!) {
|
||||||
|
deleteUserRequest(id: $requestID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation MoveUserCollection($destCollectionID: ID, $userCollectionID: ID!) {
|
||||||
|
moveUserCollection(
|
||||||
|
destCollectionID: $destCollectionID
|
||||||
|
userCollectionID: $userCollectionID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
mutation MoveUserRequest(
|
||||||
|
$sourceCollectionID: ID!
|
||||||
|
$requestID: ID!
|
||||||
|
$destinationCollectionID: ID!
|
||||||
|
$nextRequestID: ID
|
||||||
|
) {
|
||||||
|
moveUserRequest(
|
||||||
|
sourceCollectionID: $sourceCollectionID
|
||||||
|
requestID: $requestID
|
||||||
|
destinationCollectionID: $destinationCollectionID
|
||||||
|
nextRequestID: $nextRequestID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation RenameUserCollection($userCollectionID: ID!, $newTitle: String!) {
|
||||||
|
renameUserCollection(
|
||||||
|
userCollectionID: $userCollectionID
|
||||||
|
newTitle: $newTitle
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mutation UpdateGQLUserRequest($id: ID!, $request: String!, $title: String) {
|
||||||
|
updateGQLUserRequest(id: $id, request: $request, title: $title) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mutation UpdateRESTUserRequest($id: ID!, $title: String!, $request: String!) {
|
||||||
|
updateRESTUserRequest(id: $id, title: $title, request: $request) {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
request
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mutation UpdateUserCollectionOrder($collectionID: ID!, $nextCollectionID: ID) {
|
||||||
|
updateUserCollectionOrder(
|
||||||
|
collectionID: $collectionID
|
||||||
|
nextCollectionID: $nextCollectionID
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
query ExportUserCollectionsToJSON(
|
||||||
|
$collectionID: ID
|
||||||
|
$collectionType: ReqType!
|
||||||
|
) {
|
||||||
|
exportUserCollectionsToJSON(
|
||||||
|
collectionID: $collectionID
|
||||||
|
collectionType: $collectionType
|
||||||
|
) {
|
||||||
|
collectionType
|
||||||
|
exportedCollection
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
query GetGQLRootUserCollections {
|
||||||
|
# the frontend doesnt paginate right now, so giving take a big enough value to get all collections at once
|
||||||
|
rootGQLUserCollections(take: 99999) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
childrenGQL {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
query GetUserRootCollections {
|
||||||
|
# the frontend doesnt paginate right now, so giving take a big enough value to get all collections at once
|
||||||
|
rootRESTUserCollections(take: 99999) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
childrenREST {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
subscription UserCollectionCreated {
|
||||||
|
userCollectionCreated {
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
subscription UserCollectionMoved {
|
||||||
|
userCollectionMoved {
|
||||||
|
id
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
subscription UserCollectionOrderUpdated {
|
||||||
|
userCollectionOrderUpdated {
|
||||||
|
userCollection {
|
||||||
|
id
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextUserCollection {
|
||||||
|
id
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
subscription UserCollectionRemoved {
|
||||||
|
userCollectionRemoved {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
subscription userCollectionUpdated {
|
||||||
|
userCollectionUpdated {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
subscription UserRequestCreated {
|
||||||
|
userRequestCreated {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
title
|
||||||
|
request
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
subscription UserRequestDeleted {
|
||||||
|
userRequestDeleted {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
title
|
||||||
|
request
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
subscription UserRequestMoved {
|
||||||
|
userRequestMoved {
|
||||||
|
request {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
type
|
||||||
|
}
|
||||||
|
nextRequest {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
subscription UserRequestUpdated {
|
||||||
|
userRequestUpdated {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
title
|
||||||
|
request
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ export const getSyncInitFunction = <T extends DispatchingStore<any, any>>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
startSubscriptions()
|
stopSubscriptions = startSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopListeningToSubscriptions() {
|
function stopListeningToSubscriptions() {
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
export const createMapper = () => {
|
export const createMapper = <
|
||||||
const indexBackendIDMap = new Map<number, string | undefined>()
|
LocalIDType extends string | number,
|
||||||
const backendIdIndexMap = new Map<string, number | undefined>()
|
BackendIDType extends string | number
|
||||||
|
>() => {
|
||||||
|
const backendIDByLocalIDMap = new Map<
|
||||||
|
LocalIDType,
|
||||||
|
BackendIDType | undefined
|
||||||
|
>()
|
||||||
|
const localIDByBackendIDMap = new Map<
|
||||||
|
BackendIDType,
|
||||||
|
LocalIDType | undefined
|
||||||
|
>()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addEntry(localIndex: number, backendId: string) {
|
addEntry(localIdentifier: LocalIDType, backendIdentifier: BackendIDType) {
|
||||||
indexBackendIDMap.set(localIndex, backendId)
|
backendIDByLocalIDMap.set(localIdentifier, backendIdentifier)
|
||||||
backendIdIndexMap.set(backendId, localIndex)
|
localIDByBackendIDMap.set(backendIdentifier, localIdentifier)
|
||||||
},
|
},
|
||||||
getValue() {
|
getValue() {
|
||||||
return indexBackendIDMap
|
return backendIDByLocalIDMap
|
||||||
},
|
},
|
||||||
getBackendIdByIndex(localIndex: number) {
|
getBackendIDByLocalID(localIdentifier: LocalIDType) {
|
||||||
return indexBackendIDMap.get(localIndex)
|
return backendIDByLocalIDMap.get(localIdentifier)
|
||||||
},
|
},
|
||||||
getIndexByBackendId(backendId: string) {
|
getLocalIDByBackendID(backendId: BackendIDType) {
|
||||||
return backendIdIndexMap.get(backendId)
|
return localIDByBackendIDMap.get(backendId)
|
||||||
},
|
},
|
||||||
removeEntry(backendId?: string, index?: number) {
|
removeEntry(backendId?: BackendIDType, index?: LocalIDType) {
|
||||||
if (backendId) {
|
if (backendId) {
|
||||||
const index = backendIdIndexMap.get(backendId)
|
const index = localIDByBackendIDMap.get(backendId)
|
||||||
|
|
||||||
backendIdIndexMap.delete(backendId)
|
localIDByBackendIDMap.delete(backendId)
|
||||||
index && indexBackendIDMap.delete(index)
|
index && backendIDByLocalIDMap.delete(index)
|
||||||
} else if (index) {
|
} else if (index) {
|
||||||
const backendId = indexBackendIDMap.get(index)
|
const backendId = backendIDByLocalIDMap.get(index)
|
||||||
|
|
||||||
indexBackendIDMap.delete(index)
|
backendIDByLocalIDMap.delete(index)
|
||||||
backendId && backendIdIndexMap.delete(backendId)
|
backendId && localIDByBackendIDMap.delete(backendId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { createHoppApp } from "@hoppscotch/common"
|
import { createHoppApp } from "@hoppscotch/common"
|
||||||
import { def as authDef } from "./platform/auth"
|
import { def as authDef } from "./platform/auth"
|
||||||
import { def as environmentsDef } from "./platform/environments/environments.platform"
|
import { def as environmentsDef } from "./platform/environments/environments.platform"
|
||||||
|
import { def as collectionsDef } from "./platform/collections/collections.platform"
|
||||||
|
|
||||||
createHoppApp("#app", {
|
createHoppApp("#app", {
|
||||||
auth: authDef,
|
auth: authDef,
|
||||||
sync: {
|
sync: {
|
||||||
environments: environmentsDef,
|
environments: environmentsDef,
|
||||||
|
collections: collectionsDef,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
import {
|
||||||
|
runGQLQuery,
|
||||||
|
runGQLSubscription,
|
||||||
|
runMutation,
|
||||||
|
} from "@hoppscotch/common/helpers/backend/GQLClient"
|
||||||
|
import {
|
||||||
|
CreateRestRootUserCollectionDocument,
|
||||||
|
CreateRestRootUserCollectionMutation,
|
||||||
|
CreateRestRootUserCollectionMutationVariables,
|
||||||
|
CreateRestUserRequestMutation,
|
||||||
|
CreateRestUserRequestMutationVariables,
|
||||||
|
CreateRestUserRequestDocument,
|
||||||
|
CreateRestChildUserCollectionMutation,
|
||||||
|
CreateRestChildUserCollectionMutationVariables,
|
||||||
|
CreateRestChildUserCollectionDocument,
|
||||||
|
DeleteUserCollectionMutation,
|
||||||
|
DeleteUserCollectionMutationVariables,
|
||||||
|
DeleteUserCollectionDocument,
|
||||||
|
RenameUserCollectionMutation,
|
||||||
|
RenameUserCollectionMutationVariables,
|
||||||
|
RenameUserCollectionDocument,
|
||||||
|
MoveUserCollectionMutation,
|
||||||
|
MoveUserCollectionMutationVariables,
|
||||||
|
MoveUserCollectionDocument,
|
||||||
|
DeleteUserRequestMutation,
|
||||||
|
DeleteUserRequestMutationVariables,
|
||||||
|
DeleteUserRequestDocument,
|
||||||
|
MoveUserRequestDocument,
|
||||||
|
MoveUserRequestMutation,
|
||||||
|
MoveUserRequestMutationVariables,
|
||||||
|
UpdateUserCollectionOrderMutation,
|
||||||
|
UpdateUserCollectionOrderMutationVariables,
|
||||||
|
UpdateUserCollectionOrderDocument,
|
||||||
|
GetUserRootCollectionsQuery,
|
||||||
|
GetUserRootCollectionsQueryVariables,
|
||||||
|
GetUserRootCollectionsDocument,
|
||||||
|
UserCollectionCreatedDocument,
|
||||||
|
UserCollectionUpdatedDocument,
|
||||||
|
UserCollectionRemovedDocument,
|
||||||
|
UserCollectionMovedDocument,
|
||||||
|
UserCollectionOrderUpdatedDocument,
|
||||||
|
ExportUserCollectionsToJsonQuery,
|
||||||
|
ExportUserCollectionsToJsonQueryVariables,
|
||||||
|
ExportUserCollectionsToJsonDocument,
|
||||||
|
UserRequestCreatedDocument,
|
||||||
|
UserRequestUpdatedDocument,
|
||||||
|
UserRequestMovedDocument,
|
||||||
|
UserRequestDeletedDocument,
|
||||||
|
UpdateRestUserRequestMutation,
|
||||||
|
UpdateRestUserRequestMutationVariables,
|
||||||
|
UpdateRestUserRequestDocument,
|
||||||
|
CreateGqlRootUserCollectionMutation,
|
||||||
|
CreateGqlRootUserCollectionMutationVariables,
|
||||||
|
CreateGqlRootUserCollectionDocument,
|
||||||
|
CreateGqlUserRequestMutation,
|
||||||
|
CreateGqlUserRequestMutationVariables,
|
||||||
|
CreateGqlUserRequestDocument,
|
||||||
|
CreateGqlChildUserCollectionMutation,
|
||||||
|
CreateGqlChildUserCollectionMutationVariables,
|
||||||
|
CreateGqlChildUserCollectionDocument,
|
||||||
|
UpdateGqlUserRequestMutation,
|
||||||
|
UpdateGqlUserRequestMutationVariables,
|
||||||
|
UpdateGqlUserRequestDocument,
|
||||||
|
GetGqlRootUserCollectionsQuery,
|
||||||
|
GetGqlRootUserCollectionsQueryVariables,
|
||||||
|
GetGqlRootUserCollectionsDocument,
|
||||||
|
ReqType,
|
||||||
|
} from "../../api/generated/graphql"
|
||||||
|
|
||||||
|
export const createRESTRootUserCollection = (title: string) =>
|
||||||
|
runMutation<
|
||||||
|
CreateRestRootUserCollectionMutation,
|
||||||
|
CreateRestRootUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateRestRootUserCollectionDocument, {
|
||||||
|
title,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const createGQLRootUserCollection = (title: string) =>
|
||||||
|
runMutation<
|
||||||
|
CreateGqlRootUserCollectionMutation,
|
||||||
|
CreateGqlRootUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateGqlRootUserCollectionDocument, {
|
||||||
|
title,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const createRESTUserRequest = (
|
||||||
|
title: string,
|
||||||
|
request: string,
|
||||||
|
collectionID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
CreateRestUserRequestMutation,
|
||||||
|
CreateRestUserRequestMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateRestUserRequestDocument, {
|
||||||
|
title,
|
||||||
|
request,
|
||||||
|
collectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const createGQLUserRequest = (
|
||||||
|
title: string,
|
||||||
|
request: string,
|
||||||
|
collectionID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
CreateGqlUserRequestMutation,
|
||||||
|
CreateGqlUserRequestMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateGqlUserRequestDocument, {
|
||||||
|
title,
|
||||||
|
request,
|
||||||
|
collectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const createRESTChildUserCollection = (
|
||||||
|
title: string,
|
||||||
|
parentUserCollectionID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
CreateRestChildUserCollectionMutation,
|
||||||
|
CreateRestChildUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateRestChildUserCollectionDocument, {
|
||||||
|
title,
|
||||||
|
parentUserCollectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const createGQLChildUserCollection = (
|
||||||
|
title: string,
|
||||||
|
parentUserCollectionID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
CreateGqlChildUserCollectionMutation,
|
||||||
|
CreateGqlChildUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(CreateGqlChildUserCollectionDocument, {
|
||||||
|
title,
|
||||||
|
parentUserCollectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const deleteUserCollection = (userCollectionID: string) =>
|
||||||
|
runMutation<
|
||||||
|
DeleteUserCollectionMutation,
|
||||||
|
DeleteUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(DeleteUserCollectionDocument, {
|
||||||
|
userCollectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const renameUserCollection = (
|
||||||
|
userCollectionID: string,
|
||||||
|
newTitle: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
RenameUserCollectionMutation,
|
||||||
|
RenameUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(RenameUserCollectionDocument, { userCollectionID, newTitle })()
|
||||||
|
|
||||||
|
export const moveUserCollection = (
|
||||||
|
sourceCollectionID: string,
|
||||||
|
destinationCollectionID?: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
MoveUserCollectionMutation,
|
||||||
|
MoveUserCollectionMutationVariables,
|
||||||
|
""
|
||||||
|
>(MoveUserCollectionDocument, {
|
||||||
|
userCollectionID: sourceCollectionID,
|
||||||
|
destCollectionID: destinationCollectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const editUserRequest = (
|
||||||
|
requestID: string,
|
||||||
|
title: string,
|
||||||
|
request: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateRestUserRequestMutation,
|
||||||
|
UpdateRestUserRequestMutationVariables,
|
||||||
|
""
|
||||||
|
>(UpdateRestUserRequestDocument, {
|
||||||
|
id: requestID,
|
||||||
|
request,
|
||||||
|
title,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const editGQLUserRequest = (
|
||||||
|
requestID: string,
|
||||||
|
title: string,
|
||||||
|
request: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateGqlUserRequestMutation,
|
||||||
|
UpdateGqlUserRequestMutationVariables,
|
||||||
|
""
|
||||||
|
>(UpdateGqlUserRequestDocument, {
|
||||||
|
id: requestID,
|
||||||
|
request,
|
||||||
|
title,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const deleteUserRequest = (requestID: string) =>
|
||||||
|
runMutation<
|
||||||
|
DeleteUserRequestMutation,
|
||||||
|
DeleteUserRequestMutationVariables,
|
||||||
|
""
|
||||||
|
>(DeleteUserRequestDocument, {
|
||||||
|
requestID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const moveUserRequest = (
|
||||||
|
sourceCollectionID: string,
|
||||||
|
destinationCollectionID: string,
|
||||||
|
requestID: string,
|
||||||
|
nextRequestID?: string
|
||||||
|
) =>
|
||||||
|
runMutation<MoveUserRequestMutation, MoveUserRequestMutationVariables, "">(
|
||||||
|
MoveUserRequestDocument,
|
||||||
|
{
|
||||||
|
sourceCollectionID,
|
||||||
|
destinationCollectionID,
|
||||||
|
requestID,
|
||||||
|
nextRequestID,
|
||||||
|
}
|
||||||
|
)()
|
||||||
|
|
||||||
|
export const updateUserCollectionOrder = (
|
||||||
|
collectionID: string,
|
||||||
|
nextCollectionID?: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateUserCollectionOrderMutation,
|
||||||
|
UpdateUserCollectionOrderMutationVariables,
|
||||||
|
""
|
||||||
|
>(UpdateUserCollectionOrderDocument, {
|
||||||
|
collectionID,
|
||||||
|
nextCollectionID,
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const getUserRootCollections = () =>
|
||||||
|
runGQLQuery<
|
||||||
|
GetUserRootCollectionsQuery,
|
||||||
|
GetUserRootCollectionsQueryVariables,
|
||||||
|
""
|
||||||
|
>({
|
||||||
|
query: GetUserRootCollectionsDocument,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getGQLRootUserCollections = () =>
|
||||||
|
runGQLQuery<
|
||||||
|
GetGqlRootUserCollectionsQuery,
|
||||||
|
GetGqlRootUserCollectionsQueryVariables,
|
||||||
|
""
|
||||||
|
>({
|
||||||
|
query: GetGqlRootUserCollectionsDocument,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const exportUserCollectionsToJSON = (
|
||||||
|
collectionID?: string,
|
||||||
|
collectionType: ReqType.Rest | ReqType.Gql = ReqType.Rest
|
||||||
|
) =>
|
||||||
|
runGQLQuery<
|
||||||
|
ExportUserCollectionsToJsonQuery,
|
||||||
|
ExportUserCollectionsToJsonQueryVariables,
|
||||||
|
""
|
||||||
|
>({
|
||||||
|
query: ExportUserCollectionsToJsonDocument,
|
||||||
|
variables: { collectionID, collectionType },
|
||||||
|
})
|
||||||
|
|
||||||
|
export const runUserCollectionCreatedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserCollectionCreatedDocument })
|
||||||
|
|
||||||
|
export const runUserCollectionUpdatedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserCollectionUpdatedDocument })
|
||||||
|
|
||||||
|
export const runUserCollectionRemovedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserCollectionRemovedDocument })
|
||||||
|
|
||||||
|
export const runUserCollectionMovedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserCollectionMovedDocument })
|
||||||
|
|
||||||
|
export const runUserCollectionOrderUpdatedSubscription = () =>
|
||||||
|
runGQLSubscription({
|
||||||
|
query: UserCollectionOrderUpdatedDocument,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const runUserRequestCreatedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserRequestCreatedDocument })
|
||||||
|
|
||||||
|
export const runUserRequestUpdatedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserRequestUpdatedDocument })
|
||||||
|
|
||||||
|
export const runUserRequestMovedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserRequestMovedDocument })
|
||||||
|
|
||||||
|
export const runUserRequestDeletedSubscription = () =>
|
||||||
|
runGQLSubscription({ query: UserRequestDeletedDocument })
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
import {
|
||||||
|
graphqlCollectionStore,
|
||||||
|
navigateToFolderWithIndexPath,
|
||||||
|
restCollectionStore,
|
||||||
|
} from "@hoppscotch/common/newstore/collections"
|
||||||
|
import { createMapper } from "../../lib/sync/mapper"
|
||||||
|
import {
|
||||||
|
restCollectionsMapper,
|
||||||
|
collectionReorderOrMovingOperations,
|
||||||
|
restRequestsMapper,
|
||||||
|
} from "./collections.sync"
|
||||||
|
import { gqlCollectionsMapper, gqlRequestsMapper } from "./gqlCollections.sync"
|
||||||
|
|
||||||
|
function reorderItems(array: unknown[], from: number, to: number) {
|
||||||
|
const item = array.splice(from, 1)[0]
|
||||||
|
if (from < to) {
|
||||||
|
array.splice(to - 1, 0, item)
|
||||||
|
} else {
|
||||||
|
array.splice(to, 0, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestType = "REST" | "GQL"
|
||||||
|
|
||||||
|
export function moveCollectionInMapper(
|
||||||
|
folderPath: string,
|
||||||
|
destinationPath?: string,
|
||||||
|
collectionType: RequestType = "REST"
|
||||||
|
) {
|
||||||
|
const indexes = folderPath.split("/")
|
||||||
|
indexes.pop()
|
||||||
|
const collectionPath = indexes.join("/")
|
||||||
|
|
||||||
|
const { collectionsMapper, requestsMapper, collectionStore } =
|
||||||
|
getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
// Store the backend id of the folder to move for adding it to the destinationPath
|
||||||
|
const collectionToMoveBackendID =
|
||||||
|
collectionsMapper.getBackendIDByLocalID(folderPath)
|
||||||
|
|
||||||
|
// Remove the request from its current position
|
||||||
|
collectionsMapper.removeEntry(undefined, folderPath)
|
||||||
|
|
||||||
|
// We are assuming moveRequestInMapper is called after the item is moved in the store,
|
||||||
|
// so we'll fetch the index of the last added item + 1 to add to the mapper
|
||||||
|
// but in the case of the same parent, the destinationPath will change
|
||||||
|
// eg:
|
||||||
|
// 0. Collection 0
|
||||||
|
// 1. Collection 1
|
||||||
|
// in the above example, if we move Collection 0 to Collection 1 ( folderPath: 0, destinationPath: 1 ),
|
||||||
|
// the effective index of Collection 1, when using navigateToFolderWithIndexPath will be 0
|
||||||
|
// so we check if the moving is between same parent folders / collections and adds a workaround for this
|
||||||
|
const isSameParentPath =
|
||||||
|
getParentPathFromPath(folderPath) == getParentPathFromPath(destinationPath)
|
||||||
|
|
||||||
|
let changedDestinationPath: string | undefined
|
||||||
|
|
||||||
|
if (isSameParentPath) {
|
||||||
|
const lastFolderPathIndex = folderPath.split("/").pop()
|
||||||
|
const folderIndex = lastFolderPathIndex && parseInt(lastFolderPathIndex)
|
||||||
|
|
||||||
|
const lastDestinationPathIndex =
|
||||||
|
destinationPath && destinationPath.split("/").pop()
|
||||||
|
const destinationIndex =
|
||||||
|
destinationPath &&
|
||||||
|
lastDestinationPathIndex &&
|
||||||
|
parseInt(lastDestinationPathIndex)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(folderIndex == 0 || folderIndex) &&
|
||||||
|
(destinationIndex == 0 || destinationIndex) &&
|
||||||
|
folderIndex < destinationIndex
|
||||||
|
) {
|
||||||
|
const destinationParentPath = getParentPathFromPath(destinationPath)
|
||||||
|
changedDestinationPath = destinationParentPath
|
||||||
|
? `${destinationParentPath}/${destinationIndex - 1}`
|
||||||
|
: `${destinationIndex - 1}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationFolder =
|
||||||
|
changedDestinationPath &&
|
||||||
|
navigateToFolderWithIndexPath(
|
||||||
|
collectionStore.value.state,
|
||||||
|
changedDestinationPath.split("/").map((pathIndex) => parseInt(pathIndex))
|
||||||
|
)
|
||||||
|
|
||||||
|
const destinationCollectionID =
|
||||||
|
destinationPath && collectionsMapper.getBackendIDByLocalID(destinationPath)
|
||||||
|
|
||||||
|
if (destinationFolder && collectionToMoveBackendID) {
|
||||||
|
const destinationIndex = destinationFolder.folders.length
|
||||||
|
|
||||||
|
const newPath = `${destinationPath}/${destinationIndex}`
|
||||||
|
collectionsMapper.addEntry(newPath, collectionToMoveBackendID)
|
||||||
|
|
||||||
|
changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType)
|
||||||
|
|
||||||
|
collectionToMoveBackendID &&
|
||||||
|
collectionReorderOrMovingOperations.push({
|
||||||
|
sourceCollectionID: collectionToMoveBackendID,
|
||||||
|
destinationCollectionID,
|
||||||
|
reorderOperation: {
|
||||||
|
fromPath: folderPath,
|
||||||
|
toPath: `${changedDestinationPath}/${destinationIndex}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// destinationPath won't be there, when moving to the root
|
||||||
|
if (!destinationPath && collectionToMoveBackendID) {
|
||||||
|
const destinationIndex = collectionStore.value.state.length
|
||||||
|
const newPath = `${destinationIndex}`
|
||||||
|
|
||||||
|
collectionsMapper.addEntry(newPath, collectionToMoveBackendID)
|
||||||
|
|
||||||
|
changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderIndexesAfterEntryRemoval(
|
||||||
|
collectionPath,
|
||||||
|
collectionsMapper,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
reorderIndexesAfterEntryRemoval(
|
||||||
|
collectionPath,
|
||||||
|
requestsMapper,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveRequestInMapper(
|
||||||
|
requestIndex: number,
|
||||||
|
path: string,
|
||||||
|
destinationPath: string,
|
||||||
|
requestType: RequestType
|
||||||
|
) {
|
||||||
|
const { collectionStore, requestsMapper } =
|
||||||
|
getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
// Store the backend id of the request to move for adding it to the destinationPath
|
||||||
|
const requestToMoveBackendID = requestsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}/${requestIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove the request from its current position
|
||||||
|
requestsMapper.removeEntry(undefined, `${path}/${requestIndex}`)
|
||||||
|
reorderIndexesAfterEntryRemoval(path, requestsMapper, requestType)
|
||||||
|
|
||||||
|
// We are assuming moveRequestInMapper is called after the item is moved in the store,
|
||||||
|
// so we'll fetch the index of the last added item + 1 to add to the mapper
|
||||||
|
const destinationFolder = navigateToFolderWithIndexPath(
|
||||||
|
collectionStore.value.state,
|
||||||
|
destinationPath.split("/").map((pathIndex) => parseInt(pathIndex))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (destinationFolder && requestToMoveBackendID) {
|
||||||
|
const destinationIndex = destinationFolder.requests.length
|
||||||
|
|
||||||
|
requestsMapper.addEntry(
|
||||||
|
`${destinationPath}/${destinationIndex}`,
|
||||||
|
requestToMoveBackendID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we allow reordering in the same parent collection right now
|
||||||
|
export function reorderRequestsMapper(
|
||||||
|
requestIndex: number,
|
||||||
|
path: string,
|
||||||
|
nextRequestIndex: number,
|
||||||
|
requestType: RequestType
|
||||||
|
) {
|
||||||
|
const { requestsMapper } = getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const directChildren = getDirectChildrenEntriesFromMapper(
|
||||||
|
path,
|
||||||
|
requestsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
reorderItems(directChildren, requestIndex, nextRequestIndex)
|
||||||
|
|
||||||
|
directChildren.forEach((item, index) => {
|
||||||
|
item[1] && requestsMapper.addEntry(`${path}/${index}`, item[1])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// we allow reordering in the same parent collection right now
|
||||||
|
|
||||||
|
export function reorderCollectionsInMapper(
|
||||||
|
collectionPath: string,
|
||||||
|
destinationCollectionPath: string,
|
||||||
|
requestType: RequestType
|
||||||
|
) {
|
||||||
|
const { requestsMapper, collectionsMapper } =
|
||||||
|
getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const indexes = collectionPath.split("/")
|
||||||
|
indexes.pop()
|
||||||
|
const parentCollectionPath = indexes.join("/")
|
||||||
|
|
||||||
|
const directChildren = getDirectChildrenEntriesFromMapper(
|
||||||
|
parentCollectionPath,
|
||||||
|
collectionsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
const collectionIndex = collectionPath.split("/").pop()
|
||||||
|
const destinationIndex = destinationCollectionPath.split("/").pop()
|
||||||
|
|
||||||
|
collectionIndex &&
|
||||||
|
destinationIndex &&
|
||||||
|
reorderItems(
|
||||||
|
directChildren,
|
||||||
|
parentCollectionPath
|
||||||
|
? parseInt(collectionIndex)
|
||||||
|
: parseInt(collectionPath),
|
||||||
|
parentCollectionPath
|
||||||
|
? parseInt(destinationIndex)
|
||||||
|
: parseInt(destinationCollectionPath)
|
||||||
|
)
|
||||||
|
|
||||||
|
const previousCollectionEntries: Record<
|
||||||
|
string,
|
||||||
|
[string, string | undefined][]
|
||||||
|
> = {}
|
||||||
|
|
||||||
|
const previousRequestEntries: Record<string, [string, string | undefined][]> =
|
||||||
|
{}
|
||||||
|
|
||||||
|
directChildren.forEach(([path, backendID], index) => {
|
||||||
|
const newPath = parentCollectionPath
|
||||||
|
? `${parentCollectionPath}/${index}`
|
||||||
|
: `${index}`
|
||||||
|
|
||||||
|
const indexes = path.split("/")
|
||||||
|
const childIndex = indexes.pop()
|
||||||
|
|
||||||
|
if (childIndex && index != parseInt(childIndex)) {
|
||||||
|
backendID && collectionsMapper.addEntry(newPath, backendID)
|
||||||
|
|
||||||
|
const existingCollectionsOnNewPath = getChildrenEntriesFromMapper(
|
||||||
|
newPath,
|
||||||
|
collectionsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingRequestsOnNewPath = getChildrenEntriesFromMapper(
|
||||||
|
newPath,
|
||||||
|
requestsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
previousCollectionEntries[newPath] = existingCollectionsOnNewPath
|
||||||
|
previousRequestEntries[newPath] = existingRequestsOnNewPath
|
||||||
|
|
||||||
|
removeAllChildCollectionsFromMapper(newPath, requestType)
|
||||||
|
removeAllChildRequestsFromMapper(newPath, requestType)
|
||||||
|
|
||||||
|
if (path in previousCollectionEntries && path in previousRequestEntries) {
|
||||||
|
previousCollectionEntries[path].forEach(([previousPath, backendID]) => {
|
||||||
|
const pattern = new RegExp(`^(${path})\/`)
|
||||||
|
|
||||||
|
const updatedPath = previousPath.replace(pattern, `${newPath}/`)
|
||||||
|
|
||||||
|
backendID && collectionsMapper.addEntry(updatedPath, backendID)
|
||||||
|
})
|
||||||
|
|
||||||
|
previousRequestEntries[path].forEach(([previousPath, backendID]) => {
|
||||||
|
const pattern = new RegExp(`^(${path})\/`)
|
||||||
|
|
||||||
|
const updatedPath = previousPath.replace(pattern, `${newPath}/`)
|
||||||
|
|
||||||
|
backendID && requestsMapper.addEntry(updatedPath, backendID)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
changeParentForAllChildrenFromMapper(path, newPath, requestType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeAndReorderEntries(
|
||||||
|
localIndex: string,
|
||||||
|
collectionType: RequestType
|
||||||
|
) {
|
||||||
|
const { collectionsMapper, requestsMapper } =
|
||||||
|
getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
// get the collectionPath from the localIndex
|
||||||
|
const indexes = localIndex.split("/")
|
||||||
|
indexes.pop()
|
||||||
|
const collectionPath = indexes.join("/")
|
||||||
|
|
||||||
|
collectionsMapper.removeEntry(undefined, localIndex)
|
||||||
|
|
||||||
|
removeAllChildCollectionsFromMapper(localIndex, collectionType)
|
||||||
|
removeAllChildRequestsFromMapper(localIndex, collectionType)
|
||||||
|
|
||||||
|
reorderIndexesAfterEntryRemoval(
|
||||||
|
collectionPath,
|
||||||
|
collectionsMapper,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
reorderIndexesAfterEntryRemoval(
|
||||||
|
collectionPath,
|
||||||
|
requestsMapper,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeAllChildRequestsFromMapper(
|
||||||
|
collectionPath: string,
|
||||||
|
requestType: RequestType
|
||||||
|
) {
|
||||||
|
const { requestsMapper } = getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const childRequestMapperEntries = getChildrenEntriesFromMapper(
|
||||||
|
collectionPath,
|
||||||
|
requestsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
childRequestMapperEntries.forEach(([path]) => {
|
||||||
|
typeof path == "string" && requestsMapper.removeEntry(undefined, path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeAllChildCollectionsFromMapper(
|
||||||
|
collectionPath: string,
|
||||||
|
collectionType: RequestType
|
||||||
|
) {
|
||||||
|
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
const childCollectionMapperEntries = getChildrenEntriesFromMapper(
|
||||||
|
collectionPath,
|
||||||
|
collectionsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
childCollectionMapperEntries.forEach(([path]) => {
|
||||||
|
typeof path == "string" && collectionsMapper.removeEntry(undefined, path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeParentForAllChildrenFromMapper(
|
||||||
|
currentParentPath: string,
|
||||||
|
newParentPath: string,
|
||||||
|
collectionType: RequestType
|
||||||
|
) {
|
||||||
|
const { collectionsMapper, requestsMapper } =
|
||||||
|
getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
const childCollectionsMapperEntries = getChildrenEntriesFromMapper(
|
||||||
|
currentParentPath,
|
||||||
|
collectionsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
const childRequestsMapperEntries = getChildrenEntriesFromMapper(
|
||||||
|
currentParentPath,
|
||||||
|
requestsMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
const pattern = new RegExp(`^(${currentParentPath})`)
|
||||||
|
|
||||||
|
childCollectionsMapperEntries.forEach(([path, backendID]) => {
|
||||||
|
const newPath =
|
||||||
|
typeof path == "string" && path.replace(pattern, newParentPath)
|
||||||
|
|
||||||
|
if (newPath && typeof backendID == "string") {
|
||||||
|
collectionsMapper.removeEntry(undefined, path)
|
||||||
|
collectionsMapper.addEntry(newPath, backendID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
childRequestsMapperEntries.forEach(([path, backendID]) => {
|
||||||
|
const newPath =
|
||||||
|
typeof path == "string" && path.replace(pattern, newParentPath)
|
||||||
|
|
||||||
|
if (newPath && typeof backendID == "string") {
|
||||||
|
requestsMapper.removeEntry(undefined, path)
|
||||||
|
requestsMapper.addEntry(newPath, backendID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChildrenEntriesFromMapper(
|
||||||
|
path: string,
|
||||||
|
mapper: ReturnType<typeof createMapper<string, string>>
|
||||||
|
) {
|
||||||
|
let mapperEntries = Array.from(mapper.getValue().entries())
|
||||||
|
|
||||||
|
// if there are no path( eg: "" ), all the entries are children, so return the entire mapperEntries without filtering
|
||||||
|
if (!path) return mapperEntries
|
||||||
|
|
||||||
|
mapperEntries = mapperEntries.filter((entry) => {
|
||||||
|
const pattern = new RegExp(`^${path}\/(\\w+)\/?.*$`)
|
||||||
|
|
||||||
|
return !!(typeof entry[0] == "string" && entry[0].match(pattern))
|
||||||
|
})
|
||||||
|
|
||||||
|
return mapperEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDirectChildrenEntriesFromMapper(
|
||||||
|
path: string,
|
||||||
|
mapper: ReturnType<typeof createMapper<string, string>>
|
||||||
|
) {
|
||||||
|
let mapperEntries = Array.from(mapper.getValue().entries())
|
||||||
|
|
||||||
|
mapperEntries = mapperEntries.filter((entry) => {
|
||||||
|
const pattern = new RegExp(path ? `^${path}\/\\d+$` : `^\\d+$`)
|
||||||
|
|
||||||
|
return !!(typeof entry[0] == "string" && entry[0].match(pattern))
|
||||||
|
})
|
||||||
|
|
||||||
|
return mapperEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reorderIndexesAfterEntryRemoval(
|
||||||
|
pathToReorder: string,
|
||||||
|
mapper: ReturnType<typeof createMapper<string, string>>,
|
||||||
|
requestType: RequestType
|
||||||
|
) {
|
||||||
|
const directChildren = getDirectChildrenEntriesFromMapper(
|
||||||
|
pathToReorder,
|
||||||
|
mapper
|
||||||
|
)
|
||||||
|
|
||||||
|
directChildren.forEach(([path, backendID], index) => {
|
||||||
|
const indexes = path.split("/").map((index) => parseInt(index))
|
||||||
|
const childIndex = indexes.pop()
|
||||||
|
const collectionPath = indexes.join("/")
|
||||||
|
|
||||||
|
if (childIndex != index && backendID) {
|
||||||
|
const newPath = collectionPath ? `${collectionPath}/${index}` : `${index}`
|
||||||
|
|
||||||
|
mapper.removeEntry(undefined, path)
|
||||||
|
mapper.addEntry(newPath, backendID)
|
||||||
|
changeParentForAllChildrenFromMapper(path, newPath, requestType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParentPathFromPath(path: string | undefined) {
|
||||||
|
const indexes = path ? path.split("/") : []
|
||||||
|
indexes.pop()
|
||||||
|
|
||||||
|
return indexes.join("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMappersAndStoreByType(type: "GQL" | "REST") {
|
||||||
|
const isGQL = type == "GQL"
|
||||||
|
|
||||||
|
const collectionsMapper = isGQL ? gqlCollectionsMapper : restCollectionsMapper
|
||||||
|
|
||||||
|
const requestsMapper = isGQL ? gqlRequestsMapper : restRequestsMapper
|
||||||
|
|
||||||
|
const collectionStore = isGQL ? graphqlCollectionStore : restCollectionStore
|
||||||
|
|
||||||
|
return { collectionsMapper, requestsMapper, collectionStore }
|
||||||
|
}
|
||||||
@@ -0,0 +1,824 @@
|
|||||||
|
import { authEvents$, def as platformAuth } from "@platform/auth"
|
||||||
|
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
|
||||||
|
import { runDispatchWithOutSyncing } from "../../lib/sync"
|
||||||
|
|
||||||
|
import {
|
||||||
|
exportUserCollectionsToJSON,
|
||||||
|
runUserCollectionCreatedSubscription,
|
||||||
|
runUserCollectionMovedSubscription,
|
||||||
|
runUserCollectionOrderUpdatedSubscription,
|
||||||
|
runUserCollectionRemovedSubscription,
|
||||||
|
runUserCollectionUpdatedSubscription,
|
||||||
|
runUserRequestCreatedSubscription,
|
||||||
|
runUserRequestDeletedSubscription,
|
||||||
|
runUserRequestMovedSubscription,
|
||||||
|
runUserRequestUpdatedSubscription,
|
||||||
|
} from "./collections.api"
|
||||||
|
import {
|
||||||
|
collectionReorderOrMovingOperations,
|
||||||
|
collectionsSyncer,
|
||||||
|
restCollectionsOperations,
|
||||||
|
restCollectionsMapper,
|
||||||
|
restRequestsMapper,
|
||||||
|
} from "./collections.sync"
|
||||||
|
import {
|
||||||
|
moveCollectionInMapper,
|
||||||
|
removeAndReorderEntries,
|
||||||
|
reorderIndexesAfterEntryRemoval,
|
||||||
|
reorderCollectionsInMapper,
|
||||||
|
getMappersAndStoreByType,
|
||||||
|
} from "./collections.mapper"
|
||||||
|
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import {
|
||||||
|
addRESTCollection,
|
||||||
|
setRESTCollections,
|
||||||
|
editRESTCollection,
|
||||||
|
removeRESTCollection,
|
||||||
|
moveRESTFolder,
|
||||||
|
updateRESTCollectionOrder,
|
||||||
|
saveRESTRequestAs,
|
||||||
|
navigateToFolderWithIndexPath,
|
||||||
|
editRESTRequest,
|
||||||
|
removeRESTRequest,
|
||||||
|
moveRESTRequest,
|
||||||
|
updateRESTRequestOrder,
|
||||||
|
addRESTFolder,
|
||||||
|
editRESTFolder,
|
||||||
|
removeRESTFolder,
|
||||||
|
addGraphqlFolder,
|
||||||
|
addGraphqlCollection,
|
||||||
|
editGraphqlFolder,
|
||||||
|
editGraphqlCollection,
|
||||||
|
removeGraphqlFolder,
|
||||||
|
removeGraphqlCollection,
|
||||||
|
saveGraphqlRequestAs,
|
||||||
|
editGraphqlRequest,
|
||||||
|
moveGraphqlRequest,
|
||||||
|
removeGraphqlRequest,
|
||||||
|
setGraphqlCollections,
|
||||||
|
} from "@hoppscotch/common/newstore/collections"
|
||||||
|
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
|
||||||
|
import {
|
||||||
|
HoppCollection,
|
||||||
|
HoppGQLRequest,
|
||||||
|
HoppRESTRequest,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import {
|
||||||
|
gqlCollectionsOperations,
|
||||||
|
gqlCollectionsSyncer,
|
||||||
|
} from "./gqlCollections.sync"
|
||||||
|
import { ReqType } from "../../api/generated/graphql"
|
||||||
|
|
||||||
|
function initCollectionsSync() {
|
||||||
|
const currentUser$ = platformAuth.getCurrentUserStream()
|
||||||
|
collectionsSyncer.startStoreSync()
|
||||||
|
collectionsSyncer.setupSubscriptions(setupSubscriptions)
|
||||||
|
|
||||||
|
gqlCollectionsSyncer.startStoreSync()
|
||||||
|
|
||||||
|
loadUserRootCollections("REST")
|
||||||
|
loadUserRootCollections("GQL")
|
||||||
|
|
||||||
|
// TODO: test & make sure the auth thing is working properly
|
||||||
|
currentUser$.subscribe(async (user) => {
|
||||||
|
if (user) {
|
||||||
|
loadUserRootCollections("REST")
|
||||||
|
loadUserRootCollections("GQL")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
authEvents$.subscribe((event) => {
|
||||||
|
if (event.event == "login" || event.event == "token_refresh") {
|
||||||
|
collectionsSyncer.startListeningToSubscriptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event == "logout") {
|
||||||
|
collectionsSyncer.stopListeningToSubscriptions()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportedUserCollectionREST = {
|
||||||
|
id?: string
|
||||||
|
folders: ExportedUserCollectionREST[]
|
||||||
|
requests: Array<HoppRESTRequest & { id: string }>
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportedUserCollectionGQL = {
|
||||||
|
id?: string
|
||||||
|
folders: ExportedUserCollectionGQL[]
|
||||||
|
requests: Array<HoppGQLRequest & { id: string }>
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportedCollectionToHoppCollection(
|
||||||
|
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
|
||||||
|
collectionType: "REST" | "GQL"
|
||||||
|
): HoppCollection<HoppRESTRequest | HoppGQLRequest> {
|
||||||
|
if (collectionType == "REST") {
|
||||||
|
const restCollection = collection as ExportedUserCollectionREST
|
||||||
|
|
||||||
|
return {
|
||||||
|
v: 1,
|
||||||
|
name: restCollection.name,
|
||||||
|
folders: restCollection.folders.map((folder) =>
|
||||||
|
exportedCollectionToHoppCollection(folder, collectionType)
|
||||||
|
),
|
||||||
|
requests: restCollection.requests.map(
|
||||||
|
({
|
||||||
|
v,
|
||||||
|
auth,
|
||||||
|
body,
|
||||||
|
endpoint,
|
||||||
|
headers,
|
||||||
|
method,
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
preRequestScript,
|
||||||
|
testScript,
|
||||||
|
}) => ({
|
||||||
|
v,
|
||||||
|
auth,
|
||||||
|
body,
|
||||||
|
endpoint,
|
||||||
|
headers,
|
||||||
|
method,
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
preRequestScript,
|
||||||
|
testScript,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||||
|
|
||||||
|
return {
|
||||||
|
v: 1,
|
||||||
|
name: gqlCollection.name,
|
||||||
|
folders: gqlCollection.folders.map((folder) =>
|
||||||
|
exportedCollectionToHoppCollection(folder, collectionType)
|
||||||
|
),
|
||||||
|
requests: gqlCollection.requests.map(({ v, auth, headers, name }) => ({
|
||||||
|
v,
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
name,
|
||||||
|
})) as HoppGQLRequest[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMapperEntriesForExportedCollection(
|
||||||
|
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
|
||||||
|
localPath: string,
|
||||||
|
collectionType: "REST" | "GQL"
|
||||||
|
) {
|
||||||
|
const { collectionsMapper, requestsMapper } =
|
||||||
|
getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
if (collection.id) {
|
||||||
|
collectionsMapper.addEntry(localPath, collection.id)
|
||||||
|
|
||||||
|
collection.folders.forEach((folder, index) => {
|
||||||
|
addMapperEntriesForExportedCollection(
|
||||||
|
folder,
|
||||||
|
`${localPath}/${index}`,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
collection.requests.forEach((request, index) => {
|
||||||
|
const requestID = request.id
|
||||||
|
|
||||||
|
requestID && requestsMapper.addEntry(`${localPath}/${index}`, requestID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadUserRootCollections(collectionType: "REST" | "GQL") {
|
||||||
|
const res = await exportUserCollectionsToJSON(
|
||||||
|
undefined,
|
||||||
|
collectionType == "REST" ? ReqType.Rest : ReqType.Gql
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const collectionsJSONString =
|
||||||
|
res.right.exportUserCollectionsToJSON.exportedCollection
|
||||||
|
const exportedCollections = (
|
||||||
|
JSON.parse(collectionsJSONString) as Array<
|
||||||
|
ExportedUserCollectionGQL | ExportedUserCollectionREST
|
||||||
|
>
|
||||||
|
).map((collection) => ({ v: 1, ...collection }))
|
||||||
|
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "REST"
|
||||||
|
? setRESTCollections(
|
||||||
|
exportedCollections.map(
|
||||||
|
(collection) =>
|
||||||
|
exportedCollectionToHoppCollection(
|
||||||
|
collection,
|
||||||
|
"REST"
|
||||||
|
) as HoppCollection<HoppRESTRequest>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: setGraphqlCollections(
|
||||||
|
exportedCollections.map(
|
||||||
|
(collection) =>
|
||||||
|
exportedCollectionToHoppCollection(
|
||||||
|
collection,
|
||||||
|
"GQL"
|
||||||
|
) as HoppCollection<HoppGQLRequest>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
exportedCollections.forEach((collection, index) =>
|
||||||
|
addMapperEntriesForExportedCollection(
|
||||||
|
collection,
|
||||||
|
`${index}`,
|
||||||
|
collectionType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSubscriptions() {
|
||||||
|
let subs: ReturnType<typeof runGQLSubscription>[1][] = []
|
||||||
|
|
||||||
|
const userCollectionCreatedSub = setupUserCollectionCreatedSubscription()
|
||||||
|
const userCollectionUpdatedSub = setupUserCollectionUpdatedSubscription()
|
||||||
|
const userCollectionRemovedSub = setupUserCollectionRemovedSubscription()
|
||||||
|
const userCollectionMovedSub = setupUserCollectionMovedSubscription()
|
||||||
|
const userCollectionOrderUpdatedSub =
|
||||||
|
setupUserCollectionOrderUpdatedSubscription()
|
||||||
|
const userRequestCreatedSub = setupUserRequestCreatedSubscription()
|
||||||
|
const userRequestUpdatedSub = setupUserRequestUpdatedSubscription()
|
||||||
|
const userRequestDeletedSub = setupUserRequestDeletedSubscription()
|
||||||
|
const userRequestMovedSub = setupUserRequestMovedSubscription()
|
||||||
|
|
||||||
|
subs = [
|
||||||
|
userCollectionCreatedSub,
|
||||||
|
userCollectionUpdatedSub,
|
||||||
|
userCollectionRemovedSub,
|
||||||
|
userCollectionMovedSub,
|
||||||
|
userCollectionOrderUpdatedSub,
|
||||||
|
userRequestCreatedSub,
|
||||||
|
userRequestUpdatedSub,
|
||||||
|
userRequestDeletedSub,
|
||||||
|
userRequestMovedSub,
|
||||||
|
]
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subs.forEach((sub) => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserCollectionCreatedSubscription() {
|
||||||
|
const [userCollectionCreated$, userCollectionCreatedSub] =
|
||||||
|
runUserCollectionCreatedSubscription()
|
||||||
|
|
||||||
|
userCollectionCreated$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const collectionType = res.right.userCollectionCreated.type
|
||||||
|
|
||||||
|
const { collectionsMapper, collectionStore } =
|
||||||
|
getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
const userCollectionBackendID = res.right.userCollectionCreated.id
|
||||||
|
const parentCollectionID = res.right.userCollectionCreated.parent?.id
|
||||||
|
|
||||||
|
const userCollectionLocalID = collectionsMapper.getLocalIDByBackendID(
|
||||||
|
userCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
// collection already exists in store ( this instance created it )
|
||||||
|
if (userCollectionLocalID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCollectionPath =
|
||||||
|
parentCollectionID &&
|
||||||
|
collectionsMapper.getLocalIDByBackendID(parentCollectionID)
|
||||||
|
|
||||||
|
// only folders will have parent collection id
|
||||||
|
if (parentCollectionID && parentCollectionPath) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "GQL"
|
||||||
|
? addGraphqlFolder(
|
||||||
|
res.right.userCollectionCreated.title,
|
||||||
|
parentCollectionPath
|
||||||
|
)
|
||||||
|
: addRESTFolder(
|
||||||
|
res.right.userCollectionCreated.title,
|
||||||
|
parentCollectionPath
|
||||||
|
)
|
||||||
|
|
||||||
|
const parentCollection = navigateToFolderWithIndexPath(
|
||||||
|
collectionStore.value.state,
|
||||||
|
parentCollectionPath
|
||||||
|
.split("/")
|
||||||
|
.map((pathIndex) => parseInt(pathIndex))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
const folderIndex = parentCollection.folders.length - 1
|
||||||
|
collectionsMapper.addEntry(
|
||||||
|
`${parentCollectionPath}/${folderIndex}`,
|
||||||
|
userCollectionBackendID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// root collections won't have parentCollectionID
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "GQL"
|
||||||
|
? addGraphqlCollection({
|
||||||
|
name: res.right.userCollectionCreated.title,
|
||||||
|
folders: [],
|
||||||
|
requests: [],
|
||||||
|
v: 1,
|
||||||
|
})
|
||||||
|
: addRESTCollection({
|
||||||
|
name: res.right.userCollectionCreated.title,
|
||||||
|
folders: [],
|
||||||
|
requests: [],
|
||||||
|
v: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const localIndex = collectionStore.value.state.length - 1
|
||||||
|
collectionsMapper.addEntry(`${localIndex}`, userCollectionBackendID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userCollectionCreatedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserCollectionUpdatedSubscription() {
|
||||||
|
const [userCollectionUpdated$, userCollectionUpdatedSub] =
|
||||||
|
runUserCollectionUpdatedSubscription()
|
||||||
|
|
||||||
|
userCollectionUpdated$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const collectionType = res.right.userCollectionUpdated.type
|
||||||
|
|
||||||
|
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
const updatedCollectionBackendID = res.right.userCollectionUpdated.id
|
||||||
|
const updatedCollectionLocalPath =
|
||||||
|
collectionsMapper.getLocalIDByBackendID(updatedCollectionBackendID)
|
||||||
|
|
||||||
|
const isFolder =
|
||||||
|
updatedCollectionLocalPath &&
|
||||||
|
updatedCollectionLocalPath.split("/").length > 1
|
||||||
|
|
||||||
|
// updated collection is a folder
|
||||||
|
if (isFolder) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "REST"
|
||||||
|
? editRESTFolder(updatedCollectionLocalPath, {
|
||||||
|
name: res.right.userCollectionUpdated.title,
|
||||||
|
})
|
||||||
|
: editGraphqlFolder(updatedCollectionLocalPath, {
|
||||||
|
name: res.right.userCollectionUpdated.title,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// updated collection is a root collection
|
||||||
|
if (updatedCollectionLocalPath && !isFolder) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "REST"
|
||||||
|
? editRESTCollection(parseInt(updatedCollectionLocalPath), {
|
||||||
|
name: res.right.userCollectionUpdated.title,
|
||||||
|
})
|
||||||
|
: editGraphqlCollection(parseInt(updatedCollectionLocalPath), {
|
||||||
|
name: res.right.userCollectionUpdated.title,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userCollectionUpdatedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserCollectionMovedSubscription() {
|
||||||
|
const [userCollectionMoved$, userCollectionMovedSub] =
|
||||||
|
runUserCollectionMovedSubscription()
|
||||||
|
|
||||||
|
userCollectionMoved$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const movedMetadata = res.right.userCollectionMoved
|
||||||
|
|
||||||
|
const sourcePath = restCollectionsMapper.getLocalIDByBackendID(
|
||||||
|
movedMetadata.id
|
||||||
|
)
|
||||||
|
|
||||||
|
let destinationPath: string | undefined
|
||||||
|
|
||||||
|
if (movedMetadata.parent?.id) {
|
||||||
|
destinationPath = restCollectionsMapper.getLocalIDByBackendID(
|
||||||
|
movedMetadata.parent?.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened(
|
||||||
|
{
|
||||||
|
sourceCollectionID: movedMetadata.id,
|
||||||
|
destinationCollectionID: movedMetadata.parent?.id,
|
||||||
|
sourcePath,
|
||||||
|
destinationPath,
|
||||||
|
},
|
||||||
|
"MOVING"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hasAlreadyHappened) {
|
||||||
|
sourcePath &&
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
moveRESTFolder(sourcePath, destinationPath ?? null)
|
||||||
|
})
|
||||||
|
|
||||||
|
sourcePath &&
|
||||||
|
moveCollectionInMapper(sourcePath, destinationPath, "REST")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userCollectionMovedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserCollectionRemovedSubscription() {
|
||||||
|
const [userCollectionRemoved$, userCollectionRemovedSub] =
|
||||||
|
runUserCollectionRemovedSubscription()
|
||||||
|
|
||||||
|
userCollectionRemoved$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const removedCollectionBackendID = res.right.userCollectionRemoved.id
|
||||||
|
const collectionType = res.right.userCollectionRemoved.type
|
||||||
|
|
||||||
|
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
|
||||||
|
|
||||||
|
const collectionsOperations =
|
||||||
|
collectionType == "REST"
|
||||||
|
? restCollectionsOperations
|
||||||
|
: gqlCollectionsOperations
|
||||||
|
|
||||||
|
const removedCollectionLocalPath =
|
||||||
|
collectionsMapper.getLocalIDByBackendID(removedCollectionBackendID)
|
||||||
|
|
||||||
|
// TODO: seperate operations for rest and gql
|
||||||
|
const isInOperations = !!collectionsOperations.find(
|
||||||
|
(operation) =>
|
||||||
|
operation.type == "COLLECTION_REMOVED" &&
|
||||||
|
operation.collectionBackendID == removedCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
// the collection is already removed
|
||||||
|
if (!removedCollectionLocalPath || isInOperations) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFolder =
|
||||||
|
removedCollectionLocalPath &&
|
||||||
|
removedCollectionLocalPath.split("/").length > 1
|
||||||
|
|
||||||
|
if (removedCollectionLocalPath && isFolder) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "REST"
|
||||||
|
? removeRESTFolder(removedCollectionLocalPath)
|
||||||
|
: removeGraphqlFolder(removedCollectionLocalPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedCollectionLocalPath && !isFolder) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
collectionType == "REST"
|
||||||
|
? removeRESTCollection(parseInt(removedCollectionLocalPath))
|
||||||
|
: removeGraphqlCollection(parseInt(removedCollectionLocalPath))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
removedCollectionLocalPath &&
|
||||||
|
removeAndReorderEntries(removedCollectionLocalPath, collectionType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userCollectionRemovedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserCollectionOrderUpdatedSubscription() {
|
||||||
|
const [userCollectionOrderUpdated$, userCollectionOrderUpdatedSub] =
|
||||||
|
runUserCollectionOrderUpdatedSubscription()
|
||||||
|
|
||||||
|
userCollectionOrderUpdated$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const { userCollection, nextUserCollection } =
|
||||||
|
res.right.userCollectionOrderUpdated
|
||||||
|
|
||||||
|
const sourceCollectionID = userCollection.id
|
||||||
|
const destinationCollectionID = nextUserCollection?.id
|
||||||
|
|
||||||
|
const sourcePath =
|
||||||
|
restCollectionsMapper.getLocalIDByBackendID(sourceCollectionID)
|
||||||
|
|
||||||
|
let destinationPath: string | undefined
|
||||||
|
|
||||||
|
if (destinationCollectionID) {
|
||||||
|
destinationPath = restCollectionsMapper.getLocalIDByBackendID(
|
||||||
|
destinationCollectionID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened(
|
||||||
|
{
|
||||||
|
sourceCollectionID,
|
||||||
|
destinationCollectionID,
|
||||||
|
sourcePath,
|
||||||
|
destinationPath,
|
||||||
|
},
|
||||||
|
"REORDERING"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hasAlreadyHappened) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
if (
|
||||||
|
sourcePath &&
|
||||||
|
destinationPath &&
|
||||||
|
sourceCollectionID &&
|
||||||
|
destinationCollectionID
|
||||||
|
) {
|
||||||
|
updateRESTCollectionOrder(sourcePath, destinationPath)
|
||||||
|
reorderCollectionsInMapper(sourcePath, destinationPath, "REST")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userCollectionOrderUpdatedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserRequestCreatedSubscription() {
|
||||||
|
const [userRequestCreated$, userRequestCreatedSub] =
|
||||||
|
runUserRequestCreatedSubscription()
|
||||||
|
|
||||||
|
userRequestCreated$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const collectionID = res.right.userRequestCreated.collectionID
|
||||||
|
const request = JSON.parse(res.right.userRequestCreated.request)
|
||||||
|
const requestID = res.right.userRequestCreated.id
|
||||||
|
|
||||||
|
const requestType = res.right.userRequestCreated.type
|
||||||
|
|
||||||
|
const { collectionsMapper, requestsMapper, collectionStore } =
|
||||||
|
getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const hasAlreadyHappened =
|
||||||
|
!!requestsMapper.getLocalIDByBackendID(requestID)
|
||||||
|
|
||||||
|
if (hasAlreadyHappened) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionPath =
|
||||||
|
collectionsMapper.getLocalIDByBackendID(collectionID)
|
||||||
|
|
||||||
|
if (collectionID && collectionPath) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
requestType == "REST"
|
||||||
|
? saveRESTRequestAs(collectionPath, request)
|
||||||
|
: saveGraphqlRequestAs(collectionPath, request)
|
||||||
|
|
||||||
|
const target = navigateToFolderWithIndexPath(
|
||||||
|
collectionStore.value.state,
|
||||||
|
collectionPath.split("/").map((index) => parseInt(index))
|
||||||
|
)
|
||||||
|
|
||||||
|
const requestPath =
|
||||||
|
target && `${collectionPath}/${target?.requests.length - 1}`
|
||||||
|
|
||||||
|
requestPath && requestsMapper.addEntry(requestPath, requestID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userRequestCreatedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserRequestUpdatedSubscription() {
|
||||||
|
const [userRequestUpdated$, userRequestUpdatedSub] =
|
||||||
|
runUserRequestUpdatedSubscription()
|
||||||
|
|
||||||
|
userRequestUpdated$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const requestType = res.right.userRequestUpdated.type
|
||||||
|
|
||||||
|
const { requestsMapper, collectionsMapper } =
|
||||||
|
getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const requestPath = requestsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.userRequestUpdated.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const indexes = requestPath?.split("/")
|
||||||
|
const requestIndex = indexes && indexes[indexes?.length - 1]
|
||||||
|
const requestParentPath = collectionsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.userRequestUpdated.collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
requestIndex &&
|
||||||
|
requestParentPath &&
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
requestType == "REST"
|
||||||
|
? editRESTRequest(
|
||||||
|
requestParentPath,
|
||||||
|
parseInt(requestIndex),
|
||||||
|
JSON.parse(res.right.userRequestUpdated.request)
|
||||||
|
)
|
||||||
|
: editGraphqlRequest(
|
||||||
|
requestParentPath,
|
||||||
|
parseInt(requestIndex),
|
||||||
|
JSON.parse(res.right.userRequestUpdated.request)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userRequestUpdatedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserRequestMovedSubscription() {
|
||||||
|
const [userRequestMoved$, userRequestMovedSub] =
|
||||||
|
runUserRequestMovedSubscription()
|
||||||
|
|
||||||
|
userRequestMoved$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const requestType = res.right.userRequestMoved.request.type
|
||||||
|
|
||||||
|
const { collectionsMapper } = getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const requestID = res.right.userRequestMoved.request.id
|
||||||
|
const requestIndex = getRequestIndexFromRequestID(requestID)
|
||||||
|
|
||||||
|
const sourceCollectionPath = getCollectionPathFromRequestID(requestID)
|
||||||
|
|
||||||
|
const destinationCollectionID =
|
||||||
|
res.right.userRequestMoved.request.collectionID
|
||||||
|
const destinationCollectionPath = collectionsMapper.getLocalIDByBackendID(
|
||||||
|
destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
const nextRequest = res.right.userRequestMoved.nextRequest
|
||||||
|
|
||||||
|
// there is no nextRequest, so request is moved
|
||||||
|
if (
|
||||||
|
requestIndex &&
|
||||||
|
sourceCollectionPath &&
|
||||||
|
destinationCollectionPath &&
|
||||||
|
!nextRequest
|
||||||
|
) {
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
requestType == "REST"
|
||||||
|
? moveRESTRequest(
|
||||||
|
sourceCollectionPath,
|
||||||
|
parseInt(requestIndex),
|
||||||
|
destinationCollectionPath
|
||||||
|
)
|
||||||
|
: moveGraphqlRequest(
|
||||||
|
sourceCollectionPath,
|
||||||
|
parseInt(requestIndex),
|
||||||
|
destinationCollectionPath
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is nextRequest, so request is reordered
|
||||||
|
if (
|
||||||
|
requestIndex &&
|
||||||
|
sourceCollectionPath &&
|
||||||
|
destinationCollectionPath &&
|
||||||
|
nextRequest &&
|
||||||
|
// we don't have request reordering for graphql yet
|
||||||
|
requestType == "REST"
|
||||||
|
) {
|
||||||
|
const nextRequestIndex = getRequestIndexFromRequestID(nextRequest.id)
|
||||||
|
|
||||||
|
nextRequestIndex &&
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
updateRESTRequestOrder(
|
||||||
|
parseInt(requestIndex),
|
||||||
|
parseInt(nextRequestIndex),
|
||||||
|
destinationCollectionPath
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userRequestMovedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserRequestDeletedSubscription() {
|
||||||
|
const [userRequestDeleted$, userRequestDeletedSub] =
|
||||||
|
runUserRequestDeletedSubscription()
|
||||||
|
|
||||||
|
userRequestDeleted$.subscribe((res) => {
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const requestType = res.right.userRequestDeleted.type
|
||||||
|
|
||||||
|
const { requestsMapper, collectionsMapper } =
|
||||||
|
getMappersAndStoreByType(requestType)
|
||||||
|
|
||||||
|
const deletedRequestPath = requestsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.userRequestDeleted.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const indexes = deletedRequestPath?.split("/")
|
||||||
|
const requestIndex = indexes && indexes[indexes?.length - 1]
|
||||||
|
const requestParentPath = collectionsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.userRequestDeleted.collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
requestIndex &&
|
||||||
|
requestParentPath &&
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
requestType == "REST"
|
||||||
|
? removeRESTRequest(requestParentPath, parseInt(requestIndex))
|
||||||
|
: removeGraphqlRequest(requestParentPath, parseInt(requestIndex))
|
||||||
|
})
|
||||||
|
|
||||||
|
deletedRequestPath &&
|
||||||
|
reorderIndexesAfterEntryRemoval(
|
||||||
|
deletedRequestPath,
|
||||||
|
requestsMapper,
|
||||||
|
requestType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userRequestDeletedSub
|
||||||
|
}
|
||||||
|
|
||||||
|
export const def: CollectionsPlatformDef = {
|
||||||
|
initCollectionsSync,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestIndexFromRequestID(requestID: string) {
|
||||||
|
const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* requestPath is in the form collectionPath/requestIndex,
|
||||||
|
* so to get requestIndex we just split the requestPath with / and get the last element
|
||||||
|
*/
|
||||||
|
const requestPathIndexes = requestPath?.split("/")
|
||||||
|
const requestIndex =
|
||||||
|
requestPathIndexes && requestPathIndexes[requestPathIndexes?.length - 1]
|
||||||
|
|
||||||
|
return requestIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollectionPathFromRequestID(requestID: string) {
|
||||||
|
const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID)
|
||||||
|
const requestPathIndexes = requestPath?.split("/")
|
||||||
|
|
||||||
|
// requestIndex will be the last element, remove it
|
||||||
|
requestPathIndexes?.pop()
|
||||||
|
|
||||||
|
return requestPathIndexes?.join("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasReorderingOrMovingAlreadyHappened(
|
||||||
|
incomingOperation: {
|
||||||
|
sourceCollectionID: string
|
||||||
|
destinationCollectionID: string | undefined
|
||||||
|
sourcePath: string | undefined
|
||||||
|
destinationPath: string | undefined
|
||||||
|
},
|
||||||
|
type: "REORDERING" | "MOVING"
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
sourcePath,
|
||||||
|
sourceCollectionID,
|
||||||
|
destinationCollectionID,
|
||||||
|
destinationPath,
|
||||||
|
} = incomingOperation
|
||||||
|
|
||||||
|
// TODO: implement this as a module
|
||||||
|
// Something like, SyncOperations.hasAlreadyHappened( type: "REORDER_COLLECTIONS", payload )
|
||||||
|
return !!collectionReorderOrMovingOperations.find((reorderOperation) =>
|
||||||
|
reorderOperation.sourceCollectionID == sourceCollectionID &&
|
||||||
|
reorderOperation.destinationCollectionID == destinationCollectionID &&
|
||||||
|
type == "MOVING"
|
||||||
|
? reorderOperation.reorderOperation.fromPath == destinationPath
|
||||||
|
: reorderOperation.reorderOperation.fromPath == sourcePath &&
|
||||||
|
type == "MOVING"
|
||||||
|
? reorderOperation.reorderOperation.toPath == sourcePath
|
||||||
|
: reorderOperation.reorderOperation.toPath == destinationPath
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
import {
|
||||||
|
navigateToFolderWithIndexPath,
|
||||||
|
removeGraphqlCollection,
|
||||||
|
removeRESTCollection,
|
||||||
|
removeRESTRequest,
|
||||||
|
restCollectionStore,
|
||||||
|
} from "@hoppscotch/common/newstore/collections"
|
||||||
|
import {
|
||||||
|
getSettingSubject,
|
||||||
|
settingsStore,
|
||||||
|
} from "@hoppscotch/common/newstore/settings"
|
||||||
|
|
||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync"
|
||||||
|
|
||||||
|
import { StoreSyncDefinitionOf } from "../../lib/sync"
|
||||||
|
import { createMapper } from "../../lib/sync/mapper"
|
||||||
|
import {
|
||||||
|
createRESTChildUserCollection,
|
||||||
|
createRESTRootUserCollection,
|
||||||
|
createRESTUserRequest,
|
||||||
|
deleteUserCollection,
|
||||||
|
deleteUserRequest,
|
||||||
|
editUserRequest,
|
||||||
|
moveUserCollection,
|
||||||
|
moveUserRequest,
|
||||||
|
renameUserCollection,
|
||||||
|
updateUserCollectionOrder,
|
||||||
|
} from "./collections.api"
|
||||||
|
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import {
|
||||||
|
removeAndReorderEntries,
|
||||||
|
moveCollectionInMapper,
|
||||||
|
reorderIndexesAfterEntryRemoval,
|
||||||
|
reorderCollectionsInMapper,
|
||||||
|
reorderRequestsMapper,
|
||||||
|
moveRequestInMapper,
|
||||||
|
} from "./collections.mapper"
|
||||||
|
import { gqlCollectionsMapper } from "./gqlCollections.sync"
|
||||||
|
|
||||||
|
// restCollectionsMapper uses the collectionPath as the local identifier
|
||||||
|
export const restCollectionsMapper = createMapper<string, string>()
|
||||||
|
|
||||||
|
// restRequestsMapper uses the collectionPath/requestIndex as the local identifier
|
||||||
|
export const restRequestsMapper = createMapper<string, string>()
|
||||||
|
|
||||||
|
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||||
|
// TODO: use importCollectionsJSON to do this
|
||||||
|
const recursivelySyncCollections = async (
|
||||||
|
collection: HoppCollection<HoppRESTRequest>,
|
||||||
|
collectionPath: string,
|
||||||
|
parentUserCollectionID?: string
|
||||||
|
) => {
|
||||||
|
let parentCollectionID = parentUserCollectionID
|
||||||
|
|
||||||
|
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||||
|
if (!parentUserCollectionID) {
|
||||||
|
const res = await createRESTRootUserCollection(collection.name)
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
parentCollectionID = res.right.createRESTRootUserCollection.id
|
||||||
|
restCollectionsMapper.addEntry(collectionPath, parentCollectionID)
|
||||||
|
} else {
|
||||||
|
parentCollectionID = undefined
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if parentUserCollectionID exists, create the collection as a child collection
|
||||||
|
const res = await createRESTChildUserCollection(
|
||||||
|
collection.name,
|
||||||
|
parentUserCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const childCollectionId = res.right.createRESTChildUserCollection.id
|
||||||
|
restCollectionsMapper.addEntry(collectionPath, childCollectionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the requests
|
||||||
|
if (parentCollectionID) {
|
||||||
|
collection.requests.forEach(async (request, index) => {
|
||||||
|
const res =
|
||||||
|
parentCollectionID &&
|
||||||
|
(await createRESTUserRequest(
|
||||||
|
request.name,
|
||||||
|
JSON.stringify(request),
|
||||||
|
parentCollectionID
|
||||||
|
))
|
||||||
|
|
||||||
|
if (res && E.isRight(res)) {
|
||||||
|
const requestId = res.right.createRESTUserRequest.id
|
||||||
|
restRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the folders aka child collections
|
||||||
|
if (parentCollectionID)
|
||||||
|
collection.folders.forEach(async (folder, index) => {
|
||||||
|
recursivelySyncCollections(
|
||||||
|
folder,
|
||||||
|
`${collectionPath}/${index}`,
|
||||||
|
parentCollectionID
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: generalize this
|
||||||
|
// TODO: ask backend to send enough info on the subscription to not need this
|
||||||
|
export const collectionReorderOrMovingOperations: {
|
||||||
|
sourceCollectionID: string
|
||||||
|
destinationCollectionID?: string
|
||||||
|
reorderOperation: {
|
||||||
|
fromPath: string
|
||||||
|
toPath?: string
|
||||||
|
}
|
||||||
|
}[] = []
|
||||||
|
|
||||||
|
type OperationStatus = "pending" | "completed"
|
||||||
|
|
||||||
|
type OperationCollectionRemoved = {
|
||||||
|
type: "COLLECTION_REMOVED"
|
||||||
|
collectionBackendID: string
|
||||||
|
status: OperationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export const restCollectionsOperations: Array<OperationCollectionRemoved> = []
|
||||||
|
|
||||||
|
export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||||
|
typeof restCollectionStore
|
||||||
|
> = {
|
||||||
|
appendCollections({ entries }) {
|
||||||
|
let indexStart = restCollectionStore.value.state.length - entries.length
|
||||||
|
|
||||||
|
entries.forEach((collection) => {
|
||||||
|
recursivelySyncCollections(collection, `${indexStart}`)
|
||||||
|
indexStart++
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async addCollection({ collection }) {
|
||||||
|
const lastCreatedCollectionIndex =
|
||||||
|
restCollectionStore.value.state.length - 1
|
||||||
|
|
||||||
|
await recursivelySyncCollections(
|
||||||
|
collection,
|
||||||
|
`${lastCreatedCollectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
removeDuplicateCollectionsFromStore("REST")
|
||||||
|
},
|
||||||
|
async removeCollection({ collectionIndex }) {
|
||||||
|
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${collectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (backendIdentifier) {
|
||||||
|
restCollectionsOperations.push({
|
||||||
|
collectionBackendID: backendIdentifier,
|
||||||
|
type: "COLLECTION_REMOVED",
|
||||||
|
status: "pending",
|
||||||
|
})
|
||||||
|
await deleteUserCollection(backendIdentifier)
|
||||||
|
removeAndReorderEntries(`${collectionIndex}`, "REST")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editCollection({ partialCollection: collection, collectionIndex }) {
|
||||||
|
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${collectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (backendIdentifier && collection.name) {
|
||||||
|
renameUserCollection(backendIdentifier, collection.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async addFolder({ name, path }) {
|
||||||
|
const parentCollectionBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
|
||||||
|
if (parentCollectionBackendID) {
|
||||||
|
// TODO: remove this replaceAll thing when updating the mapper
|
||||||
|
const res = await createRESTChildUserCollection(
|
||||||
|
name,
|
||||||
|
parentCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
// after the folder is created add the path of the folder with its backend id to the mapper
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const folderBackendID = res.right.createRESTChildUserCollection.id
|
||||||
|
const parentCollection = navigateToFolderWithIndexPath(
|
||||||
|
restCollectionStore.value.state,
|
||||||
|
path.split("/").map((index) => parseInt(index))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentCollection && parentCollection.folders.length > 0) {
|
||||||
|
const folderIndex = parentCollection.folders.length - 1
|
||||||
|
restCollectionsMapper.addEntry(
|
||||||
|
`${path}/${folderIndex}`,
|
||||||
|
folderBackendID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editFolder({ folder, path }) {
|
||||||
|
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const folderName = folder.name
|
||||||
|
|
||||||
|
if (folderBackendId && folderName) {
|
||||||
|
renameUserCollection(folderBackendId, folderName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeFolder({ path }) {
|
||||||
|
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (folderBackendId) {
|
||||||
|
await deleteUserCollection(folderBackendId)
|
||||||
|
removeAndReorderEntries(path, "REST")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async moveFolder({ destinationPath, path }) {
|
||||||
|
const sourceCollectionBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
|
||||||
|
const destinationCollectionBackendID = destinationPath
|
||||||
|
? restCollectionsMapper.getBackendIDByLocalID(destinationPath)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (sourceCollectionBackendID) {
|
||||||
|
await moveUserCollection(
|
||||||
|
sourceCollectionBackendID,
|
||||||
|
destinationCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
moveCollectionInMapper(path, destinationPath ?? undefined, "REST")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editRequest({ path, requestIndex, requestNew }) {
|
||||||
|
const requestPath = `${path}/${requestIndex}`
|
||||||
|
|
||||||
|
const requestBackendID =
|
||||||
|
restRequestsMapper.getBackendIDByLocalID(requestPath)
|
||||||
|
|
||||||
|
if (requestBackendID) {
|
||||||
|
editUserRequest(
|
||||||
|
requestBackendID,
|
||||||
|
(requestNew as HoppRESTRequest).name,
|
||||||
|
JSON.stringify(requestNew)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveRequestAs({ path, request }) {
|
||||||
|
const parentCollectionBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
|
||||||
|
if (parentCollectionBackendID) {
|
||||||
|
const res = await createRESTUserRequest(
|
||||||
|
(request as HoppRESTRequest).name,
|
||||||
|
JSON.stringify(request),
|
||||||
|
parentCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingPath =
|
||||||
|
E.isRight(res) &&
|
||||||
|
restRequestsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.createRESTUserRequest.id
|
||||||
|
)
|
||||||
|
|
||||||
|
// remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved )
|
||||||
|
if (existingPath) {
|
||||||
|
const indexes = existingPath.split("/")
|
||||||
|
const existingRequestIndex = indexes.pop()
|
||||||
|
const existingRequestParentPath = indexes.join("/")
|
||||||
|
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
existingRequestIndex &&
|
||||||
|
removeRESTRequest(
|
||||||
|
existingRequestParentPath,
|
||||||
|
parseInt(existingRequestIndex)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCollection = navigateToFolderWithIndexPath(
|
||||||
|
restCollectionStore.value.state,
|
||||||
|
path.split("/").map((index) => parseInt(index))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
const lastCreatedRequestIndex = parentCollection.requests.length - 1
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
restRequestsMapper.addEntry(
|
||||||
|
`${path}/${lastCreatedRequestIndex}`,
|
||||||
|
res.right.createRESTUserRequest.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeRequest({ path, requestIndex }) {
|
||||||
|
const requestPath = `${path}/${requestIndex}`
|
||||||
|
const requestBackendID =
|
||||||
|
restRequestsMapper.getBackendIDByLocalID(requestPath)
|
||||||
|
|
||||||
|
if (requestBackendID) {
|
||||||
|
await deleteUserRequest(requestBackendID)
|
||||||
|
restRequestsMapper.removeEntry(requestPath)
|
||||||
|
reorderIndexesAfterEntryRemoval(path, restRequestsMapper, "REST")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveRequest({ destinationPath, path, requestIndex }) {
|
||||||
|
moveOrReorderRequests(requestIndex, path, destinationPath)
|
||||||
|
},
|
||||||
|
updateRequestOrder({
|
||||||
|
destinationCollectionPath,
|
||||||
|
destinationRequestIndex,
|
||||||
|
requestIndex,
|
||||||
|
}) {
|
||||||
|
/**
|
||||||
|
* currently the FE implementation only supports reordering requests between the same collection,
|
||||||
|
* so destinationCollectionPath and sourceCollectionPath will be same
|
||||||
|
*/
|
||||||
|
moveOrReorderRequests(
|
||||||
|
requestIndex,
|
||||||
|
destinationCollectionPath,
|
||||||
|
destinationCollectionPath,
|
||||||
|
destinationRequestIndex
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async updateCollectionOrder({
|
||||||
|
collectionIndex: collectionPath,
|
||||||
|
destinationCollectionIndex: destinationCollectionPath,
|
||||||
|
}) {
|
||||||
|
const sourceBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(collectionPath)
|
||||||
|
|
||||||
|
const destinationBackendID = restCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
destinationCollectionPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if (sourceBackendID) {
|
||||||
|
collectionReorderOrMovingOperations.push({
|
||||||
|
sourceCollectionID: sourceBackendID,
|
||||||
|
destinationCollectionID: destinationBackendID,
|
||||||
|
reorderOperation: {
|
||||||
|
fromPath: `${parseInt(destinationCollectionPath) - 1}`,
|
||||||
|
toPath: destinationCollectionPath,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await updateUserCollectionOrder(sourceBackendID, destinationBackendID)
|
||||||
|
|
||||||
|
const currentSourcePath =
|
||||||
|
restCollectionsMapper.getLocalIDByBackendID(sourceBackendID)
|
||||||
|
|
||||||
|
const hasAlreadyHappened = !!(
|
||||||
|
currentSourcePath == `${parseInt(destinationCollectionPath) - 1}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hasAlreadyHappened) {
|
||||||
|
reorderCollectionsInMapper(
|
||||||
|
collectionPath,
|
||||||
|
destinationCollectionPath,
|
||||||
|
"REST"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collectionsSyncer = getSyncInitFunction(
|
||||||
|
restCollectionStore,
|
||||||
|
storeSyncDefinition,
|
||||||
|
() => settingsStore.value.syncCollections,
|
||||||
|
getSettingSubject("syncCollections")
|
||||||
|
)
|
||||||
|
|
||||||
|
async function moveOrReorderRequests(
|
||||||
|
requestIndex: number,
|
||||||
|
path: string,
|
||||||
|
destinationPath: string,
|
||||||
|
nextRequestIndex?: number
|
||||||
|
) {
|
||||||
|
const sourceCollectionBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
const destinationCollectionBackendID =
|
||||||
|
restCollectionsMapper.getBackendIDByLocalID(destinationPath)
|
||||||
|
|
||||||
|
const requestBackendID = restRequestsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}/${requestIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
let nextRequestBackendID: string | undefined
|
||||||
|
|
||||||
|
// we only need this for reordering requests, not for moving requests
|
||||||
|
if (nextRequestIndex) {
|
||||||
|
nextRequestBackendID = restRequestsMapper.getBackendIDByLocalID(
|
||||||
|
`${destinationPath}/${nextRequestIndex}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
sourceCollectionBackendID &&
|
||||||
|
destinationCollectionBackendID &&
|
||||||
|
requestBackendID
|
||||||
|
) {
|
||||||
|
await moveUserRequest(
|
||||||
|
sourceCollectionBackendID,
|
||||||
|
destinationCollectionBackendID,
|
||||||
|
requestBackendID,
|
||||||
|
nextRequestBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextRequestBackendID && nextRequestIndex) {
|
||||||
|
reorderRequestsMapper(requestIndex, path, nextRequestIndex, "REST")
|
||||||
|
} else {
|
||||||
|
moveRequestInMapper(requestIndex, path, destinationPath, "REST")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeDuplicateCollectionsFromStore(
|
||||||
|
collectionType: "REST" | "GQL"
|
||||||
|
) {
|
||||||
|
const collectionsMapper =
|
||||||
|
collectionType === "REST" ? restCollectionsMapper : gqlCollectionsMapper
|
||||||
|
|
||||||
|
const mapperEntries = Array.from(collectionsMapper.getValue().entries())
|
||||||
|
|
||||||
|
const seenBackendIDs = new Set<string>()
|
||||||
|
|
||||||
|
const localIDsToRemove = new Set<string>()
|
||||||
|
|
||||||
|
mapperEntries.forEach(([localID, backendID]) => {
|
||||||
|
if (backendID && seenBackendIDs.has(backendID)) {
|
||||||
|
localIDsToRemove.add(localID)
|
||||||
|
} else {
|
||||||
|
backendID && seenBackendIDs.add(backendID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
localIDsToRemove.forEach((localID) => {
|
||||||
|
collectionType === "REST"
|
||||||
|
? removeRESTCollection(parseInt(localID))
|
||||||
|
: removeGraphqlCollection(parseInt(localID))
|
||||||
|
|
||||||
|
collectionsMapper.removeEntry(undefined, localID)
|
||||||
|
|
||||||
|
const indexes = localID.split("/")
|
||||||
|
indexes.pop()
|
||||||
|
const parentPath = indexes.join("/")
|
||||||
|
|
||||||
|
reorderIndexesAfterEntryRemoval(parentPath, collectionsMapper, "REST")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
import {
|
||||||
|
graphqlCollectionStore,
|
||||||
|
navigateToFolderWithIndexPath,
|
||||||
|
removeGraphqlRequest,
|
||||||
|
} from "@hoppscotch/common/newstore/collections"
|
||||||
|
import {
|
||||||
|
getSettingSubject,
|
||||||
|
settingsStore,
|
||||||
|
} from "@hoppscotch/common/newstore/settings"
|
||||||
|
|
||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync"
|
||||||
|
|
||||||
|
import { StoreSyncDefinitionOf } from "../../lib/sync"
|
||||||
|
import { createMapper } from "../../lib/sync/mapper"
|
||||||
|
import {
|
||||||
|
createGQLChildUserCollection,
|
||||||
|
createGQLRootUserCollection,
|
||||||
|
createGQLUserRequest,
|
||||||
|
deleteUserCollection,
|
||||||
|
deleteUserRequest,
|
||||||
|
editGQLUserRequest,
|
||||||
|
moveUserRequest,
|
||||||
|
renameUserCollection,
|
||||||
|
} from "./collections.api"
|
||||||
|
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import {
|
||||||
|
moveRequestInMapper,
|
||||||
|
removeAndReorderEntries,
|
||||||
|
reorderIndexesAfterEntryRemoval,
|
||||||
|
reorderRequestsMapper,
|
||||||
|
} from "./collections.mapper"
|
||||||
|
import { removeDuplicateCollectionsFromStore } from "./collections.sync"
|
||||||
|
|
||||||
|
// gqlCollectionsMapper uses the collectionPath as the local identifier
|
||||||
|
export const gqlCollectionsMapper = createMapper<string, string>()
|
||||||
|
|
||||||
|
// gqlRequestsMapper uses the collectionPath/requestIndex as the local identifier
|
||||||
|
export const gqlRequestsMapper = createMapper<string, string>()
|
||||||
|
|
||||||
|
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||||
|
// TODO: use importCollectionsJSON to do this
|
||||||
|
const recursivelySyncCollections = async (
|
||||||
|
collection: HoppCollection<HoppRESTRequest>,
|
||||||
|
collectionPath: string,
|
||||||
|
parentUserCollectionID?: string
|
||||||
|
) => {
|
||||||
|
let parentCollectionID = parentUserCollectionID
|
||||||
|
|
||||||
|
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||||
|
if (!parentUserCollectionID) {
|
||||||
|
const res = await createGQLRootUserCollection(collection.name)
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
parentCollectionID = res.right.createGQLRootUserCollection.id
|
||||||
|
gqlCollectionsMapper.addEntry(collectionPath, parentCollectionID)
|
||||||
|
} else {
|
||||||
|
parentCollectionID = undefined
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if parentUserCollectionID exists, create the collection as a child collection
|
||||||
|
const res = await createGQLChildUserCollection(
|
||||||
|
collection.name,
|
||||||
|
parentUserCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const childCollectionId = res.right.createGQLChildUserCollection.id
|
||||||
|
gqlCollectionsMapper.addEntry(collectionPath, childCollectionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the requests
|
||||||
|
if (parentCollectionID) {
|
||||||
|
collection.requests.forEach(async (request, index) => {
|
||||||
|
const res =
|
||||||
|
parentCollectionID &&
|
||||||
|
(await createGQLUserRequest(
|
||||||
|
request.name,
|
||||||
|
JSON.stringify(request),
|
||||||
|
parentCollectionID
|
||||||
|
))
|
||||||
|
|
||||||
|
if (res && E.isRight(res)) {
|
||||||
|
const requestId = res.right.createGQLUserRequest.id
|
||||||
|
gqlRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the folders aka child collections
|
||||||
|
if (parentCollectionID)
|
||||||
|
collection.folders.forEach(async (folder, index) => {
|
||||||
|
recursivelySyncCollections(
|
||||||
|
folder,
|
||||||
|
`${collectionPath}/${index}`,
|
||||||
|
parentCollectionID
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: generalize this
|
||||||
|
// TODO: ask backend to send enough info on the subscription to not need this
|
||||||
|
export const collectionReorderOrMovingOperations: {
|
||||||
|
sourceCollectionID: string
|
||||||
|
destinationCollectionID?: string
|
||||||
|
reorderOperation: {
|
||||||
|
fromPath: string
|
||||||
|
toPath?: string
|
||||||
|
}
|
||||||
|
}[] = []
|
||||||
|
|
||||||
|
type OperationStatus = "pending" | "completed"
|
||||||
|
|
||||||
|
type OperationCollectionRemoved = {
|
||||||
|
type: "COLLECTION_REMOVED"
|
||||||
|
collectionBackendID: string
|
||||||
|
status: OperationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export const gqlCollectionsOperations: Array<OperationCollectionRemoved> = []
|
||||||
|
|
||||||
|
export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||||
|
typeof graphqlCollectionStore
|
||||||
|
> = {
|
||||||
|
appendCollections({ entries }) {
|
||||||
|
let indexStart = graphqlCollectionStore.value.state.length - entries.length
|
||||||
|
|
||||||
|
entries.forEach((collection) => {
|
||||||
|
recursivelySyncCollections(collection, `${indexStart}`)
|
||||||
|
indexStart++
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async addCollection({ collection }) {
|
||||||
|
const lastCreatedCollectionIndex =
|
||||||
|
graphqlCollectionStore.value.state.length - 1
|
||||||
|
|
||||||
|
await recursivelySyncCollections(
|
||||||
|
collection,
|
||||||
|
`${lastCreatedCollectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
removeDuplicateCollectionsFromStore("GQL")
|
||||||
|
},
|
||||||
|
async removeCollection({ collectionIndex }) {
|
||||||
|
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${collectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (backendIdentifier) {
|
||||||
|
gqlCollectionsOperations.push({
|
||||||
|
collectionBackendID: backendIdentifier,
|
||||||
|
type: "COLLECTION_REMOVED",
|
||||||
|
status: "pending",
|
||||||
|
})
|
||||||
|
await deleteUserCollection(backendIdentifier)
|
||||||
|
removeAndReorderEntries(`${collectionIndex}`, "GQL")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editCollection({ collection, collectionIndex }) {
|
||||||
|
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${collectionIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (backendIdentifier && collection.name) {
|
||||||
|
renameUserCollection(backendIdentifier, collection.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async addFolder({ name, path }) {
|
||||||
|
const parentCollectionBackendID =
|
||||||
|
gqlCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
|
||||||
|
if (parentCollectionBackendID) {
|
||||||
|
// TODO: remove this replaceAll thing when updating the mapper
|
||||||
|
const res = await createGQLChildUserCollection(
|
||||||
|
name,
|
||||||
|
parentCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
// after the folder is created add the path of the folder with its backend id to the mapper
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const folderBackendID = res.right.createGQLChildUserCollection.id
|
||||||
|
const parentCollection = navigateToFolderWithIndexPath(
|
||||||
|
graphqlCollectionStore.value.state,
|
||||||
|
path.split("/").map((index) => parseInt(index))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentCollection && parentCollection.folders.length > 0) {
|
||||||
|
const folderIndex = parentCollection.folders.length - 1
|
||||||
|
gqlCollectionsMapper.addEntry(
|
||||||
|
`${path}/${folderIndex}`,
|
||||||
|
folderBackendID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editFolder({ folder, path }) {
|
||||||
|
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (folderBackendId) {
|
||||||
|
renameUserCollection(folderBackendId, folder.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeFolder({ path }) {
|
||||||
|
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (folderBackendId) {
|
||||||
|
await deleteUserCollection(folderBackendId)
|
||||||
|
removeAndReorderEntries(path, "GQL")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editRequest({ path, requestIndex, requestNew }) {
|
||||||
|
const requestPath = `${path}/${requestIndex}`
|
||||||
|
|
||||||
|
const requestBackendID =
|
||||||
|
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
|
||||||
|
|
||||||
|
if (requestBackendID) {
|
||||||
|
editGQLUserRequest(
|
||||||
|
requestBackendID,
|
||||||
|
(requestNew as HoppRESTRequest).name,
|
||||||
|
JSON.stringify(requestNew)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveRequestAs({ path, request }) {
|
||||||
|
const parentCollectionBackendID =
|
||||||
|
gqlCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
|
||||||
|
if (parentCollectionBackendID) {
|
||||||
|
const res = await createGQLUserRequest(
|
||||||
|
(request as HoppRESTRequest).name,
|
||||||
|
JSON.stringify(request),
|
||||||
|
parentCollectionBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingPath =
|
||||||
|
E.isRight(res) &&
|
||||||
|
gqlRequestsMapper.getLocalIDByBackendID(
|
||||||
|
res.right.createGQLUserRequest.id
|
||||||
|
)
|
||||||
|
|
||||||
|
// remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved )
|
||||||
|
if (existingPath) {
|
||||||
|
const indexes = existingPath.split("/")
|
||||||
|
const existingRequestIndex = indexes.pop()
|
||||||
|
const existingRequestParentPath = indexes.join("/")
|
||||||
|
|
||||||
|
runDispatchWithOutSyncing(() => {
|
||||||
|
existingRequestIndex &&
|
||||||
|
removeGraphqlRequest(
|
||||||
|
existingRequestParentPath,
|
||||||
|
parseInt(existingRequestIndex)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCollection = navigateToFolderWithIndexPath(
|
||||||
|
graphqlCollectionStore.value.state,
|
||||||
|
path.split("/").map((index) => parseInt(index))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
const lastCreatedRequestIndex = parentCollection.requests.length - 1
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
gqlRequestsMapper.addEntry(
|
||||||
|
`${path}/${lastCreatedRequestIndex}`,
|
||||||
|
res.right.createGQLUserRequest.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeRequest({ path, requestIndex }) {
|
||||||
|
const requestPath = `${path}/${requestIndex}`
|
||||||
|
const requestBackendID =
|
||||||
|
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
|
||||||
|
|
||||||
|
if (requestBackendID) {
|
||||||
|
await deleteUserRequest(requestBackendID)
|
||||||
|
gqlRequestsMapper.removeEntry(requestPath)
|
||||||
|
reorderIndexesAfterEntryRemoval(path, gqlRequestsMapper, "GQL")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveRequest({ destinationPath, path, requestIndex }) {
|
||||||
|
moveOrReorderRequests(requestIndex, path, destinationPath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const gqlCollectionsSyncer = getSyncInitFunction(
|
||||||
|
graphqlCollectionStore,
|
||||||
|
storeSyncDefinition,
|
||||||
|
() => settingsStore.value.syncCollections,
|
||||||
|
getSettingSubject("syncCollections")
|
||||||
|
)
|
||||||
|
|
||||||
|
async function moveOrReorderRequests(
|
||||||
|
requestIndex: number,
|
||||||
|
path: string,
|
||||||
|
destinationPath: string,
|
||||||
|
nextRequestIndex?: number
|
||||||
|
) {
|
||||||
|
const sourceCollectionBackendID =
|
||||||
|
gqlCollectionsMapper.getBackendIDByLocalID(path)
|
||||||
|
const destinationCollectionBackendID =
|
||||||
|
gqlCollectionsMapper.getBackendIDByLocalID(destinationPath)
|
||||||
|
|
||||||
|
const requestBackendID = gqlRequestsMapper.getBackendIDByLocalID(
|
||||||
|
`${path}/${requestIndex}`
|
||||||
|
)
|
||||||
|
|
||||||
|
let nextRequestBackendID: string | undefined
|
||||||
|
|
||||||
|
// we only need this for reordering requests, not for moving requests
|
||||||
|
if (nextRequestIndex) {
|
||||||
|
nextRequestBackendID = gqlRequestsMapper.getBackendIDByLocalID(
|
||||||
|
`${destinationPath}/${nextRequestIndex}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
sourceCollectionBackendID &&
|
||||||
|
destinationCollectionBackendID &&
|
||||||
|
requestBackendID
|
||||||
|
) {
|
||||||
|
await moveUserRequest(
|
||||||
|
sourceCollectionBackendID,
|
||||||
|
destinationCollectionBackendID,
|
||||||
|
requestBackendID,
|
||||||
|
nextRequestBackendID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextRequestBackendID && nextRequestIndex) {
|
||||||
|
reorderRequestsMapper(requestIndex, path, nextRequestIndex, "GQL")
|
||||||
|
} else {
|
||||||
|
moveRequestInMapper(requestIndex, path, destinationPath, "GQL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,7 +166,7 @@ function setupUserEnvironmentUpdatedSubscription() {
|
|||||||
} else {
|
} else {
|
||||||
// handle the case for normal environments
|
// handle the case for normal environments
|
||||||
|
|
||||||
const localIndex = environmentsMapper.getIndexByBackendId(id)
|
const localIndex = environmentsMapper.getLocalIDByBackendID(id)
|
||||||
|
|
||||||
if (localIndex && name) {
|
if (localIndex && name) {
|
||||||
runDispatchWithOutSyncing(() => {
|
runDispatchWithOutSyncing(() => {
|
||||||
@@ -197,7 +197,7 @@ function setupUserEnvironmentDeletedSubscription() {
|
|||||||
if (E.isRight(res)) {
|
if (E.isRight(res)) {
|
||||||
const { id } = res.right.userEnvironmentDeleted
|
const { id } = res.right.userEnvironmentDeleted
|
||||||
|
|
||||||
const localIndex = environmentsMapper.getIndexByBackendId(id)
|
const localIndex = environmentsMapper.getLocalIDByBackendID(id)
|
||||||
|
|
||||||
if (localIndex) {
|
if (localIndex) {
|
||||||
runDispatchWithOutSyncing(() => {
|
runDispatchWithOutSyncing(() => {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
updateUserEnvironment,
|
updateUserEnvironment,
|
||||||
} from "./environments.api"
|
} from "./environments.api"
|
||||||
|
|
||||||
export const environmentsMapper = createMapper()
|
export const environmentsMapper = createMapper<number, string>()
|
||||||
export const globalEnvironmentMapper = createMapper()
|
export const globalEnvironmentMapper = createMapper<number, string>()
|
||||||
|
|
||||||
export const storeSyncDefinition: StoreSyncDefinitionOf<
|
export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||||
typeof environmentsStore
|
typeof environmentsStore
|
||||||
@@ -73,7 +73,7 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateEnvironment({ envIndex, updatedEnv }) {
|
updateEnvironment({ envIndex, updatedEnv }) {
|
||||||
const backendId = environmentsMapper.getBackendIdByIndex(envIndex)
|
const backendId = environmentsMapper.getBackendIDByLocalID(envIndex)
|
||||||
console.log(environmentsMapper)
|
console.log(environmentsMapper)
|
||||||
|
|
||||||
if (backendId) {
|
if (backendId) {
|
||||||
@@ -81,7 +81,7 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteEnvironment({ envIndex }) {
|
async deleteEnvironment({ envIndex }) {
|
||||||
const backendId = environmentsMapper.getBackendIdByIndex(envIndex)
|
const backendId = environmentsMapper.getBackendIDByLocalID(envIndex)
|
||||||
|
|
||||||
if (backendId) {
|
if (backendId) {
|
||||||
await deleteUserEnvironment(backendId)()
|
await deleteUserEnvironment(backendId)()
|
||||||
@@ -89,14 +89,14 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setGlobalVariables({ entries }) {
|
setGlobalVariables({ entries }) {
|
||||||
const backendId = globalEnvironmentMapper.getBackendIdByIndex(0)
|
const backendId = globalEnvironmentMapper.getBackendIDByLocalID(0)
|
||||||
|
|
||||||
if (backendId) {
|
if (backendId) {
|
||||||
updateUserEnvironment(backendId, { name: "", variables: entries })()
|
updateUserEnvironment(backendId, { name: "", variables: entries })()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearGlobalVariables() {
|
clearGlobalVariables() {
|
||||||
const backendId = globalEnvironmentMapper.getBackendIdByIndex(0)
|
const backendId = globalEnvironmentMapper.getBackendIDByLocalID(0)
|
||||||
|
|
||||||
if (backendId) {
|
if (backendId) {
|
||||||
clearGlobalEnvironmentVariables(backendId)
|
clearGlobalEnvironmentVariables(backendId)
|
||||||
|
|||||||
1516
packages/hoppscotch-selfhost-web/src/tests/collections.sync.spec.ts
Normal file
1516
packages/hoppscotch-selfhost-web/src/tests/collections.sync.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user