Move private user out of getDestinationsForUser

This commit is contained in:
Ian Philips 2022-09-12 08:34:16 -06:00
parent deb0d4a2d2
commit f18d5825c2
4 changed files with 103 additions and 80 deletions

View File

@ -1,4 +1,5 @@
import { notification_subscription_types } from 'common/user' import { notification_subscription_types, PrivateUser } from './user'
import { DOMAIN } from './envs/constants'
export type Notification = { export type Notification = {
id: string id: string
@ -53,6 +54,7 @@ export type notification_source_update_types =
| 'deleted' | 'deleted'
| 'closed' | 'closed'
/* Optional - if possible use a keyof notification_subscription_types */
export type notification_reason_types = export type notification_reason_types =
| 'tagged_user' | 'tagged_user'
| 'on_new_follow' | 'on_new_follow'
@ -90,7 +92,11 @@ export type notification_reason_types =
| 'your_contract_closed' | 'your_contract_closed'
| 'subsidized_your_market' | 'subsidized_your_market'
// Adding a new key:value here is optional, you can also just use a key of notification_subscription_types // Adding a new key:value here is optional, you can just use a key of notification_subscription_types
// You might want to add a key:value here if there will be multiple notification reasons that map to the same
// subscription type, i.e. 'comment_on_contract_you_follow' and 'comment_on_contract_with_users_answer' both map to
// 'all_comments_on_watched_markets' subscription type
// TODO: perhaps better would be to map notification_subscription_types to arrays of notification_reason_types
export const notificationReasonToSubscriptionType: Partial< export const notificationReasonToSubscriptionType: Partial<
Record<notification_reason_types, keyof notification_subscription_types> Record<notification_reason_types, keyof notification_subscription_types>
> = { > = {
@ -127,3 +133,27 @@ export const notificationReasonToSubscriptionType: Partial<
reply_to_users_answer: 'all_replies_to_my_answers_on_watched_markets', reply_to_users_answer: 'all_replies_to_my_answers_on_watched_markets',
reply_to_users_comment: 'all_replies_to_my_comments_on_watched_markets', reply_to_users_comment: 'all_replies_to_my_comments_on_watched_markets',
} }
export const getDestinationsForUser = async (
privateUser: PrivateUser,
reason: notification_reason_types | keyof notification_subscription_types
) => {
const notificationSettings = privateUser.notificationSubscriptionTypes
let destinations
let subscriptionType: keyof notification_subscription_types | undefined
if (Object.keys(notificationSettings).includes(reason)) {
subscriptionType = reason as keyof notification_subscription_types
destinations = notificationSettings[subscriptionType]
} else {
const key = reason as notification_reason_types
subscriptionType = notificationReasonToSubscriptionType[key]
destinations = subscriptionType
? notificationSettings[subscriptionType]
: []
}
return {
sendToEmail: destinations.includes('email'),
sendToBrowser: destinations.includes('browser'),
urlToManageThisNotification: `${DOMAIN}/notifications?section=${subscriptionType}`,
}
}

View File

@ -1,10 +1,10 @@
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { import {
getDestinationsForUser,
Notification, Notification,
notification_reason_types, notification_reason_types,
notificationReasonToSubscriptionType,
} from '../../common/notification' } from '../../common/notification'
import { notification_subscription_types, User } from '../../common/user' import { User } from '../../common/user'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { getPrivateUser, getValues } from './utils' import { getPrivateUser, getValues } from './utils'
import { Comment } from '../../common/comment' import { Comment } from '../../common/comment'
@ -23,7 +23,6 @@ import {
sendNewAnswerEmail, sendNewAnswerEmail,
sendNewCommentEmail, sendNewCommentEmail,
} from './emails' } from './emails'
import { DOMAIN } from 'common/lib/envs/constants'
const firestore = admin.firestore() const firestore = admin.firestore()
type recipients_to_reason_texts = { type recipients_to_reason_texts = {
@ -61,8 +60,12 @@ export const createNotification = async (
) => { ) => {
for (const userId in userToReasonTexts) { for (const userId in userToReasonTexts) {
const { reason } = userToReasonTexts[userId] const { reason } = userToReasonTexts[userId]
const { sendToBrowser, sendToEmail, privateUser } = const privateUser = await getPrivateUser(userId)
await getDestinationsForUser(userId, reason) if (!privateUser) continue
const { sendToBrowser, sendToEmail } = await getDestinationsForUser(
privateUser,
reason
)
if (sendToBrowser) { if (sendToBrowser) {
const notificationRef = firestore const notificationRef = firestore
.collection(`/users/${userId}/notifications`) .collection(`/users/${userId}/notifications`)
@ -93,7 +96,12 @@ export const createNotification = async (
if (!sendToEmail) continue if (!sendToEmail) continue
if (reason === 'your_contract_closed' && privateUser && sourceContract) { if (reason === 'your_contract_closed' && privateUser && sourceContract) {
await sendMarketCloseEmail(reason, sourceUser, sourceContract) await sendMarketCloseEmail(
reason,
sourceUser,
privateUser,
sourceContract
)
} else if (reason === 'tagged_user') { } else if (reason === 'tagged_user') {
// TODO: send email to tagged user in new contract // TODO: send email to tagged user in new contract
} else if (reason === 'subsidized_your_market') { } else if (reason === 'subsidized_your_market') {
@ -197,35 +205,6 @@ export const createNotification = async (
await sendNotificationsIfSettingsPermit(userToReasonTexts) await sendNotificationsIfSettingsPermit(userToReasonTexts)
} }
export const getDestinationsForUser = async (
userId: string,
reason: notification_reason_types | keyof notification_subscription_types
) => {
const privateUser = await getPrivateUser(userId)
if (!privateUser)
return { sendToEmail: false, sendToBrowser: false, privateUser: null }
const notificationSettings = privateUser.notificationSubscriptionTypes
let destinations
let subscriptionType: keyof notification_subscription_types | undefined
if (Object.keys(notificationSettings).includes(reason)) {
subscriptionType = reason as keyof notification_subscription_types
destinations = notificationSettings[subscriptionType]
} else {
const key = reason as notification_reason_types
subscriptionType = notificationReasonToSubscriptionType[key]
destinations = subscriptionType
? notificationSettings[subscriptionType]
: []
}
return {
sendToEmail: destinations.includes('email'),
sendToBrowser: destinations.includes('browser'),
privateUser,
urlToManageThisNotification: `${DOMAIN}/notifications?section=${subscriptionType}`,
}
}
export const createCommentOrAnswerOrUpdatedContractNotification = async ( export const createCommentOrAnswerOrUpdatedContractNotification = async (
sourceId: string, sourceId: string,
sourceType: 'comment' | 'answer' | 'contract', sourceType: 'comment' | 'answer' | 'contract',
@ -314,9 +293,10 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
recipientIdsList.includes(userId) recipientIdsList.includes(userId)
) )
return return
const privateUser = await getPrivateUser(userId)
if (!privateUser) return
const { sendToBrowser, sendToEmail } = await getDestinationsForUser( const { sendToBrowser, sendToEmail } = await getDestinationsForUser(
userId, privateUser,
reason reason
) )
@ -328,7 +308,7 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
if (sourceType === 'comment') { if (sourceType === 'comment') {
await sendNewCommentEmail( await sendNewCommentEmail(
reason, reason,
userId, privateUser,
sourceUser, sourceUser,
sourceContract, sourceContract,
sourceText, sourceText,
@ -341,7 +321,7 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
} else if (sourceType === 'answer') } else if (sourceType === 'answer')
await sendNewAnswerEmail( await sendNewAnswerEmail(
reason, reason,
userId, privateUser,
sourceUser.name, sourceUser.name,
sourceText, sourceText,
sourceContract, sourceContract,
@ -354,7 +334,7 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
) )
await sendMarketResolutionEmail( await sendMarketResolutionEmail(
reason, reason,
userId, privateUser,
resolutionData.userInvestments[userId], resolutionData.userInvestments[userId],
resolutionData.userPayouts[userId], resolutionData.userPayouts[userId],
sourceUser, sourceUser,
@ -534,8 +514,10 @@ export const createTipNotification = async (
contract?: Contract, contract?: Contract,
group?: Group group?: Group
) => { ) => {
const privateUser = await getPrivateUser(toUser.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
toUser.id, privateUser,
'tip_received' 'tip_received'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -565,6 +547,7 @@ export const createTipNotification = async (
} }
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
// TODO: send notification to users that are watching the contract and want highly tipped comments only
// maybe TODO: send email notification to bet creator // maybe TODO: send email notification to bet creator
} }
@ -576,7 +559,12 @@ export const createBetFillNotification = async (
contract: Contract, contract: Contract,
idempotencyKey: string idempotencyKey: string
) => { ) => {
const { sendToBrowser } = await getDestinationsForUser(toUser.id, 'bet_fill') const privateUser = await getPrivateUser(toUser.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser(
privateUser,
'bet_fill'
)
if (!sendToBrowser) return if (!sendToBrowser) return
const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id) const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id)
@ -616,8 +604,10 @@ export const createReferralNotification = async (
referredByContract?: Contract, referredByContract?: Contract,
referredByGroup?: Group referredByGroup?: Group
) => { ) => {
const privateUser = await getPrivateUser(toUser.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
toUser.id, privateUser,
'you_referred_user' 'you_referred_user'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -668,8 +658,10 @@ export const createLoanIncomeNotification = async (
idempotencyKey: string, idempotencyKey: string,
income: number income: number
) => { ) => {
const privateUser = await getPrivateUser(toUser.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
toUser.id, privateUser,
'loan_income' 'loan_income'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -704,8 +696,10 @@ export const createChallengeAcceptedNotification = async (
acceptedAmount: number, acceptedAmount: number,
contract: Contract contract: Contract
) => { ) => {
const privateUser = await getPrivateUser(challengeCreator.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
challengeCreator.id, privateUser,
'challenge_accepted' 'challenge_accepted'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -743,8 +737,10 @@ export const createBettingStreakBonusNotification = async (
amount: number, amount: number,
idempotencyKey: string idempotencyKey: string
) => { ) => {
const privateUser = await getPrivateUser(user.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
user.id, privateUser,
'betting_streak_incremented' 'betting_streak_incremented'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -784,8 +780,10 @@ export const createLikeNotification = async (
contract: Contract, contract: Contract,
tip?: TipTxn tip?: TipTxn
) => { ) => {
const privateUser = await getPrivateUser(toUser.id)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
toUser.id, privateUser,
'liked_and_tipped_your_contract' 'liked_and_tipped_your_contract'
) )
if (!sendToBrowser) return if (!sendToBrowser) return
@ -828,8 +826,11 @@ export const createUniqueBettorBonusNotification = async (
amount: number, amount: number,
idempotencyKey: string idempotencyKey: string
) => { ) => {
console.log('createUniqueBettorBonusNotification')
const privateUser = await getPrivateUser(contractCreatorId)
if (!privateUser) return
const { sendToBrowser } = await getDestinationsForUser( const { sendToBrowser } = await getDestinationsForUser(
contractCreatorId, privateUser,
'unique_bettors_on_your_contract' 'unique_bettors_on_your_contract'
) )
if (!sendToBrowser) return if (!sendToBrowser) return

View File

@ -18,12 +18,14 @@ import { formatNumericProbability } from '../../common/pseudo-numeric'
import { sendTemplateEmail, sendTextEmail } from './send-email' import { sendTemplateEmail, sendTextEmail } from './send-email'
import { getUser } from './utils' import { getUser } from './utils'
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details' import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
import { notification_reason_types } from '../../common/notification' import {
import { getDestinationsForUser } from './create-notification' notification_reason_types,
getDestinationsForUser,
} from '../../common/notification'
export const sendMarketResolutionEmail = async ( export const sendMarketResolutionEmail = async (
reason: notification_reason_types, reason: notification_reason_types,
userId: string, privateUser: PrivateUser,
investment: number, investment: number,
payout: number, payout: number,
creator: User, creator: User,
@ -33,14 +35,11 @@ export const sendMarketResolutionEmail = async (
resolutionProbability?: number, resolutionProbability?: number,
resolutions?: { [outcome: string]: number } resolutions?: { [outcome: string]: number }
) => { ) => {
const { const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
privateUser, await getDestinationsForUser(privateUser, reason)
sendToEmail,
urlToManageThisNotification: unsubscribeUrl,
} = await getDestinationsForUser(userId, reason)
if (!privateUser || !privateUser.email || !sendToEmail) return if (!privateUser || !privateUser.email || !sendToEmail) return
const user = await getUser(userId) const user = await getUser(privateUser.id)
if (!user) return if (!user) return
const outcome = toDisplayResolution( const outcome = toDisplayResolution(
@ -53,7 +52,7 @@ export const sendMarketResolutionEmail = async (
const subject = `Resolved ${outcome}: ${contract.question}` const subject = `Resolved ${outcome}: ${contract.question}`
const creatorPayoutText = const creatorPayoutText =
creatorPayout >= 1 && userId === creator.id creatorPayout >= 1 && privateUser.id === creator.id
? ` (plus ${formatMoney(creatorPayout)} in commissions)` ? ` (plus ${formatMoney(creatorPayout)} in commissions)`
: '' : ''
@ -310,15 +309,13 @@ export const sendThankYouEmail = async (
export const sendMarketCloseEmail = async ( export const sendMarketCloseEmail = async (
reason: notification_reason_types, reason: notification_reason_types,
user: User, user: User,
privateUser: PrivateUser,
contract: Contract contract: Contract
) => { ) => {
const { const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
privateUser, await getDestinationsForUser(privateUser, reason)
sendToEmail,
urlToManageThisNotification: unsubscribeUrl,
} = await getDestinationsForUser(user.id, reason)
if (!privateUser || !privateUser.email || !sendToEmail) return if (!privateUser.email || !sendToEmail) return
const { username, name, id: userId } = user const { username, name, id: userId } = user
const firstName = name.split(' ')[0] const firstName = name.split(' ')[0]
@ -344,7 +341,7 @@ export const sendMarketCloseEmail = async (
export const sendNewCommentEmail = async ( export const sendNewCommentEmail = async (
reason: notification_reason_types, reason: notification_reason_types,
userId: string, privateUser: PrivateUser,
commentCreator: User, commentCreator: User,
contract: Contract, contract: Contract,
commentText: string, commentText: string,
@ -353,11 +350,8 @@ export const sendNewCommentEmail = async (
answerText?: string, answerText?: string,
answerId?: string answerId?: string
) => { ) => {
const { const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
privateUser, await getDestinationsForUser(privateUser, reason)
sendToEmail,
urlToManageThisNotification: unsubscribeUrl,
} = await getDestinationsForUser(userId, reason)
if (!privateUser || !privateUser.email || !sendToEmail) return if (!privateUser || !privateUser.email || !sendToEmail) return
const { question } = contract const { question } = contract
@ -421,7 +415,7 @@ export const sendNewCommentEmail = async (
export const sendNewAnswerEmail = async ( export const sendNewAnswerEmail = async (
reason: notification_reason_types, reason: notification_reason_types,
userId: string, privateUser: PrivateUser,
name: string, name: string,
text: string, text: string,
contract: Contract, contract: Contract,
@ -429,14 +423,11 @@ export const sendNewAnswerEmail = async (
) => { ) => {
const { creatorId } = contract const { creatorId } = contract
// Don't send the creator's own answers. // Don't send the creator's own answers.
if (userId === creatorId) return if (privateUser.id === creatorId) return
const { const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
privateUser, await getDestinationsForUser(privateUser, reason)
sendToEmail, if (!privateUser.email || !sendToEmail) return
urlToManageThisNotification: unsubscribeUrl,
} = await getDestinationsForUser(userId, reason)
if (!privateUser || !privateUser.email || !sendToEmail) return
const { question, creatorUsername, slug } = contract const { question, creatorUsername, slug } = contract

View File

@ -1006,6 +1006,7 @@ function getReasonForShowingNotification(
) { ) {
const { sourceType, sourceUpdateType, reason, sourceSlug } = notification const { sourceType, sourceUpdateType, reason, sourceSlug } = notification
let reasonText: string let reasonText: string
// TODO: we could leave out this switch and just use the reason field now that they have more information
switch (sourceType) { switch (sourceType) {
case 'comment': case 'comment':
if (reason === 'reply_to_users_answer') if (reason === 'reply_to_users_answer')