mongodb: only update document if is not updated - mongodb

I have this function. Allow take a service only is not taken:
is taken only if the available param is true.
function takeService(req, res) {
var serviceId = req.params.id;
var driverId = req.body.driverId;
Service.findById(serviceId, (err, service) =>{
if (!err) {
if (!service) {
res.status(404).send({message: 'Not found'});
} else {
if (service.available === false ) {
res.status(409).send({message: 'The service is taken'});
} else {
Service.findByIdAndUpdate(serviceId, {
driverId,
status: 1,
available: false
}, (err, serviceUpdated) =>{
if (!err && serviceUpdated) {
res.status(200).send({message: "tomado"});
}
});
}
}
}
});
}
Schemas:
var ServiceSchema = Schema({
clientId: {
type: String,
ref: 'Client'
},
available: Boolean,
routeId: {
type: String,
ref: 'Route'
},
date: Date,
radius: Number,
driverId: {
type: String,
ref: 'Driver'
},
status: Number,
time: String,
createdTime: Number,
rateId: {
type: String,
ref: 'Rate'
}
});
var DriverSchema = Schema({
name: String,
surname: String,
username: String,
password: String,
status: { type: Number, default: 0 },
oneSignalId: String,
plate: String,
make: String,
year: String,
model: String,
groupId: [{
type: String,
ref: 'DriverGroup'
}],
unit: String,
telephone: String
});
The problem is when two devices call to this function, in some cases both find the document and check if is available and then both update the same document. I am looking a some validation in the schema for autocheck this property.

If I understand the problem correctly, the main issue is that two devices may think that a service is still available.
The ultimate cause of this is that there's a race condition between findById and findByIdAndUpdate: between those two calls, there's a window of time in which another request can change the document in the database.
To fix this, you can use the atomic findAndModify command, which Mongoose exposes as (amongst others) Model#findOneAndUpdate.
Your code would become something like this:
function takeService(req, res) {
var serviceId = req.params.id;
var driverId = req.body.driverId;
Service.findOneAndUpdate({
_id : serviceId,
available : true
}, {
driverId : driverId,
status : 1,
available : false,
}, (err, service) => {
if (err) {
return res.status(500);
} else if (! service) {
return res.status(409).send({message: 'The service is taken'});
} else {
return res.status(200).send({message: "tomado"});
}
});
}
There are a few differences with your original code that you should be aware of:
you can't distinguish between a service not existing (invalid/unknown serviceId) and a service that is not available anymore; in both cases, the update will yield no result and a 409 response is sent back;
findOneAndUpdate will return the old document, before it was updated. If you want to receive the updated document, pass the new option in the query:
Service.findOneAndUpdate({ ... }, { ... }, { new : true }, (err, service) => { ... })
I added an error handler in there, that sends back a 500 ("Internal Server Error") response.

Related

MongoDB won't save url string

I am trying to save an object in my Mongo database. The issue I face is that when I create the schema, it saves every single entry except the img url. I logged the url before creating the schema and it prints it successfully but when I create the schema object it doesn't get the value from the body.
router.post('/', async (req, res) => {
console.log("URL:", req.body.img) //Logs the url successfully
const pet = new Pet({
name: req.body.name,
petType: req.body.petType,
breed: req.body.breed,
age: req.body.age,
img: req.body.img, //i can't get it here
contact: req.body.contact,
location: req.body.location,
userp: req.body.contact,
})
console.log("This is a Pet");
console.log(pet); //logs everything except the "img" field.
try {
const savedPet = await pet.save();
console.log("This pet was saved", savedPet);
res.json(savedPet); //returns an object without the "img" field
} catch (err) {
res.json({ message: err });
}
});
Edit:
Here is my schema file as well:
const mongoose = require('mongoose');
const petSchema = mongoose.Schema({
name: {
type: String,
required: false
},
petType: {
type: String,
require: true
},
breed: {
type: String,
require: false
},
age: {
type: Number,
require: false
},
img: {
data: String,
require: false
},
contact: {
type: String,
require: true
},
location: {
type: String,
require: true
},
userp: {
type: String,
require: true
}
});
module.exports = mongoose.model('Pet', petSchema);```
I found the error. You use data instead type param specifically in this property.
img: {
type: String,
require: false
},

Get results of aggregation query in mongoose using objectId, virtual types (it works in mongo shell)

My code on the backend, in case it matters (NodeJS and MogoDB):
//my includes at the top of the file
const mongoose = require('mongoose');
const Appt = mongoose.model('Appt');
const ApptType = mongoose.model('ApptType');
const ApptStatus = mongoose.model('ApptStatus');
var moment = require('moment-timezone');
moment().tz('America/New_York');
now = moment(); // add this 2 of 4
dayStart = now.startOf('day');
dayEnd = now.endOf('day');
// the aggregation query that's not returning correctly
Appt.aggregate([
{
$match: {
patientID: appt.patientID._id,
scheduled: {
$gte: new Date(start),
$lt: new Date(appt.pmtduedate)
}
}
},
{
$group: {
_id: 'id',
payment: { $sum: '$payment' },
pmtdue: { $sum: '$pmtdue' },
visits: { $sum: 1 }
}
}
]).exec(
err => {
console.log(`Error finding past payments`, err);
callback(err);
},
result => {
console.log(`RESULT: ${result}`);
pastPayments = result;
if (!pastPayments || pastdueamt === 0) {
pastdueamt = 0;
console.log(`2. getCurrentDue ${pastdueamt}`);
this.getCurrentDue(appt, pastdueamt, today, callback);
} else {
console.log(`pastPayments ${pastPayments}`);
console.log(
`planamt ${planamt} pmtdue ${pastPayments.pmtdue} payments: ${pastPayments.payment}`
);
pastdueamt =
pastPayments.pmtdue === 0
? planamt - pastPayments.payment
: pastPayments.pmtdue - pastPayments.payment;
console.log(`pastdueamt calculated: ${pastdueamt}`);
console.log(`2. getCurrentDue`);
this.getCurrentDue(appt, pastdueamt, today, callback);
}
}
);
When I run my query in mongo, the expected results return. In my app, the results of this query above return nothing (no error, either). I've tried doing the following:
$match: {
patientID: new mongoose.types.ObjectId(appt.patientID._id),
I've also tried:
$match: {
patientID: { $toObjectId: appt.patientID._id },
but I get errors on both of these options. The first returns an error of
TypeError: Cannot read property 'ObjectId' of undefined.
The second returns some sort of mongo error
errmsg: 'unknown operator: $toObjectId',
code: 2,
codeName: 'BadValue',
name: 'MongoError',
[Symbol(mongoErrorContextSymbol)]: {} }
How do I do mongoose aggregation successfully using objectIds, virtual types, etc.?
EDITED TO ADD MY SCHEMAS:
const apptSchema = new mongoose.Schema(
{
ID: Number,
patientID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Patient'
},
oldPatientID: Number,
status: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptStatus'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptType'
},
scheduled: Date,
note: String,
reminder: Boolean,
cell: Boolean,
email: Boolean,
subjective: String,
assessment: String,
plan: String,
planamt: Number,
objective: {
clearUC: Boolean,
UCcheck: String,
thompson: String,
activator: String,
other: String
},
updated: {
type: Date,
default: new Date()
},
pmtdue: Number,
pmtduedate: Date,
payment: Number,
pmttype: String,
paid: Boolean,
pmtnote: String
},
{ toJSON: { virtuals: true } }
);

hapi js api design joi validate error

While designing hapi js API with mongoose, facing issue with designing the joi validation for nested schemas. I am a newbie please help with the error
models/ vahana.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var vahSchema = new Schema({
vehRegNumber: { type: String, required: false, unique: true},
techSpecMeta: {
make: String,
model: String,
height: Number,
vehBodyType: String,
seatingCapacity: Number,
doorCountr: Number,
stdCityMilege: Number,
stdHighwayMilege: Number,
},
servSpecMeta: {
assignedDriver: {
name: String,
contact: Number,
license: {
regNo: { type: String, required: false, unique: true},
dateOfIssue: Date,
validDate: Date,
}
},
buy: {
purchaseDate: Date,
purchaseDealer: String,
purchasePrice: Number,
},
servicing: {
servicingDate: Date,
serviceStation: String,
serviceCost: Number,
}
},
dynmSpecMeta: {
date: Date,
busRoute: {
path: String,
distance: Number,
avgTime: Number,
},
distCovered: Number,
fuelConsumption: Number,
}
})
var Vahana = mongoose.model('Vahana', vahSchema);
module.exports = Vahana;
server.js
// ================ Base Setup ========================
var Hapi = require('hapi');
var server = new Hapi.Server();
var Joi = require('joi');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/vahanaDB');
var vahModel = require('./models/vahana');
server.connection({port: 7002});
server.register({
register: require('hapi-swagger'),
options: {
apiVersion: "0.0.1"
}
}, function (err) {
if (err) {
server.log(['error'], 'hapi-swagger load error: ' + err)
} else {
server.log(['start'], 'hapi-swagger interface loaded')
}
});
// =============== Routes for our API =======================
// POST route
server.route({
method: 'POST',
path: '/api/vehicle',
config: {
tags: ['api'],
description: 'Save vehicle data to mongodb',
notes: 'Save user vehicle to mongodb',
validate: {
payload: {
vehRegNumber: Joi.string().required(),
techSpecMeta.make: Joi.string().required()
}
}
},
handler: function (request, reply) {
var vehicle = new vahModel(request.payload);
vehicle.save(function (error) {
if (error) {
reply({
statusCode: 503,
message: error
});
} else {
reply({
statusCode: 201,
message: 'User Saved Successfully'
});
}
});
}
});
// =============== Start our Server =======================
server.start(function () {
console.log('Server running at:', server.info.uri);
console.log('I am running on localhost:7002');
});
I am getting following error after running
nodemon server.js
techSpecMeta.make: Joi.string().required()
^
SyntaxError: Unexpected token
If you want to have a key like "techSpecMeta.make" it needs to be in quotes.
payload: {
vehRegNumber: Joi.string().required(),
'techSpecMeta.make': Joi.string().required()
}

Push ObjectId to nested array in Mongoose

(Basic library CRUD application)
I am trying to create a document containing some global data about a given book, and then within a User document, add the ObjectId of the newly-created book to an array containing all books belonging to that user.
I have three data models in my application:
var userSchema = new mongoose.Schema({
name: String,
password: String,
email: String,
books: [BookInstanceSchema],
shelves: [String]
});
var bookSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
author: {
type: String,
required: true
},
description: String,
pageCount: Number,
ISBN: String,
googleID: String,
thumbnail: String,
publisher: String,
published: String,
});
var BookInstanceSchema = new mongoose.Schema({
bookId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
},
userReview: String,
userRating: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 4,
default: 0
},
shelf: String
});
The User model contains a nested array of BookInstances, which contain user-specific data such as ratings or reviews for a given book. A bookInstance in turn contains a reference to the global data for a book, to avoid duplicating data that isn't specific to any user.
What I'm trying to do is first save the global data for a book (thus generating an _id), and when done, save a bookInstance containing that _id in a given user's array of books:
router.post('/save/:id', function(req, res) {
var url = encodeurl('https://www.googleapis.com/books/v1/volumes/' + req.params.id);
request(url, function(err, response, data) {
parsedData = JSON.parse(data);
var newBook = {
title: parsedData.volumeInfo.title,
author: parsedData.volumeInfo.authors[0],
description: parsedData.volumeInfo.description,
pageCount: parsedData.volumeInfo.pageCount,
ISBN: parsedData.volumeInfo.description,
googleID: parsedData.id,
publisher: parsedData.volumeInfo.publisher,
published: parsedData.volumeInfo.publishedDate,
thumbnail: parsedData.volumeInfo.imageLinks.thumbnail
};
Book.create(newBook, function(err, newBook) {
if (err) {
console.log(err);
}
else {
console.log(newBook._id);
console.log(mongoose.Types.ObjectId.isValid(newbook._id));
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}
},
{
upsert: true
},
function(err, data){
if(err) {
console.log(err);
}
else {
res.redirect('/');
}
});
}
});
});
});
I'm getting the error:
message: 'Cast to ObjectId failed for value "hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0" at path "_id" for model "User"',
name: 'CastError',
stringValue: '"hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0"',
kind: 'ObjectId',
value: 'hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0',
path: '_id',
reason: undefined,
Every time, the value in the error (in this case, jhHy8T...) is different than the newBook._id I'm attempting to push into the array:
console.log(newBook._id); // 5a120272d4201d4399e465f5
console.log(mongoose.Types.ObjectId.isValid(newBook._id)); // true
It seems to me something is wrong with my User update statement:
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}...
Any help or suggestions on how to better organize my data are appreciated. Thanks!

Get single attribute in model using Mongoose

I have 2 Schemas : StepSchema, StepRelationshipSchema
var StepSchema = new Schema(
{
name: { type: String, required: true },
description: { type: String, default: '' },
isVisible: { type: Boolean, default: true }
}, options
);
var StepRelationshipSchema = new Schema(
{
workflowId: { type: String, required: true },
stepId: { type: String, required: true },
prevSteps: [ Schema.Types.Mixed ] ,
nextSteps: [ Schema.Types.Mixed ] ,
gotoStep: { type: String, default: '' }
}, options
);
In StepSchema, I want to create a static method to get nextSteps in StepRelationshipSchema.
Can I use this, thank you so much.
StepSchema.statics.getNextSteps = function(workflowId, currStepId) {
return StepRelationship.findOne({
workflowId: workflowId,
stepId: currStepId
}).nextSteps
};
As #JohnnyHK suggested in his comments, findOne() is async thus you need to use a callback function as follows:
// create a query for next stepswith a blogpost _id matching workflowId and currStepId
schema.statics.getNextSteps = function (workflowId, currStepId, callback) {
return this.model('StepRelationship').findOne({
workflowId: workflowId,
stepId: currStepId
}, callback);
}