Deadlock with Sequelize using beforeCreate and findOrCreate on a model - javascript

I'm getting the error:
Unhandled rejection SequelizeDatabaseError: ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
Here is my model:
var bcrypt = require('bcryptjs');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
username: {type: DataTypes.STRING, unique: true},
password: DataTypes.STRING,
email: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
isActive: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function (models) {
User.hasMany(models.Comment);
User.hasMany(models.Message, {as: 'receivedMessages', foreignKey: 'Receiver'});
User.hasMany(models.Message, {as: 'sentMessages', foreignKey: 'Sender'});
}
}
});
User.hook('beforeCreate', function (user, options, fn) {
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
return fn(err, user);
});
});
});
User.findOrCreate({
where: {
username: 'admin'
},
defaults: {
username: 'admin',
email: 'admin#admin.com',
password: 'admin',
isAdmin: true,
isActive: true
}
});
return User;
};
If I remove one of the hooks (beforeCreate or findOrCreate), it works fine.
It seems to be an issue about transaction but I can't find a way to make it work.
The doc talks about passing the transaction but I don't know how to pass the transaction to findOrCreate.
Here is the doc: http://docs.sequelizejs.com/en/latest/docs/hooks/#model-hooks
Any idea? Thanks in advance!!

Not a solution but a workaround, instead of using the hook "findOrCreate", I've done it by my own:
User.find(
{
where: {
username: 'admin'
}
}
).then(function (user) {
if (!user) {
User.create({
username: 'admin',
email: 'admin#admin.com',
password: 'admin',
isAdmin: true,
isActive: true
}).then(function (newUser) {
console.log('Admin created with id: ' + newUser.id);
});
}
}
);
Maybe not the best answer but it works.

Can also be done with Promise.resolve
Promise.resolve(clientService.update(previousClient, { qa_inspection_template_id: null }, userId, transaction)).then(async ()=>
{
const client = await clientService.show(data.client_id, true);
await clientService.update(client, { qa_inspection_template_id: template.id }, userId, transaction);
});

Related

._doc in Mongoose

I have this js code
app.post('/auth', async (req, res) => {
try {
const user = UserModel.findOne({email: req.body.email}).exec()
if (!user) return res.status(404).json({
message: 'Not find user'
})
const isValidPassword = bcrypt.compare(req.body.password,user._doc.passwordHash)
if (!isValidPassword) return res.status(404).json({
message: 'Incorrect password'
})
}
catch (err) {
console.log(err)
res.status(500).json({
message: 'error'
})
}
})
And I have this Schema
import mongoose from 'mongoose'
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
passwordHash: {
type: String,
required: true
},
telegramUrl: {
type: String,
required: true
},
avatarUrl: String
},
{
timestamps: true
}
)
export default mongoose.model('User', userSchema)
In this line
const isValidPassword = bcrypt.compare(req.body.password,user._doc.passwordHash)
I have error: Cannot read properties of undefined (reading 'passwordHash'). Why am I getting an error? He writes to me that ._doc undefined but why? Help me please
use these two methods in your schema
const bcrypt = require("bcrypt");
// Create Hash Salt Password ..
userSchema.pre("save", async function (next) {
if (!this.isModified("passwordHash")) return next();
this.passwordHash = await bcrypt.hash(this.passwordHash, 12);
next();
});
// Compare Password ...
userSchema.methods.comparePassword = function (passwordHash) {
return bcrypt.compareSync(passwordHash, this.passwordHash);
};
And in your auth code
app.post('/auth', async (req, res) => {
try {
const user = await UserModel.findOne({email: req.body.email}).exec()
if (user) {
res.status(400).json(
{ message: 'User already register'})
}
else{
const newuser = New User({
name: req.body.name,
// also write other schema fields
}
const res = await newuser.save();
console.log(res)
catch (err) {
console.log(err)
res.status(500).json({
message: 'error'
})
}
})
I hope this should resolve your problem

Can't push items into mongodb arrays

I'm trying to make a simple social media app using react, express and mongodb.
This is the user model:
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true},
followers: { type: Array, required: false },
following: { type: Array, required: false },
likes: { type: Array, required: false},
},
{ collection: 'users' }
)
This is the express server:
app.post('/api/follow', async (req, res) => {
const {token, username} = req.body
if (token === null)
{
return res.json({status: 'error'})
}
const user = await User.findOne({username}).lean()
const _visitor = jwt.verify(token, JWT_SECRET)
const visitor = await User.findOne({username: _visitor.username})
if (!user)
{
return res.json({status: 'error', error: 'User not found.'})
}
if (!visitor)
{
return res.json({status: 'error', error: 'User not found.'})
}
visitor.following.push(user._id)
user.followers.push(me._id)
return res.json({status: 'ok'})
})
But when I check the mongodb compass the following and followers arrays are empty.
The best way is to use findOneAndUpdate() method to update a value.
Also, if you are updating from two different collections you can use transactions. This is optional but can be useful to avoid inconsitences in your DB.
So your code can be something similar to this:
const updateVisitor = await User.findOneAndUpdate(
{
username: _visitor.username
},
{
$push:{
following: user._id
}
})
Example here
An the same code for user:
const updateUser = await User.findOneAndUpdate(
{
username: username
},
{
$push:{
followers: me._id
}
})

MongoDB only updates partially

My model has "id", "liked", "likedBy" and "matched" fields.
I can update my database and add id according to my hypotethical likes; it stores target's id to my current user's liked field, current user's id to target's likedBy field.
I'm trying to achieve, if a user has both liked and likedBy id matching then put liked id to my matched field on both users, but I can't for some reason. It just doesn't care if statement there.
Any ideas why?
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
}
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
//check matching
if (user.likedBy === user.liked) {
user.matched.unshift({ user: req.user.id });
}
await user.save();
await loggedUser.save();
res.status(200).send("Liked!");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
My Schema:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
picture: {
data: Buffer,
contentType: String,
},
age: {
type: Number,
required: true,
},
gender: {
type: String,
required: true,
},
job: {
type: String,
required: true,
},
desc: {
type: String,
default: "Hasn't written anything yet.",
},
liked: [{}],
likedBy: [{}],
matched: [{}],
});
module.exports = User = mongoose.model("user", UserSchema);
I found the mistake I made.
I'm trying to compare objects, which isn't possible really. I got index of my array then extracted the data I need and stored it into value1 & value2.
I found my mistake the moment I console.log'ed my conditions as below:
if(console.log(user.liked) === console.log(user.likedBy)){
...}
Working version:
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
} else {
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
const value1 = user.likedBy[0].user;
const value2 = user.liked[0].user;
if (value1 === value2) {
user.matched.unshift({ user: req.user.id });
loggedUser.matched.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
res.status(200).send("Liked & Matched!");
} else {
res.status(200).send("Liked!");
}
}
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});

mongoose schema method returning undefined

I want to create a method that validates the user's password by using bcrypt.compare()
here is the code below.
UserSchema.methods.validatePassword = async (data) => {
console.log(this.email); // returns undefined
console.log(this.first_name); // returns undefined
return await bcrypt.compare(data, this.password);
};
here is the UserSchema I created
const UserSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }
);
when getting this.password in my schema .pre('save', ..) it works but shows undefined when I use schema methods. :(
here is the implementation of the method
const verifySignIn = async (req, res, next) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({
status: 'failed',
message: 'User Not found.',
});
}
const isValid = await user.validatePassword(password);
if (!isValid) {
return res.status(401).send({
message: 'Invalid Password!',
data: {
user: null,
},
});
}
next();
} catch (err) {
Server.serverError(res, err);
}
};
In the guide it says:
Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document ...
So in this case, you just need to change UserSchema.methods.validatePassword = async (data) => {... to UserSchema.methods.validatePassword = async function(data) {...

sequelize.js custom validator, check for unique username / password

Imagine I have defined the following custom validator function:
isUnique: function () { // This works as expected
throw new Error({error:[{message:'Email address already in use!'}]});
}
However, when I attempt to query the DB I run into problems:
isUnique: function (email) { // This doesn't work
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function () { // This gets called
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
});
}
How can I query the ORM in a custom validator and trigger a validation error based on the response from the ORM?
You can verify if the email already exists like that:
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail:true
},
unique: {
args: true,
msg: 'Email address already in use!'
}
}
Here's a simplified sample of a functioning isUnique validation callback (works as of SequelizeJS v2.0.0). I added comments to explain the important bits:
var UserModel = sequelize.define('User', {
id: {
type: Sequelize.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true
},
email: {
type: Sequelize.STRING,
validate: {
isUnique: function(value, next) {
UserModel.find({
where: {email: value},
attributes: ['id']
})
.done(function(error, user) {
if (error)
// Some unexpected error occured with the find method.
return next(error);
if (user)
// We found a user with this email address.
// Pass the error to the next method.
return next('Email address already in use!');
// If we got this far, the email address hasn't been used yet.
// Call next with no arguments when validation is successful.
next();
});
}
}
}
});
module.exports = UserModel;
With Sequelize 2.0, you need to catch Validation Errors.
First, define the User Model with a custom validator:
var User = sequelize.define('User',
{
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({where: {email: value}})
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
other_field: Sequelize.STRING
});
module.exports = User;
Then, in the controller, catch any Validation Errors:
var Sequelize = require('sequelize'),
_ = require('lodash'),
User = require('./path/to/User.model');
exports.create = function (req, res) {
var allowedKeys = ['email', 'other_field'];
var attributes = _.pick(req.body, allowedKeys);
User.create(attributes)
.then(function (user) {
res.json(user);
})
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
});
Success callback is called even if no user is found. You have to check if the function passes a user as an argument:
isUnique: function (email) {
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function (u) { // This gets called
if(u){
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
}
});
}
Define the User Model with a custom validator:
const { DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const UserModel = sequelize.define('user', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUnique: (value, next) => {
UserModel.findAll({
where: { email: value },
attributes: ['id'],
})
.then((user) => {
if (user.length != 0)
next(new Error('Email address already in use!'));
next();
})
.catch((onError) => console.log(onError));
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
},
});
module.exports = UserModel;

Categories

Resources