Linked notification settings to notification rules
This commit is contained in:
parent
d88b4ed081
commit
87dd678752
|
@ -1,3 +1,5 @@
|
||||||
|
import { exhaustive_notification_subscribe_types } from 'common/user'
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string
|
||||||
|
@ -53,26 +55,91 @@ export type notification_source_update_types =
|
||||||
|
|
||||||
export type notification_reason_types =
|
export type notification_reason_types =
|
||||||
| 'tagged_user'
|
| 'tagged_user'
|
||||||
| 'on_users_contract'
|
// | 'on_users_contract'
|
||||||
| 'on_contract_with_users_shares_in'
|
// | 'on_contract_with_users_shares_in'
|
||||||
| 'on_contract_with_users_shares_out'
|
// | 'on_contract_with_users_shares_out'
|
||||||
| 'on_contract_with_users_answer'
|
// | 'on_contract_with_users_answer'
|
||||||
| 'on_contract_with_users_comment'
|
// | 'on_contract_with_users_comment'
|
||||||
| 'reply_to_users_answer'
|
|
||||||
| 'reply_to_users_comment'
|
|
||||||
| 'on_new_follow'
|
| 'on_new_follow'
|
||||||
| 'you_follow_user'
|
| 'contract_from_followed_user'
|
||||||
| 'added_you_to_group'
|
| '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'
|
// | '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'
|
// | 'you_follow_contract'
|
||||||
| 'liked_your_contract'
|
| 'liked_your_contract'
|
||||||
| 'liked_and_tipped_your_contract'
|
| 'liked_and_tipped_your_contract'
|
||||||
|
| 'comment_on_your_contract'
|
||||||
|
| 'answer_on_your_contract'
|
||||||
|
| 'comment_on_contract_you_follow'
|
||||||
|
| 'answer_on_contract_you_follow'
|
||||||
|
| 'update_on_contract_you_follow'
|
||||||
|
| 'resolution_on_contract_you_follow'
|
||||||
|
| 'comment_on_contract_with_users_shares_in'
|
||||||
|
| 'answer_on_contract_with_users_shares_in'
|
||||||
|
| 'update_on_contract_with_users_shares_in'
|
||||||
|
| 'resolution_on_contract_with_users_shares_in'
|
||||||
|
| 'comment_on_contract_with_users_answer'
|
||||||
|
| 'update_on_contract_with_users_answer'
|
||||||
|
| 'resolution_on_contract_with_users_answer'
|
||||||
|
| 'answer_on_contract_with_users_answer'
|
||||||
|
| 'comment_on_contract_with_users_comment'
|
||||||
|
| 'answer_on_contract_with_users_comment'
|
||||||
|
| 'update_on_contract_with_users_comment'
|
||||||
|
| 'resolution_on_contract_with_users_comment'
|
||||||
|
| 'reply_to_users_answer'
|
||||||
|
| 'reply_to_users_comment'
|
||||||
|
| 'your_contract_closed'
|
||||||
|
|
||||||
|
export const notificationReasonToSubscribeTypeMap: Record<
|
||||||
|
notification_reason_types,
|
||||||
|
keyof exhaustive_notification_subscribe_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',
|
||||||
|
user_joined_to_bet_on_your_market: 'referral_bonuses',
|
||||||
|
unique_bettors_on_your_contract: 'unique_bettor_bonuses',
|
||||||
|
tip_received: 'tips_on_your_comments',
|
||||||
|
bet_fill: 'limit_order_fills',
|
||||||
|
user_joined_from_your_group_invite: 'referral_bonuses',
|
||||||
|
challenge_accepted: 'limit_order_fills',
|
||||||
|
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',
|
||||||
|
comment_on_your_contract: 'all_comments_on_my_markets',
|
||||||
|
answer_on_your_contract: 'all_answers_on_my_markets',
|
||||||
|
comment_on_contract_you_follow: 'all_comments_on_watched_markets',
|
||||||
|
answer_on_contract_you_follow: 'all_answers_on_watched_markets',
|
||||||
|
update_on_contract_you_follow: 'market_updates_on_watched_markets',
|
||||||
|
resolution_on_contract_you_follow: 'resolutions_on_watched_markets',
|
||||||
|
comment_on_contract_with_users_shares_in:
|
||||||
|
'all_comments_on_contracts_with_shares_in_on_watched_markets',
|
||||||
|
answer_on_contract_with_users_shares_in:
|
||||||
|
'all_answers_on_contracts_with_shares_in_on_watched_markets',
|
||||||
|
update_on_contract_with_users_shares_in:
|
||||||
|
'market_updates_with_shares_in_on_watched_markets',
|
||||||
|
resolution_on_contract_with_users_shares_in:
|
||||||
|
'resolutions_on_watched_markets_with_shares_in',
|
||||||
|
comment_on_contract_with_users_answer: 'all_comments_on_watched_markets',
|
||||||
|
update_on_contract_with_users_answer: 'market_updates_on_watched_markets',
|
||||||
|
resolution_on_contract_with_users_answer: 'resolutions_on_watched_markets',
|
||||||
|
answer_on_contract_with_users_answer: 'all_answers_on_watched_markets',
|
||||||
|
comment_on_contract_with_users_comment: 'all_comments_on_watched_markets',
|
||||||
|
answer_on_contract_with_users_comment: 'all_answers_on_watched_markets',
|
||||||
|
update_on_contract_with_users_comment: 'market_updates_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_comment: 'all_replies_to_my_comments_on_watched_markets',
|
||||||
|
your_contract_closed: 'my_markets_closed',
|
||||||
|
}
|
||||||
|
|
185
common/user.ts
185
common/user.ts
|
@ -1,3 +1,5 @@
|
||||||
|
import { filterDefined } from './util/array'
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: string
|
id: string
|
||||||
createdTime: number
|
createdTime: number
|
||||||
|
@ -67,44 +69,53 @@ export type PrivateUser = {
|
||||||
notificationSubscriptionTypes: exhaustive_notification_subscribe_types
|
notificationSubscriptionTypes: exhaustive_notification_subscribe_types
|
||||||
}
|
}
|
||||||
|
|
||||||
export type notification_receive_types = 'email' | 'browser'
|
export type notification_destination_types = 'email' | 'browser'
|
||||||
|
|
||||||
export type exhaustive_notification_subscribe_types = {
|
export type exhaustive_notification_subscribe_types = {
|
||||||
// Watched Markets
|
// Watched Markets
|
||||||
all_comments: notification_receive_types[] // Email currently - seems bad
|
all_comments_on_watched_markets: notification_destination_types[] // Email currently - seems bad
|
||||||
all_answers: notification_receive_types[] // Email currently - seems bad
|
all_answers_on_watched_markets: notification_destination_types[] // Email currently - seems bad
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
tipped_comments: notification_receive_types[] // Email
|
tipped_comments_on_watched_markets: notification_destination_types[] // Email
|
||||||
comments_by_followed_users: notification_receive_types[]
|
comments_by_followed_users_on_watched_markets: notification_destination_types[]
|
||||||
all_replies_to_my_comments: notification_receive_types[] // Email
|
all_replies_to_my_comments_on_watched_markets: notification_destination_types[] // Email
|
||||||
all_replies_to_my_answers: notification_receive_types[] // Email
|
all_replies_to_my_answers_on_watched_markets: notification_destination_types[] // Email
|
||||||
|
all_comments_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[]
|
||||||
|
|
||||||
// Answers
|
// Answers
|
||||||
answers_by_followed_users: notification_receive_types[]
|
answers_by_followed_users_on_watched_markets: notification_destination_types[]
|
||||||
answers_by_market_creator: notification_receive_types[]
|
answers_by_market_creator_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_receive_types[] // Email, Recommended
|
my_markets_closed: notification_destination_types[] // Email, Recommended
|
||||||
all_comments_on_my_markets: notification_receive_types[] // Email
|
all_comments_on_my_markets: notification_destination_types[] // Email
|
||||||
all_answers_on_my_markets: notification_receive_types[] // Email
|
all_answers_on_my_markets: notification_destination_types[] // Email
|
||||||
|
|
||||||
// Market updates
|
// Market updates
|
||||||
resolutions: notification_receive_types[] // Email
|
resolutions_on_watched_markets: notification_destination_types[] // Email
|
||||||
market_updates: notification_receive_types[]
|
resolutions_on_watched_markets_with_shares_in: notification_destination_types[] // Email
|
||||||
probability_updates: notification_receive_types[] // Email - would want persistent changes only though
|
market_updates_on_watched_markets: notification_destination_types[]
|
||||||
|
market_updates_with_shares_in_on_watched_markets: notification_destination_types[]
|
||||||
|
probability_updates_on_watched_markets: notification_destination_types[] // Email - would want persistent changes only though
|
||||||
|
|
||||||
// Balance Changes
|
// Balance Changes
|
||||||
loans: notification_receive_types[]
|
loan_income: notification_destination_types[]
|
||||||
betting_streaks: notification_receive_types[]
|
betting_streaks: notification_destination_types[]
|
||||||
referral_bonuses: notification_receive_types[]
|
referral_bonuses: notification_destination_types[]
|
||||||
unique_bettor_bonuses: notification_receive_types[]
|
unique_bettor_bonuses: notification_destination_types[]
|
||||||
|
tips_on_your_comments: notification_destination_types[]
|
||||||
|
tips_on_your_markets: notification_destination_types[]
|
||||||
|
limit_order_fills: notification_destination_types[]
|
||||||
|
|
||||||
// General
|
// General
|
||||||
user_tagged_you: notification_receive_types[] // Email
|
user_tagged_you: notification_destination_types[] // Email
|
||||||
new_markets_by_followed_users: notification_receive_types[] // Email
|
new_followers: notification_destination_types[] // Email
|
||||||
trending_markets: notification_receive_types[] // Email
|
group_adds: notification_destination_types[] // Email
|
||||||
profit_loss_updates: notification_receive_types[] // Email
|
new_markets_by_followed_users: notification_destination_types[] // Email
|
||||||
|
trending_markets: notification_destination_types[] // Email
|
||||||
|
profit_loss_updates: notification_destination_types[] // Email
|
||||||
}
|
}
|
||||||
export type notification_subscribe_types = 'all' | 'less' | 'none'
|
export type notification_subscribe_types = 'all' | 'less' | 'none'
|
||||||
|
|
||||||
|
@ -118,3 +129,131 @@ export type PortfolioMetrics = {
|
||||||
|
|
||||||
export const MANIFOLD_USERNAME = 'ManifoldMarkets'
|
export const MANIFOLD_USERNAME = 'ManifoldMarkets'
|
||||||
export const MANIFOLD_AVATAR_URL = 'https://manifold.markets/logo-bg-white.png'
|
export const MANIFOLD_AVATAR_URL = 'https://manifold.markets/logo-bg-white.png'
|
||||||
|
|
||||||
|
export const getDefaultNotificationSettings = (
|
||||||
|
userId: string,
|
||||||
|
privateUser?: PrivateUser,
|
||||||
|
noEmails?: boolean
|
||||||
|
) => {
|
||||||
|
const prevPref = privateUser?.notificationPreferences ?? 'all'
|
||||||
|
const wantsLess = prevPref === 'less'
|
||||||
|
const wantsAll = prevPref === 'all'
|
||||||
|
const {
|
||||||
|
unsubscribedFromCommentEmails,
|
||||||
|
unsubscribedFromAnswerEmails,
|
||||||
|
unsubscribedFromResolutionEmails,
|
||||||
|
unsubscribedFromWeeklyTrendingEmails,
|
||||||
|
} = privateUser || {}
|
||||||
|
|
||||||
|
const constructPref = (browserIf: boolean, emailIf: boolean) => {
|
||||||
|
const browser = browserIf ? 'browser' : undefined
|
||||||
|
const email = noEmails ? undefined : emailIf ? 'email' : undefined
|
||||||
|
return filterDefined([browser, email]) as notification_destination_types[]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// Watched Markets
|
||||||
|
all_comments_on_watched_markets: constructPref(
|
||||||
|
wantsAll,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
),
|
||||||
|
all_answers_on_watched_markets: constructPref(
|
||||||
|
wantsAll,
|
||||||
|
!unsubscribedFromAnswerEmails
|
||||||
|
),
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
tips_on_your_comments: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
),
|
||||||
|
comments_by_followed_users_on_watched_markets: constructPref(
|
||||||
|
wantsAll,
|
||||||
|
false
|
||||||
|
), //wantsAll ? browserOnly : none,
|
||||||
|
all_replies_to_my_comments_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
all_replies_to_my_answers_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref(
|
||||||
|
wantsAll,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
),
|
||||||
|
|
||||||
|
// Answers
|
||||||
|
answers_by_followed_users_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromAnswerEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
answers_by_market_creator_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromAnswerEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref(
|
||||||
|
wantsAll,
|
||||||
|
!unsubscribedFromAnswerEmails
|
||||||
|
),
|
||||||
|
|
||||||
|
// On users' markets
|
||||||
|
my_markets_closed: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromResolutionEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none, // High priority
|
||||||
|
all_comments_on_my_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
all_answers_on_my_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromAnswerEmails
|
||||||
|
), //wantsAll || wantsLess ? both : none,
|
||||||
|
|
||||||
|
// Market updates
|
||||||
|
resolutions_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromResolutionEmails
|
||||||
|
),
|
||||||
|
market_updates_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
market_updates_with_shares_in_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
resolutions_on_watched_markets_with_shares_in: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromResolutionEmails
|
||||||
|
),
|
||||||
|
|
||||||
|
//Balance Changes
|
||||||
|
loan_income: constructPref(wantsAll || wantsLess, false),
|
||||||
|
betting_streaks: constructPref(wantsAll || wantsLess, false),
|
||||||
|
referral_bonuses: constructPref(wantsAll || wantsLess, true),
|
||||||
|
unique_bettor_bonuses: constructPref(wantsAll || wantsLess, false),
|
||||||
|
tipped_comments_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
!unsubscribedFromCommentEmails
|
||||||
|
),
|
||||||
|
tips_on_your_markets: constructPref(wantsAll || wantsLess, true),
|
||||||
|
limit_order_fills: constructPref(wantsAll || wantsLess, false),
|
||||||
|
|
||||||
|
// General
|
||||||
|
user_tagged_you: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
||||||
|
new_followers: constructPref(wantsAll || wantsLess, true),
|
||||||
|
new_markets_by_followed_users: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
||||||
|
trending_markets: constructPref(
|
||||||
|
false,
|
||||||
|
!unsubscribedFromWeeklyTrendingEmails
|
||||||
|
),
|
||||||
|
profit_loss_updates: constructPref(false, true),
|
||||||
|
probability_updates_on_watched_markets: constructPref(
|
||||||
|
wantsAll || wantsLess,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
group_adds: constructPref(wantsAll || wantsLess, true),
|
||||||
|
} as exhaustive_notification_subscribe_types
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,30 @@
|
||||||
"functions": {
|
"functions": {
|
||||||
"predeploy": "cd functions && yarn build",
|
"predeploy": "cd functions && yarn build",
|
||||||
"runtime": "nodejs16",
|
"runtime": "nodejs16",
|
||||||
"source": "functions/dist"
|
"source": "functions/dist",
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
".git",
|
||||||
|
"firebase-debug.log",
|
||||||
|
"firebase-debug.*.log"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"firestore": {
|
"firestore": {
|
||||||
"rules": "firestore.rules",
|
"rules": "firestore.rules",
|
||||||
"indexes": "firestore.indexes.json"
|
"indexes": "firestore.indexes.json"
|
||||||
|
},
|
||||||
|
"emulators": {
|
||||||
|
"functions": {
|
||||||
|
"port": 5001
|
||||||
|
},
|
||||||
|
"firestore": {
|
||||||
|
"port": 8080
|
||||||
|
},
|
||||||
|
"pubsub": {
|
||||||
|
"port": 8085
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,7 @@ service cloud.firestore {
|
||||||
&& request.resource.data.diff(resource.data).affectedKeys()
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
.hasOnly(['isSeen', 'viewTime']);
|
.hasOnly(['isSeen', 'viewTime']);
|
||||||
}
|
}
|
||||||
|
|
||||||
match /{somePath=**}/groupMembers/{memberId} {
|
match /{somePath=**}/groupMembers/{memberId} {
|
||||||
allow read;
|
allow read;
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ service cloud.firestore {
|
||||||
allow read;
|
allow read;
|
||||||
}
|
}
|
||||||
|
|
||||||
match /groups/{groupId} {
|
match /groups/{groupId} {
|
||||||
allow read;
|
allow read;
|
||||||
allow update: if (request.auth.uid == resource.data.creatorId || isAdmin())
|
allow update: if (request.auth.uid == resource.data.creatorId || isAdmin())
|
||||||
&& request.resource.data.diff(resource.data)
|
&& request.resource.data.diff(resource.data)
|
||||||
|
@ -182,7 +182,7 @@ service cloud.firestore {
|
||||||
|
|
||||||
match /groupMembers/{memberId}{
|
match /groupMembers/{memberId}{
|
||||||
allow create: if request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId || (request.auth.uid == request.resource.data.userId && get(/databases/$(database)/documents/groups/$(groupId)).data.anyoneCanJoin);
|
allow create: if request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId || (request.auth.uid == request.resource.data.userId && get(/databases/$(database)/documents/groups/$(groupId)).data.anyoneCanJoin);
|
||||||
allow delete: if request.auth.uid == resource.data.userId;
|
allow delete: if request.auth.uid == resource.data.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGroupMember() {
|
function isGroupMember() {
|
||||||
|
|
1
functions/.gitignore
vendored
1
functions/.gitignore
vendored
|
@ -17,4 +17,5 @@ package-lock.json
|
||||||
ui-debug.log
|
ui-debug.log
|
||||||
firebase-debug.log
|
firebase-debug.log
|
||||||
firestore-debug.log
|
firestore-debug.log
|
||||||
|
pubsub-debug.log
|
||||||
firestore_export/
|
firestore_export/
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { getNewMultiBetInfo } from '../../common/new-bet'
|
import { getNewMultiBetInfo } from '../../common/new-bet'
|
||||||
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
||||||
import { getContract, getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { sendNewAnswerEmail } from './emails'
|
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
|
@ -97,10 +96,6 @@ export const createanswer = newEndpoint(opts, async (req, auth) => {
|
||||||
return answer
|
return answer
|
||||||
})
|
})
|
||||||
|
|
||||||
const contract = await getContract(contractId)
|
|
||||||
|
|
||||||
if (answer && contract) await sendNewAnswerEmail(answer, contract)
|
|
||||||
|
|
||||||
return answer
|
return answer
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
MAX_GROUP_NAME_LENGTH,
|
MAX_GROUP_NAME_LENGTH,
|
||||||
MAX_ID_LENGTH,
|
MAX_ID_LENGTH,
|
||||||
} from '../../common/group'
|
} from '../../common/group'
|
||||||
import { APIError, newEndpoint, validate } from '../../functions/src/api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
|
|
|
@ -4,10 +4,11 @@ import {
|
||||||
notification_reason_types,
|
notification_reason_types,
|
||||||
notification_source_update_types,
|
notification_source_update_types,
|
||||||
notification_source_types,
|
notification_source_types,
|
||||||
|
notificationReasonToSubscribeTypeMap,
|
||||||
} from '../../common/notification'
|
} from '../../common/notification'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { getValues, log } from './utils'
|
import { getPrivateUser, getValues } from './utils'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from '../../common/comment'
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { Bet, LimitBet } from '../../common/bet'
|
import { Bet, LimitBet } from '../../common/bet'
|
||||||
|
@ -15,13 +16,17 @@ import { Answer } from '../../common/answer'
|
||||||
import { getContractBetMetrics } from '../../common/calculate'
|
import { getContractBetMetrics } from '../../common/calculate'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from '../../common/util/object'
|
||||||
import { TipTxn } from '../../common/txn'
|
import { TipTxn } from '../../common/txn'
|
||||||
import { Group, GROUP_CHAT_SLUG } from '../../common/group'
|
import { Group } from '../../common/group'
|
||||||
import { Challenge } from '../../common/challenge'
|
import { Challenge } from '../../common/challenge'
|
||||||
import { richTextToString } from '../../common/util/parse'
|
|
||||||
import { Like } from '../../common/like'
|
import { Like } from '../../common/like'
|
||||||
|
import {
|
||||||
|
sendMarketResolutionEmail,
|
||||||
|
sendNewAnswerEmail,
|
||||||
|
sendNewCommentEmail,
|
||||||
|
} from './emails'
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
type user_to_reason_texts = {
|
type recipients_to_reason_texts = {
|
||||||
[userId: string]: { reason: notification_reason_types }
|
[userId: string]: { reason: notification_reason_types }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +48,7 @@ export const createNotification = async (
|
||||||
|
|
||||||
const shouldGetNotification = (
|
const shouldGetNotification = (
|
||||||
userId: string,
|
userId: string,
|
||||||
userToReasonTexts: user_to_reason_texts
|
userToReasonTexts: recipients_to_reason_texts
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
sourceUser.id != userId &&
|
sourceUser.id != userId &&
|
||||||
|
@ -52,17 +57,21 @@ export const createNotification = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUsersNotifications = async (
|
const createUsersNotifications = async (
|
||||||
userToReasonTexts: user_to_reason_texts
|
userToReasonTexts: recipients_to_reason_texts
|
||||||
) => {
|
) => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.keys(userToReasonTexts).map(async (userId) => {
|
Object.keys(userToReasonTexts).map(async (userId) => {
|
||||||
|
const { reason } = userToReasonTexts[userId]
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(userId, reason)
|
||||||
|
if (!sendToBrowser) return Promise.resolve()
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${userId}/notifications`)
|
.collection(`/users/${userId}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
const notification: Notification = {
|
const notification: Notification = {
|
||||||
id: idempotencyKey,
|
id: idempotencyKey,
|
||||||
userId,
|
userId,
|
||||||
reason: userToReasonTexts[userId].reason,
|
reason,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
isSeen: false,
|
isSeen: false,
|
||||||
sourceId,
|
sourceId,
|
||||||
|
@ -85,7 +94,7 @@ export const createNotification = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyUsersFollowers = async (
|
const notifyUsersFollowers = async (
|
||||||
userToReasonTexts: user_to_reason_texts
|
userToReasonTexts: recipients_to_reason_texts
|
||||||
) => {
|
) => {
|
||||||
const followers = await firestore
|
const followers = await firestore
|
||||||
.collectionGroup('follows')
|
.collectionGroup('follows')
|
||||||
|
@ -99,14 +108,14 @@ export const createNotification = async (
|
||||||
shouldGetNotification(followerUserId, userToReasonTexts)
|
shouldGetNotification(followerUserId, userToReasonTexts)
|
||||||
) {
|
) {
|
||||||
userToReasonTexts[followerUserId] = {
|
userToReasonTexts[followerUserId] = {
|
||||||
reason: 'you_follow_user',
|
reason: 'contract_from_followed_user',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyFollowedUser = (
|
const notifyFollowedUser = (
|
||||||
userToReasonTexts: user_to_reason_texts,
|
userToReasonTexts: recipients_to_reason_texts,
|
||||||
followedUserId: string
|
followedUserId: string
|
||||||
) => {
|
) => {
|
||||||
if (shouldGetNotification(followedUserId, userToReasonTexts))
|
if (shouldGetNotification(followedUserId, userToReasonTexts))
|
||||||
|
@ -116,7 +125,7 @@ export const createNotification = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyTaggedUsers = (
|
const notifyTaggedUsers = (
|
||||||
userToReasonTexts: user_to_reason_texts,
|
userToReasonTexts: recipients_to_reason_texts,
|
||||||
userIds: (string | undefined)[]
|
userIds: (string | undefined)[]
|
||||||
) => {
|
) => {
|
||||||
userIds.forEach((id) => {
|
userIds.forEach((id) => {
|
||||||
|
@ -128,7 +137,7 @@ export const createNotification = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyContractCreator = async (
|
const notifyContractCreator = async (
|
||||||
userToReasonTexts: user_to_reason_texts,
|
userToReasonTexts: recipients_to_reason_texts,
|
||||||
sourceContract: Contract,
|
sourceContract: Contract,
|
||||||
options?: { force: boolean }
|
options?: { force: boolean }
|
||||||
) => {
|
) => {
|
||||||
|
@ -137,12 +146,12 @@ export const createNotification = async (
|
||||||
shouldGetNotification(sourceContract.creatorId, userToReasonTexts)
|
shouldGetNotification(sourceContract.creatorId, userToReasonTexts)
|
||||||
)
|
)
|
||||||
userToReasonTexts[sourceContract.creatorId] = {
|
userToReasonTexts[sourceContract.creatorId] = {
|
||||||
reason: 'on_users_contract',
|
reason: 'your_contract_closed',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyUserAddedToGroup = (
|
const notifyUserAddedToGroup = (
|
||||||
userToReasonTexts: user_to_reason_texts,
|
userToReasonTexts: recipients_to_reason_texts,
|
||||||
relatedUserId: string
|
relatedUserId: string
|
||||||
) => {
|
) => {
|
||||||
if (shouldGetNotification(relatedUserId, userToReasonTexts))
|
if (shouldGetNotification(relatedUserId, userToReasonTexts))
|
||||||
|
@ -151,7 +160,7 @@ export const createNotification = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToReasonTexts: user_to_reason_texts = {}
|
const userToReasonTexts: recipients_to_reason_texts = {}
|
||||||
// The following functions modify the userToReasonTexts object in place.
|
// The following functions modify the userToReasonTexts object in place.
|
||||||
|
|
||||||
if (sourceType === 'follow' && recipients?.[0]) {
|
if (sourceType === 'follow' && recipients?.[0]) {
|
||||||
|
@ -188,54 +197,63 @@ export const createNotification = async (
|
||||||
await createUsersNotifications(userToReasonTexts)
|
await createUsersNotifications(userToReasonTexts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDestinationsForUser = async (
|
||||||
|
userId: string,
|
||||||
|
reason: notification_reason_types
|
||||||
|
) => {
|
||||||
|
const privateUser = await getPrivateUser(userId)
|
||||||
|
if (!privateUser) return { sendToEmail: false, sendToBrowser: false }
|
||||||
|
const notificationSettings = privateUser.notificationSubscriptionTypes
|
||||||
|
console.log('notificationSettings', notificationSettings)
|
||||||
|
console.log('reason', reason)
|
||||||
|
console.log('notif reason to type map', notificationReasonToSubscribeTypeMap)
|
||||||
|
const subscribeType = notificationReasonToSubscribeTypeMap[reason]
|
||||||
|
console.log('subscribeType', subscribeType)
|
||||||
|
const destinations =
|
||||||
|
notificationSettings[notificationReasonToSubscribeTypeMap[reason]]
|
||||||
|
console.log('destinations', destinations)
|
||||||
|
return {
|
||||||
|
sendToEmail: destinations.includes('email'),
|
||||||
|
sendToBrowser: destinations.includes('browser'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
sourceType: notification_source_types,
|
sourceType: 'comment' | 'answer' | 'contract',
|
||||||
sourceUpdateType: notification_source_update_types,
|
sourceUpdateType: 'created' | 'updated' | 'resolved',
|
||||||
sourceUser: User,
|
sourceUser: User,
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
sourceText: string,
|
sourceText: string,
|
||||||
sourceContract: Contract,
|
sourceContract: Contract,
|
||||||
miscData?: {
|
miscData?: {
|
||||||
relatedSourceType?: notification_source_types
|
repliedToType?: 'comment' | 'answer'
|
||||||
|
repliedToId?: string
|
||||||
|
repliedToContent?: string
|
||||||
repliedUserId?: string
|
repliedUserId?: string
|
||||||
taggedUserIds?: string[]
|
taggedUserIds?: string[]
|
||||||
|
},
|
||||||
|
resolutionData?: {
|
||||||
|
bets: Bet[]
|
||||||
|
userInvestments: { [userId: string]: number }
|
||||||
|
userPayouts: { [userId: string]: number }
|
||||||
|
creator: User
|
||||||
|
creatorPayout: number
|
||||||
|
contract: Contract
|
||||||
|
outcome: string
|
||||||
|
resolutionProbability?: number
|
||||||
|
resolutions?: { [outcome: string]: number }
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const { relatedSourceType, repliedUserId, taggedUserIds } = miscData ?? {}
|
const {
|
||||||
|
repliedToType,
|
||||||
|
repliedToContent,
|
||||||
|
repliedUserId,
|
||||||
|
taggedUserIds,
|
||||||
|
repliedToId,
|
||||||
|
} = miscData ?? {}
|
||||||
|
|
||||||
const createUsersNotifications = async (
|
const recipientIdsList: string[] = []
|
||||||
userToReasonTexts: user_to_reason_texts
|
|
||||||
) => {
|
|
||||||
await Promise.all(
|
|
||||||
Object.keys(userToReasonTexts).map(async (userId) => {
|
|
||||||
const notificationRef = firestore
|
|
||||||
.collection(`/users/${userId}/notifications`)
|
|
||||||
.doc(idempotencyKey)
|
|
||||||
const notification: Notification = {
|
|
||||||
id: idempotencyKey,
|
|
||||||
userId,
|
|
||||||
reason: userToReasonTexts[userId].reason,
|
|
||||||
createdTime: Date.now(),
|
|
||||||
isSeen: false,
|
|
||||||
sourceId,
|
|
||||||
sourceType,
|
|
||||||
sourceUpdateType,
|
|
||||||
sourceContractId: sourceContract.id,
|
|
||||||
sourceUserName: sourceUser.name,
|
|
||||||
sourceUserUsername: sourceUser.username,
|
|
||||||
sourceUserAvatarUrl: sourceUser.avatarUrl,
|
|
||||||
sourceText,
|
|
||||||
sourceContractCreatorUsername: sourceContract.creatorUsername,
|
|
||||||
sourceContractTitle: sourceContract.question,
|
|
||||||
sourceContractSlug: sourceContract.slug,
|
|
||||||
sourceSlug: sourceContract.slug,
|
|
||||||
sourceTitle: sourceContract.question,
|
|
||||||
}
|
|
||||||
await notificationRef.set(removeUndefinedProps(notification))
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get contract follower documents and check here if they're a follower
|
// get contract follower documents and check here if they're a follower
|
||||||
const contractFollowersSnap = await firestore
|
const contractFollowersSnap = await firestore
|
||||||
|
@ -244,48 +262,128 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
||||||
const contractFollowersIds = contractFollowersSnap.docs.map(
|
const contractFollowersIds = contractFollowersSnap.docs.map(
|
||||||
(doc) => doc.data().id
|
(doc) => doc.data().id
|
||||||
)
|
)
|
||||||
log('contractFollowerIds', contractFollowersIds)
|
|
||||||
|
const createBrowserNotification = async (
|
||||||
|
userId: string,
|
||||||
|
reason: notification_reason_types
|
||||||
|
) => {
|
||||||
|
const notificationRef = firestore
|
||||||
|
.collection(`/users/${userId}/notifications`)
|
||||||
|
.doc(idempotencyKey)
|
||||||
|
const notification: Notification = {
|
||||||
|
id: idempotencyKey,
|
||||||
|
userId,
|
||||||
|
reason,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
isSeen: false,
|
||||||
|
sourceId,
|
||||||
|
sourceType,
|
||||||
|
sourceUpdateType,
|
||||||
|
sourceContractId: sourceContract.id,
|
||||||
|
sourceUserName: sourceUser.name,
|
||||||
|
sourceUserUsername: sourceUser.username,
|
||||||
|
sourceUserAvatarUrl: sourceUser.avatarUrl,
|
||||||
|
sourceText,
|
||||||
|
sourceContractCreatorUsername: sourceContract.creatorUsername,
|
||||||
|
sourceContractTitle: sourceContract.question,
|
||||||
|
sourceContractSlug: sourceContract.slug,
|
||||||
|
sourceSlug: sourceContract.slug,
|
||||||
|
sourceTitle: sourceContract.question,
|
||||||
|
}
|
||||||
|
return await notificationRef.set(removeUndefinedProps(notification))
|
||||||
|
}
|
||||||
|
|
||||||
const stillFollowingContract = (userId: string) => {
|
const stillFollowingContract = (userId: string) => {
|
||||||
return contractFollowersIds.includes(userId)
|
return contractFollowersIds.includes(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldGetNotification = (
|
const sendNotificationsIfSettingsPermit = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
userToReasonTexts: user_to_reason_texts
|
reason: notification_reason_types
|
||||||
) => {
|
) => {
|
||||||
return (
|
if (
|
||||||
sourceUser.id != userId &&
|
!stillFollowingContract(sourceContract.creatorId) ||
|
||||||
!Object.keys(userToReasonTexts).includes(userId)
|
sourceUser.id == userId ||
|
||||||
|
recipientIdsList.includes(userId)
|
||||||
)
|
)
|
||||||
}
|
return
|
||||||
|
|
||||||
const notifyContractFollowers = async (
|
const { sendToBrowser, sendToEmail } = await getDestinationsForUser(
|
||||||
userToReasonTexts: user_to_reason_texts
|
userId,
|
||||||
) => {
|
reason
|
||||||
for (const userId of contractFollowersIds) {
|
)
|
||||||
if (shouldGetNotification(userId, userToReasonTexts))
|
|
||||||
userToReasonTexts[userId] = {
|
if (sendToBrowser) {
|
||||||
reason: 'you_follow_contract',
|
await createBrowserNotification(userId, reason)
|
||||||
}
|
recipientIdsList.push(userId)
|
||||||
|
}
|
||||||
|
if (sendToEmail) {
|
||||||
|
if (sourceType === 'comment') {
|
||||||
|
// if the source contract is a free response contract, send the email
|
||||||
|
await sendNewCommentEmail(
|
||||||
|
userId,
|
||||||
|
sourceUser,
|
||||||
|
sourceContract,
|
||||||
|
sourceText,
|
||||||
|
sourceId,
|
||||||
|
// TODO: Add any paired bets to the comment
|
||||||
|
undefined,
|
||||||
|
repliedToType === 'answer' ? repliedToContent : undefined,
|
||||||
|
repliedToType === 'answer' ? repliedToId : undefined
|
||||||
|
)
|
||||||
|
} else if (sourceType === 'answer')
|
||||||
|
await sendNewAnswerEmail(
|
||||||
|
userId,
|
||||||
|
sourceUser.name,
|
||||||
|
sourceText,
|
||||||
|
sourceContract,
|
||||||
|
sourceUser.avatarUrl
|
||||||
|
)
|
||||||
|
else if (
|
||||||
|
sourceType === 'contract' &&
|
||||||
|
sourceUpdateType === 'resolved' &&
|
||||||
|
resolutionData
|
||||||
|
)
|
||||||
|
await sendMarketResolutionEmail(
|
||||||
|
userId,
|
||||||
|
resolutionData.userInvestments[userId],
|
||||||
|
resolutionData.userPayouts[userId],
|
||||||
|
sourceUser,
|
||||||
|
resolutionData.creatorPayout,
|
||||||
|
sourceContract,
|
||||||
|
resolutionData.outcome,
|
||||||
|
resolutionData.resolutionProbability,
|
||||||
|
resolutionData.resolutions
|
||||||
|
)
|
||||||
|
recipientIdsList.push(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyContractCreator = async (
|
const notifyContractFollowers = async () => {
|
||||||
userToReasonTexts: user_to_reason_texts
|
for (const userId of contractFollowersIds) {
|
||||||
) => {
|
await sendNotificationsIfSettingsPermit(
|
||||||
if (
|
userId,
|
||||||
shouldGetNotification(sourceContract.creatorId, userToReasonTexts) &&
|
sourceType === 'answer'
|
||||||
stillFollowingContract(sourceContract.creatorId)
|
? 'answer_on_contract_you_follow'
|
||||||
)
|
: sourceType === 'comment'
|
||||||
userToReasonTexts[sourceContract.creatorId] = {
|
? 'comment_on_contract_you_follow'
|
||||||
reason: 'on_users_contract',
|
: sourceUpdateType === 'updated'
|
||||||
}
|
? 'update_on_contract_you_follow'
|
||||||
|
: 'resolution_on_contract_you_follow'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyOtherAnswerersOnContract = async (
|
const notifyContractCreator = async () => {
|
||||||
userToReasonTexts: user_to_reason_texts
|
await sendNotificationsIfSettingsPermit(
|
||||||
) => {
|
sourceContract.creatorId,
|
||||||
|
sourceType === 'comment'
|
||||||
|
? 'comment_on_your_contract'
|
||||||
|
: 'answer_on_your_contract'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyOtherAnswerersOnContract = async () => {
|
||||||
const answers = await getValues<Answer>(
|
const answers = await getValues<Answer>(
|
||||||
firestore
|
firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
|
@ -293,20 +391,23 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
||||||
.collection('answers')
|
.collection('answers')
|
||||||
)
|
)
|
||||||
const recipientUserIds = uniq(answers.map((answer) => answer.userId))
|
const recipientUserIds = uniq(answers.map((answer) => answer.userId))
|
||||||
recipientUserIds.forEach((userId) => {
|
await Promise.all(
|
||||||
if (
|
recipientUserIds.map((userId) =>
|
||||||
shouldGetNotification(userId, userToReasonTexts) &&
|
sendNotificationsIfSettingsPermit(
|
||||||
stillFollowingContract(userId)
|
userId,
|
||||||
|
sourceType === 'answer'
|
||||||
|
? 'answer_on_contract_with_users_answer'
|
||||||
|
: sourceType === 'comment'
|
||||||
|
? 'comment_on_contract_with_users_answer'
|
||||||
|
: sourceUpdateType === 'updated'
|
||||||
|
? 'update_on_contract_with_users_answer'
|
||||||
|
: 'resolution_on_contract_with_users_answer'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
userToReasonTexts[userId] = {
|
)
|
||||||
reason: 'on_contract_with_users_answer',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyOtherCommentersOnContract = async (
|
const notifyOtherCommentersOnContract = async () => {
|
||||||
userToReasonTexts: user_to_reason_texts
|
|
||||||
) => {
|
|
||||||
const comments = await getValues<Comment>(
|
const comments = await getValues<Comment>(
|
||||||
firestore
|
firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
|
@ -314,20 +415,23 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
||||||
.collection('comments')
|
.collection('comments')
|
||||||
)
|
)
|
||||||
const recipientUserIds = uniq(comments.map((comment) => comment.userId))
|
const recipientUserIds = uniq(comments.map((comment) => comment.userId))
|
||||||
recipientUserIds.forEach((userId) => {
|
await Promise.all(
|
||||||
if (
|
recipientUserIds.map((userId) =>
|
||||||
shouldGetNotification(userId, userToReasonTexts) &&
|
sendNotificationsIfSettingsPermit(
|
||||||
stillFollowingContract(userId)
|
userId,
|
||||||
|
sourceType === 'answer'
|
||||||
|
? 'answer_on_contract_with_users_comment'
|
||||||
|
: sourceType === 'comment'
|
||||||
|
? 'comment_on_contract_with_users_comment'
|
||||||
|
: sourceUpdateType === 'updated'
|
||||||
|
? 'update_on_contract_with_users_comment'
|
||||||
|
: 'resolution_on_contract_with_users_comment'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
userToReasonTexts[userId] = {
|
)
|
||||||
reason: 'on_contract_with_users_comment',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyBettorsOnContract = async (
|
const notifyBettorsOnContract = async () => {
|
||||||
userToReasonTexts: user_to_reason_texts
|
|
||||||
) => {
|
|
||||||
const betsSnap = await firestore
|
const betsSnap = await firestore
|
||||||
.collection(`contracts/${sourceContract.id}/bets`)
|
.collection(`contracts/${sourceContract.id}/bets`)
|
||||||
.get()
|
.get()
|
||||||
|
@ -343,88 +447,77 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
recipientUserIds.forEach((userId) => {
|
await Promise.all(
|
||||||
if (
|
recipientUserIds.map((userId) =>
|
||||||
shouldGetNotification(userId, userToReasonTexts) &&
|
sendNotificationsIfSettingsPermit(
|
||||||
stillFollowingContract(userId)
|
userId,
|
||||||
|
sourceType === 'answer'
|
||||||
|
? 'answer_on_contract_with_users_shares_in'
|
||||||
|
: sourceType === 'comment'
|
||||||
|
? 'comment_on_contract_with_users_shares_in'
|
||||||
|
: sourceUpdateType === 'updated'
|
||||||
|
? 'update_on_contract_with_users_shares_in'
|
||||||
|
: 'resolution_on_contract_with_users_shares_in'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
userToReasonTexts[userId] = {
|
)
|
||||||
reason: 'on_contract_with_users_shares_in',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyRepliedUser = (
|
const notifyRepliedUser = async (
|
||||||
userToReasonTexts: user_to_reason_texts,
|
|
||||||
relatedUserId: string,
|
relatedUserId: string,
|
||||||
relatedSourceType: notification_source_types
|
relatedSourceType: notification_source_types
|
||||||
) => {
|
) => {
|
||||||
if (
|
await sendNotificationsIfSettingsPermit(
|
||||||
shouldGetNotification(relatedUserId, userToReasonTexts) &&
|
relatedUserId,
|
||||||
stillFollowingContract(relatedUserId)
|
relatedSourceType === 'answer'
|
||||||
) {
|
? 'reply_to_users_answer'
|
||||||
if (relatedSourceType === 'comment') {
|
: 'reply_to_users_comment'
|
||||||
userToReasonTexts[relatedUserId] = {
|
)
|
||||||
reason: 'reply_to_users_comment',
|
|
||||||
}
|
|
||||||
} else if (relatedSourceType === 'answer') {
|
|
||||||
userToReasonTexts[relatedUserId] = {
|
|
||||||
reason: 'reply_to_users_answer',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyTaggedUsers = (
|
const notifyTaggedUsers = async (userIds: string[]) => {
|
||||||
userToReasonTexts: user_to_reason_texts,
|
await Promise.all(
|
||||||
userIds: (string | undefined)[]
|
userIds.map((userId) =>
|
||||||
) => {
|
sendNotificationsIfSettingsPermit(userId, 'tagged_user')
|
||||||
userIds.forEach((id) => {
|
)
|
||||||
console.log('tagged user: ', id)
|
)
|
||||||
// Allowing non-following users to get tagged
|
|
||||||
if (id && shouldGetNotification(id, userToReasonTexts))
|
|
||||||
userToReasonTexts[id] = {
|
|
||||||
reason: 'tagged_user',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyLiquidityProviders = async (
|
const notifyLiquidityProviders = async () => {
|
||||||
userToReasonTexts: user_to_reason_texts
|
|
||||||
) => {
|
|
||||||
const liquidityProviders = await firestore
|
const liquidityProviders = await firestore
|
||||||
.collection(`contracts/${sourceContract.id}/liquidity`)
|
.collection(`contracts/${sourceContract.id}/liquidity`)
|
||||||
.get()
|
.get()
|
||||||
const liquidityProvidersIds = uniq(
|
const liquidityProvidersIds = uniq(
|
||||||
liquidityProviders.docs.map((doc) => doc.data().userId)
|
liquidityProviders.docs.map((doc) => doc.data().userId)
|
||||||
)
|
)
|
||||||
liquidityProvidersIds.forEach((userId) => {
|
await Promise.all(
|
||||||
if (
|
liquidityProvidersIds.map((userId) =>
|
||||||
shouldGetNotification(userId, userToReasonTexts) &&
|
sendNotificationsIfSettingsPermit(
|
||||||
stillFollowingContract(userId)
|
userId,
|
||||||
) {
|
sourceType === 'answer'
|
||||||
userToReasonTexts[userId] = {
|
? 'answer_on_contract_with_users_shares_in'
|
||||||
reason: 'on_contract_with_users_shares_in',
|
: sourceType === 'comment'
|
||||||
}
|
? 'comment_on_contract_with_users_shares_in'
|
||||||
}
|
: sourceUpdateType === 'updated'
|
||||||
})
|
? 'update_on_contract_with_users_shares_in'
|
||||||
|
: 'resolution_on_contract_with_users_shares_in'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const userToReasonTexts: user_to_reason_texts = {}
|
|
||||||
|
|
||||||
if (sourceType === 'comment') {
|
if (sourceType === 'comment') {
|
||||||
if (repliedUserId && relatedSourceType)
|
if (repliedUserId && repliedToType)
|
||||||
notifyRepliedUser(userToReasonTexts, repliedUserId, relatedSourceType)
|
await notifyRepliedUser(repliedUserId, repliedToType)
|
||||||
if (sourceText) notifyTaggedUsers(userToReasonTexts, taggedUserIds ?? [])
|
await notifyTaggedUsers(taggedUserIds ?? [])
|
||||||
}
|
}
|
||||||
await notifyContractCreator(userToReasonTexts)
|
await notifyContractCreator()
|
||||||
await notifyOtherAnswerersOnContract(userToReasonTexts)
|
await notifyOtherAnswerersOnContract()
|
||||||
await notifyLiquidityProviders(userToReasonTexts)
|
await notifyLiquidityProviders()
|
||||||
await notifyBettorsOnContract(userToReasonTexts)
|
await notifyBettorsOnContract()
|
||||||
await notifyOtherCommentersOnContract(userToReasonTexts)
|
await notifyOtherCommentersOnContract()
|
||||||
// if they weren't added previously, add them now
|
// if they weren't notified previously, notify them now
|
||||||
await notifyContractFollowers(userToReasonTexts)
|
await notifyContractFollowers()
|
||||||
|
|
||||||
await createUsersNotifications(userToReasonTexts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTipNotification = async (
|
export const createTipNotification = async (
|
||||||
|
@ -436,8 +529,13 @@ export const createTipNotification = async (
|
||||||
contract?: Contract,
|
contract?: Contract,
|
||||||
group?: Group
|
group?: Group
|
||||||
) => {
|
) => {
|
||||||
const slug = group ? group.slug + `#${commentId}` : commentId
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
toUser.id,
|
||||||
|
'tip_received'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
|
const slug = group ? group.slug + `#${commentId}` : commentId
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${toUser.id}/notifications`)
|
.collection(`/users/${toUser.id}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
@ -471,6 +569,9 @@ export const createBetFillNotification = async (
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
idempotencyKey: string
|
idempotencyKey: string
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(toUser.id, 'bet_fill')
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id)
|
const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id)
|
||||||
const fillAmount = fill?.amount ?? 0
|
const fillAmount = fill?.amount ?? 0
|
||||||
|
|
||||||
|
@ -498,38 +599,6 @@ export const createBetFillNotification = async (
|
||||||
return await notificationRef.set(removeUndefinedProps(notification))
|
return await notificationRef.set(removeUndefinedProps(notification))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createGroupCommentNotification = async (
|
|
||||||
fromUser: User,
|
|
||||||
toUserId: string,
|
|
||||||
comment: Comment,
|
|
||||||
group: Group,
|
|
||||||
idempotencyKey: string
|
|
||||||
) => {
|
|
||||||
if (toUserId === fromUser.id) return
|
|
||||||
const notificationRef = firestore
|
|
||||||
.collection(`/users/${toUserId}/notifications`)
|
|
||||||
.doc(idempotencyKey)
|
|
||||||
const sourceSlug = `/group/${group.slug}/${GROUP_CHAT_SLUG}`
|
|
||||||
const notification: Notification = {
|
|
||||||
id: idempotencyKey,
|
|
||||||
userId: toUserId,
|
|
||||||
reason: 'on_group_you_are_member_of',
|
|
||||||
createdTime: Date.now(),
|
|
||||||
isSeen: false,
|
|
||||||
sourceId: comment.id,
|
|
||||||
sourceType: 'comment',
|
|
||||||
sourceUpdateType: 'created',
|
|
||||||
sourceUserName: fromUser.name,
|
|
||||||
sourceUserUsername: fromUser.username,
|
|
||||||
sourceUserAvatarUrl: fromUser.avatarUrl,
|
|
||||||
sourceText: richTextToString(comment.content),
|
|
||||||
sourceSlug,
|
|
||||||
sourceTitle: `${group.name}`,
|
|
||||||
isSeenOnHref: sourceSlug,
|
|
||||||
}
|
|
||||||
await notificationRef.set(removeUndefinedProps(notification))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createReferralNotification = async (
|
export const createReferralNotification = async (
|
||||||
toUser: User,
|
toUser: User,
|
||||||
referredUser: User,
|
referredUser: User,
|
||||||
|
@ -538,6 +607,12 @@ export const createReferralNotification = async (
|
||||||
referredByContract?: Contract,
|
referredByContract?: Contract,
|
||||||
referredByGroup?: Group
|
referredByGroup?: Group
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
toUser.id,
|
||||||
|
'you_referred_user'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${toUser.id}/notifications`)
|
.collection(`/users/${toUser.id}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
@ -582,6 +657,12 @@ export const createLoanIncomeNotification = async (
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
income: number
|
income: number
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
toUser.id,
|
||||||
|
'loan_income'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${toUser.id}/notifications`)
|
.collection(`/users/${toUser.id}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
@ -612,6 +693,12 @@ export const createChallengeAcceptedNotification = async (
|
||||||
acceptedAmount: number,
|
acceptedAmount: number,
|
||||||
contract: Contract
|
contract: Contract
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
challengeCreator.id,
|
||||||
|
'challenge_accepted'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${challengeCreator.id}/notifications`)
|
.collection(`/users/${challengeCreator.id}/notifications`)
|
||||||
.doc()
|
.doc()
|
||||||
|
@ -645,6 +732,12 @@ export const createBettingStreakBonusNotification = async (
|
||||||
amount: number,
|
amount: number,
|
||||||
idempotencyKey: string
|
idempotencyKey: string
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
user.id,
|
||||||
|
'betting_streak_incremented'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${user.id}/notifications`)
|
.collection(`/users/${user.id}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
@ -680,6 +773,12 @@ export const createLikeNotification = async (
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
tip?: TipTxn
|
tip?: TipTxn
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
toUser.id,
|
||||||
|
'liked_and_tipped_your_contract'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${toUser.id}/notifications`)
|
.collection(`/users/${toUser.id}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
@ -727,6 +826,12 @@ export const createUniqueBettorBonusNotification = async (
|
||||||
amount: number,
|
amount: number,
|
||||||
idempotencyKey: string
|
idempotencyKey: string
|
||||||
) => {
|
) => {
|
||||||
|
const { sendToBrowser } = await getDestinationsForUser(
|
||||||
|
contractCreatorId,
|
||||||
|
'unique_bettors_on_your_contract'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
const notificationRef = firestore
|
const notificationRef = firestore
|
||||||
.collection(`/users/${contractCreatorId}/notifications`)
|
.collection(`/users/${contractCreatorId}/notifications`)
|
||||||
.doc(idempotencyKey)
|
.doc(idempotencyKey)
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { PrivateUser, User } from '../../common/user'
|
import {
|
||||||
|
getDefaultNotificationSettings,
|
||||||
|
PrivateUser,
|
||||||
|
User,
|
||||||
|
} from '../../common/user'
|
||||||
import { getUser, getUserByUsername, getValues } from './utils'
|
import { getUser, getUserByUsername, getValues } from './utils'
|
||||||
import { randomString } from '../../common/util/random'
|
import { randomString } from '../../common/util/random'
|
||||||
import {
|
import {
|
||||||
|
@ -79,6 +83,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => {
|
||||||
email,
|
email,
|
||||||
initialIpAddress: req.ip,
|
initialIpAddress: req.ip,
|
||||||
initialDeviceToken: deviceToken,
|
initialDeviceToken: deviceToken,
|
||||||
|
notificationSubscriptionTypes: getDefaultNotificationSettings(auth.uid),
|
||||||
}
|
}
|
||||||
|
|
||||||
await firestore.collection('private-users').doc(auth.uid).create(privateUser)
|
await firestore.collection('private-users').doc(auth.uid).create(privateUser)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { DOMAIN } from '../../common/envs/constants'
|
import { DOMAIN } from '../../common/envs/constants'
|
||||||
import { Answer } from '../../common/answer'
|
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from '../../common/calculate'
|
||||||
import { Comment } from '../../common/comment'
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { PrivateUser, User } from '../../common/user'
|
import { PrivateUser, User } from '../../common/user'
|
||||||
import {
|
import {
|
||||||
|
@ -18,6 +16,7 @@ import { getPrivateUser, getUser } from './utils'
|
||||||
import { getFunctionUrl } from '../../common/api'
|
import { getFunctionUrl } from '../../common/api'
|
||||||
import { richTextToString } from '../../common/util/parse'
|
import { richTextToString } from '../../common/util/parse'
|
||||||
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
|
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
|
||||||
|
import { JSONContent } from '@tiptap/core'
|
||||||
|
|
||||||
const UNSUBSCRIBE_ENDPOINT = getFunctionUrl('unsubscribe')
|
const UNSUBSCRIBE_ENDPOINT = getFunctionUrl('unsubscribe')
|
||||||
|
|
||||||
|
@ -346,7 +345,8 @@ export const sendNewCommentEmail = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
commentCreator: User,
|
commentCreator: User,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
comment: Comment,
|
commentContent: JSONContent | string,
|
||||||
|
commentId: string,
|
||||||
bet?: Bet,
|
bet?: Bet,
|
||||||
answerText?: string,
|
answerText?: string,
|
||||||
answerId?: string
|
answerId?: string
|
||||||
|
@ -359,14 +359,17 @@ export const sendNewCommentEmail = async (
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
const { question, creatorUsername, slug } = contract
|
const { question } = contract
|
||||||
const marketUrl = `https://${DOMAIN}/${creatorUsername}/${slug}#${comment.id}`
|
const marketUrl = `https://${DOMAIN}/${contract.creatorUsername}/${contract.slug}#${commentId}`
|
||||||
const emailType = 'market-comment'
|
const emailType = 'market-comment'
|
||||||
const unsubscribeUrl = `${UNSUBSCRIBE_ENDPOINT}?id=${userId}&type=${emailType}`
|
const unsubscribeUrl = `${UNSUBSCRIBE_ENDPOINT}?id=${userId}&type=${emailType}`
|
||||||
|
|
||||||
const { name: commentorName, avatarUrl: commentorAvatarUrl } = commentCreator
|
const { name: commentorName, avatarUrl: commentorAvatarUrl } = commentCreator
|
||||||
const { content } = comment
|
|
||||||
const text = richTextToString(content)
|
const text =
|
||||||
|
typeof commentContent !== 'string'
|
||||||
|
? richTextToString(commentContent)
|
||||||
|
: commentContent
|
||||||
|
|
||||||
let betDescription = ''
|
let betDescription = ''
|
||||||
if (bet) {
|
if (bet) {
|
||||||
|
@ -380,7 +383,7 @@ export const sendNewCommentEmail = async (
|
||||||
const from = `${commentorName} on Manifold <no-reply@manifold.markets>`
|
const from = `${commentorName} on Manifold <no-reply@manifold.markets>`
|
||||||
|
|
||||||
if (contract.outcomeType === 'FREE_RESPONSE' && answerId && answerText) {
|
if (contract.outcomeType === 'FREE_RESPONSE' && answerId && answerText) {
|
||||||
const answerNumber = `#${answerId}`
|
const answerNumber = answerId ? `#${answerId}` : ''
|
||||||
|
|
||||||
return await sendTemplateEmail(
|
return await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
|
@ -423,14 +426,15 @@ export const sendNewCommentEmail = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendNewAnswerEmail = async (
|
export const sendNewAnswerEmail = async (
|
||||||
answer: Answer,
|
userId: string,
|
||||||
contract: Contract
|
name: string,
|
||||||
|
text: string,
|
||||||
|
contract: Contract,
|
||||||
|
avatarUrl?: string
|
||||||
) => {
|
) => {
|
||||||
// Send to just the creator for now.
|
const { creatorId } = contract
|
||||||
const { creatorId: userId } = contract
|
|
||||||
|
|
||||||
// Don't send the creator's own answers.
|
// Don't send the creator's own answers.
|
||||||
if (answer.userId === userId) return
|
if (userId === creatorId) return
|
||||||
|
|
||||||
const privateUser = await getPrivateUser(userId)
|
const privateUser = await getPrivateUser(userId)
|
||||||
if (
|
if (
|
||||||
|
@ -441,7 +445,6 @@ export const sendNewAnswerEmail = async (
|
||||||
return
|
return
|
||||||
|
|
||||||
const { question, creatorUsername, slug } = contract
|
const { question, creatorUsername, slug } = contract
|
||||||
const { name, avatarUrl, text } = answer
|
|
||||||
|
|
||||||
const marketUrl = `https://${DOMAIN}/${creatorUsername}/${slug}`
|
const marketUrl = `https://${DOMAIN}/${creatorUsername}/${slug}`
|
||||||
const emailType = 'market-answer'
|
const emailType = 'market-answer'
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { compact, uniq } from 'lodash'
|
import { compact } from 'lodash'
|
||||||
import { getContract, getUser, getValues } from './utils'
|
import { getContract, getUser, getValues } from './utils'
|
||||||
import { ContractComment } from '../../common/comment'
|
import { ContractComment } from '../../common/comment'
|
||||||
import { sendNewCommentEmail } from './emails'
|
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
import {
|
import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
|
||||||
createCommentOrAnswerOrUpdatedContractNotification,
|
|
||||||
filterUserIdsForOnlyFollowerIds,
|
|
||||||
} from './create-notification'
|
|
||||||
import { parseMentions, richTextToString } from '../../common/util/parse'
|
import { parseMentions, richTextToString } from '../../common/util/parse'
|
||||||
import { addUserToContractFollowers } from './follow-market'
|
import { addUserToContractFollowers } from './follow-market'
|
||||||
|
|
||||||
|
@ -77,10 +73,10 @@ export const onCreateCommentOnContract = functions
|
||||||
const comments = await getValues<ContractComment>(
|
const comments = await getValues<ContractComment>(
|
||||||
firestore.collection('contracts').doc(contractId).collection('comments')
|
firestore.collection('contracts').doc(contractId).collection('comments')
|
||||||
)
|
)
|
||||||
const relatedSourceType = comment.replyToCommentId
|
const repliedToType = answer
|
||||||
? 'comment'
|
|
||||||
: comment.answerOutcome
|
|
||||||
? 'answer'
|
? 'answer'
|
||||||
|
: comment.replyToCommentId
|
||||||
|
? 'comment'
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const repliedUserId = comment.replyToCommentId
|
const repliedUserId = comment.replyToCommentId
|
||||||
|
@ -96,31 +92,11 @@ export const onCreateCommentOnContract = functions
|
||||||
richTextToString(comment.content),
|
richTextToString(comment.content),
|
||||||
contract,
|
contract,
|
||||||
{
|
{
|
||||||
relatedSourceType,
|
repliedToType,
|
||||||
|
repliedToId: comment.replyToCommentId || answer?.id,
|
||||||
|
repliedToContent: answer ? answer.text : undefined,
|
||||||
repliedUserId,
|
repliedUserId,
|
||||||
taggedUserIds: compact(parseMentions(comment.content)),
|
taggedUserIds: compact(parseMentions(comment.content)),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const recipientUserIds = await filterUserIdsForOnlyFollowerIds(
|
|
||||||
uniq([
|
|
||||||
contract.creatorId,
|
|
||||||
...comments.map((comment) => comment.userId),
|
|
||||||
]).filter((id) => id !== comment.userId),
|
|
||||||
contractId
|
|
||||||
)
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
recipientUserIds.map((userId) =>
|
|
||||||
sendNewCommentEmail(
|
|
||||||
userId,
|
|
||||||
commentCreator,
|
|
||||||
contract,
|
|
||||||
comment,
|
|
||||||
bet,
|
|
||||||
answer?.text,
|
|
||||||
answer?.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,32 +13,7 @@ export const onUpdateContract = functions.firestore
|
||||||
if (!contractUpdater) throw new Error('Could not find contract updater')
|
if (!contractUpdater) throw new Error('Could not find contract updater')
|
||||||
|
|
||||||
const previousValue = change.before.data() as Contract
|
const previousValue = change.before.data() as Contract
|
||||||
if (previousValue.isResolved !== contract.isResolved) {
|
if (
|
||||||
let resolutionText = contract.resolution ?? contract.question
|
|
||||||
if (contract.outcomeType === 'FREE_RESPONSE') {
|
|
||||||
const answerText = contract.answers.find(
|
|
||||||
(answer) => answer.id === contract.resolution
|
|
||||||
)?.text
|
|
||||||
if (answerText) resolutionText = answerText
|
|
||||||
} else if (contract.outcomeType === 'BINARY') {
|
|
||||||
if (resolutionText === 'MKT' && contract.resolutionProbability)
|
|
||||||
resolutionText = `${contract.resolutionProbability}%`
|
|
||||||
else if (resolutionText === 'MKT') resolutionText = 'PROB'
|
|
||||||
} else if (contract.outcomeType === 'PSEUDO_NUMERIC') {
|
|
||||||
if (resolutionText === 'MKT' && contract.resolutionValue)
|
|
||||||
resolutionText = `${contract.resolutionValue}`
|
|
||||||
}
|
|
||||||
|
|
||||||
await createCommentOrAnswerOrUpdatedContractNotification(
|
|
||||||
contract.id,
|
|
||||||
'contract',
|
|
||||||
'resolved',
|
|
||||||
contractUpdater,
|
|
||||||
eventId,
|
|
||||||
resolutionText,
|
|
||||||
contract
|
|
||||||
)
|
|
||||||
} else if (
|
|
||||||
previousValue.closeTime !== contract.closeTime ||
|
previousValue.closeTime !== contract.closeTime ||
|
||||||
previousValue.question !== contract.question
|
previousValue.question !== contract.question
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { difference, mapValues, groupBy, sumBy } from 'lodash'
|
import { mapValues, groupBy, sumBy } from 'lodash'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
|
@ -8,10 +8,8 @@ import {
|
||||||
MultipleChoiceContract,
|
MultipleChoiceContract,
|
||||||
RESOLUTIONS,
|
RESOLUTIONS,
|
||||||
} from '../../common/contract'
|
} from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getUser, isProd, payUser } from './utils'
|
import { getUser, isProd, payUser } from './utils'
|
||||||
import { sendMarketResolutionEmail } from './emails'
|
|
||||||
import {
|
import {
|
||||||
getLoanPayouts,
|
getLoanPayouts,
|
||||||
getPayouts,
|
getPayouts,
|
||||||
|
@ -23,7 +21,7 @@ import { removeUndefinedProps } from '../../common/util/object'
|
||||||
import { LiquidityProvision } from '../../common/liquidity-provision'
|
import { LiquidityProvision } from '../../common/liquidity-provision'
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
import { getContractBetMetrics } from '../../common/calculate'
|
import { getContractBetMetrics } from '../../common/calculate'
|
||||||
import { floatingEqual } from '../../common/util/math'
|
import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string(),
|
contractId: z.string(),
|
||||||
|
@ -163,15 +161,44 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
|
||||||
|
|
||||||
const userPayoutsWithoutLoans = groupPayoutsByUser(payouts)
|
const userPayoutsWithoutLoans = groupPayoutsByUser(payouts)
|
||||||
|
|
||||||
await sendResolutionEmails(
|
const userInvestments = mapValues(
|
||||||
bets,
|
groupBy(bets, (bet) => bet.userId),
|
||||||
userPayoutsWithoutLoans,
|
(bets) => getContractBetMetrics(contract, bets).invested
|
||||||
|
)
|
||||||
|
let resolutionText = contract.resolution ?? contract.question
|
||||||
|
if (contract.outcomeType === 'FREE_RESPONSE') {
|
||||||
|
const answerText = contract.answers.find(
|
||||||
|
(answer) => answer.id === contract.resolution
|
||||||
|
)?.text
|
||||||
|
if (answerText) resolutionText = answerText
|
||||||
|
} else if (contract.outcomeType === 'BINARY') {
|
||||||
|
if (resolutionText === 'MKT' && contract.resolutionProbability)
|
||||||
|
resolutionText = `${contract.resolutionProbability}%`
|
||||||
|
else if (resolutionText === 'MKT') resolutionText = 'PROB'
|
||||||
|
} else if (contract.outcomeType === 'PSEUDO_NUMERIC') {
|
||||||
|
if (resolutionText === 'MKT' && contract.resolutionValue)
|
||||||
|
resolutionText = `${contract.resolutionValue}`
|
||||||
|
}
|
||||||
|
await createCommentOrAnswerOrUpdatedContractNotification(
|
||||||
|
contract.id,
|
||||||
|
'contract',
|
||||||
|
'resolved',
|
||||||
creator,
|
creator,
|
||||||
creatorPayout,
|
contract.id + '-resolution',
|
||||||
|
resolutionText,
|
||||||
contract,
|
contract,
|
||||||
outcome,
|
undefined,
|
||||||
resolutionProbability,
|
{
|
||||||
resolutions
|
bets,
|
||||||
|
userInvestments,
|
||||||
|
userPayouts: userPayoutsWithoutLoans,
|
||||||
|
creator,
|
||||||
|
creatorPayout,
|
||||||
|
contract,
|
||||||
|
outcome,
|
||||||
|
resolutionProbability,
|
||||||
|
resolutions,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return updatedContract
|
return updatedContract
|
||||||
|
@ -188,51 +215,51 @@ const processPayouts = async (payouts: Payout[], isDeposit = false) => {
|
||||||
.catch((e) => ({ status: 'error', message: e }))
|
.catch((e) => ({ status: 'error', message: e }))
|
||||||
.then(() => ({ status: 'success' }))
|
.then(() => ({ status: 'success' }))
|
||||||
}
|
}
|
||||||
|
//
|
||||||
const sendResolutionEmails = async (
|
// const sendResolutionEmails = async (
|
||||||
bets: Bet[],
|
// bets: Bet[],
|
||||||
userPayouts: { [userId: string]: number },
|
// userPayouts: { [userId: string]: number },
|
||||||
creator: User,
|
// creator: User,
|
||||||
creatorPayout: number,
|
// creatorPayout: number,
|
||||||
contract: Contract,
|
// contract: Contract,
|
||||||
outcome: string,
|
// outcome: string,
|
||||||
resolutionProbability?: number,
|
// resolutionProbability?: number,
|
||||||
resolutions?: { [outcome: string]: number }
|
// resolutions?: { [outcome: string]: number }
|
||||||
) => {
|
// ) => {
|
||||||
const investedByUser = mapValues(
|
// const investedByUser = mapValues(
|
||||||
groupBy(bets, (bet) => bet.userId),
|
// groupBy(bets, (bet) => bet.userId),
|
||||||
(bets) => getContractBetMetrics(contract, bets).invested
|
// (bets) => getContractBetMetrics(contract, bets).invested
|
||||||
)
|
// )
|
||||||
const investedUsers = Object.keys(investedByUser).filter(
|
// const investedUsers = Object.keys(investedByUser).filter(
|
||||||
(userId) => !floatingEqual(investedByUser[userId], 0)
|
// (userId) => !floatingEqual(investedByUser[userId], 0)
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
const nonWinners = difference(investedUsers, Object.keys(userPayouts))
|
// const nonWinners = difference(investedUsers, Object.keys(userPayouts))
|
||||||
const emailPayouts = [
|
// const emailPayouts = [
|
||||||
...Object.entries(userPayouts),
|
// ...Object.entries(userPayouts),
|
||||||
...nonWinners.map((userId) => [userId, 0] as const),
|
// ...nonWinners.map((userId) => [userId, 0] as const),
|
||||||
].map(([userId, payout]) => ({
|
// ].map(([userId, payout]) => ({
|
||||||
userId,
|
// userId,
|
||||||
investment: investedByUser[userId] ?? 0,
|
// investment: investedByUser[userId] ?? 0,
|
||||||
payout,
|
// payout,
|
||||||
}))
|
// }))
|
||||||
|
//
|
||||||
await Promise.all(
|
// await Promise.all(
|
||||||
emailPayouts.map(({ userId, investment, payout }) =>
|
// emailPayouts.map(({ userId, investment, payout }) =>
|
||||||
sendMarketResolutionEmail(
|
// sendMarketResolutionEmail(
|
||||||
userId,
|
// userId,
|
||||||
investment,
|
// investment,
|
||||||
payout,
|
// payout,
|
||||||
creator,
|
// creator,
|
||||||
creatorPayout,
|
// creatorPayout,
|
||||||
contract,
|
// contract,
|
||||||
outcome,
|
// outcome,
|
||||||
resolutionProbability,
|
// resolutionProbability,
|
||||||
resolutions
|
// resolutions
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
function getResolutionParams(contract: Contract, body: string) {
|
function getResolutionParams(contract: Contract, body: string) {
|
||||||
const { outcomeType } = contract
|
const { outcomeType } = contract
|
||||||
|
|
30
functions/src/scripts/create-new-notification-preferences.ts
Normal file
30
functions/src/scripts/create-new-notification-preferences.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
import { getDefaultNotificationSettings } from 'common/user'
|
||||||
|
import { getAllPrivateUsers, isProd } from 'functions/src/utils'
|
||||||
|
initAdmin()
|
||||||
|
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const privateUsers = await getAllPrivateUsers()
|
||||||
|
const disableEmails = !isProd()
|
||||||
|
await Promise.all(
|
||||||
|
privateUsers.map((privateUser) => {
|
||||||
|
if (!privateUser.id) return Promise.resolve()
|
||||||
|
return firestore
|
||||||
|
.collection('private-users')
|
||||||
|
.doc(privateUser.id)
|
||||||
|
.update({
|
||||||
|
notificationSubscriptionTypes: getDefaultNotificationSettings(
|
||||||
|
privateUser.id,
|
||||||
|
privateUser,
|
||||||
|
disableEmails
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) main().then(() => process.exit())
|
|
@ -3,7 +3,7 @@ import * as admin from 'firebase-admin'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
|
||||||
import { PrivateUser, User } from 'common/user'
|
import { getDefaultNotificationSettings, PrivateUser, User } from 'common/user'
|
||||||
import { STARTING_BALANCE } from 'common/economy'
|
import { STARTING_BALANCE } from 'common/economy'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
@ -21,6 +21,7 @@ async function main() {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email,
|
email,
|
||||||
username,
|
username,
|
||||||
|
notificationSubscriptionTypes: getDefaultNotificationSettings(user.id),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.totalDeposits === undefined) {
|
if (user.totalDeposits === undefined) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Row } from 'web/components/layout/row'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
exhaustive_notification_subscribe_types,
|
exhaustive_notification_subscribe_types,
|
||||||
notification_receive_types,
|
notification_destination_types,
|
||||||
} from 'common/user'
|
} from 'common/user'
|
||||||
import { updatePrivateUser } from 'web/lib/firebase/users'
|
import { updatePrivateUser } from 'web/lib/firebase/users'
|
||||||
import { Switch } from '@headlessui/react'
|
import { Switch } from '@headlessui/react'
|
||||||
|
@ -28,79 +28,6 @@ import toast from 'react-hot-toast'
|
||||||
export function NotificationSettings() {
|
export function NotificationSettings() {
|
||||||
const privateUser = usePrivateUser()
|
const privateUser = usePrivateUser()
|
||||||
const [showWatchModal, setShowWatchModal] = useState(false)
|
const [showWatchModal, setShowWatchModal] = useState(false)
|
||||||
const prevPref = privateUser?.notificationPreferences
|
|
||||||
const browserOnly = ['browser']
|
|
||||||
const emailOnly = ['email']
|
|
||||||
const both = ['email', 'browser']
|
|
||||||
const wantsLess = prevPref === 'less'
|
|
||||||
const wantsAll = prevPref === 'all'
|
|
||||||
|
|
||||||
const constructPref = (browserIf: boolean, emailIf: boolean | undefined) => {
|
|
||||||
const browser = browserIf ? 'browser' : undefined
|
|
||||||
const email = emailIf ? 'email' : undefined
|
|
||||||
return filterDefined([browser, email]) as notification_receive_types[]
|
|
||||||
}
|
|
||||||
if (privateUser && !privateUser.notificationSubscriptionTypes) {
|
|
||||||
updatePrivateUser(privateUser.id, {
|
|
||||||
notificationSubscriptionTypes: {
|
|
||||||
// Watched Markets
|
|
||||||
all_comments: constructPref(
|
|
||||||
wantsAll,
|
|
||||||
!privateUser.unsubscribedFromCommentEmails
|
|
||||||
),
|
|
||||||
all_answers: constructPref(
|
|
||||||
wantsAll,
|
|
||||||
!privateUser.unsubscribedFromAnswerEmails
|
|
||||||
),
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
tipped_comments: constructPref(wantsAll || wantsLess, true),
|
|
||||||
comments_by_followed_users: constructPref(wantsAll, false), //wantsAll ? browserOnly : none,
|
|
||||||
all_replies_to_my_comments: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
|
||||||
all_replies_to_my_answers: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
|
||||||
|
|
||||||
// Answers
|
|
||||||
answers_by_followed_users: constructPref(
|
|
||||||
wantsAll || wantsLess,
|
|
||||||
!privateUser.unsubscribedFromAnswerEmails
|
|
||||||
), //wantsAll || wantsLess ? both : none,
|
|
||||||
answers_by_market_creator: constructPref(
|
|
||||||
wantsAll || wantsLess,
|
|
||||||
!privateUser.unsubscribedFromAnswerEmails
|
|
||||||
), //wantsAll || wantsLess ? both : none,
|
|
||||||
|
|
||||||
// On users' markets
|
|
||||||
my_markets_closed: constructPref(
|
|
||||||
wantsAll || wantsLess,
|
|
||||||
!privateUser.unsubscribedFromResolutionEmails
|
|
||||||
), //wantsAll || wantsLess ? both : none, // High priority
|
|
||||||
all_comments_on_my_markets: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
|
||||||
all_answers_on_my_markets: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
|
||||||
|
|
||||||
// Market updates
|
|
||||||
resolutions: constructPref(wantsAll || wantsLess, true),
|
|
||||||
market_updates: constructPref(wantsAll || wantsLess, false),
|
|
||||||
|
|
||||||
//Balance Changes
|
|
||||||
loans: browserOnly,
|
|
||||||
betting_streaks: browserOnly,
|
|
||||||
referral_bonuses: both,
|
|
||||||
unique_bettor_bonuses: browserOnly,
|
|
||||||
|
|
||||||
// General
|
|
||||||
user_tagged_you: constructPref(wantsAll || wantsLess, true), //wantsAll || wantsLess ? both : none,
|
|
||||||
new_markets_by_followed_users: constructPref(
|
|
||||||
wantsAll || wantsLess,
|
|
||||||
true
|
|
||||||
), //wantsAll || wantsLess ? both : none,
|
|
||||||
trending_markets: constructPref(
|
|
||||||
false,
|
|
||||||
!privateUser.unsubscribedFromWeeklyTrendingEmails
|
|
||||||
),
|
|
||||||
profit_loss_updates: emailOnly,
|
|
||||||
} as exhaustive_notification_subscribe_types,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateUser || !privateUser.notificationSubscriptionTypes) {
|
if (!privateUser || !privateUser.notificationSubscriptionTypes) {
|
||||||
return <LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} />
|
return <LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} />
|
||||||
|
@ -120,22 +47,28 @@ export function NotificationSettings() {
|
||||||
'new_markets_by_followed_users',
|
'new_markets_by_followed_users',
|
||||||
'trending_markets',
|
'trending_markets',
|
||||||
'profit_loss_updates',
|
'profit_loss_updates',
|
||||||
|
'all_comments_on_contracts_with_shares_in',
|
||||||
|
'all_answers_on_contracts_with_shares_in',
|
||||||
]
|
]
|
||||||
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<exhaustive_notification_subscribe_types>]: string
|
||||||
} = {
|
} = {
|
||||||
all_comments: 'All',
|
all_comments_on_watched_markets: 'All',
|
||||||
// tipped_comments: 'Tipped',
|
// tipped_comments: 'Tipped',
|
||||||
// comments_by_followed_users: 'By followed users',
|
// comments_by_followed_users: 'By followed users',
|
||||||
all_replies_to_my_comments: 'Replies to your comments',
|
all_replies_to_my_comments_on_watched_markets: 'Replies to your comments',
|
||||||
|
all_comments_on_contracts_with_shares_in_on_watched_markets:
|
||||||
|
'On markets you have shares in',
|
||||||
}
|
}
|
||||||
const watched_markets_explanations_answers: {
|
const watched_markets_explanations_answers: {
|
||||||
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string
|
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string
|
||||||
} = {
|
} = {
|
||||||
all_answers: 'All',
|
all_answers_on_watched_markets: 'All',
|
||||||
all_replies_to_my_answers: 'Replies to your answers',
|
all_replies_to_my_answers_on_watched_markets: 'Replies to your answers',
|
||||||
|
all_answers_on_contracts_with_shares_in_on_watched_markets:
|
||||||
|
'On markets you have shares in',
|
||||||
// answers_by_followed_users: 'By followed users',
|
// answers_by_followed_users: 'By followed users',
|
||||||
// answers_by_market_creator: 'Submitted by the market creator',
|
// answers_by_market_creator: 'Submitted by the market creator',
|
||||||
}
|
}
|
||||||
|
@ -149,15 +82,15 @@ export function NotificationSettings() {
|
||||||
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<exhaustive_notification_subscribe_types>]: string
|
||||||
} = {
|
} = {
|
||||||
resolutions: 'Market resolutions',
|
resolutions_on_watched_markets: 'Market resolutions',
|
||||||
market_updates: 'Updates made by the creator',
|
market_updates_on_watched_markets: 'Updates made by the creator',
|
||||||
// probability_updates: 'Changes in probability',
|
// probability_updates: 'Changes in probability',
|
||||||
}
|
}
|
||||||
|
|
||||||
const balance_change_explanations: {
|
const balance_change_explanations: {
|
||||||
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string
|
[key in keyof Partial<exhaustive_notification_subscribe_types>]: string
|
||||||
} = {
|
} = {
|
||||||
loans: '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_bettor_bonuses: 'Unique bettor bonuses on your markets',
|
||||||
|
@ -175,7 +108,7 @@ export function NotificationSettings() {
|
||||||
const NotificationSettingLine = (
|
const NotificationSettingLine = (
|
||||||
description: string,
|
description: string,
|
||||||
key: string,
|
key: string,
|
||||||
value: notification_receive_types[]
|
value: notification_destination_types[]
|
||||||
) => {
|
) => {
|
||||||
const previousInAppValue = value.includes('browser')
|
const previousInAppValue = value.includes('browser')
|
||||||
const previousEmailValue = value.includes('email')
|
const previousEmailValue = value.includes('email')
|
||||||
|
@ -348,7 +281,7 @@ export function NotificationSettings() {
|
||||||
)}
|
)}
|
||||||
{Section(
|
{Section(
|
||||||
<UserIcon className={'h-6 w-6'} />,
|
<UserIcon className={'h-6 w-6'} />,
|
||||||
'On Your Markets',
|
'On Markets You Created',
|
||||||
watched_markets_explanations_your_markets
|
watched_markets_explanations_your_markets
|
||||||
)}
|
)}
|
||||||
{Section(
|
{Section(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { notification_subscribe_types, PrivateUser } from 'common/user'
|
import { PrivateUser } from 'common/user'
|
||||||
import { Notification } from 'common/notification'
|
import { Notification } from 'common/notification'
|
||||||
import { getNotificationsQuery } from 'web/lib/firebase/notifications'
|
import { getNotificationsQuery } from 'web/lib/firebase/notifications'
|
||||||
import { groupBy, map, partition } from 'lodash'
|
import { groupBy, map, partition } from 'lodash'
|
||||||
|
@ -23,11 +23,8 @@ function useNotifications(privateUser: PrivateUser) {
|
||||||
if (!result.data) return undefined
|
if (!result.data) return undefined
|
||||||
const notifications = result.data as Notification[]
|
const notifications = result.data as Notification[]
|
||||||
|
|
||||||
return getAppropriateNotifications(
|
return notifications.filter((n) => !n.isSeenOnHref)
|
||||||
notifications,
|
}, [result.data])
|
||||||
privateUser.notificationPreferences
|
|
||||||
).filter((n) => !n.isSeenOnHref)
|
|
||||||
}, [privateUser.notificationPreferences, result.data])
|
|
||||||
|
|
||||||
return notifications
|
return notifications
|
||||||
}
|
}
|
||||||
|
@ -112,28 +109,28 @@ export function groupNotifications(notifications: Notification[]) {
|
||||||
return notificationGroups
|
return notificationGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
const lessPriorityReasons = [
|
// const lessPriorityReasons = [
|
||||||
'on_contract_with_users_comment',
|
// 'on_contract_with_users_comment',
|
||||||
'on_contract_with_users_answer',
|
// 'on_contract_with_users_answer',
|
||||||
// Notifications not currently generated for users who've sold their shares
|
// // Notifications not currently generated for users who've sold their shares
|
||||||
'on_contract_with_users_shares_out',
|
// 'on_contract_with_users_shares_out',
|
||||||
// Not sure if users will want to see these w/ less:
|
// // Not sure if users will want to see these w/ less:
|
||||||
// 'on_contract_with_users_shares_in',
|
// // 'on_contract_with_users_shares_in',
|
||||||
]
|
// ]
|
||||||
|
|
||||||
function getAppropriateNotifications(
|
// function getAppropriateNotifications(
|
||||||
notifications: Notification[],
|
// notifications: Notification[],
|
||||||
notificationPreferences?: notification_subscribe_types
|
// notificationPreferences?: notification_subscribe_types
|
||||||
) {
|
// ) {
|
||||||
if (notificationPreferences === 'all') return notifications
|
// if (notificationPreferences === 'all') return notifications
|
||||||
if (notificationPreferences === 'less')
|
// if (notificationPreferences === 'less')
|
||||||
return notifications.filter(
|
// return notifications.filter(
|
||||||
(n) =>
|
// (n) =>
|
||||||
n.reason &&
|
// n.reason &&
|
||||||
// Show all contract notifications and any that aren't in the above list:
|
// // Show all contract notifications and any that aren't in the above list:
|
||||||
(n.sourceType === 'contract' || !lessPriorityReasons.includes(n.reason))
|
// (n.sourceType === 'contract' || !lessPriorityReasons.includes(n.reason))
|
||||||
)
|
// )
|
||||||
if (notificationPreferences === 'none') return []
|
// if (notificationPreferences === 'none') return []
|
||||||
|
//
|
||||||
return notifications
|
// return notifications
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1003,7 +1003,7 @@ function getReasonForShowingNotification(
|
||||||
else reasonText = justSummary ? `commented` : `commented on`
|
else reasonText = justSummary ? `commented` : `commented on`
|
||||||
break
|
break
|
||||||
case 'contract':
|
case 'contract':
|
||||||
if (reason === 'you_follow_user')
|
if (reason === 'contract_from_followed_user')
|
||||||
reasonText = justSummary ? 'asked the question' : 'asked'
|
reasonText = justSummary ? 'asked the question' : 'asked'
|
||||||
else if (sourceUpdateType === 'resolved')
|
else if (sourceUpdateType === 'resolved')
|
||||||
reasonText = justSummary ? `resolved the question` : `resolved`
|
reasonText = justSummary ? `resolved the question` : `resolved`
|
||||||
|
@ -1011,7 +1011,8 @@ function getReasonForShowingNotification(
|
||||||
else reasonText = justSummary ? 'updated the question' : `updated`
|
else reasonText = justSummary ? 'updated the question' : `updated`
|
||||||
break
|
break
|
||||||
case 'answer':
|
case 'answer':
|
||||||
if (reason === 'on_users_contract') reasonText = `answered your question `
|
if (reason === 'answer_on_your_contract')
|
||||||
|
reasonText = `answered your question `
|
||||||
else reasonText = `answered`
|
else reasonText = `answered`
|
||||||
break
|
break
|
||||||
case 'follow':
|
case 'follow':
|
||||||
|
|
Loading…
Reference in New Issue
Block a user