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) => {