From 9e7b3c08874d924a6d09bcf7a0898f24fb2786db Mon Sep 17 00:00:00 2001 From: James Grugett Date: Sun, 10 Jul 2022 12:21:42 -0500 Subject: [PATCH] Simple bet fill notification --- common/notification.ts | 1 + functions/src/create-notification.ts | 36 +++++++++++++++++++- functions/src/on-create-bet.ts | 51 +++++++++++++++++++++++++++- web/pages/notifications.tsx | 16 ++++++++- 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/common/notification.ts b/common/notification.ts index da8a045a..63a44a52 100644 --- a/common/notification.ts +++ b/common/notification.ts @@ -62,3 +62,4 @@ export type notification_reason_types = | 'unique_bettors_on_your_contract' | 'on_group_you_are_member_of' | 'tip_received' + | 'bet_fill' diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts index 519720fd..0d3432a7 100644 --- a/functions/src/create-notification.ts +++ b/functions/src/create-notification.ts @@ -10,7 +10,7 @@ import { Contract } from '../../common/contract' import { getUserByUsername, getValues } from './utils' import { Comment } from '../../common/comment' import { uniq } from 'lodash' -import { Bet } from '../../common/bet' +import { Bet, LimitBet } from '../../common/bet' import { Answer } from '../../common/answer' import { getContractBetMetrics } from '../../common/calculate' import { removeUndefinedProps } from '../../common/util/object' @@ -382,3 +382,37 @@ export const createTipNotification = async ( } return await notificationRef.set(removeUndefinedProps(notification)) } + +export const createBetFillNotification = async ( + fromUser: User, + toUser: User, + bet: Bet, + userBet: LimitBet, + contract: Contract, + idempotencyKey: string +) => { + const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id) + const fillAmount = fill?.amount ?? 0 + + const notificationRef = firestore + .collection(`/users/${toUser.id}/notifications`) + .doc(idempotencyKey) + const notification: Notification = { + id: idempotencyKey, + userId: toUser.id, + reason: 'bet_fill', + createdTime: Date.now(), + isSeen: false, + sourceId: userBet.id, + sourceType: 'bet', + sourceUpdateType: 'updated', + sourceUserName: fromUser.name, + sourceUserUsername: fromUser.username, + sourceUserAvatarUrl: fromUser.avatarUrl, + sourceText: fillAmount.toString(), + sourceContractCreatorUsername: contract.creatorUsername, + sourceContractTitle: contract.question, + sourceContractSlug: contract.slug, + } + return await notificationRef.set(removeUndefinedProps(notification)) +} diff --git a/functions/src/on-create-bet.ts b/functions/src/on-create-bet.ts index 3e615e42..5789ed0b 100644 --- a/functions/src/on-create-bet.ts +++ b/functions/src/on-create-bet.ts @@ -1,7 +1,11 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { keyBy } from 'lodash' -import { Bet } from '../../common/bet' +import { Bet, LimitBet } from '../../common/bet' +import { getContract, getUser, getValues } from './utils' +import { createBetFillNotification } from './create-notification' +import { filterDefined } from '../../common/util/array' const firestore = admin.firestore() @@ -11,6 +15,8 @@ export const onCreateBet = functions.firestore const { contractId } = context.params as { contractId: string } + const { eventId } = context + const bet = change.data() as Bet const lastBetTime = bet.createdTime @@ -18,4 +24,47 @@ export const onCreateBet = functions.firestore .collection('contracts') .doc(contractId) .update({ lastBetTime, lastUpdatedTime: Date.now() }) + + await notifyFills(bet, contractId, eventId) }) + +const notifyFills = async (bet: Bet, contractId: string, eventId: string) => { + if (!bet.fills) return + + const user = await getUser(bet.userId) + if (!user) return + const contract = await getContract(contractId) + if (!contract) return + + const matchedFills = bet.fills.filter((fill) => fill.matchedBetId !== null) + const matchedBets = ( + await Promise.all( + matchedFills.map((fill) => + getValues( + firestore.collectionGroup('bets').where('id', '==', fill.matchedBetId) + ) + ) + ) + ).flat() + + const betUsers = await Promise.all( + matchedBets.map((bet) => getUser(bet.userId)) + ) + const betUsersById = keyBy(filterDefined(betUsers), 'id') + + await Promise.all( + matchedBets.map((matchedBet) => { + const matchedUser = betUsersById[matchedBet.userId] + if (!matchedUser) return + + return createBetFillNotification( + user, + matchedUser, + bet, + matchedBet, + contract, + eventId + ) + }) + ) +} diff --git a/web/pages/notifications.tsx b/web/pages/notifications.tsx index 3a8e4bc0..161d3659 100644 --- a/web/pages/notifications.tsx +++ b/web/pages/notifications.tsx @@ -795,6 +795,8 @@ function getSourceIdForLinkComponent( return sourceId case 'contract': return '' + case 'bet': + return '' default: return sourceId } @@ -861,8 +863,17 @@ function NotificationTextLabel(props: { {'+' + formatMoney(parseInt(sourceText))} ) + } else if (sourceType === 'bet' && sourceText) { + return ( + <> + Filled{' '} + + {formatMoney(parseInt(sourceText))} + {' '} + of your limit bet + + ) } - // return default text return (
@@ -913,6 +924,9 @@ function getReasonForShowingNotification( else if (sourceSlug) reasonText = 'joined because you shared' else reasonText = 'joined because of you' break + case 'bet': + reasonText = 'bet against you' + break default: reasonText = '' }