How to query a model with different attributes - javascript

I'm building an app in Express and using Postgres for my database, Sequelize for ORM.
In my database each Post can have one of the 5 types, 1, 2, 3, 4, 5.
I want to show the amount of all the posts by type.
router.route('/post).get(async (req, res) => {
const postOne = await Post.findAll({
where: {
state: 1
}
});
const postTwo = await Post.findAll({
where: {
state: 2
}
});
res.send({ postOne: postOne.length, postTwo: postTwo.length });
I can write like this for all of the 5 types, but I was wondering if there was any shorter way to do it so that I don't have to write the same code 5 times.
Thanks!

const getPostWithType = (type) => Post.findAll({
where: {
state: type
}
});
Now you can await getPostWithType(1) instead. Or even better:
const postTypes = [1,2,3,4,5];
const allPosts = await Promise.all(postTypes.map(getPostWithType));
// allPosts is now an array with the results. You can map it again to get the lengths
const allPostLengths = allPosts.map(p => p.length);

what about using an array? Like below...
let promiseArray = [];
for (let i = 1; i <= 5; i++) {
let promise = Post.findAll({
where: {
state: i
}
});
promiseArray.push(promise);
}
Promise.all(promiseArray).then(function(response) {
//Do whatever...
});

Related

mongodb full $text search returns duplicate items when sorting and filtering

I am using full-text search in my API and sorting using the score. The issue is after searching I usually get an item more than once and this is not the behaviour i expected. Please, how can i correct this?
here is how my router is designed
router.get('/place', async (req, res) => {
const match = {
deactivated: false,
}
const sort = {}
if (req.query.search) {
match.$text = {$search: req.query.search}
sort.score = {$meta: "textScore"}
}
const noOnPage = parseInt(req.query.limit) || 20
const pageNo = (parseInt(req.query.page)-1)*parseInt(req.query.limit) || 0
const endIndex = parseInt(req.query.page)*parseInt(req.query.limit)
const next = parseInt(req.query.page)+1
const previous = parseInt(req.query.page)-1
try {
const count = await Place.find(match).countDocuments().exec()
const place = await Place.find(match, sort)
.limit(noOnPage)
.skip(pageNo)
.sort(sort)
const result = {}
// Shows the search result count
result.resultCount = count
// Shows the previous page number
if (parseInt(req.query.page)!=1) {
result.previous = previous
}
// Shows the next page number
if (endIndex < count) {
result.next = next
}
// assigns the search results to variable names results
result.results = place
res.status(200).send(result)
} catch (e) {
res.status(400).send({ "message": "something went wrong please reload page" })
}
})
You could try using the mongoose paginate module.below are the simple changes you would need to make.
Example User model:
import {model, Schema} from 'mongoose';
const mongoosePaginate = require('mongoose-paginate-v2');
const userSchema = new Schema({
firstname: { type: String,trim:true},
lastname: { type: String,trim:true},
isconfirmed:{type:Boolean,default:false},
},
{timestamps:true});
userSchema.plugin(mongoosePaginate);
const appUsers:any = model('appusers', userSchema);
export default appUsers;
Sample get all users method, note the sort can be passed in options. You can play with the options object as you please
getAllUsers(req:Request,res:Response){
const options = {page: req.body.page?req.body.page:1,limit: 10,collation:
{locale: 'en'},sort:{createdAt:-1},select: {_id:1,firstname:1,lastname:1}};
appUsers.paginate({}, options, function(err:any, result:any) {
if(err){
res.status(500).json({status:false,msg:"Failed to fetch All
users",err:err});
}else{
res.status(200).json({status:true,msg:"List populated",data:result});
}
});
}

Storing MongoDB document inside a Node.js array and return it

I try to get specific documents from MongoDB with Node.js and insert them into array.
const getStockComments = async (req) => {
const stockname = req.params.stockName;
var comments = [];
var data = [];
const stock = await stockModel.findOne({ name: stockname });
comments = stock.comments;
comments.forEach(async (commentId) => {
const comm = await commentModel.findOne({ _id: commentId });
data.push(comm);
console.log(data); // This returns the data in loops, because its inside a loop.
});
console.log(data); // This not returns the data and i don't know why.
return data;
};
The first console.log(data) returns the same data a lot of times because its inside a loop.
But the second console.log(data) dosen't returns the data at all.
What I'm doing wrong?
Instead of using loop , you can use $in operator to simplify things .
const getStockComments = async (req) => {
const stockname = req.params.stockName;
var comments = [];
var data = [];
const stock = await stockModel.findOne({ name: stockname });
comments = stock.comments;
commentModel.find({ _id: { $in: comments } }, (err, comments) => {
data = comments;
});
console.log(data);
return data;
};

Delay the foreach Loop in Node JS

I hope you are well. I am getting data from one API and sending to Shopify store API. As its working fine but its entering some products as when it iterates in the loop API is busy with index suppose 0,1,2 and then indexes 3,4,...10 bypassed. So , according to me I should delay the foreach loop with 10,15 seconds. Please help me to do this . I tried it many times with SETTimer etc but foreach loop structure is difficult for me as a new person. Please check the below code. Thanks
const request = require('request');
const { json } = require('express');
const { Parser } = require('json2csv');
const fastcsv = require('fast-csv');
//const csv = require('csv-parser');
const fs = require('fs');
const { privateDecrypt } = require('crypto');
const { time } = require('console');
const fields = ['Vendor', 'Price', 'SKU','error'];
const opts = { fields };
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const csvWriter = createCsvWriter({
path: 'C:/Users/IT City/Desktop/arslan.csv',
header: [
{id: 'Vendor', title: 'Vendor'},
{id: 'Price', title: 'Price'},
{id: 'SKU', title: 'SKU'},
{id: 'error', title: 'error'},
]
});
let new_products = {
product_final: {
Vendor: String,
Price: String,
SKU: String,
Error: String,
}
};
//////////First API from which I am getting the data
const options = {
method: 'GET',
url: 'https://api.dexi.io/runs/f58f7795-c11a-478c-b670-c7ae5df8677b/latest/result',
headers: {
accept: 'application/json',
json: true,
'x-dexiio-access': '9d56e967dfXXXXXXX725e234b311655c96',
'x-dexiio-account': '5e597feb-99axxxxxxxx-37f1315723ab'
}
};
products = ['Vendor','Price','SKU','Error']
let product = {};
request(options, function (error, response, body) {
if (error) throw new Error(error);
pro = JSON.parse(body);
/////Looping through the each Item from the first API and sending data to Shopify. But its entering only 10-12 products
//// As handle is busy to entering the product.
/// I have to delay the foreach loop 10, 15 seconds
pro.rows.forEach(
row => {
for (let z = 0; z < row.length; z++)
{
product[pro.headers[z]] = row[z];
product_final2[pro.headers[z]] = row[z];
}
productssdata.push(product_final2)
products.push(product)
var Price = product.Price;
var SKU = product.SKU;
var Vendor = product.Vendor;
var body_html = "THISFSDFSDFSDFSDFSFSDF";
let new_products = {
product: {
title: Vendor,
body_html: Price,
vendor: Vendor,
product_type: SKU,
tags: Price
}
};
const options = {
method: 'POST',
url:
'https://0abcfsdfsdf4bb6532f3b#amjad.myshopify.com/admin/api/2020-07/products.json',
headers: {
accept: 'application/json',
'apiKey': '07649cABSDCCSD8ffbae7af02',
'password': 'sSDCSDFDF',
body: new_products,
json: true,
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body)
});
}
);
}
);
you don't need to use setTimeOut() to delay the loop. thats what async and await are for let me share you an example how to make the forEach loop delay with await!!.
step1 : return a function with promise and use await until it is complete.
const wait =async props => {
return new Promise((reslove,reject) => {
return reslove(Math.random());
})
}
const x = [1,2,3,4]
x.forEach(async number =>{
const num = await wait();
console.log('start')
console.log(num);
console.log('end');
})
Request is deprecated
It can't be used with await anyway, which makes it inconvenient. There used to be another module, request-promise, that as wrapping request and return a Promise, so one could await it, but it's still deprecated.
For these reasons, use Axios or Fetch instead
You can't use await or delay a .forEach() loop, but you can in a for loop.
You think you need to delay the calls, because they are asynchronous, but in reality you should simply await each call. Delaying calls with an arbitrary timeout is a dirty workaround.
In the end, you can do something like :
( async () => {
let options = {
url : "http://......"
};
const response = await axios.get(options); // Add try/catch block around this to manage errors
const pro = response.data;
for( let row of pro.rows) {
options = {
url : "http://some.other.url"
}
const response = await axios.get(options); // Each call will be done one by one and awaited in order
}
})()
setTimeout(pro.rows.forEach((row) => {...}), 10000)
This executes the forEach after 10 seconds.

How to combine two json response and send it as a whole?

I am using zomato API which sends only 20 names in one api call and I want atleast 60 responses so I thought of calling the same api three times and combining all the responses together.
app.get('/locations/:query', async (req, res) => {
const query = req.params.query;
const data = await zomato.cities({ q: query,count: 1 })
const cityId= await (data[0].id);
const restaurants1 = await zomato.search({ entity_id: cityId, entity_type: 'city', start:0, count:20, sort:'rating', order:'desc' })
const restaurants2 = await zomato.search({ entity_id: cityId, entity_type: 'city', start:20, count:40, sort:'rating', order:'desc' })
const restaurants = Object.assign(restaurants1,...restaurants2);
res.send(restaurants);
})
As of now I have only tried till 40 but even this does not work. If another constant restaurant3 is there which has start 40 and end 60, how do I merge these three and send them back?
I haven't worked with zomato-api before but according to their API-documentation, you should be able to do this:
app.get('/locations/:query', async (req, res) => {
const query = req.params.query;
const data = await zomato.cities({ q: query,count: 1 })
const cityId= data[0].id;
const result = [];
const nrOfRequests = 2; // change this accordingly to increase the nr of requests
let currCount = 0;
const nrOfEntries = 20;
for(let i=0; i < nrOfRequests ; i++) {
const response = await zomato.search({ entity_id: cityId, entity_type: 'city', start:currCount, count:nrOfEntries, sort:'rating', order:'desc' });
result.push(...response.restaurants);
currCount += nrOfEntries;
}
res.send(result);
});
Basically you should be able to loop for the desired amount of requests by updating the start parameter for each iteration and then storing the resulting restaurants in your result.
You shouldn't be spreading restaurants2 in your Object.assign(). Use one of the following:
const restaurants = Object.assign(restaurants1, restaurants2);
// or
const restaurants = { ...restaurants1, ...restaurants2 };

Firestore simple leaderboard function

I'm tring to write a cloud function that ranks my users under the /mobile_user node by earned_points and assigns them a rank. I have successfully done this but now i want to write those same 10 users to another node called leaderboard. How can i accomplish this?
Here is my current function which already ranks them from 1 to 10:
exports.leaderboardUpdate2 = functions.https.onRequest((req, res) =>{
const updates = [];
const leaderboard = {};
const rankref = admin.firestore().collection('mobile_user');
const leaderboardRef = admin.firestore().collection('leaderboard');
return rankref.orderBy("earned_points").limit(10).get().then(function(top10) {
let i = 0;
console.log(top10)
top10.forEach(function(childSnapshot) {
const r = top10.size - i;
console.log(childSnapshot)
updates.push(childSnapshot.ref.update({rank: r}));
leaderboard[childSnapshot.key] = Object.assign(childSnapshot, {rank: r});
i++;
console.log(leaderboard)
});
updates.push(leaderboardRef.add(leaderboard));
return Promise.all(updates);
}).then(() => {
res.status(200).send("Mobile user ranks updated");
}).catch((err) => {
console.error(err);
res.status(500).send("Error updating ranks.");
});
});
This successfully updates the /mobile_user node where all my users are but i want to "export" those 10 users to the leaderboard node once the function executes.
(Note that the leaderboard node should have only 10 records at all times)
There are two problems in your Cloud Function:
Firstly you cannot directly use the childSnapshot object (neither with Object.assign nor directly) to create a new document. You have to use childSnapshot.data(), see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot
Secondly, you use childSnapshot.key while it should be childSnapshot.id, see the same document than above.
Finally, note that, with your code structure, the users document are added as maps under a unique leaderboard document. I am not sure it is exactly what you want, so you may adapt your code for this specific point.
So the following should work:
exports.leaderboardUpdate2 = functions.https.onRequest((req, res) => {
const updates = [];
const leaderboard = {};
const rankref = admin.firestore().collection('mobile_user');
const leaderboardRef = admin.firestore().collection('leaderboard');
return rankref
.orderBy('earned_points')
.limit(10)
.get()
.then(function(top10) {
let i = 0;
console.log(top10);
top10.forEach(function(childSnapshot) {
const r = top10.size - i;
updates.push(childSnapshot.ref.update({ rank: r }));
leaderboard[childSnapshot.id] = Object.assign(childSnapshot.data(), {
rank: r
});
i++;
});
updates.push(leaderboardRef.add(leaderboard));
return Promise.all(updates);
})
.then(() => {
res.status(200).send('Mobile user ranks updated');
})
.catch(err => {
console.error(err);
res.status(500).send('Error updating ranks.');
});
});
Following your comment, here is a new version, that writes a doc, in the leaderboard Collection, for each mobile_user. Note that we use a DocumentReference and together with the set() method, as follows: leaderboardRef.doc(childSnapshot.id).set()
exports.leaderboardUpdate2 = functions.https.onRequest((req, res) => {
const updates = [];
const leaderboard = {};
const rankref = admin.firestore().collection('mobile_user');
const leaderboardRef = admin.firestore().collection('leaderboard');
return rankref
.orderBy('earned_points')
.limit(10)
.get()
.then(function(top10) {
let i = 0;
console.log(top10);
top10.forEach(function(childSnapshot) {
const r = top10.size - i;
updates.push(childSnapshot.ref.update({ rank: r }));
updates.push(
leaderboardRef.doc(childSnapshot.id).set(
Object.assign(childSnapshot.data(), {
rank: r
})
)
);
i++;
});
return Promise.all(updates);
})
.then(() => {
res.status(200).send('Mobile user ranks updated');
})
.catch(err => {
console.error(err);
res.status(500).send('Error updating ranks.');
});
});

Categories

Resources