mongodb edit array don't update it - javascript

I'm trying to edit the collection of users where their purchase date has expired but when I'm saving, user.role have been updated but purchaseHistory don't have been updated.
Here is the function
async function checkExpiredPurchase() {
const users = await User.find({
"purchaseHistory.expdate": { $exists: true, $lte: new Date() }
});
for(const user of users) {
let purchases = await user.purchaseHistory.map(purchase => {
if(purchase.expdate && purchase.expdate <= new Date()) {
purchase.expdate = null;
}
return purchase
});
user.role = "Utilisateur"
user.purchaseHistory = purchases
console.log(user.purchaseHistory) //here it return [{"date":"2022-12-12T20:53:48.467Z","item":"VIP","expdate":null,"methode":"PayPal"}] which is what I want
user.save(function(err, resolve) {
if (err)
console.log('db error', err)
// saved!
});
}
}
And when I check on MongoDB Compass (after refreshing ofc), I see that the role have been updated but not the array with purchaseHistory since expdate is still egal to a date and not to "null"
And I've also tried with this code for update
User.updateOne(
{ _id: user.id },
{ $set: { purchaseHistory: purchases }, $set: { role: "Utilisateur" } },
).then(() => { console.log('should be good')})
But as well as the other one, role update, not purchaseHistory
There was never an error in the console. Thank you in advance for any answers you can bring me

I was not able to reproduce the problem, it worked here just fine:
var mongoose = require('mongoose');
mongoose.set('strictQuery', true);
(async () => {
await mongoose.connect("mongodb://user:password#0.0.0.0:50000", { dbName: "somedb" });
const User = mongoose.model("user-test", {
purchaseHistory: [{ expdate: Date, item: String }],
role: String
});
await User.deleteMany({});
await User.insertMany([
{ role: "User", purchaseHistory: [{ expdate: "1800-01-01", item: "VIP" }], },
{ role: "User 2", purchaseHistory: [{ expdate: "2050-01-01", item: "VIP" }], }
]);
const users = await User.find({
"purchaseHistory.expdate": { $exists: true, $lte: new Date() }
});
console.log("\r\nDOCUMENT BEFORE SAVE:\r\n\r\n", users[0]);
for (const user of users) {
user.purchaseHistory.forEach(purchase => {
if (purchase.expdate && purchase.expdate <= new Date()) {
purchase.expdate = null;
}
});
user.role = "Utilisateur"
console.log("\r\nDOCUMENT AFTER SAVE:\r\n\r\n", await user.save());
}
})();

Related

Why could the findOrCreate plugin create several documents at once?

I'm currently working on a MERN application with following/follower function for the users. I decided to create separate schemas for following and follower relationships detached from user schema.
Follower schema
const mongoose = require('mongoose');
const findOrCreate = require('mongoose-findorcreate');
const ObjectId = mongoose.Schema.Types.ObjectId;
const followerSchema = mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
follower: {
type: [ObjectId],
ref: 'User'
}
});
followerSchema.plugin(findOrCreate);
const Follower = mongoose.model('Follower', followerSchema);
module.exports = { followerSchema, Follower };
Following schema
const mongoose = require('mongoose');
const findOrCreate = require('mongoose-findorcreate');
const ObjectId = mongoose.Schema.Types.ObjectId;
const followingSchema = mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
following: {
type: [ObjectId],
ref: 'User'
}
});
followingSchema.plugin(findOrCreate);
const Following = mongoose.model('Following', followingSchema);
module.exports = { followingSchema, Following };
The problem however starts in my service where followings are created as supposed to. However, for followers mongoose create 6-8 documents at once with userIds that don't even exist in my db.
Here's the code of the followerService (it's the first function)
const { Follower } = require('../models/followerModel');
const { Following } = require('../models/followingModel');
const { User } = require('../models/userModel');
const mongoose = require('mongoose');
exports.changeFollowStatus = async (user, userId) => {
console.log({ userId: userId, user: user._id });
const newFollowing = await Following.findOrCreate({ userId: user._id }, (err, following, created) => {
console.log({following: following});
if (!err && !isFollowing(following, userId)) {
following.following.push(mongoose.Types.ObjectId(userId));
following.save();
User.findByIdAndUpdate(mongoose.Types.ObjectId(userId), {$inc: {follower: 1}});
} else {
const followingIndex = following.following.indexOf(mongoose.Types.ObjectId(userId));
following.following.splice(followingIndex, 1);
following.save();
User.findByIdAndUpdate(mongoose.Types.ObjectId(userId), { $inc: { follower: -1 } });
}
});
const newFollower = await Follower.findOrCreate({ userId: mongoose.Types.ObjectId(userId) }, (err, follower, created) => {
console.log({follower: follower});
if (!err && !isFollower(follower, user._id)) {
follower.follower.push(user._id);
follower.save();
User.findByIdAndUpdate(user._id, { $inc: { following: 1 } });
} else {
const followerIndex = follower.follower.indexOf(user._id);
follower.follower.splice(followerIndex, 1);
follower.save();
User.findByIdAndUpdate(user._id, { $inc: { following: -1 } });
}
});
};
exports.showFollowings = async (userId) => {
const followings = await Following.findOrCreate({ userId: mongoose.Types.ObjectId(userId) });
return followings.following;
};
exports.showFollowers = async (userId) => {
const followers = await Follower.findOrCreate({ userId: mongoose.Types.ObjectId(userId) });
return followers.follower;
};
const isFollowing = (newFollowing, userId) => {
return newFollowing.following.includes(mongoose.Types.ObjectId(userId));
};
const isFollower = (newFollower, userId) => {
return newFollower.follower.includes(userId);
}
Now, my following adding code and follower adding code look almost identical, but for some reason for followers, mongoose creates many more documents. The strange thing is that there is a follower document with the correct userId, but many other with random userIds get created which doesn't happen with followings which works as supposed to.
I also checked whether I pass the correct values and everything seems to be fine. But just for reference, here's the controller function from which I pass the values.
exports.changeFollowingStatus = async (req, res, next) => {
passport.authenticate('jwt', async (err, user, info) => {
if (err) {
console.error({ authError: err });
};
if (info !== undefined) {
console.error({ authError: info.message });
res.status(403).send(info.message);
} else {
console.log({params: req.params});
const userToFollow = req.params.id;
await FollowerService.changeFollowStatus(user, userToFollow);
res.status(200).send({ message: 'success' })
}
})(req, res, next);
};
Could anyone help me with this bug or at least navigate me towards the right direction? I can't seem to find solution to my problem. Thank you all in advance!

How to use updateOne in Mongodb using NodeJS

Below is my code to update a website field in Mongodb using NodeJS, instead of updating however it adds a new field to my database.
let { ObjectId } = require('mongodb');
async get(id) {
if (!id) throw 'You must provide an id to search for';
const restaurantsCollection = await restaurants();
const res = await restaurantsCollection.findOne({ _id: id });
if (res === null) throw 'No dog with that id';
res._id = res._id.toString().replace(/ObjectId\("(.*)"\)/, "$1")
return res;
}
async rename(id, newWebsite) {
if (!id) throw 'You must provide an id to search for';
if (!newWebsite) throw 'You must provide a name for the restaurant';
let newid = ObjectId(id);
const restaurantsCollection = await restaurants();
const updatedInfo = await restaurantsCollection.updateOne(
{ _id: newid },
{ $set: {website:newWebsite} }
);
if (updatedInfo.modifiedCount === 0) {
throw 'could not update restaurant successfully';
}
return await this.get(newid);
}
};
Below is the output I get:
{
_id: new ObjectId("6157825a4dcae5beb8f759bf"),
name: 'Hey',
location: 'New York City, New York',
phoneNumber: '123-456-7890',
website: 'http://www.hey.com',
priceRange: '$$$$',
cuisines: [ 'Cuban', 'Italian' ],
overallRating: 3,
serviceOptions: { dineIn: true, takeOut: true, delivery: false },
newWebsite: 'http://www.hi.com'
}
Instead of the website being replaced from hey.com to hi.com it just adds a new field, what am I doing wrong?
Instead of using { $set: {website:newWebsite} }
It should just be {website:newWebsite}

findOneAndUpdate is not a function of mongoose

I know this question has been answered before but I can't seem to implement the changes into what im working with. I'm trying to create a daily command that rewards a user for doing s!daily. I get the error,
TypeError: profileData.findOneAndUpdate is not a function
at Object.execute (C:\Users--\Desktop\DiscBot\commands\daily.js:35:43)
at module.exports (C:\Users--\Desktop\DiscBot\events\client\message.js:34:13)
daily.js, one having error at line 35 for findOneAndUpdate is not a function
const Schema = require('../models/profileSchema')
//cache users that claim daily rewards
let claimedCache = []
const clearCache = () => {
claimedCache = []
setTimeout(clearCache, 1000 * 60 * 10)
}
clearCache()
//message to make it easier later
const alreadyClaimed = 'You have already claimed your daily rewards'
module.exports = {
name: "daily",
aliases: ["day", "d"],
permissions: [],
description: "Claim your daily rewards!",
async execute(message, args, cmd, client, Discord, profileData) {
const { serverID, member } = message
const { id } = member
//If user is in cache return message
if (claimedCache.includes(id)) {
console.log('Returning from cache')
message.reply(alreadyClaimed)
return
}
//Put everything in object for later
const obj = {
guildId: serverID,
userId: id,
}
//Results is an update that either updates if is user is not in array and doesn't if they are, but it doesn't know what findOneAndUpdate is (thought it was just a mongo/mongoose function??)
try {
const results = await profileData.findOneAndUpdate(obj)
console.log('RESULTS:', results)
if (results) {
const then = new Date(results.updatedAt).getTime()
const now = new Date().getTime()
const diffTime = Math.abs(now - then)
const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24))
if (diffDays <= 1) {
claimedCache.push(id)
message.reply(alreadyClaimed)
return
}
}
//after the update increase coins by 50 and send claimed message
await profileRewardsSchema.findOneAndUpdate(obj, obj, {
upsert: true,
})
claimedCache.push(id)
const amount = 50;
await profileModel.findOneAndUpdate(
{
userID: id,
},
{
$inc: {
coins: amount,
},
}
);
message.reply('You have claimed your daily rewards!')
}catch (err) {
console.log(err);
}
}
}
message.js, heres where I make profileModel a thing using mongoose to pass it into my commands
const profileModel = require("../../models/profileSchema");
const config = require('../../config.json');
module.exports = async (Discord, client, message) => {
//command handler start
const prefix = 's!';
if (!message.content.startsWith(prefix) || message.author.bot) return;
//database junk
let profileData;
try {
profileData = await profileModel.findOne({ userID: message.author.id });
if (!profileData) {
let profile = await profileModel.create({
userID: message.author.id,
serverID: message.guild.id,
coins: 10,
bank: 0,
});
profile.save();
}
} catch (err) {
console.log("Error creating new database profile");
}
const args = message.content.slice(prefix.length).split(/ +/);
const cmd = args.shift().toLowerCase();
const command = client.commands.get(cmd) || client.commands.find(a => a.aliases && a.aliases.includes(cmd));
if(!command) return message.channel.send(":x: This is not a valid command");
try {
command.execute(message, args, cmd, client, Discord, profileData);
} catch (err) {
message.reply('There was an error executing that command!');
}
};
profileSchema.js, Where profile is made into mongo database
const mongoose = require("mongoose");
const profileSchema = new mongoose.Schema({
userID: { type: String, require: true, unique: true },
serverID: { type: String, require: true },
coins: { type: Number, default: 10 },
bank: { type: Number },
},
{
timestamps: true,
}
)
const model = mongoose.model("ProfileModels", profileSchema);
module.exports = model;
main.js, where mongoose is connected, then passed on
mongoose.connect(process.env.MONGODB_SRV, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
})
You are trying to call findOneAndUpdate on the document, which you passed to execute function at message.js. Check the example of how to use findOneAndUpdate
https://mongoosejs.com/docs/tutorials/findoneandupdate.html
Most of the time this error happen when you call findOneAndUpdate in mongoose when you call it on the instance of the model NOT the actual model
so instead of this
var NewUser = new User(req.user);
NewUser.findOneAndUpdate...
do this
var NewUser = new User(req.user);
User.findOneAndUpdate(
{ name: NewUser.name },
{ name: NewUser.name},
{ upsert: true });

Mongoose find by a subdocument's value

I have 2 schemas
const schema = Schema({
headLine: {
type: String,
required: false
},
availableDays: [{
type: Schema.Types.ObjectId,
ref: AvailableDay
}]
}, {collection: 'providers', timestamps: true});
module.exports = mongoose.model("Provider", schema);
const schema = Schema({
day: {
type: String,
enum: ['Mondays','Tuesdays','Wednesdays','Thursdays','Fridays','Saturdays','Sundays']
},
timeFrom: String,
timeTo: String
}, {collection: 'availableDays', timestamps: true});
module.exports = mongoose.model("AvailableDay", schema);
Then in a route I call to a repository like this
router.get('/', async (req, res) => {
const match = {};
const sort = {};
const options = {};
// Arrange sort
if(req.query.sortBy){
const sortArray = JSON.parse(req.query.sortBy);
sortArray.map(e => sort[e[0]] = e[1] && e[1] === 'desc' ? -1 : 1);
options['sort'] = sort
}
// Get the pagination: limit how many, skip where it starts
if(req.query.limit) {
options['limit'] = parseInt(req.query.limit);
}
if(req.query.skip) {
options['skip'] = parseInt(req.query.skip);
}
const docs = await ProviderRepository.findBy(match, {}, options);
res.status(200).json(docs)
});
So what I need here is to filter providers for an AvailableDay monday and return the docs and count the total docs for pagination. I'm doing something like this without success
const findBy = async (params, projection = "", options = {}, callback) => {
const data = () => {
Provider.find(params, projection, options)
.populate([{path: 'user', match: {gender: 'F'}}]).exec((error, e) => {
if (error) {
console.log('error:', error)
return {error: error}; // returns error in json
}
return e.filter(i => i.user);
});
};
const total = await Provider.countDocuments(params).exec();
return {data(), total}
}
Thanks in advance
Use mongoose-aggregate-paginate-v2 and update your schema. If you use that package then you have to convert your queries from populate to aggregate style.
STEP 1: Update schema. Sample Schema:
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-aggregate-paginate-v2');
const Schema = mongoose.Schema;
let definition = {
headLine: {
type: String,
required: false
},
availableDays: [{
type: Schema.Types.ObjectId,
ref: AvailableDay
}]
};
let options = {
collection: 'providers'
};
let providerSchema = new Schema(definition, options);
providerSchema.plugin(mongoosePaginate);
module.exports = mongoose.model('providers', providerSchema);
STEP 2: Update controller. Sample code in controller:
router.get('/', async (req, res) => {
const match = {}
const sort = {
// Fill it based on your sort logic.
}
const paginateOptions = {
page: req.query.page, // Page number like: 1, 2, 3...
limit: req.query.limit // Limit like: 10, 15, 20...
};
ProviderRepository
.findBy(match, {}, sort, paginateOptions)
.then(() => {
res.status(200).json(docs)
})
.catch(() => {
res.status(HTTP_ERROR_CODE).json({ "error": "Your error message" })
})
});
STEP 3: Update manager. Sample code in manager:
const findBy = (match, projection, sort, paginateOptions) => {
if (!paginateOptions) {
paginateOptions = {
pagination: false
};
}
let providerAggregate = providerSchema.aggregate([
{
$lookup: {
from: "availableDays",
let: { days: "$availableDays" },
pipeline: [
{
$match: {
$expr: {
$in: ["$$availableDays", "$day"]
}
}
}
],
as: "availableDays"
}
},
{
$lookup: {
from: "users", // I dont know the collection name
let: { user_id: "$user" }
pipeline: [
{
$match: {
"gender": 'F',
$expr: {
$eq: ["$_id", "$$user_id"]
}
}
}
],
as: "users"
}
}
{ $sort: sort }
]);
return providerSchema
.aggregatePaginate(providerAggregate, paginateOptions)
.then(res => {
return res;
})
.catch(err => {
throw err;
});
};

Update fields in object with mongoose in mongodb

I have a simple collection in mongodb.
I use mongoose.
I have users model with one field type object.
And I want change this object dynamically. But this code doesn't work, I used findByIdAndUpdate(), findById, findOne(), findOneAndUpdate().
const UsersSchema = mongoose.Schema({
likes: {}
},
{ collection: 'users' });
const Users = mongoose.model('Users', UsersSchema);
const id ="5b4c540f14f353a4b9875af4";
const thems = ['foo', 'bar'];
Users.findById(id, (err, res) => {
thems.map(item => {
if (res.like[item]) {
res.like[item] = res.like[item] + 1;
} else {
res.like[item] = 1;
}
});
res.save();
});
I believe that, for solve this problem you need to add more fields in your schema:
I created one example with this data:
const UsersSchema = new mongoose.Schema({
likes :[
{
thema:{
type: String
},
likes_amount:{
type: Number
},
_id:false
}]
});
module.exports = mongoose.model('Users', UsersSchema);
I added one user:
var newUser = new UserModel({
likes:[{
thema:'foo',
likes_amount:1
}]
});
newUser.save();
Here the code that increment the likes per thema:
const thems = ['foo', 'bar'];
const userId = "5b4d0b1a1ce6ac3153850b6a";
UserModel.findOne({_id:userId})
.then((result) => {
var userThemas = result.likes.map(item => {
return item.thema;
});
for (var i = 0; i < thems.length; i++) {
//if exists it will increment 1 like
if (userThemas.includes(thems[i])) {
UserModel.update({_id: result._id, "likes.thema" : thems[i]}, {$inc: {"likes.$.likes_amount": 1}})
.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err)
});
} else {
//if doesn't exist it will create a thema with 1 like
UserModel.update({_id: result._id},
{
$addToSet: {
likes: {
$each: [{thema: thems[i], likes_amount: 1}]
}
}})
.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err)
});
}
}
}).catch((err) => {
console.log(err)
});
Database result of this increment:
I hope that it can help you.

Categories

Resources