feat: implement collections syncing for selfhosted (#42)

This commit is contained in:
Akash K
2023-04-01 17:22:42 +05:30
committed by GitHub
parent 9d7509b4dd
commit 86a12e2d28
38 changed files with 4190 additions and 27 deletions

View File

@@ -21,6 +21,7 @@
},
"dependencies": {
"@hoppscotch/common": "workspace:^",
"@hoppscotch/data": "workspace:^",
"axios": "^0.21.4",
"buffer": "^6.0.3",
"firebase": "^9.8.4",
@@ -66,6 +67,7 @@
"vite-plugin-static-copy": "^0.12.0",
"vite-plugin-vue-layouts": "^0.7.0",
"vite-plugin-windicss": "^1.8.8",
"vitest": "^0.29.3",
"vue-tsc": "^1.0.9",
"windicss": "^3.5.6"
}

View File

@@ -0,0 +1,11 @@
mutation CreateGQLChildUserCollection(
$title: String!
$parentUserCollectionID: ID!
) {
createGQLChildUserCollection(
title: $title
parentUserCollectionID: $parentUserCollectionID
) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation CreateGQLRootUserCollection($title: String!) {
createGQLRootUserCollection(title: $title) {
id
}
}

View File

@@ -0,0 +1,13 @@
mutation CreateGQLUserRequest(
$title: String!
$request: String!
$collectionID: ID!
) {
createGQLUserRequest(
title: $title
request: $request
collectionID: $collectionID
) {
id
}
}

View File

@@ -0,0 +1,11 @@
mutation CreateRESTChildUserCollection(
$title: String!
$parentUserCollectionID: ID!
) {
createRESTChildUserCollection(
title: $title
parentUserCollectionID: $parentUserCollectionID
) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation CreateRESTRootUserCollection($title: String!) {
createRESTRootUserCollection(title: $title) {
id
}
}

View File

@@ -0,0 +1,13 @@
mutation CreateRESTUserRequest(
$collectionID: ID!
$title: String!
$request: String!
) {
createRESTUserRequest(
collectionID: $collectionID
title: $title
request: $request
) {
id
}
}

View File

@@ -0,0 +1,3 @@
mutation DeleteUserCollection($userCollectionID: ID!) {
deleteUserCollection(userCollectionID: $userCollectionID)
}

View File

@@ -0,0 +1,3 @@
mutation DeleteUserRequest($requestID: ID!) {
deleteUserRequest(id: $requestID)
}

View File

@@ -0,0 +1,8 @@
mutation MoveUserCollection($destCollectionID: ID, $userCollectionID: ID!) {
moveUserCollection(
destCollectionID: $destCollectionID
userCollectionID: $userCollectionID
) {
id
}
}

View File

@@ -0,0 +1,15 @@
mutation MoveUserRequest(
$sourceCollectionID: ID!
$requestID: ID!
$destinationCollectionID: ID!
$nextRequestID: ID
) {
moveUserRequest(
sourceCollectionID: $sourceCollectionID
requestID: $requestID
destinationCollectionID: $destinationCollectionID
nextRequestID: $nextRequestID
) {
id
}
}

View File

@@ -0,0 +1,8 @@
mutation RenameUserCollection($userCollectionID: ID!, $newTitle: String!) {
renameUserCollection(
userCollectionID: $userCollectionID
newTitle: $newTitle
) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation UpdateGQLUserRequest($id: ID!, $request: String!, $title: String) {
updateGQLUserRequest(id: $id, request: $request, title: $title) {
id
}
}

View File

@@ -0,0 +1,7 @@
mutation UpdateRESTUserRequest($id: ID!, $title: String!, $request: String!) {
updateRESTUserRequest(id: $id, title: $title, request: $request) {
id
collectionID
request
}
}

View File

@@ -0,0 +1,6 @@
mutation UpdateUserCollectionOrder($collectionID: ID!, $nextCollectionID: ID) {
updateUserCollectionOrder(
collectionID: $collectionID
nextCollectionID: $nextCollectionID
)
}

View File

@@ -0,0 +1,12 @@
query ExportUserCollectionsToJSON(
$collectionID: ID
$collectionType: ReqType!
) {
exportUserCollectionsToJSON(
collectionID: $collectionID
collectionType: $collectionType
) {
collectionType
exportedCollection
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,10 @@
subscription UserCollectionCreated {
userCollectionCreated {
parent {
id
}
id
title
type
}
}

View File

@@ -0,0 +1,9 @@
subscription UserCollectionMoved {
userCollectionMoved {
id
parent {
id
}
type
}
}

View File

@@ -0,0 +1,17 @@
subscription UserCollectionOrderUpdated {
userCollectionOrderUpdated {
userCollection {
id
parent {
id
}
}
nextUserCollection {
id
parent {
id
}
}
}
}

View File

@@ -0,0 +1,6 @@
subscription UserCollectionRemoved {
userCollectionRemoved {
id
type
}
}

View File

@@ -0,0 +1,10 @@
subscription userCollectionUpdated {
userCollectionUpdated {
id
title
type
parent {
id
}
}
}

View File

@@ -0,0 +1,9 @@
subscription UserRequestCreated {
userRequestCreated {
id
collectionID
title
request
type
}
}

View File

@@ -0,0 +1,9 @@
subscription UserRequestDeleted {
userRequestDeleted {
id
collectionID
title
request
type
}
}

View File

@@ -0,0 +1,13 @@
subscription UserRequestMoved {
userRequestMoved {
request {
id
collectionID
type
}
nextRequest {
id
collectionID
}
}
}

View File

@@ -0,0 +1,9 @@
subscription UserRequestUpdated {
userRequestUpdated {
id
collectionID
title
request
type
}
}

View File

@@ -79,7 +79,7 @@ export const getSyncInitFunction = <T extends DispatchingStore<any, any>>(
)
}
startSubscriptions()
stopSubscriptions = startSubscriptions()
}
function stopListeningToSubscriptions() {

View File

@@ -1,32 +1,41 @@
export const createMapper = () => {
const indexBackendIDMap = new Map<number, string | undefined>()
const backendIdIndexMap = new Map<string, number | undefined>()
export const createMapper = <
LocalIDType extends string | number,
BackendIDType extends string | number
>() => {
const backendIDByLocalIDMap = new Map<
LocalIDType,
BackendIDType | undefined
>()
const localIDByBackendIDMap = new Map<
BackendIDType,
LocalIDType | undefined
>()
return {
addEntry(localIndex: number, backendId: string) {
indexBackendIDMap.set(localIndex, backendId)
backendIdIndexMap.set(backendId, localIndex)
addEntry(localIdentifier: LocalIDType, backendIdentifier: BackendIDType) {
backendIDByLocalIDMap.set(localIdentifier, backendIdentifier)
localIDByBackendIDMap.set(backendIdentifier, localIdentifier)
},
getValue() {
return indexBackendIDMap
return backendIDByLocalIDMap
},
getBackendIdByIndex(localIndex: number) {
return indexBackendIDMap.get(localIndex)
getBackendIDByLocalID(localIdentifier: LocalIDType) {
return backendIDByLocalIDMap.get(localIdentifier)
},
getIndexByBackendId(backendId: string) {
return backendIdIndexMap.get(backendId)
getLocalIDByBackendID(backendId: BackendIDType) {
return localIDByBackendIDMap.get(backendId)
},
removeEntry(backendId?: string, index?: number) {
removeEntry(backendId?: BackendIDType, index?: LocalIDType) {
if (backendId) {
const index = backendIdIndexMap.get(backendId)
const index = localIDByBackendIDMap.get(backendId)
backendIdIndexMap.delete(backendId)
index && indexBackendIDMap.delete(index)
localIDByBackendIDMap.delete(backendId)
index && backendIDByLocalIDMap.delete(index)
} else if (index) {
const backendId = indexBackendIDMap.get(index)
const backendId = backendIDByLocalIDMap.get(index)
indexBackendIDMap.delete(index)
backendId && backendIdIndexMap.delete(backendId)
backendIDByLocalIDMap.delete(index)
backendId && localIDByBackendIDMap.delete(backendId)
}
},
}

View File

@@ -1,10 +1,12 @@
import { createHoppApp } from "@hoppscotch/common"
import { def as authDef } from "./platform/auth"
import { def as environmentsDef } from "./platform/environments/environments.platform"
import { def as collectionsDef } from "./platform/collections/collections.platform"
createHoppApp("#app", {
auth: authDef,
sync: {
environments: environmentsDef,
collections: collectionsDef,
},
})

View File

@@ -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 })

View File

@@ -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 }
}

View File

@@ -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
)
}

View File

@@ -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")
})
}

View File

@@ -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")
}
}
}

View File

@@ -166,7 +166,7 @@ function setupUserEnvironmentUpdatedSubscription() {
} else {
// handle the case for normal environments
const localIndex = environmentsMapper.getIndexByBackendId(id)
const localIndex = environmentsMapper.getLocalIDByBackendID(id)
if (localIndex && name) {
runDispatchWithOutSyncing(() => {
@@ -197,7 +197,7 @@ function setupUserEnvironmentDeletedSubscription() {
if (E.isRight(res)) {
const { id } = res.right.userEnvironmentDeleted
const localIndex = environmentsMapper.getIndexByBackendId(id)
const localIndex = environmentsMapper.getLocalIDByBackendID(id)
if (localIndex) {
runDispatchWithOutSyncing(() => {

View File

@@ -17,8 +17,8 @@ import {
updateUserEnvironment,
} from "./environments.api"
export const environmentsMapper = createMapper()
export const globalEnvironmentMapper = createMapper()
export const environmentsMapper = createMapper<number, string>()
export const globalEnvironmentMapper = createMapper<number, string>()
export const storeSyncDefinition: StoreSyncDefinitionOf<
typeof environmentsStore
@@ -73,7 +73,7 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
}
},
updateEnvironment({ envIndex, updatedEnv }) {
const backendId = environmentsMapper.getBackendIdByIndex(envIndex)
const backendId = environmentsMapper.getBackendIDByLocalID(envIndex)
console.log(environmentsMapper)
if (backendId) {
@@ -81,7 +81,7 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
}
},
async deleteEnvironment({ envIndex }) {
const backendId = environmentsMapper.getBackendIdByIndex(envIndex)
const backendId = environmentsMapper.getBackendIDByLocalID(envIndex)
if (backendId) {
await deleteUserEnvironment(backendId)()
@@ -89,14 +89,14 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
}
},
setGlobalVariables({ entries }) {
const backendId = globalEnvironmentMapper.getBackendIdByIndex(0)
const backendId = globalEnvironmentMapper.getBackendIDByLocalID(0)
if (backendId) {
updateUserEnvironment(backendId, { name: "", variables: entries })()
}
},
clearGlobalVariables() {
const backendId = globalEnvironmentMapper.getBackendIdByIndex(0)
const backendId = globalEnvironmentMapper.getBackendIDByLocalID(0)
if (backendId) {
clearGlobalEnvironmentVariables(backendId)

File diff suppressed because it is too large Load Diff