From dcbdc66df52cfddb551ea9091395496591835961 Mon Sep 17 00:00:00 2001 From: mantikoros <95266179+mantikoros@users.noreply.github.com> Date: Thu, 17 Feb 2022 12:18:02 -0600 Subject: [PATCH] Close emails (#50) * script init for stephen dev * market close emails * order of operations * template email * sendMarketCloseEmail: handle unsubscribe * remove debugging * marketCloseEmails: every hour --- common/contract.ts | 1 + functions/src/emails.ts | 38 +++++++++++++++++- functions/src/index.ts | 1 + functions/src/market-close-emails.ts | 58 ++++++++++++++++++++++++++++ functions/src/scripts/script-init.ts | 2 +- 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 functions/src/market-close-emails.ts diff --git a/common/contract.ts b/common/contract.ts index ed642cfa..f5733059 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -28,6 +28,7 @@ export type Contract = { resolutionTime?: number // When the contract creator resolved the market resolution?: outcome // Chosen by creator; must be one of outcomes resolutionProbability?: number + closeEmailsSent?: number volume24Hours: number volume7Days: number diff --git a/functions/src/emails.ts b/functions/src/emails.ts index 3dbfeb53..ba20df1e 100644 --- a/functions/src/emails.ts +++ b/functions/src/emails.ts @@ -1,7 +1,9 @@ +import _ = require('lodash') import { getProbability } from '../../common/calculate' import { Contract } from '../../common/contract' +import { CREATOR_FEE } from '../../common/fees' import { PrivateUser, User } from '../../common/user' -import { formatPercent } from '../../common/util/format' +import { formatMoney, formatPercent } from '../../common/util/format' import { sendTemplateEmail, sendTextEmail } from './send-email' import { getPrivateUser, getUser } from './utils' @@ -88,3 +90,37 @@ Austin from Manifold https://manifold.markets/` ) } + +export const sendMarketCloseEmail = async ( + user: User, + privateUser: PrivateUser, + contract: Contract +) => { + if ( + !privateUser || + privateUser.unsubscribedFromResolutionEmails || + !privateUser.email + ) + return + + const { username, name, id: userId } = user + const firstName = name.split(' ')[0] + + const { question, pool: pools, slug } = contract + const pool = formatMoney(_.sum(_.values(pools))) + const url = `https://manifold.markets/${username}/${slug}` + + await sendTemplateEmail( + privateUser.email, + 'Your market has closed', + 'market-close', + { + name: firstName, + question, + pool, + url, + userId, + creatorFee: (CREATOR_FEE * 100).toString(), + } + ) +} diff --git a/functions/src/index.ts b/functions/src/index.ts index f46e72a8..202136c9 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -18,3 +18,4 @@ export * from './update-contract-metrics' export * from './update-user-metrics' export * from './backup-db' export * from './change-user-info' +export * from './market-close-emails' diff --git a/functions/src/market-close-emails.ts b/functions/src/market-close-emails.ts new file mode 100644 index 00000000..83caca89 --- /dev/null +++ b/functions/src/market-close-emails.ts @@ -0,0 +1,58 @@ +import * as functions from 'firebase-functions' +import * as admin from 'firebase-admin' + +import { Contract } from '../../common/contract' +import { getPrivateUser, getUserByUsername } from './utils' +import { sendMarketCloseEmail } from './emails' + +export const marketCloseEmails = functions.pubsub + .schedule('every hour') + .onRun(async () => { + await sendMarketCloseEmails() + }) + +const firestore = admin.firestore() + +async function sendMarketCloseEmails() { + const contracts = await firestore.runTransaction(async (transaction) => { + const snap = await transaction.get( + firestore.collection('contracts').where('isResolved', '!=', true) + ) + + return snap.docs + .map((doc) => { + const contract = doc.data() as Contract + + if ( + contract.resolution || + (contract.closeEmailsSent ?? 0) >= 1 || + (contract.closeTime ?? 0) > Date.now() + ) + return undefined + + transaction.update(doc.ref, { + closeEmailsSent: (contract.closeEmailsSent ?? 0) + 1, + }) + + return contract + }) + .filter((x) => !!x) as Contract[] + }) + + for (let contract of contracts) { + console.log( + 'sending close email for', + contract.slug, + 'closed', + contract.closeTime + ) + + const user = await getUserByUsername(contract.creatorUsername) + if (!user) continue + + const privateUser = await getPrivateUser(user.id) + if (!privateUser) continue + + await sendMarketCloseEmail(user, privateUser, contract) + } +} diff --git a/functions/src/scripts/script-init.ts b/functions/src/scripts/script-init.ts index 5924ad1d..9a7c1b5d 100644 --- a/functions/src/scripts/script-init.ts +++ b/functions/src/scripts/script-init.ts @@ -14,7 +14,7 @@ const pathsToPrivateKey = { stephen: '../../../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-351a65eca3.json', stephenDev: - '../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json', + '../../../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json', } export const initAdmin = (who: keyof typeof pathsToPrivateKey) => {