Is this possible to update multiple data with sequlize? - node.js

I have created add product API like this. This is working fine. I'm posting successfully varient data by sharing product id as a foreign key, but I'm confused about how can I update product data. Can I update data by using this code?
try {
const { name, description, photo, tag, productId ,lableType,reccomendedProduct, varientDetails, isTex,GSTrate,GSTtyp, HSNcode, categoryId, subCategoryId, videoUpload,} = req.body;
const data= db.product.findOne({ where: { id: productId },
include: [{ model: db.tagModel, attributes: ["id","name","productId"]}, { model: db.reccomendProduct, attributes: ["id", "productName","productId"]},
{ model: db.varientModel, attributes: ["id", "sort","sku","productId","waightunitno","unit","mrp","discount","price","stock","minstock","outofstock"]}]
}).then(product => {
if (product) {
db.product.update({
categoryId: categoryId ? categoryId : product.categoryId,
subCategoryId: subCategoryId ? subCategoryId : product.subCategoryId,
name:name,
description:description,
lableType:lableType,
isTex:isTex,
// photo:req.file ? req.file.location:'',
photo:photo,
GSTrate:GSTrate,
GSTtyp:GSTtyp,
HSNcode:HSNcode,
categoryId:categoryId,
subCategoryId:subCategoryId,
videoUpload:videoUpload }, { where: { id: product.id }
})
}
if(varientDetails ) {
db.varientModel.findAll ({ where: { productId:productId }})
.then(varient => {
console.log(varient+data)
for (let i=0; i < varientDetails.length; i++) {
db.varientModel.update({
productId:productId,
sort:varientDetails[i].sort,
sku: varientDetails[i].sku,
waightunitno:varientDetails[i].waightunitno,
unit:varientDetails[i].unit,
mrp:varientDetails[i].mrp,
discount: varientDetails[i].discount,
price: varientDetails[i].price,
stock: varientDetails[i].stack,
minstock: varientDetails[i].minstock,
outofstock: varientDetails[i].outofstock
}, { where: { productId:productId[i] }
})
}
})
}

Yes, there are ways to do it.
I don't find them as expressive and as clear as multiple one.
1. Creating Query on own
You can create function like this
function updateUsers(updateFirstValue, updateSecondValue, productIds) {
let query = "";
for (let index = 0; index < productIds.length; index++) {
query += `update tableName set firstUpdateValue="${updateFirstValue[index]}",secondUpdateValue="${updateSecondValue[index]}" where productId="${productIds[index]}";`;
}
return query;
}
//This is vulnerable to SQL Injection so change according to your needs
//It's just idea of doing it
let firstUpdates = [800, 900, 10];
let secondUpdates = [1.23, 2.23, 8.97];
let productIds = [1, 9, 3];
let generatedQuery = updateUsers(firstUpdates, secondUpdates, productIds);
console.log(generatedQuery);
// to run this with sequelize we can execute plain query with this
//sequelize.query(generatedQuery);
2. Using bulkCreate and updateOnDuplicate
let updatingValue = [
{productId:1, sort:100,sku:800},
{productId:2, sort:800,sku:8.27},
{productId:3, sort:400,sku:7.77}
];
model.bulkCreate(updatingValue,{
fields:["productid","sort","sku"],
updateOnDuplicate: ["sort","sku"]
}
// these are the column name that gets updated if primaryKey(productId) gets matched you have to update these accordingly
)
It had problem before but is updated now this PR
Other methods but quite complicated are also here.

Related

Creating a relationship between models with additional info in Mongo, Express, Node

somewhat new to Node and been struggling with this model relationship and not finding an answer here.
I have four models I'm trying to create relationships between:
User
Review
Topics
Courses
When a User leaves a Review on a Course in a certain Topic, I want to track a "topic score" on the User model.
So if a User Reviewed a programming Course, they should get +10 to their programming Topic score. Then I should be able to query User.scores.programming to get their Programming score.
The Reviews are being created fine, it's just the Topic scoring part where I'm running into issues.
Here's how my User schema are set up, just the relevant part:
const userSchema = new mongoose.Schema({
...
scores: [{
topic: {
type: mongoose.Schema.ObjectId,
ref: 'Topic'
},
score: {
type: Number,
default: 0
}
}]
});
And here's the code I have so far right now for trying to increment the score:
const updateUserScores = async (userId, course) => {
const user = await User.findOne({_id: userId}).populate('scores');
const userScores = user.scores;
let topics = await Course.findOne({_id: course}).populate('tags');
topics = topics.tags.map(x => x._id);
// I know it works for here to get the array of topics that they need to be scored on
// Then we need to go through each topic ID, see if they have a score for it...
// If they do, add 10 to that score. If not, add it and set it to 10
for (topic in topics) {
const operator = userScores.includes(topic) ? true : false;
if (!operator) {
// Add it to the set, this is not currently working right
const userScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: [topic, 10] }},
{ new: true}
)
} else {
// Get the score value, add 10 to it
}
}
}
I know I probably have a few different things wrong here, and I've been very stuck on making progress. Any pointers or examples I can look at would be extremely helpful!
Alright so after lots of messing around I eventually figured it out.
User model stayed the same:
scores: [{
topic: {
type: mongoose.Schema.ObjectId,
ref: 'Tag'
},
score: {
type: Number,
default: 0
}
}]
Then for the code to increment their score on each topic when they leave a review:
const updateUserScores = async (userId, course) => {
const user = await User.findOne({_id: userId}).populate({
path : 'scores',
populate: [
{ path: 'topic' }
]
});
let userScores = user.scores;
userScores = userScores.map(x => x.topic._id);
let topics = await Course.findOne({_id: course}).populate('tags');
topics = topics.tags.map(x => x._id);
for (t of topics) {
const operator = userScores.includes(t) ? true : false;
if (!operator) {
const userScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: {topic: t, score: 10}}},
{ new: true}
);
} else {
const currentScore = await user.scores.find(o => o.topic._id == `${t}`);
const userScoring = await User
.findByIdAndUpdate(userId,
{ $pull: { scores: {_id: currentScore._id}}},
{ new: true }
)
const userReScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: {topic: t, score: (currentScore.score + 10)}}},
{ new: true }
)
}
}
}
Ugly and not super elegant, but it gets the job done.

SEQUELIZE: Cant access database of an intermediate table

I am learning how to use sequelize in nodeJS, I really like it but there are some tables that i am not being be able to access, let me show you:
GIVEN THESE ASSOCIATIONS:
Product.belongsTo(User,{constraints:true, onDelete:'CASCADE'});
User.hasMany(Product);
User.hasOne(Cart);
Cart.belongsTo(User);
Cart.belongsToMany(Product,{ through:CartItem});
Product.belongsToMany(Cart,{ through:CartItem});
I am trying to build a controller that post products in the cart, it looks like this:
exports.addToCart = (req, res, next) => {
const ID = req.params.cartProductID;
let newQuantity = 1;
let fetchedCart;
req.user
.getCart().then(cart => {
fetchedCart = cart;
return cart.getProducts({ where: { id: ID } });
}).then(products => {
let product;
if (products.length > 0) {
product = products[0];
}
if (product) {
const oldQuantity = product.cartItems.quantity;
newQuantity = oldQuantity + 1;
...
On this last line, I get an error :
TypeError: Cannot read property 'quantity' of undefined if I log that last product on the console I can actually see this:
id: 1,
name: 'fdff',
description: 'fff',
price: 33,
createdAt: 2020-03-24T17:19:58.000Z,
updatedAt: 2020-03-24T17:19:58.000Z,
userId: 1,
'cart-item': [cart-item]
the question is, what is the correct way to access that 'cart-item' object?
Thanks in advance.
Marc

Mongo/Node: Filtering By Single Properties?

I am dealing with a query with a criteria object that is being passed as the first argument to this query:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
console.log(criteria);
const query = Artist.find({ age: { $gte: 19, $lte: 44 } })
.sort({ [sortProperty]: 1 })
.skip(offset)
.limit(limit);
return Promise.all([query, Artist.count]).then(results => {
return {
all: results[0],
count: results[1],
offset: offset,
limit: limit
};
});
};
By default, the criteria object has a single name property that is an empty string.
The age property points to an object that has both min and max values assigned to it. I also have a yearsActive property inside of the criteria object and that also has a min and max value.
So three different properties: age, name and yearsActive.
This has been an extremely challenging one for me and if you look above that's as far as I got.
When my criteria property is console logged it only has a name { name: "" }. It has no yearsActive or age by default when it first starts. So that is where the point of the sliders come in. When I start moving these sliders around on the frontend, then it gets the age and yearsActive appended to the criteria object.
So I need to figure out how to update the query to consider for example the different ages and I have been considering using an if conditional inside a helper function.
Regarding to the comment that I left you.
You have three states at least one when you retrieve the data to the UI. In this case, I would recommend you use aggregation in order to retrieve the data as a model as your business.
For example, the problem as you have is that sometimes you don't know about the max or min value for age or yearsActive, but also you should have an identifier that could be an ObjectId which will be used to update the model identified by that property.
Artist.aggregate([
{
$match: { age: { $gte: 19, $lte: 44 } }
},
{
$sort: { yourProperty: 1 }
},
{
$skip: 10
},
{
$limit: 10
},
{
$project: {
// You set your properties to retrieve with the 1 as flag
propertieX: 1,
"another.property": 1,
"age.max": {
$cond: {
if: { $eq: [ "", "$age.max" ] },
then: 0, // Or the value that you want to set it
else: "$age.max"
}
}
}
}]);
The other state is when you do the query according to the parameters that you're submitting from the form.
If you assurance to retrieve a model with the logic as you want. For example you should return this model in every request using $project and applying the default values when doesn't exist the manipulation in the front-end side as in the searching should be easy to manage.
{
ObjectId: YOUR_OBJECT_ID,
age: {
min: YOUR_MIN_VALUE,
max: YOUR_MAX_VALUE
},
yearsActive: {
min: YOUR_MIN_VALUE,
max: YOUR_MAX_VALUE
}
}
Finally, when you would send the data to save it you should sent the entire model that you returned but the must important thing is identify only that element by the ObjectId to do the update.
NOTE: This is an approach that I will do according with the information that I understand from your question, If I'm bad with me interpretation let me know, and if you want to share more information or open a repository to understand in code, should more easy to me understand the problem.
So what I decided to do since the code would look messy to throw all inside the Artist.find({}) was to create a separate helper function:
const buildQuery = (criteria) => {
console.log(criteria);
};
This helper function is being called with the criteria object and I have to form up the object in such a way that it will represent the query the way in which I want to search the Artist collection.
What made this difficult to wrap my head around was the not very well formed object for searching over a collection with its random properties such as age which has a min and a max which Mongo does not know how to deal with by default. MongoDB does not know what min and max mean exactly.
So inside the helper function I made a separate object to return from this function thats going to represent the actual query that I want to send off to Mongo.
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
};
I am not modifying the object in anyway, I am just reading some of the desired search results or what the user wants to see from this UI object and so I made this object called query and I added the idea of age.
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
query.age = {};
};
I decided to do an if conditional inside of the helper function for the specific age range that I want to find.
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {};
}
};
So this is where the Mongo query operators come into play. The two operators I want to be concerned with is the greater than or equal to ($gte) and the less than or equal to ($lte) operators.
This is how I actually implemented in practice:
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {
$gte: criteria.age.min,
$lte: criteria.age.max
};
}
};
The query object here will eventually be returned from the buildQuery function:
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {
$gte: criteria.age.min,
$lte: criteria.age.max
};
}
return query;
};
That query object will be passed off to the find operation:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
const query = Artist.find(buildQuery(criteria))
.sort({ [sortProperty]: 1 })
.skip(offset)
.limit(limit);
return Promise.all([query, Artist.count]).then(results => {
return {
all: results[0],
count: results[1],
offset: offset,
limit: limit
};
});
};
const buildQuery = (criteria) => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {
$gte: criteria.age.min,
$lte: criteria.age.max
};
}
return query;
};
So what I am doing here is to get the equivalent of Artist.find({ age: { $gte: minAge, $lte: maxAge }).
So for yearsActive I decided to implement something that is nearly identical:
const buildQuery = criteria => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {
$gte: criteria.age.min,
$lte: criteria.age.max
};
}
if (criteria.yearsActive) {
}
return query;
};
So if the user changes the slider, I am going to expect my criteria object to have a yearsActive property defined on it like so:
const buildQuery = criteria => {
console.log(criteria);
const query = {};
if (criteria.age) {
query.age = {
$gte: criteria.age.min,
$lte: criteria.age.max
};
}
if (criteria.yearsActive) {
query.yearsActive = {
$gte: criteria.yearsActive.min,
$lte: criteria.yearsActive.max
}
}
return query;
};

MongoDB - find one and add a new property

Background: Im developing an app that shows analytics for inventory management.
It gets an office EXCEL file uploaded, and as the file uploads the app convert it to an array of JSONs. Then, it comapers each json object with the objects in the DB, change its quantity according to the XLS file, and add a timestamp to the stamps array which contain the changes in qunatity.
For example:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":100,
"Warehouse":4,
"stamps":[]
}
after the XLS upload, lets say we sold 10 units, it should look like that:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":90,
"Warehouse":4,
"stamps":[{"1548147562": -10}]
}
Right now i cant find the right commands for mongoDB to do it, Im developing in Node.js and Angular, Would love to read some ideas.
for (let i = 0; i < products.length; i++) {
ProductsDatabase.findOneAndUpdate(
{"_id": products[i]['id']},
//CHANGE QUANTITY AND ADD A STAMP
...
}
You would need two operations here. The first will be to get an array of documents from the db that match the ones in the JSON array. From the list you compare the 'product_quantity' keys and if there is a change, create a new array of objects with the product id and change in quantity.
The second operation will be an update which uses this new array with the change in quantity for each matching product.
Armed with this new array of updated product properties, it would be ideal to use a bulk update for this as looping through the list and sending
each update request to the server can be computationally costly.
Consider using the bulkWrite method which is on the model. This accepts an array of write operations and executes each of them of which a typical update operation
for your use case would have the following structure
{ updateOne :
{
"filter" : <document>,
"update" : <document>,
"upsert" : <boolean>,
"collation": <document>,
"arrayFilters": [ <filterdocument1>, ... ]
}
}
So your operations would follow this pattern:
(async () => {
let bulkOperations = []
const ids = products.map(({ id }) => id)
const matchedProducts = await ProductDatabase.find({
'_id': { '$in': ids }
}).lean().exec()
for(let product in products) {
const [matchedProduct, ...rest] = matchedProducts.filter(p => p._id === product.id)
const { _id, product_quantity } = matchedProduct
const changeInQuantity = product.product_quantity - product_quantity
if (changeInQuantity !== 0) {
const stamps = { [(new Date()).getTime()] : changeInQuantity }
bulkOperations.push({
'updateOne': {
'filter': { _id },
'update': {
'$inc': { 'product_quantity': changeInQuantity },
'$push': { stamps }
}
}
})
}
}
const bulkResult = await ProductDatabase.bulkWrite(bulkOperations)
console.log(bulkResult)
})()
You can use mongoose's findOneAndUpdate to update the existing value of a document.
"use strict";
const ids = products.map(x => x._id);
let operations = products.map(xlProductData => {
return ProductsDatabase.find({
_id: {
$in: ids
}
}).then(products => {
return products.map(productData => {
return ProductsDatabase.findOneAndUpdate({
_id: xlProductData.id // or product._id
}, {
sku: xlProductData.sku,
product_name: xlProductData.product_name,
product_cost: xlProductData.product_cost,
product_price: xlProductData.product_price,
Warehouse: xlProductData.Warehouse,
product_quantity: productData.product_quantity - xlProductData.product_quantity,
$push: {
stamps: {
[new Date().getTime()]: -1 * xlProductData.product_quantity
}
},
updated_at: new Date()
}, {
upsert: false,
returnNewDocument: true
});
});
});
});
Promise.all(operations).then(() => {
console.log('All good');
}).catch(err => {
console.log('err ', err);
});

How to add dynamic additional attributes into a junction table with ManyToMany association in Sequelize

I created a many-to-many association by sequelize in my koa app. But I had no idea on how to create additional attributes in the junction table. Thanks.
I referred to the official doc of sequelize but didn't find a solution. In brief:
"an order can have many items"
"an item can exist in many orders"
Then I created OrderItems as junction table.
But I have trouble in inserting value into the junction table
// definitions
const Item = sequelize.define('item', itemSchema);
const Order = sequelize.define('order', orderSchema);
// junction table
const OrderItems = sequelize.define('order_item', {
item_quantity: { type: Sequelize.INTEGER } // number of a certain item in a certain order.
});
// association
Item.belongsToMany(Order, { through: OrderItems, foreignKey: 'item_id' });
Order.belongsToMany(Item, { through: OrderItems, foreignKey: 'order_id' });
// insert value
const itemVals = [{ name: 'foo', price: 6 }, { name: 'bar', price: 7 }];
const orderVals = [
{
date: '2019-01-06',
items: [{ name: 'foo', item_quantity: 12 }]
},
{
date: '2019-01-07',
items: [{ name: 'foo', item_quantity: 14 }]
}
]
items = Item.bulkCreate(itemVals)
orders = Order.bulkCreate(orderVals)
//Questions here: create entries in junction table
for (let order of orders) {
const itemsInOrder = Item.findAll({
where: {
name: {
[Op.in]: order.items.map(item => item.name)
}
}
})
order.addItems(itemsInOrder, {
through: {
item_quantity: 'How to solve here?'
}
})
}
// my current manual solution:
// need to configure column names in junction table manually.
// Just like what we do in native SQL.
const junctionValList =[]
for (let orderVal of orderVals) {
orderVal.id = (await Order.findOne(/* get order id */)).dataValues.id
for (let itemVal of orderVal.items) {
itemVal.id = (await Item.findOne(/* get item id similarly */)).dataValues.id
const entyInJunctionTable = {
item_id: itemVal.id,
order_id: orderVal.id,
item_quantity: itemVal.item_quantity
}
junctionValList.push(entyInJunctionTable)
}
}
OrderItems.bulkCreate(junctionValList).then(/* */).catch(/* */)
In case that this script it's for seeding purpose you can do something like this:
/*
Create an array in which all promises will be stored.
We use it like this because async/await are not allowed inside of 'for', 'map' etc.
*/
const promises = orderVals.map((orderVal) => {
// 1. Create the order
return Order.create({ date: orderVal.date, /* + other properties */ }).then((order) => {
// 2. For each item mentioned in 'orderVal.items'...
return orderVal.items.map((orderedItem) => {
// ...get the DB instance
return Item.findOne({ where: { name: orderedItem.name } }).then((item) => {
// 3. Associate it with current order
return order.addItem(item.id, { through: { item_quantity: orderedItem.item_quantity } });
});
});
});
});
await Promise.all(promises);
But it's not an efficient way to do it in general. First of all, there are a lot of nested functions. But the biggest problem is that you associate items with the orders, based on their name and it's possible that in the future you will have multiple items with the same name.
You should try to use an item id, this way you will be sure about the outcome and also the script it will be much shorter.

Resources