Cache likes and liked by user ids on contract

This commit is contained in:
Ian Philips 2022-08-30 08:38:07 -06:00
parent 2e502a774c
commit 73b75c3d9b
10 changed files with 80 additions and 34 deletions

View File

@ -59,6 +59,8 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
popularityScore?: number
followerCount?: number
featuredOnHomeRank?: number
likedByUserIds?: string[]
likedByUserCount?: number
} & T
export type BinaryContract = Contract & Binary

View File

@ -1,7 +1,7 @@
export type Like = {
id: string
id: string // will be id of the object liked, i.e. contract.id
userId: string
contractId: string
type: 'contract'
createdTime: number
tipTxnId?: string
}

View File

@ -32,6 +32,7 @@ export * from './reset-betting-streaks'
export * from './reset-weekly-emails-flag'
export * from './on-update-contract-follow'
export * from './on-create-like'
export * from './on-delete-like'
// v2
export * from './health'

View File

@ -4,6 +4,7 @@ import { Like } from '../../common/like'
import { getContract, getUser, log } from './utils'
import { createLikeNotification } from './create-notification'
import { TipTxn } from '../../common/txn'
import { uniq } from 'lodash'
const firestore = admin.firestore()
@ -12,11 +13,28 @@ export const onCreateLike = functions.firestore
.onCreate(async (change, context) => {
const like = change.data() as Like
const { eventId } = context
await handleCreateLike(like, eventId)
if (like.type === 'contract') {
await handleCreateLikeNotification(like, eventId)
await updateContractLikes(like)
}
})
const handleCreateLike = async (like: Like, eventId: string) => {
const contract = await getContract(like.contractId)
const updateContractLikes = async (like: Like) => {
const contract = await getContract(like.id)
if (!contract) {
log('Could not find contract')
return
}
const likedByUserIds = uniq(contract.likedByUserIds ?? [])
likedByUserIds.push(like.userId)
await firestore
.collection('contracts')
.doc(like.id)
.update({ likedByUserIds, likedByUserCount: likedByUserIds.length })
}
const handleCreateLikeNotification = async (like: Like, eventId: string) => {
const contract = await getContract(like.id)
if (!contract) {
log('Could not find contract')
return

View File

@ -0,0 +1,32 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Like } from '../../common/like'
import { getContract, log } from './utils'
import { uniq } from 'lodash'
const firestore = admin.firestore()
export const onDeleteLike = functions.firestore
.document('users/{userId}/likes/{likeId}')
.onDelete(async (change) => {
const like = change.data() as Like
if (like.type === 'contract') {
await removeContractLike(like)
}
})
const removeContractLike = async (like: Like) => {
const contract = await getContract(like.id)
if (!contract) {
log('Could not find contract')
return
}
const likedByUserIds = uniq(contract.likedByUserIds ?? [])
const newLikedByUserIds = likedByUserIds.filter(
(userId) => userId !== like.userId
)
await firestore.collection('contracts').doc(like.id).update({
likedByUserIds: newLikedByUserIds,
likedByUserCount: newLikedByUserIds.length,
})
}

View File

@ -2,6 +2,7 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { FieldValue } from 'firebase-admin/firestore'
// TODO: should cache the follower user ids in the contract as these triggers aren't idempotent
export const onDeleteContractFollow = functions.firestore
.document('contracts/{contractId}/follows/{userId}')
.onDelete(async (change, context) => {

View File

@ -19,7 +19,9 @@ export function LikeMarketButton(props: {
const { contract, user } = props
const likes = useUserLikes(user?.id)
const likedContractIds = likes?.map((l) => l.contractId)
const likedContractIds = likes
?.filter((l) => l.type === 'contract')
.map((l) => l.id)
if (!user) return <div />
const onLike = async () => {

View File

@ -35,10 +35,8 @@ export function UserLikesButton(props: { user: User }) {
{likedContract.question}
</SiteLink>
<XIcon
className="ml-2 h-5 w-5 cursor-pointer"
onClick={() => async () => {
await unLikeContract(user.id, likedContract.id)
}}
className="ml-2 h-5 w-5 shrink-0 cursor-pointer"
onClick={() => unLikeContract(user.id, likedContract.id)}
/>
</Row>
))}

View File

@ -15,21 +15,24 @@ export const useUserLikes = (userId: string | undefined) => {
return contractIds
}
export const useUserLikedContracts = (userId: string | undefined) => {
const [contractIds, setContractIds] = useState<Like[] | undefined>()
const [likes, setLikes] = useState<Like[] | undefined>()
const [contracts, setContracts] = useState<Contract[] | undefined>()
useEffect(() => {
if (userId) return listenForLikes(userId, setContractIds)
if (userId)
return listenForLikes(userId, (likes) => {
setLikes(likes.filter((l) => l.type === 'contract'))
})
}, [userId])
useEffect(() => {
if (contractIds)
if (likes)
Promise.all(
contractIds.map(async (like) => {
return await getContractFromId(like.contractId)
likes.map(async (like) => {
return await getContractFromId(like.id)
})
).then((contracts) => setContracts(filterDefined(contracts)))
}, [contractIds])
}, [likes])
return contracts
}

View File

@ -1,19 +1,12 @@
import {
collection,
deleteDoc,
doc,
query,
setDoc,
where,
} from 'firebase/firestore'
import { collection, deleteDoc, doc, setDoc } from 'firebase/firestore'
import { db } from 'web/lib/firebase/init'
import toast from 'react-hot-toast'
import { transact } from 'web/lib/firebase/api'
import { removeUndefinedProps } from 'common/lib/util/object'
import { Like } from 'common/lib/like'
import { removeUndefinedProps } from 'common/util/object'
import { Like } from 'common/like'
import { track } from '@amplitude/analytics-browser'
import { User } from 'common/user'
import { Contract } from 'common/lib/contract'
import { Contract } from 'common/contract'
export const LIKE_TIP_AMOUNT = 5
@ -22,11 +15,7 @@ function getLikesCollection(userId: string) {
}
export const unLikeContract = async (userId: string, contractId: string) => {
const ref = await query(
getLikesCollection(userId),
where('contractId', '==', contractId)
)
const snapshot = await ref.get()
const ref = await doc(getLikesCollection(userId), contractId)
return await deleteDoc(ref)
}
@ -51,13 +40,13 @@ export const likeContract = async (user: User, contract: Contract) => {
console.log('result', result)
}
// create new like in db under users collection
const ref = doc(getLikesCollection(user.id))
const ref = doc(getLikesCollection(user.id), contract.id)
// contract slug and question are set via trigger
const like = removeUndefinedProps({
id: ref.id,
userId: user.id,
createdTime: Date.now(),
contractId: contract.id,
type: 'contract',
tipTxnId: result.txn.id,
} as Like)
track('like', {