Sequelize User M2M Group - Find Users that are in Groups - node.js

In my project, I have Users and Groups, and a User can have multiple Groups. I'm having trouble filtering a list of users based on those who are in a certain group.
Here's my setup:
User Model
module.exports = (sequelize) => {
const model = sequelize.define('users',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
field: 'uID',
},
username: {
type: Sequelize.STRING, allowNull: false, unique: true, field: 'uEmail',
},
}, {
timestamps: false,
tableName: 'Users',
});
model.modelName = 'users';
return model;
};
Group Model
module.exports = (sequelize) => {
const model = sequelize.define('groups', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
field: 'gID',
},
name: { type: Sequelize.STRING, allowNull: false, field: 'gName' },
description: { type: Sequelize.STRING, allowNull: false, field: 'gDescription' },
}, {
timestamps: false,
tableName: 'Groups',
});
model.modelName = 'groups';
return model;
};
M2M Through Table :: UserGroups
module.exports = (sequelize) => {
const model = sequelize.define('usergroups', {
}, {
timestamps: false,
tableName: 'UserGroups',
});
model.modelName = 'usergroups';
return model;
};
Association
User.belongsToMany(Group, {
through: UserGroup,
as: 'groups',
foreignKey: 'uID',
});
Group.belongsToMany(User, {
through: UserGroup,
as: 'users',
foreignKey: 'gID',
});
My db Users look like this (in shorthand):
User1.groups = [1,2,3]
User2.groups = [1,3]
User3.groups = [2,3]
I'm building the search rest API: GET /users?groups[]=1&groups[]=2
In building the query, I've tried to filter based on the groups passed in using this format:
let query = {};
query.include = [
{
model: Group,
as: 'groups',
where: {id: {[Op.in]: filter.groups}},
attributes: ['id', 'name'],
},
];
const result = yield User.findAndCountAll(query);
or tried using through (with required: true or required: false):
let query = {};
query.include = [
{
model: Group,
as: 'groups',
through: { where: {'gID': {[Op.in]: filter.groups}}, required: true},
attributes: ['id', 'name'],
},
];
const result = yield User.findAndCountAll(query);
It kinda works, except the returned objects associated groups are abbreviated. If I pass in this filter:
GET /users?groups[]=1
The response comes back with the right users, but the groups associated with those users are filtered:
[
{
"id": 1,
"username": "user1#site.com",
"groups": [
{
"id": 1,
"name": "My Group",
"usergroups": {
"uID": 1,
"gID": 1
}
}
]
},
{
"id": 2,
"username": "user2#site.com",
"groups": [
{
"id": 1,
"name": "My Group",
"usergroups": {
"uID": 2,
"gID": 1
}
}
]
}
]
But where are groups 2 and 3 for User1, and group 2 for User2?
It's almost like I need to get all the user ids in a specific group (through the Group service class) then do:
query.where = {id: {[Op.in]: userIdsArray}}

Related

Count in Many-to-Many relationship

I want to count the number of likes when I fetch the post details.
User Model
User.init(
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
field: 'user_name',
},
}
);
static associate(models) {
// User has many posts --> One-to-Many Relationship
this.hasMany(models.Post, {
foreignKey: 'userId',
});
// Likes relationship
this.belongsToMany(models.Post, {
through: 'PostLikes',
foreignKey: 'userId',
as: 'likes',
});
}
And here is the Post Model
Post.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
body: {
type: DataTypes.STRING,
allowNull: false,
},
}
);
static associate(models) {
// User-Post Relationship
this.belongsTo(models.User, {
foreignKey: 'userId',
});
// Likes Relationship
this.belongsToMany(models.User, {
through: 'PostLikes',
foreignKey: 'postId',
as: 'likes',
});
}
So, it now creates a joined table PostLikes and I am trying query that fetches the post along with the likes and number of likes on that post.
const postsIdCol = '"likes->PostLikes"."postId"';
const usersIdCol = '"likes->PostLikes"."userId"';
const postCol = '"Post"."id"';
const response = await Post.findOne({
where: {
id: postid,
},
// includeIgnoreAttributes : false,
attributes: [
'id', 'body', 'createdAt',
[sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol})`), 'likesCount'],
],
include: [
{
model: Comment, ----> Post is also associated with comments, ignore this
as: 'comments',
attributes: ['id', 'content', 'createdAt', 'updatedAt'],
},
{
model: User,
as: 'likes',
through: {
attributes: [],
},
attributes: ['id', 'userName'],
},
],
});
return response;
The Response I am getting on doing this query is like this :
{
"data": {
"id": "182d6377-5bf6-4b65-9e29-cb79acc85c0a",
"body": "hoping for the best",
"likesCount": "6", -----> this should be 3
"likes": [
{
"id": "1af4b9ea-7c58-486f-a37a-e46461487b06",
"userName": "sdfbsd",
},
{
"id": "484202b0-a6d9-416d-a8e2-6681deffa3d1",
"userName": "ndnadonfsu",
},
{
"id": "b3c70bee-e839-4449-b213-62813af031d1",
"userName": "difniandca",
}
]
}
}
You need another PARTITION BY column from the other association than the one that you want to count.
For example, if you want to count the likes, you need to partition by parent id (Post.id) and other association id (Comment.id).
If you want to count the comments, you need to partition by parent id (Post.id) and other association id ("likes->PostLikes"."UserId").
[Sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol}, "Comments"."id")`), 'likesCount']
Where it says "Comments", you need to add your comment table name.

Return only desired columns from Sequelize.js with join

I am switching from using node-mssql to Sequelize in order to access my database in a simpler fashion. As a newbie to sequelize I am stumbling my way through pulling the correct data.
As I am converting a .net site with .net authentication to a node site I am using the existing authentication database. Currently I am trying to pull all roles for an existing user.
Here is the code I have so far. It returns both userID and roleID along with the username and role name that I desire. How can I remove these 2 ID columns from my query results?
test.aspnet_Users.findAll({
logging: console.log,
where: { LoweredUserName: `mcad2\\${user}` },
attributes: ['LoweredUserName'],
include: {
model: test.aspnet_Roles,
as: 'RoleId_aspnet_Roles',
attributes: ['LoweredRoleName']
}
}).then(user => {
console.log('\n\n' + JSON.stringify(user))
})
The database is set up so that both userID and roleID are contained in a third table, aspnet_UsersInRoles. This is a relatively simple 2 join query but I am not certain how to make it work using sequelize.
Here is that sequelize-auto code which was created:
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('aspnet_Roles', {
ApplicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'aspnet_Applications',
key: 'ApplicationId'
}
},
RoleId: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
RoleName: {
type: DataTypes.STRING(256),
allowNull: false
},
LoweredRoleName: {
type: DataTypes.STRING(256),
allowNull: false
},
Description: {
type: DataTypes.STRING(256),
allowNull: true
}
}, {
sequelize,
tableName: 'aspnet_Roles',
schema: 'dbo',
timestamps: false,
indexes: [
{
name: "aspnet_Roles_index1",
unique: true,
fields: [
{ name: "ApplicationId" },
{ name: "LoweredRoleName" },
]
},
{
name: "PK__aspnet_Roles__31EC6D26",
unique: true,
fields: [
{ name: "RoleId" },
]
},
]
});
};
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('aspnet_Users', {
ApplicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'aspnet_Applications',
key: 'ApplicationId'
}
},
UserId: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
UserName: {
type: DataTypes.STRING(256),
allowNull: false
},
LoweredUserName: {
type: DataTypes.STRING(256),
allowNull: false
},
MobileAlias: {
type: DataTypes.STRING(16),
allowNull: true
},
IsAnonymous: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
LastActivityDate: {
type: DataTypes.DATE,
allowNull: false
}
}, {
sequelize,
tableName: 'aspnet_Users',
schema: 'dbo',
timestamps: false,
indexes: [
{
name: "aspnet_Users_Index",
unique: true,
fields: [
{ name: "ApplicationId" },
{ name: "LoweredUserName" },
]
},
{
name: "aspnet_Users_Index2",
fields: [
{ name: "ApplicationId" },
{ name: "LastActivityDate" },
]
},
{
name: "PK__aspnet_Users__03317E3D",
unique: true,
fields: [
{ name: "UserId" },
]
},
]
});
};
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('aspnet_UsersInRoles', {
UserId: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'aspnet_Users',
key: 'UserId'
}
},
RoleId: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'aspnet_Roles',
key: 'RoleId'
}
}
}, {
sequelize,
tableName: 'aspnet_UsersInRoles',
schema: 'dbo',
timestamps: false,
indexes: [
{
name: "aspnet_UsersInRoles_index",
fields: [
{ name: "RoleId" },
]
},
{
name: "PK__aspnet_UsersInRo__35BCFE0A",
unique: true,
fields: [
{ name: "UserId" },
{ name: "RoleId" },
]
},
]
});
};
var DataTypes = require("sequelize").DataTypes;
var _aspnet_Applications = require("./aspnet_Applications");
var _aspnet_Membership = require("./aspnet_Membership");
var _aspnet_Paths = require("./aspnet_Paths");
var _aspnet_PersonalizationAllUsers = require("./aspnet_PersonalizationAllUsers");
var _aspnet_PersonalizationPerUser = require("./aspnet_PersonalizationPerUser");
var _aspnet_Profile = require("./aspnet_Profile");
var _aspnet_Roles = require("./aspnet_Roles");
var _aspnet_SchemaVersions = require("./aspnet_SchemaVersions");
var _aspnet_Users = require("./aspnet_Users");
var _aspnet_UsersInRoles = require("./aspnet_UsersInRoles");
var _aspnet_WebEvent_Events = require("./aspnet_WebEvent_Events");
var _aspnet_ZoneNumbers = require("./aspnet_ZoneNumbers");
var _aspnet_ZonePositions = require("./aspnet_ZonePositions");
function initModels(sequelize) {
var aspnet_Applications = _aspnet_Applications(sequelize, DataTypes);
var aspnet_Membership = _aspnet_Membership(sequelize, DataTypes);
var aspnet_Paths = _aspnet_Paths(sequelize, DataTypes);
var aspnet_PersonalizationAllUsers = _aspnet_PersonalizationAllUsers(sequelize, DataTypes);
var aspnet_PersonalizationPerUser = _aspnet_PersonalizationPerUser(sequelize, DataTypes);
var aspnet_Profile = _aspnet_Profile(sequelize, DataTypes);
var aspnet_Roles = _aspnet_Roles(sequelize, DataTypes);
var aspnet_SchemaVersions = _aspnet_SchemaVersions(sequelize, DataTypes);
var aspnet_Users = _aspnet_Users(sequelize, DataTypes);
var aspnet_UsersInRoles = _aspnet_UsersInRoles(sequelize, DataTypes);
var aspnet_WebEvent_Events = _aspnet_WebEvent_Events(sequelize, DataTypes);
var aspnet_ZoneNumbers = _aspnet_ZoneNumbers(sequelize, DataTypes);
var aspnet_ZonePositions = _aspnet_ZonePositions(sequelize, DataTypes);
aspnet_Roles.belongsToMany(aspnet_Users, { as: 'UserId_aspnet_Users', through: aspnet_UsersInRoles, foreignKey: "RoleId", otherKey: "UserId" });
aspnet_Users.belongsToMany(aspnet_Roles, { as: 'RoleId_aspnet_Roles', through: aspnet_UsersInRoles, foreignKey: "UserId", otherKey: "RoleId" });
aspnet_Membership.belongsTo(aspnet_Applications, { as: "Application", foreignKey: "ApplicationId"});
aspnet_Applications.hasMany(aspnet_Membership, { as: "aspnet_Memberships", foreignKey: "ApplicationId"});
aspnet_Paths.belongsTo(aspnet_Applications, { as: "Application", foreignKey: "ApplicationId"});
aspnet_Applications.hasMany(aspnet_Paths, { as: "aspnet_Paths", foreignKey: "ApplicationId"});
aspnet_Roles.belongsTo(aspnet_Applications, { as: "Application", foreignKey: "ApplicationId"});
aspnet_Applications.hasMany(aspnet_Roles, { as: "aspnet_Roles", foreignKey: "ApplicationId"});
aspnet_Users.belongsTo(aspnet_Applications, { as: "Application", foreignKey: "ApplicationId"});
aspnet_Applications.hasMany(aspnet_Users, { as: "aspnet_Users", foreignKey: "ApplicationId"});
aspnet_PersonalizationAllUsers.belongsTo(aspnet_Paths, { as: "Path", foreignKey: "PathId"});
aspnet_Paths.hasOne(aspnet_PersonalizationAllUsers, { as: "aspnet_PersonalizationAllUser", foreignKey: "PathId"});
aspnet_PersonalizationPerUser.belongsTo(aspnet_Paths, { as: "Path", foreignKey: "PathId"});
aspnet_Paths.hasMany(aspnet_PersonalizationPerUser, { as: "aspnet_PersonalizationPerUsers", foreignKey: "PathId"});
aspnet_UsersInRoles.belongsTo(aspnet_Roles, { as: "Role", foreignKey: "RoleId"});
aspnet_Roles.hasMany(aspnet_UsersInRoles, { as: "aspnet_UsersInRoles", foreignKey: "RoleId"});
aspnet_Membership.belongsTo(aspnet_Users, { as: "User", foreignKey: "UserId"});
aspnet_Users.hasOne(aspnet_Membership, { as: "aspnet_Membership", foreignKey: "UserId"});
aspnet_PersonalizationPerUser.belongsTo(aspnet_Users, { as: "User", foreignKey: "UserId"});
aspnet_Users.hasMany(aspnet_PersonalizationPerUser, { as: "aspnet_PersonalizationPerUsers", foreignKey: "UserId"});
aspnet_Profile.belongsTo(aspnet_Users, { as: "User", foreignKey: "UserId"});
aspnet_Users.hasOne(aspnet_Profile, { as: "aspnet_Profile", foreignKey: "UserId"});
aspnet_UsersInRoles.belongsTo(aspnet_Users, { as: "User", foreignKey: "UserId"});
aspnet_Users.hasMany(aspnet_UsersInRoles, { as: "aspnet_UsersInRoles", foreignKey: "UserId"});
return {
aspnet_Applications,
aspnet_Membership,
aspnet_Paths,
aspnet_PersonalizationAllUsers,
aspnet_PersonalizationPerUser,
aspnet_Profile,
aspnet_Roles,
aspnet_SchemaVersions,
aspnet_Users,
aspnet_UsersInRoles,
aspnet_WebEvent_Events,
aspnet_ZoneNumbers,
aspnet_ZonePositions,
};
}
module.exports = initModels;
module.exports.initModels = initModels;
module.exports.default = initModels;
You can add an "exclude" option in the attribute option of the query and exclude the given columns:
test.aspnet_Users.findAll({
logging: console.log,
where: { LoweredUserName: `mcad2\\${user}` },
attributes: { exclude: ['userID'], include: ['LoweredUserName'] },
include: {
model: test.aspnet_Roles,
as: 'RoleId_aspnet_Roles',
attributes: { exclude: ['roleID'], include: ['LoweredRoleName'] },
}
}).then(user => {
console.log('\n\n' + JSON.stringify(user))
})
If I understood your structure correctly this should exclude userID and roleID in the fetched data.

Sequelize fetch include data based on alias where condition

I have two tables Employee and Department
Department
const Department = Sequelize.define(
"Department",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
modelName: "Department",
tableName: "departments",
},
);
Department.associate = function (models) {
// associations can be defined here
models.Department.hasMany(models.Employee, {
foreignKey: "department_id",
as: "employees",
});
};
return Department;
};
Employee
const Employee = Sequelize.define(
"Employee",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
status: {
type: DataTypes.STRING,
defaultValue: "active",
},
departmentId: {
type: DataTypes.INTEGER,
},
},
{
underscored: true,
timestamps: true,
modelName: "Employee",
tableName: "employees",
},
);
Employee.associate = function (models) {
models.Employee.belongsTo(models.Department, {
foreignKey: "department_id",
as: "department",
});
};
return Employee;
};
Now I have to fetch the list of employees and putting a filter of department_id = 1
const { departmentId } = req.body;
const employees = await Employee.findAll({
include: [
{
model: Department,
where: {
id: departmentId,
},
},
],
});
I am getting the issue. Department is mapped by association "departments"
Cannot fetch the data.
I found the answer on sequelize docs
const employees = await Employee.findAll({
include: [
{
association: "department", // this is the place to change
where: {
id: departmentId,
},
},
],
});
Learnings:
We will not be able to put association and model together.
We will be able to use the Model if no association is there.
We will be able to use association if there is one.
References: https://sequelize.org/master/manual/eager-loading.html#:~:text=You%20can%20also%20include%20by%20alias%20name%20by%20specifying%20a%20string%20that%20matches%20the%20association%20alias

findAll sequelize with model returning odd values

Solved thanks to : https://stackoverflow.com/a/64702949/14585149
I am looking to list all transactions I have on a mysql database with the associated users.
I have 2 tables / models : Transaction & User
Transaction :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('transactions', {
transaction_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
user_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'users',
key: 'user_id'
}
},
account_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'accounts',
key: 'account_id'
}
},
}, { tableName: 'transactions' }); };
User :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('users', {
user_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: 'recipients',
key: 'created_by'
}
},
contact_id: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'contacts',
key: 'contact_id'
}
}, }, {
tableName: 'users',
timestamps: false
});
};
I have made the associations :
Transaction.hasOne(User, {foreignKey:'user_id'});
User.belongsTo(Transaction, {foreignKey:'user_id'});
and my code is :
api.get('/trx', async (req, res) => {
Transaction.findAll({
attributes: ['transaction_id','user_id','account_id'],
include: [{
model: User,
attributes: ['user_id']}]
})
.then(intrx => res.json(intrx))
.catch(res.catch_error)
});
the result :
[
{
"transaction_id": 1,
"user_id": 4,
"account_id": 1,
"user": {
"user_id": 1
}
},
{
"transaction_id": 2,
"user_id": 4,
"account_id": 75,
"user": {
"user_id": 2
}
}
]
why the values of user_id are different ?
I am expecting the user_id = 4 instead of 1 in my first result and user_id = 4 in my second result.
what I am doing wrong ?
You should reverse your associations like this:
Transaction.belongsTo(User, {foreignKey:'user_id'});
User.hasOne(Transaction, {foreignKey:'user_id'});
because Transaction has link to User i.e. belongs to it.

Sequelize: Bind multiple models for join query and create custom columns

I'd like to apply a join and groupBy in Sequelize v5 that will fetch the records from five models tables and return the records as below format.
{
"data": [
{
"products": {
"id": "01",
"name": "lorium",
"description": "ipsum",
"product_images": [
{
"url": "", // From images tbl
"image_type": "Front" // From imge_types tbl
},
{
"url": "",
"image_type": "Back"
}
]
},
"vendor": {
"first_name": "john",
"last_name": "doe"
}
}
]
}
I have created separate all five models and assign association to them.
Product Model::
const Product = SQLize.define('product', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, }
product_title: { type: new DataTypes.STRING(255) },
vendor_id: { type: DataTypes.INTEGER.UNSIGNED }
});
Product.hasMany(ProductImages, {foreignKey: 'product_id', targetKey: 'id', as :'product_img_refs'})
export { Product };
Vendor Model::
const Vendor = SQLize.define('vendor', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
first_name: { type: DataTypes.STRING(100) },
last_name: { type: DataTypes.STRING(100) }
});
Product.belongsTo(Vendor, {foreignKey: 'id'})
Vendor.hasOne(Product, {foreignKey: 'id'})
export { Vendor }
Product Images Model::
const ProductImages = SQLize.define('product_images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
product_id: { type: DataTypes.INTEGER },
product_image_id: { type: DataTypes.INTEGER }
img_type_id: { type: DataTypes.INTEGER }
});
export {ProductImages}
Images Model::
const ImagesModel = SQLize.define('images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
img_url: { type: DataTypes.STRING }
});
export { ImagesModel }
Image Types Model::
const ImageTypes = SQLize.define('image_types', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
image_type: { type: DataTypes.STRING }
});
export { ImageTypes }
Below is the repository file on which i have performed the SQLize operation: Updated::
public async getProductData() {
var prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages, as: 'product_img_refs' }
]
});
return prodData;
}
I am not getting the correct way to bind the all models that will return me a result as described in the above json format.
To get the nested output as shown in the question, you would need to create associations between the following:
ProductImages and ImagesModel
ProductImages and ImageTypes
Once done, you can nest the models in the findAll options as shown below:
// Create associations (depends on your data, may be different)
ProductImages.hasOne(ImagesModel);
ProductImages.hasOne(ImageTypes);
// Return product data with nested models
let prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{
model: ProductImages, as: 'product_img_refs',
include: [
{ model: ImagesModel }, // Join ImagesModel onto ProductImages (add aliases as needed)
{ model: ImageTypes } // Join ImageTypes onto ProductImages (add aliases as needed)
]
}
]
});
I found the issue. you were trying to include ProductImages model into Vendor. As per your association, ProductImages associate with Product not with Vendor.
So please try this
let prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages }
]
});

Resources