chore: merge hoppscotch/staging into self-hosted/main

This commit is contained in:
Andrew Bastin
2023-03-30 15:14:59 +05:30
8 changed files with 186 additions and 16 deletions

View File

@@ -1,268 +0,0 @@
import {
addDoc,
collection,
deleteDoc,
doc,
getDocs,
getFirestore,
limit,
onSnapshot,
orderBy,
query,
updateDoc,
} from "firebase/firestore"
import { FormDataKeyValue } from "@hoppscotch/data"
import { platform } from "~/platform"
import { getSettingSubject, settingsStore } from "~/newstore/settings"
import {
GQLHistoryEntry,
graphqlHistoryStore,
HISTORY_LIMIT,
RESTHistoryEntry,
restHistoryStore,
setGraphqlHistoryEntries,
setRESTHistoryEntries,
translateToNewGQLHistory,
translateToNewRESTHistory,
} from "~/newstore/history"
type HistoryFBCollections = "history" | "graphqlHistory"
/**
* Whether the history are loaded. If this is set to true
* Updates to the history store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedRESTHistory = false
/**
* Whether the history are loaded. If this is set to true
* Updates to the history store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedGraphqlHistory = false
const purgeFormDataFromRequest = (req: RESTHistoryEntry): RESTHistoryEntry => {
if (req.request.body.contentType !== "multipart/form-data") return req
req.request.body.body = req.request.body.body.map<FormDataKeyValue>(
(formData) => {
if (!formData.isFile) return formData
return {
active: formData.active,
isFile: false, // Something we can do to keep the status ?
key: formData.key,
value: "",
}
}
)
return req
}
async function writeHistory(
entry: RESTHistoryEntry | GQLHistoryEntry,
col: HistoryFBCollections
) {
const processedEntry =
col === "history"
? purgeFormDataFromRequest(entry as RESTHistoryEntry)
: entry
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to sync history")
const hs = {
...processedEntry,
updatedOn: new Date(),
}
try {
await addDoc(collection(getFirestore(), "users", currentUser.uid, col), hs)
} catch (e) {
console.error("error writing to history", hs, e)
throw e
}
}
async function deleteHistory(
entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string },
col: HistoryFBCollections
) {
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to delete history")
try {
await deleteDoc(
doc(getFirestore(), "users", currentUser.uid, col, entry.id)
)
} catch (e) {
console.error("error deleting history", entry, e)
throw e
}
}
async function clearHistory(col: HistoryFBCollections) {
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to clear history")
const { docs } = await getDocs(
collection(getFirestore(), "users", currentUser.uid, col)
)
await Promise.all(docs.map((e) => deleteHistory(e as any, col)))
}
async function toggleStar(
entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string },
col: HistoryFBCollections
) {
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null) throw new Error("User not logged in to toggle star")
try {
await updateDoc(
doc(getFirestore(), "users", currentUser.uid, col, entry.id),
{ star: !entry.star }
)
} catch (e) {
console.error("error toggling star", entry, e)
throw e
}
}
export function initHistory() {
const currentUser$ = platform.auth.getCurrentUserStream()
const restHistorySub = restHistoryStore.dispatches$.subscribe((dispatch) => {
const currentUser = platform.auth.getCurrentUser()
if (loadedRESTHistory && currentUser && settingsStore.value.syncHistory) {
if (dispatch.dispatcher === "addEntry") {
writeHistory(dispatch.payload.entry, "history")
} else if (dispatch.dispatcher === "deleteEntry") {
deleteHistory(dispatch.payload.entry, "history")
} else if (dispatch.dispatcher === "clearHistory") {
clearHistory("history")
} else if (dispatch.dispatcher === "toggleStar") {
toggleStar(dispatch.payload.entry, "history")
}
}
})
const gqlHistorySub = graphqlHistoryStore.dispatches$.subscribe(
(dispatch) => {
const currentUser = platform.auth.getCurrentUser()
if (
loadedGraphqlHistory &&
currentUser &&
settingsStore.value.syncHistory
) {
if (dispatch.dispatcher === "addEntry") {
writeHistory(dispatch.payload.entry, "graphqlHistory")
} else if (dispatch.dispatcher === "deleteEntry") {
deleteHistory(dispatch.payload.entry, "graphqlHistory")
} else if (dispatch.dispatcher === "clearHistory") {
clearHistory("graphqlHistory")
} else if (dispatch.dispatcher === "toggleStar") {
toggleStar(dispatch.payload.entry, "graphqlHistory")
}
}
}
)
let restSnapshotStop: (() => void) | null = null
let graphqlSnapshotStop: (() => void) | null = null
const currentUserSub = currentUser$.subscribe((user) => {
if (!user) {
// Clear the snapshot listeners when the user logs out
if (restSnapshotStop) {
restSnapshotStop()
restSnapshotStop = null
}
if (graphqlSnapshotStop) {
graphqlSnapshotStop()
graphqlSnapshotStop = null
}
} else {
restSnapshotStop = onSnapshot(
query(
collection(getFirestore(), "users", user.uid, "history"),
orderBy("updatedOn", "desc"),
limit(HISTORY_LIMIT)
),
(historyRef) => {
const history: RESTHistoryEntry[] = []
historyRef.forEach((doc) => {
const entry = doc.data()
entry.id = doc.id
entry.updatedOn = doc.data().updatedOn.toDate()
history.push(translateToNewRESTHistory(entry))
})
loadedRESTHistory = false
setRESTHistoryEntries(history)
loadedRESTHistory = true
}
)
graphqlSnapshotStop = onSnapshot(
query(
collection(getFirestore(), "users", user.uid, "graphqlHistory"),
orderBy("updatedOn", "desc"),
limit(HISTORY_LIMIT)
),
(historyRef) => {
const history: GQLHistoryEntry[] = []
historyRef.forEach((doc) => {
const entry = doc.data()
entry.id = doc.id
entry.updatedOn = doc.data().updatedOn.toDate()
history.push(translateToNewGQLHistory(entry))
})
loadedGraphqlHistory = false
setGraphqlHistoryEntries(history)
loadedGraphqlHistory = true
}
)
}
})
let oldSyncStatus = settingsStore.value.syncHistory
const syncStop = getSettingSubject("syncHistory").subscribe((newStatus) => {
if (oldSyncStatus === true && newStatus === false) {
restSnapshotStop?.()
graphqlSnapshotStop?.()
oldSyncStatus = newStatus
} else if (oldSyncStatus === false && newStatus === true) {
syncStop.unsubscribe()
restHistorySub.unsubscribe()
gqlHistorySub.unsubscribe()
currentUserSub.unsubscribe()
initHistory()
}
})
}

View File

@@ -1,8 +1,6 @@
import { initializeApp } from "firebase/app"
import { platform } from "~/platform"
import { initAnalytics } from "./analytics"
import { initHistory } from "./history"
import { initSettings } from "./settings"
const firebaseConfig = {
apiKey: import.meta.env.VITE_API_KEY,
@@ -23,9 +21,9 @@ export function initializeFirebase() {
initializeApp(firebaseConfig)
platform.auth.performAuthInit()
initSettings()
platform.sync.settings.initSettingsSync()
platform.sync.collections.initCollectionsSync()
initHistory()
platform.sync.history.initHistorySync()
platform.sync.environments.initEnvironmentsSync()
initAnalytics()

View File

@@ -163,6 +163,20 @@ const RESTHistoryDispatchers = defineDispatchers({
}),
}
},
// only used for history.sync.ts to prevent double insertion of history entries from storeSync and Subscriptions
removeDuplicateEntry(currentVal: RESTHistoryType, { id }: { id: string }) {
const entries = currentVal.state.filter((e) => e.id === id)
if (entries.length == 2) {
const indexToRemove = currentVal.state.findIndex((e) => e.id === id)
currentVal.state.splice(indexToRemove, 1)
}
return {
state: currentVal.state,
}
},
})
const GQLHistoryDispatchers = defineDispatchers({
@@ -212,6 +226,20 @@ const GQLHistoryDispatchers = defineDispatchers({
}),
}
},
// only used for history.sync.ts to prevent double insertion of history entries from storeSync and Subscriptions
removeDuplicateEntry(currentVal: GraphqlHistoryType, { id }: { id: string }) {
const entries = currentVal.state.filter((e) => e.id === id)
if (entries.length == 2) {
const indexToRemove = currentVal.state.findIndex((e) => e.id === id)
currentVal.state.splice(indexToRemove, 1)
}
return {
state: currentVal.state,
}
},
})
export const restHistoryStore = new DispatchingStore(
@@ -297,6 +325,20 @@ export function toggleGraphqlHistoryEntryStar(entry: GQLHistoryEntry) {
})
}
export function removeDuplicateRestHistoryEntry(id: string) {
restHistoryStore.dispatch({
dispatcher: "removeDuplicateEntry",
payload: { id },
})
}
export function removeDuplicateGraphqlHistoryEntry(id: string) {
graphqlHistoryStore.dispatch({
dispatcher: "removeDuplicateEntry",
payload: { id },
})
}
// Listen to completed responses to add to history
completedRESTResponse$.subscribe((res) => {
if (res !== null) {

View File

@@ -0,0 +1,3 @@
export type HistoryPlatformDef = {
initHistorySync: () => void
}

View File

@@ -2,6 +2,8 @@ import { AuthPlatformDef } from "./auth"
import { UIPlatformDef } from "./ui"
import { EnvironmentsPlatformDef } from "./environments"
import { CollectionsPlatformDef } from "./collections"
import { SettingsPlatformDef } from "./settings"
import { HistoryPlatformDef } from "./history"
export type PlatformDef = {
ui?: UIPlatformDef
@@ -9,6 +11,8 @@ export type PlatformDef = {
sync: {
environments: EnvironmentsPlatformDef
collections: CollectionsPlatformDef
settings: SettingsPlatformDef
history: HistoryPlatformDef
}
}

View File

@@ -0,0 +1,3 @@
export type SettingsPlatformDef = {
initSettingsSync: () => void
}