552e6e6c3a
* Show custom feed of contracts from folds your follow or have bet on. * Add tw-elements UI library * Add loading spinner while feed loads * Switch from onSnapshot to our listenForValues, which doesn't set with partial cached values * Change home tags to communities * Remove tw-elements for now
166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
import {
|
|
collection,
|
|
collectionGroup,
|
|
deleteDoc,
|
|
doc,
|
|
getDocs,
|
|
query,
|
|
setDoc,
|
|
updateDoc,
|
|
where,
|
|
} from 'firebase/firestore'
|
|
import _ from 'lodash'
|
|
import { Fold } from '../../../common/fold'
|
|
import { Contract, contractCollection } from './contracts'
|
|
import { db } from './init'
|
|
import { User } from './users'
|
|
import { getValue, getValues, listenForValue, listenForValues } from './utils'
|
|
|
|
const foldCollection = collection(db, 'folds')
|
|
|
|
export function foldPath(
|
|
fold: Fold,
|
|
subpath?: 'edit' | 'markets' | 'leaderboards'
|
|
) {
|
|
return `/fold/${fold.slug}${subpath ? `/${subpath}` : ''}`
|
|
}
|
|
|
|
export function updateFold(fold: Fold, updates: Partial<Fold>) {
|
|
return updateDoc(doc(foldCollection, fold.id), updates)
|
|
}
|
|
|
|
export function deleteFold(fold: Fold) {
|
|
return deleteDoc(doc(foldCollection, fold.id))
|
|
}
|
|
|
|
export async function listAllFolds() {
|
|
return getValues<Fold>(foldCollection)
|
|
}
|
|
|
|
export function listenForFolds(setFolds: (folds: Fold[]) => void) {
|
|
return listenForValues(foldCollection, setFolds)
|
|
}
|
|
|
|
export function getFold(foldId: string) {
|
|
return getValue<Fold>(doc(foldCollection, foldId))
|
|
}
|
|
|
|
export async function getFoldBySlug(slug: string) {
|
|
const q = query(foldCollection, where('slug', '==', slug))
|
|
const folds = await getValues<Fold>(q)
|
|
|
|
return folds.length === 0 ? null : folds[0]
|
|
}
|
|
|
|
function contractsByTagsQuery(tags: string[]) {
|
|
return query(
|
|
contractCollection,
|
|
where(
|
|
'lowercaseTags',
|
|
'array-contains-any',
|
|
tags.map((tag) => tag.toLowerCase())
|
|
)
|
|
)
|
|
}
|
|
|
|
export async function getFoldContracts(fold: Fold) {
|
|
const {
|
|
tags,
|
|
contractIds,
|
|
excludedContractIds,
|
|
creatorIds,
|
|
excludedCreatorIds,
|
|
} = fold
|
|
|
|
const [tagsContracts, includedContracts] = await Promise.all([
|
|
// TODO: if tags.length > 10, execute multiple parallel queries
|
|
tags.length > 0 ? getValues<Contract>(contractsByTagsQuery(tags)) : [],
|
|
|
|
// TODO: if contractIds.length > 10, execute multiple parallel queries
|
|
contractIds.length > 0
|
|
? getValues<Contract>(
|
|
query(contractCollection, where('id', 'in', contractIds))
|
|
)
|
|
: [],
|
|
])
|
|
|
|
const excludedContractsSet = new Set(excludedContractIds)
|
|
|
|
const creatorSet = creatorIds ? new Set(creatorIds) : undefined
|
|
const excludedCreatorSet = excludedCreatorIds
|
|
? new Set(excludedCreatorIds)
|
|
: undefined
|
|
|
|
const approvedContracts = tagsContracts.filter((contract) => {
|
|
const { id, creatorId } = contract
|
|
|
|
if (excludedContractsSet.has(id)) return false
|
|
if (creatorSet && !creatorSet.has(creatorId)) return false
|
|
if (excludedCreatorSet && excludedCreatorSet.has(creatorId)) return false
|
|
|
|
return true
|
|
})
|
|
|
|
return [...approvedContracts, ...includedContracts]
|
|
}
|
|
|
|
export function listenForTaggedContracts(
|
|
tags: string[],
|
|
setContracts: (contracts: Contract[]) => void
|
|
) {
|
|
return listenForValues<Contract>(contractsByTagsQuery(tags), setContracts)
|
|
}
|
|
|
|
export function listenForFold(
|
|
foldId: string,
|
|
setFold: (fold: Fold | null) => void
|
|
) {
|
|
return listenForValue(doc(foldCollection, foldId), setFold)
|
|
}
|
|
|
|
export function followFold(fold: Fold, user: User) {
|
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
|
return setDoc(followDoc, { userId: user.id })
|
|
}
|
|
|
|
export function unfollowFold(fold: Fold, user: User) {
|
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
|
return deleteDoc(followDoc)
|
|
}
|
|
|
|
export function listenForFollow(
|
|
fold: Fold,
|
|
user: User,
|
|
setFollow: (following: boolean) => void
|
|
) {
|
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
|
return listenForValue(followDoc, (value) => {
|
|
setFollow(!!value)
|
|
})
|
|
}
|
|
|
|
export async function getFoldsByTags(tags: string[]) {
|
|
if (tags.length === 0) return []
|
|
|
|
const lowercaseTags = tags.map((tag) => tag.toLowerCase())
|
|
const folds = await getValues<Fold>(
|
|
// TODO: split into multiple queries if tags.length > 10.
|
|
query(
|
|
foldCollection,
|
|
where('lowercaseTags', 'array-contains-any', lowercaseTags)
|
|
)
|
|
)
|
|
|
|
return _.sortBy(folds, (fold) => -1 * fold.followCount)
|
|
}
|
|
|
|
export async function getFollowedFolds(userId: string) {
|
|
const snapshot = await getDocs(
|
|
query(collectionGroup(db, 'followers'), where('userId', '==', userId))
|
|
)
|
|
const foldIds = snapshot.docs.map(
|
|
(doc) => doc.ref.parent.parent?.id as string
|
|
)
|
|
return foldIds
|
|
}
|