Skip to content

Commit 8032ba1

Browse files
authored
Merge pull request #1715 from Mephistic/email-sending-script
Test script for sending the notifications email digest email
2 parents 60a8250 + 4fe98a6 commit 8032ba1

File tree

3 files changed

+228
-1
lines changed

3 files changed

+228
-1
lines changed

functions/src/notifications/deliverNotifications.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
NotificationEmailDigest,
1212
Position,
1313
UserDigest
14-
} from "../email/types"
14+
} from "./emailTypes"
1515
import { prepareHandlebars } from "../email/handlebarsHelpers"
1616
import { getAuth } from "firebase-admin/auth"
1717
import { Frequency } from "../auth/types"
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import * as handlebars from "handlebars"
2+
import * as fs from "fs"
3+
import { Script } from "./types"
4+
import * as helpers from "../../functions/src/email/helpers"
5+
6+
import {
7+
BillDigest,
8+
BillResult,
9+
NotificationEmailDigest,
10+
UserDigest
11+
} from "functions/src/notifications/emailTypes"
12+
import { Record, String } from "runtypes"
13+
import { Timestamp } from "functions/src/firebase"
14+
import { Frequency } from "components/auth"
15+
16+
const path = require("path")
17+
18+
const PARTIALS_DIR = "./functions/src/email/partials/"
19+
const EMAIL_TEMPLATE_PATH = "./functions/src/email/digestEmail.handlebars"
20+
21+
// Define Handlebars helper functions
22+
handlebars.registerHelper("toLowerCase", helpers.toLowerCase)
23+
handlebars.registerHelper("noUpdatesFormat", helpers.noUpdatesFormat)
24+
handlebars.registerHelper("isDefined", helpers.isDefined)
25+
function registerPartials(directoryPath: string) {
26+
console.log("REGISTERING PARTIALS")
27+
28+
const filenames = fs.readdirSync(directoryPath)
29+
30+
filenames.forEach(filename => {
31+
const partialPath = path.join(directoryPath, filename)
32+
const stats = fs.statSync(partialPath)
33+
34+
if (stats.isDirectory()) {
35+
// Recursive call for directories
36+
registerPartials(partialPath)
37+
} else if (stats.isFile() && path.extname(filename) === ".handlebars") {
38+
// Register partials for .handlebars files
39+
const partialName = path.basename(filename, ".handlebars")
40+
const partialContent = fs.readFileSync(partialPath, "utf8")
41+
handlebars.registerPartial(partialName, partialContent)
42+
}
43+
})
44+
}
45+
46+
const renderToHtmlString = (digestData: NotificationEmailDigest) => {
47+
// TODO: Can we register these earlier since they're shared across all notifs - maybe at startup?
48+
registerPartials(PARTIALS_DIR)
49+
50+
console.log("DEBUG: Working directory: ", process.cwd())
51+
console.log(
52+
"DEBUG: Digest template path: ",
53+
path.resolve(EMAIL_TEMPLATE_PATH)
54+
)
55+
56+
const templateSource = fs.readFileSync(
57+
path.resolve(EMAIL_TEMPLATE_PATH),
58+
"utf8"
59+
)
60+
const compiledTemplate = handlebars.compile(templateSource)
61+
return compiledTemplate({ digestData })
62+
}
63+
64+
// Summary of Bills
65+
const bills: BillDigest[] = [
66+
{
67+
billId: "H868",
68+
billName:
69+
"An Act improving campaign finance reporting by state ballot question committees",
70+
billCourt: "194",
71+
endorseCount: 2,
72+
neutralCount: 0,
73+
opposeCount: 1
74+
},
75+
{
76+
billId: "H1436",
77+
billName: "An Act relative to debt-free public higher education",
78+
billCourt: "194",
79+
endorseCount: 2,
80+
neutralCount: 0,
81+
opposeCount: 0
82+
},
83+
{
84+
billId: "H533",
85+
billName: "An Act to expand the use of career and academic plans",
86+
billCourt: "194",
87+
endorseCount: 10,
88+
neutralCount: 2,
89+
opposeCount: 24
90+
},
91+
{
92+
billId: "H841",
93+
billName:
94+
"An Act granting the city of Boston the authority to endow legal voting rights in municipal elections for city of Boston residents aged 16 and 17 years old",
95+
billCourt: "194",
96+
endorseCount: 35,
97+
neutralCount: 20,
98+
opposeCount: 10
99+
},
100+
{
101+
billId: "H54",
102+
billName:
103+
"An Act to build resilient infrastructure to generate higher-ed transformation",
104+
billCourt: "194",
105+
endorseCount: 0,
106+
neutralCount: 0,
107+
opposeCount: 1
108+
}
109+
]
110+
111+
const billResults: BillResult[] = [
112+
{
113+
billId: "H868",
114+
court: "194",
115+
position: "endorse"
116+
},
117+
{
118+
billId: "H1436",
119+
court: "194",
120+
position: "neutral"
121+
},
122+
{
123+
billId: "H533",
124+
court: "194",
125+
position: "oppose"
126+
},
127+
{
128+
billId: "H841",
129+
court: "194",
130+
position: "endorse"
131+
},
132+
{
133+
billId: "H54",
134+
court: "194",
135+
position: "oppose"
136+
},
137+
{
138+
billId: "H66",
139+
court: "194",
140+
position: "neutral"
141+
},
142+
{
143+
billId: "H30",
144+
court: "194",
145+
position: "endorse"
146+
}
147+
]
148+
149+
const generateTestUserData = (
150+
userId: string,
151+
userName: string,
152+
numBillsWithTestimony: number
153+
): UserDigest => {
154+
return {
155+
userId,
156+
userName,
157+
bills: billResults.slice(0, Math.min(6, numBillsWithTestimony)),
158+
newTestimonyCount: numBillsWithTestimony
159+
}
160+
}
161+
162+
const users = [
163+
generateTestUserData("0BvO7rSlFjRVHuLfd7RlHRYg2DN1", "John Doe", 7),
164+
generateTestUserData("2jBTpZQ1kXVVSaJvLy2mxfduoc64", "Jane Roe", 6),
165+
generateTestUserData(
166+
"381slAnGbzP6atlF4Af4D9pYQT24",
167+
"Society for the Humane Prevention of Testimony",
168+
5
169+
),
170+
generateTestUserData("Nyvk23VDNQSoK9TQ9LK5xF1DwT64", "Person McPersonson", 4),
171+
generateTestUserData("QDPq42rNB0O6wqVzfMmDHmNE8sN3", "Iranout Ofnameideas", 3)
172+
]
173+
174+
const generateTestData = (
175+
frequency: Frequency,
176+
numBills: number,
177+
numUsers: number
178+
): NotificationEmailDigest => {
179+
return {
180+
notificationFrequency: frequency,
181+
startDate: new Date("2025-04-01T04:00:00Z"),
182+
endDate: new Date(
183+
`2025-04-${frequency === "Monthly" ? "30" : "07"}T04:00:00Z`
184+
),
185+
bills: bills.slice(0, Math.min(4, numBills)),
186+
users: users.slice(0, Math.min(4, numUsers)),
187+
numBillsWithNewTestimony: numBills,
188+
numUsersWithNewTestimony: numUsers
189+
}
190+
}
191+
192+
const Args = Record({ email: String })
193+
194+
// Send a test email with:
195+
// yarn firebase-admin -e dev run-script sendTestEmail --email="[email protected]"
196+
export const script: Script = async ({ db, args }) => {
197+
const { email } = Args.check(args)
198+
199+
// Frequency is guaranteed to be Monthly or Weekly,
200+
// and there must be at least 1 bill OR 1 user with testimony
201+
// or else a digest wouldn't be generated
202+
const digestData = generateTestData("Monthly", 4, 4)
203+
204+
// const onlyBills = generateTestData("Weekly", 4, 0)
205+
// const onlyUsers = generateTestData("Weekly", 0, 4)
206+
// const oddNumbers = generateTestData("Monthly", 1, 3)
207+
// const tooManyBills = generateTestData("Monthly", 100, 0)
208+
// const tooManyUsers = generateTestData("Monthly", 0, 100)
209+
// const tooManyBillsAndUsers = generateTestData("Monthly", 100, 100)
210+
211+
const htmlString = renderToHtmlString(digestData)
212+
213+
console.log("DEBUG: HTML String: ", htmlString)
214+
console.log("DEBUG: Email: ", email)
215+
216+
// Create an email document in /notifications_mails to queue up the send
217+
const result = await db.collection("emails").add({
218+
to: [email],
219+
message: {
220+
subject: "Test Notifications Digest",
221+
html: htmlString
222+
},
223+
createdAt: Timestamp.now()
224+
})
225+
226+
console.log("DEBUG: Email document created with ID: ", result.id)
227+
}

0 commit comments

Comments
 (0)