Using `mongoose transactions` when saving message in two collections in mongodb - javascript

If the student sends a message to the teacher, the message is saved in the teachers and students collections. The same situation is when a teacher sends a message to the student, they are also save in the two collections teachers and students. How can I apply mongoose transaction in the following code so that the message is saved in two collections.I don' want a situation that the message will be saved in one collection and in the other collection will not be saved due to an error. I am asking for help and clues
const express = require('express');
const mongoose = require('mongoose');
require('dotenv').config();
//const app = express();
// Connect to our Database and handle an bad connections
module.exports.db = mongoose
.createConnection(process.env.DATABASE, {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
useCreateIndex: true
}
);
//controller
const db = require('./connection');
const session = db.startSession();
session.startTransaction();
module.exports.sendMessage = (req, res) => {
let {sender, receiver, msg, role} = req.body;
var hex = /[0-9A-Fa-f]{6}/g;
sender = (hex.test(sender))? mongoose.Types.ObjectId(sender) : sender;
receiver = (hex.test(receiver))? mongoose.Types.ObjectId(receiver) : receiver;
console.log(sender, receiver, msg, 'send');
if(role === 'tutor') {
let teacherMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
sender : sender
})
let studentMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
receiver : receiver
})
db.db.collection('teachers').findOneAndUpdate(
{ _id : receiver },
{ $push: { messages: teacherMessage } },
(err, updated) => {
console.log(updated, 'vvv');
updated.value.hashed_password = undefined;
updated.value.salt = undefined;
if(err) {
res.status(404).json(err);
}
if (updated) {
res.status(200).json(updated.value);
db.db.collection('students').findOneAndUpdate(
{ _id : sender },
{ $push: { messages: studentMessage } },
);
}
}, session(session)
)
}
if(role === 'student') {
let studentMessage = new StudentMessageSchema.StudentMessageSchema({
contentInfo : {
msg : msg
},
receiver : receiver
})
let teacherMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
sender : sender
})
db.db.collection('students').findOneAndUpdate(
{ _id : sender },
{ $push: { messages: studentMessage } },
(err, updated) => {
console.log(updated, 'sss');
updated.value.hashed_password = undefined;
updated.value.salt = undefined;
if(err) {
res.status(404).json(err);
}
if (updated) {
res.json(updated.value);
db.db.collection('teachers').findOneAndUpdate(
{ _id : receiver },
{ $push: { messages: teacherMessage } },
)
}
},
), session(session)
}
}
Logged variable db
{
db: NativeConnection {
base: Mongoose {
connections: [Array],
models: [Object],
modelSchemas: [Object],
options: [Object],
_pluralize: [Function: pluralize],
plugins: [Array]
},
collections: {},
models: {},
config: { autoIndex: true },
replica: false,
options: null,
otherDbs: [],
relatedDbs: {},
states: [Object: null prototype] {
'0': 'disconnected',
'1': 'connected',
'2': 'connecting',
'3': 'disconnecting',
'99': 'uninitialized',
disconnected: 0,
connected: 1,
connecting: 2,
disconnecting: 3,
uninitialized: 99
},
_readyState: 1,
_closeCalled: false,
_hasOpened: true,
_listening: false,
_connectionOptions: {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
useCreateIndex: true,
promiseLibrary: [Function: Promise]
},
client: MongoClient {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
s: [Object],
topology: [ReplSet],
[Symbol(kCapture)]: false
},
name: null,
'$initialConnection': Promise { [Circular] },
db: Db {
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
s: [Object],
serverConfig: [Getter],
bufferMaxEntries: [Getter],
databaseName: [Getter],
[Symbol(kCapture)]: false
}
}
}

I have added in the code Anytime there is an error:
await session.abortTransaction();
Otherwise (happy path) commit the changes.
await session.commitTransaction();
Abort and commit are Promise base functions, so I have changed the functions where they were used as async.
const express = require('express');
const mongoose = require('mongoose');
require('dotenv').config();
//const app = express();
// Connect to our Database and handle an bad connections
module.exports.db = mongoose
.createConnection(process.env.DATABASE, {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
useCreateIndex: true
}
);
//controller
const db = require('./connection');
module.exports.sendMessage = async (req, res) => {
const session = await db.startSession();
session.startTransaction();
let {sender, receiver, msg, role} = req.body;
var hex = /[0-9A-Fa-f]{6}/g;
sender = (hex.test(sender))? mongoose.Types.ObjectId(sender) : sender;
receiver = (hex.test(receiver))? mongoose.Types.ObjectId(receiver) : receiver;
console.log(sender, receiver, msg, 'send');
if(role === 'tutor') {
let teacherMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
sender : sender
})
let studentMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
receiver : receiver
})
db.db.collection('teachers').findOneAndUpdate(
{ _id : receiver },
{ $push: { messages: teacherMessage } },
async (err, updated) => {
console.log(updated, 'vvv');
updated.value.hashed_password = undefined;
updated.value.salt = undefined;
if(err) {
await session.abortTransaction();
res.status(404).json(err);
return
}
if (updated) {
res.status(200).json(updated.value);
db.db.collection('students').findOneAndUpdate(
{ _id : sender },
{ $push: { messages: studentMessage } },
);
}
}, session(session)
)
}
if(role === 'student') {
let studentMessage = new StudentMessageSchema.StudentMessageSchema({
contentInfo : {
msg : msg
},
receiver : receiver
})
let teacherMessage = new TeacherMessageSchema.TeacherMessageSchema({
contentInfo : {
msg : msg
},
sender : sender
})
db.db.collection('students').findOneAndUpdate(
{ _id : sender },
{ $push: { messages: studentMessage } },
async (err, updated) => {
console.log(updated, 'sss');
updated.value.hashed_password = undefined;
updated.value.salt = undefined;
if(err) {
await session.abortTransaction();
res.status(404).json(err);
return;
}
if (updated) {
res.json(updated.value);
db.db.collection('teachers').findOneAndUpdate(
{ _id : receiver },
{ $push: { messages: teacherMessage } },
)
}
},
session(session)
)
}
await session.commitTransaction();
}

Related

Mongoose mixed Type object array, can findOneAndUpdate

I have a controller edit card which updates the fields of cardsArray object.
cardsArray is mixed type object as fileds of each card object is different so i am storing mixed.type
Althought pushing new card using addCard controller works perfectly
But when edit card controller is called, it gives type error
When edit Controlller is callled is gives following error:
TypeError: Cannot read properties of null (reading 'cardsArray')
// Schema
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
cardsArray: [{ type: mongoose.Schema.Types.Mixed }],
}
);
//__Mongodb Data
{
"_id": {
"$oid": "63b43ab32fc8d3c100cafecc"
},
"name": "usern_name",
"email": "pr****#gmail.com",
"password": "$2b$12$3nwifHakrBu94BwLXAC4Nu16Kw0.xyW8vAIPTMSgY7cYttVklDIZq",
"cardsArray": [
{
"title": "some_title",
"category": "Bank",
"cardHolder": "some_name",
"cardNumber": "54545454",
"expiry": "23/01",
"cvv": "***",
"logoIndex": 72,
"isFavourite": false,
"_id": {
"$oid": "63b83cc77288d277ef359533"
}
}
],
"loginIdsArray": [],
"docsArray": [],
"activitiesArray": [],
"__v": 0
}
// Add Card Controller.js___
addCard: async (req, res) => {
console.log(req.body.data, req.body.user_id)
// console.log('obj_id', newObjectId)
req.body.data._id = newObjectId;
try {
const response = await UserDatabase.findOneAndUpdate(
{ _id: req.body.user_id },
{
$push: {
// cardsArray: req.body.data,
cardsArray: { $each: [req.body.data] },
},
},
{ returnOriginal: false }
);
res.status(200).send(response);
} catch (error) {
console.log(error)
res.status(404).json({ message: error.message });
}
},
// edit card controller.js
editCard: async (req, res) => {
const id = req.params.id;
const { category, title, cardHolder, cardNumber, expiry, cvv, logoIndex, isFavourite } = req.body;
console.log(req.params.id)
try {
const response = await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray._id': "63b709fc69a1cfa6fccd645c" },
{
$set: {
"cardsArray.$.title": req.body.title,
"cardsArray.$.category": req.body.category,
"cardsArray.$.cardHolder": req.body.cardHolder,
"cardsArray.$.cardNumber": req.body.cardNumber,
"cardsArray.$.expiry": req.body.expiry,
"cardsArray.$.cvv": req.body.cvv,
"cardsArray.$.logoIndex": req.body.logoIndex,
"cardsArray.$.isFavourite": req.body.isFavourite
}
},
);
console.log(response)
res.status(201).json(response.cardsArray);
} catch (error) {
console.log(error)
res.status(404).json({ message: error.message });
}
}
it means that there is no data matching the following _id and cardsArray._id
i thinks so its fails to find the feild 'cardsArray._id',first try finding the value by just findOne
await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray._id': "63b709fc69a1cfa6fccd645c" })
if your find doesn't work try below methord not sure but it may work you have to either find the id and loop through the cardsArray of use $in or $match
await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray':{$in:[ {_id:"63b709fc69a1cfa6fccd645c" }]})

Promise pending even after its resolved

I want to get the array of rooms and assign it to each property wrt their property_id, but the value returned is a pending promise. Not sure what's wrong. Although when I log the rooms inside the then it does log the value correctly. The result of console.log(property) is given below.
const vendorProfile = catchAsync(async (req, res, next) => {
await passport.authenticate("vendor-jwt", { session: false }, (err, user, info) => {
if (err) {
res.error = err || info.message;
return next(401);
}
if (!user) {
res.error = info.message;
return next(401);
}
return Promise.resolve(
getVendorProfileInfo(user._id)
.then((result) => {
if (result == "error") {
res.error = "Failed to fetch Vendor Profile";
next(500);
}
return getPropertyByVendorId(result._id).then((prop) => {
for (const property of prop) {
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => rooms);
console.log(property);
}
res.message = "Vendor Profile fetched successfully";
res.data = {
vendor_info: result,
vendor_properties: prop,
};
return next(200);
});
})
.catch((err) => {
Logger.error(err);
res.error = "Failed to get vendor profile";
return next(500);
})
).catch((err) => {
Logger.error(err);
res.error = "Failed to get vendor profile";
return next(500);
});
})(req, res, next);
});
This is the function to get all the rooms for that property_id:
const getAllRooms = (propertyId) => {
return Promise.resolve(Room.find({ property_id: propertyId }).then((result) => result)).catch((err) => {
Logger.error(err);
return "error";
});
};
Here is my console.log(property):
{
property_basic_info: {
property_name: 'Welcome',
property_star_rating: 1,
property_booking_since: 2021,
property_channel_manager: ''
},
property_location: {
property_geo_loc: { coordinates: [Array], type: 'Point' },
property_locality: 'bhandup',
property_address: 'MAHAVIR UNIVERSE',
property_country: 'India',
property_state: 'Maharashtra',
property_city: 'Mumbai',
property_zip_code: '400078'
},
property_contact_details: { phone_no: '7059462868', email: 'roy.srijan#outlook.com' },
property_amenities: {
basic_facilities: [ 'Electricity', 'Air Conditioning', 'Elevator/ Lift', 'Bathroom' ],
general_services: [ 'Food', 'Bellboy service' ],
outdoor_activities_sports: [],
common_area: [],
food_drink: [],
health_wellness: [],
business_center_conference: [],
beauty_spa: [],
security: []
},
property_policies: {
checkin_time: '10:06',
checkout_time: '22:06',
cancellation_policy: 'Free cancellation upto 48 hrs'
},
property_rules: {
id_proof: {
acceptable_identity_proofs: 'Adhaar',
unacceptable_identity_proofs: 'Adhaar',
allow_same_id: true
},
guest_profile: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object]
],
general_safety_hygiene_guidelines: [],
room_safety_hygiene: [],
social_distancing: [],
food_drinks_hygiene: [],
property_restrictions: [],
pet_policy: [],
guest_suitabilty: [],
checkin_checkout_policy: [],
extra_bed_policy: [ [Object] ],
custom_policy: []
},
property_finance_legal: { gst_details: '29AAACR4849R2ZG' },
property_status: 1,
property_photo_id: [],
_id: 61607791b1af193c7b8b9f08,
vendor_id: 61607775b1af193c7b8b9f07,
createdAt: 2021-10-08T16:53:37.734Z,
updatedAt: 2021-10-08T16:53:37.734Z,
__v: 0,
property_rooms: Promise { <pending> }
}
Thanks in advance.
That's because you are logging the promise outside the then method.
The promise is resolved async so outside then it is not resolved yet.
you have to change this line:
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => rooms);
console.log(property);
to
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => console.log(rooms));
or use async/await to work with it like sync values

How to reply to someone's tweet using node.js twit?

I'm trying to make a bot that responds to my friend's tweets with an image every time they tweet. Everything works fine, but I can't get it to respond to the original tweet, only replies. Is there any way to do this?
function tweetEvent(tweetMsg) {
var replyto = tweetMsg.in_reply_to_screen_name;
var from = tweetMsg.user.screen_name;
var id = tweetMsg.id_str;
if(from === "---"){
var b64content = fs.readFileSync('media/img.png', {
encoding: 'base64'
})
T.post('media/upload', {
media_data: b64content
}, uploaded);
function uploaded(err, data, response) {
var mediaIdStr = data.media_id_string;
var params = {
status,
in_reply_to_status_id: id,
media_ids: [mediaIdStr]
}
//! Post tweet
T.post('statuses/update', params, tweeted);
};
function tweeted(err, reply) {
if(err) {
console.log(err);
console.log();
console.log("Error.");
} else {
console.log("tweeted");
}
}
}
}
I want it to reply to the original tweet, the only way it's worked is by using:
if(replyto === "---"){
}
But that doesn't do what I want it to do.
I was able to do it with if (tweet.in_reply_to_screen_name === null) tweetEvent(tweet);
const tweets = [
{
created_at: "Wed Feb 10 03:50:17 +0000 2021",
id: 1359348938248183800,
id_str: "1359348938248183809",
text: "reply test 1290hHhbjikl",
truncated: false,
entities: [Object],
metadata: [Object],
source:
'Twitter Web App',
in_reply_to_status_id: 1359339654080790500,
in_reply_to_status_id_str: "1359339654080790530",
in_reply_to_user_id: 1167232572067369000,
in_reply_to_user_id_str: "1167232572067368960",
in_reply_to_screen_name: "OpenDreamPhone",
user: [Object],
geo: null,
coordinates: null,
place: null,
contributors: null,
is_quote_status: false,
retweet_count: 0,
favorite_count: 0,
favorited: false,
retweeted: false,
lang: "in",
},
{
created_at: "Wed Feb 10 03:13:23 +0000 2021",
id: 1359339654080790500,
id_str: "1359339654080790530",
text: "this is a test. 1290hHhbjikl",
truncated: false,
entities: [Object],
metadata: [Object],
source:
'Twitter Web App',
in_reply_to_status_id: null,
in_reply_to_status_id_str: null,
in_reply_to_user_id: null,
in_reply_to_user_id_str: null,
in_reply_to_screen_name: null,
user: [Object],
geo: null,
coordinates: null,
place: null,
contributors: null,
is_quote_status: false,
retweet_count: 0,
favorite_count: 0,
favorited: false,
retweeted: false,
lang: "en",
},
];
const tweetEvent = (tweet) => {
var b64content = fs.readFileSync("twitt/img.png", {
encoding: "base64",
});
T.post(
"media/upload",
{
media_data: b64content,
},
uploaded
);
function uploaded(err, data, response) {
var mediaIdStr = data.media_id_string;
var params = {
status: "hi",
in_reply_to_status_id: tweet.id_str,
media_ids: [mediaIdStr],
};
//! Post tweet
T.post("statuses/update", params, tweeted);
}
function tweeted(err, reply) {
if (err) {
console.log(err);
console.log();
console.log("Error.");
} else {
console.log(reply);
console.log("tweeted");
}
}
};
tweets.forEach((tweet) => {
if (tweet.in_reply_to_screen_name === null) tweetEvent(tweet);
});

How to deal with Unhandled rejection SequelizeConnectionError: connect ETIMEDOUT

The following code is a project which parses directories of CSV files to input into a MySQL database. It often works, however I am plagued by an ETIMEDOUT error. I am a junior developer and am unsure how to handle this situation, or why it is occurring. It may be because I am parsing too much information at once. Any advise on how to handle this would be greatly appreciated.
const app = require('express')();
var fs = require('fs');
var Papa = require('papaparse');
var Sequelize = require('sequelize');
var _ = require('underscore');
var mainDirectory = ['./Relay-Data'];
var sequelize = new Sequelize('relay_cloud','root','admin',{
host: 'localhost',
dialect: 'mysql',
logging: false,
pool: {
max: 5,
min: 0,
idle: 50000,
acquire: 50000,
evict: 50000,
handleDisconnects: true
}
});
var Relay = sequelize.define('relay',{
Timestamp:{
type: Sequelize.DATE,
field: 'Timestamp',
},
Identifier:{
type: Sequelize.INTEGER,
field: 'Identifier',
},
Building:{
type: Sequelize.STRING,
field: 'Building',
},
Device_Type:{
type: Sequelize.STRING,
field: 'Device_Type'
},
Unit:{
type: Sequelize.STRING,
field: 'Unit'
},
Energy:{
type: Sequelize.DECIMAL,
field: 'Energy'
},
Volume:{
type: Sequelize.DECIMAL,
field: 'Volume'
},
File_Name:{
type: Sequelize.STRING,
field: 'File_Name'
}
},{
timestamps: false,
freezeTableName: true
});
Promise.all(mainDirectory.map(function(main) {
return new Promise(function(resolve, reject) {
fs.readdir(main, function(er,dirs){
if (er) {
return reject(er);
}
for(var i = 0; i < dirs.length; i++){
dirs[i] = mainDirectory + "/" + dirs[i];
}
resolve(dirs);
});
}).then(function(dirs){
return Promise.all(dirs.map(function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(er, files) {
if (er) {
return reject(er);
}
resolve(files);
});
}).then(function(files) {
return Promise.all(files.map(function(file) {
return new Promise(function(resolve, reject) {
fs.readFile(dir + '/' + file, 'utf-8', function(er, content) {
if (er) {
return reject(er);
}
var data_obj = Papa.parse(content,{
quotes: false,
delimiter: ";",
});
data_array = data_obj.data;
output_data_array = [];
for(var i = 10; i < data_array.length; i++){
if(data_array[i][0] !== "" ){
output_data_array.push({
"Timestamp" : data_array[i][0],
"Identifier" : data_array[i][1],
"Building" : file.split( "_" )[ 1 ],
"Device_Type" : data_array[i][3],
"Unit" : data_array[i][5],
"Energy" : parseFloat(data_array[i][11].replace(',','.').replace(' ', '')) || 0 ,
"Volume" : parseFloat(data_array[i][13].replace(',','.').replace(' ', '')) || 0,
"File_Name" : file
});
}
}
Relay.bulkCreate(output_data_array);
});
resolve();
});
}));
});
}));
});
})).catch(function(er) {
console.log(er);
});
app.listen(process.env.PORT || 3000, process.env.IP, function(){
console.log("Server is listening");
})
I've been facing this issue today, and this worked for me:
let sequelize = new Sequelize(dbName, dbUsername, dbPassword, {
host: dbHost,
// the rest of your sequelize configuration...
retry: {
match: [
/ETIMEDOUT/,
/EHOSTUNREACH/,
/ECONNRESET/,
/ECONNREFUSED/,
/ETIMEDOUT/,
/ESOCKETTIMEDOUT/,
/EHOSTUNREACH/,
/EPIPE/,
/EAI_AGAIN/,
/SequelizeConnectionError/,
/SequelizeConnectionRefusedError/,
/SequelizeHostNotFoundError/,
/SequelizeHostNotReachableError/,
/SequelizeInvalidConnectionError/,
/SequelizeConnectionTimedOutError/
],
max: 5
}
});
I was able to prevent the ETIMEDOUT error by doing the following:
setTimeout(function() {
Relay.bulkCreate(output_data_array);
}, 2);

Node.js - Counting the number of objects in MongoDB collection

I am new to Node.js and MongoDB and I am really struggling to wrap my head around callbacks. I have read a few articles but it is still quite confusing to me. In the code below, I am trying to return the count of orders that have some properties which I have expressed in the query in orderModel.count(query, next):
controllers/order.js:
var mongoose = require ('../config/db');
var orderModel = require('../models/order').model;
var User = require('./user');
var Error = require('../config/error');
createOrder: function (user, order, next) {
if (newOrder.totalPrice > user.credit && orderModel.
count({$and: [{user: order.user}, {active: true}, {$or: [{status: 0}, {status: 1}]}]},
function(err, count){
if(err)
console.log(err);
else
count; }) > 0)
return next({error: Error.InsufficientCredits});
}
I don't think I am correctly obtaining the variable count because when I tried printing out the result of the second condition in the if statement, I got this data printed out:
Query {
_mongooseOptions: {},
mongooseCollection:
NativeCollection {
collection: Collection { s: [Object] },
opts: { bufferCommands: true, capped: false },
name: 'orders',
collectionName: 'orders',
conn:
NativeConnection {
base: [Object],
collections: [Object],
models: [Object],
config: [Object],
replica: false,
hosts: null,
host: 'dsXXXXXX.mlab.com',
port: XXXXXX,
user: 'XXXX',
pass: 'XXXX',
name: 'X',
options: [Object],
otherDbs: [],
_readyState: 1,
_closeCalled: false,
_hasOpened: true,
_listening: false,
db: [Object],
_events: {},
_eventsCount: 0 },
queue: [],
buffer: false,
emitter:
EventEmitter {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined } },
....
You need to put your logic inside the Model.count() method callback function as:
var mongoose = require ('../config/db'),
orderModel = require('../models/order').model,
User = require('./user'),
Error = require('../config/error');
var createOrder = function (user, order, next) {
orderModel.count({
"user": order.user,
"active": true,
"status": { "$in": [0, 1] }
}, function(err, count) { //<-- put logic in this callback
if (err) {
console.log(err);
throw err;
} else if (newOrder.totalPrice > user.credit && count > 0) {
// logic for creating order here
} else {
return next({ "error": Error.InsufficientCredits });
}
})
}

Categories