I am working on an endpoint where I want to update the user's game stats: highestScore and totalGamesPlayed. As far as I can tell, the code should be working. However, when making a PATCH request with Postman, I receive this error:
PS. I am using findOneAndUpdate because save() wasn't working, I wasn't getting the updated version of the user.
"Cast to Number failed for value \"{ '$max': [ '$userStats.highestScore', 100 ] }\" (type Object) at path \"highestScore\"",
Here's my userModel.js:
import mongoose from "mongoose";
const userSchema = mongoose.Schema(
{
username: {
type: String,
required: [true, "Please add a username"],
},
email: {
type: String,
required: [true, "Please add your email"],
},
password: {
type: String,
required: [true, "Please add your password"],
},
userStats: [
{
totalGamesPlayed: {
type: Number,
default: 0,
},
highestScore: {
type: Number,
default: 0,
},
},
],
},
{ timestamps: true }
);
const User = mongoose.model("User", userSchema);
export default User;
My update controller:
export const updateUserStats = asyncHandler(async (req, res) => {
// Extract token from headers
const token = req.headers.authorization.split(" ")[1];
// Find the user by token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Find user by id
const user = await User.findById(decoded.id);
if (!user) {
return res.status(404).send("User not found");
}
// Update the user's stats
// Save the updated user
const updatedUser = await User.findOneAndUpdate(
{ _id: decoded.id },
{
$set: {
"userStats.totalGamesPlayed": { $inc: 1 },
"userStats.highestScore": {
$max: ["$userStats.highestScore", req.body.highestScore],
},
},
},
{ new: true }
);
if (updatedUser) {
res.json(updatedUser);
} else {
res.status(400);
throw new Error("Something went wrong");
}
});
CodePudding user response:
It seems like a mix in syntax between update $max and $max (aggregation) (also $inc )
tl;dr
Your call to findOneAndUpdate
should probably look like this:
const updatedUser = await User.findOneAndUpdate(
{ _id: decoded.id },
{
$inc: {
"userStats.totalGamesPlayed": 1
},
$max: {
"userStats.highestScore": req.body.highestScore
},
},
{ new: true }
);
explanation
The syntax you used seems like it belongs to the Aggregation Framework (see Introduction to the MongoDB Aggregation Framework), where in your case findOneAndUpdate
is an operation with slightly different syntax.
In the operation findOneAndUpdate
the $set
operator interprets the syntax you used as if you are trying to assign the Object { '$max': [ '$userStats.highestScore', 100 ] }
as if it was a number to the field $userStats.highestScore
. The $max
operator does not work inside $set
(but it would work in the Aggregation Framework).
Mongodb's more "simple" operations like insert
, find
, findOneAndUpdate
, etc.. only have one stage. The Aggregation Framework allows more sophisticated multi-stage operations. These operations work where more context or data is available. That also means some operators like $max
need to be written slightly differently to make use of additional data or sometimes to avoid ambiguity (and probably for other reasons as well).
In the simple operation of findOneAndUpdate
, there's no "direct" access to the current value of the a field. But an operator like $max
can still do a comparison and work on it.
The syntax you used, which would work in an Aggregation Framework stage, opens the door to using values from other fields to calculate the max value. But it is unavailable for findOneAndUpdate
, so the $max
syntax is simpler and more limited.
"regular" operator: $max
Aggregation Framework operator: $max (aggregation)