refactor: migrate completely to urql

This commit is contained in:
Andrew Bastin
2022-02-02 00:33:34 +05:30
parent eae94e3dbf
commit 2df1c1c6ed
26 changed files with 748 additions and 1677 deletions

View File

@@ -45,56 +45,62 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from "@nuxtjs/composition-api" import { computed } from "@nuxtjs/composition-api"
import gql from "graphql-tag" import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import { useGQLQuery } from "~/helpers/backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
import { currentUserInfo$ } from "~/helpers/teams/BackendUserInfo" import { currentUserInfo$ } from "~/helpers/teams/BackendUserInfo"
import { useReadonlyStream } from "~/helpers/utils/composables" import { useReadonlyStream } from "~/helpers/utils/composables"
export default defineComponent({ type TeamData = GetMyTeamsQuery["myTeams"][number]
props: {
doc: Boolean, defineProps<{
show: Boolean, doc: boolean
}, show: boolean
setup() { }>()
return {
currentUser: useReadonlyStream(currentUserInfo$, null), const emit = defineEmits<{
} (e: "update-collection-type", tabID: string): void
}, (e: "update-selected-team", team: TeamData | undefined): void
data() { }>()
return {
skipTeamsFetching: true, const currentUser = useReadonlyStream(currentUserInfo$, null)
}
}, const teamListQuery = useGQLQuery({
apollo: { query: GetMyTeamsDocument,
myTeams: { defer: true,
query: gql` pollDuration: 10000,
query GetMyTeams {
myTeams {
id
name
myRole
}
}
`,
pollInterval: 10000,
skip() {
return this.skipTeamsFetching
},
},
},
methods: {
onTeamSelectIntersect() {
// Load team data as soon as intersection
this.$apollo.queries.myTeams.refetch()
this.skipTeamsFetching = false
},
updateCollectionsType(tabID: string) {
this.$emit("update-collection-type", tabID)
},
updateSelectedTeam(team: any) {
this.$emit("update-selected-team", team)
},
},
}) })
const myTeams = computed(() => {
const result = teamListQuery.data
if (teamListQuery.loading) {
return []
}
return pipe(
result,
E.match(
// TODO: Handle better error case here ?
() => [],
(x) => x.myTeams
)
)
})
const onTeamSelectIntersect = () => {
// Load team data as soon as intersection
teamListQuery.execute()
}
const updateCollectionsType = (tabID: string) => {
emit("update-collection-type", tabID)
}
const updateSelectedTeam = (team: TeamData | undefined) => {
emit("update-selected-team", team)
}
</script> </script>

View File

@@ -163,9 +163,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from "@nuxtjs/composition-api" import { computed, ref, watch } from "@nuxtjs/composition-api"
import { pipe } from "fp-ts/function"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { HoppRESTRequest, HoppCollection } from "@hoppscotch/data" import { HoppRESTRequest, HoppCollection } from "@hoppscotch/data"
import { apolloClient } from "~/helpers/apollo"
import { import {
useAxios, useAxios,
useI18n, useI18n,
@@ -173,10 +173,14 @@ import {
useToast, useToast,
} from "~/helpers/utils/composables" } from "~/helpers/utils/composables"
import { currentUser$ } from "~/helpers/fb/auth" import { currentUser$ } from "~/helpers/fb/auth"
import * as teamUtils from "~/helpers/teams/utils"
import { appendRESTCollections, restCollections$ } from "~/newstore/collections" import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
import { RESTCollectionImporters } from "~/helpers/import-export/import/importers" import { RESTCollectionImporters } from "~/helpers/import-export/import/importers"
import { StepReturnValue } from "~/helpers/import-export/steps" import { StepReturnValue } from "~/helpers/import-export/steps"
import { runGQLQuery, runMutation } from "~/helpers/backend/GQLClient"
import {
ExportAsJsonDocument,
ImportFromJsonDocument,
} from "~/helpers/backend/graphql"
const props = defineProps<{ const props = defineProps<{
show: boolean show: boolean
@@ -212,11 +216,23 @@ const getJSONCollection = async () => {
if (props.collectionsType.type === "my-collections") { if (props.collectionsType.type === "my-collections") {
collectionJson.value = JSON.stringify(myCollections.value, null, 2) collectionJson.value = JSON.stringify(myCollections.value, null, 2)
} else { } else {
collectionJson.value = await teamUtils.exportAsJSON( collectionJson.value = pipe(
apolloClient, await runGQLQuery({
props.collectionsType.selectedTeam.id query: ExportAsJsonDocument,
variables: {
teamID: props.collectionsType.selectedTeam.id,
},
}),
E.matchW(
// TODO: Handle error case gracefully ?
() => {
throw new Error("Error exporting collection to JSON")
},
(x) => x.exportCollectionsToJSON
)
) )
} }
return collectionJson.value return collectionJson.value
} }
@@ -284,25 +300,19 @@ const importingMyCollections = ref(false)
const importToTeams = async (content: HoppCollection<HoppRESTRequest>) => { const importToTeams = async (content: HoppCollection<HoppRESTRequest>) => {
importingMyCollections.value = true importingMyCollections.value = true
if (props.collectionsType.type !== "team-collections") return if (props.collectionsType.type !== "team-collections") return
await teamUtils
.importFromJSON( const result = await runMutation(ImportFromJsonDocument, {
apolloClient, jsonString: JSON.stringify(content),
content, teamID: props.collectionsType.selectedTeam.id,
props.collectionsType.selectedTeam.id })()
)
.then((status) => { if (E.isLeft(result)) {
if (status) { console.error(result.left)
emit("update-team-collections")
} else { } else {
console.error(status) emit("update-team-collections")
} }
})
.catch((e) => {
console.error(e)
})
.finally(() => {
importingMyCollections.value = false importingMyCollections.value = false
})
} }
const exportJSON = () => { const exportJSON = () => {

View File

@@ -59,6 +59,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from "@nuxtjs/composition-api" import { reactive, ref, watch } from "@nuxtjs/composition-api"
import * as E from "fp-ts/Either"
import { HoppGQLRequest, isHoppRESTRequest } from "@hoppscotch/data" import { HoppGQLRequest, isHoppRESTRequest } from "@hoppscotch/data"
import cloneDeep from "lodash/cloneDeep" import cloneDeep from "lodash/cloneDeep"
import { import {
@@ -73,9 +74,12 @@ import {
setRESTSaveContext, setRESTSaveContext,
useRESTRequestName, useRESTRequestName,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import * as teamUtils from "~/helpers/teams/utils"
import { apolloClient } from "~/helpers/apollo"
import { useI18n, useToast } from "~/helpers/utils/composables" import { useI18n, useToast } from "~/helpers/utils/composables"
import { runMutation } from "~/helpers/backend/GQLClient"
import {
CreateRequestInCollectionDocument,
UpdateRequestDocument,
} from "~/helpers/backend/graphql"
const t = useI18n() const t = useI18n()
@@ -270,19 +274,19 @@ const saveRequestAs = async () => {
if (collectionsType.value.type !== "team-collections") if (collectionsType.value.type !== "team-collections")
throw new Error("Collections Type mismatch") throw new Error("Collections Type mismatch")
teamUtils runMutation(UpdateRequestDocument, {
.overwriteRequestTeams( requestID: picked.value.requestID,
apolloClient, data: {
JSON.stringify(requestUpdated), request: JSON.stringify(requestUpdated),
requestUpdated.name, title: requestUpdated.name,
picked.value.requestID },
) })().then((result) => {
.then(() => { if (E.isLeft(result)) {
requestSaved()
})
.catch((error) => {
toast.error(`${t("profile.no_permission")}`) toast.error(`${t("profile.no_permission")}`)
throw new Error(error) throw new Error(`${result.left}`)
} else {
requestSaved()
}
}) })
setRESTSaveContext({ setRESTSaveContext({
@@ -296,28 +300,27 @@ const saveRequestAs = async () => {
if (collectionsType.value.type !== "team-collections") if (collectionsType.value.type !== "team-collections")
throw new Error("Collections Type mismatch") throw new Error("Collections Type mismatch")
try { const result = await runMutation(CreateRequestInCollectionDocument, {
const req = await teamUtils.saveRequestAsTeams( collectionID: picked.value.folderID,
apolloClient, data: {
JSON.stringify(requestUpdated), request: JSON.stringify(requestUpdated),
requestUpdated.name, teamID: collectionsType.value.selectedTeam.id,
collectionsType.value.selectedTeam.id, title: requestUpdated.name,
picked.value.folderID },
) })()
if (req && req.id) { if (E.isLeft(result)) {
toast.error(`${t("profile.no_permission")}`)
console.error(result.left)
} else {
setRESTSaveContext({ setRESTSaveContext({
originLocation: "team-collection", originLocation: "team-collection",
requestID: req.id, requestID: result.right.createRequestInCollection.id,
teamID: collectionsType.value.selectedTeam.id, teamID: collectionsType.value.selectedTeam.id,
collectionID: picked.value.folderID, collectionID: picked.value.folderID,
}) })
}
requestSaved() requestSaved()
} catch (error) {
toast.error(`${t("profile.no_permission")}`)
console.error(error)
} }
} else if (picked.value.pickedType === "teams-collection") { } else if (picked.value.pickedType === "teams-collection") {
if (!isHoppRESTRequest(requestUpdated)) if (!isHoppRESTRequest(requestUpdated))
@@ -326,28 +329,27 @@ const saveRequestAs = async () => {
if (collectionsType.value.type !== "team-collections") if (collectionsType.value.type !== "team-collections")
throw new Error("Collections Type mismatch") throw new Error("Collections Type mismatch")
try { const result = await runMutation(CreateRequestInCollectionDocument, {
const req = await teamUtils.saveRequestAsTeams( collectionID: picked.value.collectionID,
apolloClient, data: {
JSON.stringify(requestUpdated), title: requestUpdated.name,
requestUpdated.name, request: JSON.stringify(requestUpdated),
collectionsType.value.selectedTeam.id, teamID: collectionsType.value.selectedTeam.id,
picked.value.collectionID },
) })()
if (req && req.id) { if (E.isLeft(result)) {
toast.error(`${t("profile.no_permission")}`)
console.error(result.left)
} else {
setRESTSaveContext({ setRESTSaveContext({
originLocation: "team-collection", originLocation: "team-collection",
requestID: req.id, requestID: result.right.createRequestInCollection.id,
teamID: collectionsType.value.selectedTeam.id, teamID: collectionsType.value.selectedTeam.id,
collectionID: picked.value.collectionID, collectionID: picked.value.collectionID,
}) })
}
requestSaved() requestSaved()
} catch (error) {
toast.error(`${t("profile.no_permission")}`)
console.error(error)
} }
} else if (picked.value.pickedType === "gql-my-request") { } else if (picked.value.pickedType === "gql-my-request") {
// TODO: Check for GQL request ? // TODO: Check for GQL request ?

View File

@@ -182,14 +182,12 @@
</template> </template>
<script> <script>
import gql from "graphql-tag"
import cloneDeep from "lodash/cloneDeep" import cloneDeep from "lodash/cloneDeep"
import { defineComponent } from "@nuxtjs/composition-api" import { defineComponent } from "@nuxtjs/composition-api"
import CollectionsMyCollection from "./my/Collection.vue" import CollectionsMyCollection from "./my/Collection.vue"
import CollectionsTeamsCollection from "./teams/Collection.vue" import CollectionsTeamsCollection from "./teams/Collection.vue"
import { currentUser$ } from "~/helpers/fb/auth" import { currentUser$ } from "~/helpers/fb/auth"
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter" import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
import * as teamUtils from "~/helpers/teams/utils"
import { import {
restCollections$, restCollections$,
addRESTCollection, addRESTCollection,
@@ -205,6 +203,16 @@ import {
useReadonlyStream, useReadonlyStream,
useStreamSubscriber, useStreamSubscriber,
} from "~/helpers/utils/composables" } from "~/helpers/utils/composables"
import { runMutation } from "~/helpers/backend/GQLClient"
import {
CreateChildCollectionDocument,
CreateNewRootCollectionDocument,
CreateRequestInCollectionDocument,
DeleteCollectionDocument,
DeleteRequestDocument,
RenameCollectionDocument,
UpdateRequestDocument,
} from "~/helpers/backend/graphql"
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -349,18 +357,16 @@ export default defineComponent({
this.collectionsType.type === "team-collections" && this.collectionsType.type === "team-collections" &&
this.collectionsType.selectedTeam.myRole !== "VIEWER" this.collectionsType.selectedTeam.myRole !== "VIEWER"
) { ) {
teamUtils runMutation(CreateNewRootCollectionDocument, {
.createNewRootCollection( title: name,
this.$apollo, teamID: this.collectionsType.selectedTeam.id,
name, })().then((result) => {
this.collectionsType.selectedTeam.id if (E.isLeft(result)) {
)
.then(() => {
this.$toast.success(this.$t("collection.created"))
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("collection.created"))
}
}) })
} }
this.displayModalAdd(false) this.displayModalAdd(false)
@@ -382,14 +388,16 @@ export default defineComponent({
this.collectionsType.type === "team-collections" && this.collectionsType.type === "team-collections" &&
this.collectionsType.selectedTeam.myRole !== "VIEWER" this.collectionsType.selectedTeam.myRole !== "VIEWER"
) { ) {
teamUtils runMutation(RenameCollectionDocument, {
.renameCollection(this.$apollo, newName, this.editingCollection.id) collectionID: this.editingCollection.id,
.then(() => { newTitle: newName,
this.$toast.success(this.$t("collection.renamed")) })().then((result) => {
}) if (E.isLeft(result)) {
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("collection.renamed"))
}
}) })
} }
this.displayModalEdit(false) this.displayModalEdit(false)
@@ -402,14 +410,16 @@ export default defineComponent({
this.collectionsType.type === "team-collections" && this.collectionsType.type === "team-collections" &&
this.collectionsType.selectedTeam.myRole !== "VIEWER" this.collectionsType.selectedTeam.myRole !== "VIEWER"
) { ) {
teamUtils runMutation(RenameCollectionDocument, {
.renameCollection(this.$apollo, name, this.editingFolder.id) collectionID: this.editingFolder.id,
.then(() => { newTitle: name,
this.$toast.success(this.$t("folder.renamed")) })().then((result) => {
}) if (E.isLeft(result)) {
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("folder.renamed"))
}
}) })
} }
@@ -433,20 +443,21 @@ export default defineComponent({
this.collectionsType.selectedTeam.myRole !== "VIEWER" this.collectionsType.selectedTeam.myRole !== "VIEWER"
) { ) {
const requestName = requestUpdateData.name || this.editingRequest.name const requestName = requestUpdateData.name || this.editingRequest.name
teamUtils
.updateRequest( runMutation(UpdateRequestDocument, {
this.$apollo, data: {
requestUpdated, request: JSON.stringify(requestUpdated),
requestName, title: requestName,
this.editingRequestIndex },
) requestID: this.editingRequestIndex,
.then(() => { })().then((result) => {
this.$toast.success(this.$t("request.renamed")) if (E.isLeft(result)) {
this.$emit("update-team-collections")
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("request.renamed"))
this.$emit("update-team-collections")
}
}) })
} }
@@ -488,34 +499,17 @@ export default defineComponent({
addRESTFolder(name, path) addRESTFolder(name, path)
} else if (this.collectionsType.type === "team-collections") { } else if (this.collectionsType.type === "team-collections") {
if (this.collectionsType.selectedTeam.myRole !== "VIEWER") { if (this.collectionsType.selectedTeam.myRole !== "VIEWER") {
this.$apollo runMutation(CreateChildCollectionDocument, {
.mutate({
mutation: gql`
mutation CreateChildCollection(
$childTitle: String!
$collectionID: ID!
) {
createChildCollection(
childTitle: $childTitle
collectionID: $collectionID
) {
id
}
}
`,
// Parameters
variables: {
childTitle: name, childTitle: name,
collectionID: folder.id, collectionID: folder.id,
}, })().then((result) => {
}) if (E.isLeft(result)) {
.then(() => {
this.$toast.success(this.$t("folder.created"))
this.$emit("update-team-collections")
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("folder.created"))
this.$emit("update-team-collections")
}
}) })
} }
} }
@@ -590,25 +584,15 @@ export default defineComponent({
} }
if (collectionsType.selectedTeam.myRole !== "VIEWER") { if (collectionsType.selectedTeam.myRole !== "VIEWER") {
this.$apollo runMutation(DeleteCollectionDocument, {
.mutate({
// Query
mutation: gql`
mutation ($collectionID: ID!) {
deleteCollection(collectionID: $collectionID)
}
`,
// Parameters
variables: {
collectionID, collectionID,
}, })().then((result) => {
}) if (E.isLeft(result)) {
.then(() => {
this.$toast.success(this.$t("state.deleted"))
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("state.deleted"))
}
}) })
} }
} }
@@ -636,14 +620,15 @@ export default defineComponent({
this.$emit("select", { picked: null }) this.$emit("select", { picked: null })
} }
teamUtils runMutation(DeleteRequestDocument, {
.deleteRequest(this.$apollo, requestIndex) requestID: requestIndex,
.then(() => { })().then((result) => {
this.$toast.success(this.$t("state.deleted")) if (E.isLeft(result)) {
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong")) this.$toast.error(this.$t("error.something_went_wrong"))
console.error(e) console.error(e)
} else {
this.$toast.success(this.$t("state.deleted"))
}
}) })
} }
}, },
@@ -654,13 +639,15 @@ export default defineComponent({
name: `${request.name} - ${this.$t("action.duplicate")}`, name: `${request.name} - ${this.$t("action.duplicate")}`,
} }
teamUtils.saveRequestAsTeams( // Error handling ?
this.$apollo, runMutation(CreateRequestInCollectionDocument, {
JSON.stringify(newReq), collectionID,
`${request.name} - ${this.$t("action.duplicate")}`, data: {
this.collectionsType.selectedTeam.id, request: JSON.stringify(newReq),
collectionID teamID: this.collectionsType.selectedTeam.id,
) title: `${request.name} - ${this.$t("action.duplicate")}`,
},
})()
} else if (this.collectionsType.type === "my-collections") { } else if (this.collectionsType.type === "my-collections") {
saveRESTRequestAs(folderPath, { saveRESTRequestAs(folderPath, {
...cloneDeep(request), ...cloneDeep(request),

View File

@@ -184,8 +184,9 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api" import { defineComponent, ref } from "@nuxtjs/composition-api"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { runMutation } from "~/helpers/backend/GQLClient"
import { DeleteCollectionDocument } from "~/helpers/backend/graphql"
import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest" import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest"
import * as teamUtils from "~/helpers/teams/utils"
export default defineComponent({ export default defineComponent({
name: "Folder", name: "Folder",
@@ -257,23 +258,25 @@ export default defineComponent({
this.$emit("select", { picked: null }) this.$emit("select", { picked: null })
} }
teamUtils runMutation(DeleteCollectionDocument, {
.deleteCollection(this.$apollo, this.folder.id) collectionID: this.folder.id,
.then(() => { })().then((result) => {
if (E.isLeft(result)) {
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
console.error(result.left)
} else {
this.$toast.success(`${this.$t("state.deleted")}`) this.$toast.success(`${this.$t("state.deleted")}`)
this.$emit("update-team-collections") this.$emit("update-team-collections")
}
}) })
.catch((e) => {
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
console.error(e)
})
this.$emit("update-team-collections") this.$emit("update-team-collections")
} }
}, },
expandCollection(collectionID) { expandCollection(collectionID: number) {
this.$emit("expand-collection", collectionID) this.$emit("expand-collection", collectionID)
}, },
async dropEvent({ dataTransfer }) { async dropEvent({ dataTransfer }: any) {
this.dragging = !this.dragging this.dragging = !this.dragging
const requestIndex = dataTransfer.getData("requestIndex") const requestIndex = dataTransfer.getData("requestIndex")
const moveRequestResult = await moveRESTTeamRequest( const moveRequestResult = await moveRESTTeamRequest(
@@ -283,7 +286,7 @@ export default defineComponent({
if (E.isLeft(moveRequestResult)) if (E.isLeft(moveRequestResult))
this.$toast.error(`${this.$t("error.something_went_wrong")}`) this.$toast.error(`${this.$t("error.something_went_wrong")}`)
}, },
removeRequest({ collectionIndex, folderName, requestIndex }) { removeRequest({ collectionIndex, folderName, requestIndex }: any) {
this.$emit("remove-request", { this.$emit("remove-request", {
collectionIndex, collectionIndex,
folderName, folderName,

View File

@@ -240,9 +240,9 @@ import {
import { defineActionHandler } from "~/helpers/actions" import { defineActionHandler } from "~/helpers/actions"
import { copyToClipboard } from "~/helpers/utils/clipboard" import { copyToClipboard } from "~/helpers/utils/clipboard"
import { useSetting } from "~/newstore/settings" import { useSetting } from "~/newstore/settings"
import { overwriteRequestTeams } from "~/helpers/teams/utils"
import { apolloClient } from "~/helpers/apollo"
import { createShortcode } from "~/helpers/backend/mutations/Shortcode" import { createShortcode } from "~/helpers/backend/mutations/Shortcode"
import { runMutation } from "~/helpers/backend/GQLClient"
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
const t = useI18n() const t = useI18n()
@@ -506,17 +506,18 @@ const saveRequest = () => {
// TODO: handle error case (NOTE: overwriteRequestTeams is async) // TODO: handle error case (NOTE: overwriteRequestTeams is async)
try { try {
overwriteRequestTeams( runMutation(UpdateRequestDocument, {
apolloClient, requestID: saveCtx.requestID,
JSON.stringify(req), data: {
req.name, title: req.name,
saveCtx.requestID request: JSON.stringify(req),
) },
.then(() => { })().then((result) => {
toast.success(`${t("request.saved")}`) if (E.isLeft(result)) {
})
.catch(() => {
toast.error(`${t("profile.no_permission")}`) toast.error(`${t("profile.no_permission")}`)
} else {
toast.success(`${t("request.saved")}`)
}
}) })
} catch (error) { } catch (error) {
showSaveRequestModal.value = true showSaveRequestModal.value = true

View File

@@ -1,127 +0,0 @@
import {
ApolloClient,
HttpLink,
InMemoryCache,
QueryOptions,
OperationVariables,
split,
ApolloError,
isApolloError as _isApolloError,
} from "@apollo/client/core"
import { WebSocketLink } from "@apollo/client/link/ws"
import { setContext } from "@apollo/client/link/context"
import { getMainDefinition } from "@apollo/client/utilities"
import { ref, onMounted, onBeforeUnmount, Ref } from "@nuxtjs/composition-api"
import { authIdToken$ } from "./fb/auth"
let authToken: String | null = null
export function registerApolloAuthUpdate() {
authIdToken$.subscribe((token) => {
authToken = token
})
}
/**
* Injects auth token if available
*/
const authLink = setContext((_, { headers }) => {
if (authToken) {
return {
headers: {
...headers,
authorization: `Bearer ${authToken}`,
},
}
} else {
return {
headers,
}
}
})
const httpLink = new HttpLink({
uri:
process.env.CONTEXT === "production"
? "https://api.hoppscotch.io/graphql"
: "https://api.hoppscotch.io/graphql",
})
const wsLink = new WebSocketLink({
uri:
process.env.CONTEXT === "production"
? "wss://api.hoppscotch.io/graphql"
: "wss://api.hoppscotch.io/graphql",
options: {
reconnect: true,
lazy: true,
connectionParams: () => {
return {
authorization: `Bearer ${authToken}`,
}
},
},
})
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
)
},
wsLink,
httpLink
)
export const apolloClient = new ApolloClient({
link: authLink.concat(splitLink),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: "network-only",
errorPolicy: "ignore",
},
watchQuery: {
fetchPolicy: "network-only",
errorPolicy: "ignore",
},
},
})
export function isApolloError(x: any): x is ApolloError {
return _isApolloError(x)
}
export function useGQLQuery<T = any, TVariables = OperationVariables>(
options: QueryOptions<TVariables, T>
): { loading: Ref<boolean>; data: Ref<T | ApolloError | undefined> } {
const loading = ref(true)
const data = ref<T | ApolloError | undefined>()
let subscription: ZenObservable.Subscription | null = null
onMounted(() => {
subscription = apolloClient.watchQuery(options).subscribe((result) => {
if (result.error) {
data.value = result.error
} else {
data.value = result.data
}
loading.value = false
})
})
onBeforeUnmount(() => {
if (subscription) {
subscription.unsubscribe()
}
})
return {
loading,
data,
}
}

View File

@@ -0,0 +1,11 @@
mutation CreateChildCollection(
$childTitle: String!
$collectionID: ID!
) {
createChildCollection(
childTitle: $childTitle
collectionID: $collectionID
) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation CreateNewRootCollection($title: String!, $teamID: ID!) {
createRootCollection(title: $title, teamID: $teamID) {
id
}
}

View File

@@ -0,0 +1,12 @@
mutation CreateRequestInCollection($data: CreateTeamRequestInput!, $collectionID: ID!) {
createRequestInCollection(data: $data, collectionID: $collectionID) {
id
collection {
id
team {
id
name
}
}
}
}

View File

@@ -0,0 +1,3 @@
mutation DeleteCollection($collectionID: ID!) {
deleteCollection(collectionID: $collectionID)
}

View File

@@ -0,0 +1,3 @@
mutation DeleteRequest($requestID: ID!) {
deleteRequest(requestID: $requestID)
}

View File

@@ -0,0 +1,3 @@
mutation importFromJSON($jsonString: String!, $teamID: ID!) {
importCollectionsFromJSON(jsonString: $jsonString, teamID: $teamID)
}

View File

@@ -0,0 +1,5 @@
mutation RenameCollection($newTitle: String!, $collectionID: ID!) {
renameCollection(newTitle: $newTitle, collectionID: $collectionID) {
id
}
}

View File

@@ -0,0 +1,6 @@
mutation UpdateRequest($data: UpdateTeamRequestInput!, $requestID: ID!) {
updateRequest(data: $data, requestID: $requestID) {
id
title
}
}

View File

@@ -0,0 +1,3 @@
query ExportAsJSON($teamID: ID!) {
exportCollectionsToJSON(teamID: $teamID)
}

View File

@@ -0,0 +1,7 @@
query GetMyTeams {
myTeams {
id
name
myRole
}
}

View File

@@ -0,0 +1,8 @@
query GetUserInfo {
me {
uid
displayName
email
photoURL
}
}

View File

@@ -1,7 +1,9 @@
import { pipe } from "fp-ts/function"
import * as E from "fp-ts/Either"
import { BehaviorSubject } from "rxjs" import { BehaviorSubject } from "rxjs"
import gql from "graphql-tag"
import { authIdToken$ } from "../fb/auth" import { authIdToken$ } from "../fb/auth"
import { apolloClient } from "../apollo" import { runGQLQuery } from "../backend/GQLClient"
import { GetUserInfoDocument } from "../backend/graphql"
/* /*
* This file deals with interfacing data provided by the * This file deals with interfacing data provided by the
@@ -52,27 +54,22 @@ export function initUserInfo() {
* Runs the actual user info fetching * Runs the actual user info fetching
*/ */
async function updateUserInfo() { async function updateUserInfo() {
try { const result = await runGQLQuery({
const { data } = await apolloClient.query({ query: GetUserInfoDocument,
query: gql`
query GetUserInfo {
me {
uid
displayName
email
photoURL
}
}
`,
}) })
currentUserInfo$.next({ currentUserInfo$.next(
uid: data.me.uid, pipe(
displayName: data.me.displayName, result,
email: data.me.email, E.matchW(
photoURL: data.me.photoURL, () => null,
(x) => ({
uid: x.me.uid,
displayName: x.me.displayName ?? null,
email: x.me.email ?? null,
photoURL: x.me.photoURL ?? null,
}) })
} catch (e) { )
currentUserInfo$.next(null) )
} )
} }

View File

@@ -1,150 +0,0 @@
import * as E from "fp-ts/Either"
import { BehaviorSubject, Subscription } from "rxjs"
import cloneDeep from "lodash/cloneDeep"
import { runGQLQuery, runGQLSubscription } from "../backend/GQLClient"
import {
GetTeamMembersDocument,
TeamMemberAddedDocument,
TeamMemberRemovedDocument,
TeamMemberUpdatedDocument,
} from "../backend/graphql"
export interface TeamsTeamMember {
membershipID: string
user: {
uid: string
email: string | null
}
role: "OWNER" | "EDITOR" | "VIEWER"
}
export default class TeamMemberAdapter {
members$: BehaviorSubject<TeamsTeamMember[]>
private teamMemberAdded$: Subscription | null
private teamMemberRemoved$: Subscription | null
private teamMemberUpdated$: Subscription | null
constructor(private teamID: string | null) {
this.members$ = new BehaviorSubject<TeamsTeamMember[]>([])
this.teamMemberAdded$ = null
this.teamMemberUpdated$ = null
this.teamMemberRemoved$ = null
if (this.teamID) this.initialize()
}
changeTeamID(newTeamID: string | null) {
this.members$.next([])
this.teamID = newTeamID
if (this.teamID) this.initialize()
}
unsubscribeSubscriptions() {
this.teamMemberAdded$?.unsubscribe()
this.teamMemberRemoved$?.unsubscribe()
this.teamMemberUpdated$?.unsubscribe()
}
private async initialize() {
await this.loadTeamMembers()
this.registerSubscriptions()
}
private async loadTeamMembers(): Promise<void> {
if (!this.teamID) return
const result: TeamsTeamMember[] = []
let cursor: string | null = null
while (true) {
const res = await runGQLQuery({
query: GetTeamMembersDocument,
variables: {
teamID: this.teamID,
cursor,
},
})
if (E.isLeft(res))
throw new Error(`Team Members List Load failed: ${res.left}`)
// TODO: Improve this with TypeScript
result.push(...(res.right.team!.members as any))
if ((res.right.team!.members as any[]).length === 0) break
else {
cursor =
res.right.team!.members[res.right.team!.members.length - 1]
.membershipID
}
}
this.members$.next(result)
}
private registerSubscriptions() {
if (!this.teamID) return
this.teamMemberAdded$ = runGQLSubscription({
query: TeamMemberAddedDocument,
variables: {
teamID: this.teamID,
},
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Member Added Subscription Failed: ${result.left}`)
// TODO: Improve typing
this.members$.next([
...(this.members$.value as any),
result.right.teamMemberAdded as any,
])
})
this.teamMemberRemoved$ = runGQLSubscription({
query: TeamMemberRemovedDocument,
variables: {
teamID: this.teamID,
},
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(
`Team Member Removed Subscription Failed: ${result.left}`
)
this.members$.next(
this.members$.value.filter(
(el) => el.user.uid !== result.right.teamMemberRemoved
)
)
})
this.teamMemberUpdated$ = runGQLSubscription({
query: TeamMemberUpdatedDocument,
variables: {
teamID: this.teamID,
},
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(
`Team Member Updated Subscription Failed: ${result.left}`
)
const list = cloneDeep(this.members$.value)
// TODO: Improve typing situation
const obj = list.find(
(el) =>
el.user.uid === (result.right.teamMemberUpdated.user!.uid as any)
)
if (!obj) return
Object.assign(obj, result.right.teamMemberUpdated)
})
}
}

View File

@@ -1,573 +0,0 @@
import gql from "graphql-tag"
import { BehaviorSubject } from "rxjs"
/**
* Returns an observable list of team members in the given Team
*
* @param {ApolloClient<any>} apollo - Instance of ApolloClient
* @param {string} teamID - ID of the team to observe
*
* @returns {{user: {uid: string, email: string}, role: 'OWNER' | 'EDITOR' | 'VIEWER'}}
*/
export async function getLiveTeamMembersList(apollo, teamID) {
const subject = new BehaviorSubject([])
const { data } = await apollo.query({
query: gql`
query GetTeamMembers($teamID: ID!) {
team(teamID: $teamID) {
members {
user {
uid
email
}
role
}
}
}
`,
variables: {
teamID,
},
})
subject.next(data.team.members)
const addedSub = apollo
.subscribe({
query: gql`
subscription TeamMemberAdded($teamID: ID!) {
teamMemberAdded(teamID: $teamID) {
user {
uid
email
}
role
}
}
`,
variables: {
teamID,
},
})
.subscribe(({ data }) => {
subject.next([...subject.value, data.teamMemberAdded])
})
const updateSub = apollo
.subscribe({
query: gql`
subscription TeamMemberUpdated($teamID: ID!) {
teamMemberUpdated(teamID: $teamID) {
user {
uid
email
}
role
}
}
`,
variables: {
teamID,
},
})
.subscribe(({ data }) => {
const val = subject.value.find(
(member) => member.user.uid === data.teamMemberUpdated.user.uid
)
if (!val) return
Object.assign(val, data.teamMemberUpdated)
})
const removeSub = apollo
.subscribe({
query: gql`
subscription TeamMemberRemoved($teamID: ID!) {
teamMemberRemoved(teamID: $teamID)
}
`,
variables: {
teamID,
},
})
.subscribe(({ data }) => {
subject.next(
subject.value.filter(
(member) => member.user.uid !== data.teamMemberAdded.user.uid
)
)
})
const mainSub = subject.subscribe({
complete() {
addedSub.unsubscribe()
updateSub.unsubscribe()
removeSub.unsubscribe()
mainSub.unsubscribe()
},
})
return subject
}
export function createTeam(apollo, name) {
return apollo.mutate({
mutation: gql`
mutation ($name: String!) {
createTeam(name: $name) {
name
}
}
`,
variables: {
name,
},
})
}
export function addTeamMemberByEmail(apollo, userRole, userEmail, teamID) {
return apollo.mutate({
mutation: gql`
mutation addTeamMemberByEmail(
$userRole: TeamMemberRole!
$userEmail: String!
$teamID: ID!
) {
addTeamMemberByEmail(
userRole: $userRole
userEmail: $userEmail
teamID: $teamID
) {
role
}
}
`,
variables: {
userRole,
userEmail,
teamID,
},
})
}
export function updateTeamMemberRole(apollo, userID, newRole, teamID) {
return apollo.mutate({
mutation: gql`
mutation updateTeamMemberRole(
$newRole: TeamMemberRole!
$userUid: ID!
$teamID: ID!
) {
updateTeamMemberRole(
newRole: $newRole
userUid: $userUid
teamID: $teamID
) {
role
}
}
`,
variables: {
newRole,
userUid: userID,
teamID,
},
})
}
export function renameTeam(apollo, name, teamID) {
return apollo.mutate({
mutation: gql`
mutation renameTeam($newName: String!, $teamID: ID!) {
renameTeam(newName: $newName, teamID: $teamID) {
id
}
}
`,
variables: {
newName: name,
teamID,
},
})
}
export function removeTeamMember(apollo, userID, teamID) {
return apollo.mutate({
mutation: gql`
mutation removeTeamMember($userUid: ID!, $teamID: ID!) {
removeTeamMember(userUid: $userUid, teamID: $teamID)
}
`,
variables: {
userUid: userID,
teamID,
},
})
}
export async function deleteTeam(apollo, teamID) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($teamID: ID!) {
deleteTeam(teamID: $teamID)
}
`,
variables: {
teamID,
},
})
if (response !== undefined) break
}
return response
}
export function exitTeam(apollo, teamID) {
return apollo.mutate({
mutation: gql`
mutation ($teamID: ID!) {
leaveTeam(teamID: $teamID)
}
`,
variables: {
teamID,
},
})
}
export async function rootCollectionsOfTeam(apollo, teamID) {
const collections = []
let cursor = ""
while (true) {
const response = await apollo.query({
query: gql`
query rootCollectionsOfTeam($teamID: ID!, $cursor: ID!) {
rootCollectionsOfTeam(teamID: $teamID, cursor: $cursor) {
id
title
}
}
`,
variables: {
teamID,
cursor,
},
fetchPolicy: "no-cache",
})
if (response.data.rootCollectionsOfTeam.length === 0) break
response.data.rootCollectionsOfTeam.forEach((collection) => {
collections.push(collection)
})
cursor = collections[collections.length - 1].id
}
return collections
}
export async function getCollectionChildren(apollo, collectionID) {
const children = []
const response = await apollo.query({
query: gql`
query getCollectionChildren($collectionID: ID!) {
collection(collectionID: $collectionID) {
children {
id
title
}
}
}
`,
variables: {
collectionID,
},
fetchPolicy: "no-cache",
})
response.data.collection.children.forEach((child) => {
children.push(child)
})
return children
}
export async function getCollectionRequests(apollo, collectionID) {
const requests = []
let cursor = ""
while (true) {
const response = await apollo.query({
query: gql`
query getCollectionRequests($collectionID: ID!, $cursor: ID) {
requestsInCollection(collectionID: $collectionID, cursor: $cursor) {
id
title
request
}
}
`,
variables: {
collectionID,
cursor,
},
fetchPolicy: "no-cache",
})
response.data.requestsInCollection.forEach((request) => {
requests.push(request)
})
if (response.data.requestsInCollection.length < 10) {
break
}
cursor = requests[requests.length - 1].id
}
return requests
}
export async function renameCollection(apollo, title, id) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($newTitle: String!, $collectionID: ID!) {
renameCollection(newTitle: $newTitle, collectionID: $collectionID) {
id
}
}
`,
variables: {
newTitle: title,
collectionID: id,
},
})
if (response !== undefined) break
}
return response
}
export async function updateRequest(apollo, request, requestName, requestID) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($data: UpdateTeamRequestInput!, $requestID: ID!) {
updateRequest(data: $data, requestID: $requestID) {
id
}
}
`,
variables: {
data: {
request: JSON.stringify(request),
title: requestName,
},
requestID,
},
})
if (response !== undefined) break
}
return response
}
export async function addChildCollection(apollo, title, id) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($childTitle: String!, $collectionID: ID!) {
createChildCollection(
childTitle: $childTitle
collectionID: $collectionID
) {
id
}
}
`,
variables: {
childTitle: title,
collectionID: id,
},
})
if (response !== undefined) break
}
return response
}
export async function deleteCollection(apollo, id) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($collectionID: ID!) {
deleteCollection(collectionID: $collectionID)
}
`,
variables: {
collectionID: id,
},
})
if (response !== undefined) break
}
return response
}
export async function deleteRequest(apollo, requestID) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($requestID: ID!) {
deleteRequest(requestID: $requestID)
}
`,
variables: {
requestID,
},
})
if (response !== undefined) break
}
return response
}
export async function createNewRootCollection(apollo, title, id) {
let response
while (true) {
response = await apollo.mutate({
mutation: gql`
mutation ($title: String!, $teamID: ID!) {
createRootCollection(title: $title, teamID: $teamID) {
id
}
}
`,
variables: {
title,
teamID: id,
},
})
if (response !== undefined) break
}
return response
}
export async function saveRequestAsTeams(
apollo,
request,
title,
teamID,
collectionID
) {
const x = await apollo.mutate({
mutation: gql`
mutation ($data: CreateTeamRequestInput!, $collectionID: ID!) {
createRequestInCollection(data: $data, collectionID: $collectionID) {
id
collection {
id
team {
id
name
}
}
}
}
`,
variables: {
collectionID,
data: {
teamID,
title,
request,
},
},
})
return x.data?.createRequestInCollection
}
export async function overwriteRequestTeams(apollo, request, title, requestID) {
await apollo.mutate({
mutation: gql`
mutation updateRequest($data: UpdateTeamRequestInput!, $requestID: ID!) {
updateRequest(data: $data, requestID: $requestID) {
id
title
}
}
`,
variables: {
requestID,
data: {
request,
title,
},
},
})
}
export async function importFromMyCollections(apollo, collectionID, teamID) {
const response = await apollo.mutate({
mutation: gql`
mutation importFromMyCollections(
$fbCollectionPath: String!
$teamID: ID!
) {
importCollectionFromUserFirestore(
fbCollectionPath: $fbCollectionPath
teamID: $teamID
) {
id
title
}
}
`,
variables: {
fbCollectionPath: collectionID,
teamID,
},
})
return response.data != null
}
export async function importFromJSON(apollo, collections, teamID) {
const response = await apollo.mutate({
mutation: gql`
mutation importFromJSON($jsonString: String!, $teamID: ID!) {
importCollectionsFromJSON(jsonString: $jsonString, teamID: $teamID)
}
`,
variables: {
jsonString: JSON.stringify(collections),
teamID,
},
})
return response.data != null
}
export async function replaceWithJSON(apollo, collections, teamID) {
const response = await apollo.mutate({
mutation: gql`
mutation replaceWithJSON($jsonString: String!, $teamID: ID!) {
replaceCollectionsWithJSON(jsonString: $jsonString, teamID: $teamID)
}
`,
variables: {
jsonString: JSON.stringify(collections),
teamID,
},
})
return response.data != null
}
export async function exportAsJSON(apollo, teamID) {
const response = await apollo.query({
query: gql`
query exportAsJSON($teamID: ID!) {
exportCollectionsToJSON(teamID: $teamID)
}
`,
variables: {
teamID,
},
})
return response.data.exportCollectionsToJSON
}

View File

@@ -52,7 +52,6 @@ import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { setupLocalPersistence } from "~/newstore/localpersistence" import { setupLocalPersistence } from "~/newstore/localpersistence"
import { performMigrations } from "~/helpers/migrations" import { performMigrations } from "~/helpers/migrations"
import { initUserInfo } from "~/helpers/teams/BackendUserInfo" import { initUserInfo } from "~/helpers/teams/BackendUserInfo"
import { registerApolloAuthUpdate } from "~/helpers/apollo"
import { applySetting, useSetting } from "~/newstore/settings" import { applySetting, useSetting } from "~/newstore/settings"
import { logPageView } from "~/helpers/fb/analytics" import { logPageView } from "~/helpers/fb/analytics"
import { hookKeybindingsListener } from "~/helpers/keybindings" import { hookKeybindingsListener } from "~/helpers/keybindings"
@@ -193,8 +192,6 @@ export default defineComponent({
}, },
beforeMount() { beforeMount() {
setupLocalPersistence() setupLocalPersistence()
registerApolloAuthUpdate()
}, },
async mounted() { async mounted() {
performMigrations() performMigrations()

View File

@@ -100,7 +100,6 @@ export default {
"~/plugins/v-tippy", "~/plugins/v-tippy",
"~/plugins/v-focus", "~/plugins/v-focus",
"~/plugins/v-textarea", "~/plugins/v-textarea",
"~/plugins/vue-apollo",
"~/plugins/init-fb.ts", "~/plugins/init-fb.ts",
"~/plugins/crisp", "~/plugins/crisp",
{ src: "~/plugins/web-worker", ssr: false }, { src: "~/plugins/web-worker", ssr: false },

View File

@@ -34,7 +34,6 @@
}, },
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.0.3", "@apidevtools/swagger-parser": "^10.0.3",
"@apollo/client": "^3.5.8",
"@codemirror/autocomplete": "^0.19.0", "@codemirror/autocomplete": "^0.19.0",
"@codemirror/closebrackets": "^0.19.0", "@codemirror/closebrackets": "^0.19.0",
"@codemirror/commands": "^0.19.0", "@codemirror/commands": "^0.19.0",
@@ -104,7 +103,6 @@
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"tern": "^0.24.3", "tern": "^0.24.3",
"uuid": "8.3.2", "uuid": "8.3.2",
"vue-apollo": "^3.1.0",
"vue-functional-data-merge": "^3.1.0", "vue-functional-data-merge": "^3.1.0",
"vue-github-button": "^1.3.0", "vue-github-button": "^1.3.0",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",

View File

@@ -1,15 +0,0 @@
import Vue from "vue"
import VueApollo from "vue-apollo"
import { apolloClient } from "~/helpers/apollo"
const vueApolloProvider = new VueApollo({
defaultClient: apolloClient as any,
})
Vue.use(VueApollo)
export default (ctx: any) => {
const { app } = ctx
app.apolloProvider = vueApolloProvider
}

936
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff