SEQUELIZE: Cant access database of an intermediate table - node.js

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

Related

Is this possible to update multiple data with sequlize?

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.

How to query through nested keys in Google datastore?

I have users, stored under specific unique IDs. I need to query them by a certain property, but I cannot figure out how to do that when my unique ID is part of the key.
const user1 = {
id: 1,
name: 'John Smith',
cityId: 102,
};
const user1 = {
id: 2,
name: 'Rudy Black',
cityId: 102,
};
const upsertUser = user => {
const key = datastore.key([ 'users', user.id ]);
return datastore.upsert({ key, data: user });
});
const getUsersInCity = async cityId => {
const query = datastore.createQuery('users').filter('cityId', '=', cityId);
const [ users ] = await datastore.runQuery(query);
return users;
};
upsertUser(user1);
upsertUser(user2);
console.log(await getUsersInCity(102)); // Expected: John Smith, Rudy Black
An example of code that you can give it a try using to query values via ID, it's using the below code:
const key = this.datastore.key(['Customer', id:number]);
await this.datastore
.get(key)
.then(results => {
console.log(results);
})
.catch(err => { console.error('ERROR:', err); });
This code was get from the article Google DataStore Query By Nodejs. This article provides some good examples of queries that you can give a look.
I would recommend you to take a look at it and use the above code as a start point.
Let me know if the information helped you!

How do I keep mongo's array index from changing during an update?

I am trying to update an array within my object. However, every time I send the post call, the index in the array changes.
I have tried using $set and manually updating the array... but the index on the array keeps changing.
Here is the model:
const MappedImageSchema = new Schema({
imageUrl: {type: String, required: true},
name: {type: String, required: true},
areas:[
{
name: {type: String},
shape: {type: String},
coords:[{type: Number}],
}
]
});
module.exports = MappedImage = mongoose.model('mappedImages', MappedImageSchema)
Here is the code that performs the update:
// #route POST api/maps/:id/areas
// #desc add an area to a map (by map id)
// #access Private
router.post('/:id/areas/:id_area', passport.authenticate('jwt', { session: false }),
(req, res) => {
MappedImage.findById(req.params.id)
.then(map => {
// get all of the areas from the map...
var allAreas = map.areas;
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
// set the updated information in the correct map area
allAreas[areaIndex] = updatedArea;
var query = {_id: req.params.id}; // this is the MAP id
var update = {$set: {areas:allAreas}}; // update the areas
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
})
.catch(err => res.status(404).json({ mapnotfound: 'Map not found while updating area' }));
}
);
Here is the data BEFORE the call
{
"_id": "5c5c69dda40e872258b4531d",
"imageUrl": "url test",
"name": "name test",
"areas": [
{
"coords": [1,2,3,4,5,6],
"_id": "5c5c8db2f904932dd8d4c560", <---- _id changes every time !
"name": "area name",
"shape": "poly"
}
],
"__v": 3
}
Here is the Postman call I make:
The result of the call is the name gets changed... but so does the index... making the next call fail with "no area found with that index".
What is perplexing about this problem is the _id for the map does not get updated when I run this code:
router.post('/:id', passport.authenticate('jwt', { session: false }),
(req, res) => {
var query = {_id: req.params.id};
var update = {imageUrl: req.body.imageUrl, name: req.body.name};
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
});
Update 1
I tried using the areas index and updating just that area... but the _id changes with this code as well:
... same code all the way down to here
allAreas[areaIndex] = updatedArea;
// but instead of calling 'findOneAndUpdate'... call map save
map.save().then(map => res.json(map));
Update 2
I can't get this code to work as areas._id and areas.$ are undefined ?
var query = {_id: req.params.id, areas._id: id_area}; // this is the MAP id
var update = {$set: {areas.$: updatedArea}}; // update the area
Update 3
So, putting the _id in the updatedArea fixes this issue... but it "feels" wrong to do so: ( per eol answer )
const updatedArea = {
_id: req.params.id_area,
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
Update 4
eol - thanks for the verification on the mongoDB side... If that solves the DB id problem... I just need to know why my query is failing. I tried this and all I see in the terminal output is "creating query"... I never see the "query" and it's definition... so something is wrong and I don't know how to figure out what. Here is what I have now:
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": id_area};
console.log('query');
console.log(query);
Update 5
Figured it out why the query not being output, id_area is not defined... but req.params.id_area is !
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": req.params.id_area};
console.log('query');
Update 6
Code is in... but it is still not working. A picture is worth a 1000 words... so here are two:
This one shows the areas ID is still changing:
Here is the code I have now:
console.log('Update area');
console.log('changing area ' + req.params.id_area);
//console.log(req.body);
const { errors, isValid } = mapValidators.validateAreaInput(req.body);
// Check Validation
if(!isValid){
return res.status(400).json(errors);
}
MappedImage.findById(req.params.id)
.then(map => {
// Check to see if area exists
if (
map.areas.filter(
area => area._id.toString() === req.params.id_area
).length === 0
) {
return res.status(404).json({ areanotfound: 'Area does not exist' });
}
console.log('area exists');
// get all of the areas from the map...
var allAreas = map.areas;
console.log('all areas');
console.log(allAreas);
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
console.log('area index');
console.log(areaIndex);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
name: req.body.name,
shape: req.body.shape,
preFillColor: req.body.preFillColor,
fillColor: req.body.fillColor,
coords: coords,
};
console.log('updated area');
console.log(updatedArea);
// set the updated information in the maps areas
allAreas[areaIndex] = updatedArea;
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": req.params.id_area};
console.log('query');
console.log(query);
var update = {$set: {"areas.$": updatedArea}};
console.log('update');
console.log(update);
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
})
.catch(err => res.status(404).json({ mapnotfound: 'Map not found while updating area' }));
Here is the terminal output:
You could try setting the _id property in the updatedArea object with the value of the area that you'd like to update. This would prevent creating a new id while using the $set operator. Something like this:
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
_id: id_area,
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
...
Note that with the above solution you're always setting a new array, which is why new id's are generated.
You could also try updating the specific element in the array using the $ operator:
var query = {"_id": req.params.id, "areas._id": id_area}; // this is the MAP id
var update = {$set: {"areas.$": updatedArea}}; // update the area
See the screenshots below for an example (executing the commands in the mongodb-shell) where I'm trying to only update the second array element (i.e. with _id 5c5c8db2f904932dd8d4c561)

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.

Mongoose: $inc not working

I am not sure what the problem is, as I've read numerous examples.
Taken from what I was advised here in this StackOverFlow(Mongoose - Increment a value inside an array of objects), I changed the format of poll at ease to accommodate what was recommended.
So I was able to create a document format as so:
{
"_id": "584c4160b3b22e1bdad59bce",
"title": "Food",
"description": "test",
"labelOptions": {
"burger": 29,
"coffee": 44,
"pizza": 23
},
"date": "Dec 10, 2016",
"__v": 0
}
Here's what I have so far:
Poll Model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const pollData = new Schema({
title: String,
description: String,
labelOptions: {},
date: String
})
module.exports = mongoose.model('PollData', pollData)
Using express and mongoose, here's what I have:
app.put('/polls/:id', function(req, res){
let id = req.params.id;
let labelOption = req.query.labelOption;
let query = `labelOptions.${labelOption}`
Poll.findByIdAndUpdate(
id,
{$inc: { query: 1 } },
function(err, document){
console.log(err)
console.log(document)
}
)
})
In my terminal, I see that console.log(document it receives the document I was looking for but it does not update the value at all.
Am I setting up the Model correctly? Or does Mongoose does not support template strings?
***update
This is snippet of how I am creating documents
let labelOptions = {}; <=== creating object literal to hold as placeholder
const title = req.body.title;
const description = req.body.description;
req.body.labelOptions.split(/,\s*/).map( prop =>{
labelOptions[prop] = 0 // set counter to default 0
})
const poll = new Poll({
title: title,
description: description,
labelOptions: labelOptions,
date: moment().format('MMM D, YYYY')
});
poll.save(function(err) {
if (err) { return next(err); }
res.json({ message : 'Poll added!'})
});
After doing some research across the internet, I found the reason why it wasnt working: You can't initialize objects with 'dynamic' keys.
Source: Mongoose update on a string variable not working?
By knowing that, it was just a simple solution to initialize an literal object as so:
let id = req.params.id;
let labelOption = req.query.labelOption;
let query = "labelOptions." + labelOption
let obj = {
[query] : 1
}
Poll.findByIdAndUpdate(
id,
{$inc: obj },
function(err, document){
console.log(err)
console.log(document)
}
)

Resources