Async Await inside for loop? - javascript

Im struggling with this. I have in my db Profile{name: string, table:0}, and im using a for loop to asign the number of the table to the different users, but this is not working. The for loop works fine if i checked in the console, but in the postman request, is not working.
When i filter by table number from the database, there are more than 5 results per table. And in somecases, there are irregular, like, table 1: 8 users, table 2: 5 users, so on...
const ProfileSchema = new Schema(
{
name: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
moderator: {
type: Boolean,
default: false,
},
email: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
institution: {
type: String,
/* required: true */
},
score: {
type: [Number],
default: [],
},
active: {
type: Boolean,
required: true,
default: false,
},
img: String,
meetLink: String,
table: {
type: Number,
default: 0,
},
group: Number,
},
routes/index.js
router.put('/asignTable', async (req, res) => {
let users = await Profile.find()
shuffle(users)
asignTable(users)
res.send(users)
routes/utils.js
async function asignTable(users) {
var contador = 0
let numTable = 1
filter = {}
for (const user of users) {
const filter = { name: user.name }
const update = { table: numTable }
if (contador == 5) {
contador = 0
numTable++
console.log(`desde el if ${contador}`)
} else if (contador < 5) {
await Profile.findOneAndUpdate(filter, update).then(contador++)
console.log(`desde el elseif contador:${contador} numtable:${numTable}`)
}
}
}
})

Related

Mongoose update a deeply nested sub document only having the sub document _id

I have the following schema for a grid (evaluation grid). Grid => Sections => Criteria => Levels and I want to update a single Level element.
const mongoose = require("mongoose");
const levelSchema = mongoose.Schema(
{
title: {
type: String,
required: true,
},
value: {
type: Number,
required: true,
},
},
{ timestamps: true }
);
exports.Level = mongoose.model("Level", levelSchema);
const criterionSchema = mongoose.Schema(
{
title: {
type: String,
required: true,
},
levels: [levelSchema],
},
{ timestamps: true }
);
criterionSchema.virtual("weight").get(function () {
return Math.max(this.levels.map((level) => level.weigth));
});
exports.Criterion = mongoose.model("Criterion", criterionSchema);
const sectionSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
criteria: [criterionSchema],
},
{ timestamps: true }
);
sectionSchema.virtual("weight").get(function () {
return this.criteria.reduce((acc, criteria) => acc + criteria.weight, 0);
});
exports.Section = mongoose.model("Section", sectionSchema);
const schema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
sections: [sectionSchema],
code: { type: Number, required: true },
course: {
type: mongoose.Schema.Types.ObjectId,
ref: "Course",
required: true,
},
},
{ timestamps: true }
);
schema.virtual("weight").get(function () {
return this.sections.reduce((acc, section) => acc + section.weight, 0);
});
exports.Grid = mongoose.model("Grid", schema);
I was able to retrieve a specific Level's Grid with this code :
Grid.findOne({"sections.criteria.levels._id": levelId})
So I tried FindOneAndUpdate with this code :
const grid = await Grid.findOneAndUpdate(
{ "sections.criteria.levels._id": req.params.levelId },
{
$set: {
"sections.$[].criteria.$[].levels.$[].title": req.body.title,
},
},
{ new: true });
But, it changed ALL the Levels of the grid.
How can we update a single Level sub, sub, sub document and returns it ?

.findOne / .findById keep running if no result is found

I face an issue that i can't resolve alone.
I have a MongoDB collection, in this collection i have 1 document atm.
When i use .findById(_id) or .findOne({_id : id}) with the right _id, everything works.
When i use .findById(_id) or .findOne({_id : id}) with the wrong _id (for test purposes), i have no response (no undefined, no null, nothing) from the DB and my request keep running.
Ty for your time, take care !
EDIT :
Document :
export interface OrderDocument extends mongoose.Document {
user: UserDocument['_id'];
asset_bought: AssetDocument['_id'];
asset_b_symbol: string;
asset_sold: AssetDocument['_id'];
asset_s_symbol: string;
exchange: ExchangeDocument['_id'];
exchange_name: string;
is_draft: Boolean;
amount: number;
atm_price: number;
date: Date;
}
Collection's schema :
const orderSchema = new mongoose.Schema(
{
_id: { type: mongoose.Schema.Types.ObjectId },
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true,
},
asset_bought: {
type: mongoose.Schema.Types.ObjectId,
ref: 'assets',
required: true,
},
asset_b_symbol: {
type: String,
required: true,
},
asset_sold: {
type: mongoose.Schema.Types.ObjectId,
ref: 'assets',
required: true,
},
asset_s_symbol: {
type: String,
required: true,
},
exchange: {
type: mongoose.Schema.Types.ObjectId,
ref: 'exchanges',
required: true,
},
exchange_name: {
type: String,
required: true,
},
is_draft: { type: Boolean, default: false },
amount: { type: Number, required: true },
atm_price: { type: Number, required: true },
date: { type: Date, required: true },
},
{ timestamps: true }
);
Service
export async function findAndPopulateOrders(
searchType: 'id' | 'one' | 'many',
query: FilterQuery<OrderDocument>,
_collections: Array<string>
) {
const collections = _collections.join(' ');
if (searchType === 'id') {
return await OrderModel.findById(query).populate(collections);
} else if (searchType === 'one') {
return await OrderModel.findOne(query).populate(collections);
} else if (searchType === 'many') {
return await OrderModel.find(query).populate(collections);
} else {
return {};
}
}

Need help regarding POST API

I have a mongoose schema as below, I am trying to create a new club buts always getting undefined error. I checked many times but not success. Can someone please help me to CRUD operation. I am new to programming, trying my best.
const schoolSchema = new mongoose.Schema
({
schoolName: { type: String, unique: true, required: true },
feePlan: {
primary: { type: String, enum: ['Plan-A', 'Plan-B'], default: 'Plan-A', required: true },
secondary: { type: String, enum: ['Plan-A', 'Plan-B'], default: '', },
},
schoolContact: {
email:
{ type: String, lowercase: true, trim: true, index: true, unique: true, required: true },
phonePrimary:
{ type: String, trim: true, unique: true, required: true },
phoneSecondary:
{ type: String, trim: true },
headInstructor:
{ type: String, required: true },
websiteUrl: { type: String, trim: true, default: '' },
businessAddress:
{
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
zip: { type: String, required: true },
country: { type: String, required: true },
},
otherAddress: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
zip: { type: String, required: true },
country: { type: String, required: true },
},
},
active: { type: Boolean, default: true },
timestamps: { type: Date, deafault: true },
});
const schoolModel = mongoose.model('School', schoolSchema);
module.exports = schoolModel;
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
router.post('/add', function (req, res, next) {
let schoolName = req.body.schoolName;
let feePlan = req.body.feePlan;
let primary = req.body.primary;
let secondary = req.body.secondary;
let schoolContact = req.body.schoolContact;
let email = req.body.email;
let phonePrimary = req.body.phonePrimary;
let phoneSecondary = req.body.phoneSecondary;
let headInstructor = req.body.headInstructor;
let websiteUrl = req.body.websiteUrl;
let businessAddress = req.body.businessAddress;
let street = req.body.street;
let city = req.body.city;
let state = req.body.state;
let zip = req.body.zip;
let country = req.body.country;
let otherAddress = req.body.otherAddress;
let streetOth = req.body.streetOth;
let cityOth = req.body.cityOth;
let stateOth = req.body.stateOth;
let zipOth = req.body.zipOth;
let countryOth = req.body.countryOth;
let schoolObj = new schoolModel({
schoolName: schoolName,
feePlan: {
primary: primary,
secondary: secondary,
},
schoolContact: {
email: email,
phonePrimary: phonePrimary,
phoneSecondary: phoneSecondary,
headInstructor: headInstructor,
websiteUrl: websiteUrl,
businessAddress:
{
street: street,
city: city,
state: state,
zip: zip,
country: country,
},
otherAddress: {
streetOth: streetOth,
cityOth: cityOth,
stateOth: stateOth,
zipOth: zipOth,
countryOth: countryOth,
},
},
active: { type: Boolean, default: true },
timestamps: { type: Date, deafault: true },
});
schoolObj.save(function (err, schoolObj) {
if (err) {
res.send({ status: 500, message: 'Unable to Add school' });
console.log(schoolObj);
}
else {
res.send({ status: 200, message: 'school Added Successfully', schoolDetails: schoolObj });
}
});
});
Thanks Justinas, and everyone who took a look. I was able to solve the issue. I don't need to declare all the fields. Just below solution worked.
let schoolName = req.body.schoolName;
let feePlan = req.body.feePlan;
let schoolContact = req.body.schoolContact;
let schoolObj = new schoolModel({
schoolName: schoolName,
feePlan: feePlan,
schoolContact: schoolContact,

Mongoose data flow

I have built a simple MERN app where users can rate phone numbers. Users just fill in the phone number, choose rating (1 - 5 star rating), their city & short text. The app has search function with filter & sorting options. It all works good enough ATM but I think it might break when multiple concurrent users use the website because I update the phone number model (mobileSchema) after a rating (messageSchema) has been submitted - using Mongoose middlewares (post hooks).
For example, I need to calculate number of ratings (messagesCount) for phone number. I use Message.countDocuments({ mobile: mobile._id }) for that. However, I also need to update other properties of phone number (mobileSchema - lastMessageDate, globalRating, averageRating) so that operation takes some time. I believe the number of ratings might not be right when 2 users submit rating at the same time - it will increment the number of ratings (messagesCount) by 1 instead of 2.
Is there a better approach? Can a post hook be fired after the previous post hook already finished?
Sample code:
const mobileSchema = new Schema({
number: { type: String, required: true },
plan: { type: String, required: true },
date: { type: Date, default: Date.now, required: true, index: 1 },
messagesCount: { type: Number, default: 0, index: 1 },
lastMessageDate: { type: Date, index: 1 },
// normal mean
globalRating: { type: Number, default: 0, index: 1 },
// weighted mean
averageRating: { type: Number, default: 0, index: 1 }
});
const messageSchema = new Schema({
comment: { type: String, required: true },
city: { type: Schema.Types.ObjectId, ref: 'City', required: true, index: 1 },
rating: { type: Number, required: true, index: 1 },
date: { type: Date, default: Date.now, required: true, index: 1 },
mobile: { type: Schema.Types.ObjectId, ref: 'Mobile', required: true },
user: { type: Schema.Types.ObjectId, ref: 'User', required: true }
});
messageSchema.post('save', function (message, next) {
const messageModel = this.constructor;
return updateMobile(messageModel, message, next, 1);
});
const updateMobile = (messageModel, message, next, addMessage) => {
const { _id } = message.mobile;
const cityId = message.city._id;
const lastMessageDate = message.date;
let mobile;
hooks.get(Mobile, { _id })
.then(mobileRes => {
mobile = mobileRes;
return Message.countDocuments({ mobile: mobile._id })
})
.then(messagesCount => {
if (messagesCount <= 0) {
const deleteMobile = Mobile.findOneAndDelete({ _id: mobile._id })
const deleteSeen = SeenMobile.findOneAndDelete({ mobile: mobile._id, user: message.user._id })
const cityMobile = updateCityMobile(messageModel, mobile, cityId)
Promise.all([deleteMobile, deleteSeen, cityMobile])
.then(() => {
return next();
})
.catch((err) => {
console.log(err);
return next();
})
}
else {
if (addMessage === -1) lastMessageDate = mobile.lastMessageDate;
const ratings = hooks.updateGlobalRating(mobile, messageModel)
.then(() => hooks.updateAverageRating(mobile, messageModel))
.then(() => {
return new Promise((resolve, reject) => {
mobile.set({
messagesCount,
lastMessageDate
});
mobile.save((err, mobile) => {
if (err) return reject(err);
resolve();
});
})
})
const cityMobile = updateCityMobile(messageModel, mobile, cityId)
Promise.all([ratings, cityMobile])
.then(([ratings, cityMobile]) => {
return next();
})
.catch(err => console.log(err))
}
})
.catch(err => {
console.log(err);
})
}
I think you are always going to run into async issues with your approach. I don't believe you can "synchronize" the hooks; seems to go against everything that is true about MongoDB. However, at a high level, you might have more success grabbing the totals/summaries at run-time, rather than trying to keep them always in sync. For instance, if you need the total number of messages for a given mobile device, why not:
Messages.find({mobile: mobile._id})
and then count the results? That will save you storing the summaries and keeping them updated. However, I also think your current approach could work, but you probably need to scrap the "countDocuments". Something a bit more async friendly, like:
Mobile.aggregation([
{ $match: { _id: mobile._id } },
{ $add: [ "$mobile.messagesCount", 1 ] }
]);
Ultimately I think your design would be strengthened if you stored Messages as an array inside of Mobile, so you can just push the message on it. But to answer the question directly, the aggregation should keep everything tidy.
I found this answer: Locking a document in MongoDB
I will calculate all the values I need (messagesCount, globalRating etc.) in post hook and then I will check if the mobile document has the same __v (version) value during final findOneAndUpdate operation (because this operation locks the document and can increment __v). If it has different __v then I will call the post hook again to ensure it will calculate the right values.
First we need to fix some database structure here
Mobile schema
const mobileSchema = new Schema({
number: { type: String, required: true },
plan: { type: String, required: true },
date: { type: Date, default: Date.now, required: true, index: 1 },
//messagesCount: { type: Number, default: 0, index: 1 },
//lastMessageDate: { type: Date, index: 1 },
// normal mean
//globalRating: { type: Number, default: 0, index: 1 },
// weighted mean
//averageRating: { type: Number, default: 0, index: 1 }
});
Message schema
const messageSchema = new Schema({
comment: { type: String, required: true },
city: { type: Schema.Types.ObjectId, ref: 'City', required: true, index: 1 },
//rating: { type: Number, required: true, index: 1 },
date: { type: Date, default: Date.now, required: true, index: 1 },
mobile: { type: Schema.Types.ObjectId, ref: 'Mobile', required: true },
user: { type: Schema.Types.ObjectId, ref: 'User', required: true }
});
Rating system (take all rating or make them a set)
(numerator & denominator after 100 ratings it is difficult to read every single one) can also check for the mobile
const ratingSchema = new Schema({
mobile: { type: String, required: true },
commmentId:{type:String, required: true, index: 1}
rate: { type: Number required: true, },
//rating: { type: Number, required: true, index: 1 },
timestamp: { type: Date, default: Date.now, required: true, index: 1 }
denominator:{ type: Number},
numerator:{type:Number}
});
Thanks

Manipulating Mongoose/MongoDB Array using Node.js

I've noticed there's little documentation and info about how I should manipulate an array of objects using Mongoosejs.
I have the following model/Schema for an User:
'use strict';
/**
* Module Dependencies
*/
var bcrypt = require('bcrypt-nodejs');
var crypto = require('crypto');
var mongoose = require('mongoose');
/**
* Custom types
*/
var ObjectId = mongoose.Schema.Types.ObjectId;
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, index: true },
password: { type: String },
type: { type: String, default: 'user' },
facebook: { type: String, unique: true, sparse: true },
twitter: { type: String, unique: true, sparse: true },
google: { type: String, unique: true, sparse: true },
github: { type: String, unique: true, sparse: true },
tokens: Array,
profile: {
name: { type: String, default: '' },
gender: { type: String, default: '' },
location: { type: String, default: '' },
website: { type: String, default: '' },
picture: { type: String, default: '' },
phone: {
work: { type: String, default: '' },
home: { type: String, default: '' },
mobile: { type: String, default: '' }
}
},
activity: {
date_established: { type: Date, default: Date.now },
last_logon: { type: Date, default: Date.now },
last_updated: { type: Date }
},
resetPasswordToken: { type: String },
resetPasswordExpires: { type: Date },
verified: { type: Boolean, default: true },
verifyToken: { type: String },
enhancedSecurity: {
enabled: { type: Boolean, default: false },
type: { type: String }, // sms or totp
token: { type: String },
period: { type: Number },
sms: { type: String },
smsExpires: { type: Date }
},
friends: [{
friend: { type: ObjectId, ref: 'User' },
verified: { type: Boolean, default: false }
}]
});
/* (...) some functions that aren't necessary to be shown here */
module.exports = mongoose.model('User', userSchema);
So as you can check I defined Friends inside User like this:
friends: [{
friend: { type: ObjectId, ref: 'User' },
verified: { type: Boolean, default: false }
}]
Now the question is how can I add, edit and delete this array in a Node.js script?
BOTTOMLINE: How can I manipulate arrays that are inside MongoDB Schemas, using Node.js and Mongoose.js? Do I always have to create a Schema function or can I access it directly?
EDIT (13/07/2014): So far I've created a HTTP GET that gives me the array like this:
app.get('/workspace/friends/:userid', passportConf.isAuthenticated, function (req, res) {
User.find({_id: req.params.userid}, function (err, items) {
if (err) {
return (err, null);
}
console.log(items[0].friends);
res.json(items[0].friends);
});
});
But this only returns an array of friendIds, but what if I want to create some sort of '/workspace/friends/:userid/del/:friendid' POST, or add POST. I can't seem to figure out how I can get this done.
You can do something like following
app.get('/workspace/friends/:userid/delete/:friendId', passportConf.isAuthenticated, function (req, res) {
User.findOne({_id: req.params.userid}, function (err, user) {
if (err) {
return (err, null);
}
for (var i = 0; i < user.friends.length; i++) {
if (user.friends[i]._id === req.params.friendId) {
user.friends = user.friends.splice(i,1)
}
}
user.save(function(err, user, numAffected){
if (!err )res.json(user)
res.send('error, couldn\'t save: %s', err)
})
});
});
What it says in mongoose docs is that
"The callback will receive three parameters, err if an error occurred, [model] which is the saved [model], and numberAffected which will be 1 when the document was found and updated in the database, otherwise 0.
The fn callback is optional. If no fn is passed and validation fails, the validation error will be emitted on the connection used to create this model."
If you need to manipulate arrays, you should convert these in objects before.
User.findOne({_id: req.params.userid}, function (err, user) {
if (err) {
return (err, null);
}
var user = user.toObject();
//... your code, an example =>
delete user.friends;
res.json(user);
});
Regards, Nicholls

Categories

Resources