首页 > 解决方案 > 更新firestore文档映射字段onWrite触发器的firebase函数仅适用于最后一个索引

问题描述

制作了一个云功能,它将发送邀请电子邮件,成功发送后它将更新地图对象字段,但由于某种原因,它仅适用于最后一个对象,而不是所有对象

功能码

exports.sendInvitationEmailForMembers = functions.firestore
  .document("/organization-members/{OMid}")
  .onWrite(async (snap, context) => {
    if (snap.after.data() === undefined) {
      return null;
    }
    let authData = nodeMailer.createTransport({
      host: "smtp.gmail.com",
      port: 465,
      secure: true,
      auth: {
        user: SENDER_EMAIL,
        pass: SENDER_PASSWORD,
      },
    });
    // Exit when the data is deleted.
    const id = context.params.OMid;
    const afterData = snap.after.data();
    const db = admin.firestore();
    for (var prop in afterData) {
      if (afterData[prop].status === "invited") {
        const email = afterData[prop].email;
        const name = afterData[prop].fullName;

        authData
          .sendMail({
            from: "info@smallworldmusic.com",
            to: `${email}`,
            subject: "Organization Team Invitation",
            text: `Dear ${name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
            html: `Dear ${name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
          })
          .then(() => {
            db.collection("organization-members")
              .doc(id)
              .update({ [[prop] + ".status"]: "pending" })
              .then(() => {
                console.log("Successfully updated");
              })
              .catch(() => {
                console.log("Error occured while updating invitation status");
              });
          })
          .catch((err) => console.log(err.message));
      }
    }
  });

请注意,仅更改了最后一个对象状态,但它应该更改了整个文档对象的状态

在此处输入图像描述

标签: node.jsfirebasegoogle-cloud-firestoregoogle-cloud-functions

解决方案


代码本身存在许多小问题。这些包括:

  • 您的onWrite函数是自触发的。每次发送电子邮件时,您当前都会使用该电子邮件的发送状态更新文档 - 但这意味着邀请会再次发送给尚未更新其状态的人。这意味着在最坏的情况下,如果您有N收件人 - 这些用户中的每一个都可能多次收到您的邀请电子邮件N
  • 同样,如果调用sendMail失败,则每次文档更新时都会重新尝试。如果一封特定的电子邮件始终失败,您可能会陷入无限循环。这可以通过设置"failed"状态来缓解。
  • 您的 GMail 身份验证详细信息似乎在您的代码中。考虑为此使用环境配置
  • 您的功能是连接到 GMail 以发送电子邮件,即使没有要发送的电子邮件。
  • 酌情使用console.logconsole.error,以便它们在 Cloud Functions 日志中正确显示。例如记录函数结束的原因。
  • 电子邮件的text属性不应包含 HTML 之类的<br>
exports.sendInvitationEmailForMembers = functions.firestore
  .document("/organization-members/{OMid}")
  .onWrite(async (change, context) => {
    // If deleted, cancel
    if (!change.after.exists) {
      console.log("Document deleted. Cancelling function.");
      return null;
    }
    
    const OMid = context.params.OMid; // keep parameter names consistent.
    const data = change.after.data();
    
    const pendingInvitations = [];
    for (const index in data) {
      if (data[index].status === "invited") {
        // collect needed information
        pendingInvitations.push({
          email: data.email,
          fullName: data.fullName,
          index
        });
        
        // alternatively: pendingInvitations.push({ ...data[index], index });
      }
    }
    
    // If no invites needed, cancel
    if (pendingInvitations.length == 0) {
      console.log("No invitations to be sent. Cancelling function.");
      return null;
    }
    
    // WARNING: Gmail has a sending limit of 500 emails per day.
    // See also: https://support.google.com/mail/answer/81126
    
    // Create transport to send emails
    const transport = nodeMailer.createTransport({
      host: "smtp.gmail.com",
      port: 465,
      secure: true,
      auth: {
        user: SENDER_EMAIL, // Suggestion: Use `functions.config()`, see above
        pass: SENDER_PASSWORD,
      },
    });
    
    // Create an object to store all the status changes
    const pendingDocUpdate = {};
    
    // For each invitation, attempt to send the email, and stash the result in `pendingDocUpdate`.
    // Wait until all emails have been sent.
    await Promise.all(pendingInvitations.map((invitee) => {
      return transport
          .sendMail({
            from: "info@smallworldmusic.com", // shouldn't this be SENDER_EMAIL?
            to: `${invitee.email}`,
            subject: "Organization Team Invitation",
            text: `Dear ${invitee.name}, you have been invited to join the team of 'organization name', please visit this link to sign up and join. http://localhost:3000/`,
            html: `Dear ${invitee.name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
          })
          .then(() => {
            console.log(`Sent invite to ${invitee.email}.`);
            pendingDocUpdate[`${invitee.index}.status`] = "pending";
          }, (err) => {
            console.error(`Failed to invite ${invitee.email}.`, err);
            pendingDocUpdate[`${invitee.index}.status`] = "failed"; // save failure, so that emails are not spammed at the invitee
          });
    }));
    
    // Update the document with the changes
    return admin.firestore()
      .doc(`/organization-members/${OMid}`)
      .update(pendingUpdates)
      .then(() => {
        console.log('Applied invitation status changes successfully. Finished.')
      }, (err) => {
        console.error('Error occured while updating invitation statuses.', err);
      });
  });

推荐阅读