feat: implement collections syncing for selfhosted (#42)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
// 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(() => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
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