Specifying a condition in payload sent in findByIdAndUpdate - javascript

Accounts model:
let accountsSchema = new mongoose.Schema({
posts:{
type: Array
}
});
const Post = mongoose.model("Account", postSchema);
controller in Posts microservice, which populates the posts array in the Accounts microservice:
let myAccountPosts = await axios.get(`${process.env.ACCOUNTS_MICROSERVICE_URL}/router/account/${userID}`)
.then((resp) => {
resp.data.message.map((account) =>{
account.posts.map((post) =>{
if(myUserPostID !== post._id){
//the following resets the entire posts array in the other microservice, consider resetting/deleting only that particular post
let deletePostFromAccountsMicroservice = axios.patch(`${process.env.ACCOUNTS_MICROSERVICE_URL}/router/account/update/${userID}`, {"posts.$[]": "abc"}, (err) =>{
if(err) return err;
});
}else{
console.log("no")
}
});
});
}).catch((err) =>{
console.log("err: ", err.message);
return err;
});
Now I need to be able to update a single object in the array and not override the whole array.
This is what the response looks like:
{
posts:[{_id:'6290ed85716a08d29dab3aa5'}, { _id: '1234ed85716a08d29b35342' } ]
}
In the following line of code:
let deletePostFromAccountsMicroservice = axios.patch(`${process.env.ACCOUNTS_MICROSERVICE_URL}/router/account/update/${userID}`, {"posts.$[]": "abc"}, (err) =>{
if(err) return err;
});
I want the posts.$[] to target only the
object that has an _id that matches this condition (myUserPostID !== post._id)
How do achieve that?

Related

Node Js: Remove string array element from mongoDB

I have a user schema as follows:
const UserSchema = new mongoose.Schema({
skills: [String]
});
module.exports = mongoose.model("User", UserSchema);
And a Fetch request to delete a skill as follows:
const deleteItem = async (id) => {
try {
await fetch(`http://localhost:5000/api/user/deleteskill`, {
method: "DELETE",
headers: { "Content-Type": "application/JSON", token: accessToken },
body: JSON.stringify({ userid: userid , skill:id}),
})
.then((res) => res.json())
.then((data) => {
console.log("USER SKILLS:", data.userskills);
});
} catch (err) {
console.log(err);
}
};
Server
const deleteSkill = async (req, res) => {
try {
const user = await User.findById(req.body.userid)
//user.skills.pull(req.body.skill);
// removeskill = user.skills.filter(function(item) {
// return item !== req.body.skill
// })
if (user.skills.includes(req.body.skill)) {
res.status(400).json("Item Still Exists");
} else {
res.status(200).json("Item Deleted");
}
} catch (error) {
res.status(500).send({ error: error.message });
}
};
the array is in the following structure
[
'skill1', 'java', 'skill5'
]
I have tried to remove the user skill from the array in several ways but I still get res.status(400).json("Item Still Exists");. What I'm doing wrong?
Use the findOneAndUpdate method to find a document with the user id and update it in one atomic operation:
const deleteSkill = async (req, res) => {
try {
let message = "Item Deleted";
let status = 200;
const user = await User.findOneAndUpdate(
{ _id: req.body.userid },
{ $pull: { skills: req.body.skill } },
{ new: true }
)
if (user && user.skills.includes(req.body.skill)) {
message = "Item Still Exists";
status = 400;
} else if (!user) {
message = "User Not Found";
status = 404;
}
res.status(status).send({ message });
} catch (error) {
res.status(500).send({ error: error.message });
}
};
I believe you want to remove skills from the database then the following function could help you out.
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("mydb");
var myquery = { userid: userid, skillid: skillid};
dbo.collection("skills").deleteOne(myquery, function(err, obj) {
if (err) throw err;
console.log("1 document deleted");
db.close();
});
});
You have a method of removing elements from arrays, if you want to remove the first one you could use array.shift (more on it here), but if you want to delete it completely from your database you could always, find it and then update it.
User.update({ _id: userid }, { $pull: { "skills": "[skill]" }})

How to check if mongoose array has particular element inside object or not?

Basically what i am trying to do is-
getting comments and foreach comment getting its commentVote
getting the commentVote, checking if votes array inside each commentVote contains postedBy property or not also inlucdes loggedin
user id inside postedBy property
my controller -
exports.getComments = (req, res) => {
const { featureId } = req.params;
Comment.find({ feature: featureId })
.populate("postedBy", "_id username")
.sort({ createdAt: -1 })
.exec((err, comments) => {
if (err) {
return res.status(400).send({
error: errorHandler(err),
});
}
let commentsArray = comments;
commentsArray.forEach((comment) => {
CommentVote.findOne({ comment: comment._id }).exec(
(err, commentvote) => {
if (err) {
return res.status(400).send({
error: errorHandler(err),
});
}
console.log("commentvote.votes", commentvote.votes.length);
const votes = commentvote.votes;
const test = commentvote.votes.includes("req.auth._id");
console.log(test);
});
}
);
});
});
};
I am wondering if i can get true or false on console.log(test);
because based on that i will send response to that particular comment
That particular comment is liked or not!

Unable to access user info object property - facebook chat api

I'm using Facebook chat api to create a simple cli script that will reply to messages that are sent to my facebook account. I'm trying to assign and get the user name and my name to use them inside the reply but they are always undefined. I think that the object property aren't assigned correctly. Is there a fix for this?
require('dotenv').config();
const fs = require('fs');
const fb = require('facebook-chat-api');
const path = require('path');
const appStateFile = path.format({ dir: __dirname, base: 'appstate.json' });
let currentUser = null;
if( !fs.existsSync(appStateFile) ){
//debug .env
console.log(process.env);
fb({email: process.env.FB_EMAIL, password: process.env.FB_PWD}, (err, api) => {
if(err){
return console.log(err);
}
console.log(api);
api.setOptions({
listenEvents: true
});
fs.writeFileSync(appStateFile, JSON.stringify(api.getAppState()));
let id = api.getCurrentUserID();
api.getUserInfo(id, (err, profile) => {
console.log(profile); // profile is logged correctly
currentUser = profile;
});
api.listenMqtt( (err, event) => {
if(err){
return console.log(err);
}
if(event.type === 'message'){
console.log(event.body)
api.getUserInfo(event.senderID, (err, user) => {
if(err){
return console.log(err);
}
console.log(user); // user object is logged correctly
api.sendMessage('...', event.threadID)
});
}
});
});
}else{
fb({appState: JSON.parse(fs.readFileSync(appStateFile))}, (err, api) => {
if(err){
return console.log(err);
}
console.log(api);
api.setOptions({
listenEvents: true
});
let id = api.getCurrentUserID();
api.getUserInfo(id, (err, profile) => {
console.log(profile);
currentUser = profile;
});
api.listenMqtt( (err, event) => {
if(err){
return console.log(err);
}
if(event.type === 'message'){
console.log(event.body)
api.getUserInfo(event.senderID, (err, user) => {
if(err){
return console.log(err);
}
console.log(user)
api.sendMessage(`FB Pager v1.0.\nHi ${user.name}!Your message was forwarded with an email to ${currentUser.name}.`, event.threadID)
});
}
});
});
}
I think the problem here is that api.getUserInfo is asynchronous.
So you would need to nest them to get it to work.
Or you can try this, since getUSerInfo allows you to add an array of user ids to get the data for:
api.listenMqtt((err, event) => {
if (err) {
return console.log(err);
}
if (event.type === "message") {
const currentUserId = api.getCurrentUserID();
const senderId = event.senderID;
api.getUserInfo([currentUserId, senderId], (err, ret) => {
if(err) return console.error(err);
// Ret should contain the two users
// See: https://github.com/Schmavery/facebook-chat-api/blob/master/DOCS.md#getUserInfo
console.log(ret);
});
}
});
Nesting user calls method:
api.listenMqtt((err, event) => {
if (err) {
return console.log(err);
}
if (event.type === "message") {
let currentUserId = api.getCurrentUserID();
api.getUserInfo(currentUserId, (err1, signedInUser) => {
if (err1) {
return console.log(err);
}
api.getUserInfo(event.senderID, (err2, userInMessage) => {
if (err2) {
return console.log(err);
}
console.log(signedInUser, userInMessage)
api.sendMessage("...", event.threadID);
});
});
}
});
After a lot of debug I've found the correct way to access the needed informations. Since the user informations after that are retrived are mapped to another object that is the userId, the only way to access to each property is to use a for loop. Initially I was thinking that this can be avoided but unfortunately it's necessary otherwise using only dot notation will result in undefined. This is how I've solved
api.getUserInfo(userId, (err, user) => {
let username;
if(err){
return console.log(err);
}
for(var prop in user){
username = user[prop].name;
}
api.sendMessage(`Hello ${username!}`, event.threadID);
});

Firebase firestore won't persist array

I have a cloud function that processes and stores data sent to it. Most of the data comes from the request except for some ID values I store in the main collection I am working with for reference. My problem is the data I get from the other cloud firestore collection is not persisted in the collection I am writing to. My code looks like this:
await emails.forEach(async (email: string) => {
try {
// This should only ever return one result
const student = await usersRef.where('userEmail', '==', email).get();
if (student.empty) {
console.log(`No matching documents for email: ${email}`);
} else {
student.forEach((s) => {
const studentData = s.data();
console.log('found student: ', studentData);
// StudentIds stores reference to student objects
studentIds.push(studentData.playerUniqueID);
});
}
} catch (err) {
console.log('Error finding students: ', err);
throw new functions.https.HttpsError('internal', 'Error finding students');
}
});
const sessionId = uuidv4();
const newSession: newSessionWriteRequest = {
emails, // emails is also an array of strings and is persisted properly
owner,
// StudentIds is not empty at this point and is populated correctly. StudentIds is an array of strings
studentIds,
ongoing: true,
sessionName: data.sessionName,
startTime: data.sessionStartDate,
sessionId
};
try {
// All values except studentIds are persisted to the sessionsRef except studentIds, which is a blank array
await sessionsRef.doc(sessionId).set(newSession);
console.log('new session: ', newSession);
return newSession;
} catch (err) {
console.log('Error creating new session: ', err);
throw new functions.https.HttpsError('internal', 'Error creating new session');
}
StudentIds is just an array of strings. emails is also an array of string but is stored correctly. The only difference between the two is that emails comes from the initial request to the function whereas studentIds comes from firestore. My question is why is studentIds not being persisted correctly? Is there some kind of interaction between firebase collections I am not aware of?
The issue lied with this block of code:
await emails.forEach(async (email: string) => {
try {
// This should only ever return one result
const student = await usersRef.where('userEmail', '==', email).get();
if (student.empty) {
console.log(`No matching documents for email: ${email}`);
} else {
student.forEach((s) => {
const studentData = s.data();
console.log('found student: ', studentData);
// StudentIds stores reference to student objects
studentIds.push(studentData.playerUniqueID);
});
}
} catch (err) {
console.log('Error finding students: ', err);
throw new functions.https.HttpsError('internal', 'Error finding students');
}
});
As pointed out in the comments, my await in front of the forEach loop was doing nothing. Changing it to this fixed the issue:
const studentIds: string[] = await getStudentPlayerUniqueIds(emails);
...
const getStudentPlayerUniqueIds = async(emails: string[]): Promise<string[]> => {
const studentIds: string[] = []
try {
for (const email of emails) {
const student = await usersRef.where('userEmail', '==', email).get();
if (student.empty) {
console.log(`No matching documents for email: ${email}`);
} else {
student.forEach((s) => {
const studentData = s.data();
console.log('found student: ', studentData);
studentIds.push(studentData.playerUniqueID);
});
}
}
return studentIds;
} catch (err) {
console.log('Error finding students: ', err);
throw new functions.https.HttpsError('internal', 'Error finding students');
}
}

Node js returning values from function

I live in the PHP world but I am attempting to build out a REST Api using Node.
I have been stuck all day of trying to return an array of results from a for loop. Basically I am passing an array of field_name:field_value. I want to push the result from the update into an array to return. I can get it to log in the console but no further.
Here is a sample post json data
{
"first_name":"Jeff",
"phone":"4855555555"
}
Here is the function and loop
function UpdateKey(user_id, key, value, cb) {
connection.query('UPDATE users SET ' + key + ' = ? WHERE id = ? LIMIT 1', [value, user_id], function(err, results) {
if (err) {
callback = key + " update failed.";
} else {
callback = key + " was updated.";
}
cb(callback);
});
}
for (myKey in post_data) {
UpdateKey(user_id, myKey, post_data[myKey], function(id) {
console.log(id);
});
}
res.send(JSON.stringify({ "status": 200, "error": "", "response": my_results_here }));
I have been researching async but not sure the best route here. Any help would be great!
You could collect all results in an array and send that when the arrays size equals the keys size:
const keys = Object.keys(post_data);
const response = [];
for(const myKey of keys) {
UpdateKey(user_id, myKey, post_data[myKey], function(id) {
response.push(id);
if(keys.length === response.length) {
res.send(JSON.stringify({
status: 200,
error: "",
response
}));
}
});
}
The solution You want:
const updateUserField = (userId, field, value) => {
return Promise((resolve) => {
const query = 'UPDATE users SET ' + field + ' = ? WHERE id = ?';
const data = [value, userId];
connection.query(query, data, (error) => {
if (error) return resolve(field + ' update failed');
resolve(field + ' was updated');
});
});
};
router.post('/user/:id', async (req, res) => {
const userId = req.params.id;
const data = req.body;
const response = [];
for (const field in data) {
response.push(
await updateUserField(userId, field, data[field])
);
}
res.status(200).send({
response
});
});
or in parallel:
router.post('/user/:id', async (req, res) => {
const userId = req.params.id;
const data = req.body;
const response = await Promise.all(
Object
.keys(data)
.map(field => updateUserField(userId, field, data[field]))
);
res.status(200).send({
response
});
});
Correct solution
As I understand You want to get post data and update record in users table.
So why not just do it in one query?
Try this way:
const updateUser = (userId, data) => {
return Promise((resolve, reject) => {
const query = 'UPDATE users SET ? WHERE id = ?';
connection.query(query, [data, userId], (error) => {
if (error) return reject(error);
resolve();
});
});
};
router.post('/user/:id', async (req, res) => {
try {
const userId = req.params.id;
const data = req.body;
await updateUser(userId, data);
res.status(200).send({
message: 'User account successfully updated'
})
}
catch (error) {
console.error(error);
res.status(500).send({
message: 'Failed update user account'
});
}
});
But better think about using ORM i.e. Sequelize for security, validation and etc features that eases dev's life.

Categories

Resources