Sequelize how to join 2 tables 1:N - javascript

I have 2 models: User and Foto
Each User has a lot of fotos, and each foto can have just 1 user related.
To do that i use include, the problem is, i can use the include just when i am querying the user and not when i query the foto.
I get there is no relationshop between User and foto problem.
So at the moment i have this:
Model User:
"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:false,
defaultValue:2
},
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);
},
associate: function(models){
User.hasMany(models.Foto,{foreignKey: "userId", as: "Fotos"});
}
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
return User;
}
Model Foto:
"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
},
lat: {
type: DataTypes.STRING,
allowNull: true
},
lon: {
type: DataTypes.STRING,
allowNull: true
},
altitude: {
type: DataTypes.STRING,
allowNull: true
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
plantId: {
type: DataTypes.INTEGER,
allowNull: true
},
},
{
associate: function (models) {
Foto.belongsToMany(models.User, {as:'Users'});
}
}
);
return Foto;
}
then i try to get something like this in a json three:
[{
FotoA:{
prop1:value1,
prop2:value2,
user:{
userProp1
}
}
FotoB:{
}
}]
on my route i do the following:
allPictures: function (req, res) {
Foto.findAll({include: [{ model: User, as: "Users",where:{userId: User.id} }]})
.then(function (fotos) {
res.send(fotos);
})
},
if there is a better way to do this instad of eager loading please share it, i just need to get the userId and the username.
Thanks

I guess you defined the association wrong, as you mentioned a Foto should belong to one User.
try
Foto.belongsTo(model.User);
instead of
associate: function (models) {
Foto.belongsToMany(models.User, {as:'Users'});
}
And also there should be no need for the where clause when selecting. If your associations are defined correctly, you can simply do
Foto.findAll({include: [models.User]})

Related

How to find several fields of a foreign key in a join table in Node.js Sequelize

I have a Node.js application with Express, Sequelize as ORM and PostgreSQL for the database. In this app I have candidate model and mission model as below.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat extends Model {
static associate(models) {
this.belongsToMany(models.mission, {
through: "candidat_mission",
foreignKey: "candidatId",
otherKey: "idMission",
});
}
}
candidat.init({
candidatId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
isEmail: true,
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
}, {
sequelize,
modelName: 'candidat',
tableName: 'candidat',
freezeTableName: true,
});
return candidat;
};
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class mission extends Model {
static associate(models) {
this.belongsToMany(models.candidat, {
through: "candidat_mission",
foreignKey: "idMission",
otherKey: "candidatId",
})
}
}
mission.init({
idMission: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false
},
aliasTitle: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
sequelize,
modelName: 'mission',
tableName: 'mission',
freezeTableName: true,
});
return mission;
};
These two models are linked in many-to-many by a candidate_mission join table. In this model, I added fields like a foreign key which points to another table, that of users.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat_mission extends Model {
static associate(models) {
this.belongsTo(models.user, { foreignKey: "fk_user" });
}
}
candidat_mission.init({
candidatMissionId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
candidatId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'candidat' },
key: "candidatId",
},
},
idMission: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'mission' },
key: "idMission",
},
},
fk_user: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: { tableName: 'user' },
key: "userId",
},
},
}, {
sequelize,
modelName: 'candidat_mission',
tableName: 'candidat_mission',
timestamps: true,
freezeTableName: true,
});
return candidat_mission;
};
When I make a "GET" request, I do have the information from the candidate_mission table (if a candidate is linked to this mission), but for the user it only returns the ID and I would like it to return all the fields present in the Users model, what can I do?
Here, my function in the mission controller which allows to add a candidate to this mission :
const addCandidats = async (req, res) => {
try {
const mission = await Mission.findByPk(req.body.idMission);
if (mission) {
const candidat = await Candidat.findByPk(req.body.candidatId);
if (candidat) {
mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
return res.status(200).send(mission);
} else {
console.log("Candidat non trouvé");
return null;
}
} else {
console.log("Mission non trouvée!")
return null;
}
} catch (error) {
console.log(error);
}
};
Currently, my query returns me this :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": 1,
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
And i want it to come back to me :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": {
"userId": 1,
"email": "blabla#gmail.com",
"name": "blabla"
},
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
After associating 2 models, we have to query again to get the object along with the relationship.
await mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
const result = await CandidatMission.findOne({
where: { candidatId: req.body.candidatId, idMission: req.body.idMission },
include: models.user,
})
return res.status(200).send(result);

Sequelize bulkCreate not including newly added columns

I'm having difficulty understanding why bulkCreate will not include my two newly created columns, perhaps it's the migration?
My new migration is as follows:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
queryInterface.addColumn('users', 'brand_id', {
allowNull: true,
type: Sequelize.INTEGER,
defaultValue: null,
});
queryInterface.addColumn('users', 'store_id', {
allowNull: true,
type: Sequelize.INTEGER,
defaultValue: null,
});
return true;
},
down: (queryInterface, Sequelize) => {
queryInterface.removeColumn('users', 'brand_id');
queryInterface.removeColumn('users', 'store_id');
return true;
}
};
I have a helper function for creating multiple users for the purpose of testing that looks something like this:
const properties = { brand_id: 123 };
const user = [];
users.push(Object.assign({}, {
name: chance.last(),
email: chance.email(),
password,
access_key: uuid(),
}, properties));
const newUsers = await models.user.bulkCreate(users, { returning: true, logging: console.log });
The output of the logging is:
INSERT INTO "users" ("id","name","email","password","access_key","created_at","updated_at") VALUES (DEFAULT,'Valente','letubdo#iwefa.fm','$2a$08$B5riQzA82ChwuH1q8HpGxOBK2uQj2m.BiHcEjytiox5yD.8u1fT5W','e62bf96c-0117-490f-9c80-b60e406238b0','2018-09-25 18:30:04.666 +00:00','2018-09-25 18:30:04.666 +00:00') RETURNING *;
You'll see that brand_id is completely ignored in the query, even if I change the following:
users.push(Object.assign({}, {
name: chance.last(),
email: chance.email(),
password,
access_key: uuid(),
brand_id: 123,
}, properties));
Any idea what could be wrong?
It turns out I did not add the two new columns to the user model:
'use strict';
module.exports = (sequelize, DataTypes) => {
var user = sequelize.define('user', {
name: {
allowNull: false,
type: DataTypes.STRING
},
email: {
allowNull: false,
type: DataTypes.STRING
},
password: {
allowNull: false,
type: DataTypes.STRING
},
access_key: {
allowNull: false,
type: DataTypes.STRING
},
brand_id: {
allowNull: true,
true: DataTypes.INTEGER,
},
store_id: {
allowNull: true,
true: DataTypes.INTEGER,
}
}, {
underscored: true,
});
return user;
};

fliped foreign key relations?

i have a strange effekt at a m:n relation..
this are the model definitions:
Role Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.User */
Role.associate = function(models) {
Role.belongsToMany(
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
);
};
return Role;
};
User Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.Role */
User.associate = function(models) {
User.belongsToMany(
models.Role, {
through: 'user_role',
foreignKey: 'roleId',
}
);
};
return User;
};
the migration is the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('user', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
}).then(() => {
queryInterface.createTable('role', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
});
}).then(() => {
queryInterface.createTable('user_role', {
userId: {
type: Sequelize.UUIDV4,
references: {
model: 'User',
key: 'uuid',
},
allowNull: false,
},
roleId: {
type: Sequelize.UUIDV4,
references: {
model: 'Role',
key: 'uuid',
},
allowNull: false,
},
....
});
}).then(() => {
return queryInterface.addConstraint('user_role', ['UserId', 'RoleId'], {
unique: true,
type: 'primary key',
name: 'userrole_pkey',
});
});
},
down: (queryInterface, Sequelize) => {
....
},
};
if i try to insert now a user with a new role:
let models = require('../models');
models.Role.create({
role: 'Administrator',
description: 'Administrator Gruppe',
}).then(role => {
models.User.create({
login: 'admin',
password: '123',
nick: 'Admini',
mail: 'admin#localhost.com',
}).then(user => {
user.addRole(role);
user.save().then(() => {
console.log('admin created');
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
it tries to add the role uuid in the userid and the user uuid in the roleid.. and for that the constraint fails...
any hints or tips where i made a mistake?
found the mistake myself (with help of a college)
at
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
i set the wrong foreign key, it's not the field in the helper table, it's needed to be the source table (in this case uuid of user model) or leave it blank for sequelize's default behaviour to use the primary key.

Sequelize: Before Create Hook is Not Working As Intended

I'm trying to perform a beforeCreate operation on a model that I created via Sequelize. I'm trying to have the password and salt saved before creating the user. However, the user is created without the encrypted password or salt. I'm not too familiar with Node.JS but I'm assuming this has to do with it's asynchronous nature. Any idea how to properly introduce a callback so that my create function behaves as intended?
Model:
'use strict';
var Promise = require("bluebird");
var bcrypt =Promise.promisifyAll(require("bcrypt-nodejs"));
const SALT_ROUNDS = 10;
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
username: { type: DataTypes.STRING, unique: true, allowNull: false, validate: { notEmpty: true } },
email: { type: DataTypes.STRING, unique: true, allowNull: false, isEmail: true },
phone_number: DataTypes.STRING,
password_hash: { type: DataTypes.STRING, allowNull: false, unique: true, validate: { notEmpty: true } },
password_salt: DataTypes.STRING,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
user_type: DataTypes.INTEGER,
two_factor_enabled: { type: DataTypes.BOOLEAN, defaultValue: false, },
email_verified: DataTypes.DATE,
active: { type: DataTypes.BOOLEAN, defaultValue: true, },
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
},
validPassword: function(password, passwd, callback) {
bcrypt.compare(password, passwd, function(err, isMatch) {
if (isMatch) {
return callback(null, true);
} else {
return callback(null, false);
}
});
},
},
hooks: {
beforeCreate: function(user, {}) {
bcrypt.genSalt(SALT_ROUNDS, function(err, salt) {
bcrypt.hash(user.password_hash, salt, function(){}, function(err, hash) {
if (err) {
return sequelize.Promise.reject(err);
}
user.setDataValue('password_hash',hash);
user.setDataValue('password_salt',salt);
});
});
}
},
instanceMethods: {
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10), null);
},
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
}
});
//User.associate = (models) => {
// User.hasMany(models.UserType, {
// foreignKey: 'userId',
// as: 'userTypes'
// });
//};
return User;
};
And here's the call:
return db.User
.create({
first_name: req.body.first_name,
last_name: req.body.last_name,
username: req.body.username,
email: req.body.email,
password_hash: req.body.password
})
.then(user => res.status(201).send(user))
.catch(error => res.status(400).send(error));
},
Personnaly I use the crypto, included in node.js : https://nodejs.org/api/crypto.html
I have a function to create the hash :
function createHash(password, salt) {
const generatedSalt = typeof salt !== 'undefined' ? salt : crypto.randomBytes(128).toString('base64');
const hmac = crypto.createHmac('sha256', generatedSalt);
hmac.update(password);
const hashedPassword = hmac.digest('hex');
return {
salt: generatedSalt,
hash: hashedPassword
};
}
And in my user model :
beforeCreate: (user, options, cb) => {
const saltAndHash = createHash(user.password);
user.salt = saltAndHash.salt;
user.password = saltAndHash.hash;
return cb(null, options);
}
I hope this will help you ;)

Sequelize.js belongsToMany instance method create not working?

My models:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Entity;
return Entity = sequelize.define('Entity', {
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
classMethods: {
associate: function(models) {
return Entity.belongsToMany(models.User);
}
}
});
};
and
'use strict';
module.exports = function(sequelize, DataTypes) {
var User;
return User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
}
}, {
instanceMethods: {
display: function() {
var user;
user = {
name: this.name,
email: this.email,
username: this.username,
active: this.active
};
return user;
}
}
}, {
classMethods: {
associate: function(models) {
return User.belongsToMany(models.Entity);
}
}
});
};
I want to create a user and then attach one entity to that user, so I am doing:
newUser = {
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
};
global.db.User.create(newUser).then(function(dbUser) {
var newEntity;
newEntity = {
name: newUser.name + " Default Entity"
};
console.log(dbUser);
return dbUser.createEntity(newEntity);
}).then(function(dbEntity) {
return console.log(dbEntity);
});
But I get an error: [TypeError: Object [object SequelizeInstance] has no method 'createEntity']
This is using Sequelize v2.0.0-rc8
What am I doing wrong?
dbUser is an instance, so dbUser.createEntity() is calling the instance method createEntity() on the dbUser instance. Which doesn't exist.
The correct solution is to call create on your Entity model, with the UserId field set to dbUser.id. So, something like:
global.db.Entity.create({name: 'blah blah blah', UserId: dbUser.id})
(you may have to fiddle around with the capitalization).
Also, I think you may want User.hasMany(models.Entity), but that's just something that caught me off guard.

Categories

Resources