Mongoose update map element inside array of document - javascript

as the title says, I want to update a value in a map inside an array in a document, Im using Mongoose. This is for a Discord bot, basically adding exp to a char every time user does a command, and it works, but it doesn't look right and I've been told it doesn't work sometimes:
This is how the maps inside the array look like:
click
// This gets the user document
let user = bot.userinventories.findOne({ userID: message.author.id })
// This searches the specific map I need by name
let check = await user.get('characters').filter(char => char.name === user.get('leveling'));
// This modifies the map in only 1 value
check[0].exp++;
// This removes the entire map and saves it again
await bot.userinventories.updateOne({ userID: message.author.id },
{ $pull:
{
characters: { name: check[0].name }
}
})
await bot.userinventories.updateOne({ userID: message.author.id },
{ $push:
{
characters: check[0]
}
})
As you can see, the part that doesn't seem right is having to entirely remove the map before saving it again, this also breaks any order by date. The big problem is that users have told me they've lost characters to this.
Another thing that sometimes fails is the 'let check' line, even tho it finds a map by the exact name (because of my code) it fails randomly like 1/20 times.

I found this which works, even tho it stills moves the object to the last place of the map, but it seems cleaner than what I had:
await bot.userinventories.updateOne(
{ userID: message.author.id, "characters.name": check[0].name },
{ $set: { "characters.$.exp" : check[0].exp } }
)
Edit: for it to not move the edited object to last place, I had to make a new variable or not use a variable for '"characters.$.exp" : ' for some reason

Related

I want to make sure the Post Is Not Liked multiple times by The same user using Firebase

I have these codes to make the Like functionality
/*Here I increment by 1 every time someone submit like*/
db.collection("post").doc(postId).update({
likes: increment
})
/*Here I collect the uid of the user who submit Like and append to an Array*/
db.collection('post').doc(postId).update( {
usersArray: firebase.firestore.FieldValue.arrayUnion( userId )
});
now is possible to have code that runs every time someone submits Like to check if the user already exists in the usersArray?
Before updating the post document, you first need to read it in order to check if userId is already included in the usersArray array.
For that you shall use a Transaction to ensure that the document was not modified between the time you read it for checking the Array and the time you update it.
const postDocRef = db.collection('post').doc(postId);
return db
.runTransaction((transaction) => {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(postDocRef).then((postDoc) => {
if (!postDoc.exists) {
throw 'Document does not exist!';
}
if (postDoc.data().usersArray.includes(userId)) {
throw 'User has already liked this post!';
}
const newLikes = postDoc.data().likes + 1;
transaction.update(postDocRef, {
usersArray: firebase.firestore.FieldValue.arrayUnion(userId),
likes: newLikes,
});
});
})
.then(() => {
console.log('Transaction successfully committed!');
})
.catch((error) => {
console.log('Transaction failed: ', error);
});
Keep in mind that even with this code, malicious users can still use your configuration data with their own code to write their UID into the array a second time. To prevent this, you'll want to write security rules to only allow adding the UID if it isn't in the array already (by comparing request.resource with resource).
Use cloud functions for counter increments like post likes.We can get updated and previous value of document in onUpdate function trigger on post collection,then we can check if user is already included in userArray and increment/decrement like count accordingly.

Cannot Delete Fields with Period in Key Name

I've tried every solution in this other StackOverflow thread, and none of them have worked for me. At this point I'm absolutely stumped, and have no idea on what to try next.
The data that I'm trying to access is this:
The key I'm trying to use is a URL, which is inside the accounts map. This is the code I'm running to try and delete the key:
var userRef = db.collection('userAccounts').doc(userEmail)
let dynamicKey = `accounts.${accountURL}`
console.log(dynamicKey)
userRef.set({
[dynamicKey]: firebase.firestore.FieldValue.delete()
}, { merge: true})
.then((result) => {
console.log(result)
})
.catch((error) => {
console.log(error)
})
Looking in console, accounts.www.stackoverflow.com is printed:
So, it looks like the path should match. A note to make is that a URL with no periods works just fine, so it seems like the path IS correct, and that periods are in fact the issue.
The accepted answer by J Livengood simply doesn't work for keys with periods in the name:
[`hello.${world}`]: firebase.firestore.FieldValue.delete()
The code posted by Sam Stern simply doesn't run, and I get an error mentioning that update only takes one parameter. Contrary to the last poster (ishandutta2007), adding a 'new' before the FieldPath doesn't fix the error:
doc.update(
firebase.firestore.FieldPath("hello.world"),
firebase.firestore.FieldValue.delete());
This code (in the comments) posted by OP, Sandeep Dinesh, just doesn't work at all, even when trying to delete with a key with no period. My code is the following, and the returned Promise is undefined in the "then" portion of the code:
var userRef = db.collection('accounts').doc(userEmail)
let dynamicKey = `accounts.${accountURL}`
userRef.set({
[dynamicKey]: firebase.firestore.FieldValue.delete()
}, { merge: true})
.then((result) => {
console.log(result)
})
.catch((error) => {
console.log(error)
})
The problem you have here is a little bit different than you described. Specifically, you're trying to delete a nested map key with periods in it. It's important to realize that this nesting requires special treatment that you didn't see in other questions, which were dealing only with top-level fields.
You will need to use FieldPath here, and specify the path of the field as array elements in the constructor. From the linked API documentation (emphasis mine):
Creates a FieldPath from the provided field names. If more than one field name is provided, the path will point to a nested field in a document.
So, you should set or update with a field of new firebase.firestore.FieldPath(["accounts", "www.stackoverflow.com"]) and a value of firebase.firestore.FieldValue.delete().
documentReference.update(
new firebase.firestore.FieldPath(["accounts", "www.stackoverflow.com"]),
firebase.firestore.FieldValue.delete()
);

How can I repeat a Firestore query until a condition is met?

So I am working on a storing users information in my database and in addition to the standard data I get from a Firebase User, I wanted to add my own custom data. I am setting up a profile page for each of my users and I wanted to generate a random 10 digit number to be their profileID. I then store it in my database just in case I need it. However, I need them to be unique numbers and for them not to repeat so I'm trying to query the database to check if the profileID that I generated is already in use and if so, generate another one. However, I can't figure out how to repeat a query in Firestore other than maybe using a loop but that doesn't seem like it would work.
const usersRef: AngularFirestoreCollection = this.afs.collection('users');
var query = usersRef.ref.where("profileID", '==', profileID);
query.get().then((querySnapshot) => {
if (!querySnapshot.empty) {
profileID = this.getRandomProfileID();
}
})
A recursive function like this seems the way to go:
function getRandomProfileID() {
return new Promise(function(resolve, reject) {
var profileID = ....
var query = usersRef.ref.where("profileID", '==', profileID);
query.get().then((querySnapshot) => {
if (!querySnapshot.empty) {
this.getRandomProfileID();
}
else {
resolve(profileID);
}
})
})
}
And then you'd call it with:
getRandomProfileID().then(function(profileID) {
console.log(profileID);
});
Or if you're using a more modern version of JavaScript:
var profileID = await getRandomProfileID();
console.log(profileID);
As Doug pointed out, with async/await you can even do without the recursive function entirely:
while (true) {
let profileID = ....
let snapshot = await usersRef.ref.where("profileID", '==', profileID).get()
if (!snapshot.empty) {
break;
}
}
console.log("Found unique ID: " + profileID);
But there are some things still to consider with this approach:
Another user may generate the same profile ID between the time you generate and check it, and actually creating it. If this is a concern, you should probably use a transaction for the function above, and have it create the profile document. If it's not a realistic concern, you should still ensure in security rules that no user can ever overwrite a document that was already created by another user.
If you're going to use your own profile ID in a lot of places to identify the user, consider creating an additional collection where each document name/ID is the profile ID, and the document contents are the key into the users collection. If you're going to uniquely identify the users by their profile ID, consider using the profile ID as the key of their document in the users collection, instead of whatever key you know use.

Better performance when saving large JSON file to MySQL

I have an issue.
So, my story is:
I have a 30 GB big file (JSON) of all reddit posts in a specific timeframe.
I will not insert all values of each post into the table.
I have followed this series, and he coded what I'm trying to do in Python.
I tried to follow along (in NodeJS), but when I'm testing it, it's way too slow. It inserts one row every 5 seconds. And there 500000+ reddit posts and that would literally take years.
So here's an example of what I'm doing in.
var readStream = fs.createReadStream(location)
oboe(readStream)
.done(async function(post) {
let { parent_id, body, created_utc, score, subreddit } = data;
let comment_id = data.name;
// Checks if there is a comment with the comment id of this post's parent id in the table
getParent(parent_id, function(parent_data) {
// Checks if there is a comment with the same parent id, and then checks which one has higher score
getExistingCommentScore(parent_id, function(existingScore) {
// other code above but it isn't relevant for my question
// this function adds the query I made to a table
addToTransaction()
})
})
})
Basically what that does, is to start a read stream and then pass it on to a module called oboe.
I then get JSON in return.
Then, it checks if there is a parent saved already in the database, and then checks if there is an existing comment with the same parent id.
I need to use both functions in order to get the data that I need (only getting the "best" comment)
This is somewhat how addToTransaction looks like:
function addToTransaction(query) {
// adds the query to a table, then checks if the length of that table is 1000 or more
if (length >= 1000) {
connection.beginTransaction(function(err) {
if (err) throw new Error(err);
for (var n=0; n<transactions.length;n++) {
let thisQuery = transactions[n];
connection.query(thisQuery, function(err) {
if (err) throw new Error(err);
})
}
connection.commit();
})
}
}
What addToTransaction does, is to get the queries I made and them push them to a table, then check the length of that table and then create a new transaction, execute all those queries in a for loop, then comitting (to save).
Problem is, it's so slow that the callback function I made doesn't even get called.
My question (finally) is, is there any way I could improve the performance?
(If you're wondering why I am doing this, it is because I'm trying to create a chatbot)
I know I've posted a lot, but I tried to give you as much information as I could so you could have a better chance to help me. I appreciate any answers, and I will answer the questions you have.

Cannot get the full json with request json

When I am actually entering the XXXX YYYY, then I am getting the players json code in my html page (around 150 values).
But when I am trying to use a function on the players list it somewhy does not contain all the 150 values and the try throws me into the catch error part, where I can see that players json has only 100 players inside there.
Any idea what could be the problem?
if(yourID === "XXXX" && targetID === "YYYY"){
return players;
}
try{
if(isUserAlive(yourID)){
if(targetID === ""){
return userTargetInfo(yourID);
}
var checkForMatch = getUserTarget(yourID);
if(checkForMatch === targetID){
killTarget(targetID);
getUser(yourID).targetID = getTargetTarget(targetID);
addScore(yourID);
return userTargetInfo(yourID);
//return getTargetTargetStats(targetID);
}else{
return "INVALID";
}
}else{
return "DEAD"
}
}catch(err){
console.log("Error",console.log(players))
return "INVALID"
}
Edit: Since I had no time, I created 2 websites and divided the database into 2 different databases, so it would work under 100 people on each. Did not have time to fix the error at this point. So I won't be choosing the solution to that since I won't be trying that any time soon.
Thank you for all your help!
Check the link api that you are using , it might have pagination integrated with it . in that case i will return certain number of object 1st and then you can re-request to get next batch . Most likely they might have a option to change the no of object returned (sometimes with max value)
I'm pretty sure body is returned as a string. Try changing it to an object so you can work with it easier.
Change:
players = body;
to:
players = JSON.parse(body);
I'm not sure the rest of your code, but you may want to add var on your players variable declaration because this looks like the first time you are setting it.
Research: namespace collisions
If you are still having issues, edit your question to include the response you are getting from console.log(JSON.parse(body));. You will be able to get more helpful answers. Personally, I am curious to see the keys such as:
{ query:
{ count: 1,
created: '2017-04-23T22:03:31Z',
lang: 'en-US',
results: { channel: [Object] } } }
If it's paginated, you should see some kind of cursor key in there, or prev and next along with some kind of totalCount.
Hope this helps.

Categories