MomentJS add function not adding time - node.js

I've checked all the other forums asked on the forums here tried the clone method doesn't seem to work either. What I'm trying to do is make a key claim system and it'll get the current date and add whatever number the key has assigned to it for example key is assigned 10 days it'll add 10 days to the date. Yet it doesn't add time.
This is what it returns to console when trying to add 10 days
Thu Nov 10 2022 19:42:29 GMT-0500
Here's my code
let userDB = await userSchema.find({ UserID: id });
let keysDB = await keysSchema.find({ Key: key });
var todayTime = moment(new Date());
let todayCurTime = todayTime.clone();
userDB = new userSchema({
Name: name,
Discrim: discrim,
UserID: id,
Time: todayCurTime.add(keysDB.Time, 'days')
})
console.log(`Updated time ${todayCurTime.add(keysDB.Time, 'days')}`)
await userDB.save().catch(err => console.log(err));

Moment.add() mutates the original moment rather than returning an updated moment object (source from docs). It appears that it does return a moment, but specifically the value that the moment had before it was updated. This is a rather odd way to implement things, to say the least, so it's not surprising that it tripped you up.
You need to do your time manipulation before you read from the value. The following should work:
let userDB = await userSchema.find({ UserID: id });
let keysDB = await keysSchema.find({ Key: key });
// You don't need to pass a Date() object if you just want the current date & time
const todayTime = moment();
const todayCurTime = todayTime.clone();
todayCurTime.add(keysDB.Time, 'days')
userDB = new userSchema({
Name: name,
Discrim: discrim,
UserID: id,
Time: todayCurTime
})
console.log(`Updated time ${todayCurTime}`)
await userDB.save().catch(err => console.log(err));

Related

Firebase Firestore cloud functions: filtering documents by date conditions in node.js

I need to filter documents in Firestore by date within a cloud function using node.js. I'd like to return only those documents that have a timestamp (or date string) more than 30 days from the current date in a field named last_used. At present, not all documents have this field. One method I have thought about employing is writing a trigger function to give each document a last_used field and set the timestamp to 01/01/2022, run this once and then the scheduled function can update the field when required with the current date. The scheduled function currently selects a player document at random each day, moves the required fields into the current-live-player collection and updates the last_used field for the selected player document within available-players.
Context: I'm building a game in which a player document is selected at random each day from the available-players collection and placed into the current-live-player collection. current-live-player is made up of just this one document and updated each day at 00:00 London time. I only want Firestore to select a player document from available-players that meets my aforementioned condition: the date within last_used is more than 30 days prior to the current date.
This is what I have so far:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { firestore } = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
// Potential trigger function to give all player objects a last_used field
exports.addLastUsedToAll = functions.https.onRequest((request, response) => {
// Logic to add a last_used field with a date of 01/01/2022
});
// Select a player function that runs every day at 00:00 London time
exports.pickCurrentLivePlayer = functions.pubsub.schedule("0 0 * * *")
.timeZone("Europe/London")
.onRun(async () => {
// select a random player from available players (uses the auto-generated IDs)
// ifelse catches rare cases that could cause an error
const availablePlayers = db.collection("available-players");
const key = availablePlayers.doc().id;
availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
if(snapshot.size > 0) {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
console.log('snapshot.size > 0 ', doc.id, '=>', doc.data());
// replace live player
const newPlayerData = doc.data();
const app_first_name = newPlayerData.app_first_name;
const uid = newPlayerData.uid;
db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
// set selected player's last_used value to current time
db.collection("available-players").doc(uid).update({
last_used: admin.firestore.Timestamp.now()
})
});
}
else {
const player = availablePlayers.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
console.log('snapshot.size > 0 ', doc.id, '=>', doc.data().name);
// replace live player
const newPlayerData = doc.data();
const app_first_name = newPlayerData.app_first_name;
const uid = newPlayerData.uid;
db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
// set selected player's last_used value to current time
db.collection("available-players").doc(uid).update({
last_used: admin.firestore.Timestamp.now()
})
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
})
.catch(err => {
console.log('Error getting documents', err);
});
return null;
});
I hope that all makes sense, any help would be greatly appreciated. I know that it can be tricky to convert timestamps and then filter by date in javascript, but I'd like to use the Firestore timestamp to ensure that the functions are always running off the server time.
Edit:
In response to Frank van Puffelen's answer, the timestamp calculations are now working and each document in available players now has a last_used field populated with a timestamp. I now need to query available_players to show those documents in which the timestamp is more than 30 days ago. The issue now is that I cannot query the collection before running my random selection. The order should be: 1) calculate today's date minus 30 days; 2) query available_players for all documents with a timestamp in the last_used field more than 30 days in the past; 3) generate a key from this filtered selection; 4) query the filtered selection to give me one random document and process the rest of the update logic etc. It falls down where the filtered data is now a query snapshot rather than a collection reference and the code I have no longer runs (at least I think that's the issue!). The code worked perfectly before attempting to add this 'filter by date' functionality. I've provided a minimal repro here:
// calculate time interval
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);
// get collectionReference and filter to those more than 30 days ago
const allPlayers = db.collection("available-players");
const availablePlayers = await allPlayers.where('last_used', '<=', cutoffTime).get();
// generate key
const key = availablePlayers.doc().id;
// select a random player from the filtered selection
availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
..etc etc
I somehow need to be able to query the data to filter it by date and then run the random selection chunk of code.
To get the players that were last used more than 30 days ago would be something like this:
const availablePlayers = db.collection("available-players");
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);
const query = availablePlayers.where(last_used, "<=", cutoffTime);

Firestore promise wait for product details before returning products

I know similar questions like this have been asked 1000 times but for the life of me I am struggling with something I feel is quite simple.
We have 2 tables, one called order_lines the other called order_lines_meta, I need to first query order_lines and for each line get the order_lines_meta and return that
I have tried a lot of variations, here is where I am at and stuck, I need it to wait for the order_lines_meta to come back because otherwise I get blank metaData as the data comes after nodejs has already outputted the order_lines
At the end an object that contains order info, line items of objects and within line items a meta data object
Appreciate the help, I just can't seem to wrap my brain on this one , and I am certainly open to other ways of doing this as well
Using nodejs, express, typescript, firestore
const orderNumber = req.query.orderNumber as string;
const customerName = req.query.customerName as string;
const orderDate = req.query.orderDate as string;
const pickListObj = {
orderNumber: orderNumber,
customerName: customerName,
orderDate: orderDate,
line_items: <any>[],
};
db.collection('order_lines').where('number', '==', orderNumber).get().then((snap) => {
const promises = <any>[];
snap.forEach(async (order: any) => {
// get meta data
const metaDataObj = <any>[];
const productName = order.data().name;
const productQty = order.data().quantity;
promises.push(db.collection('worder_line_meta').where('lineId', '==', order.data().lineId).get().then((doc: any) => {
if (doc.display_value != '') {
const meta = [{display_key: doc.data().display_key, display_value: doc.data().display_value}];
metaDataObj.push(meta);
}
}));
});
return Promise.all(promises);
}).then(() => {
pickListObj.line_items.push({name: productName, quantity: productQty, meta_data: metaDataObj});
});
Move the push statement from the last .then inside the previous .then:
promises.push(db.collection('worder_line_meta')...then((doc: any) => {
if (doc.display_value != '') {
...
}
pickListObj.line_items.push({name: productName,
quantity: productQty,
meta_data: metaDataObj});
}));
In the last .then, you will then find the complete pickListObj.
However, I wonder whether it might be simpler and faster to join the two database collections right on the database and retrieve everything with one db.collection operation.

How do I copy entries from one collection to another using mongoose?

I'm trying to create a little task management site for a work project. The overall goal is here is that the tasks stay the same each month (their status can be updated and whatnot), and they need to be duplicated at the start of each new month so they can be displayed and sorted by on a table.
I already figured out how to schedule the task, I have the table I need set up. A little explanation before the code - the way I'm planning on doing this is having two different task collections - one I've called "assignments", will have the tasks that need to be duplicated (with their description, status and other necessary data) and another collection, which I called "tasks", will have the exact same data but with an additional "date" field. This is where the table will get it's data from, the date is just for sorting purposes.
This is what I have so far -
Index.js: gets all the assignments from the database, and sends the object over to the duplicate function.
router.get('/test', async function(req, res, next) {
let allTasks = await dbModule.getAllAssignments();
let result = await dbModule.duplicateTasks(allTasks);
res.json(result);
});
dbmodule.js:
getAllAssignments: () => {
allAssignments = Assignment.find({});
return allAssignments;
},
duplicateTasks: (allTasksToAdd) => {
try {
for (let i = 0; i < allTasksToAdd.length; i++) {
let newTask = new Task({
customername: allTasksToAdd.customername,
provname: allTasksToAdd.provname,
description: allTasksToAdd.description,
status: allTasksToAdd.status,
date: "07-2020"
})
newTask.save();
}
return "Done"
} catch (error) {
return "Error"
}
}
The issue arises when I try and actually duplicate the tasks. For testing purposes I've entered the date manually this time, but that's all that ends up being inserted - just the date, the rest of the data is skipped. I've heard of db.collection.copyTo(), but I'm not sure if it'll allow me to insert the field I need or if it's supported in mongoose. I know there's absolutely an easier way to do this but I can't quite figure it out. I'd love some input and suggestions if anyone has any.
Thanks.
The problem is that allTasksToAdd.customername (and the other fields your trying to access) will be undefined. You need to access the fields under the current index:
let newTask = new Task({
customername: allTasksToAdd[i].customername,
provname: allTasksToAdd[i].provname,
description: allTasksToAdd[i].description,
status: allTasksToAdd[i].status,
date: "07-2020"
})
Note that you can simplify this by using a for .. of loop instead:
for (const task of allTasksToAdd) {
const newTask = new Task({
customername: task.customername,
provname: task.provname,
description: task.description,
status: task.status,
date: "07-2020"
});
newTask.save();
}

How do I use transactions or batches to read the value of an update, then perform more updates using that value?

What is the best approach to do a batch update or transaction, that reads a value of the first update, then uses this value to make further updates?
Here is an example:
//create person
const id = await db
.collection("person")
.add({ ...person })
.then(ref => ref.id)
//then do a series of updates
let batch = db.batch()
const private_doc = db
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc, {
last_modified,
version: 1,
versions: []
})
const some_index = db.collection("data").doc("some_index")
batch.update(some_index, {
[id]: { first_name: person.first_name, last_name: person.last_name, last_modified }
})
const another_helpful_doc = db.collection("some_other_collection").doc("another_helpful_doc")
batch.update(another_helpful_doc, {
[id]: { first_name: person.first_name, last_name: person.last_name, image: person.image }
})
return batch.commit().then(() => {
person.id = id
return person
})
You can see here if there is an error any of the batch updates, the person doc will still be created - which is bad. I could add in a catch to delete the person doc if anything fails, however interested to see if this is possible with transactions or batches.
You can call the doc() method, without specifying any path, in order to create a DocumentReference with an auto-generated ID and, then, use the reference later. Note that the document corresponding to the DocumentReference is NOT created.
So, the following would do the trick, since all the writes/updates are included in the batched write:
const new_person_ref = db.collection("person").doc();
const id = new_person_ref.id;
let batch = db.batch()
batch.set(new_person_ref, { ...person })
const private_doc_ref = db // <- note the addition of ref to the variable name, it could help avoiding errors, as this is not a DocumentSnapshot but a DocumentReference.
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc_ref, {
last_modified,
version: 1,
versions: []
})
//....

Update mongodb document, if the document exists, else create

So I am trying to make a discord bot for me and my friends for tracking stats in CS GO 10 mans, and I am using cheerio for webscraping from the site that provides us the stats, and then pass them into mongodb. The scraping functionality works fine, but im trying to figure out how to avoid creating duplicate documents for each user. If I enter *userid 857575 it pulls the stats for that user, and puts in the DB, but if i call that multiple times, its making multiple documents in the DB. My question is, how would I get mongodb to update the document based on if the message author in discord matches the username in the db? So if username bob sends *userid3939 and bob already exists in the db, update the document. If bob doesnt exist, create document. code below, appreciate any tips.
module.exports.run = async (bot, message, args) => {
console.log(args);
var userUrl = 'https://popflash.site/user/' +args;
console.log(userUrl);
console.log(message.member.user.tag);
rp(userUrl)
.then(function (html) {
const arr = [];
var i = 0;
$('.stat-container', html).each(function (key, value) {
arr[i++] = $(this).find(".stat").text();
});
const stats = new Stats({
_id: mongoose.Types.ObjectId(),
userName: message.member.user.tag,
userId: args,
HLTV: arr[0],
ADR: arr[1],
HS: arr[2],
W: arr[3],
L: arr[4],
T: arr[5],
win_percent: arr[6]
});
stats.save()
.then(function (result) {
let botembed = new Discord.RichEmbed()
.setDescription(message.member.user + "'s 10 Man stats")
.setColor("#15f153")
.addField("stats", result)
return message.channel.send(botembed);
})
})
}
module.exports.help = {
name: "userid"
}
Through db.collection.update, you can specify the upsert: true option to get the behavior I think you're desiring. It will update an existing record if matched, otherwise it will create a new record.

Resources