|
| 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