Cache likes and liked by user ids on contract
This commit is contained in:
parent
2e502a774c
commit
73b75c3d9b
|
@ -59,6 +59,8 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
|
||||||
popularityScore?: number
|
popularityScore?: number
|
||||||
followerCount?: number
|
followerCount?: number
|
||||||
featuredOnHomeRank?: number
|
featuredOnHomeRank?: number
|
||||||
|
likedByUserIds?: string[]
|
||||||
|
likedByUserCount?: number
|
||||||
} & T
|
} & T
|
||||||
|
|
||||||
export type BinaryContract = Contract & Binary
|
export type BinaryContract = Contract & Binary
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export type Like = {
|
export type Like = {
|
||||||
id: string
|
id: string // will be id of the object liked, i.e. contract.id
|
||||||
userId: string
|
userId: string
|
||||||
contractId: string
|
type: 'contract'
|
||||||
createdTime: number
|
createdTime: number
|
||||||
tipTxnId?: string
|
tipTxnId?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ export * from './reset-betting-streaks'
|
||||||
export * from './reset-weekly-emails-flag'
|
export * from './reset-weekly-emails-flag'
|
||||||
export * from './on-update-contract-follow'
|
export * from './on-update-contract-follow'
|
||||||
export * from './on-create-like'
|
export * from './on-create-like'
|
||||||
|
export * from './on-delete-like'
|
||||||
|
|
||||||
// v2
|
// v2
|
||||||
export * from './health'
|
export * from './health'
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Like } from '../../common/like'
|
||||||
import { getContract, getUser, log } from './utils'
|
import { getContract, getUser, log } from './utils'
|
||||||
import { createLikeNotification } from './create-notification'
|
import { createLikeNotification } from './create-notification'
|
||||||
import { TipTxn } from '../../common/txn'
|
import { TipTxn } from '../../common/txn'
|
||||||
|
import { uniq } from 'lodash'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
@ -12,11 +13,28 @@ export const onCreateLike = functions.firestore
|
||||||
.onCreate(async (change, context) => {
|
.onCreate(async (change, context) => {
|
||||||
const like = change.data() as Like
|
const like = change.data() as Like
|
||||||
const { eventId } = context
|
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 updateContractLikes = async (like: Like) => {
|
||||||
const contract = await getContract(like.contractId)
|
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) {
|
if (!contract) {
|
||||||
log('Could not find contract')
|
log('Could not find contract')
|
||||||
return
|
return
|
||||||
|
|
32
functions/src/on-delete-like.ts
Normal file
32
functions/src/on-delete-like.ts
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { FieldValue } from 'firebase-admin/firestore'
|
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
|
export const onDeleteContractFollow = functions.firestore
|
||||||
.document('contracts/{contractId}/follows/{userId}')
|
.document('contracts/{contractId}/follows/{userId}')
|
||||||
.onDelete(async (change, context) => {
|
.onDelete(async (change, context) => {
|
||||||
|
|
|
@ -19,7 +19,9 @@ export function LikeMarketButton(props: {
|
||||||
const { contract, user } = props
|
const { contract, user } = props
|
||||||
|
|
||||||
const likes = useUserLikes(user?.id)
|
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 />
|
if (!user) return <div />
|
||||||
|
|
||||||
const onLike = async () => {
|
const onLike = async () => {
|
||||||
|
|
|
@ -35,10 +35,8 @@ export function UserLikesButton(props: { user: User }) {
|
||||||
{likedContract.question}
|
{likedContract.question}
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
<XIcon
|
<XIcon
|
||||||
className="ml-2 h-5 w-5 cursor-pointer"
|
className="ml-2 h-5 w-5 shrink-0 cursor-pointer"
|
||||||
onClick={() => async () => {
|
onClick={() => unLikeContract(user.id, likedContract.id)}
|
||||||
await unLikeContract(user.id, likedContract.id)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -15,21 +15,24 @@ export const useUserLikes = (userId: string | undefined) => {
|
||||||
return contractIds
|
return contractIds
|
||||||
}
|
}
|
||||||
export const useUserLikedContracts = (userId: string | undefined) => {
|
export const useUserLikedContracts = (userId: string | undefined) => {
|
||||||
const [contractIds, setContractIds] = useState<Like[] | undefined>()
|
const [likes, setLikes] = useState<Like[] | undefined>()
|
||||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId) return listenForLikes(userId, setContractIds)
|
if (userId)
|
||||||
|
return listenForLikes(userId, (likes) => {
|
||||||
|
setLikes(likes.filter((l) => l.type === 'contract'))
|
||||||
|
})
|
||||||
}, [userId])
|
}, [userId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contractIds)
|
if (likes)
|
||||||
Promise.all(
|
Promise.all(
|
||||||
contractIds.map(async (like) => {
|
likes.map(async (like) => {
|
||||||
return await getContractFromId(like.contractId)
|
return await getContractFromId(like.id)
|
||||||
})
|
})
|
||||||
).then((contracts) => setContracts(filterDefined(contracts)))
|
).then((contracts) => setContracts(filterDefined(contracts)))
|
||||||
}, [contractIds])
|
}, [likes])
|
||||||
|
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
import {
|
import { collection, deleteDoc, doc, setDoc } from 'firebase/firestore'
|
||||||
collection,
|
|
||||||
deleteDoc,
|
|
||||||
doc,
|
|
||||||
query,
|
|
||||||
setDoc,
|
|
||||||
where,
|
|
||||||
} from 'firebase/firestore'
|
|
||||||
import { db } from 'web/lib/firebase/init'
|
import { db } from 'web/lib/firebase/init'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { transact } from 'web/lib/firebase/api'
|
import { transact } from 'web/lib/firebase/api'
|
||||||
import { removeUndefinedProps } from 'common/lib/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { Like } from 'common/lib/like'
|
import { Like } from 'common/like'
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { Contract } from 'common/lib/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
export const LIKE_TIP_AMOUNT = 5
|
export const LIKE_TIP_AMOUNT = 5
|
||||||
|
|
||||||
|
@ -22,11 +15,7 @@ function getLikesCollection(userId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unLikeContract = async (userId: string, contractId: string) => {
|
export const unLikeContract = async (userId: string, contractId: string) => {
|
||||||
const ref = await query(
|
const ref = await doc(getLikesCollection(userId), contractId)
|
||||||
getLikesCollection(userId),
|
|
||||||
where('contractId', '==', contractId)
|
|
||||||
)
|
|
||||||
const snapshot = await ref.get()
|
|
||||||
return await deleteDoc(ref)
|
return await deleteDoc(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +40,13 @@ export const likeContract = async (user: User, contract: Contract) => {
|
||||||
console.log('result', result)
|
console.log('result', result)
|
||||||
}
|
}
|
||||||
// create new like in db under users collection
|
// 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
|
// contract slug and question are set via trigger
|
||||||
const like = removeUndefinedProps({
|
const like = removeUndefinedProps({
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
contractId: contract.id,
|
type: 'contract',
|
||||||
tipTxnId: result.txn.id,
|
tipTxnId: result.txn.id,
|
||||||
} as Like)
|
} as Like)
|
||||||
track('like', {
|
track('like', {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user