Cloud Functions Firestore get key value for increment - node.js

I'm trying to get the updated number for a Member Number from a document using Cloud Functions when an admin creates a user.
What should happen is when an Admin creates a user in their dashboard, the user is added to firebase, then the member number updates, and is applied to newMemNum, then updated in the user document.
My Code:
const memNumInc = admin.firestore.FieldValue.increment(1);
const memCounter = admin.firestore().collection(`mem_num`).doc(`a1lDbrsoXjfKeosEkDtw`);
memCounter.update({number: memNumInc}).then(() =>
memCounter.get()
.then((snap) => {
const id = snap.id;
const data = snap.data()
newMemNum = data['number']
console.log('New Member Number: ' + newMemNum);
return {id, ...data};
})
);
The increment goes ok (i.e. number goes up by 1 which is expected), but the next part of the code doesn't run and doesn't throw an error.
Also, the next part of the code that updates the user document doesn't fire at all, and no errors.
Entire Code:
// Create User Document onCreate
const createProfile = (userRecord) => {
const uid = userRecord.uid;
const docId = userRecord.uid;
const fullName = userRecord.displayName || 'New User';
const memStatus = 'Unfinancial';
const isAdmin = false;
const isNew = true;
const email = userRecord.email;
const photoUrl = userRecord.photoUrl;
const phone = '0444 123 456';
const createdAt = admin.firestore.FieldValue.serverTimestamp();
const memNumInc = admin.firestore.FieldValue.increment(1);
const memCounter = admin.firestore().collection(`mem_num`).doc(`a1lDbrsoXjfKeosEkDtw`);
memCounter.update({number: memNumInc}).then(() =>
memCounter.get()
.then((snap) => {
const id = snap.id;
const data = snap.data()
newMemNum = data['number']
console.log('New Member Number: ' + newMemNum);
return {id, ...data};
})
);
return afs
.collection(`users`)
.doc(docId)
.set({
uid: uid,
docId: docId,
fullName: fullName,
joined: createdAt,
memNum: newMemNum,
memStatus: memStatus,
isAdmin: isAdmin,
isNew: isNew,
email: email,
photoUrl: photoUrl,
phone: phone,
addedOn: createdAt,
updatedOn: createdAt
})
.then(() => console.log('User Creaeted Successfuly: ' + uid))
.catch((e) => console.log('User Creation Error: ' + e.message));
}
exports.authOnCreate = functions.auth.user().onCreate(createProfile);
If I remove the memCounter code, the rest executes no problem.

You have another return statement that most likely runs before the promise returned by get() is resolved. Try refactoring the code using async-await syntax as shown below:
const createProfile = async (userRecord) => {
// const vars ....
const memNumInc = admin.firestore.FieldValue.increment(1);
const memCounter = admin.firestore().collection(`mem_num`).doc(`a1lDbrsoXjfKeosEkDtw`);
// Update documents
await memCounter.update({
number: memNumInc
})
// Get update document data
const snap = await memCounter.get()
const id = snap.id;
const data = snap.data()
newMemNum = data['number']
console.log('New Member Number: ' + newMemNum);
return afs
.collection(`users`)
.doc(docId)
.set({ ... })
}
However, if multiple users are created simultaneously, there's a chance that they'll get the same newMemNum so a transaction might be useful as well.
Firestore also introduced a new COUNT() function that can be used to get total number of documents in a collection instead of incrementing the count every time.

Related

Firebase Function onCreate add to new collection works, but update does not

I have this onCreate Trigger, I am using it to aggregate and add record or update record. First it takes minutes to add the record and then the update never runs just keeps adding, not sure why my query is not bringing back a record to update.
Any suggestion would be great.
exports.updateTotals = functions.runWith({tinmeoutSeconds: 540})
.firestore.document("user/{userID}/CompletedTasks/{messageId}")
.onCreate(async (snap, context) => {
const mycompleted = snap.data();
const myuserid = context.params.userID;
console.log("USER: "+myuserid);
const mygroup = mycompleted.groupRef;
const myuser = mycompleted.userRef;
const newPoints = mycompleted.pointsEarned;
console.log("POINTS: "+newPoints);
const data = {
groupRef: mygroup,
userRef: myuser,
pointsTotal: newPoints,
};
const mytotalsref = db.collection("TaskPointsTotals")
.where("groupRef", "==", mygroup)
.where("userRef", "==", myuser);
const o = {};
await mytotalsref.get().then(async function(thisDoc) {
console.log("NEW POINTS: "+thisDoc.pointsTotal);
const newTotalPoints = thisDoc.pointsTotal + newPoints;
console.log("NEW TOTAL: "+newTotalPoints);
if (thisDoc.exists) {
console.log("MYDOC: "+thisDoc.id);
o.pointsTotal = newTotalPoints;
await mytotalsref.update(o);
} else {
console.log("ADDING DOCUMENT");
await db.collection("TaskPointsTotals").doc().set(data);
}
});
});
You are experiencing this behavior because while querying for updates you are getting more than 1 document and you are using thisDoc.exists on more than one document. If you have used typescript this could have been catched while writing the code.
So for the update query, if you are confident that only unique documents exist with those filters then here’s the updated code that I have recreated using in my environment.
functions/index.ts :
exports.updateTotals = functions.runWith({timeoutSeconds: 540})
.firestore.document("user/{userId}/CompletedTasks/{messageId}")
.onCreate(async (snap, context) => {
const mycompleted = snap.data();
const myuserid = context.params.userID;
console.log("USER: "+myuserid);
const mygroup = mycompleted.groupRef;
const myuser = mycompleted.userRef;
const newPoints = mycompleted.pointsEarned;
console.log("POINTS: "+newPoints);
const data = {
groupRef: mygroup,
userRef: myuser,
pointsTotal: newPoints,
};
const mytotalsref = admin.firestore()
.collection("TaskPointsTotals")
.where("groupRef", "==", mygroup)
.where("userRef", "==", myuser);
await mytotalsref.get().then(async function(thisDoc) {
if (!thisDoc.empty) { // check if the snapshot is empty or not
const doc = thisDoc.docs[0];
if(doc.exists){
const newTotalPoints = doc.data()?.pointsTotal + newPoints;
const id = doc.id;
await db.collection("TaskPointsTotals").doc(id).update({pointsTotal: newTotalPoints});
}
} else {
await db.collection("TaskPointsTotals").doc().set(data);
}
});
});
For more information about QuerySnapshot methods check this docs

Why do we use put id as a param in put method

I am trying to Increase a value in database,
const handleStockUpdate = (event) => {
event.preventDefault();
const newQuantity = event.target.restock.value;
const quantity = {quantity: newQuantity};
setNewCount({...book, quantity: book.quantity + parseInt(newQuantity)});
if(newQuantity < 0){
toast("Please enter a vlaid number")
}
else{
axios.put(`http://localhost:5000/stock/${`idHeare`}`, {quantity})
}
}
api
app.put('/stock/:id', async(req, res) => {
const id = req.params.id;
console.log(id);
const qunatity = req.body.quantity.quantity;
const book = await bookCollections.findOne({_id: ObjectId(id)});
const newUpdate = parseInt(book.quantity) + parseInt(qunatity);
const result = await bookCollections.updateOne({_id:ObjectId(id)},
$set({qunatity: newUpdate})
)
res.send(result);
})
My question is, why should I use Id? and where can I get it? without Id it gets networkErrro
Put method is used to update data.
The id you are providing to the api will be used to retrieved that row from your database which you want update. As you may know that in a database table every data row must have a unique id.
So for your book object , if you are retrieving it from the database in the first place then you should have a Id with that book data you got.
You have to provide that book id on the put request.

Find one user on moongose

I have the following problem to solve: I have multiple users in a database (mongoDB). Pass the following, in a specific url, for example: "my-page/users/here-goes-the-name-of-the-user". When displaying the username in the url, I convert it to lowercase and replace the spaces with hyphens: Husdady Mena ===> /husdady-mena.
In the database, the user names are as shown before converting the name to lowercase, eg: Husdady Mena, Donald Trump, Will Smith, etc.
I need to somehow filter a user, which is in the url as /husdady-mena with the database user: Husdady Mena, in order to obtain his information, how could that filter be done in mongoose?
A clear example with js:
const users = [{ name: 'Husdady Mena' }, {name:'Donald Trump'}, {name:'Will Smith'}, {name:'Dubá Solis'}]
const username = "dubá-solis"
const url = `my-page.com/users/${username}`
const userToFilter = url.substring(url.lastIndexOf("/") + 1)
const userFound = users.find(({ name }) => {
const usernameToLowerCase = name.toLowerCase().replace(/\s/gim, "-")
return userToFilter === usernameToLowerCase
})
console.log(userFound)
If the example is not clear to you: I make a request from the frontend to the backend to obtain a specific user, I need to obtain the user by name, it is not necessary to obtain it by id. In the url something like: my-page/users/dubá-solis is painted, I get the name of the user, which is: dubá-solis. in the backend is something like:
// Librarys
const { Router } = require('express')
const router = Router()
// Models
const User = require('#models/users/User')
router.get(
'/users/:username',
function(req, res) {
try {
// req.params.username === "dubá-solis"
const userFound = await User.findOne({ name: req.params.username })
// In database, the username is Dubá Solis, that's why it returns me null
return res.status(200).json(userFound)
} catch(err) {
console.log('[UserFound.err]', err)
}
}
)
module.exports = router
Why not just convert the username and pass that to the query function? Something like:
const convertUsername = username => {
const nameParts = username.split('-');
return nameParts.map(n => n.slice(0, 1).toUpperCase() + n.slice(1))
.join(' ');
};
console.log(convertUsername('dubá-Solis'));
console.log(convertUsername('joan-of-arc'));
Then, just do User.findOne({ name: convertUsername(req.params.username) })

Display User Tag While Mapping discordjs mongoose

So i am sorting a leaderboard and it is currently displaying the user's id. I want it to display the user tag then their balance.
I have this code
const lb = users
.slice(0)
.sort(({ Bobux: a }, { Bobux: b }) => b - a)
.map(
({ User, Bobux }, pos) => `${pos + 1}. <#${ await client.users.fetch(User).tag}> - ${commaNumber(Bobux)} Bobux`,
);
But I get the error
(node:13960) UnhandledPromiseRejectionWarning: C:\Users\Sochum\Desktop\BobloxBot\commands\leaderboard.js:26
({ User, Bobux }, pos) => `${pos + 1}. <#${ await client.users.fetch(User).tag}> - ${commaNumber(Bobux)} Bobux`,
SyntaxError: Missing } in template expression
How would I display the user's tag while sorting and displaying the top 15 users? The variable for user id is User
If I don't do await, everything returns as undefined
You can use Discord.Collection() to sort the data after fetching from mongoose database.
const { Collection } = require("discord.js")
const collection = new Collection()
// collect every member's data in the guild
await Promise.all(
message.guild.members.cache.map(async(member) => {
const id = member.id
const data = await <Schema>.findOne({ User: id }) // replace "<Schema>" with your Schema
const currency = data.Bobux
return collection.set(id, { id, currency })
})
)
const lb = collection.sort((x, y) => y.currency - x.currency).first(15) // top 15 of the collected data
// destructing our data to send it in one message
message.channel.send(lb.map((v, i) => {
return `\`${i + 1}\`. ${client.users.cache.get(v.id).tag}: ${v.currency}`
}).join("\n")
)

Iterate dataArray to create an object is not happening

How can I parse the incoming nomData data array and store those values into an object along with userEmail ? Somehow below code is not working, could someone pleas advise the issue here.
Expected database columns values:
var data = { useremail: userEmail, nomineeemail: email, nomineename: name, nomineeteam: team, reason: reason }
server.js
app.post('/service/nominateperson', async (req, res) => {
try {
const userEmail = req.body.userEmail;
const nomData = req.body.nomRegister;
const formData = {
useremail: userEmail,
nomineeemail: {},
nomineename: {},
nomineeteam: {},
reason: {}
}
const newArray = nomData.map(item => {
formData.nomineeemail = item.email;
formData.nomineename = item.name;
formData.nomineeteam = item.team;
formData.reason = item.reason;
});
var data = { useremail: userEmail, nomineeemail: email, nomineename: name, nomineeteam: team, reason: reason }
// Ideally I should get nomData
//items parsed create an data object and pass that into bulkCreat() method:
const numberOfNominations = await NominationModel.count({where: {useremail: userEmail}});
if (numberOfNominations <= 3) {
const nominationData = await NominationModel.bulkCreate(data);
res.status(200).json({message: "Nomination submitted successfully !"});
} else {
res.status(202).json({message: "Nomination limit exceeded, please try next week !"});
}
} catch (e) {
res.status(500).json({fail: e.message});
}
});
So i assume nomData is an array containing multiple nominations. So if you want to bulkCreate with data you should pass an array instead of an object.
const data = nomData.map(item => ({
useremail: userEmail,
nomineeemail: item.email,
nomineename: item.name,
nomineeteam: item.team,
reason: item.reason
}));
...
await NominationModel.bulkCreate(data)

Resources