From 16ce5619b71182a536a6cb59727941086ac1ce70 Mon Sep 17 00:00:00 2001 From: Ismael Dosil Date: Wed, 25 Mar 2026 19:02:18 -0300 Subject: [PATCH] fix(help): use Firestore trigger instead of httpsCallable Switch from Cloud Functions httpsCallable to Firestore onCreate trigger to avoid IAM permission requirements. The frontend now writes to helpRequests collection, and a Firestore trigger sends the email. Closes CHALK-097 --- functions/funcSendHelpRequest/index.js | 86 +++++++++++--------------- src/components/Firebase/Firebase.tsx | 26 ++++---- 2 files changed, 50 insertions(+), 62 deletions(-) diff --git a/functions/funcSendHelpRequest/index.js b/functions/funcSendHelpRequest/index.js index 328d79a13..3d0e07184 100644 --- a/functions/funcSendHelpRequest/index.js +++ b/functions/funcSendHelpRequest/index.js @@ -1,61 +1,47 @@ const sgMail = require("@sendgrid/mail"); const functions = require("firebase-functions"); -const {getUser} = require("../common/accessUtils"); sgMail.setApiKey(functions.config().sendgrid ? functions.config().sendgrid.key : ""); const HELP_RECIPIENT = "contact@chalkcoaching.com"; const SENDER_ADDRESS = "chalkcoaching@gmail.com"; -exports.funcSendHelpRequest = functions.https.onCall(async (data, context) => { - if (!context.auth) { - throw new functions.https.HttpsError( - "unauthenticated", - "User must be logged in to submit a help request." - ); - } +exports.funcSendHelpRequest = functions.firestore + .document("helpRequests/{requestId}") + .onCreate(async (snap) => { + const data = snap.data(); + const {message, userName, userEmail, userRole} = data; - const {message} = data; - if (!message || message.trim().length === 0) { - throw new functions.https.HttpsError( - "invalid-argument", - "Message is required." - ); - } + if (!message || message.trim().length === 0) { + console.error("Empty help request message"); + return snap.ref.update({status: "error", error: "Empty message"}); + } - const userData = await getUser(context.auth.uid); - const userName = `${userData.firstName} ${userData.lastName}`; - const userEmail = userData.email; - const userRole = userData.role; + const emailMessage = { + to: HELP_RECIPIENT, + replyTo: userEmail, + from: SENDER_ADDRESS, + subject: `CHALK Help Request from ${userName}`, + text: [ + `Help request from: ${userName}`, + `Email: ${userEmail}`, + `Role: ${userRole}`, + ``, + `Message:`, + message, + ``, + `---`, + `Sent from CHALK Coaching` + ].join("\n") + }; - const emailMessage = { - to: HELP_RECIPIENT, - replyTo: userEmail, - from: SENDER_ADDRESS, - subject: `CHALK Help Request from ${userName}`, - text: [ - `Help request from: ${userName}`, - `Email: ${userEmail}`, - `Role: ${userRole}`, - ``, - `Message:`, - message, - ``, - `---`, - `Sent from CHALK Coaching` - ].join("\n") - }; - - return sgMail.send(emailMessage) - .then(() => { - console.log("Help request email sent"); - return {success: true}; - }) - .catch((err) => { - console.error("Error sending help request:", JSON.stringify(err)); - throw new functions.https.HttpsError( - "internal", - "Failed to send help request. Please try again." - ); - }); -}); + return sgMail.send(emailMessage) + .then(() => { + console.log("Help request email sent"); + return snap.ref.update({status: "sent"}); + }) + .catch((err) => { + console.error("Error sending help request:", JSON.stringify(err)); + return snap.ref.update({status: "error", error: err.message}); + }); + }); diff --git a/src/components/Firebase/Firebase.tsx b/src/components/Firebase/Firebase.tsx index 9e6357dcf..b383e0867 100644 --- a/src/components/Firebase/Firebase.tsx +++ b/src/components/Firebase/Firebase.tsx @@ -414,18 +414,20 @@ class Firebase { } sendHelpRequest = async (message: string): Promise<{success: boolean}> => { - const sendHelpRequestFunction = this.functions.httpsCallable( - 'funcSendHelpRequest' - ) - return sendHelpRequestFunction({message}) - .then(result => { - console.log('Help request sent:', result) - return {success: true} - }) - .catch(error => { - console.error('Error sending help request:', error) - throw error - }) + if (!this.auth.currentUser) { + throw new Error('User must be logged in to submit a help request.') + } + const userDoc = await this.getUserInformation() + await this.db.collection('helpRequests').add({ + message, + userName: `${userDoc.firstName} ${userDoc.lastName}`, + userEmail: userDoc.email, + userRole: userDoc.role, + userId: this.auth.currentUser.uid, + status: 'pending', + dateCreated: new Date() + }) + return {success: true} } /**