node.js - 更新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));
}
}
});
请注意,仅更改了最后一个对象状态,但它应该更改了整个文档对象的状态
解决方案
代码本身存在许多小问题。这些包括:
- 您的
onWrite
函数是自触发的。每次发送电子邮件时,您当前都会使用该电子邮件的发送状态更新文档 - 但这意味着邀请会再次发送给尚未更新其状态的人。这意味着在最坏的情况下,如果您有N
收件人 - 这些用户中的每一个都可能多次收到您的邀请电子邮件N
。 - 同样,如果调用
sendMail
失败,则每次文档更新时都会重新尝试。如果一封特定的电子邮件始终失败,您可能会陷入无限循环。这可以通过设置"failed"
状态来缓解。 - 您的 GMail 身份验证详细信息似乎在您的代码中。考虑为此使用环境配置。
- 您的功能是连接到 GMail 以发送电子邮件,即使没有要发送的电子邮件。
- 酌情使用
console.log
和console.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);
});
});
推荐阅读
- node.js - 获取节点变量以反应组件
- odoo - Odoo Orm 日期过滤器?
- docker - Docker:“来自守护程序的错误响应:端口不可用:无法列出暴露的端口”
- python - 任务期间的 Dask 进度
- apache-kafka - 是否有kafka push 插件可用于web analytic matomo v3.13
- laravel - 将数组中的自定义变量从 COntroller 发送到 Laravel 中的通知
- javascript - 我不是在使用 JS vanilla 在数据库中查找数据
- django - 带有通过参数的Django ForeignKey
- flask - 即使明确定义了路线,Flask 也会返回 404
- python-3.x - hits>total - 限制为 10000 条记录 - 增加限制