ReferenceError: Cannot access 'savedPost' before initialization - node.js

My POST method which throws the ReferenceError:
postsRouter.post('/:postNumber', async (req, res, next) => {
const postDate = new Date().toUTCString()
const post = new Post({
...req.body,
poster: req.body.poster
? (req.body.poster.includes(config.AP) ? req.body.poster.replace(config.AP, '') : req.body.poster)
: 'nonamer',
date: postDate,
IP: req.socket.remoteAddress,
admin: req.body.poster.includes(config.AP) ? true : false
})
try {
const [savedPost, updatedBubble] = await Promise.all([
/* save() doesn't save the post with the new postnumber which it should (see the pre('save')
function in the next code block), it's only in the post that is being returned while saving. */
post.save().catch(e => { console.log('Task 1 failed!'); next(e) }),
// req.params.postNumber is the postnumber of the bubble a.k.a thread being replied to.
Post.findOneAndUpdate(
{ postNumber: req.params.postNumber },
{ $push: { replies: savedPost.postNumber } })
.catch(e => { console.log('Task 2 failed!'); next(e) })
])
res.status(201).json(savedPost)
console.log('!!!!! ' + req.socket.remoteAddress + ' saved a post: ' + JSON.stringify(savedPost))
} catch (e) {
next(e)
}
})
Also this mess of a code is in my Post model (which utilizes Counter model):
// Increase postnumber in counter collection's sole document, and insert it into new post.
postSchema.pre('save', function(next) {
let post = this
const ccc = Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 }}, // Works
{ new: true },
// inserting postnumber not working, fix
function(error, counter) {
post.postNumber = counter.currentPostNum
}
)
// Not working either
post.postNumber = ccc.currentPostNum
next()
})
My goal is I simply want to grab the currentPostNum after incrementing it, and insert that into the post before saving it.

Solved myself. I made the pre('save') function asynchronous as well, and ditched Promise.all in the controller POST method. Now everything works in tandem.
postSchema.pre('save', async function (next) {
let post = this
const counter = await Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 } }, // Works
{ new: true }
)
post.postNumber = counter.currentPostNum
next()
})

Related

Too Many Promises in single nodejs route

I want to know if one of the promises fail how can i rollback or cancel already happened operations.
and 2nd is there any other way to optimize code, it takes more time to resolve.
As the number of joined player will increase it will be taking more time is there any way to optimise it more
route.put("/UpdateResult/:id", Get_User_id, async (req, res) => {
try {
const response = await tournamentschema.findByIdAndUpdate(
req.params.id,
{
Game_Name: req.body.Game_Name,
Total_Players: req.body.Total_Players,
Prize_Pool: req.body.Prize_Pool,
Joined_User: req.body.Joined_User,
Is_Finished: true,
},
{ new: true, runValidators: true }
);
response.Joined_User.forEach(async (Player) => {
await UserModal.findByIdAndUpdate(
Player.UserId,
{
$inc: {
Wallet_Coins: Player.Kills * parseInt(response.Prize_Pool),
},
},
{ new: true }
);
});
return res.send("Result Updated Sucessfully");
} catch (error) {
console.log(error.message);
res.status(500).send(error.message);
}
});
for optimize :
in ES7 and 8 we are have new feature called promise all in for your problem its better you don't use forEach for your await function its better in first you get all your id in new array like this :
let playerCacheId = []
response.Joined_User.forEach((Player) => {
playerCacheId.push(player.id)
}
await Promise.all(playerCacheId .map(playerId => UserModal.findByIdAndUpdate(
playerId ,
{
$inc: {
Wallet_Coins: Player.Kills * parseInt(response.Prize_Pool),
},
},
{ new: true }
);
});
))
To rollback Operations use MongoDB Transactions

node mongoose how to auto increment

Trying to follow the example here:
https://www.tutorialspoint.com/mongodb/mongodb_autoincrement_sequence.htm
export interface RowProps {
id?: number; // This is to auto increment
todoText: string;
}
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const getNextSequenceValue = (sequenceName: string) => {
const sequenceDocument = db
.collection<RowProps>(collectionName)
.findAndModify({
query: { _id: sequenceName },
update: { $inc: { sequence_value: 1 } },
new: true,
});
console.log('sequenceD', sequenceDocument)
return sequenceDocument.sequence_value;
};
db.collection<RowPropsClient>(collectionName).insertOne(
{
id: getNextSequenceValue('id'),
todoText
},
(err) => {
if (err) {
console.log("err");
}
}
);
}
// db is already defined and works
// I can add to the collection so this also works.
addAutoIncrement({ db, collectionName: 'todos', todoText: 'hello' });
Error: throw new Error('Collection#findAndModify unimplemented by driver');
^
Error: Collection#findAndModify unimplemented by driver
update
Tried to follow this example:
https://medium.com/#salonimalhotra1ind/how-to-increment-a-number-value-in-mongoose-785066ba09d8
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const modelTodo = db.model(collectionName, TodosSchema);
const res = await new modelTodo({ todoText }).save();
const { _id } = res;
return new Promise((resolve, reject) => {
modelTodo.findOneAndUpdate(
{ _id },
{ $inc: { id: 1 } },
{ new: true },
(err, res) => {
if (err) {
reject(err);
}
resolve(res);
}
);
});
};
**The result is just setting the value to 1 each time - not incrementing**
Collection#findAndModify() is a method that is implemented in the MongoDB shell, but not in the Node.js driver.
You should use Collection#findOneAndUpdate instead:
const { value : sequenceDocument } = db
.collection<RowProps>(collectionName)
.findOneAndUpdate({
{ _id: sequenceName },
{ $inc: { sequence_value: 1 } },
{ returnDocument : 'after' } // equivalent to `new: true`
});
ok I don't know why I didnt do this before. All the online examples make everything unnecessarily complicated.
Just get the total count and then add it.
const addAndIncrement = async ({ db, collection, todoText }) => {
const connectedModel = db.model(collection, TodosSchema);
const documentCount = await connectedModel.count({}); // get the total count
return new connectedModel({ todoText, id: documentCount }).save();
};
Unless anyone comes up with a more performant way, this is what I'm going with.

Delay in return value - nodejs & mongoose

I'm fairly new to nodejs and I'm doing a full stack developer challenge from devchallenges.io (Shoppingify). Below, I'm trying to increase the quantity value based on whether the user clicks to increase or decrease the item quantity. However, there's a slight delay between the return value from the request and the actual value in the database. It seems that the value updates immediately which is great however, the return value in the request is the previous value rather than being the current quantity value in the database.
mongoDB Database
// #route PUT api/list/item/quantity/:id
// #desc update item quantity
// #access Private
router.put('/item/quantity/:id', auth, async (req, res) => {
const { action } = req.body;
try {
let list = await List.findOne({ user: req.user.id });
// find current quantity
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
// increase quantity
if (action === 'increase') {
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity + 1 } },
{ new: true }
);
} else {
// decrease quantity
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
}
res.json(item.quantity);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
You are defining item in here:
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
At this point list is the "old" version of the object, you want to be doing the same after the update when the list object is updated and only then to return it.
// this is the original "list" item
let item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
...
update list
...
// now "list" is updated
item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
I will just add two additional tips to improve performance, they are mutually exclusive so you'll have to choose one of the two.
in the update query add the list._id to it, If I were to guess the collection does not have an index on the items field ( and if it does it's a bad idea usually ). this means when you updated just using the item._id field it takes longer for mongo to find the object. it's quick change to both updates:
list = await List.findOneAndUpdate(
{ _id: list._id, 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
(my preferred option) do it in a single call, using the update arrayFilters option, like so:
const list = await List.findOneAndUpdate(
{
user: req.user.id,
},
{
$inc: {
'items.$[elem].quantity': action === 'increase' ? 1 : -1,
},
},
{
arrayFilters: [
{
'elem._id': new ObjectId(req.params.id),
},
],
new: true,
});
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
Mongo Playground
In my opinion now your route looks much better, you're also cutting down from 2 db calls to 1.

Updating a nested objects in Mongoose

I have the following express route:
const updateSnapshot = async (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
}
try {
const account = await Account
.findOne({ _id: accountId})
.populate({
path: 'snapshot',
model: 'Snapshot'
});
// I want to update these fields in snapshot
const snapshot = {
friends_count: data.friends_count,
updated_date: new Date()
};
account.snapshot.friends_count = snapshot.friends_count;
account.snapshot.updated_date = snapshot.updated_date;
await account.save();
return success(res, snapshot);
} catch(error) {
fail(res, error.message, 500);
}
};
I want to update the nested object snapshot (just the fields friends_count and update_date) however when I check the database it seems to have not work. What am I doing wrong here?
const updateSnapshot = (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
};
Account
.findOneAndUpdate({ _id: accountId }, {
$set: {
snapshot: {
friends_count: data.friends_count,
updated_date: new Date()
}
}
}, {
new: true
})
.then(account => {
if(!account) {
return fail(res, "Account not found", 404);
} else {
return success(res, account);
};
})
.catch(err => {
return fail(res, error.message, 500);
});
};
Here we're using the findOneAndUpdate method and promises to find and update the document in one operation.
findOneAndUpdate takes the query criteria as the first parameter, the second parameter updates values specified in the object (we're using the $set operator to update specific values of the snapshot object), and the three parameter is an object that defines the operation's options (new is set to true to return the newly updated object).
Note: $set will replace the entire snapshot object so if there are other properties inside the snapshot object, they will need to be included inside the $set object.

node mongoose updating an Object in Array doesn't work

I am trying to update an Array of objects wo any success..
the update statement does not work ..
I may have to update the table instance array and update it with the new valueand then save the table...
const picked = table.meta.permissions.find(obj => obj._id == req.params.permissionId);
console.log('picked: %j', picked); // need to update it !
// how can I update this permission object with the new value from req.body ?
table.save()
.then(savedTable => res.json(savedTable))
.catch(e => next(e););
I have an Array of permissions in a 'meta' field:
MODEL
const Schema = mongoose.Schema;
const Permission = new Schema({
permission: {
role_id: { type: String },
canWrite: { type: Boolean }
}
});
const TableSchema = new mongoose.Schema({
meta: {
name: { type: String, required: true },
permissions: [Permission],
...
},
...
);
In the controller , I firstly load the requested table and append it to the req object, then I execute the updatePermission function, and try to update the table instance permission with new values using $set
CONTROLLER
import Table from '../../models/table.model';
/**
* Load table and append to req.
*/
function load(req, res, next, id) {
Table.get(id)
.then((table) => {
req.table = table;
return next();
})
.catch(e => next(e));
}
function updatePermission(req, res, next) {
const table = req.table;
console.log('Current table.meta.permissions: %j', table.meta.permissions, '\n');
console.log('update permission: ', req.params.permissionId, ' with: ', req.body, '\n');
const query = { 'meta.permissions._id': req.params.permissionId }
const update = { $set: { 'meta.permissions.$.permission': req.body } };
const options = { new: true};
table.update(query, update, options)
.then(savedTable => res.json(savedTable))
.catch((e) => { next(e); });
}
The console log displays the current table permissions and the req.params and req.body
Why the update statement doesn't run correctly
thanks for feedback
I found a way to solve this issue but I don't know if it's the best one ?
I update the table object using some() then I save the table..
function updatePermission(req, res, next) {
const table = req.table;
table.meta.permissions.some((obj, idx) => {
if (obj._id == req.params.permissionId) {
table.meta.permissions[idx].permission = req.body;
return true;
}
return false;
});
table.save()
.then(savedTable => res.json(savedTable))
.catch(e => next(e); );
}

Resources