Merge pull request #1367 from hoppscotch/feat/gist
Collections and environments + GitHub gist
This commit is contained in:
@@ -6,6 +6,44 @@
|
|||||||
<div class="row-wrapper">
|
<div class="row-wrapper">
|
||||||
<h3 class="title">{{ $t("import_export") }} {{ $t("collections") }}</h3>
|
<h3 class="title">{{ $t("import_export") }} {{ $t("collections") }}</h3>
|
||||||
<div>
|
<div>
|
||||||
|
<v-popover>
|
||||||
|
<button class="tooltip-target icon" v-tooltip.left="$t('more')">
|
||||||
|
<i class="material-icons">more_vert</i>
|
||||||
|
</button>
|
||||||
|
<template slot="popover">
|
||||||
|
<div>
|
||||||
|
<button class="icon" @click="readCollectionGist" v-close-popover>
|
||||||
|
<i class="material-icons">code</i>
|
||||||
|
<span>{{ $t("import_from_gist") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-tooltip.bottom="{
|
||||||
|
content: !fb.currentUser
|
||||||
|
? $t('login_with_github_to') + $t('create_secret_gist')
|
||||||
|
: fb.currentUser.provider !== 'github.com'
|
||||||
|
? $t('login_with_github_to') + $t('create_secret_gist')
|
||||||
|
: null,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
:disabled="
|
||||||
|
!fb.currentUser
|
||||||
|
? true
|
||||||
|
: fb.currentUser.provider !== 'github.com'
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
"
|
||||||
|
class="icon"
|
||||||
|
@click="createCollectionGist"
|
||||||
|
v-close-popover
|
||||||
|
>
|
||||||
|
<i class="material-icons">code</i>
|
||||||
|
<span>{{ $t("create_secret_gist") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-popover>
|
||||||
<button class="icon" @click="hideModal">
|
<button class="icon" @click="hideModal">
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
</button>
|
</button>
|
||||||
@@ -93,6 +131,57 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async createCollectionGist() {
|
||||||
|
await this.$axios
|
||||||
|
.$post(
|
||||||
|
"https://api.github.com/gists",
|
||||||
|
{
|
||||||
|
files: {
|
||||||
|
"hoppscotch-collections.json": {
|
||||||
|
content: this.collectionJson,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${fb.currentUser.accessToken}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
this.$toast.success(this.$t("gist_created"), {
|
||||||
|
icon: "done",
|
||||||
|
})
|
||||||
|
window.open(response.html_url)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(this.$t("something_went_wrong"), {
|
||||||
|
icon: "error",
|
||||||
|
})
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async readCollectionGist() {
|
||||||
|
let gist = prompt(this.$t("enter_gist_url"))
|
||||||
|
if (!gist) return
|
||||||
|
await this.$axios
|
||||||
|
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
let collections = JSON.parse(Object.values(response.files)[0].content)
|
||||||
|
this.$store.commit("postwoman/replaceCollections", collections)
|
||||||
|
this.fileImported()
|
||||||
|
this.syncToFBCollections()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.failedImport()
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
hideModal() {
|
hideModal() {
|
||||||
this.$emit("hide-modal")
|
this.$emit("hide-modal")
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,44 @@
|
|||||||
<div class="row-wrapper">
|
<div class="row-wrapper">
|
||||||
<h3 class="title">{{ $t("import_export") }} {{ $t("environments") }}</h3>
|
<h3 class="title">{{ $t("import_export") }} {{ $t("environments") }}</h3>
|
||||||
<div>
|
<div>
|
||||||
|
<v-popover>
|
||||||
|
<button class="tooltip-target icon" v-tooltip.left="$t('more')">
|
||||||
|
<i class="material-icons">more_vert</i>
|
||||||
|
</button>
|
||||||
|
<template slot="popover">
|
||||||
|
<div>
|
||||||
|
<button class="icon" @click="readEnvironmentGist" v-close-popover>
|
||||||
|
<i class="material-icons">code</i>
|
||||||
|
<span>{{ $t("import_from_gist") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-tooltip.bottom="{
|
||||||
|
content: !fb.currentUser
|
||||||
|
? $t('login_with_github_to') + $t('create_secret_gist')
|
||||||
|
: fb.currentUser.provider !== 'github.com'
|
||||||
|
? $t('login_with_github_to') + $t('create_secret_gist')
|
||||||
|
: null,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
:disabled="
|
||||||
|
!fb.currentUser
|
||||||
|
? true
|
||||||
|
: fb.currentUser.provider !== 'github.com'
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
"
|
||||||
|
class="icon"
|
||||||
|
@click="createEnvironmentGist"
|
||||||
|
v-close-popover
|
||||||
|
>
|
||||||
|
<i class="material-icons">code</i>
|
||||||
|
<span>{{ $t("create_secret_gist") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-popover>
|
||||||
<button class="icon" @click="hideModal">
|
<button class="icon" @click="hideModal">
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
</button>
|
</button>
|
||||||
@@ -93,6 +131,57 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async createEnvironmentGist() {
|
||||||
|
await this.$axios
|
||||||
|
.$post(
|
||||||
|
"https://api.github.com/gists",
|
||||||
|
{
|
||||||
|
files: {
|
||||||
|
"hoppscotch-environments.json": {
|
||||||
|
content: this.environmentJson,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${fb.currentUser.accessToken}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
this.$toast.success(this.$t("gist_created"), {
|
||||||
|
icon: "done",
|
||||||
|
})
|
||||||
|
window.open(response.html_url)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(this.$t("something_went_wrong"), {
|
||||||
|
icon: "error",
|
||||||
|
})
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async readEnvironmentGist() {
|
||||||
|
let gist = prompt(this.$t("enter_gist_url"))
|
||||||
|
if (!gist) return
|
||||||
|
await this.$axios
|
||||||
|
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
let environments = JSON.parse(Object.values(response.files)[0].content)
|
||||||
|
this.$store.commit("postwoman/replaceEnvironments", environments)
|
||||||
|
this.fileImported()
|
||||||
|
this.syncToFBEnvironments()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.failedImport()
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
hideModal() {
|
hideModal() {
|
||||||
this.$emit("hide-modal")
|
this.$emit("hide-modal")
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export default {
|
|||||||
|
|
||||||
this.showLoginSuccess()
|
this.showLoginSuccess()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
// An error happened.
|
// An error happened.
|
||||||
if (err.code === "auth/account-exists-with-different-credential") {
|
if (err.code === "auth/account-exists-with-different-credential") {
|
||||||
// Step 2.
|
// Step 2.
|
||||||
@@ -80,22 +81,23 @@ export default {
|
|||||||
// Asks the user their password.
|
// Asks the user their password.
|
||||||
// In real scenario, you should handle this asynchronously.
|
// In real scenario, you should handle this asynchronously.
|
||||||
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
||||||
const user = await fb.signInWithEmailAndPassword(email, password)
|
|
||||||
|
|
||||||
|
const user = await fb.signInWithEmailAndPassword(email, password)
|
||||||
await user.linkWithCredential(pendingCred)
|
await user.linkWithCredential(pendingCred)
|
||||||
|
|
||||||
this.showLoginSuccess()
|
this.showLoginSuccess()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$toast.info(`${this.$t("login_with")}`, {
|
this.$toast.info(`${this.$t("account_exists")}`, {
|
||||||
icon: "vpn_key",
|
icon: "vpn_key",
|
||||||
duration: null,
|
duration: null,
|
||||||
closeOnSwipe: false,
|
closeOnSwipe: false,
|
||||||
action: {
|
action: {
|
||||||
text: this.$t("yes"),
|
text: this.$t("yes"),
|
||||||
onClick: async (e, toastObject) => {
|
onClick: async (e, toastObject) => {
|
||||||
const user = await fb.signInWithGithub()
|
const { user } = await fb.signInWithGithub()
|
||||||
await user.linkAndRetrieveDataWithCredential(pendingCred)
|
await user.linkAndRetrieveDataWithCredential(pendingCred)
|
||||||
|
|
||||||
this.showLoginSuccess()
|
this.showLoginSuccess()
|
||||||
@@ -109,7 +111,9 @@ export default {
|
|||||||
},
|
},
|
||||||
async signInWithGithub() {
|
async signInWithGithub() {
|
||||||
try {
|
try {
|
||||||
const { additionalUserInfo } = await fb.signInUserWithGithub()
|
const { credential, additionalUserInfo } = await fb.signInUserWithGithub()
|
||||||
|
|
||||||
|
fb.setProviderInfo(credential.providerId, credential.accessToken)
|
||||||
|
|
||||||
if (additionalUserInfo.isNewUser) {
|
if (additionalUserInfo.isNewUser) {
|
||||||
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
||||||
@@ -131,6 +135,7 @@ export default {
|
|||||||
|
|
||||||
this.showLoginSuccess()
|
this.showLoginSuccess()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
// An error happened.
|
// An error happened.
|
||||||
if (err.code === "auth/account-exists-with-different-credential") {
|
if (err.code === "auth/account-exists-with-different-credential") {
|
||||||
// Step 2.
|
// Step 2.
|
||||||
@@ -158,7 +163,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$toast.info(`${this.$t("login_with")}`, {
|
this.$toast.info(`${this.$t("account_exists")}`, {
|
||||||
icon: "vpn_key",
|
icon: "vpn_key",
|
||||||
duration: null,
|
duration: null,
|
||||||
closeOnSwipe: false,
|
closeOnSwipe: false,
|
||||||
|
|||||||
@@ -205,7 +205,11 @@ describe("FirebaseInstance", () => {
|
|||||||
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
||||||
|
|
||||||
const fb = new FirebaseInstance(mocksdk, {
|
const fb = new FirebaseInstance(mocksdk, {
|
||||||
github: () => {},
|
github: () => {
|
||||||
|
return {
|
||||||
|
addScope: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
signOutUser()
|
signOutUser()
|
||||||
@@ -218,7 +222,11 @@ describe("FirebaseInstance", () => {
|
|||||||
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
||||||
|
|
||||||
const fb = new FirebaseInstance(mocksdk, {
|
const fb = new FirebaseInstance(mocksdk, {
|
||||||
github: () => {},
|
github: () => {
|
||||||
|
return {
|
||||||
|
addScope: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
signOutUser()
|
signOutUser()
|
||||||
@@ -231,7 +239,11 @@ describe("FirebaseInstance", () => {
|
|||||||
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
||||||
|
|
||||||
const fb = new FirebaseInstance(mocksdk, {
|
const fb = new FirebaseInstance(mocksdk, {
|
||||||
github: () => {},
|
github: () => {
|
||||||
|
return {
|
||||||
|
addScope: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
signOutUser()
|
signOutUser()
|
||||||
@@ -240,11 +252,35 @@ describe("FirebaseInstance", () => {
|
|||||||
|
|
||||||
await expect(fb.signInUserWithGithub()).rejects.toEqual("test error")
|
await expect(fb.signInUserWithGithub()).rejects.toEqual("test error")
|
||||||
})
|
})
|
||||||
|
test("adds 'repo gist' scope", async () => {
|
||||||
|
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
||||||
|
|
||||||
|
const addScopeMock = jest.fn()
|
||||||
|
|
||||||
|
const fb = new FirebaseInstance(mocksdk, {
|
||||||
|
github: () => {
|
||||||
|
return {
|
||||||
|
addScope: addScopeMock
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
signOutUser()
|
||||||
|
|
||||||
|
fbFunc.mockImplementation(() => Promise.resolve("test"))
|
||||||
|
await fb.signInUserWithGithub()
|
||||||
|
|
||||||
|
expect(addScopeMock).toBeCalledWith("repo gist")
|
||||||
|
})
|
||||||
test("resolves the response the firebase request resolves", async () => {
|
test("resolves the response the firebase request resolves", async () => {
|
||||||
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
const fbFunc = jest.spyOn(mockAuth, "signInWithPopup")
|
||||||
|
|
||||||
const fb = new FirebaseInstance(mocksdk, {
|
const fb = new FirebaseInstance(mocksdk, {
|
||||||
github: () => {},
|
github: () => {
|
||||||
|
return {
|
||||||
|
addScope: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
signOutUser()
|
signOutUser()
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export class FirebaseInstance {
|
|||||||
this.app.auth().onAuthStateChanged((user) => {
|
this.app.auth().onAuthStateChanged((user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.currentUser = user
|
this.currentUser = user
|
||||||
|
|
||||||
this.currentUser.providerData.forEach((profile) => {
|
this.currentUser.providerData.forEach((profile) => {
|
||||||
let us = {
|
let us = {
|
||||||
updatedOn: new Date(),
|
updatedOn: new Date(),
|
||||||
@@ -47,10 +48,15 @@ export class FirebaseInstance {
|
|||||||
}
|
}
|
||||||
this.usersCollection
|
this.usersCollection
|
||||||
.doc(this.currentUser.uid)
|
.doc(this.currentUser.uid)
|
||||||
.set(us)
|
.set(us, { merge: true })
|
||||||
.catch((e) => console.error("error updating", us, e))
|
.catch((e) => console.error("error updating", us, e))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.usersCollection.doc(this.currentUser.uid).onSnapshot((doc) => {
|
||||||
|
this.currentUser.provider = doc.data().provider
|
||||||
|
this.currentUser.accessToken = doc.data().accessToken
|
||||||
|
})
|
||||||
|
|
||||||
this.usersCollection
|
this.usersCollection
|
||||||
.doc(this.currentUser.uid)
|
.doc(this.currentUser.uid)
|
||||||
.collection("feeds")
|
.collection("feeds")
|
||||||
@@ -131,7 +137,7 @@ export class FirebaseInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async signInUserWithGithub() {
|
async signInUserWithGithub() {
|
||||||
return await this.app.auth().signInWithPopup(this.authProviders.github())
|
return await this.app.auth().signInWithPopup(this.authProviders.github().addScope("repo gist"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async signInWithEmailAndPassword(email, password) {
|
async signInWithEmailAndPassword(email, password) {
|
||||||
@@ -288,6 +294,24 @@ export class FirebaseInstance {
|
|||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setProviderInfo(id, token) {
|
||||||
|
const us = {
|
||||||
|
updatedOn: new Date(),
|
||||||
|
provider: id,
|
||||||
|
accessToken: token,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.update(us)
|
||||||
|
.catch((e) => console.error("error updating", us, e))
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error updating", ev, e)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders)
|
export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders)
|
||||||
|
|||||||
@@ -294,5 +294,11 @@
|
|||||||
"experiments": "Experiments",
|
"experiments": "Experiments",
|
||||||
"experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ",
|
"experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ",
|
||||||
"use_experimental_url_bar": "Use experimental URL bar with environment highlighting",
|
"use_experimental_url_bar": "Use experimental URL bar with environment highlighting",
|
||||||
"select_environment": "Select environment"
|
"select_environment": "Select environment",
|
||||||
|
"login_with_github_to": "Login with GitHub to ",
|
||||||
|
"create_secret_gist": "Create secret Gist",
|
||||||
|
"gist_created": "Gist created",
|
||||||
|
"import_from_gist": "Import from Gist",
|
||||||
|
"enter_gist_url": "Enter Gist URL",
|
||||||
|
"account_exists": "Account exists with different credential - Login to link both accounts"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user