Add Firebase schema collection helpers (kind of an RFC) (#583)

* Add Firebase schema collection helpers

* Decentralize definitions from schema file (James feedback)

* Add lint comment
This commit is contained in:
Marshall Polaris 2022-06-29 12:21:40 -07:00 committed by GitHub
parent 8132fa595b
commit 3b4666ba3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 124 additions and 146 deletions

View File

@ -2,16 +2,16 @@ import { useEffect } from 'react'
import { useFirestoreDocumentData } from '@react-query-firebase/firestore' import { useFirestoreDocumentData } from '@react-query-firebase/firestore'
import { import {
Contract, Contract,
contractDocRef, contracts,
listenForContract, listenForContract,
} from 'web/lib/firebase/contracts' } from 'web/lib/firebase/contracts'
import { useStateCheckEquality } from './use-state-check-equality' import { useStateCheckEquality } from './use-state-check-equality'
import { DocumentData } from 'firebase/firestore' import { doc, DocumentData } from 'firebase/firestore'
export const useContract = (contractId: string) => { export const useContract = (contractId: string) => {
const result = useFirestoreDocumentData<DocumentData, Contract>( const result = useFirestoreDocumentData<DocumentData, Contract>(
['contracts', contractId], ['contracts', contractId],
contractDocRef(contractId), doc(contracts, contractId),
{ subscribe: true, includeMetadataChanges: true } { subscribe: true, includeMetadataChanges: true }
) )

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { useFirestoreDocumentData } from '@react-query-firebase/firestore' import { useFirestoreDocumentData } from '@react-query-firebase/firestore'
import { QueryClient } from 'react-query' import { QueryClient } from 'react-query'
import { DocumentData } from 'firebase/firestore' import { doc, DocumentData } from 'firebase/firestore'
import { PrivateUser } from 'common/user' import { PrivateUser } from 'common/user'
import { import {
getUser, getUser,
@ -10,7 +10,7 @@ import {
listenForPrivateUser, listenForPrivateUser,
listenForUser, listenForUser,
User, User,
userDocRef, users,
} from 'web/lib/firebase/users' } from 'web/lib/firebase/users'
import { useStateCheckEquality } from './use-state-check-equality' import { useStateCheckEquality } from './use-state-check-equality'
import { identifyUser, setUserProperty } from 'web/lib/service/analytics' import { identifyUser, setUserProperty } from 'web/lib/service/analytics'
@ -49,7 +49,7 @@ export const usePrivateUser = (userId?: string) => {
export const useUserById = (userId: string) => { export const useUserById = (userId: string) => {
const result = useFirestoreDocumentData<DocumentData, User>( const result = useFirestoreDocumentData<DocumentData, User>(
['users', userId], ['users', userId],
userDocRef(userId), doc(users, userId),
{ subscribe: true, includeMetadataChanges: true } { subscribe: true, includeMetadataChanges: true }
) )

View File

@ -1,6 +1,5 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import {
getFirestore,
doc, doc,
setDoc, setDoc,
deleteDoc, deleteDoc,
@ -16,8 +15,7 @@ import {
} from 'firebase/firestore' } from 'firebase/firestore'
import { sortBy, sum } from 'lodash' import { sortBy, sum } from 'lodash'
import { app } from './init' import { coll, getValues, listenForValue, listenForValues } from './utils'
import { getValues, listenForValue, listenForValues } from './utils'
import { BinaryContract, Contract } from 'common/contract' import { BinaryContract, Contract } from 'common/contract'
import { getDpmProbability } from 'common/calculate-dpm' import { getDpmProbability } from 'common/calculate-dpm'
import { createRNG, shuffle } from 'common/util/random' import { createRNG, shuffle } from 'common/util/random'
@ -28,6 +26,9 @@ import { MAX_FEED_CONTRACTS } from 'common/recommended-contracts'
import { Bet } from 'common/bet' import { Bet } from 'common/bet'
import { Comment } from 'common/comment' import { Comment } from 'common/comment'
import { ENV_CONFIG } from 'common/envs/constants' import { ENV_CONFIG } from 'common/envs/constants'
export const contracts = coll<Contract>('contracts')
export type { Contract } export type { Contract }
export function contractPath(contract: Contract) { export function contractPath(contract: Contract) {
@ -86,83 +87,72 @@ export function tradingAllowed(contract: Contract) {
) )
} }
const db = getFirestore(app)
export const contractCollection = collection(db, 'contracts')
export const contractDocRef = (contractId: string) =>
doc(db, 'contracts', contractId)
// Push contract to Firestore // Push contract to Firestore
export async function setContract(contract: Contract) { export async function setContract(contract: Contract) {
const docRef = doc(db, 'contracts', contract.id) await setDoc(doc(contracts, contract.id), contract)
await setDoc(docRef, contract)
} }
export async function updateContract( export async function updateContract(
contractId: string, contractId: string,
update: Partial<Contract> update: Partial<Contract>
) { ) {
const docRef = doc(db, 'contracts', contractId) await updateDoc(doc(contracts, contractId), update)
await updateDoc(docRef, update)
} }
export async function getContractFromId(contractId: string) { export async function getContractFromId(contractId: string) {
const docRef = doc(db, 'contracts', contractId) const result = await getDoc(doc(contracts, contractId))
const result = await getDoc(docRef) return result.exists() ? result.data() : undefined
return result.exists() ? (result.data() as Contract) : undefined
} }
export async function getContractFromSlug(slug: string) { export async function getContractFromSlug(slug: string) {
const q = query(contractCollection, where('slug', '==', slug)) const q = query(contracts, where('slug', '==', slug))
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
return snapshot.empty ? undefined : snapshot.docs[0].data()
return snapshot.empty ? undefined : (snapshot.docs[0].data() as Contract)
} }
export async function deleteContract(contractId: string) { export async function deleteContract(contractId: string) {
const docRef = doc(db, 'contracts', contractId) await deleteDoc(doc(contracts, contractId))
await deleteDoc(docRef)
} }
export async function listContracts(creatorId: string): Promise<Contract[]> { export async function listContracts(creatorId: string): Promise<Contract[]> {
const q = query( const q = query(
contractCollection, contracts,
where('creatorId', '==', creatorId), where('creatorId', '==', creatorId),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
) )
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
return snapshot.docs.map((doc) => doc.data() as Contract) return snapshot.docs.map((doc) => doc.data())
} }
export async function listTaggedContractsCaseInsensitive( export async function listTaggedContractsCaseInsensitive(
tag: string tag: string
): Promise<Contract[]> { ): Promise<Contract[]> {
const q = query( const q = query(
contractCollection, contracts,
where('lowercaseTags', 'array-contains', tag.toLowerCase()), where('lowercaseTags', 'array-contains', tag.toLowerCase()),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
) )
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
return snapshot.docs.map((doc) => doc.data() as Contract) return snapshot.docs.map((doc) => doc.data())
} }
export async function listAllContracts( export async function listAllContracts(
n: number, n: number,
before?: string before?: string
): Promise<Contract[]> { ): Promise<Contract[]> {
let q = query(contractCollection, orderBy('createdTime', 'desc'), limit(n)) let q = query(contracts, orderBy('createdTime', 'desc'), limit(n))
if (before != null) { if (before != null) {
const snap = await getDoc(doc(db, 'contracts', before)) const snap = await getDoc(doc(contracts, before))
q = query(q, startAfter(snap)) q = query(q, startAfter(snap))
} }
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
return snapshot.docs.map((doc) => doc.data() as Contract) return snapshot.docs.map((doc) => doc.data())
} }
export function listenForContracts( export function listenForContracts(
setContracts: (contracts: Contract[]) => void setContracts: (contracts: Contract[]) => void
) { ) {
const q = query(contractCollection, orderBy('createdTime', 'desc')) const q = query(contracts, orderBy('createdTime', 'desc'))
return listenForValues<Contract>(q, setContracts) return listenForValues<Contract>(q, setContracts)
} }
@ -171,7 +161,7 @@ export function listenForUserContracts(
setContracts: (contracts: Contract[]) => void setContracts: (contracts: Contract[]) => void
) { ) {
const q = query( const q = query(
contractCollection, contracts,
where('creatorId', '==', creatorId), where('creatorId', '==', creatorId),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
) )
@ -179,7 +169,7 @@ export function listenForUserContracts(
} }
const activeContractsQuery = query( const activeContractsQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
where('visibility', '==', 'public'), where('visibility', '==', 'public'),
where('volume7Days', '>', 0) where('volume7Days', '>', 0)
@ -196,7 +186,7 @@ export function listenForActiveContracts(
} }
const inactiveContractsQuery = query( const inactiveContractsQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
where('closeTime', '>', Date.now()), where('closeTime', '>', Date.now()),
where('visibility', '==', 'public'), where('visibility', '==', 'public'),
@ -214,7 +204,7 @@ export function listenForInactiveContracts(
} }
const newContractsQuery = query( const newContractsQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
where('volume7Days', '==', 0), where('volume7Days', '==', 0),
where('createdTime', '>', Date.now() - 7 * DAY_MS) where('createdTime', '>', Date.now() - 7 * DAY_MS)
@ -230,7 +220,7 @@ export function listenForContract(
contractId: string, contractId: string,
setContract: (contract: Contract | null) => void setContract: (contract: Contract | null) => void
) { ) {
const contractRef = doc(contractCollection, contractId) const contractRef = doc(contracts, contractId)
return listenForValue<Contract>(contractRef, setContract) return listenForValue<Contract>(contractRef, setContract)
} }
@ -242,7 +232,7 @@ function chooseRandomSubset(contracts: Contract[], count: number) {
} }
const hotContractsQuery = query( const hotContractsQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
where('visibility', '==', 'public'), where('visibility', '==', 'public'),
orderBy('volume24Hours', 'desc'), orderBy('volume24Hours', 'desc'),
@ -262,22 +252,22 @@ export function listenForHotContracts(
} }
export async function getHotContracts() { export async function getHotContracts() {
const contracts = await getValues<Contract>(hotContractsQuery) const data = await getValues<Contract>(hotContractsQuery)
return sortBy( return sortBy(
chooseRandomSubset(contracts, 10), chooseRandomSubset(data, 10),
(contract) => -1 * contract.volume24Hours (contract) => -1 * contract.volume24Hours
) )
} }
export async function getContractsBySlugs(slugs: string[]) { export async function getContractsBySlugs(slugs: string[]) {
const q = query(contractCollection, where('slug', 'in', slugs)) const q = query(contracts, where('slug', 'in', slugs))
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
const contracts = snapshot.docs.map((doc) => doc.data() as Contract) const data = snapshot.docs.map((doc) => doc.data())
return sortBy(contracts, (contract) => -1 * contract.volume24Hours) return sortBy(data, (contract) => -1 * contract.volume24Hours)
} }
const topWeeklyQuery = query( const topWeeklyQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
orderBy('volume7Days', 'desc'), orderBy('volume7Days', 'desc'),
limit(MAX_FEED_CONTRACTS) limit(MAX_FEED_CONTRACTS)
@ -287,7 +277,7 @@ export async function getTopWeeklyContracts() {
} }
const closingSoonQuery = query( const closingSoonQuery = query(
contractCollection, contracts,
where('isResolved', '==', false), where('isResolved', '==', false),
where('visibility', '==', 'public'), where('visibility', '==', 'public'),
where('closeTime', '>', Date.now()), where('closeTime', '>', Date.now()),
@ -296,15 +286,12 @@ const closingSoonQuery = query(
) )
export async function getClosingSoonContracts() { export async function getClosingSoonContracts() {
const contracts = await getValues<Contract>(closingSoonQuery) const data = await getValues<Contract>(closingSoonQuery)
return sortBy( return sortBy(chooseRandomSubset(data, 2), (contract) => contract.closeTime)
chooseRandomSubset(contracts, 2),
(contract) => contract.closeTime
)
} }
export async function getRecentBetsAndComments(contract: Contract) { export async function getRecentBetsAndComments(contract: Contract) {
const contractDoc = doc(db, 'contracts', contract.id) const contractDoc = doc(contracts, contract.id)
const [recentBets, recentComments] = await Promise.all([ const [recentBets, recentComments] = await Promise.all([
getValues<Bet>( getValues<Bet>(

View File

@ -1,7 +1,7 @@
import { import {
collection,
deleteDoc, deleteDoc,
doc, doc,
getDocs,
query, query,
updateDoc, updateDoc,
where, where,
@ -9,11 +9,16 @@ import {
import { sortBy } from 'lodash' import { sortBy } from 'lodash'
import { Group } from 'common/group' import { Group } from 'common/group'
import { getContractFromId } from './contracts' import { getContractFromId } from './contracts'
import { db } from './init' import {
import { getValue, getValues, listenForValue, listenForValues } from './utils' coll,
getValue,
getValues,
listenForValue,
listenForValues,
} from './utils'
import { filterDefined } from 'common/util/array' import { filterDefined } from 'common/util/array'
const groupCollection = collection(db, 'groups') export const groups = coll<Group>('groups')
export function groupPath( export function groupPath(
groupSlug: string, groupSlug: string,
@ -23,30 +28,29 @@ export function groupPath(
} }
export function updateGroup(group: Group, updates: Partial<Group>) { export function updateGroup(group: Group, updates: Partial<Group>) {
return updateDoc(doc(groupCollection, group.id), updates) return updateDoc(doc(groups, group.id), updates)
} }
export function deleteGroup(group: Group) { export function deleteGroup(group: Group) {
return deleteDoc(doc(groupCollection, group.id)) return deleteDoc(doc(groups, group.id))
} }
export async function listAllGroups() { export async function listAllGroups() {
return getValues<Group>(groupCollection) return getValues<Group>(groups)
} }
export function listenForGroups(setGroups: (groups: Group[]) => void) { export function listenForGroups(setGroups: (groups: Group[]) => void) {
return listenForValues(groupCollection, setGroups) return listenForValues(groups, setGroups)
} }
export function getGroup(groupId: string) { export function getGroup(groupId: string) {
return getValue<Group>(doc(groupCollection, groupId)) return getValue<Group>(doc(groups, groupId))
} }
export async function getGroupBySlug(slug: string) { export async function getGroupBySlug(slug: string) {
const q = query(groupCollection, where('slug', '==', slug)) const q = query(groups, where('slug', '==', slug))
const groups = await getValues<Group>(q) const docs = (await getDocs(q)).docs
return docs.length === 0 ? null : docs[0].data()
return groups.length === 0 ? null : groups[0]
} }
export async function getGroupContracts(group: Group) { export async function getGroupContracts(group: Group) {
@ -68,14 +72,14 @@ export function listenForGroup(
groupId: string, groupId: string,
setGroup: (group: Group | null) => void setGroup: (group: Group | null) => void
) { ) {
return listenForValue(doc(groupCollection, groupId), setGroup) return listenForValue(doc(groups, groupId), setGroup)
} }
export function listenForMemberGroups( export function listenForMemberGroups(
userId: string, userId: string,
setGroups: (groups: Group[]) => void setGroups: (groups: Group[]) => void
) { ) {
const q = query(groupCollection, where('memberIds', 'array-contains', userId)) const q = query(groups, where('memberIds', 'array-contains', userId))
return listenForValues<Group>(q, (groups) => { return listenForValues<Group>(q, (groups) => {
const sorted = sortBy(groups, [(group) => -group.mostRecentActivityTime]) const sorted = sortBy(groups, [(group) => -group.mostRecentActivityTime])
@ -87,12 +91,8 @@ export async function getGroupsWithContractId(
contractId: string, contractId: string,
setGroups: (groups: Group[]) => void setGroups: (groups: Group[]) => void
) { ) {
const q = query( const q = query(groups, where('contractIds', 'array-contains', contractId))
groupCollection, setGroups(await getValues<Group>(q))
where('contractIds', 'array-contains', contractId)
)
const groups = await getValues<Group>(q)
setGroups(groups)
} }
export async function joinGroup(group: Group, userId: string): Promise<Group> { export async function joinGroup(group: Group, userId: string): Promise<Group> {

View File

@ -1,18 +1,12 @@
import { import { getDoc, orderBy, query, setDoc, where } from 'firebase/firestore'
collection,
getDoc,
orderBy,
query,
setDoc,
where,
} from 'firebase/firestore'
import { doc } from 'firebase/firestore' import { doc } from 'firebase/firestore'
import { Manalink } from '../../../common/manalink' import { Manalink } from '../../../common/manalink'
import { db } from './init'
import { customAlphabet } from 'nanoid' import { customAlphabet } from 'nanoid'
import { listenForValues } from './utils' import { coll, listenForValues } from './utils'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
export const manalinks = coll<Manalink>('manalinks')
export async function createManalink(data: { export async function createManalink(data: {
fromId: string fromId: string
amount: number amount: number
@ -45,29 +39,25 @@ export async function createManalink(data: {
message, message,
} }
const ref = doc(db, 'manalinks', slug) await setDoc(doc(manalinks, slug), manalink)
await setDoc(ref, manalink)
return slug return slug
} }
const manalinkCol = collection(db, 'manalinks')
// TODO: This required an index, make sure to also set up in prod // TODO: This required an index, make sure to also set up in prod
function listUserManalinks(fromId?: string) { function listUserManalinks(fromId?: string) {
return query( return query(
manalinkCol, manalinks,
where('fromId', '==', fromId), where('fromId', '==', fromId),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
) )
} }
export async function getManalink(slug: string) { export async function getManalink(slug: string) {
const docSnap = await getDoc(doc(db, 'manalinks', slug)) return (await getDoc(doc(manalinks, slug))).data()
return docSnap.data() as Manalink
} }
export function useManalink(slug: string) { export function useManalink(slug: string) {
const [manalink, setManalink] = useState<Manalink | null>(null) const [manalink, setManalink] = useState<Manalink | undefined>(undefined)
useEffect(() => { useEffect(() => {
if (slug) { if (slug) {
getManalink(slug).then(setManalink) getManalink(slug).then(setManalink)

View File

@ -1,15 +1,14 @@
import { ManalinkTxn, DonationTxn, TipTxn } from 'common/txn' import { ManalinkTxn, DonationTxn, TipTxn, Txn } from 'common/txn'
import { collection, orderBy, query, where } from 'firebase/firestore' import { orderBy, query, where } from 'firebase/firestore'
import { db } from './init' import { coll, getValues, listenForValues } from './utils'
import { getValues, listenForValues } from './utils'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { orderBy as _orderBy } from 'lodash' import { orderBy as _orderBy } from 'lodash'
const txnCollection = collection(db, 'txns') export const txns = coll<Txn>('txns')
const getCharityQuery = (charityId: string) => const getCharityQuery = (charityId: string) =>
query( query(
txnCollection, txns,
where('toType', '==', 'CHARITY'), where('toType', '==', 'CHARITY'),
where('toId', '==', charityId), where('toId', '==', charityId),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
@ -22,7 +21,7 @@ export function listenForCharityTxns(
return listenForValues<DonationTxn>(getCharityQuery(charityId), setTxns) return listenForValues<DonationTxn>(getCharityQuery(charityId), setTxns)
} }
const charitiesQuery = query(txnCollection, where('toType', '==', 'CHARITY')) const charitiesQuery = query(txns, where('toType', '==', 'CHARITY'))
export function getAllCharityTxns() { export function getAllCharityTxns() {
return getValues<DonationTxn>(charitiesQuery) return getValues<DonationTxn>(charitiesQuery)
@ -30,7 +29,7 @@ export function getAllCharityTxns() {
const getTipsQuery = (contractId: string) => const getTipsQuery = (contractId: string) =>
query( query(
txnCollection, txns,
where('category', '==', 'TIP'), where('category', '==', 'TIP'),
where('data.contractId', '==', contractId) where('data.contractId', '==', contractId)
) )
@ -50,13 +49,13 @@ export function useManalinkTxns(userId: string) {
useEffect(() => { useEffect(() => {
// TODO: Need to instantiate these indexes too // TODO: Need to instantiate these indexes too
const fromQuery = query( const fromQuery = query(
txnCollection, txns,
where('fromId', '==', userId), where('fromId', '==', userId),
where('category', '==', 'MANALINK'), where('category', '==', 'MANALINK'),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')
) )
const toQuery = query( const toQuery = query(
txnCollection, txns,
where('toId', '==', userId), where('toId', '==', userId),
where('category', '==', 'MANALINK'), where('category', '==', 'MANALINK'),
orderBy('createdTime', 'desc') orderBy('createdTime', 'desc')

View File

@ -1,5 +1,4 @@
import { import {
getFirestore,
doc, doc,
setDoc, setDoc,
getDoc, getDoc,
@ -23,58 +22,62 @@ import {
} from 'firebase/auth' } from 'firebase/auth'
import { throttle, zip } from 'lodash' import { throttle, zip } from 'lodash'
import { app } from './init' import { app, db } from './init'
import { PortfolioMetrics, PrivateUser, User } from 'common/user' import { PortfolioMetrics, PrivateUser, User } from 'common/user'
import { createUser } from './fn-call' import { createUser } from './fn-call'
import { getValue, getValues, listenForValue, listenForValues } from './utils' import {
coll,
getValue,
getValues,
listenForValue,
listenForValues,
} from './utils'
import { feed } from 'common/feed' import { feed } from 'common/feed'
import { CATEGORY_LIST } from 'common/categories' import { CATEGORY_LIST } from 'common/categories'
import { safeLocalStorage } from '../util/local' import { safeLocalStorage } from '../util/local'
import { filterDefined } from 'common/util/array' import { filterDefined } from 'common/util/array'
export const users = coll<User>('users')
export const privateUsers = coll<PrivateUser>('private-users')
export type { User } export type { User }
export type Period = 'daily' | 'weekly' | 'monthly' | 'allTime' export type Period = 'daily' | 'weekly' | 'monthly' | 'allTime'
const db = getFirestore(app)
export const auth = getAuth(app) export const auth = getAuth(app)
export const userDocRef = (userId: string) => doc(db, 'users', userId)
export async function getUser(userId: string) { export async function getUser(userId: string) {
const docSnap = await getDoc(userDocRef(userId)) /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
return docSnap.data() as User return (await getDoc(doc(users, userId))).data()!
} }
export async function getUserByUsername(username: string) { export async function getUserByUsername(username: string) {
// Find a user whose username matches the given username, or null if no such user exists. // Find a user whose username matches the given username, or null if no such user exists.
const userCollection = collection(db, 'users') const q = query(users, where('username', '==', username), limit(1))
const q = query(userCollection, where('username', '==', username), limit(1)) const docs = (await getDocs(q)).docs
const docs = await getDocs(q) return docs.length > 0 ? docs[0].data() : null
const users = docs.docs.map((doc) => doc.data() as User)
return users[0] || null
} }
export async function setUser(userId: string, user: User) { export async function setUser(userId: string, user: User) {
await setDoc(doc(db, 'users', userId), user) await setDoc(doc(users, userId), user)
} }
export async function updateUser(userId: string, update: Partial<User>) { export async function updateUser(userId: string, update: Partial<User>) {
await updateDoc(doc(db, 'users', userId), { ...update }) await updateDoc(doc(users, userId), { ...update })
} }
export async function updatePrivateUser( export async function updatePrivateUser(
userId: string, userId: string,
update: Partial<PrivateUser> update: Partial<PrivateUser>
) { ) {
await updateDoc(doc(db, 'private-users', userId), { ...update }) await updateDoc(doc(privateUsers, userId), { ...update })
} }
export function listenForUser( export function listenForUser(
userId: string, userId: string,
setUser: (user: User | null) => void setUser: (user: User | null) => void
) { ) {
const userRef = doc(db, 'users', userId) const userRef = doc(users, userId)
return listenForValue<User>(userRef, setUser) return listenForValue<User>(userRef, setUser)
} }
@ -82,7 +85,7 @@ export function listenForPrivateUser(
userId: string, userId: string,
setPrivateUser: (privateUser: PrivateUser | null) => void setPrivateUser: (privateUser: PrivateUser | null) => void
) { ) {
const userRef = doc(db, 'private-users', userId) const userRef = doc(privateUsers, userId)
return listenForValue<PrivateUser>(userRef, setPrivateUser) return listenForValue<PrivateUser>(userRef, setPrivateUser)
} }
@ -152,36 +155,29 @@ export async function listUsers(userIds: string[]) {
if (userIds.length > 10) { if (userIds.length > 10) {
throw new Error('Too many users requested at once; Firestore limits to 10') throw new Error('Too many users requested at once; Firestore limits to 10')
} }
const userCollection = collection(db, 'users') const q = query(users, where('id', 'in', userIds))
const q = query(userCollection, where('id', 'in', userIds)) const docs = (await getDocs(q)).docs
const docs = await getDocs(q) return docs.map((doc) => doc.data())
return docs.docs.map((doc) => doc.data() as User)
} }
export async function listAllUsers() { export async function listAllUsers() {
const userCollection = collection(db, 'users') const docs = (await getDocs(users)).docs
const q = query(userCollection) return docs.map((doc) => doc.data())
const docs = await getDocs(q)
return docs.docs.map((doc) => doc.data() as User)
} }
export function listenForAllUsers(setUsers: (users: User[]) => void) { export function listenForAllUsers(setUsers: (users: User[]) => void) {
const userCollection = collection(db, 'users') listenForValues(users, setUsers)
const q = query(userCollection)
listenForValues(q, setUsers)
} }
export function listenForPrivateUsers( export function listenForPrivateUsers(
setUsers: (users: PrivateUser[]) => void setUsers: (users: PrivateUser[]) => void
) { ) {
const userCollection = collection(db, 'private-users') listenForValues(privateUsers, setUsers)
const q = query(userCollection)
listenForValues(q, setUsers)
} }
export function getTopTraders(period: Period) { export function getTopTraders(period: Period) {
const topTraders = query( const topTraders = query(
collection(db, 'users'), users,
orderBy('profitCached.' + period, 'desc'), orderBy('profitCached.' + period, 'desc'),
limit(20) limit(20)
) )
@ -191,7 +187,7 @@ export function getTopTraders(period: Period) {
export function getTopCreators(period: Period) { export function getTopCreators(period: Period) {
const topCreators = query( const topCreators = query(
collection(db, 'users'), users,
orderBy('creatorVolumeCached.' + period, 'desc'), orderBy('creatorVolumeCached.' + period, 'desc'),
limit(20) limit(20)
) )
@ -199,22 +195,21 @@ export function getTopCreators(period: Period) {
} }
export async function getTopFollowed() { export async function getTopFollowed() {
const users = await getValues<User>(topFollowedQuery) return (await getValues<User>(topFollowedQuery)).slice(0, 20)
return users.slice(0, 20)
} }
const topFollowedQuery = query( const topFollowedQuery = query(
collection(db, 'users'), users,
orderBy('followerCountCached', 'desc'), orderBy('followerCountCached', 'desc'),
limit(20) limit(20)
) )
export function getUsers() { export function getUsers() {
return getValues<User>(collection(db, 'users')) return getValues<User>(users)
} }
export async function getUserFeed(userId: string) { export async function getUserFeed(userId: string) {
const feedDoc = doc(db, 'private-users', userId, 'cache', 'feed') const feedDoc = doc(privateUsers, userId, 'cache', 'feed')
const userFeed = await getValue<{ const userFeed = await getValue<{
feed: feed feed: feed
}>(feedDoc) }>(feedDoc)
@ -222,7 +217,7 @@ export async function getUserFeed(userId: string) {
} }
export async function getCategoryFeeds(userId: string) { export async function getCategoryFeeds(userId: string) {
const cacheCollection = collection(db, 'private-users', userId, 'cache') const cacheCollection = collection(privateUsers, userId, 'cache')
const feedData = await Promise.all( const feedData = await Promise.all(
CATEGORY_LIST.map((category) => CATEGORY_LIST.map((category) =>
getValue<{ feed: feed }>(doc(cacheCollection, `feed-${category}`)) getValue<{ feed: feed }>(doc(cacheCollection, `feed-${category}`))
@ -233,7 +228,7 @@ export async function getCategoryFeeds(userId: string) {
} }
export async function follow(userId: string, followedUserId: string) { export async function follow(userId: string, followedUserId: string) {
const followDoc = doc(db, 'users', userId, 'follows', followedUserId) const followDoc = doc(collection(users, userId, 'follows'), followedUserId)
await setDoc(followDoc, { await setDoc(followDoc, {
userId: followedUserId, userId: followedUserId,
timestamp: Date.now(), timestamp: Date.now(),
@ -241,7 +236,7 @@ export async function follow(userId: string, followedUserId: string) {
} }
export async function unfollow(userId: string, unfollowedUserId: string) { export async function unfollow(userId: string, unfollowedUserId: string) {
const followDoc = doc(db, 'users', userId, 'follows', unfollowedUserId) const followDoc = doc(collection(users, userId, 'follows'), unfollowedUserId)
await deleteDoc(followDoc) await deleteDoc(followDoc)
} }
@ -259,7 +254,7 @@ export function listenForFollows(
userId: string, userId: string,
setFollowIds: (followIds: string[]) => void setFollowIds: (followIds: string[]) => void
) { ) {
const follows = collection(db, 'users', userId, 'follows') const follows = collection(users, userId, 'follows')
return listenForValues<{ userId: string }>(follows, (docs) => return listenForValues<{ userId: string }>(follows, (docs) =>
setFollowIds(docs.map(({ userId }) => userId)) setFollowIds(docs.map(({ userId }) => userId))
) )

View File

@ -1,10 +1,17 @@
import { import {
collection,
getDoc, getDoc,
getDocs, getDocs,
onSnapshot, onSnapshot,
Query, Query,
CollectionReference,
DocumentReference, DocumentReference,
} from 'firebase/firestore' } from 'firebase/firestore'
import { db } from './init'
export const coll = <T>(path: string, ...rest: string[]) => {
return collection(db, path, ...rest) as CollectionReference<T>
}
export const getValue = async <T>(doc: DocumentReference) => { export const getValue = async <T>(doc: DocumentReference) => {
const snap = await getDoc(doc) const snap = await getDoc(doc)