-
-
-
-
-
\ No newline at end of file
diff --git a/functions/src/email-templates/creating-market.html b/functions/src/email-templates/creating-market.html
index c73f7458..bf163f69 100644
--- a/functions/src/email-templates/creating-market.html
+++ b/functions/src/email-templates/creating-market.html
@@ -494,7 +494,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/interesting-markets.html b/functions/src/email-templates/interesting-markets.html
index 7c3e653d..0cee6269 100644
--- a/functions/src/email-templates/interesting-markets.html
+++ b/functions/src/email-templates/interesting-markets.html
@@ -443,7 +443,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-answer-comment.html b/functions/src/email-templates/market-answer-comment.html
index a19aa7c3..4b98730f 100644
--- a/functions/src/email-templates/market-answer-comment.html
+++ b/functions/src/email-templates/market-answer-comment.html
@@ -529,7 +529,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-answer.html b/functions/src/email-templates/market-answer.html
index b2d7f727..e3d42b9d 100644
--- a/functions/src/email-templates/market-answer.html
+++ b/functions/src/email-templates/market-answer.html
@@ -369,7 +369,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-close.html b/functions/src/email-templates/market-close.html
index ee7976b0..4abd225e 100644
--- a/functions/src/email-templates/market-close.html
+++ b/functions/src/email-templates/market-close.html
@@ -487,7 +487,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-comment.html b/functions/src/email-templates/market-comment.html
index 23e20dac..ce0669f1 100644
--- a/functions/src/email-templates/market-comment.html
+++ b/functions/src/email-templates/market-comment.html
@@ -369,7 +369,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-resolved-no-bets.html b/functions/src/email-templates/market-resolved-no-bets.html
index ff5f541f..5d886adf 100644
--- a/functions/src/email-templates/market-resolved-no-bets.html
+++ b/functions/src/email-templates/market-resolved-no-bets.html
@@ -470,7 +470,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-resolved.html b/functions/src/email-templates/market-resolved.html
index de29a0f1..767202b6 100644
--- a/functions/src/email-templates/market-resolved.html
+++ b/functions/src/email-templates/market-resolved.html
@@ -502,7 +502,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/new-market-from-followed-user.html b/functions/src/email-templates/new-market-from-followed-user.html
index 877d554f..49633fb2 100644
--- a/functions/src/email-templates/new-market-from-followed-user.html
+++ b/functions/src/email-templates/new-market-from-followed-user.html
@@ -318,7 +318,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/new-unique-bettor.html b/functions/src/email-templates/new-unique-bettor.html
index 30da8b99..51026121 100644
--- a/functions/src/email-templates/new-unique-bettor.html
+++ b/functions/src/email-templates/new-unique-bettor.html
@@ -376,7 +376,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/new-unique-bettors.html b/functions/src/email-templates/new-unique-bettors.html
index eb4c04e2..09c44d03 100644
--- a/functions/src/email-templates/new-unique-bettors.html
+++ b/functions/src/email-templates/new-unique-bettors.html
@@ -480,7 +480,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/one-week.html b/functions/src/email-templates/one-week.html
index b8e233d5..e7d14a7e 100644
--- a/functions/src/email-templates/one-week.html
+++ b/functions/src/email-templates/one-week.html
@@ -283,7 +283,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/thank-you.html b/functions/src/email-templates/thank-you.html
index 7ac72d0a..beef11ee 100644
--- a/functions/src/email-templates/thank-you.html
+++ b/functions/src/email-templates/thank-you.html
@@ -218,7 +218,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/welcome.html b/functions/src/email-templates/welcome.html
index dccec695..d6caaa0c 100644
--- a/functions/src/email-templates/welcome.html
+++ b/functions/src/email-templates/welcome.html
@@ -290,7 +290,7 @@
click here to manage your notifications.
+ " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/emails.ts b/functions/src/emails.ts
index e9ef9630..98309ebe 100644
--- a/functions/src/emails.ts
+++ b/functions/src/emails.ts
@@ -2,11 +2,7 @@ import { DOMAIN } from '../../common/envs/constants'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { Contract } from '../../common/contract'
-import {
- notification_subscription_types,
- PrivateUser,
- User,
-} from '../../common/user'
+import { PrivateUser, User } from '../../common/user'
import {
formatLargeNumber,
formatMoney,
@@ -18,11 +14,12 @@ import { formatNumericProbability } from '../../common/pseudo-numeric'
import { sendTemplateEmail, sendTextEmail } from './send-email'
import { getUser } from './utils'
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
-import {
- notification_reason_types,
- getDestinationsForUser,
-} from '../../common/notification'
+import { notification_reason_types } from '../../common/notification'
import { Dictionary } from 'lodash'
+import {
+ getNotificationDestinationsForUser,
+ notification_preference,
+} from '../../common/user-notification-preferences'
export const sendMarketResolutionEmail = async (
reason: notification_reason_types,
@@ -36,8 +33,10 @@ export const sendMarketResolutionEmail = async (
resolutionProbability?: number,
resolutions?: { [outcome: string]: number }
) => {
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser || !privateUser.email || !sendToEmail) return
const user = await getUser(privateUser.id)
@@ -154,7 +153,7 @@ export const sendWelcomeEmail = async (
const firstName = name.split(' ')[0]
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
- 'onboarding_flow' as keyof notification_subscription_types
+ 'onboarding_flow' as notification_preference
}`
return await sendTemplateEmail(
@@ -214,7 +213,7 @@ export const sendOneWeekBonusEmail = async (
if (
!privateUser ||
!privateUser.email ||
- !privateUser.notificationSubscriptionTypes.onboarding_flow.includes('email')
+ !privateUser.notificationPreferences.onboarding_flow.includes('email')
)
return
@@ -222,7 +221,7 @@ export const sendOneWeekBonusEmail = async (
const firstName = name.split(' ')[0]
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
- 'onboarding_flow' as keyof notification_subscription_types
+ 'onboarding_flow' as notification_preference
}`
return await sendTemplateEmail(
privateUser.email,
@@ -247,7 +246,7 @@ export const sendCreatorGuideEmail = async (
if (
!privateUser ||
!privateUser.email ||
- !privateUser.notificationSubscriptionTypes.onboarding_flow.includes('email')
+ !privateUser.notificationPreferences.onboarding_flow.includes('email')
)
return
@@ -255,7 +254,7 @@ export const sendCreatorGuideEmail = async (
const firstName = name.split(' ')[0]
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
- 'onboarding_flow' as keyof notification_subscription_types
+ 'onboarding_flow' as notification_preference
}`
return await sendTemplateEmail(
privateUser.email,
@@ -279,7 +278,7 @@ export const sendThankYouEmail = async (
if (
!privateUser ||
!privateUser.email ||
- !privateUser.notificationSubscriptionTypes.thank_you_for_purchases.includes(
+ !privateUser.notificationPreferences.thank_you_for_purchases.includes(
'email'
)
)
@@ -289,7 +288,7 @@ export const sendThankYouEmail = async (
const firstName = name.split(' ')[0]
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
- 'thank_you_for_purchases' as keyof notification_subscription_types
+ 'thank_you_for_purchases' as notification_preference
}`
return await sendTemplateEmail(
@@ -312,8 +311,10 @@ export const sendMarketCloseEmail = async (
privateUser: PrivateUser,
contract: Contract
) => {
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser.email || !sendToEmail) return
@@ -350,8 +351,10 @@ export const sendNewCommentEmail = async (
answerText?: string,
answerId?: string
) => {
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser || !privateUser.email || !sendToEmail) return
const { question } = contract
@@ -425,8 +428,10 @@ export const sendNewAnswerEmail = async (
// Don't send the creator's own answers.
if (privateUser.id === creatorId) return
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser.email || !sendToEmail) return
const { question, creatorUsername, slug } = contract
@@ -460,14 +465,12 @@ export const sendInterestingMarketsEmail = async (
if (
!privateUser ||
!privateUser.email ||
- !privateUser.notificationSubscriptionTypes.trending_markets.includes(
- 'email'
- )
+ !privateUser.notificationPreferences.trending_markets.includes('email')
)
return
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
- 'trending_markets' as keyof notification_subscription_types
+ 'trending_markets' as notification_preference
}`
const { name } = user
@@ -518,8 +521,10 @@ export const sendNewFollowedMarketEmail = async (
privateUser: PrivateUser,
contract: Contract
) => {
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser.email || !sendToEmail) return
const user = await getUser(privateUser.id)
if (!user) return
@@ -555,8 +560,10 @@ export const sendNewUniqueBettorsEmail = async (
userBets: Dictionary<[Bet, ...Bet[]]>,
bonusAmount: number
) => {
- const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } =
- await getDestinationsForUser(privateUser, reason)
+ const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ privateUser,
+ reason
+ )
if (!privateUser.email || !sendToEmail) return
const user = await getUser(privateUser.id)
if (!user) return
diff --git a/functions/src/index.ts b/functions/src/index.ts
index be73b6af..adfee75e 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -71,6 +71,7 @@ import { stripewebhook, createcheckoutsession } from './stripe'
import { getcurrentuser } from './get-current-user'
import { acceptchallenge } from './accept-challenge'
import { createpost } from './create-post'
+import { savetwitchcredentials } from './save-twitch-credentials'
const toCloudFunction = ({ opts, handler }: EndpointDefinition) => {
return onRequest(opts, handler as any)
@@ -96,6 +97,7 @@ const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession)
const getCurrentUserFunction = toCloudFunction(getcurrentuser)
const acceptChallenge = toCloudFunction(acceptchallenge)
const createPostFunction = toCloudFunction(createpost)
+const saveTwitchCredentials = toCloudFunction(savetwitchcredentials)
export {
healthFunction as health,
@@ -119,4 +121,5 @@ export {
getCurrentUserFunction as getcurrentuser,
acceptChallenge as acceptchallenge,
createPostFunction as createpost,
+ saveTwitchCredentials as savetwitchcredentials
}
diff --git a/functions/src/on-create-bet.ts b/functions/src/on-create-bet.ts
index f2c6b51a..ce75f0fe 100644
--- a/functions/src/on-create-bet.ts
+++ b/functions/src/on-create-bet.ts
@@ -24,6 +24,10 @@ import {
} from '../../common/antes'
import { APIError } from '../../common/api'
import { User } from '../../common/user'
+import { UNIQUE_BETTOR_LIQUIDITY_AMOUNT } from '../../common/antes'
+import { addHouseLiquidity } from './add-liquidity'
+import { DAY_MS } from '../../common/util/time'
+import { BettingStreakBonusTxn, UniqueBettorBonusTxn } from '../../common/txn'
const firestore = admin.firestore()
const BONUS_START_DATE = new Date('2022-07-13T15:30:00.000Z').getTime()
@@ -59,6 +63,12 @@ export const onCreateBet = functions
const bettor = await getUser(bet.userId)
if (!bettor) return
+ await change.ref.update({
+ userAvatarUrl: bettor.avatarUrl,
+ userName: bettor.name,
+ userUsername: bettor.username,
+ })
+
await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bettor)
await notifyFills(bet, contract, eventId, bettor)
await updateBettingStreak(bettor, bet, contract, eventId)
@@ -72,12 +82,16 @@ const updateBettingStreak = async (
contract: Contract,
eventId: string
) => {
- const betStreakResetTime = getTodaysBettingStreakResetTime()
+ const now = Date.now()
+ const currentDateResetTime = currentDateBettingStreakResetTime()
+ // if now is before reset time, use yesterday's reset time
+ const lastDateResetTime = currentDateResetTime - DAY_MS
+ const betStreakResetTime =
+ now < currentDateResetTime ? lastDateResetTime : currentDateResetTime
const lastBetTime = user?.lastBetTime ?? 0
- // If they've already bet after the reset time, or if we haven't hit the reset time yet
- if (lastBetTime > betStreakResetTime || bet.createdTime < betStreakResetTime)
- return
+ // If they've already bet after the reset time
+ if (lastBetTime > betStreakResetTime) return
const newBettingStreak = (user?.currentBettingStreak ?? 0) + 1
// Otherwise, add 1 to their betting streak
@@ -96,6 +110,7 @@ const updateBettingStreak = async (
const bonusTxnDetails = {
currentBettingStreak: newBettingStreak,
}
+ // TODO: set the id of the txn to the eventId to prevent duplicates
const result = await firestore.runTransaction(async (trans) => {
const bonusTxn: TxnData = {
fromId: fromUserId,
@@ -106,11 +121,14 @@ const updateBettingStreak = async (
token: 'M$',
category: 'BETTING_STREAK_BONUS',
description: JSON.stringify(bonusTxnDetails),
- }
+ data: bonusTxnDetails,
+ } as Omit
return await runTxn(trans, bonusTxn)
})
if (!result.txn) {
log("betting streak bonus txn couldn't be made")
+ log('status:', result.status)
+ log('message:', result.message)
return
}
@@ -120,6 +138,7 @@ const updateBettingStreak = async (
bet,
contract,
bonusAmount,
+ newBettingStreak,
eventId
)
}
@@ -149,12 +168,13 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
}
const isNewUniqueBettor = !previousUniqueBettorIds.includes(bettor.id)
-
const newUniqueBettorIds = uniq([...previousUniqueBettorIds, bettor.id])
+
// Update contract unique bettors
if (!contract.uniqueBettorIds || isNewUniqueBettor) {
log(`Got ${previousUniqueBettorIds} unique bettors`)
isNewUniqueBettor && log(`And a new unique bettor ${bettor.id}`)
+
await firestore.collection(`contracts`).doc(contract.id).update({
uniqueBettorIds: newUniqueBettorIds,
uniqueBettorCount: newUniqueBettorIds.length,
@@ -164,10 +184,14 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
// No need to give a bonus for the creator's bet
if (!isNewUniqueBettor || bettor.id == contract.creatorId) return
+ if (contract.mechanism === 'cpmm-1') {
+ await addHouseLiquidity(contract, UNIQUE_BETTOR_LIQUIDITY_AMOUNT)
+ }
+
// Create combined txn for all new unique bettors
const bonusTxnDetails = {
contractId: contract.id,
- uniqueBettorIds: newUniqueBettorIds,
+ uniqueNewBettorId: bettor.id,
}
const fromUserId = isProd()
? HOUSE_LIQUIDITY_PROVIDER_ID
@@ -175,6 +199,7 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
const fromSnap = await firestore.doc(`users/${fromUserId}`).get()
if (!fromSnap.exists) throw new APIError(400, 'From user not found.')
const fromUser = fromSnap.data() as User
+ // TODO: set the id of the txn to the eventId to prevent duplicates
const result = await firestore.runTransaction(async (trans) => {
const bonusTxn: TxnData = {
fromId: fromUser.id,
@@ -185,12 +210,14 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
token: 'M$',
category: 'UNIQUE_BETTOR_BONUS',
description: JSON.stringify(bonusTxnDetails),
- }
+ data: bonusTxnDetails,
+ } as Omit
return await runTxn(trans, bonusTxn)
})
if (result.status != 'success' || !result.txn) {
- log(`No bonus for user: ${contract.creatorId} - reason:`, result.status)
+ log(`No bonus for user: ${contract.creatorId} - status:`, result.status)
+ log('message:', result.message)
} else {
log(`Bonus txn for user: ${contract.creatorId} completed:`, result.txn?.id)
await createUniqueBettorBonusNotification(
@@ -246,6 +273,6 @@ const notifyFills = async (
)
}
-const getTodaysBettingStreakResetTime = () => {
+const currentDateBettingStreakResetTime = () => {
return new Date().setUTCHours(BETTING_STREAK_RESET_HOUR, 0, 0, 0)
}
diff --git a/functions/src/on-create-liquidity-provision.ts b/functions/src/on-create-liquidity-provision.ts
index 3a1e551f..54da7fd9 100644
--- a/functions/src/on-create-liquidity-provision.ts
+++ b/functions/src/on-create-liquidity-provision.ts
@@ -7,6 +7,7 @@ import { FIXED_ANTE } from '../../common/economy'
import {
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
HOUSE_LIQUIDITY_PROVIDER_ID,
+ UNIQUE_BETTOR_LIQUIDITY_AMOUNT,
} from '../../common/antes'
export const onCreateLiquidityProvision = functions.firestore
@@ -17,9 +18,11 @@ export const onCreateLiquidityProvision = functions.firestore
// Ignore Manifold Markets liquidity for now - users see a notification for free market liquidity provision
if (
- (liquidity.userId === HOUSE_LIQUIDITY_PROVIDER_ID ||
+ liquidity.isAnte ||
+ ((liquidity.userId === HOUSE_LIQUIDITY_PROVIDER_ID ||
liquidity.userId === DEV_HOUSE_LIQUIDITY_PROVIDER_ID) &&
- liquidity.amount === FIXED_ANTE
+ (liquidity.amount === FIXED_ANTE ||
+ liquidity.amount === UNIQUE_BETTOR_LIQUIDITY_AMOUNT))
)
return
diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts
index d98430c1..74df7dc3 100644
--- a/functions/src/place-bet.ts
+++ b/functions/src/place-bet.ts
@@ -139,7 +139,14 @@ export const placebet = newEndpoint({}, async (req, auth) => {
}
const betDoc = contractDoc.collection('bets').doc()
- trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet })
+ trans.create(betDoc, {
+ id: betDoc.id,
+ userId: user.id,
+ userAvatarUrl: user.avatarUrl,
+ userUsername: user.username,
+ userName: user.name,
+ ...newBet,
+ })
log('Created new bet document.')
if (makers) {
diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts
index b867b609..feddd67c 100644
--- a/functions/src/resolve-market.ts
+++ b/functions/src/resolve-market.ts
@@ -9,19 +9,25 @@ import {
RESOLUTIONS,
} from '../../common/contract'
import { Bet } from '../../common/bet'
-import { getUser, isProd, payUser } from './utils'
+import { getUser, getValues, isProd, log, payUser } from './utils'
import {
getLoanPayouts,
getPayouts,
groupPayoutsByUser,
Payout,
} from '../../common/payouts'
-import { isManifoldId } from '../../common/envs/constants'
+import { isAdmin, isManifoldId } from '../../common/envs/constants'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
import { APIError, newEndpoint, validate } from './api'
import { getContractBetMetrics } from '../../common/calculate'
-import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
+import { createContractResolvedNotifications } from './create-notification'
+import { CancelUniqueBettorBonusTxn, Txn } from '../../common/txn'
+import { runTxn, TxnData } from './transact'
+import {
+ DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+ HOUSE_LIQUIDITY_PROVIDER_ID,
+} from '../../common/antes'
const bodySchema = z.object({
contractId: z.string(),
@@ -76,13 +82,18 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
throw new APIError(404, 'No contract exists with the provided ID')
const contract = contractSnap.data() as Contract
const { creatorId, closeTime } = contract
+ const firebaseUser = await admin.auth().getUser(auth.uid)
const { value, resolutions, probabilityInt, outcome } = getResolutionParams(
contract,
req.body
)
- if (creatorId !== auth.uid && !isManifoldId(auth.uid))
+ if (
+ creatorId !== auth.uid &&
+ !isManifoldId(auth.uid) &&
+ !isAdmin(firebaseUser.email)
+ )
throw new APIError(403, 'User is not creator of contract')
if (contract.resolution) throw new APIError(400, 'Contract already resolved')
@@ -158,6 +169,7 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
await processPayouts(liquidityPayouts, true)
await processPayouts([...payouts, ...loanPayouts])
+ await undoUniqueBettorRewardsIfCancelResolution(contract, outcome)
const userPayoutsWithoutLoans = groupPayoutsByUser(payouts)
@@ -165,33 +177,13 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
groupBy(bets, (bet) => bet.userId),
(bets) => getContractBetMetrics(contract, bets).invested
)
- let resolutionText = outcome ?? contract.question
- if (
- contract.outcomeType === 'FREE_RESPONSE' ||
- contract.outcomeType === 'MULTIPLE_CHOICE'
- ) {
- const answerText = contract.answers.find(
- (answer) => answer.id === outcome
- )?.text
- if (answerText) resolutionText = answerText
- } else if (contract.outcomeType === 'BINARY') {
- if (resolutionText === 'MKT' && probabilityInt)
- resolutionText = `${probabilityInt}%`
- else if (resolutionText === 'MKT') resolutionText = 'PROB'
- } else if (contract.outcomeType === 'PSEUDO_NUMERIC') {
- if (resolutionText === 'MKT' && value) resolutionText = `${value}`
- }
- // TODO: this actually may be too slow to complete with a ton of users to notify?
- await createCommentOrAnswerOrUpdatedContractNotification(
- contract.id,
- 'contract',
- 'resolved',
- creator,
- contract.id + '-resolution',
- resolutionText,
+ await createContractResolvedNotifications(
contract,
- undefined,
+ creator,
+ outcome,
+ probabilityInt,
+ value,
{
bets,
userInvestments,
@@ -294,4 +286,55 @@ function validateAnswer(
}
}
+async function undoUniqueBettorRewardsIfCancelResolution(
+ contract: Contract,
+ outcome: string
+) {
+ if (outcome === 'CANCEL') {
+ const creatorsBonusTxns = await getValues(
+ firestore
+ .collection('txns')
+ .where('category', '==', 'UNIQUE_BETTOR_BONUS')
+ .where('toId', '==', contract.creatorId)
+ )
+
+ const bonusTxnsOnThisContract = creatorsBonusTxns.filter(
+ (txn) => txn.data && txn.data.contractId === contract.id
+ )
+ log('total bonusTxnsOnThisContract', bonusTxnsOnThisContract.length)
+ const totalBonusAmount = sumBy(bonusTxnsOnThisContract, (txn) => txn.amount)
+ log('totalBonusAmount to be withdrawn', totalBonusAmount)
+ const result = await firestore.runTransaction(async (trans) => {
+ const bonusTxn: TxnData = {
+ fromId: contract.creatorId,
+ fromType: 'USER',
+ toId: isProd()
+ ? HOUSE_LIQUIDITY_PROVIDER_ID
+ : DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+ toType: 'BANK',
+ amount: totalBonusAmount,
+ token: 'M$',
+ category: 'CANCEL_UNIQUE_BETTOR_BONUS',
+ data: {
+ contractId: contract.id,
+ },
+ } as Omit
+ return await runTxn(trans, bonusTxn)
+ })
+
+ if (result.status != 'success' || !result.txn) {
+ log(
+ `Couldn't cancel bonus for user: ${contract.creatorId} - status:`,
+ result.status
+ )
+ log('message:', result.message)
+ } else {
+ log(
+ `Cancel Bonus txn for user: ${contract.creatorId} completed:`,
+ result.txn?.id
+ )
+ }
+ }
+}
+
const firestore = admin.firestore()
diff --git a/functions/src/save-twitch-credentials.ts b/functions/src/save-twitch-credentials.ts
new file mode 100644
index 00000000..80dc86a6
--- /dev/null
+++ b/functions/src/save-twitch-credentials.ts
@@ -0,0 +1,22 @@
+import * as admin from 'firebase-admin'
+import { z } from 'zod'
+
+import { newEndpoint, validate } from './api'
+
+const bodySchema = z.object({
+ twitchInfo: z.object({
+ twitchName: z.string(),
+ controlToken: z.string(),
+ }),
+})
+
+
+export const savetwitchcredentials = newEndpoint({}, async (req, auth) => {
+ const { twitchInfo } = validate(bodySchema, req.body)
+ const userId = auth.uid
+
+ await firestore.doc(`private-users/${userId}`).update({ twitchInfo })
+ return { success: true }
+})
+
+const firestore = admin.firestore()
diff --git a/functions/src/scripts/backfill-contract-followers.ts b/functions/src/scripts/backfill-contract-followers.ts
index 9b936654..9b5834bc 100644
--- a/functions/src/scripts/backfill-contract-followers.ts
+++ b/functions/src/scripts/backfill-contract-followers.ts
@@ -4,14 +4,14 @@ import { initAdmin } from './script-init'
initAdmin()
import { getValues } from '../utils'
-import { Contract } from 'common/lib/contract'
-import { Comment } from 'common/lib/comment'
+import { Contract } from 'common/contract'
+import { Comment } from 'common/comment'
import { uniq } from 'lodash'
-import { Bet } from 'common/lib/bet'
+import { Bet } from 'common/bet'
import {
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
HOUSE_LIQUIDITY_PROVIDER_ID,
-} from 'common/lib/antes'
+} from 'common/antes'
const firestore = admin.firestore()
diff --git a/functions/src/scripts/create-new-notification-preferences.ts b/functions/src/scripts/create-new-notification-preferences.ts
index a6bd1a0b..4ba2e25e 100644
--- a/functions/src/scripts/create-new-notification-preferences.ts
+++ b/functions/src/scripts/create-new-notification-preferences.ts
@@ -1,8 +1,8 @@
import * as admin from 'firebase-admin'
import { initAdmin } from './script-init'
-import { getDefaultNotificationSettings } from 'common/user'
import { getAllPrivateUsers, isProd } from 'functions/src/utils'
+import { getDefaultNotificationPreferences } from 'common/user-notification-preferences'
initAdmin()
const firestore = admin.firestore()
@@ -17,7 +17,7 @@ async function main() {
.collection('private-users')
.doc(privateUser.id)
.update({
- notificationSubscriptionTypes: getDefaultNotificationSettings(
+ notificationPreferences: getDefaultNotificationPreferences(
privateUser.id,
privateUser,
disableEmails
diff --git a/functions/src/scripts/create-private-users.ts b/functions/src/scripts/create-private-users.ts
index f9b8c3a1..762e801a 100644
--- a/functions/src/scripts/create-private-users.ts
+++ b/functions/src/scripts/create-private-users.ts
@@ -3,8 +3,9 @@ import * as admin from 'firebase-admin'
import { initAdmin } from './script-init'
initAdmin()
-import { getDefaultNotificationSettings, PrivateUser, User } from 'common/user'
+import { PrivateUser, User } from 'common/user'
import { STARTING_BALANCE } from 'common/economy'
+import { getDefaultNotificationPreferences } from 'common/user-notification-preferences'
const firestore = admin.firestore()
@@ -21,7 +22,7 @@ async function main() {
id: user.id,
email,
username,
- notificationSubscriptionTypes: getDefaultNotificationSettings(user.id),
+ notificationPreferences: getDefaultNotificationPreferences(user.id),
}
if (user.totalDeposits === undefined) {
diff --git a/functions/src/scripts/denormalize-avatar-urls.ts b/functions/src/scripts/denormalize-avatar-urls.ts
index 23b7dfc9..fd95ec8f 100644
--- a/functions/src/scripts/denormalize-avatar-urls.ts
+++ b/functions/src/scripts/denormalize-avatar-urls.ts
@@ -3,12 +3,7 @@
import * as admin from 'firebase-admin'
import { initAdmin } from './script-init'
-import {
- DocumentCorrespondence,
- findDiffs,
- describeDiff,
- applyDiff,
-} from './denormalize'
+import { findDiffs, describeDiff, applyDiff } from './denormalize'
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
initAdmin()
@@ -79,43 +74,36 @@ if (require.main === module) {
getAnswersByUserId(transaction),
])
- const usersContracts = Array.from(
- usersById.entries(),
- ([id, doc]): DocumentCorrespondence => {
- return [doc, contractsByUserId.get(id) || []]
- }
- )
- const contractDiffs = findDiffs(
- usersContracts,
+ const usersContracts = Array.from(usersById.entries(), ([id, doc]) => {
+ return [doc, contractsByUserId.get(id) || []] as const
+ })
+ const contractDiffs = findDiffs(usersContracts, [
'avatarUrl',
- 'creatorAvatarUrl'
- )
+ 'creatorAvatarUrl',
+ ])
console.log(`Found ${contractDiffs.length} contracts with mismatches.`)
contractDiffs.forEach((d) => {
console.log(describeDiff(d))
applyDiff(transaction, d)
})
- const usersComments = Array.from(
- usersById.entries(),
- ([id, doc]): DocumentCorrespondence => {
- return [doc, commentsByUserId.get(id) || []]
- }
- )
- const commentDiffs = findDiffs(usersComments, 'avatarUrl', 'userAvatarUrl')
+ const usersComments = Array.from(usersById.entries(), ([id, doc]) => {
+ return [doc, commentsByUserId.get(id) || []] as const
+ })
+ const commentDiffs = findDiffs(usersComments, [
+ 'avatarUrl',
+ 'userAvatarUrl',
+ ])
console.log(`Found ${commentDiffs.length} comments with mismatches.`)
commentDiffs.forEach((d) => {
console.log(describeDiff(d))
applyDiff(transaction, d)
})
- const usersAnswers = Array.from(
- usersById.entries(),
- ([id, doc]): DocumentCorrespondence => {
- return [doc, answersByUserId.get(id) || []]
- }
- )
- const answerDiffs = findDiffs(usersAnswers, 'avatarUrl', 'avatarUrl')
+ const usersAnswers = Array.from(usersById.entries(), ([id, doc]) => {
+ return [doc, answersByUserId.get(id) || []] as const
+ })
+ const answerDiffs = findDiffs(usersAnswers, ['avatarUrl', 'avatarUrl'])
console.log(`Found ${answerDiffs.length} answers with mismatches.`)
answerDiffs.forEach((d) => {
console.log(describeDiff(d))
diff --git a/functions/src/scripts/denormalize-bet-user-data.ts b/functions/src/scripts/denormalize-bet-user-data.ts
new file mode 100644
index 00000000..3c86e140
--- /dev/null
+++ b/functions/src/scripts/denormalize-bet-user-data.ts
@@ -0,0 +1,38 @@
+// Filling in the user-based fields on bets.
+
+import * as admin from 'firebase-admin'
+import { initAdmin } from './script-init'
+import { findDiffs, describeDiff, getDiffUpdate } from './denormalize'
+import { log, writeAsync } from '../utils'
+
+initAdmin()
+const firestore = admin.firestore()
+
+// not in a transaction for speed -- may need to be run more than once
+async function denormalize() {
+ const users = await firestore.collection('users').get()
+ log(`Found ${users.size} users.`)
+ for (const userDoc of users.docs) {
+ const userBets = await firestore
+ .collectionGroup('bets')
+ .where('userId', '==', userDoc.id)
+ .get()
+ const mapping = [[userDoc, userBets.docs] as const] as const
+ const diffs = findDiffs(
+ mapping,
+ ['avatarUrl', 'userAvatarUrl'],
+ ['name', 'userName'],
+ ['username', 'userUsername']
+ )
+ log(`Found ${diffs.length} bets with mismatched user data.`)
+ const updates = diffs.map((d) => {
+ log(describeDiff(d))
+ return getDiffUpdate(d)
+ })
+ await writeAsync(firestore, updates)
+ }
+}
+
+if (require.main === module) {
+ denormalize().catch((e) => console.error(e))
+}
diff --git a/functions/src/scripts/denormalize-comment-bet-data.ts b/functions/src/scripts/denormalize-comment-bet-data.ts
index 929626c3..a5fb8759 100644
--- a/functions/src/scripts/denormalize-comment-bet-data.ts
+++ b/functions/src/scripts/denormalize-comment-bet-data.ts
@@ -3,12 +3,7 @@
import * as admin from 'firebase-admin'
import { zip } from 'lodash'
import { initAdmin } from './script-init'
-import {
- DocumentCorrespondence,
- findDiffs,
- describeDiff,
- applyDiff,
-} from './denormalize'
+import { findDiffs, describeDiff, applyDiff } from './denormalize'
import { log } from '../utils'
import { Transaction } from 'firebase-admin/firestore'
@@ -41,17 +36,20 @@ async function denormalize() {
)
)
log(`Found ${bets.length} bets associated with comments.`)
- const mapping = zip(bets, betComments)
- .map(([bet, comment]): DocumentCorrespondence => {
- return [bet!, [comment!]] // eslint-disable-line
- })
- .filter(([bet, _]) => bet.exists) // dev DB has some invalid bet IDs
- const amountDiffs = findDiffs(mapping, 'amount', 'betAmount')
- const outcomeDiffs = findDiffs(mapping, 'outcome', 'betOutcome')
- log(`Found ${amountDiffs.length} comments with mismatched amounts.`)
- log(`Found ${outcomeDiffs.length} comments with mismatched outcomes.`)
- const diffs = amountDiffs.concat(outcomeDiffs)
+ // dev DB has some invalid bet IDs
+ const mapping = zip(bets, betComments)
+ .filter(([bet, _]) => bet!.exists) // eslint-disable-line
+ .map(([bet, comment]) => {
+ return [bet!, [comment!]] as const // eslint-disable-line
+ })
+
+ const diffs = findDiffs(
+ mapping,
+ ['amount', 'betAmount'],
+ ['outcome', 'betOutcome']
+ )
+ log(`Found ${diffs.length} comments with mismatched data.`)
diffs.slice(0, 500).forEach((d) => {
log(describeDiff(d))
applyDiff(trans, d)
diff --git a/functions/src/scripts/denormalize-comment-contract-data.ts b/functions/src/scripts/denormalize-comment-contract-data.ts
index 0358c5a1..150b833d 100644
--- a/functions/src/scripts/denormalize-comment-contract-data.ts
+++ b/functions/src/scripts/denormalize-comment-contract-data.ts
@@ -2,12 +2,7 @@
import * as admin from 'firebase-admin'
import { initAdmin } from './script-init'
-import {
- DocumentCorrespondence,
- findDiffs,
- describeDiff,
- applyDiff,
-} from './denormalize'
+import { findDiffs, describeDiff, applyDiff } from './denormalize'
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
initAdmin()
@@ -43,16 +38,15 @@ async function denormalize() {
getContractsById(transaction),
getCommentsByContractId(transaction),
])
- const mapping = Object.entries(contractsById).map(
- ([id, doc]): DocumentCorrespondence => {
- return [doc, commentsByContractId.get(id) || []]
- }
+ const mapping = Object.entries(contractsById).map(([id, doc]) => {
+ return [doc, commentsByContractId.get(id) || []] as const
+ })
+ const diffs = findDiffs(
+ mapping,
+ ['slug', 'contractSlug'],
+ ['question', 'contractQuestion']
)
- const slugDiffs = findDiffs(mapping, 'slug', 'contractSlug')
- const qDiffs = findDiffs(mapping, 'question', 'contractQuestion')
- console.log(`Found ${slugDiffs.length} comments with mismatched slugs.`)
- console.log(`Found ${qDiffs.length} comments with mismatched questions.`)
- const diffs = slugDiffs.concat(qDiffs)
+ console.log(`Found ${diffs.length} comments with mismatched data.`)
diffs.slice(0, 500).forEach((d) => {
console.log(describeDiff(d))
applyDiff(transaction, d)
diff --git a/functions/src/scripts/denormalize.ts b/functions/src/scripts/denormalize.ts
index 20bfc458..d4feb425 100644
--- a/functions/src/scripts/denormalize.ts
+++ b/functions/src/scripts/denormalize.ts
@@ -2,32 +2,40 @@
// another set of documents.
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
+import { isEqual, zip } from 'lodash'
+import { UpdateSpec } from '../utils'
export type DocumentValue = {
doc: DocumentSnapshot
- field: string
- val: unknown
+ fields: string[]
+ vals: unknown[]
}
-export type DocumentCorrespondence = [DocumentSnapshot, DocumentSnapshot[]]
+export type DocumentMapping = readonly [
+ DocumentSnapshot,
+ readonly DocumentSnapshot[]
+]
export type DocumentDiff = {
src: DocumentValue
dest: DocumentValue
}
+type PathPair = readonly [string, string]
+
export function findDiffs(
- docs: DocumentCorrespondence[],
- srcPath: string,
- destPath: string
+ docs: readonly DocumentMapping[],
+ ...paths: PathPair[]
) {
const diffs: DocumentDiff[] = []
+ const srcPaths = paths.map((p) => p[0])
+ const destPaths = paths.map((p) => p[1])
for (const [srcDoc, destDocs] of docs) {
- const srcVal = srcDoc.get(srcPath)
+ const srcVals = srcPaths.map((p) => srcDoc.get(p))
for (const destDoc of destDocs) {
- const destVal = destDoc.get(destPath)
- if (destVal !== srcVal) {
+ const destVals = destPaths.map((p) => destDoc.get(p))
+ if (!isEqual(srcVals, destVals)) {
diffs.push({
- src: { doc: srcDoc, field: srcPath, val: srcVal },
- dest: { doc: destDoc, field: destPath, val: destVal },
+ src: { doc: srcDoc, fields: srcPaths, vals: srcVals },
+ dest: { doc: destDoc, fields: destPaths, vals: destVals },
})
}
}
@@ -37,12 +45,19 @@ export function findDiffs(
export function describeDiff(diff: DocumentDiff) {
function describeDocVal(x: DocumentValue): string {
- return `${x.doc.ref.path}.${x.field}: ${x.val}`
+ return `${x.doc.ref.path}.[${x.fields.join('|')}]: [${x.vals.join('|')}]`
}
return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}`
}
-export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
- const { src, dest } = diff
- transaction.update(dest.doc.ref, dest.field, src.val)
+export function getDiffUpdate(diff: DocumentDiff) {
+ return {
+ doc: diff.dest.doc.ref,
+ fields: Object.fromEntries(zip(diff.dest.fields, diff.src.vals)),
+ } as UpdateSpec
+}
+
+export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
+ const update = getDiffUpdate(diff)
+ transaction.update(update.doc, update.fields)
}
diff --git a/functions/src/scripts/update-bonus-txn-data-fields.ts b/functions/src/scripts/update-bonus-txn-data-fields.ts
new file mode 100644
index 00000000..82955fa0
--- /dev/null
+++ b/functions/src/scripts/update-bonus-txn-data-fields.ts
@@ -0,0 +1,34 @@
+import * as admin from 'firebase-admin'
+
+import { initAdmin } from './script-init'
+import { Txn } from 'common/txn'
+import { getValues } from 'functions/src/utils'
+
+initAdmin()
+
+const firestore = admin.firestore()
+
+async function main() {
+ // get all txns
+ const bonusTxns = await getValues(
+ firestore
+ .collection('txns')
+ .where('category', 'in', ['UNIQUE_BETTOR_BONUS', 'BETTING_STREAK_BONUS'])
+ )
+ // JSON parse description field and add to data field
+ const updatedTxns = bonusTxns.map((txn) => {
+ txn.data = txn.description && JSON.parse(txn.description)
+ return txn
+ })
+ console.log('updatedTxns', updatedTxns[0])
+ // update txns
+ await Promise.all(
+ updatedTxns.map((txn) => {
+ return firestore.collection('txns').doc(txn.id).update({
+ data: txn.data,
+ })
+ })
+ )
+}
+
+if (require.main === module) main().then(() => process.exit())
diff --git a/functions/src/scripts/update-notification-preferences.ts b/functions/src/scripts/update-notification-preferences.ts
new file mode 100644
index 00000000..efea57b8
--- /dev/null
+++ b/functions/src/scripts/update-notification-preferences.ts
@@ -0,0 +1,25 @@
+import * as admin from 'firebase-admin'
+
+import { initAdmin } from './script-init'
+import { getAllPrivateUsers } from 'functions/src/utils'
+import { FieldValue } from 'firebase-admin/firestore'
+initAdmin()
+
+const firestore = admin.firestore()
+
+async function main() {
+ const privateUsers = await getAllPrivateUsers()
+ await Promise.all(
+ privateUsers.map((privateUser) => {
+ if (!privateUser.id) return Promise.resolve()
+ return firestore.collection('private-users').doc(privateUser.id).update({
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ notificationPreferences: privateUser.notificationSubscriptionTypes,
+ notificationSubscriptionTypes: FieldValue.delete(),
+ })
+ })
+ )
+}
+
+if (require.main === module) main().then(() => process.exit())
diff --git a/functions/src/sell-shares.ts b/functions/src/sell-shares.ts
index 0e88a0b5..f2f475cb 100644
--- a/functions/src/sell-shares.ts
+++ b/functions/src/sell-shares.ts
@@ -112,6 +112,9 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
transaction.create(newBetDoc, {
id: newBetDoc.id,
userId: user.id,
+ userAvatarUrl: user.avatarUrl,
+ userUsername: user.username,
+ userName: user.name,
...newBet,
})
transaction.update(
diff --git a/functions/src/serve.ts b/functions/src/serve.ts
index a5291f19..6d062d40 100644
--- a/functions/src/serve.ts
+++ b/functions/src/serve.ts
@@ -27,6 +27,7 @@ import { unsubscribe } from './unsubscribe'
import { stripewebhook, createcheckoutsession } from './stripe'
import { getcurrentuser } from './get-current-user'
import { createpost } from './create-post'
+import { savetwitchcredentials } from './save-twitch-credentials'
type Middleware = (req: Request, res: Response, next: NextFunction) => void
const app = express()
@@ -65,6 +66,7 @@ addJsonEndpointRoute('/resolvemarket', resolvemarket)
addJsonEndpointRoute('/unsubscribe', unsubscribe)
addJsonEndpointRoute('/createcheckoutsession', createcheckoutsession)
addJsonEndpointRoute('/getcurrentuser', getcurrentuser)
+addJsonEndpointRoute('/savetwitchcredentials', savetwitchcredentials)
addEndpointRoute('/stripewebhook', stripewebhook, express.raw())
addEndpointRoute('/createpost', createpost)
diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts
index da7b507f..418282c7 100644
--- a/functions/src/unsubscribe.ts
+++ b/functions/src/unsubscribe.ts
@@ -1,79 +1,227 @@
import * as admin from 'firebase-admin'
import { EndpointDefinition } from './api'
-import { getUser } from './utils'
+import { getPrivateUser } from './utils'
import { PrivateUser } from '../../common/user'
+import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification'
+import { notification_preference } from '../../common/user-notification-preferences'
export const unsubscribe: EndpointDefinition = {
opts: { method: 'GET', minInstances: 1 },
handler: async (req, res) => {
const id = req.query.id as string
- let type = req.query.type as string
+ const type = req.query.type as string
if (!id || !type) {
- res.status(400).send('Empty id or type parameter.')
+ res.status(400).send('Empty id or subscription type parameter.')
+ return
+ }
+ console.log(`Unsubscribing ${id} from ${type}`)
+ const notificationSubscriptionType = type as notification_preference
+ if (notificationSubscriptionType === undefined) {
+ res.status(400).send('Invalid subscription type parameter.')
return
}
- if (type === 'market-resolved') type = 'market-resolve'
-
- if (
- ![
- 'market-resolve',
- 'market-comment',
- 'market-answer',
- 'generic',
- 'weekly-trending',
- ].includes(type)
- ) {
- res.status(400).send('Invalid type parameter.')
- return
- }
-
- const user = await getUser(id)
+ const user = await getPrivateUser(id)
if (!user) {
res.send('This user is not currently subscribed or does not exist.')
return
}
- const { name } = user
+ const previousDestinations =
+ user.notificationPreferences[notificationSubscriptionType]
+
+ console.log(previousDestinations)
+ const { email } = user
const update: Partial = {
- ...(type === 'market-resolve' && {
- unsubscribedFromResolutionEmails: true,
- }),
- ...(type === 'market-comment' && {
- unsubscribedFromCommentEmails: true,
- }),
- ...(type === 'market-answer' && {
- unsubscribedFromAnswerEmails: true,
- }),
- ...(type === 'generic' && {
- unsubscribedFromGenericEmails: true,
- }),
- ...(type === 'weekly-trending' && {
- unsubscribedFromWeeklyTrendingEmails: true,
- }),
+ notificationPreferences: {
+ ...user.notificationPreferences,
+ [notificationSubscriptionType]: previousDestinations.filter(
+ (destination) => destination !== 'email'
+ ),
+ },
}
await firestore.collection('private-users').doc(id).update(update)
- if (type === 'market-resolve')
- res.send(
- `${name}, you have been unsubscribed from market resolution emails on Manifold Markets.`
- )
- else if (type === 'market-comment')
- res.send(
- `${name}, you have been unsubscribed from market comment emails on Manifold Markets.`
- )
- else if (type === 'market-answer')
- res.send(
- `${name}, you have been unsubscribed from market answer emails on Manifold Markets.`
- )
- else if (type === 'weekly-trending')
- res.send(
- `${name}, you have been unsubscribed from weekly trending emails on Manifold Markets.`
- )
- else res.send(`${name}, you have been unsubscribed.`)
+ res.send(
+ `
+
+
+
+
+ Manifold Markets 7th Day Anniversary Gift!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${email} has been unsubscribed from email notifications related to:
+
+
+
+
+ ${NOTIFICATION_DESCRIPTIONS[notificationSubscriptionType].detailed}.
+
+
+
+
+ Click
+ here
+ to manage the rest of your notification settings.
+
+