Referrals bug fix and attribute group

This commit is contained in:
Ian Philips 2022-07-18 10:40:44 -06:00
parent db537a97ba
commit 39c38a669e
9 changed files with 94 additions and 42 deletions

View File

@ -63,3 +63,4 @@ export type notification_reason_types =
| 'on_group_you_are_member_of' | 'on_group_you_are_member_of'
| 'tip_received' | 'tip_received'
| 'bet_fill' | 'bet_fill'
| 'user_joined_from_your_group_invite'

View File

@ -38,6 +38,7 @@ export type User = {
referredByUserId?: string referredByUserId?: string
referredByContractId?: string referredByContractId?: string
referredByGroupId?: string
lastPingTime?: number lastPingTime?: number
} }

View File

@ -22,11 +22,11 @@ service cloud.firestore {
allow read; allow read;
allow update: if resource.data.id == request.auth.uid allow update: if resource.data.id == request.auth.uid
&& request.resource.data.diff(resource.data).affectedKeys() && request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'referredByContractId', 'lastPingTime']); .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime']);
// User referral rules // User referral rules
allow update: if resource.data.id == request.auth.uid allow update: if resource.data.id == request.auth.uid
&& request.resource.data.diff(resource.data).affectedKeys() && request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['referredByUserId']) .hasOnly(['referredByUserId', 'referredByContractId', 'referredByGroupId'])
// only one referral allowed per user // only one referral allowed per user
&& !("referredByUserId" in resource.data) && !("referredByUserId" in resource.data)
// user can't refer themselves // user can't refer themselves

View File

@ -253,20 +253,6 @@ export const createNotification = async (
} }
} }
const notifyUserReceivedReferralBonus = async (
userToReasonTexts: user_to_reason_texts,
relatedUserId: string
) => {
if (shouldGetNotification(relatedUserId, userToReasonTexts))
userToReasonTexts[relatedUserId] = {
// If the referrer is the market creator, just tell them they joined to bet on their market
reason:
sourceContract?.creatorId === relatedUserId
? 'user_joined_to_bet_on_your_market'
: 'you_referred_user',
}
}
const notifyContractCreatorOfUniqueBettorsBonus = async ( const notifyContractCreatorOfUniqueBettorsBonus = async (
userToReasonTexts: user_to_reason_texts, userToReasonTexts: user_to_reason_texts,
userId: string userId: string
@ -284,8 +270,6 @@ export const createNotification = async (
} else if (sourceType === 'group' && relatedUserId) { } else if (sourceType === 'group' && relatedUserId) {
if (sourceUpdateType === 'created') if (sourceUpdateType === 'created')
await notifyUserAddedToGroup(userToReasonTexts, relatedUserId) await notifyUserAddedToGroup(userToReasonTexts, relatedUserId)
} else if (sourceType === 'user' && relatedUserId) {
await notifyUserReceivedReferralBonus(userToReasonTexts, relatedUserId)
} }
// The following functions need sourceContract to be defined. // The following functions need sourceContract to be defined.
@ -435,3 +419,52 @@ export const createGroupCommentNotification = async (
} }
await notificationRef.set(removeUndefinedProps(notification)) await notificationRef.set(removeUndefinedProps(notification))
} }
export const createReferralNotification = async (
toUser: User,
referredUser: User,
idempotencyKey: string,
bonusAmount: string,
referredByContract?: Contract,
referredByGroup?: Group
) => {
const notificationRef = firestore
.collection(`/users/${toUser.id}/notifications`)
.doc(idempotencyKey)
const notification: Notification = {
id: idempotencyKey,
userId: toUser.id,
reason: referredByGroup
? 'user_joined_from_your_group_invite'
: referredByContract?.creatorId === toUser.id
? 'user_joined_to_bet_on_your_market'
: 'you_referred_user',
createdTime: Date.now(),
isSeen: false,
sourceId: referredUser.id,
sourceType: 'user',
sourceUpdateType: 'updated',
sourceContractId: referredByContract?.id,
sourceUserName: referredUser.name,
sourceUserUsername: referredUser.username,
sourceUserAvatarUrl: referredUser.avatarUrl,
sourceText: bonusAmount,
// Only pass the contract referral details if they weren't referred to a group
sourceContractCreatorUsername: !referredByGroup
? referredByContract?.creatorUsername
: undefined,
sourceContractTitle: !referredByGroup
? referredByContract?.question
: undefined,
sourceContractSlug: !referredByGroup ? referredByContract?.slug : undefined,
sourceSlug: referredByGroup
? groupPath(referredByGroup.slug)
: referredByContract?.slug,
sourceTitle: referredByGroup
? referredByGroup.name
: referredByContract?.question,
}
await notificationRef.set(removeUndefinedProps(notification))
}
const groupPath = (groupSlug: string) => `/group/${groupSlug}`

View File

@ -2,11 +2,12 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { REFERRAL_AMOUNT, User } from '../../common/user' import { REFERRAL_AMOUNT, User } from '../../common/user'
import { HOUSE_LIQUIDITY_PROVIDER_ID } from '../../common/antes' import { HOUSE_LIQUIDITY_PROVIDER_ID } from '../../common/antes'
import { createNotification } from './create-notification' import { createReferralNotification } from './create-notification'
import { ReferralTxn } from '../../common/txn' import { ReferralTxn } from '../../common/txn'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { LimitBet } from 'common/bet' import { LimitBet } from 'common/bet'
import { QuerySnapshot } from 'firebase-admin/firestore' import { QuerySnapshot } from 'firebase-admin/firestore'
import { Group } from 'common/group'
const firestore = admin.firestore() const firestore = admin.firestore()
export const onUpdateUser = functions.firestore export const onUpdateUser = functions.firestore
@ -54,6 +55,17 @@ async function handleUserUpdatedReferral(user: User, eventId: string) {
} }
console.log(`referredByContract: ${referredByContract}`) console.log(`referredByContract: ${referredByContract}`)
let referredByGroup: Group | undefined = undefined
if (user.referredByGroupId) {
const referredByGroupDoc = firestore.doc(
`groups/${user.referredByGroupId}`
)
referredByGroup = await transaction
.get(referredByGroupDoc)
.then((snap) => snap.data() as Group)
}
console.log(`referredByGroup: ${referredByGroup}`)
const txns = ( const txns = (
await firestore await firestore
.collection('txns') .collection('txns')
@ -100,18 +112,13 @@ async function handleUserUpdatedReferral(user: User, eventId: string) {
totalDeposits: referredByUser.totalDeposits + REFERRAL_AMOUNT, totalDeposits: referredByUser.totalDeposits + REFERRAL_AMOUNT,
}) })
await createNotification( await createReferralNotification(
user.id, referredByUser,
'user',
'updated',
user, user,
eventId, eventId,
txn.amount.toString(), txn.amount.toString(),
referredByContract, referredByContract,
'user', referredByGroup
referredByUser.id,
referredByContract?.slug,
referredByContract?.question
) )
}) })
} }

View File

@ -89,11 +89,11 @@ export async function getGroupsWithContractId(
setGroups(await getValues<Group>(q)) setGroups(await getValues<Group>(q))
} }
export async function addUserToGroupViaSlug(groupSlug: string, userId: string) { export async function addUserToGroupViaId(groupId: string, userId: string) {
// get group to get the member ids // get group to get the member ids
const group = await getGroupBySlug(groupSlug) const group = await getGroup(groupId)
if (!group) { if (!group) {
console.error(`Group not found: ${groupSlug}`) console.error(`Group not found: ${groupId}`)
return return
} }
return await joinGroup(group, userId) return await joinGroup(group, userId)

View File

@ -35,7 +35,7 @@ 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'
import { addUserToGroupViaSlug } from 'web/lib/firebase/groups' import { addUserToGroupViaId } from 'web/lib/firebase/groups'
import { removeUndefinedProps } from 'common/util/object' import { removeUndefinedProps } from 'common/util/object'
import { randomString } from 'common/util/random' import { randomString } from 'common/util/random'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -99,13 +99,13 @@ export function listenForPrivateUser(
const CACHED_USER_KEY = 'CACHED_USER_KEY' const CACHED_USER_KEY = 'CACHED_USER_KEY'
const CACHED_REFERRAL_USERNAME_KEY = 'CACHED_REFERRAL_KEY' const CACHED_REFERRAL_USERNAME_KEY = 'CACHED_REFERRAL_KEY'
const CACHED_REFERRAL_CONTRACT_ID_KEY = 'CACHED_REFERRAL_CONTRACT_KEY' const CACHED_REFERRAL_CONTRACT_ID_KEY = 'CACHED_REFERRAL_CONTRACT_KEY'
const CACHED_REFERRAL_GROUP_SLUG_KEY = 'CACHED_REFERRAL_GROUP_KEY' const CACHED_REFERRAL_GROUP_ID_KEY = 'CACHED_REFERRAL_GROUP_KEY'
export function writeReferralInfo( export function writeReferralInfo(
defaultReferrerUsername: string, defaultReferrerUsername: string,
contractId?: string, contractId?: string,
referralUsername?: string, referralUsername?: string,
groupSlug?: string groupId?: string
) { ) {
const local = safeLocalStorage() const local = safeLocalStorage()
const cachedReferralUser = local?.getItem(CACHED_REFERRAL_USERNAME_KEY) const cachedReferralUser = local?.getItem(CACHED_REFERRAL_USERNAME_KEY)
@ -121,7 +121,7 @@ export function writeReferralInfo(
local?.setItem(CACHED_REFERRAL_USERNAME_KEY, referralUsername) local?.setItem(CACHED_REFERRAL_USERNAME_KEY, referralUsername)
// Always write the most recent explicit group invite query value // Always write the most recent explicit group invite query value
if (groupSlug) local?.setItem(CACHED_REFERRAL_GROUP_SLUG_KEY, groupSlug) if (groupId) local?.setItem(CACHED_REFERRAL_GROUP_ID_KEY, groupId)
// Write the first contract id that we see. // Write the first contract id that we see.
const cachedReferralContract = local?.getItem(CACHED_REFERRAL_CONTRACT_ID_KEY) const cachedReferralContract = local?.getItem(CACHED_REFERRAL_CONTRACT_ID_KEY)
@ -134,14 +134,14 @@ async function setCachedReferralInfoForUser(user: User | null) {
// if the user wasn't created in the last minute, don't bother // if the user wasn't created in the last minute, don't bother
const now = dayjs().utc() const now = dayjs().utc()
const userCreatedTime = dayjs(user.createdTime) const userCreatedTime = dayjs(user.createdTime)
if (now.diff(userCreatedTime, 'minute') > 1) return if (now.diff(userCreatedTime, 'minute') > 5) return
const local = safeLocalStorage() const local = safeLocalStorage()
const cachedReferralUsername = local?.getItem(CACHED_REFERRAL_USERNAME_KEY) const cachedReferralUsername = local?.getItem(CACHED_REFERRAL_USERNAME_KEY)
const cachedReferralContractId = local?.getItem( const cachedReferralContractId = local?.getItem(
CACHED_REFERRAL_CONTRACT_ID_KEY CACHED_REFERRAL_CONTRACT_ID_KEY
) )
const cachedReferralGroupSlug = local?.getItem(CACHED_REFERRAL_GROUP_SLUG_KEY) const cachedReferralGroupId = local?.getItem(CACHED_REFERRAL_GROUP_ID_KEY)
// get user via username // get user via username
if (cachedReferralUsername) if (cachedReferralUsername)
@ -155,6 +155,9 @@ async function setCachedReferralInfoForUser(user: User | null) {
referredByContractId: cachedReferralContractId referredByContractId: cachedReferralContractId
? cachedReferralContractId ? cachedReferralContractId
: undefined, : undefined,
referredByGroupId: cachedReferralGroupId
? cachedReferralGroupId
: undefined,
}) })
) )
.catch((err) => { .catch((err) => {
@ -165,15 +168,14 @@ async function setCachedReferralInfoForUser(user: User | null) {
userId: user.id, userId: user.id,
referredByUserId: referredByUser.id, referredByUserId: referredByUser.id,
referredByContractId: cachedReferralContractId, referredByContractId: cachedReferralContractId,
referredByGroupSlug: cachedReferralGroupSlug, referredByGroupId: cachedReferralGroupId,
}) })
}) })
}) })
if (cachedReferralGroupSlug) if (cachedReferralGroupId) addUserToGroupViaId(cachedReferralGroupId, user.id)
addUserToGroupViaSlug(cachedReferralGroupSlug, user.id)
local?.removeItem(CACHED_REFERRAL_GROUP_SLUG_KEY) local?.removeItem(CACHED_REFERRAL_GROUP_ID_KEY)
local?.removeItem(CACHED_REFERRAL_USERNAME_KEY) local?.removeItem(CACHED_REFERRAL_USERNAME_KEY)
local?.removeItem(CACHED_REFERRAL_CONTRACT_ID_KEY) local?.removeItem(CACHED_REFERRAL_CONTRACT_ID_KEY)
} }

View File

@ -161,7 +161,7 @@ export default function GroupPage(props: {
referrer?: string referrer?: string
} }
if (!user && router.isReady) if (!user && router.isReady)
writeReferralInfo(creator.username, undefined, referrer, group?.slug) writeReferralInfo(creator.username, undefined, referrer, group?.id)
}, [user, creator, group, router]) }, [user, creator, group, router])
const { width } = useWindowSize() const { width } = useWindowSize()

View File

@ -713,8 +713,12 @@ function QuestionOrGroupLink(props: {
href={ href={
sourceContractCreatorUsername sourceContractCreatorUsername
? `/${sourceContractCreatorUsername}/${sourceContractSlug}` ? `/${sourceContractCreatorUsername}/${sourceContractSlug}`
: (sourceType === 'group' || sourceType === 'tip') && sourceSlug : // User's added to group or received a tip there
(sourceType === 'group' || sourceType === 'tip') && sourceSlug
? `${groupPath(sourceSlug)}` ? `${groupPath(sourceSlug)}`
: // User referral via group
sourceSlug?.includes('/group/')
? `${sourceSlug}`
: '' : ''
} }
onClick={() => onClick={() =>
@ -745,12 +749,16 @@ function getSourceUrl(notification: Notification) {
} = notification } = notification
if (sourceType === 'follow') return `/${sourceUserUsername}` if (sourceType === 'follow') return `/${sourceUserUsername}`
if (sourceType === 'group' && sourceSlug) return `${groupPath(sourceSlug)}` if (sourceType === 'group' && sourceSlug) return `${groupPath(sourceSlug)}`
// User referral via contract:
if ( if (
sourceContractCreatorUsername && sourceContractCreatorUsername &&
sourceContractSlug && sourceContractSlug &&
sourceType === 'user' sourceType === 'user'
) )
return `/${sourceContractCreatorUsername}/${sourceContractSlug}` return `/${sourceContractCreatorUsername}/${sourceContractSlug}`
// User referral:
if (sourceType === 'user' && !sourceContractSlug)
return `/${sourceUserUsername}`
if (sourceType === 'tip' && sourceContractSlug) if (sourceType === 'tip' && sourceContractSlug)
return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${sourceSlug}` return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${sourceSlug}`
if (sourceType === 'tip' && sourceSlug) return `${groupPath(sourceSlug)}` if (sourceType === 'tip' && sourceSlug) return `${groupPath(sourceSlug)}`