feat: implement user history syncing for selfhost (#60)

This commit is contained in:
Akash K
2023-04-01 18:24:58 +05:30
committed by GitHub
parent 2b44ede92b
commit 8586ced3cc
13 changed files with 543 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
import {
runMutation,
runGQLQuery,
runGQLSubscription,
} from "@hoppscotch/common/helpers/backend/GQLClient"
import {
CreateUserHistoryDocument,
CreateUserHistoryMutation,
CreateUserHistoryMutationVariables,
DeleteAllUserHistoryDocument,
DeleteAllUserHistoryMutation,
DeleteAllUserHistoryMutationVariables,
GetRestUserHistoryDocument,
GetRestUserHistoryQuery,
GetRestUserHistoryQueryVariables,
RemoveRequestFromHistoryDocument,
RemoveRequestFromHistoryMutation,
RemoveRequestFromHistoryMutationVariables,
ReqType,
ToggleHistoryStarStatusDocument,
ToggleHistoryStarStatusMutation,
ToggleHistoryStarStatusMutationVariables,
UserHistoryCreatedDocument,
UserHistoryDeletedDocument,
UserHistoryDeletedManyDocument,
UserHistoryUpdatedDocument,
} from "../../api/generated/graphql"
export const getUserHistoryEntries = () =>
runGQLQuery<GetRestUserHistoryQuery, GetRestUserHistoryQueryVariables, "">({
query: GetRestUserHistoryDocument,
})
export const createUserHistory = (
reqData: string,
resMetadata: string,
reqType: ReqType
) =>
runMutation<
CreateUserHistoryMutation,
CreateUserHistoryMutationVariables,
""
>(CreateUserHistoryDocument, {
reqData,
resMetadata,
reqType,
})()
export const toggleHistoryStarStatus = (id: string) =>
runMutation<
ToggleHistoryStarStatusMutation,
ToggleHistoryStarStatusMutationVariables,
""
>(ToggleHistoryStarStatusDocument, {
id,
})()
export const removeRequestFromHistory = (id: string) =>
runMutation<
RemoveRequestFromHistoryMutation,
RemoveRequestFromHistoryMutationVariables,
""
>(RemoveRequestFromHistoryDocument, {
id,
})()
export const deleteAllUserHistory = (reqType: ReqType) =>
runMutation<
DeleteAllUserHistoryMutation,
DeleteAllUserHistoryMutationVariables,
""
>(DeleteAllUserHistoryDocument, {
reqType,
})()
export const runUserHistoryCreatedSubscription = () =>
runGQLSubscription({
query: UserHistoryCreatedDocument,
})
export const runUserHistoryUpdatedSubscription = () =>
runGQLSubscription({
query: UserHistoryUpdatedDocument,
})
export const runUserHistoryDeletedSubscription = () =>
runGQLSubscription({
query: UserHistoryDeletedDocument,
})
export const runUserHistoryDeletedManySubscription = () =>
runGQLSubscription({
query: UserHistoryDeletedManyDocument,
})

View File

@@ -0,0 +1,261 @@
import { authEvents$, def as platformAuth } from "@platform/auth"
import {
restHistoryStore,
RESTHistoryEntry,
setRESTHistoryEntries,
addRESTHistoryEntry,
toggleRESTHistoryEntryStar,
deleteRESTHistoryEntry,
clearRESTHistory,
setGraphqlHistoryEntries,
GQLHistoryEntry,
addGraphqlHistoryEntry,
toggleGraphqlHistoryEntryStar,
graphqlHistoryStore,
deleteGraphqlHistoryEntry,
clearGraphqlHistory,
} from "@hoppscotch/common/newstore/history"
import { HistoryPlatformDef } from "@hoppscotch/common/platform/history"
import {
getUserHistoryEntries,
runUserHistoryCreatedSubscription,
runUserHistoryDeletedManySubscription,
runUserHistoryDeletedSubscription,
runUserHistoryUpdatedSubscription,
} from "./history.api"
import * as E from "fp-ts/Either"
import { restHistorySyncer, gqlHistorySyncer } from "./history.sync"
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
import { runDispatchWithOutSyncing } from "@lib/sync"
import { ReqType } from "../../api/generated/graphql"
function initHistorySync() {
const currentUser$ = platformAuth.getCurrentUserStream()
restHistorySyncer.startStoreSync()
restHistorySyncer.setupSubscriptions(setupSubscriptions)
gqlHistorySyncer.startStoreSync()
loadHistoryEntries()
currentUser$.subscribe(async (user) => {
if (user) {
await loadHistoryEntries()
}
})
authEvents$.subscribe((event) => {
if (event.event == "login" || event.event == "token_refresh") {
restHistorySyncer.startListeningToSubscriptions()
}
if (event.event == "logout") {
restHistorySyncer.stopListeningToSubscriptions()
}
})
}
function setupSubscriptions() {
let subs: ReturnType<typeof runGQLSubscription>[1][] = []
const userHistoryCreatedSub = setupUserHistoryCreatedSubscription()
const userHistoryUpdatedSub = setupUserHistoryUpdatedSubscription()
const userHistoryDeletedSub = setupUserHistoryDeletedSubscription()
const userHistoryDeletedManySub = setupUserHistoryDeletedManySubscription()
subs = [
userHistoryCreatedSub,
userHistoryUpdatedSub,
userHistoryDeletedSub,
userHistoryDeletedManySub,
]
return () => {
subs.forEach((sub) => sub.unsubscribe())
}
}
async function loadHistoryEntries() {
const res = await getUserHistoryEntries()
if (E.isRight(res)) {
const restEntries = res.right.me.RESTHistory
const gqlEntries = res.right.me.GQLHistory
const restHistoryEntries: RESTHistoryEntry[] = restEntries.map((entry) => ({
v: 1,
request: JSON.parse(entry.request),
responseMeta: JSON.parse(entry.responseMetadata),
star: entry.isStarred,
updatedOn: new Date(entry.executedOn),
id: entry.id,
}))
const gqlHistoryEntries: GQLHistoryEntry[] = gqlEntries.map((entry) => ({
v: 1,
request: JSON.parse(entry.request),
response: JSON.parse(entry.responseMetadata),
star: entry.isStarred,
updatedOn: new Date(entry.executedOn),
id: entry.id,
}))
runDispatchWithOutSyncing(() => {
setRESTHistoryEntries(restHistoryEntries)
setGraphqlHistoryEntries(gqlHistoryEntries)
})
}
}
function setupUserHistoryCreatedSubscription() {
const [userHistoryCreated$, userHistoryCreatedSub] =
runUserHistoryCreatedSubscription()
userHistoryCreated$.subscribe((res) => {
if (E.isRight(res)) {
const { id, reqType, request, responseMetadata, isStarred, executedOn } =
res.right.userHistoryCreated
const hasAlreadyBeenAdded =
reqType == ReqType.Rest
? restHistoryStore.value.state.some((entry) => entry.id == id)
: graphqlHistoryStore.value.state.some((entry) => entry.id == id)
!hasAlreadyBeenAdded &&
runDispatchWithOutSyncing(() => {
reqType == ReqType.Rest
? addRESTHistoryEntry({
v: 1,
id,
request: JSON.parse(request),
responseMeta: JSON.parse(responseMetadata),
star: isStarred,
updatedOn: new Date(executedOn),
})
: addGraphqlHistoryEntry({
v: 1,
id,
request: JSON.parse(request),
response: JSON.parse(responseMetadata),
star: isStarred,
updatedOn: new Date(executedOn),
})
})
}
})
return userHistoryCreatedSub
}
// currently the updates are only for toggling the star
function setupUserHistoryUpdatedSubscription() {
const [userHistoryUpdated$, userHistoryUpdatedSub] =
runUserHistoryUpdatedSubscription()
userHistoryUpdated$.subscribe((res) => {
if (E.isRight(res)) {
const { id, executedOn, isStarred, request, responseMetadata, reqType } =
res.right.userHistoryUpdated
if (reqType == ReqType.Rest) {
const updatedRestEntryIndex = restHistoryStore.value.state.findIndex(
(entry) => entry.id == id
)
if (updatedRestEntryIndex != -1) {
runDispatchWithOutSyncing(() => {
toggleRESTHistoryEntryStar({
v: 1,
id,
request: JSON.parse(request),
responseMeta: JSON.parse(responseMetadata),
// because the star will be toggled in the store, we need to pass the opposite value
star: !isStarred,
updatedOn: new Date(executedOn),
})
})
}
}
if (reqType == ReqType.Gql) {
const updatedGQLEntryIndex = graphqlHistoryStore.value.state.findIndex(
(entry) => entry.id == id
)
if (updatedGQLEntryIndex != -1) {
runDispatchWithOutSyncing(() => {
toggleGraphqlHistoryEntryStar({
v: 1,
id,
request: JSON.parse(request),
response: JSON.parse(responseMetadata),
// because the star will be toggled in the store, we need to pass the opposite value
star: !isStarred,
updatedOn: new Date(executedOn),
})
})
}
}
}
})
return userHistoryUpdatedSub
}
function setupUserHistoryDeletedSubscription() {
const [userHistoryDeleted$, userHistoryDeletedSub] =
runUserHistoryDeletedSubscription()
userHistoryDeleted$.subscribe((res) => {
if (E.isRight(res)) {
const { id, reqType } = res.right.userHistoryDeleted
if (reqType == ReqType.Gql) {
const deletedEntry = graphqlHistoryStore.value.state.find(
(entry) => entry.id == id
)
deletedEntry &&
runDispatchWithOutSyncing(() => {
deleteGraphqlHistoryEntry(deletedEntry)
})
}
if (reqType == ReqType.Rest) {
const deletedEntry = restHistoryStore.value.state.find(
(entry) => entry.id == id
)
deletedEntry &&
runDispatchWithOutSyncing(() => {
deleteRESTHistoryEntry(deletedEntry)
})
}
}
})
return userHistoryDeletedSub
}
function setupUserHistoryDeletedManySubscription() {
const [userHistoryDeletedMany$, userHistoryDeletedManySub] =
runUserHistoryDeletedManySubscription()
userHistoryDeletedMany$.subscribe((res) => {
if (E.isRight(res)) {
const { reqType } = res.right.userHistoryDeletedMany
runDispatchWithOutSyncing(() => {
reqType == ReqType.Rest ? clearRESTHistory() : clearGraphqlHistory()
})
}
})
return userHistoryDeletedManySub
}
export const def: HistoryPlatformDef = {
initHistorySync,
}

View File

@@ -0,0 +1,101 @@
import {
graphqlHistoryStore,
removeDuplicateRestHistoryEntry,
removeDuplicateGraphqlHistoryEntry,
restHistoryStore,
} from "@hoppscotch/common/newstore/history"
import {
getSettingSubject,
settingsStore,
} from "@hoppscotch/common/newstore/settings"
import { getSyncInitFunction } from "../../lib/sync"
import * as E from "fp-ts/Either"
import { StoreSyncDefinitionOf } from "../../lib/sync"
import {
createUserHistory,
deleteAllUserHistory,
removeRequestFromHistory,
toggleHistoryStarStatus,
} from "./history.api"
import { ReqType } from "../../api/generated/graphql"
export const restHistoryStoreSyncDefinition: StoreSyncDefinitionOf<
typeof restHistoryStore
> = {
async addEntry({ entry }) {
const res = await createUserHistory(
JSON.stringify(entry.request),
JSON.stringify(entry.responseMeta),
ReqType.Rest
)
if (E.isRight(res)) {
entry.id = res.right.createUserHistory.id
// preventing double insertion from here and subscription
removeDuplicateRestHistoryEntry(entry.id)
}
},
deleteEntry({ entry }) {
if (entry.id) {
removeRequestFromHistory(entry.id)
}
},
toggleStar({ entry }) {
if (entry.id) {
toggleHistoryStarStatus(entry.id)
}
},
clearHistory() {
deleteAllUserHistory(ReqType.Rest)
},
}
export const gqlHistoryStoreSyncDefinition: StoreSyncDefinitionOf<
typeof graphqlHistoryStore
> = {
async addEntry({ entry }) {
const res = await createUserHistory(
JSON.stringify(entry.request),
JSON.stringify(entry.response),
ReqType.Gql
)
if (E.isRight(res)) {
entry.id = res.right.createUserHistory.id
// preventing double insertion from here and subscription
removeDuplicateGraphqlHistoryEntry(entry.id)
}
},
deleteEntry({ entry }) {
if (entry.id) {
removeRequestFromHistory(entry.id)
}
},
toggleStar({ entry }) {
if (entry.id) {
toggleHistoryStarStatus(entry.id)
}
},
clearHistory() {
deleteAllUserHistory(ReqType.Gql)
},
}
export const restHistorySyncer = getSyncInitFunction(
restHistoryStore,
restHistoryStoreSyncDefinition,
() => settingsStore.value.syncHistory,
getSettingSubject("syncHistory")
)
export const gqlHistorySyncer = getSyncInitFunction(
graphqlHistoryStore,
gqlHistoryStoreSyncDefinition,
() => settingsStore.value.syncHistory,
getSettingSubject("syncHistory")
)