Select parent model with child model attribute and destroy it - sequelize/MySQL - mysql

I have User and VerifyToken model with token and user_id attributes.
I need to select an user, but with token (from tokens table) value.
I have tried:
await models.User.destroy({
hierarchy: true,
where: {
token: token
}
});
This is User model:
const VerifyToken = require('./verifyToken');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
password: DataTypes.STRING,
email: DataTypes.STRING,
}, {
tableName: 'users',
//syncOnAssociation: true
hierarchy: true
});
User.associate = function (models) {
const {VerifyToken} = models;
User.hasOne(VerifyToken, {
onDelete: 'CASCADE'
});
};
return User;
};
And VerifyToken model:
const User = require('./user');
module.exports = (sequelize, DataTypes) => {
const VerifyToken = sequelize.define('VerifyToken', {
user_id: DataTypes.INTEGER,
token: DataTypes.STRING,
}, {
tableName: 'verify_tokens',
syncOnAssociation: true,
hierarchy: true
});
VerifyToken.associations = function (models) {
const {User} = models;
VerifyToken.belongsTo(User);
};
return VerifyToken;
};
The problem is that, I even don't know where to start. I have tried with include:[{model: models.VerifyToken, where: {}}], but how to use user_id called from the child model ?
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.

The problem statement you want is to support join and delete in one sequelize operation.
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.
In sequelize documenation, Model.destroy has no include in the options property.
So the only left option is to select the user_id's from VerifyToken model, then call destroy on User model, where id in user_id got from VerifyToken.
In code it will look like following
const verifyTokens = await VerifyToken.findAll({
where: {
token: {
[Sequelize.Op.In] : YOUR_TOKENS_FOR_WHICH_YOU_WANT_TO_DELETE_YOUR_USER
}
}
}
const userIdsToDestroy = verifyTokens.map(verifyToken => verifyToken.user_id)
await User.destroy({
where: {
id: {
[Sequelize.Op.in] : userIdsToDestroy
}
}
}

Related

Sequelize belongs to many get and post request

I have belongs to many associations of the model Technology and Project. At the same time, on the client, I have two tables, free technologies and technologies on the project. Please tell me (or suggest how to do it) how to make a get request for all free technologies and a post request to add them to the table on the project. I figured out all the associations, but stopped at this one. I will be grateful for any help.
models/Project.js
const {
Model
} = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Project extends Model {
static associate(models) {
Project.hasMany(models.Role, { foreignKey: "projectId", as: "roles" });
Project.belongsToMany(models.Technology, { foreignKey: "projectId", through: "ProjectsTechnologies"});
}
};
Project.init({
title: DataTypes.STRING,
description: DataTypes.STRING,
image: DataTypes.STRING
}, {
sequelize,
modelName: "Project",
});
return Project;
};
models/Technology.js
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Technology extends Model {
static associate(models) {
Technology.belongsToMany(models.Project, { foreignKey: "technologyId", through: "ProjectsTechnologies"});
}
}
Technology.init({
name: DataTypes.STRING
}, {
sequelize,
modelName: 'Technology',
});
return Technology;
};
*models/ProjectsTechs
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class ProjectsTechnologies extends Model {
static associate(models) {
ProjectsTechnologies.belongsTo(models.Project, {foreignKey: "projectId"});
ProjectsTechnologies.belongsTo(models.Technology, {foreignKey: "technologyId"});
}
};
ProjectsTechnologies.init({
projectId: DataTypes.STRING,
technologyId: DataTypes.STRING
}, {
sequelize,
modelName: 'ProjectsTechnologies',
});
return ProjectsTechnologies;
};
Currently I'm doing this get request for get all free technologies
router.get("/techologies", async (req, res) => {
const listOfTech = await Technology.findAll({
include: [
{
model: Project,
as: "projects",
through: {
model: ProjectsTechnologies
}
}
]
});
res.json(listOfTech);
});
And post req for posting technologies in "On project" table
router.post("/create/:id", async (req, res) => {
const technology = await ProjectsTechnologies.create(req.body);
const project = await Project.findOne({
where: {
id: req.params.id
}
});
res.json(technology);
});
I also keep trying to do it differently, because these options don't seem right to me.
To query a list of technologies that are not used in any of projects you need to use a subquery in where option:
const listOfTech = await Technology.findAll({
where: Sequelize.literal('NOT EXISTS (SELECT 1 FROM `ProjectsTechnologies` where `ProjectsTechnologies`.`technologyId`=`Technology`.`id`)')
});

Nodejs Sequelize and passport Js, stuck on executing query when registing user

this is the problem I am having:
I have 3 models(users,favorites,cryptocoins)
'use strict';
const { Model} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Cryptocoin 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(models) {
// define association here
}
}
Cryptocoin.init({
coinId:{
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
coinName: DataTypes.STRING,
coinPrice: DataTypes.INTEGER,
coinAmount: DataTypes.INTEGER,
totalValue: DataTypes.STRING,
boughtOn: DataTypes.DATE
}, {
sequelize,
modelName: 'Cryptocoin',
});
return Cryptocoin;
};
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Favorite 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(models) {
// define association here
}
}
Favorite.init({
favoriteId: {
type:DataTypes.INTEGER,
primaryKey:true,
autoIncrement:true,
},
userId:{
type:DataTypes.INTEGER,
references:{
model:'Users',
key:'userId',
},
onDelete:'CASCADE',
},
coinId:{
type:DataTypes.INTEGER,
references:{
model:'Cryptocoins',
key:'coinId',
},
onDelete:'CASCADE',
},
}, {
sequelize,
modelName: 'Favorite',
});
return Favorite;
};
'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(models) {
}
}
User.init({
userId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
userName: DataTypes.STRING,
passWord: DataTypes.STRING
}, {
sequelize,
modelName: 'User',
});
return User;
};
These all have migrations which work.
Now I have passport.js file which holds the passport stategies
const bcrypt = require('bcrypt');
module.exports = function (passport, Auth) {
const LocalStrategy = require('passport-local').Strategy;
passport.use('local-signup', new LocalStrategy(
{
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
}, function (req, username, password, done) {
console.log("Signup for - ", username)
const generateHash = function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
}
Auth.findOne({
where: {
userName: username
}
}).then(function (user) {
//console.log(user);
if (user) {
return done(null, false, {
message: 'That username is already taken'
});
} else {
const userPassword = generateHash(password);
const data = {
username: username,
password: userPassword,
};
Auth.create(data).then(function (newUser, created) {
if (!newUser)return done(null, false);
if (newUser) return done(null, newUser)
});
}
});
}
));
This file is called via app.js like so
just a snippet of the code in app.js
const models = require('./models');
app.use(session({
genid: (req) => {
return uuid.v1();
},
name: 'Crypto-session',
store:new fileStore(),
secret: '------',
resave:false,
saveUninitialized:false,
}))
app.use(passport.initialize());
app.use(passport.session());
require('./config/passport')(passport,models.User);
Now when going to my /register route and inputing a username and password this is what happens
Signup for - randomUser
Executing (default): SELECT `userId`, `userName`, `passWord`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`userName` = 'randomUser' LIMIT 1;
Executing (default): INSERT INTO `Users` (`userId`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?);
And it's stuck at the executing (default) part, It gets uploaded to the database but without any username or password, console logging both of them shows they are being populated with values from my form.
Any idea how I can fix this issue ?
note: I am fairly new to express and nodeJs in general.
Answer: After brainstorming and reading docs a bit these where the issues.
Ensure the deserializeUser function is either await or then based e.g
// deserialize user
passport.deserializeUser(function (id, done) {
Auth.findByPk(id).then(function (user) {
if (user) {
done(null, user.get());
} else {
done(user.errors, null);
}
});
});
Ensure you have a redirect in you route.post method e.g
// route for register action
app.post('/register',passport.authenticate('local-signup',{ successRedirect: '/',
failureRedirect: '/register' }),function (req, res) {
});
Ensure all of your passport js fields match the database fields e.g
I had username and password, but in my database it is userName and passWord.
changing these like so
const data = {
userName: username,
passWord: userPassword,
};
Uploads everything to the database.

Sequelize ordering bug in multi join

I have Users table, that related to many Stations and each user has relation with Info table, that contains fields firstName and lastName. I grab it like this:
const joinUserInfo: any = {
model: userInfoModel,
where: { deletedAt: null },
required: true,
};
const joinStations = {
model: stationModel,
required: true,
};
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
});
Than I have a method to build order. It looks like this:
private createAdminsListOrdering(query) {
const validOrderingItems = [];
if (query.fullName) {
validOrderingItems.push(['info', 'lastName', query.fullName]);
validOrderingItems.push(['info', 'firstName', query.fullName]);
}
if (query.email) {
validOrderingItems.push(['email', query.email]);
}
if (query.station) {
validOrderingItems.push(['stations', 'name', query.station]);
}
return validOrderingItems.push(['createdAt', 'DESC']);
}
In cases with email or stations all works correct. But with info it say that didn't see and info column. When I remove stations from join like this:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo],
paranoid: false,
order,
});
My ordering by firstName and lastName working correct. What i do wrong? Thank you.
Users related with Stations as many-to-many:
#BelongsToMany(
() => stationModel,
() => userStationModel,
)
stations?: stationModel[];
The problem was in pagination... I use custom function to create pagination:
export const paginatedQuery = ({ page = 0, pageSize = 0 }) => {
if (!pageSize) {
return {};
}
const limit = pageSize;
const offset = page * pageSize;
return { offset, limit };
};
And full my request was:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
});
And, when I debug request with logging i notice, that my Info table is in subquery. Then I began to googling about subqueries in sequelize and found flag subQuery, that force to not use subqueries. After this my request works fine:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
subQuery: false,
});
Hope it will helps someone.

How to query many-to-many relationship data in Sequelize

I have got a many-to-many relationship between two models, users and groups.
I have two models which are specifying the belongsToMany with a foreignKey and through attribute.
Users model
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1'});
User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
groups model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
name: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, {through: 'UsersGroups', foreignKey: 'groupId'});
Group.hasMany(models.Payment)
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
which are being joined via a junction table UsersGroups
This works fine and I can create a new group and it links the user with it successfully but when I try fetch the data the SQL query is trying to find Groups based on User.id as opposed to User.facebookId like I specified in my model User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
I call the following code to fetch the data:
const options = {
where: {
facebookId: facebookId,
},
defaults: {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
facebookId: facebookId
},
include: [
{ model: db.Group }
]
}
db.User.findOrCreate(options)
.then((user) => {
res.send(user)
}, (err) => {
res.status(500).send(err)
})
and it returns a user but with an empty Groups array which is incorrect as there is definitely data there as I can create it fine and see it in the DB.
You can see the SQL query that is generated by Sequelize here:
SELECT `User`.*, `Groups`.`id` AS `Groups.id`, `Groups`.`name` AS `Groups.name`, `Groups`.`createdAt` AS `Groups.createdAt`, `Groups`.`updatedAt` AS `Groups.updatedAt`, `Groups.UsersGroups`.`createdAt` AS `Groups.UsersGroups.createdAt`, `Groups.UsersGroups`.`updatedAt` AS `Groups.UsersGroups.updatedAt`, `Groups.UsersGroups`.`facebookId` AS `Groups.UsersGroups.facebookId`, `Groups.UsersGroups`.`groupId` AS `Groups.UsersGroups.groupId`
FROM (
SELECT `User`.`id`, `User`.`firstName`, `User`.`lastName`, `User`.`email`, `User`.`username`, `User`.`facebookId`, `User`.`password`, `User`.`createdAt`, `User`.`updatedAt` FROM `Users` AS `User` WHERE `User`.`facebookId` = '1341052992643877' LIMIT 1) AS `User`
LEFT OUTER JOIN (`UsersGroups` AS `Groups.UsersGroups`
INNER JOIN `Groups` AS `Groups` ON `Groups`.`id` = `Groups.UsersGroups`.`groupId`
)
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`;
Note the last line
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`
needs to be
ON `User`.`facebookId` = `Groups.UsersGroups`.`facebookId`
Turns out I had to specify facebookId as a primary key in my user model
facebookId: {
type: DataTypes.STRING,
primaryKey: true,
},

Creating row in both source and association with Sequelize many to many model

I have a many to many relationship between a user and a group. A user has many groups and a group has many users.
I have a users table, a groups table and junction table called usersGroups.
My User model:
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1', allowNull: false});
User.belongsToMany(models.Group, { as: 'Groups', through: 'usersGroups', foreignKey: 'userId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
My group model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, { as: 'Users', through: 'usersGroups', foreignKey: 'groupId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
When I try create a new group with associated user with the following 2 methods, it creates a new group but no association
const values = {
userId: 1
}
const options = {
include: db.Users
}
db.Group
.create(values, options)
.then( (group) => {
res.send(group)
}).catch(err => {
res.status(500).send({err: err})
})
or
db.Group
.create()
.then( (group) => {
group.addUser({ userId: 1 }).then(result => {
res.send(result)
}).catch(err => {
res.status(500).send({err: err})
})
}).catch(err => {
res.status(500).send({err: err})
})
If you simply want to assign user to newly created group, you need to use addUser, just like you did, but this method accepts first parameter as instance of Model or ID of instance, just like the documentation says
An instance or primary key of instance to associate with this.
So you would have to perform group.addUser(userId).then(...) which in your case would be group.addUser(1).then(...).