Express & MongoDB: Duplicate documents created at the timestamp - node.js

I am using express with MongoDB as a database and while I am sending requests using Axios library to my backend, I have found that two documents were created at the time. I figured that out by checking the createdAt field and found the two documents have the exact timestamp. I am creating createdAt & updatedAt fields using mongoose-timestamp2
Here is the code responsible for sending the documents which have isSynchronized flag set to false
const syncExams = async (token) => {
let examSyncError;
await Exam.find({ isSynchronized: false })
.cursor()
.eachAsync(async (exam) => {
// use try/catch inside the loop
// so if problem occured in one document, others will be posted normally
try {
const formData = new FormData();
formData.append("exam", JSON.stringify(exam));
Object.keys(exam.images.toJSON()).forEach((key) => {
formData.append("images", fs.createReadStream(exam.images[key]));
});
const res = await axios.post(`${BACKEND_URL}/exams/`, formData, {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...formData.getHeaders(),
Authorization: `Bearer ${token}`,
},
});
console.log(res);
exam.isSynchronized = true;
await exam.save();
} catch (err) {
examSyncError = err;
}
});
if (examSyncError) {
throw examSyncError;
}
};
And here is the controller responsible for handling the coming request and creating these documents in the database.
router.post(
"/exams",
fileUpload.array("images"),
trimmer,
syncControllers.postExam
);
const postExam = async (req, res, next) => {
let createdExam;
let exam;
let patient;
try {
const reqBody = JSON.parse(req.body.exam);
reqBody.operator = reqBody.creator;
delete reqBody._id;
delete reqBody.creator;
const { nationalID } = reqBody;
patient = await Patient.findOne({
"personalData.nationalID": nationalID,
}).exec();
if (!patient) {
return next(errorEmitter("exams.get-patient.not-found", 404));
}
createdExam = new Exam(reqBody);
// insert the new paths of the images
Object.keys(reqBody.images).forEach((key, index) => {
createdExam.images[key.replace("Path", "")].Path = req.files[index].path
.split(path.sep)
.slice(-3)
.join(path.sep);
});
createdExam.patient = patient._id;
const checkDate = new Date(createdExam.examDate);
checkDate.setMonth(checkDate.getMonth() - 3);
const nearExam = await Exam.findOne({
patient: createdExam.patient,
examDate: { $gte: checkDate, $lt: createdExam.examDate },
isGradable: true,
});
if (nearExam) {
createdExam.isGradable = false;
}
exam = await createdExam.save();
patient.exams.push(createdExam);
await patient.save();
} catch (err) {
return next(errorEmitter("exams.create.fail", 500));
}
return res.status(201).json(exam.toObject({ getters: true }));
};
I am using the following package for createdAt & updatedAt records created using this plugin.
const timestamps = require("mongoose-timestamp2");
examSchema.plugin(timestamps);
This problem occurred in two or three cases only and did not happen again until then and I could not replicate the error. I do not what caused this!
What could cause this problem to occur? Is it possible that Axios sent the same request multiple times and mongoose created two documents at the same exact time, or it could be a bug in mongoose-timestamp2 plugin?
Any help would be appreciated, thank you!

Related

Apollo Server + mongoose, passing createdBy, updatedBy to every update/insert/delete operation

I am trying to pass createdBy and updatedBy information to every query with mongoose. Is there any way to do this using middlewares with express and/or Apollo Graphql?
I then will use mongoose.set to send debug information to our logging server.
I managed to solve this using AsyncLocalStorage implementation.
this is my threadContext implementation
const { AsyncLocalStorage } = require('async_hooks');
const localStorage = new AsyncLocalStorage();
const contextInit = {
user: null,
environment: process.env.DEPLOYMENT_ENV || 'local',
sourceCompany: undefined,
};
const getContext = () => localStorage.getStore();
const initializeContext = (additionalContext) => localStorage.enterWith({ ...contextInit, ...additionalContext });
const updateContext = (context) => {
Object.keys(context).forEach((k) => {
localStorage.getStore()[k] = context[k];
});
};
module.exports = {
getContext,
contextInit,
initializeContext,
updateContext,
};
then, I injected middleware to initialize the context to express
const { initializeContext: initializeThreadContext } = require('./services/threadContext');
const RequestId = require('./helpers/expressRequestId');
app.use(RequestId()); <<-- feel free to write your own
app.use((req, res, next) => {
initializeThreadContext({ requestId: req.id });
next();
});
In apollo server context to store user data
// include user in threadContext
threadContext.getContext().user = user ? { _id: user._id, name: user.name } : undefined;
then in mongoose, I am logging our queries with user data
mongoose.set('debug', (collectionName, methodName, query, doc, ...methodArgs) => {
const ignoredMethods = [/createIndex/, /watch/];
if (ignoredMethods.some((m) => new RegExp(m).test(methodName))) return;
logger.debug(`Mongodb operation '${methodName}' on '${collectionName}'`, {
collectionName,
methodName,
query,
...threadContext.getContext(),
});
});
I preferred not to store user info with createdBy instead logged it

Iterate dataArray to create an object is not happening

How can I parse the incoming nomData data array and store those values into an object along with userEmail ? Somehow below code is not working, could someone pleas advise the issue here.
Expected database columns values:
var data = { useremail: userEmail, nomineeemail: email, nomineename: name, nomineeteam: team, reason: reason }
server.js
app.post('/service/nominateperson', async (req, res) => {
try {
const userEmail = req.body.userEmail;
const nomData = req.body.nomRegister;
const formData = {
useremail: userEmail,
nomineeemail: {},
nomineename: {},
nomineeteam: {},
reason: {}
}
const newArray = nomData.map(item => {
formData.nomineeemail = item.email;
formData.nomineename = item.name;
formData.nomineeteam = item.team;
formData.reason = item.reason;
});
var data = { useremail: userEmail, nomineeemail: email, nomineename: name, nomineeteam: team, reason: reason }
// Ideally I should get nomData
//items parsed create an data object and pass that into bulkCreat() method:
const numberOfNominations = await NominationModel.count({where: {useremail: userEmail}});
if (numberOfNominations <= 3) {
const nominationData = await NominationModel.bulkCreate(data);
res.status(200).json({message: "Nomination submitted successfully !"});
} else {
res.status(202).json({message: "Nomination limit exceeded, please try next week !"});
}
} catch (e) {
res.status(500).json({fail: e.message});
}
});
So i assume nomData is an array containing multiple nominations. So if you want to bulkCreate with data you should pass an array instead of an object.
const data = nomData.map(item => ({
useremail: userEmail,
nomineeemail: item.email,
nomineename: item.name,
nomineeteam: item.team,
reason: item.reason
}));
...
await NominationModel.bulkCreate(data)

Best practice running queries in Node.js with MongoDB driver 3.6?

The official documentation of the Node.js Driver version 3.6 contains the following example for the .find() method:
const { MongoClient } = require("mongodb");
// Replace the uri string with your MongoDB deployment's connection string.
const uri = "mongodb+srv://<user>:<password>#<cluster-url>?w=majority";
const client = new MongoClient(uri);
async function run() {
try {
await client.connect();
const database = client.db("sample_mflix");
const collection = database.collection("movies");
// query for movies that have a runtime less than 15 minutes
const query = { runtime: { $lt: 15 } };
const options = {
// sort returned documents in ascending order by title (A->Z)
sort: { title: 1 },
// Include only the `title` and `imdb` fields in each returned document
projection: { _id: 0, title: 1, imdb: 1 },
};
const cursor = collection.find(query, options);
// print a message if no documents were found
if ((await cursor.count()) === 0) {
console.log("No documents found!");
}
await cursor.forEach(console.dir);
} finally {
await client.close();
}
}
To me this somewhat implies that I would have to create a new connection for each DB request I make.
Is this correct? If not, then what is the best practise to keep the connection alive for various routes?
You can use mongoose to set a connection with your database.
mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
then you need to define your models which you will use to communicate with your DB in your routes.
const MyModel = mongoose.model('Test', new Schema({ name: String }));
MyModel.findOne(function(error, result) { /* ... */ });
https://mongoosejs.com/docs/connections.html
It's 2022 and I stumbled upon your post because I've been running into the same issue. All the tutorials and guides I've found so far have setups that require reconnecting in order to do anything with the Database.
I found one solution from someone on github, that creates a class to create, save and check if a client connection exist. So, it only recreates a client connection if it doesn't already exist.
const MongoClient = require('mongodb').MongoClient
class MDB {
static async getClient() {
if (this.client) {
return this.client
}
this.client = await MongoClient.connect(this.url);
return this.client
}
}
MDB.url='<your_connection_url>'
app.get('/yourroute', async (req, res) => {
try {
const client = await MDB.getClient()
const db = client.db('your_db')
const collection = db.collection('your_collection');
const results = await collection.find({}).toArray();
res.json(results)
} catch (error) {
console.log('error:', error);
}
})

How to use MongoDB locally and directline-js for state management in Bot Framework using NodeJs and Mongoose?

I am maintaining the bot state in a local MongoDB storage. When I am trying to hand-off the conversation to an agent using directline-js, it shows an error of BotFrameworkAdapter.sendActivity(): Missing Conversation ID. The conversation ID is being saved in MongoDB
The issue is arising when I change the middle layer from Array to MongoDB. I have already successfully implemented the same bot-human hand-off using directline-js with an Array and the default Memory Storage.
MemoryStorage in BotFramework
const { BotFrameworkAdapter, MemoryStorage, ConversationState, UserState } = require('botbuilder')
const memoryStorage = new MemoryStorage();
conversationState = new ConversationState(memoryStorage);
userState = new UserState(memoryStorage);
Middle Layer for Hand-Off to Agent
case '#connect':
const user = await this.provider.connectToAgent(conversationReference);
if (user) {
await turnContext.sendActivity(`You are connected to
${ user.userReference.user.name }\n ${ JSON.stringify(user.messages) }`);
await this.adapter.continueConversation(user.userReference, async
(userContext) => {
await userContext.sendActivity('You are now connected to an agent!');
});
}
else {
await turnContext.sendActivity('There are no users in the Queue right now.');
}
The this.adapter.continueConversation throws the error when using MongoDB.
While using Array it works fine. The MongoDB and Array object are both similar in structure.
Since this works with MemoryStorage and not your MongoDB implementation, I'm guessing that there's something wrong with your MongoDB implementation. This answer will focus on that. If this isn't the case, please provide your MongoDb implementation and/or a link to your repo and I can work off that.
Mongoose is only necessary if you want to use custom models/types/interfaces. For storage that implements BotState, you just need to write a custom Storage adapter.
The basics of this are documented here. Although written for C#, you can still apply the concepts to Node.
1. Install mongodb
npm i -S mongodb
2. Create a MongoDbStorage class file
MongoDbStorage.js
var MongoClient = require('mongodb').MongoClient;
module.exports = class MongoDbStorage {
constructor(connectionUrl, db, collection) {
this.url = connectionUrl;
this.db = db;
this.collection = collection;
this.mongoOptions = {
useNewUrlParser: true,
useUnifiedTopology: true
};
}
async read(keys) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
const data = {};
await Promise.all(keys.map(async (key) => {
const doc = await col.findOne({ _id: key });
data[key] = doc ? doc.document : null;
}));
return data;
} finally {
client.close();
}
}
async write(changes) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
await Promise.all(Object.keys(changes).map((key) => {
const changesCopy = { ...changes[key] };
const documentChange = {
_id: key,
document: changesCopy
};
const eTag = changes[key].eTag;
if (!eTag || eTag === '*') {
col.updateOne({ _id: key }, { $set: { ...documentChange } }, { upsert: true });
} else if (eTag.length > 0) {
col.replaceOne({ _id: eTag }, documentChange);
} else {
throw new Error('eTag empty');
}
}));
} finally {
client.close();
}
}
async delete(keys) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
await Promise.all(Object.keys(keys).map((key) => {
col.deleteOne({ _id: key });
}));
} finally {
client.close();
}
}
async getClient() {
const client = await MongoClient.connect(this.url, this.mongoOptions)
.catch(err => { throw err; });
if (!client) throw new Error('Unable to create MongoDB client');
return client;
}
async getCollection(client) {
return client.db(this.db).collection(this.collection);
}
};
Note: I've only done a little testing on this--enough to get it to work great with the Multi-Turn-Prompt Sample. Use at your own risk and modify as necessary.
I based this off of a combination of these three storage implementations:
memoryStorage
blobStorage
cosmosDbStorage
3. Use it in your bot
index.js
const MongoDbStorage = require('./MongoDbStorage');
const mongoDbStorage = new MongoDbStorage('mongodb://localhost:27017/', 'testDatabase', 'testCollection');
const conversationState = new ConversationState(mongoDbStorage);
const userState = new UserState(mongoDbStorage);

Sequelize Transaction inside forEach loop

I'm trying to use transaction inside forEach loop using async/await syntax of Node 7.0+
When I try to print committed transaction response in console, I'm able to see the values but those same values are not committed in to DB.
Below is the code :
documentInfo.forEach(async (doc) => { // array of documentInfo
var frontImgName = await module.exports.uploadImage(docFiles, doc.front, req, res )
var backImgName = await module.exports.uploadImage(docFiles, doc.back, req, res )
var checkKycDoc = await KYCDocument.findOne({
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
}
})
if (checkKycDoc) { //update
var updateDocument = await KYCDocument.update({
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
},
}, {transaction})
log('updateDocument', updateDocument.dataValues)
} else { // insert
var newKycDocument = await new KYCDocument({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName,
status: true
}, {transaction})
log('newKycDocument', newKycDocument.dataValues)
}
if (rowCount === documentInfo.length) {
await transaction.commit() // transaction is printed on this line
log('KYC has been uploaded successfully')
helpers.createResponse(res, constants.SUCCESS,
messages.KYC_UPLOAD_SUCCESS,
{'error': messages.KYC_UPLOAD_SUCCESS}
)
} else {
rowCount++
}
})
The issue was in the create method.
To resolve the issue I had to create a new row using:
var newKycDocument = await KYCDocument.create({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {transaction})
I was missing the .create method.

Resources