manifold/web/lib/firebase/groups.ts

251 lines
7.0 KiB
TypeScript
Raw Normal View History

import {
collection,
collectionGroup,
deleteDoc,
deleteField,
doc,
getDocs,
onSnapshot,
query,
setDoc,
updateDoc,
where,
} from 'firebase/firestore'
2022-09-06 15:24:26 +00:00
import { uniq, uniqBy } from 'lodash'
import { Group, GROUP_CHAT_SLUG, GroupLink } from 'common/group'
import {
coll,
getValue,
getValues,
listenForValue,
listenForValues,
} from './utils'
import { Contract } from 'common/contract'
2022-09-06 15:24:26 +00:00
import { getContractFromId, updateContract } from 'web/lib/firebase/contracts'
import { db } from 'web/lib/firebase/init'
import { filterDefined } from 'common/util/array'
import { getUser } from 'web/lib/firebase/users'
export const groups = coll<Group>('groups')
export const groupMembers = (groupId: string) =>
collection(groups, groupId, 'groupMembers')
export const groupContracts = (groupId: string) =>
collection(groups, groupId, 'groupContracts')
2022-09-06 15:24:26 +00:00
const openGroupsQuery = query(groups, where('anyoneCanJoin', '==', true))
const memberGroupsQuery = (userId: string) =>
query(collectionGroup(db, 'groupMembers'), where('userId', '==', userId))
export function groupPath(
groupSlug: string,
2022-07-20 19:35:04 +00:00
subpath?:
| 'edit'
| 'markets'
2022-07-20 19:35:04 +00:00
| 'about'
| typeof GROUP_CHAT_SLUG
| 'leaderboards'
) {
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
}
export type GroupContractDoc = { contractId: string; createdTime: number }
export type GroupMemberDoc = { userId: string; createdTime: number }
export function updateGroup(group: Group, updates: Partial<Group>) {
return updateDoc(doc(groups, group.id), updates)
}
export function deleteFieldFromGroup(group: Group, field: string) {
return updateDoc(doc(groups, group.id), { [field]: deleteField() })
}
export function deleteGroup(group: Group) {
return deleteDoc(doc(groups, group.id))
}
export async function listAllGroups() {
return getValues<Group>(groups)
}
export async function listGroups(groupSlugs: string[]) {
return Promise.all(groupSlugs.map(getGroupBySlug))
}
export function listenForGroups(setGroups: (groups: Group[]) => void) {
return listenForValues(groups, setGroups)
}
export function listenForGroupContractDocs(
groupId: string,
setContractDocs: (docs: GroupContractDoc[]) => void
) {
return listenForValues(groupContracts(groupId), setContractDocs)
}
2022-09-06 15:24:26 +00:00
export async function listGroupContracts(groupId: string) {
const contractDocs = await getValues<{
contractId: string
createdTime: number
}>(groupContracts(groupId))
2022-09-07 13:13:34 +00:00
const contracts = await Promise.all(
2022-09-06 15:24:26 +00:00
contractDocs.map((doc) => getContractFromId(doc.contractId))
)
2022-09-07 13:13:34 +00:00
return filterDefined(contracts)
}
2022-09-06 15:24:26 +00:00
export function listenForOpenGroups(setGroups: (groups: Group[]) => void) {
return listenForValues(openGroupsQuery, setGroups)
}
2022-09-06 15:24:26 +00:00
export function getGroup(groupId: string) {
return getValue<Group>(doc(groups, groupId))
}
export async function getGroupBySlug(slug: string) {
const q = query(groups, where('slug', '==', slug))
const docs = (await getDocs(q)).docs
return docs.length === 0 ? null : docs[0].data()
}
export function listenForGroup(
groupId: string,
setGroup: (group: Group | null) => void
) {
return listenForValue(doc(groups, groupId), setGroup)
}
export function listenForMemberGroupIds(
userId: string,
setGroupIds: (groupIds: string[]) => void
) {
2022-09-06 15:24:26 +00:00
const q = memberGroupsQuery(userId)
return onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
if (snapshot.metadata.fromCache) return
const values = snapshot.docs.map((doc) => doc.ref.parent.parent?.id)
setGroupIds(filterDefined(values))
})
}
export function listenForMemberGroups(
userId: string,
setGroups: (groups: Group[]) => void
) {
return listenForMemberGroupIds(userId, (groupIds) => {
return Promise.all(groupIds.map(getGroup)).then((groups) => {
setGroups(filterDefined(groups))
})
})
}
2022-09-06 15:24:26 +00:00
export async function listAvailableGroups(userId: string) {
const [openGroups, memberGroupSnapshot] = await Promise.all([
getValues<Group>(openGroupsQuery),
getDocs(memberGroupsQuery(userId)),
])
const memberGroups = filterDefined(
await Promise.all(
memberGroupSnapshot.docs.map((doc) => {
return doc.ref.parent.parent?.id
? getGroup(doc.ref.parent.parent?.id)
: null
})
)
)
return uniqBy([...openGroups, ...memberGroups], (g) => g.id)
}
2022-07-18 16:40:44 +00:00
export async function addUserToGroupViaId(groupId: string, userId: string) {
// get group to get the member ids
2022-07-18 16:40:44 +00:00
const group = await getGroup(groupId)
if (!group) {
2022-07-18 16:40:44 +00:00
console.error(`Group not found: ${groupId}`)
return
}
2022-07-14 17:09:28 +00:00
return await joinGroup(group, userId)
}
2022-07-14 17:09:28 +00:00
export async function joinGroup(group: Group, userId: string): Promise<void> {
// create a new member document in grouoMembers collection
const memberDoc = doc(groupMembers(group.id), userId)
return await setDoc(memberDoc, {
userId,
createdTime: Date.now(),
})
}
2022-07-14 17:09:28 +00:00
export async function leaveGroup(group: Group, userId: string): Promise<void> {
// delete the member document in groupMembers collection
const memberDoc = doc(groupMembers(group.id), userId)
return await deleteDoc(memberDoc)
}
export async function addContractToGroup(
group: Group,
contract: Contract,
userId: string
) {
const newGroupLinks = [
...(contract.groupLinks ?? []),
{
groupId: group.id,
createdTime: Date.now(),
slug: group.slug,
userId,
name: group.name,
} as GroupLink,
]
// It's good to update the contract first, so the on-update-group trigger doesn't re-add them
await updateContract(contract.id, {
groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]),
groupLinks: newGroupLinks,
})
// create new contract document in groupContracts collection
const contractDoc = doc(groupContracts(group.id), contract.id)
await setDoc(contractDoc, {
contractId: contract.id,
createdTime: Date.now(),
})
}
export async function removeContractFromGroup(
group: Group,
contract: Contract
) {
if (contract.groupLinks?.map((l) => l.groupId).includes(group.id)) {
const newGroupLinks = contract.groupLinks?.filter(
(link) => link.slug !== group.slug
)
await updateContract(contract.id, {
groupSlugs:
contract.groupSlugs?.filter((slug) => slug !== group.slug) ?? [],
groupLinks: newGroupLinks ?? [],
})
}
// delete the contract document in groupContracts collection
const contractDoc = doc(groupContracts(group.id), contract.id)
await deleteDoc(contractDoc)
}
export function getGroupLinkToDisplay(contract: Contract) {
const { groupLinks } = contract
const sortedGroupLinks = groupLinks?.sort(
(a, b) => b.createdTime - a.createdTime
)
const groupCreatorAdded = sortedGroupLinks?.find(
(g) => g.userId === contract.creatorId
)
const groupToDisplay = groupCreatorAdded
? groupCreatorAdded
: sortedGroupLinks?.[0] ?? null
return groupToDisplay
}
export async function listMembers(group: Group) {
const members = await getValues<GroupMemberDoc>(groupMembers(group.id))
return await Promise.all(members.map((m) => m.userId).map(getUser))
}