diff --git a/common/comment.ts b/common/comment.ts
index cdb62fd3..71c04af4 100644
--- a/common/comment.ts
+++ b/common/comment.ts
@@ -18,6 +18,7 @@ export type Comment = {
userName: string
userUsername: string
userAvatarUrl?: string
+ bountiesAwarded?: number
} & T
export type OnContract = {
diff --git a/common/contract.ts b/common/contract.ts
index 248c9745..1255874d 100644
--- a/common/contract.ts
+++ b/common/contract.ts
@@ -62,6 +62,9 @@ export type Contract = {
featuredOnHomeRank?: number
likedByUserIds?: string[]
likedByUserCount?: number
+ flaggedByUsernames?: string[]
+ openCommentBounties?: number
+ unlistedById?: string
} & T
export type BinaryContract = Contract & Binary
diff --git a/common/economy.ts b/common/economy.ts
index 7ec52b30..d25a0c71 100644
--- a/common/economy.ts
+++ b/common/economy.ts
@@ -15,3 +15,4 @@ export const BETTING_STREAK_BONUS_AMOUNT =
export const BETTING_STREAK_BONUS_MAX = econ?.BETTING_STREAK_BONUS_MAX ?? 50
export const BETTING_STREAK_RESET_HOUR = econ?.BETTING_STREAK_RESET_HOUR ?? 7
export const FREE_MARKETS_PER_USER_MAX = econ?.FREE_MARKETS_PER_USER_MAX ?? 5
+export const COMMENT_BOUNTY_AMOUNT = econ?.COMMENT_BOUNTY_AMOUNT ?? 250
diff --git a/common/envs/prod.ts b/common/envs/prod.ts
index d0469d84..38dd4feb 100644
--- a/common/envs/prod.ts
+++ b/common/envs/prod.ts
@@ -41,6 +41,7 @@ export type Economy = {
BETTING_STREAK_BONUS_MAX?: number
BETTING_STREAK_RESET_HOUR?: number
FREE_MARKETS_PER_USER_MAX?: number
+ COMMENT_BOUNTY_AMOUNT?: number
}
type FirebaseConfig = {
diff --git a/common/group.ts b/common/group.ts
index 5220a1e8..8f5728d3 100644
--- a/common/group.ts
+++ b/common/group.ts
@@ -23,6 +23,7 @@ export type Group = {
score: number
}[]
}
+ pinnedItems: { itemId: string; type: 'post' | 'contract' }[]
}
export const MAX_GROUP_NAME_LENGTH = 75
diff --git a/common/like.ts b/common/like.ts
index 38b25dad..7ec14726 100644
--- a/common/like.ts
+++ b/common/like.ts
@@ -5,4 +5,4 @@ export type Like = {
createdTime: number
tipTxnId?: string // only holds most recent tip txn id
}
-export const LIKE_TIP_AMOUNT = 5
+export const LIKE_TIP_AMOUNT = 10
diff --git a/common/notification.ts b/common/notification.ts
index b42df541..b75e3d4a 100644
--- a/common/notification.ts
+++ b/common/notification.ts
@@ -96,6 +96,7 @@ type notification_descriptions = {
[key in notification_preference]: {
simple: string
detailed: string
+ necessary?: boolean
}
}
export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
@@ -116,8 +117,8 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
detailed: "Only answers by market creator on markets you're watching",
},
betting_streaks: {
- simple: 'For predictions made over consecutive days',
- detailed: 'Bonuses for predictions made over consecutive days',
+ simple: `For prediction streaks`,
+ detailed: `Bonuses for predictions made over consecutive days (Prediction streaks)})`,
},
comments_by_followed_users_on_watched_markets: {
simple: 'Only comments by users you follow',
@@ -159,8 +160,8 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
detailed: 'Large changes in probability on markets that you watch',
},
profit_loss_updates: {
- simple: 'Weekly profit and loss updates',
- detailed: 'Weekly profit and loss updates',
+ simple: 'Weekly portfolio updates',
+ detailed: 'Weekly portfolio updates',
},
referral_bonuses: {
simple: 'For referring new users',
@@ -208,8 +209,9 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
detailed: 'Bonuses for unique predictors on your markets',
},
your_contract_closed: {
- simple: 'Your market has closed and you need to resolve it',
- detailed: 'Your market has closed and you need to resolve it',
+ simple: 'Your market has closed and you need to resolve it (necessary)',
+ detailed: 'Your market has closed and you need to resolve it (necessary)',
+ necessary: true,
},
all_comments_on_watched_markets: {
simple: 'All new comments',
@@ -235,6 +237,11 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
simple: `Only on markets you're invested in`,
detailed: `Answers on markets that you're watching and that you're invested in`,
},
+ opt_out_all: {
+ simple: 'Opt out of all notifications (excludes when your markets close)',
+ detailed:
+ 'Opt out of all notifications excluding your own market closure notifications',
+ },
}
export type BettingStreakData = {
diff --git a/common/txn.ts b/common/txn.ts
index 2b7a32e8..c404059d 100644
--- a/common/txn.ts
+++ b/common/txn.ts
@@ -8,6 +8,7 @@ type AnyTxnType =
| UniqueBettorBonus
| BettingStreakBonus
| CancelUniqueBettorBonus
+ | CommentBountyRefund
type SourceType = 'USER' | 'CONTRACT' | 'CHARITY' | 'BANK'
export type Txn = {
@@ -31,6 +32,8 @@ export type Txn = {
| 'UNIQUE_BETTOR_BONUS'
| 'BETTING_STREAK_BONUS'
| 'CANCEL_UNIQUE_BETTOR_BONUS'
+ | 'COMMENT_BOUNTY'
+ | 'REFUND_COMMENT_BOUNTY'
// Any extra data
data?: { [key: string]: any }
@@ -98,6 +101,34 @@ type CancelUniqueBettorBonus = {
}
}
+type CommentBountyDeposit = {
+ fromType: 'USER'
+ toType: 'BANK'
+ category: 'COMMENT_BOUNTY'
+ data: {
+ contractId: string
+ }
+}
+
+type CommentBountyWithdrawal = {
+ fromType: 'BANK'
+ toType: 'USER'
+ category: 'COMMENT_BOUNTY'
+ data: {
+ contractId: string
+ commentId: string
+ }
+}
+
+type CommentBountyRefund = {
+ fromType: 'BANK'
+ toType: 'USER'
+ category: 'REFUND_COMMENT_BOUNTY'
+ data: {
+ contractId: string
+ }
+}
+
export type DonationTxn = Txn & Donation
export type TipTxn = Txn & Tip
export type ManalinkTxn = Txn & Manalink
@@ -105,3 +136,5 @@ export type ReferralTxn = Txn & Referral
export type BettingStreakBonusTxn = Txn & BettingStreakBonus
export type UniqueBettorBonusTxn = Txn & UniqueBettorBonus
export type CancelUniqueBettorBonusTxn = Txn & CancelUniqueBettorBonus
+export type CommentBountyDepositTxn = Txn & CommentBountyDeposit
+export type CommentBountyWithdrawalTxn = Txn & CommentBountyWithdrawal
diff --git a/common/user-notification-preferences.ts b/common/user-notification-preferences.ts
index 3fc0fb2f..ba9ade9d 100644
--- a/common/user-notification-preferences.ts
+++ b/common/user-notification-preferences.ts
@@ -53,6 +53,9 @@ export type notification_preferences = {
profit_loss_updates: notification_destination_types[]
onboarding_flow: notification_destination_types[]
thank_you_for_purchases: notification_destination_types[]
+
+ opt_out_all: notification_destination_types[]
+ // When adding a new notification preference, use add-new-notification-preference.ts to existing users
}
export const getDefaultNotificationPreferences = (
@@ -65,7 +68,7 @@ export const getDefaultNotificationPreferences = (
const email = noEmails ? undefined : emailIf ? 'email' : undefined
return filterDefined([browser, email]) as notification_destination_types[]
}
- return {
+ const defaults: notification_preferences = {
// Watched Markets
all_comments_on_watched_markets: constructPref(true, false),
all_answers_on_watched_markets: constructPref(true, false),
@@ -121,7 +124,10 @@ export const getDefaultNotificationPreferences = (
probability_updates_on_watched_markets: constructPref(true, false),
thank_you_for_purchases: constructPref(false, false),
onboarding_flow: constructPref(false, false),
- } as notification_preferences
+
+ opt_out_all: [],
+ }
+ return defaults
}
// Adding a new key:value here is optional, you can just use a key of notification_subscription_types
@@ -184,10 +190,18 @@ export const getNotificationDestinationsForUser = (
? notificationSettings[subscriptionType]
: []
}
+ const optOutOfAllSettings = notificationSettings['opt_out_all']
+ // Your market closure notifications are high priority, opt-out doesn't affect their delivery
+ const optedOutOfEmail =
+ optOutOfAllSettings.includes('email') &&
+ subscriptionType !== 'your_contract_closed'
+ const optedOutOfBrowser =
+ optOutOfAllSettings.includes('browser') &&
+ subscriptionType !== 'your_contract_closed'
const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
return {
- sendToEmail: destinations.includes('email'),
- sendToBrowser: destinations.includes('browser'),
+ sendToEmail: destinations.includes('email') && !optedOutOfEmail,
+ sendToBrowser: destinations.includes('browser') && !optedOutOfBrowser,
unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`,
urlToManageThisNotification: `${DOMAIN}/notifications?tab=settings§ion=${subscriptionType}`,
}
diff --git a/common/user.ts b/common/user.ts
index b1365929..233fe4cc 100644
--- a/common/user.ts
+++ b/common/user.ts
@@ -33,6 +33,8 @@ export type User = {
allTime: number
}
+ fractionResolvedCorrectly: number
+
nextLoanCached: number
followerCountCached: number
diff --git a/common/util/format.ts b/common/util/format.ts
index 4f123535..ee59d3e7 100644
--- a/common/util/format.ts
+++ b/common/util/format.ts
@@ -8,7 +8,12 @@ const formatter = new Intl.NumberFormat('en-US', {
})
export function formatMoney(amount: number) {
- const newAmount = Math.round(amount) === 0 ? 0 : Math.floor(amount) // handle -0 case
+ const newAmount =
+ // handle -0 case
+ Math.round(amount) === 0
+ ? 0
+ : // Handle 499.9999999999999 case
+ Math.floor(amount + 0.00000000001 * Math.sign(amount))
return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
}
diff --git a/firestore.rules b/firestore.rules
index 26649fa6..50f93e1f 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -102,7 +102,7 @@ service cloud.firestore {
allow update: if request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['tags', 'lowercaseTags', 'groupSlugs', 'groupLinks']);
allow update: if request.resource.data.diff(resource.data).affectedKeys()
- .hasOnly(['description', 'closeTime', 'question'])
+ .hasOnly(['description', 'closeTime', 'question', 'visibility', 'unlistedById'])
&& resource.data.creatorId == request.auth.uid;
allow update: if isAdmin();
match /comments/{commentId} {
@@ -176,7 +176,7 @@ service cloud.firestore {
allow update: if (request.auth.uid == resource.data.creatorId || isAdmin())
&& request.resource.data.diff(resource.data)
.affectedKeys()
- .hasOnly(['name', 'about', 'anyoneCanJoin', 'aboutPostId' ]);
+ .hasOnly(['name', 'about', 'anyoneCanJoin', 'aboutPostId', 'pinnedItems' ]);
allow delete: if request.auth.uid == resource.data.creatorId;
match /groupContracts/{contractId} {
diff --git a/functions/src/create-group.ts b/functions/src/create-group.ts
index 76dc1298..4b3f7446 100644
--- a/functions/src/create-group.ts
+++ b/functions/src/create-group.ts
@@ -62,6 +62,7 @@ export const creategroup = newEndpoint({}, async (req, auth) => {
totalContracts: 0,
totalMembers: memberIds.length,
postIds: [],
+ pinnedItems: [],
}
await groupRef.create(group)
diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts
index 038e0142..9bd73d05 100644
--- a/functions/src/create-notification.ts
+++ b/functions/src/create-notification.ts
@@ -1046,3 +1046,47 @@ export const createContractResolvedNotifications = async (
)
)
}
+
+export const createBountyNotification = async (
+ fromUser: User,
+ toUserId: string,
+ amount: number,
+ idempotencyKey: string,
+ contract: Contract,
+ commentId?: string
+) => {
+ const privateUser = await getPrivateUser(toUserId)
+ if (!privateUser) return
+ const { sendToBrowser } = getNotificationDestinationsForUser(
+ privateUser,
+ 'tip_received'
+ )
+ if (!sendToBrowser) return
+
+ const slug = commentId
+ const notificationRef = firestore
+ .collection(`/users/${toUserId}/notifications`)
+ .doc(idempotencyKey)
+ const notification: Notification = {
+ id: idempotencyKey,
+ userId: toUserId,
+ reason: 'tip_received',
+ createdTime: Date.now(),
+ isSeen: false,
+ sourceId: commentId ? commentId : contract.id,
+ sourceType: 'tip',
+ sourceUpdateType: 'created',
+ sourceUserName: fromUser.name,
+ sourceUserUsername: fromUser.username,
+ sourceUserAvatarUrl: fromUser.avatarUrl,
+ sourceText: amount.toString(),
+ sourceContractCreatorUsername: contract.creatorUsername,
+ sourceContractTitle: contract.question,
+ sourceContractSlug: contract.slug,
+ sourceSlug: slug,
+ sourceTitle: contract.question,
+ }
+ return await notificationRef.set(removeUndefinedProps(notification))
+
+ // maybe TODO: send email notification to comment creator
+}
diff --git a/functions/src/create-user.ts b/functions/src/create-user.ts
index ab70b4e6..c3b7ba1d 100644
--- a/functions/src/create-user.ts
+++ b/functions/src/create-user.ts
@@ -69,6 +69,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => {
followerCountCached: 0,
followedCategories: DEFAULT_CATEGORIES,
shouldShowWelcome: true,
+ fractionResolvedCorrectly: 1,
}
await firestore.collection('users').doc(auth.uid).create(user)
diff --git a/functions/src/email-templates/market-close.html b/functions/src/email-templates/market-close.html
index 4abd225e..b742c533 100644
--- a/functions/src/email-templates/market-close.html
+++ b/functions/src/email-templates/market-close.html
@@ -483,11 +483,7 @@
color: #999;
text-decoration: underline;
margin: 0;
- ">our Discord! Or,
- click here to unsubscribe from this type of notification.
+ ">our Discord!
diff --git a/functions/src/email-templates/weekly-portfolio-update.html b/functions/src/email-templates/weekly-portfolio-update.html
index fd99837f..921a58e5 100644
--- a/functions/src/email-templates/weekly-portfolio-update.html
+++ b/functions/src/email-templates/weekly-portfolio-update.html
@@ -320,7 +320,7 @@
style="line-height: 24px; margin: 10px 0; margin-top: 20px; margin-bottom: 20px;"
data-testid="4XoHRGw1Y">
- And here's some of the biggest changes in your portfolio:
+ And here's some recent changes in your investments:
diff --git a/functions/src/emails.ts b/functions/src/emails.ts
index 6888cfb1..993fac81 100644
--- a/functions/src/emails.ts
+++ b/functions/src/emails.ts
@@ -643,13 +643,13 @@ export const sendWeeklyPortfolioUpdateEmail = async (
templateData[`question${i + 1}Title`] = investment.questionTitle
templateData[`question${i + 1}Url`] = investment.questionUrl
templateData[`question${i + 1}Prob`] = investment.questionProb
- templateData[`question${i + 1}Change`] = formatMoney(investment.difference)
- templateData[`question${i + 1}ChangeStyle`] = investment.questionChangeStyle
+ templateData[`question${i + 1}Change`] = formatMoney(investment.profit)
+ templateData[`question${i + 1}ChangeStyle`] = investment.profitStyle
})
await sendTemplateEmail(
- // privateUser.email,
- 'iansphilips@gmail.com',
+ privateUser.email,
+ // 'iansphilips@gmail.com',
`Here's your weekly portfolio update!`,
investments.length === 0
? 'portfolio-update-no-movers'
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 9a8ec232..f5c45004 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -52,6 +52,7 @@ export * from './unsubscribe'
export * from './stripe'
export * from './mana-bonus-email'
export * from './close-market'
+export * from './update-comment-bounty'
import { health } from './health'
import { transact } from './transact'
@@ -65,6 +66,7 @@ import { sellshares } from './sell-shares'
import { claimmanalink } from './claim-manalink'
import { createmarket } from './create-market'
import { addliquidity } from './add-liquidity'
+import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
import { withdrawliquidity } from './withdraw-liquidity'
import { creategroup } from './create-group'
import { resolvemarket } from './resolve-market'
@@ -91,6 +93,8 @@ const sellSharesFunction = toCloudFunction(sellshares)
const claimManalinkFunction = toCloudFunction(claimmanalink)
const createMarketFunction = toCloudFunction(createmarket)
const addLiquidityFunction = toCloudFunction(addliquidity)
+const addCommentBounty = toCloudFunction(addcommentbounty)
+const awardCommentBounty = toCloudFunction(awardcommentbounty)
const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity)
const createGroupFunction = toCloudFunction(creategroup)
const resolveMarketFunction = toCloudFunction(resolvemarket)
@@ -127,4 +131,6 @@ export {
acceptChallenge as acceptchallenge,
createPostFunction as createpost,
saveTwitchCredentials as savetwitchcredentials,
+ addCommentBounty as addcommentbounty,
+ awardCommentBounty as awardcommentbounty,
}
diff --git a/functions/src/on-update-contract.ts b/functions/src/on-update-contract.ts
index 5e2a94c0..301d6286 100644
--- a/functions/src/on-update-contract.ts
+++ b/functions/src/on-update-contract.ts
@@ -7,38 +7,47 @@ export const onUpdateContract = functions.firestore
.document('contracts/{contractId}')
.onUpdate(async (change, context) => {
const contract = change.after.data() as Contract
+ const previousContract = change.before.data() as Contract
const { eventId } = context
-
- const contractUpdater = await getUser(contract.creatorId)
- if (!contractUpdater) throw new Error('Could not find contract updater')
-
- const previousValue = change.before.data() as Contract
-
- // Resolution is handled in resolve-market.ts
- if (!previousValue.isResolved && contract.isResolved) return
+ const { openCommentBounties, closeTime, question } = contract
if (
- previousValue.closeTime !== contract.closeTime ||
- previousValue.question !== contract.question
+ !previousContract.isResolved &&
+ contract.isResolved &&
+ (openCommentBounties ?? 0) > 0
) {
- let sourceText = ''
- if (
- previousValue.closeTime !== contract.closeTime &&
- contract.closeTime
- ) {
- sourceText = contract.closeTime.toString()
- } else if (previousValue.question !== contract.question) {
- sourceText = contract.question
- }
-
- await createCommentOrAnswerOrUpdatedContractNotification(
- contract.id,
- 'contract',
- 'updated',
- contractUpdater,
- eventId,
- sourceText,
- contract
- )
+ // No need to notify users of resolution, that's handled in resolve-market
+ return
+ }
+ if (
+ previousContract.closeTime !== closeTime ||
+ previousContract.question !== question
+ ) {
+ await handleUpdatedCloseTime(previousContract, contract, eventId)
}
})
+
+async function handleUpdatedCloseTime(
+ previousContract: Contract,
+ contract: Contract,
+ eventId: string
+) {
+ const contractUpdater = await getUser(contract.creatorId)
+ if (!contractUpdater) throw new Error('Could not find contract updater')
+ let sourceText = ''
+ if (previousContract.closeTime !== contract.closeTime && contract.closeTime) {
+ sourceText = contract.closeTime.toString()
+ } else if (previousContract.question !== contract.question) {
+ sourceText = contract.question
+ }
+
+ await createCommentOrAnswerOrUpdatedContractNotification(
+ contract.id,
+ 'contract',
+ 'updated',
+ contractUpdater,
+ eventId,
+ sourceText,
+ contract
+ )
+}
diff --git a/functions/src/scripts/add-new-notification-preference.ts b/functions/src/scripts/add-new-notification-preference.ts
new file mode 100644
index 00000000..d7e7072b
--- /dev/null
+++ b/functions/src/scripts/add-new-notification-preference.ts
@@ -0,0 +1,27 @@
+import * as admin from 'firebase-admin'
+
+import { initAdmin } from './script-init'
+import { getAllPrivateUsers } from 'functions/src/utils'
+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({
+ notificationPreferences: {
+ ...privateUser.notificationPreferences,
+ opt_out_all: [],
+ },
+ })
+ })
+ )
+}
+
+if (require.main === module) main().then(() => process.exit())
diff --git a/functions/src/scripts/contest/bulk-resolve-markets.ts b/functions/src/scripts/contest/bulk-resolve-markets.ts
new file mode 100644
index 00000000..2fe07c92
--- /dev/null
+++ b/functions/src/scripts/contest/bulk-resolve-markets.ts
@@ -0,0 +1,63 @@
+// Run with `npx ts-node src/scripts/contest/resolve-markets.ts`
+
+const DOMAIN = 'dev.manifold.markets'
+// Dev API key for Cause Exploration Prizes (@CEP)
+const API_KEY = '188f014c-0ba2-4c35-9e6d-88252e281dbf'
+const GROUP_SLUG = 'cart-contest'
+
+// Can just curl /v0/group/{slug} to get a group
+async function getGroupBySlug(slug: string) {
+ const resp = await fetch(`https://${DOMAIN}/api/v0/group/${slug}`)
+ return await resp.json()
+}
+
+async function getMarketsByGroupId(id: string) {
+ // API structure: /v0/group/by-id/[id]/markets
+ const resp = await fetch(`https://${DOMAIN}/api/v0/group/by-id/${id}/markets`)
+ return await resp.json()
+}
+
+/* Example curl request:
+# Resolve a binary market
+$ curl https://manifold.markets/api/v0/market/{marketId}/resolve -X POST \
+ -H 'Content-Type: application/json' \
+ -H 'Authorization: Key {...}' \
+ --data-raw '{"outcome": "YES"}'
+*/
+async function resolveMarketById(
+ id: string,
+ outcome: 'YES' | 'NO' | 'MKT' | 'CANCEL'
+) {
+ const resp = await fetch(`https://${DOMAIN}/api/v0/market/${id}/resolve`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Key ${API_KEY}`,
+ },
+ body: JSON.stringify({
+ outcome,
+ }),
+ })
+ return await resp.json()
+}
+
+async function main() {
+ const group = await getGroupBySlug(GROUP_SLUG)
+ const markets = await getMarketsByGroupId(group.id)
+
+ // Count up some metrics
+ console.log('Number of markets', markets.length)
+ console.log(
+ 'Number of resolved markets',
+ markets.filter((m: any) => m.isResolved).length
+ )
+
+ // Resolve each market to NO
+ for (const market of markets) {
+ if (!market.isResolved) {
+ console.log(`Resolving market ${market.url} to NO`)
+ const resp = await resolveMarketById(market.id, 'NO')
+ }
+ }
+}
+main()
diff --git a/functions/src/scripts/convert-tag-to-group.ts b/functions/src/scripts/convert-tag-to-group.ts
index b2e4c4d8..e1330fe1 100644
--- a/functions/src/scripts/convert-tag-to-group.ts
+++ b/functions/src/scripts/convert-tag-to-group.ts
@@ -42,6 +42,7 @@ const createGroup = async (
totalContracts: contracts.length,
totalMembers: 1,
postIds: [],
+ pinnedItems: [],
}
await groupRef.create(group)
// create a GroupMemberDoc for the creator
diff --git a/functions/src/serve.ts b/functions/src/serve.ts
index 99ac6281..d861dcbc 100644
--- a/functions/src/serve.ts
+++ b/functions/src/serve.ts
@@ -29,6 +29,7 @@ import { getcurrentuser } from './get-current-user'
import { createpost } from './create-post'
import { savetwitchcredentials } from './save-twitch-credentials'
import { testscheduledfunction } from './test-scheduled-function'
+import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
type Middleware = (req: Request, res: Response, next: NextFunction) => void
const app = express()
@@ -61,6 +62,8 @@ addJsonEndpointRoute('/sellshares', sellshares)
addJsonEndpointRoute('/claimmanalink', claimmanalink)
addJsonEndpointRoute('/createmarket', createmarket)
addJsonEndpointRoute('/addliquidity', addliquidity)
+addJsonEndpointRoute('/addCommentBounty', addcommentbounty)
+addJsonEndpointRoute('/awardCommentBounty', awardcommentbounty)
addJsonEndpointRoute('/withdrawliquidity', withdrawliquidity)
addJsonEndpointRoute('/creategroup', creategroup)
addJsonEndpointRoute('/resolvemarket', resolvemarket)
diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts
index 418282c7..57a6d183 100644
--- a/functions/src/unsubscribe.ts
+++ b/functions/src/unsubscribe.ts
@@ -4,6 +4,7 @@ import { getPrivateUser } from './utils'
import { PrivateUser } from '../../common/user'
import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification'
import { notification_preference } from '../../common/user-notification-preferences'
+import { getFunctionUrl } from '../../common/api'
export const unsubscribe: EndpointDefinition = {
opts: { method: 'GET', minInstances: 1 },
@@ -20,6 +21,8 @@ export const unsubscribe: EndpointDefinition = {
res.status(400).send('Invalid subscription type parameter.')
return
}
+ const optOutAllType: notification_preference = 'opt_out_all'
+ const wantsToOptOutAll = notificationSubscriptionType === optOutAllType
const user = await getPrivateUser(id)
@@ -37,17 +40,22 @@ export const unsubscribe: EndpointDefinition = {
const update: Partial = {
notificationPreferences: {
...user.notificationPreferences,
- [notificationSubscriptionType]: previousDestinations.filter(
- (destination) => destination !== 'email'
- ),
+ [notificationSubscriptionType]: wantsToOptOutAll
+ ? previousDestinations.push('email')
+ : previousDestinations.filter(
+ (destination) => destination !== 'email'
+ ),
},
}
await firestore.collection('private-users').doc(id).update(update)
+ const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
- res.send(
- `
-
+ const optOutAllUrl = `${unsubscribeEndpoint}?id=${id}&type=${optOutAllType}`
+ if (wantsToOptOutAll) {
+ res.send(
+ `
+
@@ -163,19 +171,6 @@ export const unsubscribe: EndpointDefinition = {
-
-
-
- |
-
@@ -186,20 +181,9 @@ export const unsubscribe: EndpointDefinition = {
data-testid="4XoHRGw1Y">
- ${email} has been unsubscribed from email notifications related to:
+ ${email} has opted out of receiving unnecessary email notifications
-
-
- ${NOTIFICATION_DESCRIPTIONS[notificationSubscriptionType].detailed}.
-
-
-
-
- Click
- here
- to manage the rest of your notification settings.
-
|
@@ -219,9 +203,193 @@ export const unsubscribe: EndpointDefinition = {
+