I am making a function for firebase cloud functions, I want a function to be called every time a new document is created in "posts". I want this function to perform the tasks that I put inside the "onCeatePost" function.
The problem I have is that I'm not sure if this is the correct way to structure such a function.
In several firebase examples I have seen that it is always called return _; or return null; at the end of a task, but I don't know how to structure the function so that all the tasks are carried out, could someone help me to restructure my function or tell me what is wrong please.
There are several if statements in the function, if the created publication does not comply with them, I would like it to skip them but continue with the other tasks that I put inside the function.
I don't know if it's too much to ask, but I'm new to this language and I haven't been able to find the answer I'm looking for. Thank you!
exports.onPostCreate = functions.firestore.document("/posts/{postId}").onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db.collection("users").doc(uid).update({"stats.posts": admin.firestore.FieldValue.increment(1)});
if (topic) {
await db.collection("topics").doc(topic.id).collection("user-authors").doc(uid).set({"date": snap.createTime});
}
if (contentForFeed == true) {
const userPath = db.collection("users").doc(uid);
await userPath.update({"stats.lastUpdate": snap.createTime});
}
if (previous) {
const previousId = previous.id;
const previousUid = previous.uid;
const refPrev = db.collection("posts").doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(priority.seconds + 120, priority.nanoseconds);
await db.collection("posts").doc(previousId).update({"newDate": newDate});
});
if (previousUid != uid) {
const path = db.collection("users").doc(uid).collection("user-posts");
const dataToSet = {"timestamp": snap.createTime, "uid": uid, "postId": onReplyToPostId};
await path(dataToSet);
}
}
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});
You'll find below the adapted code (untested) with 4 corrections.
Here are explanations for the two most important ones:
(Correction 2) In a transaction you need to use the transaction's update() method and not the "standard one"
(Correction 4) When all the asynchronous work is complete you need to return a value or a Promise. See this documntation page for more details.
exports.onPostCreate = functions.firestore
.document('/posts/{postId}')
.onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db
.collection('users')
.doc(uid)
.update({
'stats.posts': admin.firestore.FieldValue.increment(1),
});
if (topic) {
await db
.collection('topics')
.doc(topic.id)
.collection('user-authors')
.doc(uid)
.set({ date: snap.createTime });
}
if (contentForFeed == true) {
const userPath = db.collection('users').doc(uid);
await userPath.update({ 'stats.lastUpdate': snap.createTime });
}
let previousUid; // <= Correction 1
if (previous) {
const previousId = previous.id;
previousUid = previous.uid; // <= Correction 1
const refPrev = db.collection('posts').doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(
priority.seconds + 120,
priority.nanoseconds
);
t.update(refPrev, { newDate: newDate }); // <= Correction 2
});
if (previousUid != uid) {
const path = db
.collection('users')
.doc(uid)
.collection('user-posts');
const dataToSet = {
timestamp: snap.createTime,
uid: uid,
postId: onReplyToPostId,
};
await path.add(dataToSet); // <= Correction 3
}
}
return null; // <= Correction 4
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});
I have a code that after find and populate, I will then add a new key ('order_date') based on createdAt.
const summaryGetOrders = async (start_date, end_date) => {
let orders = await Order.find({ createdAt: { $gte: new Date(start_date), $lt: new Date(end_date) } }).populate(
'orderdetails_id'
);
await Promise.all(
orders.map(async (i, index) => {
i.order_date = i.createdAt;
// orders[index] = i
// orders[index].order_date = i.createdAt
})
);
console.log(orders);
return orders;
};
I have checked with console.log(i.createdAt), it do have the date element in the i, but when I do console.log(orders), it is not retunring me order_date.
Is it caused by the async await function? I am doing this because my express boilerplate automatically removes the createdAt key via the toJSON function on my model. I wish not to modify because my code is tailor not to deal with createdAt.
By default, Mongoose queries return an instance of the Mongoose Document class, not plain old JavaScript objects (POJOs) so some fields that isn't defined in the schema will not show when you console.log it (You can check for more info here).
To solve that, you can use .lean() on your query to make the result become plain javascript objects.
Beside, you don't need to use Promise.all because map is not asynchronous and using forEach may be better in this case.
const summaryGetOrders = async (start_date, end_date) => {
let orders = await Order.find({ createdAt: { $gte: new Date(start_date), $lt: new Date(end_date) } }).populate(
'orderdetails_id'
).lean();
orders.forEach((i, index) => {
i.order_date = i.createdAt;
// orders[index] = i
// orders[index].order_date = i.createdAt
})
console.log(orders);
return orders;
};
You missing the '()' in return function so that it not return new value you defined in map. try this
const summaryGetOrders = async (start_date, end_date) => {
let orders = await Order.find({ createdAt: { $gte: new Date(start_date), $lt: new Date(end_date) } }).populate(
'orderdetails_id'
);
await Promise.all(
orders.map(async (i, index) => ({
i.order_date = i.createdAt;
// orders[index] = i
// orders[index].order_date = i.createdAt
}))
);
console.log(orders);
return orders;
};
orders.map will return a new array. orders variable itself will not be changed. So whatever you are adding is in the new array. Check this.
await Promise.all(
orders = orders.map(async (i, index) => ({
i.order_date = i.createdAt;
// orders[index] = i
// orders[index].order_date = i.createdAt
}))
);
Now, you orders will be reassigned to the new array and you can get the values in it.
here i have created a function , it is framing a object by getting two values as arguments. It is working fine. But in the code wise is not simple. It is not readable. Here i am sharing the code of what i have done. Please suggest me where i have to modify the code. In order to make it more clear and readable.
import uuid from 'uuid/v4';
const questionsObject = {};
const finalDndDataFormat = {};
let value = [{"groups":[{"notes":"","questions":[{"name":"loanAmount"}]},{"questions":[{"name":"loanReason"}]},{"questions":[{"name":"email","type":"email"}]},{"questions":[{"name":"title","required":true}]},{"questions":[{"name":"firstName"},{"name":"lastName"}]}],"id":"my-needs"},{"groups":[{"questions":[{"name":"phone"},{"name":"isCellPhone"}]},{"questions":[{"name":"dateOfBirth","options":[]}]},{"questions":[{"name":"residenceStatus","options":[],"label":"q_ownRent_text","type":"radio","required":true}]},{"questions":[{"name":"addressLine1"},{"name":"addressLine2"},{"name":"city"},{"name":"province","options":[]},{"name":"postalCode"}],"title":"q2_supporting_2"},{"questions":[{"name":"employmentStatus","options":[]}]}],"id":"a-bit-about-me"},{"groups":[{"questions":[{"name":"residenceDuration","options":[]}]},{"questions":[{"name":"previousAddressLine1"},{"name":"previousAddressLine2"},{"name":"previousCity"},{"name":"previousProvince"},{"name":"previousPostalCode"}],"title":"q2_supporting_2"},{"questions":[{"name":"previousResidenceDuration"}]},{"questions":[{"name":"residenceStatus"}]},{"questions":[{"name":"principalResidence"}]},{"questions":[{"name":"residenceType"}]},{"questions":[{"name":"propertyValue"}]},{"questions":[{"name":"monthlyMortgagePayments"}]},{"questions":[{"name":"mortgageBalance"}]}],"id":"my-home"},{"groups":[{"questions":[{"name":"employmentDuration"}]},{"questions":[{"name":"previousEmploymentDuration"}]},{"questions":[{"name":"paycheckAmount"}]},{"questions":[{"name":"paycheckFrequency"}]},{"questions":[{"name":"hasAdditionalIncome"},{"name":"additionalIncomeSource"}]},{"questions":[{"name":"hasChildTaxCredit"},{"name":"childTaxCreditMonthlyAmount"},{"name":"numberOfDependentsChildTaxCredit"}]},{"questions":[{"name":"sinNumber"}]}],"id":"my-income"},{"groups":[{"questions":[{"name":"hasPaydayLoan","options":[]},{"name":"paydayLoanAmountOwing"}]},{"questions":[{"name":"hasOtherLiabilities","options":[]},{"name":"otherLiabilitySource","options":[]}]},{"questions":[{"name":"isEasyHomeCustomer","options":[]},{"name":"easyhomePaymentsLiabilityMonthlyAmount"}]}],"id":"liabilities"},{"groups":[{"notes":"","questions":[{"name":"agreeTermsConditionsAndPrivacyPolicy","options":[],"links":[]}]}],"id":"t-and-c"}]
let appPages = ["my-needs", "a-bit-about-me", "my-home", "my-income", "liabilities", "t-and-c"]
const setDndDataStructure = (value, appPages) => {
const dndFormat = value?.map((data) => {
appPages.forEach((pages) => {
switch (data?.id) {
case pages:
const questions = data?.groups || {};
questions.map((obj) => {
obj.id = uuid();
const uniqueQuestionName = obj?.questions?.map((questionsField) => questionsField?.name);
obj.unique = uniqueQuestionName[0];
});
const dndDataFormat = { [pages]: { name: pages, items: questions } };
Object.assign(questionsObject, dndDataFormat);
Object.assign(finalDndDataFormat, questionsObject);
break;
default:
break;
}
});
});
return finalDndDataFormat;
};
setDndDataStructure( value , appPages )
Result:
{"my-needs":{"name":"my-needs","items":[{"notes":"","questions":[{"name":"loanAmount"}],"id":"1b356c8f-0087-4929-8894-2a929aa25c6c","unique":"loanAmount"},{"questions":[{"name":"loanReason"}],"id":"59a57164-d945-4747-a429-763a38da61d5","unique":"loanReason"},{"questions":[{"name":"email","type":"email"}],"id":"d526fb2f-8612-4313-a4be-b1779817ccd2","unique":"email"},{"questions":[{"name":"title","required":true}],"id":"420028a5-a280-4fb4-96f6-a3fa464ad531","unique":"title"},{"questions":[{"name":"firstName"},{"name":"lastName"}],"id":"fe2f12e9-5c66-4b6b-ab82-ec7d3fe50226","unique":"firstName"}]},"a-bit-about-me":{"name":"a-bit-about-me","items":[{"questions":[{"name":"phone"},{"name":"isCellPhone"}],"id":"d74a5ba2-30a8-473b-8db9-bf82b3f2a6e9","unique":"phone"},{"questions":[{"name":"dateOfBirth","options":[]}],"id":"86ae162d-beb7-4f1a-a74b-c161515f3144","unique":"dateOfBirth"},{"questions":[{"name":"residenceStatus","options":[],"label":"q_ownRent_text","type":"radio","required":true}],"id":"0e5b151d-46f8-460d-862a-3e75aa6438df","unique":"residenceStatus"},{"questions":[{"name":"addressLine1"},{"name":"addressLine2"},{"name":"city"},{"name":"province","options":[]},{"name":"postalCode"}],"title":"q2_supporting_2","id":"31120f92-771d-4136-b465-ad7abf944718","unique":"addressLine1"},{"questions":[{"name":"employmentStatus","options":[]}],"id":"03796c7b-7ff9-4abb-9fe3-f01db45de4f3","unique":"employmentStatus"}]},"my-home":{"name":"my-home","items":[{"questions":[{"name":"residenceDuration","options":[]}],"id":"65ce7da7-005e-4f62-ba5a-0768afa01111","unique":"residenceDuration"},{"questions":[{"name":"previousAddressLine1"},{"name":"previousAddressLine2"},{"name":"previousCity"},{"name":"previousProvince"},{"name":"previousPostalCode"}],"title":"q2_supporting_2","id":"e9682e67-8e03-4ffd-b695-a8bc50dca98d","unique":"previousAddressLine1"},{"questions":[{"name":"previousResidenceDuration"}],"id":"f473e302-469b-4196-aa1a-00da1bdb4cf6","unique":"previousResidenceDuration"},{"questions":[{"name":"residenceStatus"}],"id":"c3f4f424-9619-458f-9e20-343247835ecd","unique":"residenceStatus"},{"questions":[{"name":"principalResidence"}],"id":"6c6d9aa8-e693-40f4-b805-b1333c6e97f1","unique":"principalResidence"},{"questions":[{"name":"residenceType"}],"id":"1946d5fd-86ae-4c18-aeb6-7f03d2cfc3e1","unique":"residenceType"},{"questions":[{"name":"propertyValue"}],"id":"27ed276a-e42c-4a9a-923e-fdf953ef4e56","unique":"propertyValue"},{"questions":[{"name":"monthlyMortgagePayments"}],"id":"38b306c2-4328-4c10-9281-e736348c4047","unique":"monthlyMortgagePayments"},{"questions":[{"name":"mortgageBalance"}],"id":"b67b7f19-caef-4404-9592-0c4293d92c4d","unique":"mortgageBalance"}]},"my-income":{"name":"my-income","items":[{"questions":[{"name":"employmentDuration"}],"id":"ad78527d-c5d9-42f3-879e-79c78c10c72d","unique":"employmentDuration"},{"questions":[{"name":"previousEmploymentDuration"}],"id":"596d9c81-5549-4e98-935d-faf65cd91b8d","unique":"previousEmploymentDuration"},{"questions":[{"name":"paycheckAmount"}],"id":"076827a0-1e77-4455-bfb5-d0d7c59fd34c","unique":"paycheckAmount"},{"questions":[{"name":"paycheckFrequency"}],"id":"709704d6-5451-4fcb-93d1-c11090d789b4","unique":"paycheckFrequency"},{"questions":[{"name":"hasAdditionalIncome"},{"name":"additionalIncomeSource"}],"id":"a17cde6c-6a3f-4792-bfc6-bafa2237cf1c","unique":"hasAdditionalIncome"},{"questions":[{"name":"hasChildTaxCredit"},{"name":"childTaxCreditMonthlyAmount"},{"name":"numberOfDependentsChildTaxCredit"}],"id":"a138e279-712b-4e0a-9adb-30c4b89f754f","unique":"hasChildTaxCredit"},{"questions":[{"name":"sinNumber"}],"id":"ab5e04ac-3cd7-431f-adbf-a63ecf410af0","unique":"sinNumber"}]},"liabilities":{"name":"liabilities","items":[{"questions":[{"name":"hasPaydayLoan","options":[]},{"name":"paydayLoanAmountOwing"}],"id":"6aedc455-1204-4dfb-8f76-97c4bdc0b66b","unique":"hasPaydayLoan"},{"questions":[{"name":"hasOtherLiabilities","options":[]},{"name":"otherLiabilitySource","options":[]}],"id":"fda26736-db4f-4a38-b122-dd6a898be9df","unique":"hasOtherLiabilities"},{"questions":[{"name":"isEasyHomeCustomer","options":[]},{"name":"easyhomePaymentsLiabilityMonthlyAmount"}],"id":"fda94364-9b90-4a93-80ac-698415a5207d","unique":"isEasyHomeCustomer"}]},"t-and-c":{"name":"t-and-c","items":[{"notes":"","questions":[{"name":"agreeTermsConditionsAndPrivacyPolicy","options":[],"links":[]}],"id":"b7c74dce-1251-4831-8066-65cb73a598c7","unique":"agreeTermsConditionsAndPrivacyPolicy"}]}}
Above code is the sample i have done.
Thanks in advance
Just add a few comments
//At the end, setDnDStructure will leave x structured as y
const setDndDataStructure = (value, appPages) => {
const dndFormat = value?.map((data) => { // mapping x to y so that z
appPages.forEach((pages) => {
switch (data?.id) {
case pages:
const questions = data?.groups || {};
questions.map((obj) => {
obj.id = uuid();
const uniqueQuestionName = obj?.questions?.map((questionsField) => questionsField?.name);
obj.unique = uniqueQuestionName[0];
});
const dndDataFormat = { [pages]: { name: pages, items: questions } };
Object.assign(questionsObject, dndDataFormat);
Object.assign(finalDndDataFormat, questionsObject);
break;
default:
break;
}
});
});
return finalDndDataFormat;
};
The comments I've added should give you an idea of how you can make this more readable. Outline what you are trying to achieve at the end, and use comments in your function to outline what each step is doing