What is wrong with my Mongoose data seeding script? - node.js

I am trying to populate my MongoDB database with data via a script (JS) that I run through npm run.
const mongoose = require('mongoose')
const dbConf = require('../config/database.js')
const User = require('../app/models/user.js')
const Account = require('../app/models/account.js')
mongoose.connect(dbConf.url, { useMongoClient: true })
mongoose.Promise = global.Promise
const users = [
{
_id: "58c039018060197ca0b52d4c",
email: "user1#example.com",
password: "foo",
balance_cents: 100
},
{
_id: "58c03ada8060197ca0b52d52",
email: "user2#example.com",
password: "foo",
balance_cents: 50000
}
]
const accounts = [
{
name: "Postbank",
iban: "1232423423",
swift: "2444444"
},
{
name: "DKB",
iban: "1234923423",
swift: "6667898"
},
{
name: "Fidor",
iban: "909873423",
swift: "998733"
}
]
async function dropDatabase () {
console.log('Removing User collection')
await User.remove()
console.log('Success!')
}
async function seedUsers () {
console.log('Seeding users..')
try {
await User.insertMany(users)
console.log('Success!')
} catch(error) {
console.log('Error:' + e)
}
}
dropDatabase()
seedUsers()
process.exit(0)
But it doesn't seem to get past the await statements in each function. The output is:
Removing User collection
Seeding users..
And there are no objects in the database. I am using the exact same syntax as I found in a tutorial and can't understand why this is not working.
Any idea anyone? I guess it is a very stupid error that I just don't see right now. Thanks!

Your code isn't waiting for dropDatabase() and seedUsers() to complete before calling process.exit(0).
You need to wrap both of those calls in another async method that can wait for their completion.
async function doBoth() {
await dropDatabase();
await seedUsers();
process.exit(0);
}
doBoth();
However, you probably shouldn't need to call process.exit(0), but that's a separate issue.

Related

Mongoose document _id is null, so when I try to save I get MongooseError: document must have and id before saving

I'm making a discord bot to scrape prices from Amazon. Im using a mongoDB database to store links users give to the bot to track the price of the item the link leads to.
My issue is when I run my code and use the add command, my console reads...
Starting...
Online! Logged in as Amazon Price Tracker#6927
Connected to Database
null
MongooseError: document must have an _id before saving
at C:\Users\logic\Documents\Disc Bot\node_modules\mongoose\lib\model.js:291:18
at processTicksAndRejections (node:internal/process/task_queues:78:11)
Disconnected from Database
I've read the doc's and my understanding is mongoose generates a unique id automatically. I am aware that you can override this my defining an id in your schema, but I haven't done this so I don't know why console.log(a) prints null, and the .save() errors out.
My add.js file
//add function using mongoose for mongodb
const { SlashCommandBuilder } = require("#discordjs/builders");
const mongoose = require("mongoose");
const { MongoDBurl } = require("../config.json");
const Link = require("../Schemas/Link.js");
module.exports = {
//Build the slash command
data: new SlashCommandBuilder()
.setName("add")
.setDescription("add a url to watch list")
.addStringOption(option =>
option.setName("url")
.setDescription("url to add to watch list")
.setRequired(true),
),
//Function that runs when the command is used
async execute (interaction) {
const URL = interaction.options.getString("url");
const user = interaction.user.username;
await interaction.reply(`On it! Adding ${URL} to your watch list`)
//Connect to the database, throws an error if it can't connect
await mongoose.connect(MongoDBurl)
.then( () => console.log("Connected to Database"))
.catch(err => console.log(err));
//Check if the link is already in the database
var exists = await Link.exists({ link: URL}).exec()
.catch(err => console.log(err))
if (exists) {
console.log("This Document Already Exists")
interaction.editReply(`Oops! That link is already in my database.`)
} else {
//If the link dosen't exist, create a document and save it to the database
var newLink = new Link({ user: user }, { link: URL }, { price: "N/A" })
// Debuging variable
var a = newLink.id;
console.log(a)
await newLink.save()
.then( () => {
console.log("Document Saved")
interaction.editReply(`All done! I have saved ${URL} to your watch list.`)
})
.catch(err => {
console.log(err)
interaction.editReply("Oops! Something went wrong, I wasen't able to save this link.")
})
}
//Close the connection when we finish
await mongoose.connection.close()
.then( () => console.log("Disconnected from Database"))
}
};
My Link.js file
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const LinkSchema = new Schema({
user: {
type: String,
requiered: true
},
link: {
type: String,
requiered: true
},
price: {
type: String,
requiered: true
},
})
module.exports = mongoose.model("Link", LinkSchema);
When creating a new modal, the options must be within the same pair of curly braces, however when updating, its separate since you are changing multiple elements.
That's why the error was occurring. You have already shared a working piece of code so I'm guessing you no longer need one.
So I found my issue. I changed this line
var newLink = new Link({ user: user }, { link: URL }, { price: "N/A" })
To
const newLink = new Link({ user: user, link: URL, price: "N/A" });
I don't know why this fixed it, I don't think its because I changed var -> const, and looking at the documentation I thought the first line was the correct way to do this
The line I originally used from the documentation
Tank.updateOne({ size: 'large' }, { name: 'T-90' }, function(err, res) {
// Updated at most one doc, `res.nModified` contains the number
// of docs that MongoDB updated
});
Is this an error in the documentation? or a possible bug? either way the issue is now resolved.

Saving array of children of a Mongoose Schema and then adding returned IDs to parent

i'm trying to loop over an array of objects, saving them to MongoDB and then add the returned ObjectIds to a parent Schema which then is also saved. I'm at a loss here.
Everything gets saved correctly but the Recipe (parent) apparently is saved before I get the returned ObjectIds of the Tags (children). I feel like I've used the async and await keywords a bit to often.
Can someone help? Code simplified, but I can post more if needed.
Parent Schema:
const recipe = new mongoose.Schema(
{
name: String,
ingredients: [
{
type: mongoose.SchemaTypes.ObjectId,
ref: "Ingredient",
},
],
}
);
Child Schema:
const ingredientSchema = new mongoose.Schema({
value: String,
label: String,
});
Payload:
{
name: "Rezept",
ingredients: [
{
label: "zutat",
value: "Zutat",
},
{
label: "schokolade",
value: "Schokolade",
},
],
};
My router:
recipesRouter.post("/", async (req, res) => {
const { body } = req;
const saveIngredients = async () => {
let ingredientIDs = [];
body.ingredients.map(async (ingredient) => {
const i = new Ingredient({
value: ingredient.value,
label: ingredient.label,
});
const savedIngredient = await i.save();
ingredientIDs.push(savedIngredient._id);
});
return ingredientIDs;
};
const recipe = new Recipe({
name: body.name,
ingredients: (await saveIngredients()) || [],
});
const savedRecipe = await recipe.save();
res.status(201).json(savedRecipe);
});
Returned recipe:
savedRecipe: {
name: 'asd',
ingredients: [],
_id: new ObjectId("62782b45a431e6efb7b8b1a7"),
}
As I said, both ingredients individually and the recipe is saved to the MongoDB after this but not the ingredient IDs in the recipe. The returned recipe has an empty array in ingredients. I guess the recipe is saved too soon before MongoDB can return ObjectIds for the ingredients.
Thanks for any help.
First of all, your post method is an async, so everything inside it is wrapped in a resolved promise automatically.
Do you really need to make your saveIngredients as an async? IMHO, it's better to let the saveIngredients not be in another async.
And then we can remove the empty list, and just wait for the saveIngredients() finish first.
const recipe = new Recipe({
name: body.name,
ingredients: await saveIngredients(),
});
Your guess is correct, the Recipe was saved first because all the conditions are fulfilled because it doesn't need to wait for the saveIngredients since you provided a [] as the default value. And your saveIngredients is run in parallel.
I got it smh. Turns out async in a .map or .foreach doesn't go well. I turned it into a simple for loop. It's still bloated/lot of steps imo but it works!
recipesRouter.post("/", async (req, res) => {
const { body } = req;
const saveIngredients = async () => {
let ingredientIDs = [];
for (let i = 0; i < body.ingredients.length; i++) {
const el = body.ingredients[i];
const ing = new Ingredient({
value: el.value,
label: el.label,
});
const savedIngredient = await ing.save();
ingredientIDs.push(savedIngredient._id);
}
return ingredientIDs;
};
const ingredientIDs = await saveIngredients();
const recipe = new Recipe({
name: body.name,
ingredients: ingredientIDs,
});
const savedRecipe = await recipe.save();
res.status(201).json(savedRecipe);
});

Why does the “TypeError: client.db is not a function” appear on Node.js and how do I fix it?

I am writing mongodb aggreagation query in nodejs. I tried my best to figure it out, but the codes won't work in my method. I'm getting following error:TypeError: client.db is not a function
const { MongoClient, ObjectId } = require('mongodb');
async function main(){
const uri = `mongodb://${dbUser}:${dbPassword}#${ipAddress}:${port}/${dbName}`;
const client = new MongoClient(uri);
try {
await client.connect();
await printStudents("541516516165164489d3aee");
} finally {
await client.close();
}
}
main().catch(console.error);
/**
* Print the students for a given schoolId
* #param {MongoClient} client A MongoClient that is connected to a cluster with the education database
* #param {ObjectId} schoolId
*/
async function printStudents(client,schoolId){
const pipeline = [
{
'$match' : { '_id' : ObjectId(schoolId) }
},
{
'$project':
{
'_id': { '$toString': '$_id'}
}
},
{
'$lookup':
{
'from': 'students',
'localField': '_id',
'foreignField': 'schools',
'as': 'Students'
}
}
];
const aggCursor = client.db("education").collection("schools").aggregate(pipeline);
await aggCursor.forEach( err => {
console.log(`${err._id}: ${err.$lookup}`);
});
}
I hope I get some good advice on how to tackle the problem properly. :)
You're passing a string as the first argument, which should be the mongo db client.
Change from :
await printStudents("541516516165164489d3aee");
to :
await printStudents(client,"541516516165164489d3aee");
When calling the printStudents method, you have not passed the client object as a parameter. By calling printStudents("541516516165164489d3aee"), the client parameter is a string and the schoolId parameter is undefined.
You should call printStudents(client, "541516516165164489d3aee") instead.

I have 2 issues with mongoose aggregation and index method

I have 2 issues
FIRST ONE:
I am trying to make review schema that a user should add 1 review per bootcamp
Code:
ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true });
It doesnt work .. and the user still can add more than one review
SECOND ISSUE:
I am trying to calculate the averagerating of reviews but it doesn`t get added to the db when am fetching the bootcamps
Code:
// Static Method to get the avg rating of reviews and save
ReviewSchema.statics.getAverageRating = async function (bootcampId) {
const obj = await this.aggregate([
{
$match: { bootcamp: bootcampId },
},
{
$group: {
_id: '$bootcamp',
averageRating: { $avg: '$rating' },
},
},
]);
try {
await this.model('Bootcamp').findByIdAndUpdate(bootcampId, {
averageRating: obj[0].averageRating,
});
} catch (err) {
console.log(err);
}
//Call averageRating after save
ReviewSchema.post('save', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
//Call averageRating before remove
ReviewSchema.pre('remove', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
** It doesnt work and the averagerating never gets added to the database (as a bootcamp`s field)**
I Did the same as the tutorial and it didn`t work at the first but then i figured out that missing a semi-colon.

mongoose filter by multiple conditions and execute to update data

I am wondering what would be the best approach to make schema functions using mongoose. I have never used this so the way I think is somewhat limited, same goes for looking for docs, without knowing what's available, is not very efficient.
Through docs I found that either using findOneAndUpdate might solve the problem; but there are some constraints.
Here is the code I am planning to run:
models/Bookmark.js
const mongoose = require('mongoose')
const bookmarkItemSchema = new mongoose.Schema({
restaurantId: String,
cachedAttr: {
name: String,
latitude: Number,
longitude: Number,
},
})
const bookmarkListSchema = new mongoose.Schema({
listName: String,
items: [bookmarkItemSchema],
})
const bookmarkSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
lists: [bookmarkListSchema],
})
// const add = (lists, userId) => {
// let bookmark = Bookmark.findOne({userId})
// bookmark.lists.listName === lists.listName //current, new
// ? bookmark.lists.items.push(lists.items)
// : bookmark.lists.push(lists)
// return bookmark
// }
mongoose.model('Bookmark', bookmarkSchema)
Routes/bookmark.js
router.post('/bookmarks', async (req, res) => {
const {lists} = req.body
console.log(lists)
if (!lists) {
return res.status(422).send({error: 'You must provide lists'})
}
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
try {
// const bookmark = Bookmark.add(lists, req.user._id, obj)
// await bookmark.save()
// res.send(bookmark)
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){ // THIS IS UNDEFINED. How to get this object?
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
} catch (e) {
res.status(422).send({error: e.message})
}
})
The req.body looks like this:
{
"lists": {
"listName": "My Saved List",
"items": {
"restaurantId": "abcdefg",
"cachedAttr": {
"name": "abcdefg",
"latitude": 200,
"longitude": 200
}
}
}
}
Basically what I commented out in the models/Bookmark.js file is what I would really like to do.
If the userId's list name already exists, then I would like to just add an item to the list.
Otherwise, I would like to add a new list to the object.
What is the best approach for doing this? Is there a straight forward mongoose api that I could use for this problem? or do I need to make two separated function that would handle each case and make that as schema methods and handle it in the routes file?

Resources