refactor: monorepo+pnpm (removed husky)
This commit is contained in:
84
packages/hoppscotch-app/helpers/teams/BackendUserInfo.ts
Normal file
84
packages/hoppscotch-app/helpers/teams/BackendUserInfo.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import gql from "graphql-tag"
|
||||
import { authIdToken$ } from "../fb/auth"
|
||||
import { apolloClient } from "../apollo"
|
||||
|
||||
/*
|
||||
* This file deals with interfacing data provided by the
|
||||
* Hoppscotch Backend server
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the information provided about a user
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/**
|
||||
* UID of the user
|
||||
*/
|
||||
uid: string
|
||||
/**
|
||||
* Displayable name of the user (or null if none available)
|
||||
*/
|
||||
displayName: string | null
|
||||
/**
|
||||
* Email of the user (or null if none available)
|
||||
*/
|
||||
email: string | null
|
||||
/**
|
||||
* URL to the profile photo of the user (or null if none available)
|
||||
*/
|
||||
photoURL: string | null
|
||||
/**
|
||||
* Whether the user has access to Early Access features
|
||||
*/
|
||||
eaInvited: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable subject onto the currently logged in user info (is null if not logged in)
|
||||
*/
|
||||
export const currentUserInfo$ = new BehaviorSubject<UserInfo | null>(null)
|
||||
|
||||
/**
|
||||
* Initializes the currenUserInfo$ view and sets up its update mechanism
|
||||
*/
|
||||
export function initUserInfo() {
|
||||
authIdToken$.subscribe((token) => {
|
||||
if (token) {
|
||||
updateUserInfo()
|
||||
} else {
|
||||
currentUserInfo$.next(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the actual user info fetching
|
||||
*/
|
||||
async function updateUserInfo() {
|
||||
try {
|
||||
const { data } = await apolloClient.query({
|
||||
query: gql`
|
||||
query GetUserInfo {
|
||||
me {
|
||||
uid
|
||||
displayName
|
||||
email
|
||||
photoURL
|
||||
eaInvited
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
currentUserInfo$.next({
|
||||
uid: data.me.uid,
|
||||
displayName: data.me.displayName,
|
||||
email: data.me.email,
|
||||
photoURL: data.me.photoURL,
|
||||
eaInvited: data.me.eaInvited,
|
||||
})
|
||||
} catch (e) {
|
||||
currentUserInfo$.next(null)
|
||||
}
|
||||
}
|
||||
11
packages/hoppscotch-app/helpers/teams/TeamCollection.ts
Normal file
11
packages/hoppscotch-app/helpers/teams/TeamCollection.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TeamRequest } from "./TeamRequest"
|
||||
|
||||
/**
|
||||
* Defines how a Team Collection is represented in the TeamCollectionAdapter
|
||||
*/
|
||||
export interface TeamCollection {
|
||||
id: string
|
||||
title: string
|
||||
children: TeamCollection[] | null
|
||||
requests: TeamRequest[] | null
|
||||
}
|
||||
563
packages/hoppscotch-app/helpers/teams/TeamCollectionAdapter.ts
Normal file
563
packages/hoppscotch-app/helpers/teams/TeamCollectionAdapter.ts
Normal file
@@ -0,0 +1,563 @@
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import { gql } from "graphql-tag"
|
||||
import pull from "lodash/pull"
|
||||
import remove from "lodash/remove"
|
||||
import { translateToNewRequest } from "../types/HoppRESTRequest"
|
||||
import { TeamCollection } from "./TeamCollection"
|
||||
import { TeamRequest } from "./TeamRequest"
|
||||
import {
|
||||
rootCollectionsOfTeam,
|
||||
getCollectionChildren,
|
||||
getCollectionRequests,
|
||||
} from "./utils"
|
||||
import { apolloClient } from "~/helpers/apollo"
|
||||
|
||||
/*
|
||||
* NOTE: These functions deal with REFERENCES to objects and mutates them, for a simpler implementation.
|
||||
* Be careful when you play with these.
|
||||
*
|
||||
* I am not a fan of mutating references but this is so much simpler compared to mutating clones
|
||||
* - Andrew
|
||||
*/
|
||||
|
||||
/**
|
||||
* Finds the parent of a collection and returns the REFERENCE (or null)
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to look in
|
||||
* @param {string} collID - ID of the collection to find the parent of
|
||||
* @param {TeamCollection} currentParent - (used for recursion, do not set) The parent in the current iteration (undefined if root)
|
||||
*
|
||||
* @returns REFERENCE to the collecton or null if not found or the collection is in root
|
||||
*/
|
||||
function findParentOfColl(
|
||||
tree: TeamCollection[],
|
||||
collID: string,
|
||||
currentParent?: TeamCollection
|
||||
): TeamCollection | null {
|
||||
for (const coll of tree) {
|
||||
// If the root is parent, return null
|
||||
if (coll.id === collID) return currentParent || null
|
||||
|
||||
// Else run it in children
|
||||
if (coll.children) {
|
||||
const result = findParentOfColl(coll.children, collID, coll)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a REFERENCE collection in the given tree (or null)
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to look in
|
||||
* @param {string} targetID - The ID of the collection to look for
|
||||
*
|
||||
* @returns REFERENCE to the collection or null if not found
|
||||
*/
|
||||
function findCollInTree(
|
||||
tree: TeamCollection[],
|
||||
targetID: string
|
||||
): TeamCollection | null {
|
||||
for (const coll of tree) {
|
||||
// If the direct child matched, then return that
|
||||
if (coll.id === targetID) return coll
|
||||
|
||||
// Else run it in the children
|
||||
if (coll.children) {
|
||||
const result = findCollInTree(coll.children, targetID)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing matched, return null
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a REFERENCE to the collection containing a given request ID in tree (or null)
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to look in
|
||||
* @param {string} reqID - The ID of the request to look for
|
||||
*
|
||||
* @returns REFERENCE to the collection or null if request not found
|
||||
*/
|
||||
function findCollWithReqIDInTree(
|
||||
tree: TeamCollection[],
|
||||
reqID: string
|
||||
): TeamCollection | null {
|
||||
for (const coll of tree) {
|
||||
// Check in root collections (if expanded)
|
||||
if (coll.requests) {
|
||||
if (coll.requests.find((req) => req.id === reqID)) return coll
|
||||
}
|
||||
|
||||
// Check in children of collections
|
||||
if (coll.children) {
|
||||
const result = findCollWithReqIDInTree(coll.children, reqID)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
|
||||
// No matches
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a REFERENCE to the request with the given ID (or null)
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to look in
|
||||
* @param {string} reqID - The ID of the request to look for
|
||||
*
|
||||
* @returns REFERENCE to the request or null if request not found
|
||||
*/
|
||||
function findReqInTree(
|
||||
tree: TeamCollection[],
|
||||
reqID: string
|
||||
): TeamRequest | null {
|
||||
for (const coll of tree) {
|
||||
// Check in root collections (if expanded)
|
||||
if (coll.requests) {
|
||||
const match = coll.requests.find((req) => req.id === reqID)
|
||||
if (match) return match
|
||||
}
|
||||
|
||||
// Check in children of collections
|
||||
if (coll.children) {
|
||||
const match = findReqInTree(coll.children, reqID)
|
||||
if (match) return match
|
||||
}
|
||||
}
|
||||
|
||||
// No matches
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a collection in the tree with the specified data
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to update in (THIS WILL BE MUTATED!)
|
||||
* @param {Partial<TeamCollection> & Pick<TeamCollection, "id">} updateColl - An object defining all the fields that should be updated (ID is required to find the target collection)
|
||||
*/
|
||||
function updateCollInTree(
|
||||
tree: TeamCollection[],
|
||||
updateColl: Partial<TeamCollection> & Pick<TeamCollection, "id">
|
||||
) {
|
||||
const el = findCollInTree(tree, updateColl.id)
|
||||
|
||||
// If no match, stop the operation
|
||||
if (!el) return
|
||||
|
||||
// Update all the specified keys
|
||||
Object.assign(el, updateColl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a collection in the tree
|
||||
*
|
||||
* @param {TeamCollection[]} tree - The tree to delete in (THIS WILL BE MUTATED!)
|
||||
* @param {string} targetID - ID of the collection to delete
|
||||
*/
|
||||
function deleteCollInTree(tree: TeamCollection[], targetID: string) {
|
||||
// Get the parent owning the collection
|
||||
const parent = findParentOfColl(tree, targetID)
|
||||
|
||||
// If we found a parent, update it
|
||||
if (parent && parent.children) {
|
||||
parent.children = parent.children.filter((coll) => coll.id !== targetID)
|
||||
}
|
||||
|
||||
// If there is no parent, it could mean:
|
||||
// 1. The collection with that ID does not exist
|
||||
// 2. The collection is in root (therefore, no parent)
|
||||
|
||||
// Let's look for element, if not exist, then stop
|
||||
const el = findCollInTree(tree, targetID)
|
||||
if (!el) return
|
||||
|
||||
// Collection exists, so this should be in root, hence removing element
|
||||
pull(tree, el)
|
||||
}
|
||||
|
||||
/**
|
||||
* TeamCollectionAdapter provides a reactive collections list for a specific team
|
||||
*/
|
||||
export default class TeamCollectionAdapter {
|
||||
/**
|
||||
* The reactive list of collections
|
||||
*
|
||||
* A new value is emitted when there is a change
|
||||
* (Use views instead)
|
||||
*/
|
||||
collections$: BehaviorSubject<TeamCollection[]>
|
||||
|
||||
// Fields for subscriptions, used for destroying once not needed
|
||||
private teamCollectionAdded$: ZenObservable.Subscription | null
|
||||
private teamCollectionUpdated$: ZenObservable.Subscription | null
|
||||
private teamCollectionRemoved$: ZenObservable.Subscription | null
|
||||
private teamRequestAdded$: ZenObservable.Subscription | null
|
||||
private teamRequestUpdated$: ZenObservable.Subscription | null
|
||||
private teamRequestDeleted$: ZenObservable.Subscription | null
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {string | null} teamID - ID of the team to listen to, or null if none decided and the adapter should stand by
|
||||
*/
|
||||
constructor(private teamID: string | null) {
|
||||
this.collections$ = new BehaviorSubject<TeamCollection[]>([])
|
||||
this.teamCollectionAdded$ = null
|
||||
this.teamCollectionUpdated$ = null
|
||||
this.teamCollectionRemoved$ = null
|
||||
this.teamRequestAdded$ = null
|
||||
this.teamRequestDeleted$ = null
|
||||
this.teamRequestUpdated$ = null
|
||||
|
||||
if (this.teamID) this.initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the team the adapter is looking at
|
||||
*
|
||||
* @param {string | null} newTeamID - ID of the team to listen to, or null if none decided and the adapter should stand by
|
||||
*/
|
||||
changeTeamID(newTeamID: string | null) {
|
||||
this.collections$.next([])
|
||||
|
||||
this.teamID = newTeamID
|
||||
|
||||
if (this.teamID) this.initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from the subscriptions
|
||||
* NOTE: Once this is called, no new updates to the tree will be detected
|
||||
*/
|
||||
unsubscribeSubscriptions() {
|
||||
this.teamCollectionAdded$?.unsubscribe()
|
||||
this.teamCollectionUpdated$?.unsubscribe()
|
||||
this.teamCollectionRemoved$?.unsubscribe()
|
||||
this.teamRequestAdded$?.unsubscribe()
|
||||
this.teamRequestDeleted$?.unsubscribe()
|
||||
this.teamRequestUpdated$?.unsubscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the adapter
|
||||
*/
|
||||
private async initialize() {
|
||||
await this.loadRootCollections()
|
||||
this.registerSubscriptions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the root collections
|
||||
*/
|
||||
private async loadRootCollections(): Promise<void> {
|
||||
const colls = await rootCollectionsOfTeam(apolloClient, this.teamID)
|
||||
this.collections$.next(colls)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs addition of a collection to the tree
|
||||
*
|
||||
* @param {TeamCollection} collection - The collection to add to the tree
|
||||
* @param {string | null} parentCollectionID - The parent of the new collection, pass null if this collection is in root
|
||||
*/
|
||||
private addCollection(
|
||||
collection: TeamCollection,
|
||||
parentCollectionID: string | null
|
||||
) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
if (!parentCollectionID) {
|
||||
tree.push(collection)
|
||||
} else {
|
||||
const parentCollection = findCollInTree(tree, parentCollectionID)
|
||||
|
||||
if (!parentCollection) return
|
||||
|
||||
if (parentCollection.children != null) {
|
||||
parentCollection.children.push(collection)
|
||||
} else {
|
||||
parentCollection.children = [collection]
|
||||
}
|
||||
}
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing collection in tree
|
||||
*
|
||||
* @param {Partial<TeamCollection> & Pick<TeamCollection, "id">} collectionUpdate - Object defining the fields that need to be updated (ID is required to find the target)
|
||||
*/
|
||||
private updateCollection(
|
||||
collectionUpdate: Partial<TeamCollection> & Pick<TeamCollection, "id">
|
||||
) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
updateCollInTree(tree, collectionUpdate)
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a collection from the tree
|
||||
*
|
||||
* @param {string} collectionID - ID of the collection to remove
|
||||
*/
|
||||
private removeCollection(collectionID: string) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
deleteCollInTree(tree, collectionID)
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a request to the tree
|
||||
*
|
||||
* @param {TeamRequest} request - The request to add to the tree
|
||||
*/
|
||||
private addRequest(request: TeamRequest) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
// Check if we have the collection (if not, then not loaded?)
|
||||
const coll = findCollInTree(tree, request.collectionID)
|
||||
if (!coll) return // Ignore add request
|
||||
|
||||
// Collection is not expanded
|
||||
if (!coll.requests) return
|
||||
|
||||
// Collection is expanded hence append request
|
||||
coll.requests.push(request)
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a request from the tree
|
||||
*
|
||||
* @param {string} requestID - ID of the request to remove
|
||||
*/
|
||||
private removeRequest(requestID: string) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
// Find request in tree, don't attempt if no collection or no requests (expansion?)
|
||||
const coll = findCollWithReqIDInTree(tree, requestID)
|
||||
if (!coll || !coll.requests) return
|
||||
|
||||
// Remove the collection
|
||||
remove(coll.requests, (req) => req.id === requestID)
|
||||
|
||||
// Publish new tree
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the request in tree
|
||||
*
|
||||
* @param {Partial<TeamRequest> & Pick<TeamRequest, 'id'>} requestUpdate - Object defining all the fields to update in request (ID of the request is required)
|
||||
*/
|
||||
private updateRequest(
|
||||
requestUpdate: Partial<TeamRequest> & Pick<TeamRequest, "id">
|
||||
) {
|
||||
const tree = this.collections$.value
|
||||
|
||||
// Find request, if not present, don't update
|
||||
const req = findReqInTree(tree, requestUpdate.id)
|
||||
if (!req) return
|
||||
|
||||
Object.assign(req, requestUpdate)
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the subscriptions to listen to team collection updates
|
||||
*/
|
||||
registerSubscriptions() {
|
||||
this.teamCollectionAdded$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamCollectionAdded($teamID: String!) {
|
||||
teamCollectionAdded(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
parent {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.addCollection(
|
||||
{
|
||||
id: data.teamCollectionAdded.id,
|
||||
children: null,
|
||||
requests: null,
|
||||
title: data.teamCollectionAdded.title,
|
||||
},
|
||||
data.teamCollectionAdded.parent?.id
|
||||
)
|
||||
})
|
||||
|
||||
this.teamCollectionUpdated$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamCollectionUpdated($teamID: String!) {
|
||||
teamCollectionUpdated(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
parent {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.updateCollection({
|
||||
id: data.teamCollectionUpdated.id,
|
||||
title: data.teamCollectionUpdated.title,
|
||||
})
|
||||
})
|
||||
|
||||
this.teamCollectionRemoved$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamCollectionRemoved($teamID: String!) {
|
||||
teamCollectionRemoved(teamID: $teamID)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.removeCollection(data.teamCollectionRemoved)
|
||||
})
|
||||
|
||||
this.teamRequestAdded$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamRequestAdded($teamID: String!) {
|
||||
teamRequestAdded(teamID: $teamID) {
|
||||
id
|
||||
collectionID
|
||||
request
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.addRequest({
|
||||
id: data.teamRequestAdded.id,
|
||||
collectionID: data.teamRequestAdded.collectionID,
|
||||
request: translateToNewRequest(
|
||||
JSON.parse(data.teamRequestAdded.request)
|
||||
),
|
||||
title: data.teamRequestAdded.title,
|
||||
})
|
||||
})
|
||||
|
||||
this.teamRequestUpdated$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamRequestUpdated($teamID: String!) {
|
||||
teamRequestUpdated(teamID: $teamID) {
|
||||
id
|
||||
collectionID
|
||||
request
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.updateRequest({
|
||||
id: data.teamRequestUpdated.id,
|
||||
collectionID: data.teamRequestUpdated.collectionID,
|
||||
request: JSON.parse(data.teamRequestUpdated.request),
|
||||
title: data.teamRequestUpdated.title,
|
||||
})
|
||||
})
|
||||
|
||||
this.teamRequestDeleted$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamRequestDeleted($teamID: String!) {
|
||||
teamRequestDeleted(teamID: $teamID)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.removeRequest(data.teamRequestDeleted)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a collection on the tree
|
||||
*
|
||||
* When a collection is loaded initially in the adapter, children and requests are not loaded (they will be set to null)
|
||||
* Upon expansion those two fields will be populated
|
||||
*
|
||||
* @param {string} collectionID - The ID of the collection to expand
|
||||
*/
|
||||
async expandCollection(collectionID: string): Promise<void> {
|
||||
// TODO: While expanding one collection, block (or queue) the expansion of the other, to avoid race conditions
|
||||
const tree = this.collections$.value
|
||||
|
||||
const collection = findCollInTree(tree, collectionID)
|
||||
|
||||
if (!collection) return
|
||||
|
||||
if (collection.children != null) return
|
||||
|
||||
const collections: TeamCollection[] = (
|
||||
await getCollectionChildren(apolloClient, collectionID)
|
||||
).map<TeamCollection>((el) => {
|
||||
return {
|
||||
id: el.id,
|
||||
title: el.title,
|
||||
children: null,
|
||||
requests: null,
|
||||
}
|
||||
})
|
||||
|
||||
const requests: TeamRequest[] = (
|
||||
await getCollectionRequests(apolloClient, collectionID)
|
||||
).map<TeamRequest>((el) => {
|
||||
return {
|
||||
id: el.id,
|
||||
collectionID,
|
||||
title: el.title,
|
||||
request: translateToNewRequest(JSON.parse(el.request)),
|
||||
}
|
||||
})
|
||||
|
||||
collection.children = collections
|
||||
collection.requests = requests
|
||||
|
||||
this.collections$.next(tree)
|
||||
}
|
||||
}
|
||||
160
packages/hoppscotch-app/helpers/teams/TeamMemberAdapter.ts
Normal file
160
packages/hoppscotch-app/helpers/teams/TeamMemberAdapter.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import gql from "graphql-tag"
|
||||
import cloneDeep from "lodash/cloneDeep"
|
||||
import * as Apollo from "@apollo/client/core"
|
||||
import { apolloClient } from "~/helpers/apollo"
|
||||
|
||||
interface TeamsTeamMember {
|
||||
membershipID: string
|
||||
user: {
|
||||
uid: string
|
||||
email: string
|
||||
}
|
||||
role: "OWNER" | "EDITOR" | "VIEWER"
|
||||
}
|
||||
|
||||
export default class TeamMemberAdapter {
|
||||
members$: BehaviorSubject<TeamsTeamMember[]>
|
||||
|
||||
private teamMemberAdded$: ZenObservable.Subscription | null
|
||||
private teamMemberRemoved$: ZenObservable.Subscription | null
|
||||
private teamMemberUpdated$: ZenObservable.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> {
|
||||
const result: TeamsTeamMember[] = []
|
||||
|
||||
let cursor: string | null = null
|
||||
while (true) {
|
||||
const response: Apollo.ApolloQueryResult<any> = await apolloClient.query({
|
||||
query: gql`
|
||||
query GetTeamMembers($teamID: String!, $cursor: String) {
|
||||
team(teamID: $teamID) {
|
||||
members(cursor: $cursor) {
|
||||
membershipID
|
||||
user {
|
||||
uid
|
||||
email
|
||||
}
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
cursor,
|
||||
},
|
||||
})
|
||||
|
||||
result.push(...response.data.team.members)
|
||||
|
||||
if ((response.data.team.members as any[]).length === 0) break
|
||||
else {
|
||||
cursor =
|
||||
response.data.team.members[response.data.team.members.length - 1]
|
||||
.membershipID
|
||||
}
|
||||
}
|
||||
|
||||
this.members$.next(result)
|
||||
}
|
||||
|
||||
private registerSubscriptions() {
|
||||
this.teamMemberAdded$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamMemberAdded($teamID: String!) {
|
||||
teamMemberAdded(teamID: $teamID) {
|
||||
user {
|
||||
uid
|
||||
email
|
||||
}
|
||||
role
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.members$.next([...this.members$.value, data.teamMemberAdded])
|
||||
})
|
||||
|
||||
this.teamMemberRemoved$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamMemberRemoved($teamID: String!) {
|
||||
teamMemberRemoved(teamID: $teamID)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
this.members$.next(
|
||||
this.members$.value.filter(
|
||||
(el) => el.user.uid !== data.teamMemberRemoved
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
this.teamMemberUpdated$ = apolloClient
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamMemberUpdated($teamID: String!) {
|
||||
teamMemberUpdated(teamID: $teamID) {
|
||||
user {
|
||||
uid
|
||||
email
|
||||
}
|
||||
role
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID: this.teamID,
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
const list = cloneDeep(this.members$.value)
|
||||
const obj = list.find(
|
||||
(el) => el.user.uid === data.teamMemberUpdated.user.uid
|
||||
)
|
||||
|
||||
if (!obj) return
|
||||
|
||||
Object.assign(obj, data.teamMemberUpdated)
|
||||
})
|
||||
}
|
||||
}
|
||||
11
packages/hoppscotch-app/helpers/teams/TeamRequest.ts
Normal file
11
packages/hoppscotch-app/helpers/teams/TeamRequest.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { HoppRESTRequest } from "../types/HoppRESTRequest"
|
||||
|
||||
/**
|
||||
* Defines how a Teams request is represented in TeamCollectionAdapter
|
||||
*/
|
||||
export interface TeamRequest {
|
||||
id: string
|
||||
collectionID: string
|
||||
title: string
|
||||
request: HoppRESTRequest
|
||||
}
|
||||
576
packages/hoppscotch-app/helpers/teams/utils.js
Normal file
576
packages/hoppscotch-app/helpers/teams/utils.js
Normal file
@@ -0,0 +1,576 @@
|
||||
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: String!) {
|
||||
team(teamID: $teamID) {
|
||||
members {
|
||||
user {
|
||||
uid
|
||||
email
|
||||
}
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID,
|
||||
},
|
||||
})
|
||||
|
||||
subject.next(data.team.members)
|
||||
|
||||
const addedSub = apollo
|
||||
.subscribe({
|
||||
query: gql`
|
||||
subscription TeamMemberAdded($teamID: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!
|
||||
) {
|
||||
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: String!
|
||||
$teamID: String!
|
||||
) {
|
||||
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: String!) {
|
||||
renameTeam(newName: $newName, teamID: $teamID) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
newName: name,
|
||||
teamID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeTeamMember(apollo, userID, teamID) {
|
||||
return apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation removeTeamMember($userUid: String!, $teamID: String!) {
|
||||
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: String!) {
|
||||
deleteTeam(teamID: $teamID)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID,
|
||||
},
|
||||
})
|
||||
if (response !== undefined) break
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
export function exitTeam(apollo, teamID) {
|
||||
return apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation ($teamID: String!) {
|
||||
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: String!, $cursor: String!) {
|
||||
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: String!) {
|
||||
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: String!, $cursor: String) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!
|
||||
) {
|
||||
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: String!
|
||||
) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
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: String!) {
|
||||
exportCollectionsToJSON(teamID: $teamID)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamID,
|
||||
},
|
||||
})
|
||||
return response.data.exportCollectionsToJSON
|
||||
}
|
||||
Reference in New Issue
Block a user