Conditionally create new entries using promises in Waterline (Sails.js) - node.js

I have an array of "products".
I want to save these products to the database if the database is empty, and when all of the db operations finish i want to display a message.
I could not manage to do it using bluebird promises (using .all or .map). I was able to create an item by just returning Product.create(products[0]). I can't wrap my head around it, I am new to promises.
This is the bootstrap file of my sails.js project but this question is about how to use bluebird promises. How can I manage to wait for multiple async tasks (create 3 products) to finish and then continue?
products = [
{
barcode: 'ABC',
description: 'seed1',
price: 1
},
{
barcode: 'DEF',
description: 'seed2',
price: 2
},
{
barcode: 'GHI',
description: 'seed3',
price: 3
}
];
Product.count()
.then(function(numProducts) {
if (numProducts > 0) {
// if database is not empty, do nothing
console.log('Number of product records in db: ', numProducts);
} else {
// if database is empty, create seed data
console.log('There are no product records in db.');
// ???
return Promise.map(function(product){
return Product.create(product);
});
}
})
.then(function(input) {
// q2) Also here how can decide to show proper message
//console.log("No seed products created (no need, db already populated).");
// vs
console.log("Seed products created.");
})
.catch(function(err) {
console.log("ERROR: Failed to create seed data.");
});

Figured it out...
products = [
{
barcode: 'ABC',
description: 'seed1',
price: 1
},
{
barcode: 'DEF',
description: 'seed2',
price: 2
},
{
barcode: 'GHI',
description: 'seed3',
price: 3
}
];
Product.count()
.then(function(numProducts) {
//if (numProducts > 0) {
if(false) {
// if database is not empty, do nothing
console.log('Number of product records in db: ', numProducts);
return [];
} else {
// if database is empty, create seed data
console.log('There are no product records in db.');
return products;
}
})
.map(function(product){
console.log("Product created: ", product);
return Product.create(product);
})
.then(function(input) {
console.log("Seed production complete.");
})
.catch(function(err) {
console.log("ERROR: Failed to create seed data.");
});

Related

Mongoose - how to fetch specific number and message ID

I'm trying to make the bot basically edit the message of any specific case mentioned for example if i do -case 5 test it will look for case 5 and it's message. So far when i do it, it basically changes the recent case number message, instead of the one i want it to change. like if i do case 5 test and the latest case is #9, it will change 9 instead of 5.
This is how i send the message:
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id }, { $inc: { 'caseID': 1 } }, { new: true }, async function (err, doc) {
if (err) throw err;
if (!doc) return;
if (doc.modLog.enabled) {
if (msg.channel.guild.channels.get(doc.modLog.channelID)) {
let m = await msg.channel.guild.channels.get(doc.modLog.channelID).createMessage({
embed: {
title: `${action} | Case #${doc.caseID}`,
color: colour,
fields: [
{
name: 'User',
value: user,
inline: true
},
{
name: 'Moderator',
value: moderator ? moderator : 'No issuer.',
inline: true
},
{
name: 'Reason',
value: reason ? reason : 'No reason.'
}
]
}
});
doc.messageID = m.id;
doc.type = action;
doc.caseID = doc.caseID;
//doc.caseID = m.id
doc.moderatorID = moderator,
doc.targetID = user
doc.save();
}
}
})
that is how i send my message. And you can see i'm storing the things so when someone changes a specific case's reason, for example: case 5 spamming, i would want it to look for caseID 5, and then edit the message through it's ID. but i'm not sure how am i doing it wrong. I'm trying to make each case store it's own message ID and i would really appreciate any help. This is what i use to look for the case and edit's reason.
Modlog.findOne({ guildID: msg.guildID }, async (err, doc) => {
if (err) throw err;
if (!doc.modLog.enabled) return msg.channel.createMessage(`Modlog is not enabled in this server! ${this.emoji.cross}`);
if (isNaN(Number(caseID))) return msg.channel.createMessage(`Case \`#${caseID}\` was not a number! ${this.emoji.cross}`);
if (doc.caseID === undefined) return msg.channel.createMessage(`Couldn\'t find case \`#${caseID}\`! ${this.emoji.cross}`);
const moderator = this.bot.users.get(doc.moderatorID) || {
username: 'Unknown User',
discriminator: '0000'
}
const target = this.bot.users.get(doc.targetID) || {
username: 'Unknown User',
discriminator: '0000'
}
let embed = {
title: `${doc.type} | Case #${doc.caseID}`,
fields: [
{
name: 'User',
value: `${target.username}#${target.discriminator} (${target.id})`,
inline: true
},
{
name: 'Moderator',
value: `${moderator.username}#${moderator.discriminator} (${moderator.id})`,
inline: true
},
{
name: 'Reason',
value: reason
}
]
};
try {
await this.bot.editMessage(doc.modLog.channelID, doc.messageID, { embed: embed });
await msg.channel.createMessage(`Case **#${caseID}** has been updated. ${this.emoji.tick}`);
} catch (e) {
await msg.channel.createMessage(`I\'m unable to edit that case or it has been deleted. ${this.emoji.cross}`);
}
});```
Solution: Search for Case ID
It seems you didn't look for the case ID, and only looked for the guild's ID in the filter parameter.
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id }, { ... }, { ... }, ... {
...
}
In your code, only guildID was passed into the filter parameter. This causes Mongoose to look for the most recently initialized document for the server. For your case, you should also pass caseID into the filter parameter.
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id, caseID: caseIDArg }, { ... }, { ... }, ... {
...
}
Replace caseIDArg with your supposed caseID argument in the message's content. For example, args[1] or however you programmed your argument handler to work.
Hope this helped to answer your question!

Resolve Promise in Nodejs Nested Map Functions and Return Array of Objects From API Call [duplicate]

I'm building an API to add movies to wishlist. I have an endpoint to get all movies in wishlist. My approach was to get the movie ids (not from mongodb) and make an API request to another API to get the movie objects.
This has been successful so far but the problem now is I am getting two objects fused into one object like below:
{
id: 7,
url: 'https://www.tvmaze.com/shows/7/homeland',
name: 'Homeland',
language: 'English',
genres: [ 'Drama', 'Thriller', 'Espionage' ],
status: 'Ended',
runtime: 60,
averageRuntime: 60,
premiered: '2011-10-02',
officialSite: 'http://www.sho.com/sho/homeland/home',
schedule: { time: '21:00', days: [ 'Sunday' ] },
rating: { average: 8.2 },
image: {
medium: 'https://static.tvmaze.com/uploads/images/medium_portrait/230/575652.jpg',
original: 'https://static.tvmaze.com/uploads/images/original_untouched/230/575652.jpg'
},
summary: '<p>The winner of 6 Emmy Awards including Outstanding Drama Series, <b>Homeland</b> is an edge-of-your-seat sensation. Marine Sergeant Nicholas Brody is both a decorated hero and a serious threat. CIA officer Carrie Mathison is tops in her field despite being bipolar. The delicate dance these two complex characters perform, built on lies, suspicion, and desire, is at the heart of this gripping, emotional thriller in which nothing short of the fate of our nation is at stake.</p>',
}
This is the second object below. Notice how there's no comma separating both objects
{
id: 1,
url: 'https://www.tvmaze.com/shows/1/under-the-dome',
name: 'Under the Dome',
language: 'English',
genres: [ 'Drama', 'Science-Fiction', 'Thriller' ],
status: 'Ended',
runtime: 60,
averageRuntime: 60,
premiered: '2013-06-24',
schedule: { time: '22:00', days: [ 'Thursday' ] },
rating: { average: 6.6 },
image: {
medium: 'https://static.tvmaze.com/uploads/images/medium_portrait/81/202627.jpg',
original: 'https://static.tvmaze.com/uploads/images/original_untouched/81/202627.jpg'
},
summary: "<p><b>Under the Dome</b> is the story of a small town that is suddenly and inexplicably sealed off from the rest of the world by an enormous transparent dome. The town's inhabitants must deal with surviving the post-apocalyptic conditions while searching for answers about the dome, where it came from and if and when it will go away.</p>",
}
My question now is how do I convert both objects to an array and send as a response from my own API. API code is below:
module.exports = {
fetchAll: async (req, res, next) => {
var idsArr = [];
var showsArr;
var shows;
try {
let wishlist = await Wishlist.find({});
if (wishlist == null) {
res.status(404)
.json({
success: false,
msg: 'No Movies Found in Wishlist',
wishlist: []
})
}
// console.log(wishlist);
wishlist.map((item) => {
idsArr.push(item.id);
})
console.log(idsArr);
idsArr.map(async (id) => {
shows = await axios.get(`https://api.tvmaze.com/shows/${id}`);
console.log(shows.data);
// console.log(showsArr);
// showsArr = [shows.data];
})
console.log(showsArr);
return res.status(200)
.json({
success: true,
msg: 'All Movies in Wishlist Fetched',
wishlist: showsArr
})
} catch (err) {
console.log(err);
next(err);
}
},
... // other methods
}
I have tried creating an empty array. shows.data which is the actual response and then I've tried adding it to my array using showsArr.push(shows.data) previously without much success. I get undefined when I log to console.
Here the ids range from 1 to 240+, in case one wants to try out the endpoint - https://api.tvmaze.com/shows/${id}
How would I go about achieving this? Thanks.
Just like when converting the wishlist array to an array of ids, you would need to push the data items into your new showsArr.
However, this doesn't actually work, since it's asynchronous - you also need to wait for them, using Promise.all on an array of promises. And you actually shouldn't be using push at all with map, a map call already creates an array containing the callback return values for you. So you can simplify the code to
module.exports = {
async fetchAll(req, res, next) {
try {
const wishlist = await Wishlist.find({});
if (wishlist == null) {
res.status(404)
.json({
success: false,
msg: 'No Movies Found in Wishlist',
wishlist: []
})
}
const idsArr = wishlist.map((item) => {
// ^^^^^^^^^^^^^^
return item.id;
// ^^^^^^
});
console.log(idsArr);
const promisesArr = idsArr.map(async (id) => {
const show = await axios.get(`https://api.tvmaze.com/shows/${id}`);
console.log(shows.data);
return shows.data;
// ^^^^^^^^^^^^^^^^^^
});
const showsArr = await Promise.all(promisesArr);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
console.log(showsArr);
return res.status(200)
.json({
success: true,
msg: 'All Movies in Wishlist Fetched',
wishlist: showsArr
})
} catch (err) {
console.log(err);
next(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.

How to save array data in nodejs using mongoose?

I am stuck to save data in mongoDb. Here data is in array and i need to insert data if mongodb does not have. Please look code:-
var contactPersonData = [{
Name: 'Mr. Bah',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'Mr. Sel',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'Mr.ATEL',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'ANISH',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'sunny ji',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'ashish',
Organization: 'Ashima Limited - Point 2'
}]
console.log('filedata', contactPersonData);
var escapeData = [];
var tempArr = [];
function saveContact(personObj, mainCallback) {
var tempC = personObj['Organization'].trim();
var insertData = {};
Contact.findOne({ companyName: tempC })
.exec(function(err, contact) {
if (err)
return mainCallback(err);
console.log('find com', contact)
if (contact) {
//document exists
mainCallback(null, insertData);
} else {
var newContact = new Contact({ companyName: tempC, createdBy: '58ae5d18ba71d4056f30f7b1' });
newContact.save(function(err, contact) {
if (err)
return mainCallback(err);
console.log('new contact', contact)
insertData.contactId = contact._id;
insertData.name = personObj['Name'];
insertData.email = personObj['Email'];
insertData.contactNumber = { number: personObj['Phone'] };
insertData.designation = personObj['Designation'];
tempArr.push(insertData);
mainCallback(null, insertData);
})
}
});
}
async.map(contactPersonData, saveContact, function(err, result) {
console.log(err)
console.log(result)
},
function(err) {
if (err)
return next(err);
res.status(200).json({ unsaved: escapeData })
})
As per above code it has to insert six document instead of one. I think that above iteration not wait to complete previous one. So, the if condition is always false and else is executed.
Your saveContact() function is fine. The reason you get 6 documents instead of 1 document is that async.map() runs you code in parallel. All the 6 requests are made in parallel, not one after the another.
From the async.map() function's documentation -
Note, that since this function applies the iteratee to each item in parallel, there is no guarantee that the iteratee functions will complete in order.
As a result before the document is created in your database all queries are already run and all the 6 queries are not able to find that document since its still in the process of creation. Therefore your saveContact() method creates all the 6 documents.
If you run your code again then no more documents will be formed because by that time your document would be formed.
You should try running your code using async.mapSeries() to process your request serially. Just replace map() with mapSeries() in your above code. This way it will wait for one request to complete and then execute another and as a result only one document will be created. More on async.mapSeries() here.
You seem to be using async.map() wrong.
First, async.map() only has 3 parameters (i.e. coll, iteratee, and callback) so why do you have 4? In your case, coll is your contactPersonData, iteratee is your saveContact function, and callback is an anonymous function.
Second, the whole point of using async.map() is to create a new array. You are not using it that way and, instead, are using it more like an async.each().
Third, you probably should loop through the elements sequentially and not in parallel. Therefore, you should use async.mapSeries() instead of async.map().
Here's how I would revise/shorten your code:
var contactPersonData = [{
Name: 'Mr. Bah',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'Mr. Sel',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'Mr.ATEL',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'ANISH',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'sunny ji',
Organization: 'Ashima Limited - Point 2'
}, {
Name: 'ashish',
Organization: 'Ashima Limited - Point 2'
}];
function saveContact(personObj, mainCallback) {
var tempC = personObj.Organization.trim();
Contact.findOne({ companyName: tempC }, function (err, contact) {
if (err)
return mainCallback(err);
console.log('found contact', contact);
// document exists, so mark it as complete and pass the old item
if (contact)
return mainCallback(null, contact);
// document does not exist, so add it
contact = new Contact({ companyName: tempC, createdBy: '58ae5d18ba71d4056f30f7b1' });
contact.save(function (err, contact) {
if (err)
return mainCallback(err);
console.log('created new contact', contact)
// mark it as complete and pass a new/transformed item
mainCallback(null, {
contactId: contact._id,
name: personObj.Name,
email: personObj.Email, // ??
contactNumber: { number: personObj.Phone }, // ??
designation: personObj.Designation // ??
});
});
});
};
async.mapSeries(contactPersonData, saveContact, function (err, contacts) {
if (err)
return next(err);
// at this point, contacts will have an array of your old and new/transformed items
console.log('transformed contacts', contacts);
res.json({ unsaved: contacts });
});
In terms of ?? comments, it means you don't have these properties in your contactPersonData and would therefore be undefined.

MongoDB: how to insert a sub-document?

I am using sub-documents in my MEAN project, to handle orders and items per order.
These are my (simplified) schemas:
var itemPerOrderSchema = new mongoose.Schema({
itemId: String,
count: Number
});
var OrderSchema = new mongoose.Schema({
customerId: String,
date: String,
items: [ itemPerOrderSchema ]
});
To insert items in itemPerOrderSchema array I currently do:
var orderId = '123';
var item = { itemId: 'xyz', itemsCount: 7 };
Order.findOne({ id: orderId }, function(err, order) {
order.items.push(item);
order.save();
});
The problem is that I obviously want one item per itemId, and this way I obtain many sub-documents per item...
One solution could be to loop through all order.items, but this is not optimal, of course (order.items could me many...).
The same problem could arise when querying order.items...
The question is: how do I insert items in itemPerOrderSchema array without having to loop through all items already inserted on the order?
If you can use an object instead of array for items, maybe you can change your schema a bit for a single-query update.
Something like this:
{
customerId: 123,
items: {
xyz: 14,
ds2: 7
}
}
So, each itemId is a key in an object, not an element of the array.
let OrderSchema = new mongoose.Schema({
customerId: String,
date: String,
items: mongoose.Schema.Types.Mixed
});
Then updating your order is super simple. Let's say you want to add 3 of items number 'xyz' to customer 123.
db.orders.update({
customerId: 123
},
{
$inc: {
'items.xyz': 3
}
},
{
upsert: true
});
Passing upsert here to create the order even if the customer doesn't have an entry.
The downsides of this:
it is that if you use aggregation framework, it is either impossible to iterate over your items, or if you have a limited, known set of itemIds, then very verbose. You could solve that one with mapReduce, which can be a little slower, depending on how many of them you have there, so YMMB.
you do not have a clean items array on the client. You could fix that with either client extracting this info (a simple let items = Object.keys(order.items).map(key => ({ key: order.items[key] })); or with a mongoose virtual field or schema.path(), but this is probably another question, already answered.
First of all, you probably need to add orderId to your itemPerOrderSchema because the combination of orderId and itemId will make the record unique.
Assuming that orderId is added to the itemPerOrderSchema, I would suggest the following implementation:
function addItemToOrder(orderId, newItem, callback) {
Order.findOne({ id: orderId }, function(err, order) {
if (err) {
return callback(err);
}
ItemPerOrder.findOne({ orderId: orderId, itemId: newItem.itemId }, function(err, existingItem) {
if (err) {
return callback(err);
}
if (!existingItem) {
// there is no such item for this order yet, adding a new one
order.items.push(newItem);
order.save(function(err) {
return callback(err);
});
}
// there is already item with itemId for this order, updating itemsCount
itemPerOrder.update(
{ id: existingItem.id },
{ $inc: { itemsCount: newItem.itemsCount }}, function(err) {
return callback(err);
}
);
});
});
}
addItemToOrder('123', { itemId: ‘1’, itemsCount: 7 }, function(err) {
if (err) {
console.log("Error", err);
}
console.log("Item successfully added to order");
});
Hope this may help.

Resources