Sequelize, custom setter, doesn't set - node.js

Unfortunatly the documentation for model property setters and getters is somewhat deficient and I'm having trouble getting my little setter to work.
var bcrypt = require('bcrypt');
module.exports = function( sequelize, DataTypes )
{
var User = sequelize.define('User', {
username: { type:DataTypes.STRING, unique: true, allowNull: false },
email: { type:DataTypes.STRING, allowNull: false, unique: true },
userlevel: { type:DataTypes.INTEGER, allowNull:false, defaultValue:0 },
password: { type:DataTypes.STRING,
set: function(v) {
var pw = this;
var r;
bcrypt.genSalt(10, function(err,salt) {
bcrypt.hash(v, salt, function(err,hash) {
pw.setDataValue('password', hash);
});
});
} }
});
return User;
}
Now from what I can tell based on github issues custom setters on properties are not called on create() so calling
db.User.create( { username:'guest', email:'guest#guest', userlevel:1, password:'guest' } ).success( function(record) { console.log(record) });
results in the following insert:
Executing (default): INSERT INTO `Users` (`id`,`username`,`email`,`userlevel`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'guest','guest#guest',100,'2014-02-25 01:05:17','2014-02-25 01:05:17');
so I went ahead and added the following in the success clause:
u.set('password', 'stupid');
u.save();
I can see that my setter is getting properly called and that the hash is getting set on the password property. However once the setter ends and I return back to my u.save() line the u object is back to it's previous state with no password set.
Any ideas?

You are experiencing this issue, because getters and setters are currently only support synchronous actions. Saying this, you can find a working solution here:
var User = sequelize.define('User', {
username: { type: DataTypes.STRING, allowNull: false, unique: true },
email: { type: DataTypes.STRING, allowNull: false, unique: true },
userlevel: { type: DataTypes.INTEGER, allowNull:false, defaultValue:0 },
password: {
type: Sequelize.STRING,
set: function(v) {
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync(v, salt);
this.setDataValue('password', hash);
}
}
})

Related

Error on INSERT INTO command with Sequelize?

I have some weird bug when trying to mock my database.
First, here is my User model (with default timestamp because I didnt add anything to options):
const { Sequelize, Model, DataTypes } = require("sequelize");
const sequelize = require("../db");
const User = sequelize.define("user", {
id: {
type: DataTypes.INTEGER, // or maybe Type STRING?
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
first_name: {
type: DataTypes.STRING,
},
last_name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
number_of_answers: {
type: DataTypes.INTEGER,
defaultValue: 0,
}
});
module.exports = User;
I already have one user that I had inserted by Signup endpoint:
Screenshot from psql - SELECT * FROM users;
Now, I am trying to INSERT INTO users and I made a sql file with Mockaroo. Here is example of one line, and error that I am getting when I try to run this command.
psql error on INSERT INTO command
Does anyone know what is a problem here and how can I insert user in colletion via psql.
If you want to know how my endpoint for signup works, here is my code:
// User Signup
router.post("/signup", async (req, res, next) => {
try {
// Crypting password
const hashedPw = await bcrypt.hash(req.body.password, 12);
const results = await User.create({
first_name: req.body.firstName,
last_name: req.body.lastName,
email: req.body.email,
password: hashedPw,
});
res.status(201).json({
status: "User saved to database",
data: {
results: results,
},
});
} catch (err) {
next(err);
}
});
Okay, I found out that I need to put attributes in between "".. So it would be like this below..
INSERT INTO users ("id", ...,"createdAt" ..) VALUES ("idValue", ...);

sequelize beforeCreate hook is not executing

I wrote a BeforeCreate hook in my sequelize model. when i hit create user route then it saying user.user_id can't be null and even before create hook function not executing. I have followed documentation of sequelize.They have mentioned same as I use.I wrote a BeforeCreate hook in my sequelize model. when i hit create user route then it saying user.user_id can't be null and even before create hook function not executing. I have followed documentation of sequelize.They have mentioned same as I use.
const sequelize = require("kvell-db-plugin-sequelize").dbInstance;
const Sequelize = require("kvell-db-plugin-sequelize").dbLib;
const shortid = require("shortid");
const User = sequelize.define(
"user",
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false
},
user_id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
unique: true
},
user_fname: {
type: Sequelize.STRING,
allowNull: false
},
user_lname: {
type: Sequelize.STRING,
allowNull: false
},
user_fullName: {
type: Sequelize.VIRTUAL,
get() {
return `${this.user_fname} ${this.user_lname}`;
},
set(value) {
throw new Error("Do not try to set the `fullName` value!");
}
},
user_email: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
validate: {
isEmail: true
},
unique: {
args: true,
msg: "Email address already in use!"
}
},
user_credential: {
type: Sequelize.STRING,
allowNull: false
},
user_roles: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false,
defaultValue: ["Admin"]
},
admin: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: true
},
user_img: {
type: Sequelize.STRING,
allowNull: true
}
},
{
timestamps: true
}
);
User.beforeCreate(async (user, options) => {
console.log("inside hooks");
let id = `user_${shortid.generate()}`;
user.user_id = id;
});
const toJSON = User.prototype.toJSON;
User.prototype.toJSON = function({ attributes = [] } = {}) {
const obj = toJSON.call(this);
if (!attributes.length) {
return obj;
}
return attributes.reduce((result, attribute) => {
result[attribute] = obj[attribute];
return result;
}, {});
};
module.exports = User;
The real answer (alluded to, but not explicitly stated, in one of the comments) is that beforeCreate hooks are applied after model validation.
This means if you have any field in your model (eg id ) which cannot be null, Sequelize will evaluate this prior to applying the beforeCreate field. In your case, Sequelize never gets as far as the beforeCreate hook, because the null id is failing validation every time.
Your accepted answer works around this by setting allowNull = true, thus circumventing the validation of your (briefly) null id. But the better option (rather than to distort your model by allowing null id) is almost certainly to instead use the correct hook: beforeValidate. This hook is applied before the model criteria are evaluated.
It is a very simple change:
User.beforeValidate(async (user, options) => {
console.log("inside hooks");
let id = `user_${shortid.generate()}`;
user.user_id = id;
});
NB: async is redundant here.
user_id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
unique: true
}
In this section I have set user_id allowNull = false beforeCreate execute after the beforeValidate hook. But in beforeValidate hooks it was throwing error cause of user.user_id===null so i allowed allowNull===true and now it's working.
Try to remove async in this piece of your code:
async (user, options)

email validation using express passport and sequelize

I need a basic email verification after a user signs up.
my pseudo code for this is something like
1.user signs up
2.his data is stored in database.
3.a token is generated using crypto.
4.token is then send to email id provided.
5.user clicks the link a account is verified.
meanwhile a separate sequelize schema is created that stores the email id and the token.
now my problem is how to implement this in my project
passport.use('local-signup', new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, email, password, done) {
var generateHash = function (password) {
return bCrypt.hashSync(password, bCrypt.genSaltSync(8), null);
};
User.findOne({
where: {
email: email.toLowerCase()
}
}).then(function (user) {
if (user) {
return done(null, false, {
message: 'That email is already taken'
});
}
else {
var userPassword = generateHash(password);
var data =
{
email: email.toLowerCase(),
password: userPassword,
firstname: req.body.firstname,
lastname: req.body.lastname,
mobileno: req.body.mobileno,
//verified_email: false,
//verified_mob: false
};
User.create(data).then(function (newUser, created) {
if (!newUser) {
return done(null, false);
}
if (newUser) {
return done(null, newUser);
}
});
}
});
}
));
i am new to nodejs but with all my understanding i guess things need to be impemented in
if (newUser) {
return done(null, newUser);
}
any guidance is appreciated.
my user schema..
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('user', {
id: {
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstname: {
type: Sequelize.STRING,
notEmpty: true
},
lastname: {
type: Sequelize.STRING,
notEmpty: true
},
//username: { type: Sequelize.STRING },
//about: { type: Sequelize.TEXT },
mobileno: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING,
validate: { isEmail: true }
},
password: {
type: Sequelize.STRING,
allowNull: false
},
last_login: {
type: Sequelize.DATE
},
verified_email: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
verified_mob: {
type: Sequelize.BOOLEAN,
defaultValue: false,
}
//status: { type: Sequelize.ENUM('active', 'inactive'), defaultValue: 'active' }
});
return User;
}
At any circumstances do not share your token with any of your users. User ID's can easily be extracted by tokens and this can be harmful.
Instead, try this approach;
I assume your user model is something like this at your database
{
name: String,
auth: {
password: String //This will be bcrypted.
email: {
address: String,
verified: String || Boolean,
}
}
}
As you can see, verified field holds a String field or Boolean field.
At the moment you create user, (model.create({...}) sequence) preset the value of verified to sha256 of current time (you can use sha256(moment().format())) and save user.
At mail, send user a link like, yoursite.com/verify?code=[code] and then,
Create a route for user/verify?code=[code] in controller. Get user get the user holds code in verified field and change it to 'true'
You are done.

hasMany called with something that's not an instance of Sequelize.Model

as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.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);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}
You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.
You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;
I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});
So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})
It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);
None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example
I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'use strict'
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}
I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});

Many to many relationships not being cleaned up on destroy() in sequelize

I have two models, User and Role.
var User = sequelize.define('User', {
username: { type:DataTypes.STRING, unique: true, allowNull: false },
email: { type:DataTypes.STRING, allowNull: false, unique: true },
password: { type:DataTypes.STRING,
set: function(v) {
var salt = bcrypt.genSaltSync(10);
console.log(v + ' ' + salt);
var hash = bcrypt.hashSync(v, salt);
this.setDataValue('password', hash);
}
},
token: {type:DataTypes.STRING, allowNull:true, unique: false }
},{
associate: function(models)
{
User.hasMany(models.Role);
}
});
var Role = sequelize.define('Role', {
rolename: { type:DataTypes.STRING, unique: true, allowNull: false }
}, {
associate: function(models)
{
Role.hasMany(models.User);
}
});
As you can see a user can have many roles. These are of course joined in a many to many table.
The problem is that if I remove a user from the database like so:
db.User.find( { where: {username:username}, include:[db.Role]} )
.success( function(user) {
db.Role.find( {where: {}})
user.destroy()
...
The user record is in fact destroyed but the roles related to that user are orphaned in the RoleUsers table. What is the recommended best practice in the framework for removing them?
Currently we support onDelete and onUpdate constraints for relations in sequelize. However in does not currently work for many to many. If you post a feature request at https://github.com/sequelize/sequelize/issues i might have time to look at it soon

Resources