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:
parent
8132fa595b
commit
3b4666ba3e
|
@ -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 }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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))
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user