It's alive... It's alive, it's moving, it's alive, it's alive, it's alive, it's alive, IT'S ALIVE'

This commit is contained in:
Ian Philips 2022-09-09 09:08:02 -06:00
parent 9c8d4c8d94
commit 06467dc1d7
5 changed files with 173 additions and 169 deletions

View File

@ -1,4 +1,4 @@
import { exhaustive_notification_subscribe_types } from 'common/user' import { notification_subscription_types } from 'common/user'
export type Notification = { export type Notification = {
id: string id: string
@ -55,26 +55,17 @@ export type notification_source_update_types =
export type notification_reason_types = export type notification_reason_types =
| 'tagged_user' | 'tagged_user'
// | 'on_users_contract'
// | 'on_contract_with_users_shares_in'
// | 'on_contract_with_users_shares_out'
// | 'on_contract_with_users_answer'
// | 'on_contract_with_users_comment'
| 'on_new_follow' | 'on_new_follow'
| 'contract_from_followed_user' | 'contract_from_followed_user'
| 'added_you_to_group'
| 'you_referred_user' | 'you_referred_user'
| 'user_joined_to_bet_on_your_market' | 'user_joined_to_bet_on_your_market'
| 'unique_bettors_on_your_contract' | 'unique_bettors_on_your_contract'
// | 'on_group_you_are_member_of'
| 'tip_received' | 'tip_received'
| 'bet_fill' | 'bet_fill'
| 'user_joined_from_your_group_invite' | 'user_joined_from_your_group_invite'
| 'challenge_accepted' | 'challenge_accepted'
| 'betting_streak_incremented' | 'betting_streak_incremented'
| 'loan_income' | 'loan_income'
// | 'you_follow_contract'
| 'liked_your_contract'
| 'liked_and_tipped_your_contract' | 'liked_and_tipped_your_contract'
| 'comment_on_your_contract' | 'comment_on_your_contract'
| 'answer_on_your_contract' | 'answer_on_your_contract'
@ -97,25 +88,19 @@ export type notification_reason_types =
| 'reply_to_users_answer' | 'reply_to_users_answer'
| 'reply_to_users_comment' | 'reply_to_users_comment'
| 'your_contract_closed' | 'your_contract_closed'
| 'subsidized_your_market'
export const notificationReasonToSubscribeTypeMap: Record< // Adding a new key:value here is optional, you can also just use a key of exhaustive_notification_subscribe_types
notification_reason_types, export const notificationReasonToSubscriptionType: Partial<
keyof exhaustive_notification_subscribe_types Record<notification_reason_types, keyof notification_subscription_types>
> = { > = {
tagged_user: 'user_tagged_you',
on_new_follow: 'new_followers',
contract_from_followed_user: 'new_markets_by_followed_users',
added_you_to_group: 'group_adds',
you_referred_user: 'referral_bonuses', you_referred_user: 'referral_bonuses',
user_joined_to_bet_on_your_market: 'referral_bonuses', user_joined_to_bet_on_your_market: 'referral_bonuses',
unique_bettors_on_your_contract: 'unique_bettor_bonuses',
tip_received: 'tips_on_your_comments', tip_received: 'tips_on_your_comments',
bet_fill: 'limit_order_fills', bet_fill: 'limit_order_fills',
user_joined_from_your_group_invite: 'referral_bonuses', user_joined_from_your_group_invite: 'referral_bonuses',
challenge_accepted: 'limit_order_fills', challenge_accepted: 'limit_order_fills',
betting_streak_incremented: 'betting_streaks', betting_streak_incremented: 'betting_streaks',
loan_income: 'loan_income',
liked_your_contract: 'tips_on_your_markets',
liked_and_tipped_your_contract: 'tips_on_your_markets', liked_and_tipped_your_contract: 'tips_on_your_markets',
comment_on_your_contract: 'all_comments_on_my_markets', comment_on_your_contract: 'all_comments_on_my_markets',
answer_on_your_contract: 'all_answers_on_my_markets', answer_on_your_contract: 'all_answers_on_my_markets',
@ -128,7 +113,7 @@ export const notificationReasonToSubscribeTypeMap: Record<
answer_on_contract_with_users_shares_in: answer_on_contract_with_users_shares_in:
'all_answers_on_contracts_with_shares_in_on_watched_markets', 'all_answers_on_contracts_with_shares_in_on_watched_markets',
update_on_contract_with_users_shares_in: update_on_contract_with_users_shares_in:
'market_updates_with_shares_in_on_watched_markets', 'market_updates_on_watched_markets_with_shares_in',
resolution_on_contract_with_users_shares_in: resolution_on_contract_with_users_shares_in:
'resolutions_on_watched_markets_with_shares_in', 'resolutions_on_watched_markets_with_shares_in',
comment_on_contract_with_users_answer: 'all_comments_on_watched_markets', comment_on_contract_with_users_answer: 'all_comments_on_watched_markets',
@ -141,5 +126,4 @@ export const notificationReasonToSubscribeTypeMap: Record<
resolution_on_contract_with_users_comment: 'resolutions_on_watched_markets', resolution_on_contract_with_users_comment: 'resolutions_on_watched_markets',
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',
your_contract_closed: 'my_markets_closed',
} }

View File

@ -66,12 +66,12 @@ export type PrivateUser = {
initialIpAddress?: string initialIpAddress?: string
apiKey?: string apiKey?: string
notificationPreferences?: notification_subscribe_types notificationPreferences?: notification_subscribe_types
notificationSubscriptionTypes: exhaustive_notification_subscribe_types notificationSubscriptionTypes: notification_subscription_types
} }
export type notification_destination_types = 'email' | 'browser' export type notification_destination_types = 'email' | 'browser'
export type exhaustive_notification_subscribe_types = { export type notification_subscription_types = {
// Watched Markets // Watched Markets
all_comments_on_watched_markets: notification_destination_types[] // Email currently - seems bad all_comments_on_watched_markets: notification_destination_types[] // Email currently - seems bad
all_answers_on_watched_markets: notification_destination_types[] // Email currently - seems bad all_answers_on_watched_markets: notification_destination_types[] // Email currently - seems bad
@ -89,31 +89,31 @@ export type exhaustive_notification_subscribe_types = {
all_answers_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[] all_answers_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[]
// On users' markets // On users' markets
my_markets_closed: notification_destination_types[] // Email, Recommended your_contract_closed: notification_destination_types[] // Email, Recommended
all_comments_on_my_markets: notification_destination_types[] // Email all_comments_on_my_markets: notification_destination_types[] // Email
all_answers_on_my_markets: notification_destination_types[] // Email all_answers_on_my_markets: notification_destination_types[] // Email
subsidized_your_market: notification_destination_types[] // Email
// Market updates // Market updates
resolutions_on_watched_markets: notification_destination_types[] // Email resolutions_on_watched_markets: notification_destination_types[] // Email
resolutions_on_watched_markets_with_shares_in: notification_destination_types[] // Email resolutions_on_watched_markets_with_shares_in: notification_destination_types[] // Email
market_updates_on_watched_markets: notification_destination_types[] market_updates_on_watched_markets: notification_destination_types[]
market_updates_with_shares_in_on_watched_markets: notification_destination_types[] market_updates_on_watched_markets_with_shares_in: notification_destination_types[]
probability_updates_on_watched_markets: notification_destination_types[] // Email - would want persistent changes only though probability_updates_on_watched_markets: notification_destination_types[] // Email - would want persistent changes only though
// Balance Changes // Balance Changes
loan_income: notification_destination_types[] loan_income: notification_destination_types[]
betting_streaks: notification_destination_types[] betting_streaks: notification_destination_types[]
referral_bonuses: notification_destination_types[] referral_bonuses: notification_destination_types[]
unique_bettor_bonuses: notification_destination_types[] unique_bettors_on_your_contract: notification_destination_types[]
tips_on_your_comments: notification_destination_types[] tips_on_your_comments: notification_destination_types[]
tips_on_your_markets: notification_destination_types[] tips_on_your_markets: notification_destination_types[]
limit_order_fills: notification_destination_types[] limit_order_fills: notification_destination_types[]
// General // General
user_tagged_you: notification_destination_types[] // Email tagged_user: notification_destination_types[] // Email
new_followers: notification_destination_types[] // Email on_new_follow: notification_destination_types[] // Email
group_adds: notification_destination_types[] // Email contract_from_followed_user: notification_destination_types[] // Email
new_markets_by_followed_users: notification_destination_types[] // Email
trending_markets: notification_destination_types[] // Email trending_markets: notification_destination_types[] // Email
profit_loss_updates: notification_destination_types[] // Email profit_loss_updates: notification_destination_types[] // Email
} }
@ -169,15 +169,15 @@ export const getDefaultNotificationSettings = (
comments_by_followed_users_on_watched_markets: constructPref( comments_by_followed_users_on_watched_markets: constructPref(
wantsAll, wantsAll,
false false
), //wantsAll ? browserOnly : none, ),
all_replies_to_my_comments_on_watched_markets: constructPref( all_replies_to_my_comments_on_watched_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromCommentEmails !unsubscribedFromCommentEmails
), //wantsAll || wantsLess ? both : none, ),
all_replies_to_my_answers_on_watched_markets: constructPref( all_replies_to_my_answers_on_watched_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromCommentEmails !unsubscribedFromCommentEmails
), //wantsAll || wantsLess ? both : none, ),
all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref( all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref(
wantsAll, wantsAll,
!unsubscribedFromCommentEmails !unsubscribedFromCommentEmails
@ -187,29 +187,30 @@ export const getDefaultNotificationSettings = (
answers_by_followed_users_on_watched_markets: constructPref( answers_by_followed_users_on_watched_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromAnswerEmails !unsubscribedFromAnswerEmails
), //wantsAll || wantsLess ? both : none, ),
answers_by_market_creator_on_watched_markets: constructPref( answers_by_market_creator_on_watched_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromAnswerEmails !unsubscribedFromAnswerEmails
), //wantsAll || wantsLess ? both : none, ),
all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref( all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref(
wantsAll, wantsAll,
!unsubscribedFromAnswerEmails !unsubscribedFromAnswerEmails
), ),
// On users' markets // On users' markets
my_markets_closed: constructPref( your_contract_closed: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromResolutionEmails !unsubscribedFromResolutionEmails
), //wantsAll || wantsLess ? both : none, // High priority ), // High priority
all_comments_on_my_markets: constructPref( all_comments_on_my_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromCommentEmails !unsubscribedFromCommentEmails
), //wantsAll || wantsLess ? both : none, ),
all_answers_on_my_markets: constructPref( all_answers_on_my_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromAnswerEmails !unsubscribedFromAnswerEmails
), //wantsAll || wantsLess ? both : none, ),
subsidized_your_market: constructPref(wantsAll || wantsLess, true),
// Market updates // Market updates
resolutions_on_watched_markets: constructPref( resolutions_on_watched_markets: constructPref(
@ -220,7 +221,7 @@ export const getDefaultNotificationSettings = (
wantsAll || wantsLess, wantsAll || wantsLess,
false false
), ),
market_updates_with_shares_in_on_watched_markets: constructPref( market_updates_on_watched_markets_with_shares_in: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
false false
), ),
@ -233,7 +234,10 @@ export const getDefaultNotificationSettings = (
loan_income: constructPref(wantsAll || wantsLess, false), loan_income: constructPref(wantsAll || wantsLess, false),
betting_streaks: constructPref(wantsAll || wantsLess, false), betting_streaks: constructPref(wantsAll || wantsLess, false),
referral_bonuses: constructPref(wantsAll || wantsLess, true), referral_bonuses: constructPref(wantsAll || wantsLess, true),
unique_bettor_bonuses: constructPref(wantsAll || wantsLess, false), unique_bettors_on_your_contract: constructPref(
wantsAll || wantsLess,
false
),
tipped_comments_on_watched_markets: constructPref( tipped_comments_on_watched_markets: constructPref(
wantsAll || wantsLess, wantsAll || wantsLess,
!unsubscribedFromCommentEmails !unsubscribedFromCommentEmails
@ -242,9 +246,9 @@ export const getDefaultNotificationSettings = (
limit_order_fills: constructPref(wantsAll || wantsLess, false), limit_order_fills: constructPref(wantsAll || wantsLess, false),
// General // General
user_tagged_you: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none, tagged_user: constructPref(wantsAll || wantsLess, true),
new_followers: constructPref(wantsAll || wantsLess, true), on_new_follow: constructPref(wantsAll || wantsLess, true),
new_markets_by_followed_users: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none, contract_from_followed_user: constructPref(wantsAll || wantsLess, true),
trending_markets: constructPref( trending_markets: constructPref(
false, false,
!unsubscribedFromWeeklyTrendingEmails !unsubscribedFromWeeklyTrendingEmails
@ -254,6 +258,5 @@ export const getDefaultNotificationSettings = (
wantsAll || wantsLess, wantsAll || wantsLess,
false false
), ),
group_adds: constructPref(wantsAll || wantsLess, true), } as notification_subscription_types
} as exhaustive_notification_subscribe_types
} }

View File

@ -2,11 +2,9 @@ import * as admin from 'firebase-admin'
import { import {
Notification, Notification,
notification_reason_types, notification_reason_types,
notification_source_update_types, notificationReasonToSubscriptionType,
notification_source_types,
notificationReasonToSubscribeTypeMap,
} from '../../common/notification' } from '../../common/notification'
import { User } from '../../common/user' import { notification_subscription_types, 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'
@ -20,6 +18,7 @@ import { Group } from '../../common/group'
import { Challenge } from '../../common/challenge' import { Challenge } from '../../common/challenge'
import { Like } from '../../common/like' import { Like } from '../../common/like'
import { import {
sendMarketCloseEmail,
sendMarketResolutionEmail, sendMarketResolutionEmail,
sendNewAnswerEmail, sendNewAnswerEmail,
sendNewCommentEmail, sendNewCommentEmail,
@ -32,8 +31,8 @@ type recipients_to_reason_texts = {
export const createNotification = async ( export const createNotification = async (
sourceId: string, sourceId: string,
sourceType: notification_source_types, sourceType: 'contract' | 'liquidity' | 'follow',
sourceUpdateType: notification_source_update_types, sourceUpdateType: 'closed' | 'created',
sourceUser: User, sourceUser: User,
idempotencyKey: string, idempotencyKey: string,
sourceText: string, sourceText: string,
@ -56,15 +55,14 @@ export const createNotification = async (
) )
} }
const createUsersNotifications = async ( const sendNotificationsIfSettingsPermit = async (
userToReasonTexts: recipients_to_reason_texts userToReasonTexts: recipients_to_reason_texts
) => { ) => {
await Promise.all( for (const userId in userToReasonTexts) {
Object.keys(userToReasonTexts).map(async (userId) => { const { reason } = userToReasonTexts[userId]
const { reason } = userToReasonTexts[userId] const { sendToBrowser, sendToEmail, privateUser } =
const { sendToBrowser } = await getDestinationsForUser(userId, reason) await getDestinationsForUser(userId, reason)
if (!sendToBrowser) return Promise.resolve() if (sendToBrowser) {
const notificationRef = firestore const notificationRef = firestore
.collection(`/users/${userId}/notifications`) .collection(`/users/${userId}/notifications`)
.doc(idempotencyKey) .doc(idempotencyKey)
@ -89,8 +87,22 @@ export const createNotification = async (
sourceTitle: title ? title : sourceContract?.question, sourceTitle: title ? title : sourceContract?.question,
} }
await notificationRef.set(removeUndefinedProps(notification)) await notificationRef.set(removeUndefinedProps(notification))
}) }
)
if (!sendToEmail) continue
if (reason === 'your_contract_closed' && privateUser && sourceContract) {
await sendMarketCloseEmail(sourceUser, privateUser, sourceContract)
} else if (reason === 'tagged_user') {
// TODO: send email to tagged user in new contract
} else if (reason === 'subsidized_your_market') {
// TODO: send email to creator of market that was subsidized
} else if (reason === 'contract_from_followed_user') {
// TODO: send email to follower of user who created market
} else if (reason === 'on_new_follow') {
// TODO: send email to user who was followed
}
}
} }
const notifyUsersFollowers = async ( const notifyUsersFollowers = async (
@ -146,31 +158,18 @@ export const createNotification = async (
shouldGetNotification(sourceContract.creatorId, userToReasonTexts) shouldGetNotification(sourceContract.creatorId, userToReasonTexts)
) )
userToReasonTexts[sourceContract.creatorId] = { userToReasonTexts[sourceContract.creatorId] = {
reason: 'your_contract_closed', reason:
sourceType === 'liquidity'
? 'subsidized_your_market'
: 'your_contract_closed',
} }
} }
const notifyUserAddedToGroup = (
userToReasonTexts: recipients_to_reason_texts,
relatedUserId: string
) => {
if (shouldGetNotification(relatedUserId, userToReasonTexts))
userToReasonTexts[relatedUserId] = {
reason: 'added_you_to_group',
}
}
const userToReasonTexts: recipients_to_reason_texts = {}
// The following functions modify the userToReasonTexts object in place. // The following functions modify the userToReasonTexts object in place.
const userToReasonTexts: recipients_to_reason_texts = {}
if (sourceType === 'follow' && recipients?.[0]) { if (sourceType === 'follow' && recipients?.[0]) {
notifyFollowedUser(userToReasonTexts, recipients[0]) notifyFollowedUser(userToReasonTexts, recipients[0])
} else if (
sourceType === 'group' &&
sourceUpdateType === 'created' &&
recipients
) {
recipients.forEach((r) => notifyUserAddedToGroup(userToReasonTexts, r))
} else if ( } else if (
sourceType === 'contract' && sourceType === 'contract' &&
sourceUpdateType === 'created' && sourceUpdateType === 'created' &&
@ -194,27 +193,33 @@ export const createNotification = async (
await notifyContractCreator(userToReasonTexts, sourceContract) await notifyContractCreator(userToReasonTexts, sourceContract)
} }
await createUsersNotifications(userToReasonTexts) await sendNotificationsIfSettingsPermit(userToReasonTexts)
} }
const getDestinationsForUser = async ( const getDestinationsForUser = async (
userId: string, userId: string,
reason: notification_reason_types reason: notification_reason_types | keyof notification_subscription_types
) => { ) => {
const privateUser = await getPrivateUser(userId) const privateUser = await getPrivateUser(userId)
if (!privateUser) return { sendToEmail: false, sendToBrowser: false } if (!privateUser)
return { sendToEmail: false, sendToBrowser: false, privateUser: null }
const notificationSettings = privateUser.notificationSubscriptionTypes const notificationSettings = privateUser.notificationSubscriptionTypes
console.log('notificationSettings', notificationSettings) let destinations
console.log('reason', reason) if (Object.keys(notificationSettings).includes(reason)) {
console.log('notif reason to type map', notificationReasonToSubscribeTypeMap) const key = reason as keyof notification_subscription_types
const subscribeType = notificationReasonToSubscribeTypeMap[reason] destinations = notificationSettings[key]
console.log('subscribeType', subscribeType) } else {
const destinations = const key = reason as notification_reason_types
notificationSettings[notificationReasonToSubscribeTypeMap[reason]] const subscriptionType = notificationReasonToSubscriptionType[key]
console.log('destinations', destinations) destinations = subscriptionType
? notificationSettings[subscriptionType]
: []
}
return { return {
sendToEmail: destinations.includes('email'), sendToEmail: destinations.includes('email'),
sendToBrowser: destinations.includes('browser'), sendToBrowser: destinations.includes('browser'),
privateUser,
} }
} }
@ -463,24 +468,23 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
) )
} }
const notifyRepliedUser = async ( const notifyRepliedUser = async () => {
relatedUserId: string, if (sourceType === 'comment' && repliedUserId && repliedToType)
relatedSourceType: notification_source_types await sendNotificationsIfSettingsPermit(
) => { repliedUserId,
await sendNotificationsIfSettingsPermit( repliedToType === 'answer'
relatedUserId, ? 'reply_to_users_answer'
relatedSourceType === 'answer' : 'reply_to_users_comment'
? 'reply_to_users_answer' )
: 'reply_to_users_comment'
)
} }
const notifyTaggedUsers = async (userIds: string[]) => { const notifyTaggedUsers = async () => {
await Promise.all( if (sourceType === 'comment' && taggedUserIds && taggedUserIds.length > 0)
userIds.map((userId) => await Promise.all(
sendNotificationsIfSettingsPermit(userId, 'tagged_user') taggedUserIds.map((userId) =>
sendNotificationsIfSettingsPermit(userId, 'tagged_user')
)
) )
)
} }
const notifyLiquidityProviders = async () => { const notifyLiquidityProviders = async () => {
@ -506,11 +510,8 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
) )
} }
if (sourceType === 'comment') { await notifyRepliedUser()
if (repliedUserId && repliedToType) await notifyTaggedUsers()
await notifyRepliedUser(repliedUserId, repliedToType)
await notifyTaggedUsers(taggedUserIds ?? [])
}
await notifyContractCreator() await notifyContractCreator()
await notifyOtherAnswerersOnContract() await notifyOtherAnswerersOnContract()
await notifyLiquidityProviders() await notifyLiquidityProviders()
@ -559,6 +560,8 @@ export const createTipNotification = async (
sourceTitle: group?.name, sourceTitle: group?.name,
} }
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
// maybe TODO: send email notification to bet creator
} }
export const createBetFillNotification = async ( export const createBetFillNotification = async (
@ -597,6 +600,8 @@ export const createBetFillNotification = async (
sourceContractId: contract.id, sourceContractId: contract.id,
} }
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
// maybe TODO: send email notification to bet creator
} }
export const createReferralNotification = async ( export const createReferralNotification = async (
@ -650,6 +655,8 @@ export const createReferralNotification = async (
: referredByContract?.question, : referredByContract?.question,
} }
await notificationRef.set(removeUndefinedProps(notification)) await notificationRef.set(removeUndefinedProps(notification))
// TODO send email notification
} }
export const createLoanIncomeNotification = async ( export const createLoanIncomeNotification = async (
@ -779,13 +786,16 @@ export const createLikeNotification = async (
) )
if (!sendToBrowser) return if (!sendToBrowser) return
// not handling just likes, must include tip
if (!tip) return
const notificationRef = firestore const notificationRef = firestore
.collection(`/users/${toUser.id}/notifications`) .collection(`/users/${toUser.id}/notifications`)
.doc(idempotencyKey) .doc(idempotencyKey)
const notification: Notification = { const notification: Notification = {
id: idempotencyKey, id: idempotencyKey,
userId: toUser.id, userId: toUser.id,
reason: tip ? 'liked_and_tipped_your_contract' : 'liked_your_contract', reason: 'liked_and_tipped_your_contract',
createdTime: Date.now(), createdTime: Date.now(),
isSeen: false, isSeen: false,
sourceId: like.id, sourceId: like.id,
@ -802,20 +812,8 @@ export const createLikeNotification = async (
sourceTitle: contract.question, sourceTitle: contract.question,
} }
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
}
export async function filterUserIdsForOnlyFollowerIds( // TODO send email notification
userIds: string[],
contractId: string
) {
// get contract follower documents and check here if they're a follower
const contractFollowersSnap = await firestore
.collection(`contracts/${contractId}/follows`)
.get()
const contractFollowersIds = contractFollowersSnap.docs.map(
(doc) => doc.data().id
)
return userIds.filter((id) => contractFollowersIds.includes(id))
} }
export const createUniqueBettorBonusNotification = async ( export const createUniqueBettorBonusNotification = async (
@ -857,4 +855,6 @@ export const createUniqueBettorBonusNotification = async (
sourceContractCreatorUsername: contract.creatorUsername, sourceContractCreatorUsername: contract.creatorUsername,
} }
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
// TODO send email notification
} }

View File

@ -3,7 +3,6 @@ import * as admin from 'firebase-admin'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { getPrivateUser, getUserByUsername } from './utils' import { getPrivateUser, getUserByUsername } from './utils'
import { sendMarketCloseEmail } from './emails'
import { createNotification } from './create-notification' import { createNotification } from './create-notification'
export const marketCloseNotifications = functions export const marketCloseNotifications = functions
@ -56,7 +55,6 @@ async function sendMarketCloseEmails() {
const privateUser = await getPrivateUser(user.id) const privateUser = await getPrivateUser(user.id)
if (!privateUser) continue if (!privateUser) continue
await sendMarketCloseEmail(user, privateUser, contract)
await createNotification( await createNotification(
contract.id, contract.id,
'contract', 'contract',

View File

@ -4,7 +4,7 @@ import { LoadingIndicator } from 'web/components/loading-indicator'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import clsx from 'clsx' import clsx from 'clsx'
import { import {
exhaustive_notification_subscribe_types, notification_subscription_types,
notification_destination_types, notification_destination_types,
} from 'common/user' } from 'common/user'
import { updatePrivateUser } from 'web/lib/firebase/users' import { updatePrivateUser } from 'web/lib/firebase/users'
@ -33,29 +33,42 @@ export function NotificationSettings() {
return <LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} /> return <LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} />
} }
// should be keyof Partial<exhaustive_notification_subscribe_types>[] but can't figure out how to make that work const emailsEnabled: Array<keyof notification_subscription_types> = [
// TODO: re-enable emails for renamed subscribe types 'all_comments_on_watched_markets',
const emailsEnabled = [ 'all_replies_to_my_comments_on_watched_markets',
// 'all_comments', 'all_comments_on_contracts_with_shares_in_on_watched_markets',
// 'all_answers',
// 'resolutions', 'all_answers_on_watched_markets',
'all_replies_to_my_comments', 'all_replies_to_my_answers_on_watched_markets',
'all_replies_to_my_answers', 'all_answers_on_contracts_with_shares_in_on_watched_markets',
'your_contract_closed',
'all_comments_on_my_markets', 'all_comments_on_my_markets',
'all_answers_on_my_markets', 'all_answers_on_my_markets',
'my_markets_closed',
'probability_updates', 'resolutions_on_watched_markets_with_shares_in',
'user_tagged_you', 'resolutions_on_watched_markets',
'new_markets_by_followed_users',
'tagged_user',
'trending_markets', 'trending_markets',
'profit_loss_updates',
'all_comments_on_contracts_with_shares_in', // TODO: add these
'all_answers_on_contracts_with_shares_in', // 'contract_from_followed_user',
// 'referral_bonuses',
// 'unique_bettors_on_your_contract',
// 'tips_on_your_markets',
// 'tips_on_your_comments',
// 'subsidized_your_market',
// 'on_new_follow',
// maybe the following?
// 'profit_loss_updates',
// 'probability_updates_on_watched_markets',
// 'limit_order_fills',
] ]
const browserDisabled = ['trending_markets', 'profit_loss_updates'] const browserDisabled = ['trending_markets', 'profit_loss_updates']
const watched_markets_explanations_comments: { const watched_markets_explanations_comments: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
all_comments_on_watched_markets: 'All', all_comments_on_watched_markets: 'All',
all_replies_to_my_comments_on_watched_markets: 'Replies to your comments', all_replies_to_my_comments_on_watched_markets: 'Replies to your comments',
@ -64,7 +77,7 @@ export function NotificationSettings() {
// comments_by_followed_users_on_watched_markets: 'By followed users', // comments_by_followed_users_on_watched_markets: 'By followed users',
} }
const watched_markets_explanations_answers: { const watched_markets_explanations_answers: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
all_answers_on_watched_markets: 'All', all_answers_on_watched_markets: 'All',
all_replies_to_my_answers_on_watched_markets: 'Replies to your answers', all_replies_to_my_answers_on_watched_markets: 'Replies to your answers',
@ -74,45 +87,49 @@ export function NotificationSettings() {
// answers_by_market_creator_on_watched_markets: 'By market creator', // answers_by_market_creator_on_watched_markets: 'By market creator',
} }
const watched_markets_explanations_your_markets: { const watched_markets_explanations_your_markets: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
my_markets_closed: 'Your market has closed (and needs resolution)', your_contract_closed: 'Your market has closed (and needs resolution)',
all_comments_on_my_markets: 'Comments on your markets', all_comments_on_my_markets: 'Comments on your markets',
all_answers_on_my_markets: 'Answers on your markets', all_answers_on_my_markets: 'Answers on your markets',
subsidized_your_market: 'Your market was subsidized',
} }
const watched_markets_explanations_market_updates: { const watched_markets_explanations_market_updates: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
resolutions_on_watched_markets: 'Market resolutions', resolutions_on_watched_markets: 'Market resolutions',
resolutions_on_watched_markets_with_shares_in:
'Market resolutions you have shares in',
market_updates_on_watched_markets: 'Updates made by the creator', market_updates_on_watched_markets: 'Updates made by the creator',
market_updates_on_watched_markets_with_shares_in:
'Updates made by the creator on markets you have shares in',
// probability_updates_on_watched_markets: 'Probability updates', // probability_updates_on_watched_markets: 'Probability updates',
} }
const balance_change_explanations: { const balance_change_explanations: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
loan_income: 'Automatic loans from your profitable bets', loan_income: 'Automatic loans from your profitable bets',
betting_streaks: 'Betting streak bonuses', betting_streaks: 'Betting streak bonuses',
referral_bonuses: 'Referral bonuses from referring users', referral_bonuses: 'Referral bonuses from referring users',
unique_bettor_bonuses: 'Unique bettor bonuses on your markets', unique_bettors_on_your_contract: 'Unique bettor bonuses on your markets',
tips_on_your_comments: 'Tips on your comments', tips_on_your_comments: 'Tips on your comments',
limit_order_fills: 'Limit order fills', limit_order_fills: 'Limit order fills',
} }
const general_explanations: { const general_explanations: {
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string [key in keyof Partial<notification_subscription_types>]: string
} = { } = {
user_tagged_you: 'A user tagged you', tagged_user: 'A user tagged you',
new_markets_by_followed_users: 'New markets created by users you follow', contract_from_followed_user: 'New markets created by users you follow',
trending_markets: 'Weekly trending markets', trending_markets: 'Weekly trending markets',
new_followers: 'New followers', on_new_follow: 'New followers',
group_adds: 'When someone adds you to a group',
// profit_loss_updates: 'Weekly profit/loss updates', // profit_loss_updates: 'Weekly profit/loss updates',
} }
const NotificationSettingLine = ( const NotificationSettingLine = (
description: string, description: string,
key: string, key: keyof notification_subscription_types,
value: notification_destination_types[] value: notification_destination_types[]
) => { ) => {
const previousInAppValue = value.includes('browser') const previousInAppValue = value.includes('browser')
@ -217,22 +234,18 @@ export function NotificationSettings() {
) )
} }
const getUsersSavedPreference = (key: string) => { const getUsersSavedPreference = (
return Object.keys(privateUser.notificationSubscriptionTypes).includes(key) key: keyof notification_subscription_types
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment ) => {
//@ts-ignore return privateUser.notificationSubscriptionTypes[key] ?? []
privateUser.notificationSubscriptionTypes[
Object.keys(privateUser.notificationSubscriptionTypes).filter(
(x) => x === key
)[0]
]
: ''
} }
const Section = ( const Section = (
icon: ReactNode, icon: ReactNode,
label: string, label: string,
map: { [key: string]: string } subscriptionTypeToDescription: {
[key in keyof Partial<notification_subscription_types>]: string
}
) => { ) => {
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
return ( return (
@ -255,8 +268,14 @@ export function NotificationSettings() {
)} )}
</Row> </Row>
<Col className={clsx(expanded ? 'block' : 'hidden', 'gap-2 p-2')}> <Col className={clsx(expanded ? 'block' : 'hidden', 'gap-2 p-2')}>
{Object.entries(map).map(([key, value]) => {Object.entries(subscriptionTypeToDescription).map(([key, value]) =>
NotificationSettingLine(value, key, getUsersSavedPreference(key)) NotificationSettingLine(
value,
key as keyof notification_subscription_types,
getUsersSavedPreference(
key as keyof notification_subscription_types
)
)
)} )}
</Col> </Col>
</Col> </Col>