Mongoose transaction over separate databases - node.js

I am trying to create a transaction to create documents in two separate databases using Mongoose. I wrote this piece of code, it creates docs in the databases, but when if I throw an error in the code, the docs still appear in the db's. Can anybody help me figure out what I do wrong?
const mongodb = mongoose.createConnection(MONGODB_URL, mongoOptions);
const session = await mongodb.startSession();
session.startTransaction();
try {
let db = mongodb.useDb('superUs');
const OrgModel = db.model('Organization', Organization, 'Organizations');
const newOrg = OrgModel.create([org, { session }]);
db = mongodb.useDb('anotherDb');
const UtilRecModel = db.model('User', User, 'util');
const NewUtilRec = UtilRecModel
.create([{ username: 'user', password: 'pwd', isAdmin: false }, { session }]);
session.commitTransaction();
session.endSession();
} catch(err) {
session.abortTransaction();
console.log('err');
}

const dbName = `organization-${org.title}`;
let sessionSuper;
let sessionOrg;
try {
const dbSuper = mongodb.useDb('superUs');
const dbOrg = mongodb.useDb(dbName);
const OrgModel = dbSuper.model('Organization', Organization, 'Organizations');
const UtilRecModel = dbOrg.model('User', User, 'util');
sessionSuper = await dbSuper.startSession();
sessionOrg = await OrgModel.startSession();
sessionSuper.startTransaction();
sessionOrg.startTransaction();
await OrgModel.create([org], { session: sessionSuper });
await UtilRecModel.create([{ username: 'util', password: 'util', isAdmin: false }], { session: sessionOrg });
sessionSuper.commitTransaction();
sessionSuper.endSession();
sessionOrg.commitTransaction();
sessionOrg.endSession();
} catch(err) {
sessionSuper.abortTransaction();
sessionOrg.abortTransaction();
sessionSuper.endSession();
sessionOrg.endSession();
throw(err);
}

Related

Writing a database seeder class to seed multiple collections. (NodeJS, MongoDB)

Hi all I have been struggling all day with this and I was hopingsomeone might be able to assist me on figuring out the functionality. I'm still very new to asynchronous programming so any advice would be highly appreciated! I wrote two seeder classes which work great independently, and am opting to create a databaseSeeder class that I can use to run all my migrations from one file.
The issue I'm facing is that I need the UserSeeder to complete first because the ProductSeeder uses the ID's from the User model to link products to users. Please find my code for a reference:
User Seeder:
const mongoose = require("mongoose");
const User = require("../models/User");
const dotenv = require("dotenv");
const { faker } = require("#faker-js/faker");
const { v4: uuidv4 } = require("uuid");
dotenv.config();
const database = process.env.MONGOLAB_URI;
mongoose
.connect(database, {
useUnifiedTopology: true,
useNewUrlParser: true,
})
.then(() => console.log("User Collection Connected"))
.catch((err) => console.log(err));
const seedUserList = async () => {
let users = [];
for (let i = 0; i < 5; i++) {
const userSeeder = new User({
_id: uuidv4(),
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
username: faker.internet.userName(),
email: faker.internet.email(),
contact_number: faker.phone.number("### ### ####"),
password: faker.internet.password(),
address: faker.address.streetAddress(),
avatar: faker.image.people(1920, 1080, true),
rating: 3,
isVerified: false,
isValidated: false,
});
users.push(userSeeder);
}
const seedUsers = async () => {
await User.deleteMany({});
await User.insertMany(users);
};
seedUsers().then(() => {
console.log("Users Seeded Successfully!");
mongoose.connection.close();
});
};
seedUserList();
module.exports = {
seedUserList,
};
Product Seeder:
const mongoose = require("mongoose");
const Product = require("../models/Product");
const User = require("../models/User");
const dotenv = require("dotenv");
const { faker } = require("#faker-js/faker");
const { v4: uuidv4 } = require("uuid");
dotenv.config();
const database = process.env.MONGOLAB_URI;
mongoose
.connect(database, {
useUnifiedTopology: true,
useNewUrlParser: true,
})
.then(() => console.log("Products Collection Connected"))
.catch((err) => console.log(err));
const seedProductList = async () => {
let products = [];
let ids = [];
let idList = await User.find().select("_id");
idList.map((r) => r.toObject());
for (let i = 0; i < idList.length; i++) {
idList[i] = JSON.stringify(idList[i]);
ids.push(idList[i].substring(8, 44));
}
for (let i = 0; i < 25; i++) {
const productSeeder = new Product({
_id: uuidv4(),
name: faker.commerce.product(),
price: faker.commerce.price(),
description: faker.commerce.productDescription(),
image: faker.image.image(1920, 1080, true),
category: "Tools",
times_borrowed: faker.datatype.number(),
last_borrowed: faker.date.past(),
product_status: faker.helpers.arrayElement(["Available", "In Use"]),
user_id: faker.helpers.arrayElement(ids),
});
products.push(productSeeder);
}
const seedProducts = async () => {
await Product.deleteMany({});
await Product.insertMany(products);
};
seedProducts().then(() => {
console.log("Products Seeded Successfully!");
mongoose.connection.close();
});
};
seedProductList();
module.exports = {
seedProductList,
};
Database Seeder:
const { seedUserList } = require("./userSeeder");
const { seedProductList } = require("./productSeeder");
const mongoose = require("mongoose");
const seedDatabase = async () => {
seedUserList().then(() => {
console.log("Seeding Users!");
});
await seedProductList().then(() => {
console.log("Seeding Products!");
});
};
seedDatabase().then(() => {
console.log("Database Successfully Seeded!");
mongoose.connection.close();
});
Terminal Output
E:\Projects\Shopping-Platform>node src/seeders/databaseSeeder.js
Seeding Users!
User Collection Connected
Products Collection Connected
Seeding Products!
Database Successfully Seeded!
Products Seeded Successfully!
Users Seeded Successfully!
I've been playing around with the await functionality and wrapping each function with it's own await call so that the ProductSeeder waits for the UserSeeder to complete first before it executes, but so far no luck!

How do I reference documents from other collection in express

I have 2 collections here >>course & author
I need to prepare my course schema in such a way that it references the author and at the post request I only need to put the id.I am using #joi13 This is what I have done so far.
course schema
const mongoose = require('mongoose')
const Joi = require('joi')
Joi.objectId= require('joi-objectid')(Joi)
// schema
const courseSchema = new mongoose.Schema({
...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
})
// the model
const Course = mongoose.model('Courses', courseSchema)
// validation
const courseValidation = (course) => {
const schema = {
...
authorId: Joi.objectId().required()
}
return Joi.validate(course, schema)
}
the course router
const {Course, validate} = require('../models/course')
const express = require('express')
const router = express.Router()
const {Author} = require('../models/author')
// post
router.post('/', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send(error.details[0].message)
const title = await Course.findOne({title : req.body.title})
if (title) return res.status(400).send('That user exists')
const author = await Author.find()
if (!author) return res.status(404).send('No such Author')
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})
try {
course = await course.save()
res.send(course)
}
catch(er){
console.log(er)
}
})
The error
At line, const author = await Author.find() will return array of authors
while creating course at following lines of you are using author._id which will be undefined, So you have to find a specific Author using findOne (return a single author as object) or you have to use an indexed element of an author like author[0]._id
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})

mongodb: cannot set property as null though its working in shell

I am trying to build OTP generation and verification module with
Nodejs,mongodb,Nexmo
BUT IT IS WORKING IN SHELL
here is my code
PHONE SHCHEMA
const phoneSchema = new Schema({
mobileNo:{type: String},
count:{type:String},
verified:{
type:String,
defaultValue:0
},
lastFive:{
type:Date,
default: Date.now
},
},{ timestamps: true})
const Phone = mongoose.model('Phone', phoneSchema)
module.exports = Phone
OTP SCHEMA
const otpSchema = new Schema({
otp:{
type:Number,
unique: true
},
mobileNo:{ type:String,}
},{ timestamps: true})
const Otp = mongoose.model('Otp', otpSchema)
module.exports = Otp
LOGIN CONTROLLER
const nexmo = new Nexmo({
apiKey: '#######',
apiSecret: '########'
})
exports.getOtp = async (req,res,next)=>{
try{
const {mobileNo} = req.body
let phone = await MobileNo.findOne({mobileNo})
if(!phone){
const error = new Error('Number you have entered is not registered Please contact dealer')
throw error
}
else{
Otp.findOneAndDelete({mobileNo})
const dates= phone.lastFive.toString().split(';')
if(dates.length>=5){
const msec=(Date.now()- parseInt(dates[0]) )
const minute=Math.floor(msec / 1000 / 60)
if(minute<=5){
return res.json({
err:'5 minute 5 Otp limit. Wait'
})
}else{
phone.count++;
phone.lastFive=Date.now();
}}
else{
phone.count++;
phone.lastFive=`${phone.lastFive} ${Date.now()}`
}}
let otp,randomOtp;
while(true){
randomOtp = Math.floor( Math.random() * (10000 - 1000) + 1000)
otp = await Otp.findOne({otp} )
if(!otp){
otp = new Otp({
otp:randomOtp,
mobileNo
})
setTimeout(()=>{
otp.delete();
},1000*60*5);
break}}
const from = 'Nexmo'
const to = mobileNo
const text = `Verification code :- ${randomOtp}`
nexmo.message.sendSms(from, to, text,async (err, response) => {
if (err) {
next(err);
} else {
await phone.save()
await otp.save()
res.json({
msg:'OTP is send Successfuly',
success:true
})
}
})}
catch(err){
next(err)}}
VERIFYING OTP
exports.verifyOtp= async(req,res,next)=>{
const {otp,mobileNo}= req.body;
try{
const checkOtp = await Otp.findOne({otp,mobileNo});
if(!checkOtp){
throw new Error('Invalid Otp')
}
else
{
const findPhone = await MobileNo.findOne({ mobileNo:checkOtp.mobileNo,otp} )
findPhone.verified=1;
findPhone.save();
checkOtp.delete();
return res.json({
msg:'OTP is verified',
success:true
})}}
catch(err){
next(err)}}
** ROUTE **
const loginController = require('../controllers/loginController')
router.post('/getotp', loginController.getOtp)
router.post('/verifyotp', loginController.verifyOtp)
module.exports = router
** ERROR**
findPhone null TypeError: Cannot set property 'verified' of null
at exports.verifyOtp (######\backend\src\controllers\loginController.js:164:31)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
<

Mongoose validation fails for required fields even when I provide values for that fields

I have the following model:
/**
* #module optikos
*/
const mongoose = require('mongoose');
/**
* Define the schema of the main table(Document)
* Needs to be redefined to add user role here
*/
const dbUri = 'mongodb://localhost:27017/optikosmaindb';
mongoose.connect(dbUri,{ useNewUrlParser: true });
const optikosSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
required:[true,'Email is required']
},
password: {
type: String,
required:[true,'Password is required']
},
dbpath: {
type: String,
required:true
},
role:{
type:String,
lowercase:true,
enum:{
values:['admin','employee','shop owner'],
message:'`${VALUE} is not a valid role`'
},
default:'shop owner'
},
payment_status: {
/***
* Only show owners need to have a payment status
*/
required:this.role == 'shop owner',
type: String,
lowercase:true,
enum:{
values:['pending','paid'],
message:'`{VALUE} is not a valid payment status`'
},
default: 'pending' //will change to paid once a payment transaction is completed
},
payment_reset: {
required:this.role == 'shop owner',
type: Date,
default:Date.now
}
});
const Optikos = mongoose.model('optikos',optikosSchema);
module.exports = Optikos;
I have another file called index.js in which I require it like this to create some users for my database:
const admins = require('./admins');
const Optikos = require('../schemas/optikos');
const bcrypt = require('bcrypt');
async function setup() {
console.log('Setting app Optikos main database');
try {
let registeredAdmins = admins.map(async admin=>{
let {email,name,password} = admin;
let role = 'admin';
let saltRounds = 10;
password = await bcrypt.hash(password,saltRounds);
let dbpath = dbUri;
let user = new Optikos({
'name':name,
'email':email,
'password':password,
'role':role,
'dbpath':dbpath
});
return user;
});
let response = await Optikos.create(registeredAdmins);
console.log('Registered admins')
process.exit(0);
} catch (e) {
console.log('Error error error')
console.log(e)
process.exit(1)
}
}
setup();
The admins file is an array of objects containing a name,email and password fields.However after trying to run the indexfile I get the following: "{ ValidationError: optikos validation failed: dbpath: Path dbpath is required., password: Password is required, email: Email is required".However If I log all the variables that I am passing to "new Optikos()" I get the values that I expect.What is wrong?
I solved the required fields error by doing this:
let registeredAdmins = admins.map(async admin=>{
let {email,name,password} = admin;
let role = 'admin';
let saltRounds = 10;
password = await bcrypt.hash(password,saltRounds);
let dbpath = dbUri;
let user = new Optikos({
'name':name,
'email':email,
'password':password,
'role':role,
'dbpath':dbpath
});
return user;
});
Promise.all(registeredAdmins)
.then(async users=>{
console.log(users)
await Optikos.create(users);
console.log('Yeap')
}).catch(e=>{
console.log(e)
})
Since registeredAdmins is a list of promises I use Promise.all() to get the values of the resolved promises.Thanks #AbdullahDibas for the hint
It is usually due to the fact that async await doesn't work the expected way in the situations of looping often ( though another alternative for the same is for-await-in, but it is still not considered to be production safe, or a good practise by some of the eslint standards ) and so, you may wish to rephrase your code as :
const admins = require('./admins');
const Optikos = require('../schemas/optikos');
const bcrypt = require('bcrypt');
async function setup() {
console.log('Setting app Optikos main database');
const passwordPromises = [];
try {
admins.forEach(admin => {
let { password } = admin;
let saltRounds = 10;
passwordPromises.push(bcrypt.hash(password, saltRounds));
});
const passwords = await Promise.all(passwordPromises);
let registeredAdmins = admins.map((admin, index) => {
let { email, name } = admin;
let user = new Optikos({
'name': name,
'email': email,
// The below would work because Promise.all resolves them in
// the sequential order. Also, since there would be a one - to - one
// mapping in the indices of the admins and the password due to the above stated fact
// the following would work
'password': passwords[index],
'role': 'admin',
'dbpath': dbUri
});
return user;
});
let response = await Optikos.create(registeredAdmins);
console.log('Registered admins')
process.exit(0);
} catch (e) {
console.log('Error error error')
console.log(e)
process.exit(1)
}
}

I want to create a user in Pagerduty using Node js. But I am not able to do. Can someone help me in this?

I am new to Node js. Below code I have written.It does not show any output.
const pdClient = require('node-pagerduty');
const pdApiKey = 'XXXXXXXXXXXXXX';
const pd = new pdClient(pdApiKey);
let from = 'XXXXXXXXXX#XXX.com';
let payload = {
user: {
type: 'user',
name: 'test',
email: 'test#gmail.com',
role: 'Manager'
}
};
var res = pd.users.createUser(from, payload);
console.log(res);
I believe the node-pagerduty client is promise based. Therefore the API functions return a Promise that you have to call .then() on or use await.
If you try:
pd.users.createUser(from, payload).then((result) => {
console.log('Result: ', result);
}).catch((error) => {
console.log("Error: ", error);
});
You should get a result!
If you wish to use the async / await pattern you can try:
async function testCreateUser() {
const pdClient = require('node-pagerduty');
const pdApiKey = 'XXXXXXXXXXXXXX';
const pd = new pdClient(pdApiKey);
let from = 'XXXXXXXXXX#XXX.com';
let payload = {
user: {
type: 'user',
name: 'test',
email: 'test#gmail.com',
role: 'Manager'
}
};
var res = await pd.users.createUser(from, payload);
console.log(res);
}
testCreateUser();

Resources