From 7e2d2e762719e773577a9540a40624aa6f362287 Mon Sep 17 00:00:00 2001 From: aerhartic Date: Tue, 25 Mar 2025 20:12:58 -0400 Subject: [PATCH 1/3] feat: admin notifications for org sign up --- .../src/notifications/deliverNotifications.ts | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/functions/src/notifications/deliverNotifications.ts b/functions/src/notifications/deliverNotifications.ts index 2c70eda8b..278a03f03 100644 --- a/functions/src/notifications/deliverNotifications.ts +++ b/functions/src/notifications/deliverNotifications.ts @@ -107,6 +107,137 @@ const deliverEmailNotifications = async () => { await Promise.all(emailPromises) } +export const deliverOrgUpgradeStatus = async (userId: string, accept: boolean) => { + //// const reportRef = db.collection("users").doc(userId) + + type Profile = { + email: string, + fullName: string, + role: string, + } + + const userProf = await db + .collection("profiles") + //.where("id", "==", userId) + .get() + + functions.firestore.document(`/profiles/${userId}`) + .onWrite(async (snapshot, context) => { + console.log(snapshot.before.data) + console.log(snapshot.after.data) + + const userSnap = userProf.docs.map((doc => { + return doc + + })).find((doc => { + const profile = doc.data() as Profile + profile.role === 'statusPending' + + }))?.id + + const before = snapshot.before.data() as Profile + const after = snapshot.after.data() as Profile + if(before.role === 'statusPending' && after.role === 'organization') { + let statusMessage: {subject: string, text: string, html: string }; + + accept ? statusMessage = { + subject: "Organization Approved", + text: "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!", + html: "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!", + } : + statusMessage = { + subject:"Organization Denied", + text: "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", + html: "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", + } + + await db.collection("emails").add({ + to: ['aerhartic@gmail.com'], + message: statusMessage, + createdAt: Timestamp.now() + }) + } + if (!snapshot.after.exists) { + console.error("New snapshot does not exist") + return + } + }) + } + +export const adminNotification = async (orgName?: string, email?: string) => { + + + ////const admins = ['mvictor@mapletestimony.org', 'nsanders@mapletestimony.org'] + + functions.firestore.document("/profiles/") + .onWrite(async (snapshot, context) => { + console.log(snapshot.before.data) + console.log(snapshot.after.data) + + //if(snapshot.after.data.length > snapshot.before.data.length) { + await db.collection("emails").add({ + to: ['aerhartic@gmail.com'], + message: { + subject: `The organization test requests approval`, + text: `The organization test has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${email}`, + html: `The organization test has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${email}`, + } , + createdAt: Timestamp.now() + }) + // } + if (!snapshot.after.exists) { + console.error("New snapshot does not exist") + return + } + }) + } + + export type Report = { + userId?: string, + userName?: string | null, + userEmail?: string | null, + } + + export const adminTestimonyNotification = async (report?: Report, testimonyTitle?: string, testimonyId?: string) => { + //// const users = await db.collection("/users/RxvVz9ua1GYxnYjPJQ6yKmEkdfC2").select("email").get() + + /* const orgUser = users.docs.find(async user => user?.email === userId) + + if(userAdmin1 && userAdmin2) { + const verifiedEmail = await getVerifiedUserEmail(userAdmin1.id) + const verifiedEmailalt = await getVerifiedUserEmail(reportRef?.id) + if (!verifiedEmail) { + console.log( + `Skipping user ${orgUser?.id} because they have no verified email address` + ) + return + } */ + + ////const admins = ['mvictor@mapletestimony.org', 'nsanders@mapletestimony.org'] + + // .firestore.document('/profiles/${resourceName}/batches/{batchId}') + + + + await db.collection("emails").add({ + to: ['aerhartic@gmail.com'], + message: { + subject: `The testimony ${testimonyTitle} requests approval`, + text: `The testimony ${testimonyTitle} has been reported and requires approval. Please respond accordingly, you can also contact them at ${report?.userEmail}`, + html: `The testimony ${testimonyTitle} has been reported and requires approval. Please respond accordingly, you can also contact them at ${report?.userEmail}`, + } , + createdAt: Timestamp.now() + + + /* const profilesSnapshot = await db + .document("profiles").onCreate() + //.where("nextDigestAt", "<=", now) + .get() + // */ + + }) + } + // TODO: Unit tests const buildDigestData = async ( userId: string, @@ -239,3 +370,48 @@ export const httpsDeliverNotifications = functions.https.onRequest( } } ) + +export const httpsDeliverOrgUpgradeStatus = functions.https.onRequest( + async (request, response) => { + try { + await deliverEmailNotifications() + + console.log("DEBUG: deliverNotifications completed") + + response.status(200).send("Successfully delivered notifications") + } catch (error) { + console.error("Error in deliverNotifications:", error) + response.status(500).send("Internal server error") + } + } +) + +export const httpsDeliverAdminNotifications = functions.https.onRequest( + async (request, response) => { + try { + await adminNotification() + + console.log("DEBUG: deliverAdminNotifications completed") + + response.status(200).send("Successfully delivered admin notifications") + } catch (error) { + console.error("Error in deliverNotifications:", error) + response.status(500).send("Internal server error") + } + } +) + +export const httpsDeliverAdminTestinomyNotifications = functions.https.onRequest( + async (request, response) => { + try { + await deliverEmailNotifications() + + console.log("DEBUG: deliverNotifications completed") + + response.status(200).send("Successfully delivered notifications") + } catch (error) { + console.error("Error in deliverNotifications:", error) + response.status(500).send("Internal server error") + } + } +) From 5b92ad3203c71792de9d8840a887cfc13b25b4a2 Mon Sep 17 00:00:00 2001 From: aerhartic Date: Tue, 25 Mar 2025 20:17:47 -0400 Subject: [PATCH 2/3] index files and runnotifications --- functions/src/index.ts | 1 + functions/src/notifications/index.ts | 4 +++- scripts/firebase-admin/runNotificationFunctions.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 1230f3390..a64e4cce9 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -37,6 +37,7 @@ export { cleanupNotifications, deliverNotifications, httpsDeliverNotifications, + httpsDeliverAdminNotifications, httpsCleanupNotifications, updateUserNotificationFrequency } from "./notifications" diff --git a/functions/src/notifications/index.ts b/functions/src/notifications/index.ts index 203f603ad..62a24c859 100644 --- a/functions/src/notifications/index.ts +++ b/functions/src/notifications/index.ts @@ -8,7 +8,8 @@ import { } from "./cleanupNotifications" import { deliverNotifications, - httpsDeliverNotifications + httpsDeliverNotifications, + httpsDeliverAdminNotifications, } from "./deliverNotifications" import { updateUserNotificationFrequency } from "./updateUserNotificationFrequency" @@ -20,6 +21,7 @@ export { cleanupNotifications, deliverNotifications, httpsDeliverNotifications, + httpsDeliverAdminNotifications, httpsCleanupNotifications, updateUserNotificationFrequency } diff --git a/scripts/firebase-admin/runNotificationFunctions.ts b/scripts/firebase-admin/runNotificationFunctions.ts index ec3c673ab..89d9aac5c 100644 --- a/scripts/firebase-admin/runNotificationFunctions.ts +++ b/scripts/firebase-admin/runNotificationFunctions.ts @@ -13,6 +13,7 @@ const notificationFunctions: { [K in FunctionName]?: K } = { httpsDeliverNotifications: "httpsDeliverNotifications", + httpsDeliverAdminNotifications: "httpsDeliverAdminNotifications", httpsCleanupNotifications: "httpsCleanupNotifications" } From 2f8e0133521bf1e75357449881fad04800d19e91 Mon Sep 17 00:00:00 2001 From: aerhartic Date: Tue, 8 Apr 2025 21:00:01 -0400 Subject: [PATCH 3/3] feat: notifications emails --- functions/src/index.ts | 7 +- .../src/notifications/deliverNotifications.ts | 221 +++++++----------- functions/src/notifications/index.ts | 12 +- 3 files changed, 106 insertions(+), 134 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index a64e4cce9..9fe6b3ffb 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -38,8 +38,13 @@ export { deliverNotifications, httpsDeliverNotifications, httpsDeliverAdminNotifications, + adminNotification, httpsCleanupNotifications, - updateUserNotificationFrequency + updateUserNotificationFrequency, + deliverOrgUpgradeStatus, + httpsDeliverAdminTestinomyNotifications, + adminTestimonyNotification, + httpsDeliverOrgUpgradeStatus } from "./notifications" export { diff --git a/functions/src/notifications/deliverNotifications.ts b/functions/src/notifications/deliverNotifications.ts index 278a03f03..d12cd362a 100644 --- a/functions/src/notifications/deliverNotifications.ts +++ b/functions/src/notifications/deliverNotifications.ts @@ -106,137 +106,95 @@ const deliverEmailNotifications = async () => { // Wait for all email documents to be created await Promise.all(emailPromises) } +type ProfileData = { + email: string + fullName: string + role: string +} -export const deliverOrgUpgradeStatus = async (userId: string, accept: boolean) => { - //// const reportRef = db.collection("users").doc(userId) - - type Profile = { - email: string, - fullName: string, - role: string, - } - - const userProf = await db - .collection("profiles") - //.where("id", "==", userId) - .get() - - functions.firestore.document(`/profiles/${userId}`) - .onWrite(async (snapshot, context) => { - console.log(snapshot.before.data) - console.log(snapshot.after.data) - - const userSnap = userProf.docs.map((doc => { - return doc - - })).find((doc => { - const profile = doc.data() as Profile - profile.role === 'statusPending' - - }))?.id - - const before = snapshot.before.data() as Profile - const after = snapshot.after.data() as Profile - if(before.role === 'statusPending' && after.role === 'organization') { - let statusMessage: {subject: string, text: string, html: string }; - - accept ? statusMessage = { - subject: "Organization Approved", - text: "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!", - html: "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!", - } : - statusMessage = { - subject:"Organization Denied", - text: "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", - html: "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", - } +export const deliverOrgUpgradeStatus = functions.firestore + .document("profiles/{userId}") + .onWrite(async (snapshot, context) => { + const before = snapshot.before.data() as ProfileData + const after = snapshot.after.data() as ProfileData + if (before.role === "rolePending") { await db.collection("emails").add({ - to: ['aerhartic@gmail.com'], - message: statusMessage, + to: [ + "aerhartic@gmail.com", + "mvictor@mapletestimony.org", + "nsanders@mapletestimony.org" + ], + subject: `Organization ${ + after.role === "organization" ? "Approved" : "Denied" + }`, + text: + after.role === "organization" + ? "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!" + : "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", + html: + after.role === "organization" + ? "Your organization's profile has been approved on MAPLE. Thank you for signing up! You can now post testimony for your community to see!" + : "Unfortunately, your request for an organization profile on MAPLE was denied. We apologize for any confusion. Please email admin@mapletestimony.org for further discussion.", createdAt: Timestamp.now() }) + if (!snapshot.after.exists) { + console.error("New snapshot does not exist") + return + } } - if (!snapshot.after.exists) { - console.error("New snapshot does not exist") - return - } - }) - } - -export const adminNotification = async (orgName?: string, email?: string) => { - - - ////const admins = ['mvictor@mapletestimony.org', 'nsanders@mapletestimony.org'] - - functions.firestore.document("/profiles/") - .onWrite(async (snapshot, context) => { - console.log(snapshot.before.data) - console.log(snapshot.after.data) + }) - //if(snapshot.after.data.length > snapshot.before.data.length) { - await db.collection("emails").add({ - to: ['aerhartic@gmail.com'], - message: { - subject: `The organization test requests approval`, - text: `The organization test has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${email}`, - html: `The organization test has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${email}`, - } , +export const adminNotification = functions.firestore + .document("profiles/{userId}") + .onCreate(async (snapshot, context) => { + const organization = snapshot.data() as ProfileData + + await db.collection("emails").add({ + to: [ + "aerhartic@gmail.com", + "mvictor@mapletestimony.org", + "nsanders@mapletestimony.org" + ], + message: { + subject: `${organization.fullName} requests approval as an Organization`, + text: `${organization.fullName} has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${organization.email}`, + html: `${organization.fullName} has signed up and now requests approval. Please decide whether to accept or reject their request. Along with responding via the Admin page, you can also contact them at ${organization.email}` + }, createdAt: Timestamp.now() }) - // } - if (!snapshot.after.exists) { - console.error("New snapshot does not exist") - return - } - }) - } - - export type Report = { - userId?: string, - userName?: string | null, - userEmail?: string | null, - } - - export const adminTestimonyNotification = async (report?: Report, testimonyTitle?: string, testimonyId?: string) => { - //// const users = await db.collection("/users/RxvVz9ua1GYxnYjPJQ6yKmEkdfC2").select("email").get() - - /* const orgUser = users.docs.find(async user => user?.email === userId) - - if(userAdmin1 && userAdmin2) { - const verifiedEmail = await getVerifiedUserEmail(userAdmin1.id) - const verifiedEmailalt = await getVerifiedUserEmail(reportRef?.id) - if (!verifiedEmail) { - console.log( - `Skipping user ${orgUser?.id} because they have no verified email address` - ) - return - } */ - - ////const admins = ['mvictor@mapletestimony.org', 'nsanders@mapletestimony.org'] - - // .firestore.document('/profiles/${resourceName}/batches/{batchId}') - - - - await db.collection("emails").add({ - to: ['aerhartic@gmail.com'], - message: { - subject: `The testimony ${testimonyTitle} requests approval`, - text: `The testimony ${testimonyTitle} has been reported and requires approval. Please respond accordingly, you can also contact them at ${report?.userEmail}`, - html: `The testimony ${testimonyTitle} has been reported and requires approval. Please respond accordingly, you can also contact them at ${report?.userEmail}`, - } , - createdAt: Timestamp.now() - - - /* const profilesSnapshot = await db - .document("profiles").onCreate() - //.where("nextDigestAt", "<=", now) - .get() - // */ - + // } + if (!snapshot.exists) { + console.error("New snapshot does not exist") + return + } + }) +export const adminTestimonyNotification = functions.firestore + .document("reports/{reportId}") + .onCreate(async (snapshot, context) => { + type Report = { + reportId?: string + testimonyId?: string + } + const testimony = snapshot.data() as Report + await db.collection("emails").add({ + to: [ + "aerhartic@gmail.com", + "mvictor@mapletestimony.org", + "nsanders@mapletestimony.org" + ], + message: { + subject: `The testimony report ${testimony.reportId} requests approval`, + text: `The testimony ${testimony.testimonyId} has been reported and requires approval; please respond accordingly for report ${testimony.reportId}.`, + html: `The testimony ${testimony.testimonyId} has been reported and requires approval; please respond accordingly for report ${testimony.reportId}.` + }, + createdAt: Timestamp.now() }) - } + if (!snapshot.exists) { + console.error("New snapshot does not exist") + return + } + }) // TODO: Unit tests const buildDigestData = async ( @@ -374,22 +332,22 @@ export const httpsDeliverNotifications = functions.https.onRequest( export const httpsDeliverOrgUpgradeStatus = functions.https.onRequest( async (request, response) => { try { - await deliverEmailNotifications() + deliverOrgUpgradeStatus - console.log("DEBUG: deliverNotifications completed") + console.log("DEBUG: deliverOrgUpgradeStatus completed") - response.status(200).send("Successfully delivered notifications") + response.status(200).send("Successfully deliverOrgUpgradeStatus") } catch (error) { - console.error("Error in deliverNotifications:", error) + console.error("Error in deliverOrgUpgradeStatus:", error) response.status(500).send("Internal server error") } } -) +) export const httpsDeliverAdminNotifications = functions.https.onRequest( async (request, response) => { try { - await adminNotification() + adminNotification console.log("DEBUG: deliverAdminNotifications completed") @@ -401,10 +359,10 @@ export const httpsDeliverAdminNotifications = functions.https.onRequest( } ) -export const httpsDeliverAdminTestinomyNotifications = functions.https.onRequest( - async (request, response) => { +export const httpsDeliverAdminTestinomyNotifications = + functions.https.onRequest(async (request, response) => { try { - await deliverEmailNotifications() + adminTestimonyNotification console.log("DEBUG: deliverNotifications completed") @@ -413,5 +371,4 @@ export const httpsDeliverAdminTestinomyNotifications = functions.https.onRequest console.error("Error in deliverNotifications:", error) response.status(500).send("Internal server error") } - } -) + }) diff --git a/functions/src/notifications/index.ts b/functions/src/notifications/index.ts index 62a24c859..ebd24e69e 100644 --- a/functions/src/notifications/index.ts +++ b/functions/src/notifications/index.ts @@ -10,6 +10,11 @@ import { deliverNotifications, httpsDeliverNotifications, httpsDeliverAdminNotifications, + adminNotification, + deliverOrgUpgradeStatus, + httpsDeliverOrgUpgradeStatus, + adminTestimonyNotification, + httpsDeliverAdminTestinomyNotifications } from "./deliverNotifications" import { updateUserNotificationFrequency } from "./updateUserNotificationFrequency" @@ -22,6 +27,11 @@ export { deliverNotifications, httpsDeliverNotifications, httpsDeliverAdminNotifications, + adminNotification, httpsCleanupNotifications, - updateUserNotificationFrequency + updateUserNotificationFrequency, + deliverOrgUpgradeStatus, + httpsDeliverAdminTestinomyNotifications, + adminTestimonyNotification, + httpsDeliverOrgUpgradeStatus }