Sequelize: OR between parent where clause and child where clause - node.js

I have 2 models:
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
});
User.associate = (models) => {
User.hasOne(models.Profile, {
foreignKey: {
name: 'user_id',
},
});
};
const Profile = sequelize.define('Profile', {
name: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
}, {
tableName: 'profiles',
freezeTableName: true,
timestamps: false,
});
Profile.associate = (models) => {
Profile.belongsTo(models.User, {
foreignKey: {
name: 'user_id',
},
});
};
I would like to get all users where the email address OR the name matches a certain condition. Something like:
User
.all({
where: {
email: {
$like: filter
},
},
include: [{
model: Profile,
where: {
name: {
$like: filter
},
},
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
but it returns all users where user.email AND profile.name matches the condition. I would like to have OR between the 2 where clause.
Is it possible?
Note:
I'm using Sequelize 4.0.0.
Update:
In case of anybody else struggles with this, the solution is:
User
.all({
where: {
$or: {
email: {
$like: filter
},
'$Profile.name$': {
$like: filter
}
}
},
include: [{
model: Profile,
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});

In case if anyone else is looking for this, here is how I managed to solve it:
User
.all({
where: {
$or: {
email: {
$like: filter
},
'$Profile.name$': {
$like: filter
}
}
},
include: [{
model: Profile,
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
Thanks #Ninja Coding for confirming the solution.

Related

sequelize includes not returning data

I am trying to get data mapped with empid from 2 tables viz-skillsrepo and certifications and render it to frontend,I am getting all data from certifications table,but i need data only of the empid which i send in request
tried using includes method
app.get('/updateprofile/:id', function (req, res) {
db.skillsrepo.find({
where: { employeeId: req.params.id },
include: [
{
model: db.certifications
},
{
model: db.attachments
},
{
model: db.project
}
]
}).then(result => {
if (result != null) {
res.render('updateprofile', {
user: result,
eid: req.params.id,
});
console.log("**********", result)
}
})
});
This is the Schema:
var skillsrepo = exports.skillsrepo = connection.define('skillsrepo', {
firstname: {
type: Sequelize.STRING(23)
},
lastname: {
type: Sequelize.STRING(23)
},
highQual: {
type: Sequelize.STRING(23)
},
fivekeystrenghts: {
type: Sequelize.TEXT
},
domain: {
type: Sequelize.STRING(23)
},
technicalskills: {
type: Sequelize.STRING(23)
},
typesoftesting: {
type: Sequelize.STRING(23)
},
employeeId: {
type: Sequelize.INTEGER(11),
references: {
model: 'employeemastertablee',
key: 'id'
}
}
});
skillsrepo.hasMany(certifications, {
foreignKey: "employeeId"
});
certifications.belongsTo(skillsrepo, {
foreignKey: "employeeId"
});

How to build model with sequelize for belong to many association

This is what I wrote in Country.js (exactly the same as User.js except datatypes) :
module.exports = function(sequelize, DataTypes) {
const Country = sequelize.define('country',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
code: {
type: DataTypes.INTEGER
},
alpha2: {
type: DataTypes.STRING
},
alpha3: {
type: DataTypes.STRING
},
name_en: {
type: DataTypes.STRING
},
name_fr: {
type: DataTypes.STRING
}
},
{
freezeTableName: true,
timestamps: false
});
Country.associate = ( models ) => {
models.Country.belongsToMany(models.User, {
through: 'country_user',
as: 'user',
foreignKey: 'id_country'
});
};
return Country;
}
This is my query :
router.get('/thisuserCountries', function(req, res, next){
User(db, Sequelize.DataTypes).findOne({
include: [{
model: Country(db, Sequelize.DataTypes),
as: 'countries',
required: false,
attributes: ['id'],
}],
where: {
email: 'jerome.charlat#gmail.com'
}
})
.then(user => {
if(user) {
res.json(user)
}
else {
res.send('User does not exist')
}
})
.catch(err => {
res.send('error: ' + err)
})
})
This is my db.js :
const Sequelize = require('sequelize')
const db = new Sequelize('travel_memories', 'root', '', {
host: 'localhost',
dialect: 'mysql',
port: 3306
})
db
.authenticate()
.then(() => {
console.log('Connection has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
const models = {
Country: db.import('../models/Country'),
User: db.import('../models/User'),
CountryUserJoin: db.import('../models/Country_user')
};
Object.keys(models).forEach((modelName) => {
if('associate' in models[modelName]){
models[modelName].associate(models);
}
});
module.exports = db
Postman says : error SequelizeEagerLoadingError: country is not associated to user!
But, I think I should write in the through parameter the model User_country when I associate tables in each model. So i tried to write something like :
Country.associate = ( models ) => {
models.Country.belongsToMany(models.User, {
through: models.Country_user,
as: 'user',
foreignKey: 'id_country'
});
};
And console says when I launch server, before querying anything :
SequelizeAssociationError: country.belongsToMany(user) requires through option, pass either a string or a model.
So I am blocked. I used the example in documentation to write the assocation with models.foo. But in fact models comes from nowhere..
Thanks again for your help !
There's not a lot of documentation about this, but here it says that you should use a through option when querying or selecting belongs-to-many attributes, just like this:
...
User(db, Sequelize.DataTypes).findOne({
include: [{
model: Country(db, Sequelize.DataTypes),
as: 'countries',
required: false,
through: {
attributes: ['id']
}
}],
where: {
email: 'jerome.charlat#gmail.com'
}
})
...

Error when trying to insert row to table because of UUID foreign key with sequelize.js

I am using sequelize.js with node.js and postgres.
I got 2 simple tables from an example as a 'POC' of sorts.
I changed the ID to be UUID and I am having an issue with the insert into the second table ( with the UUID FK ).
I am using postman to test it.
I am creating todo rows with UUID with no issues,
Then I am trying to create a todo item which has a todo id as foreign key
and it seems that it is failing to recognize that ID!
I tried a manual script in postgres and it worked.
I am probably missing something code wise but I cant figure out what.
here is the error which is being returned to me in postman -
{
"name": "SequelizeDatabaseError",
"parent": {
"name": "error",
"length": 96,
"severity": "ERROR",
"code": "22P02",
"file": "uuid.c",
"line": "137",
"routine": "string_to_uuid",
"sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
},
"original": {
"name": "error",
"length": 96,
"severity": "ERROR",
"code": "22P02",
"file": "uuid.c",
"line": "137",
"routine": "string_to_uuid",
"sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
},
"sql": "INSERT INTO \"TodoItems\" (\"id\",\"content\",\"complete\",\"createdAt\",\"updatedAt\",\"todoId\") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
}
Here are the relevant js files -
todoItems.js controller -
const TodoItem = require('../dal/models').TodoItem;
const uuid = require('uuid/v4');
module.exports = {
create(req, res) {
return TodoItem
.create({
content: req.body.content,
todoId: req.params.todoId,
})
.then(todoItem => res.status(201).send(todoItem))
.catch(error => res.status(400).send(error));
},
update(req, res) {
return TodoItem
.find({
where: {
id: req.params.todoItemId,
todoId: req.params.todoId,
},
})
.then(todoItem => {
if (!todoItem) {
return res.status(404).send({
message: 'TodoItem Not Found',
});
}
return todoItem
.update({
content: req.body.content || todoItem.content,
complete: req.body.complete || todoItem.complete,
})
.then(updatedTodoItem => res.status(200).send(updatedTodoItem))
.catch(error => res.status(400).send(error));
})
.catch(error => res.status(400).send(error));
},
destroy(req, res) {
return TodoItem
.find({
where: {
id: req.params.todoItemId,
todoId: req.params.todoId,
},
})
.then(todoItem => {
if (!todoItem) {
return res.status(404).send({
message: 'TodoItem Not Found',
});
}
return todoItem
.destroy()
.then(() => res.status(204).send())
.catch(error => res.status(400).send(error));
})
.catch(error => res.status(400).send(error));
},
};
todos.js controller-
const Todo = require('../dal/models').Todo;
const TodoItem = require('../dal/models').TodoItem;
module.exports = {
create(req, res) {
return Todo
.create({
title: req.body.title,
})
.then((todo) => res.status(201).send(todo))
.catch((error) => res.status(400).send(error));
},
list(req, res) {
return Todo
.findAll({
include: [{
model: TodoItem,
as: 'todoItems',
}],
order: [
['createdAt', 'DESC'],
[{ model: TodoItem, as: 'todoItems' }, 'createdAt', 'ASC'],
],
})
.then((todos) => res.status(200).send(todos))
.catch((error) => res.status(400).send(error));
},
retrieve(req, res) {
return Todo
.findByPk(req.params.todoId, {
include: [{
model: TodoItem,
as: 'todoItems',
}],
})
.then((todo) => {
if (!todo) {
return res.status(404).send({
message: 'Todo Not Found',
});
}
return res.status(200).send(todo);
})
.catch((error) => res.status(400).send(error));
},
update(req, res) {
return Todo
.findByPk(req.params.todoId, {
include: [{
model: TodoItem,
as: 'todoItems',
}],
})
.then(todo => {
if (!todo) {
return res.status(404).send({
message: 'Todo Not Found',
});
}
return todo
.update({
title: req.body.title || todo.title,
})
.then(() => res.status(200).send(todo))
.catch((error) => res.status(400).send(error));
})
.catch((error) => res.status(400).send(error));
},
destroy(req, res) {
return Todo
.findByPk(req.params.todoId)
.then(todo => {
if (!todo) {
return res.status(400).send({
message: 'Todo Not Found',
});
}
return todo
.destroy()
.then(() => res.status(204).send())
.catch((error) => res.status(400).send(error));
})
.catch((error) => res.status(400).send(error));
},
};
todo table create migration -
module.exports = {
up: (queryInterface, Sequelize) =>
queryInterface.createTable('Todos', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
}),
down: (queryInterface /* , Sequelize */) => queryInterface.dropTable('Todos'),
};
todo-item table create migration -
module.exports = {
up: (queryInterface, Sequelize) =>
queryInterface.createTable('TodoItems', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
content: {
type: Sequelize.STRING,
allowNull: false,
},
complete: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
todoId: {
type: Sequelize.UUID,
onDelete: 'CASCADE',
references: {
model: 'Todos',
key: 'id',
as: 'todoId',
},
},
}),
down: (queryInterface /* , Sequelize */) =>
queryInterface.dropTable('TodoItems'),
};
todo model -
const uuid = require('uuid/v4');
'use strict';
module.exports = (sequelize, DataTypes) => {
const Todo = sequelize.define('Todo', {
title: {
type: DataTypes.STRING,
allowNull: false,
}
});
Todo.associate = (models) => {
Todo.hasMany(models.TodoItem, {
foreignKey: 'todoId',
as: 'todoItems',
});
};
Todo.beforeCreate((item, _ ) => {
return item.id = uuid();
});
return Todo;
};
todo-item model -
const uuid = require('uuid/v4');
'use strict';
module.exports = (sequelize, DataTypes) => {
const TodoItem = sequelize.define('TodoItem', {
content: {
type: DataTypes.STRING,
allowNull: false,
},
complete: {
type: DataTypes.BOOLEAN,
defaultValue: false,
}
});
TodoItem.associate = (models) => {
TodoItem.belongsTo(models.Todo, {
foreignKey: 'todoId',
onDelete: 'CASCADE',
});
};
TodoItem.beforeCreate((item, _ ) => {
return item.id = uuid();
});
return TodoItem;
};
What does your router code look like? Are you using correct path parameter for todoId? If you're using express for example. it should look like app.post("/todos/:todoId/todo_items", todoItemController.create) . Note the camelcase todoId . That will ensure that the req.params.todoId you're referencing in todoItems controller would have the right value.
Also, make sure you have a correct body parser to handle req.body.content correctly. In express, this would be done via body body-parser library and app.use(bodyParser.json()) . Add a breakpoint or log statement in the todoItem controller create code and verify that you actually have the correct parameter values.
If you happen to have the error above, it might be because you are nesting other entities in your request body and therefore the UUID is not getting converted from string to a UUID.
For instance if you have a request body like
{
"Transaction": {
"id" : "f2ec9ecf-31e5-458d-847e-5fcca0a90c3e",
"currency" : "USD",
"type_id" : "bfa944ea-4ce1-4dad-a74e-aaa449212ebf",
"total": 8000.00,
"fees": 43.23,
"description":"Description here"
},
}
and therefore in your controller you are creating your entity like
try {
await Transaction.create(
{
id: req.body.Transaction.id,
currency: req.body.Transaction.currency,
type_id: req.body.Transaction.type_id,
total: req.body.Transaction.total,
fees: req.body.Transaction.fees,
description: req.body.Transaction.description,
}......
Your id and type_id are mostly likely not being converted from string to a UUID.
There are multiple ways of tackling this. The most straightforward approach is to do an explicit conversion from string to UUID.
To do this, import parse from the uuid npm module and do the explicit conversion as you can see in the code sample below.
const { parse: uuidParse } = require("uuid");
try {
await Transaction.create(
{
id: uuidParse(req.body.Transaction.id),
currency: req.body.Transaction.currency,
type_id: uuidParse(req.body.Transaction.type_id),
total: req.body.Transaction.total,
fees: req.body.Transaction.fees,
description: req.body.Transaction.description,
}.....
This explicit conversion from string to a UUID will mostly solve the issue.

Sequelize BelongsToMany: custom column in WHERE clause

I have this 3 models:
var User = sequelize.define('users', {
name: {
type: DataTypes.STRING(50),
field: 'name'
}
});
var Book = sequelize.define('books', {
name: {
type: DataTypes.STRING(50),
field: 'name'
}
});
var UserBook = sequelize.define('userbook', {
validated: {
type: DataTypes.INTEGER,
field: 'validated',
defaultValue: 0
}
});
Book.belongsToMany(User, {through: UserBook, foreignKey: 'bookId'});
User.belongsToMany(Book, {through: UserBook, foreignKey: 'userId'});
Now I want to get books for a user where validated=1
I have tried this:
User.findOne({
where: { id: req.session.user.id }
})
.then(function(user){
user.getBooks({
through: { validated: 1 }
})
.then(function(books){
return res.json({success: true, books: books});
})
});
But it returns all books without checking for validated=1
How to get all books with validated=1 ?
Thank you
I've you tried this :
User.findOne({
where: { id: req.session.user.id }
})
.then(function (user) {
user.getBooks({
where: { validated: 1 }
})
.then(function (books) {
return res.json({ success: true, books: books });
})
});
?
Edit (it works):
User.findById(req.session.user.id, {
include: [
{
model: Book,
through: {
where: { validated: 1 }
}
}
]
}).then((user) => {
console.log(user);
})

Match query in Mongoose for arrays does not work

Below you can find my code where I try to exclude from result all users who have authorized user in any of following lists: friends, sentFriendRequests and receiveFriendRequests. Unfortunately the code is not working (other conditions work perfect e.g. filter by id, fullName and email), but I can not figure out why. Thanks in advance for any kind of help.
User.findOne({_id: req.user.id})
.exec(function (err, user) {
if (err) {
logger.error('Friend 500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
console.log('User id: ' + user._id)
User.aggregate([
{
$project: {
fullName: {
$concat: ['$firstName', ' ', '$lastName']
},
email: 1,
avatarPath: 1,
mutualFriends: {
$size: {
$setIntersection: [user.friends, '$friends']
}
}
}
},
{
$match: {
$and: [
{
_id: { $ne: user._id }
},
{
$or: [
{
fullName: { $regex: req.query.keyWord, $options: 'i'}
},
{
email: { $regex: req.query.keyWord, $options: 'i'}
}
]
},
{
friends: { $ne: user._id }
},
{
receivedFriendRequests: { $ne: user._id }
},
{
sentFriendRequests: { $ne: user._id }
}
]
}
},
{ $sort: { mutualFriends: -1} }
])
.exec(function (err, result) {
if (err) {
logger.error('Friend 500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
return res.json({
data: result
})
})
})
Schema Definition
var UserSchema = new Schema({
firstName: {
type: String
},
frstNameNormalized: {
type: String
},
lastName: {
type: String
},
lastNameNormalized: {
type: String
},
email: {
type: String,
// unique: true,
// required: true,
lowercase: true,
// match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please enter a valid email address']
},
birthDate: {
type: Date
},
facebookID: {
type: String
},
twitterID: {
type: String
},
password: {
type: String
},
nickname: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
avatarPath: {
type: String,
default: ''
},
friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
receivedFriendRequests: [{ type: Schema.Types.ObjectId, ref: 'User' }],
sentFriendRequests: [{ type: Schema.Types.ObjectId, ref: 'User' }],
sharedFriendCheckIns: [{ type: Schema.Types.ObjectId, ref: 'User' }],
resetPasswordToken: String,
resetPasswordExpires: Date,
emailConfirmToken: String,
emailConfirmExpires: Date,
emailConfirmed: {
type: Boolean,
default: false
}
})
....
var User = module.exports = mongoose.model('User', UserSchema)
A full model description can be found on the following link. MongoDB version is v3.2.8
In case that someone needs it, I have resolved this problem. Since I am a beginner the solution is trivial and probably not optimal but it does what it should to.
FriendController.route('/find')
.get(passport.authenticate('jwt', { session: false }), function (req, res) {
// get users who are not firends of authorized user nor he/she send to them friend request or received it
var parts = (req.query.keyWord).split(' ')
var firstName = ''
var lastName = ''
if (parts.length > 1) {
firstName = (req.query.keyWord).split(' ').slice(0, -1).join(' ')
lastName = (req.query.keyWord).split(' ').slice(-1).join(' ')
} else {
firstName = req.query.keyWord
lastName = req.query.keyWord
}
User.findOne({_id: req.user.id})
.exec(function (err, user) {
if (err) {
logger.error('Friend 500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
User.find({
_id: { $ne: user.id },
friends: { $ne: user.id },
receivedFriendRequests: { $ne: user.id },
sentFriendRequests: { $ne: user.id },
$or: [
{
firstName: { $regex: firstName, $options: 'i'}
},
{
lastName: { $regex: lastName, $options: 'i'}
},
{
email: req.query.keyWord
}
]
})
.select('_id')
.exec(function (err, potentialFriends) {
if (err) {
logger.error('Friend 500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
var ids = potentialFriends.map(function (potentialFriend) {
return potentialFriend._id
})
User.aggregate([
{
$project: {
firstName: 1,
lastName: 1,
email: 1,
avatarPath: 1,
mutualFriends: {
$size: {
$setIntersection: [user.friends, '$friends']
}
}
}
},
{
$match: {
_id: { $in: ids }
}
},
{ $sort: { mutualFriends: -1} }
])
.exec(function (err, result) {
if (err) {
logger.error('Friend 500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
return res.json({
data: result
})
})
})
})
})

Resources