Fetching a value from a result? - node.js

So I am fetching values from chuck norris api. I am able to fetch and present values when given one value such as when using random event from the api. My question is, how can I present a value given to me when there is more than one value displayed in a list?
let topic = args.join(" "); //defines topic set as varliable to use in query search
fetch(`https://api.chucknorris.io/jokes/search?query=${topic}`).then(result => result.json());
const { sub } = await fetch(`https://api.chucknorris.io/jokes/search?query=${topic}`).then(result => result.json());
if(topic !== " ") { return message.channel.send(sub)};

It sounds like you're asking how to pick one item at random from an Array. Math.random gives you a random number between 0 and 1. How can you convert this to a random index in an Array? Multiply by the size of your Array first, then round down with Math.floor:
const response = await fetch(`https://api.chucknorris.io/jokes/search?query=${topic}`);
const jokes = await response.json();
const randomIndex = Math.floor(Math.random() * jokes.length);
const randomJoke = jokes[randomIndex];
return message.channel.send(randomJoke)

Easiest way to choose a random value from an array of items is to do the following:
const randomItem = allItems[Math.floor(Math.random() * allItems.length)];

Related

Typescript cloud function to aggregate firestore field value

I have a document where i have two fields;
category
duration in micromilliseconds
Category could have multiple values like category1, category2 etc.
I want to calculate sum of duration of all the documents where category == category1. Please help with the query.
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
admin.initializeApp()
export const updateCharts =
functions.firestore.document('users/{UserId}/count/{uid}')
.onWrite(async(change, _) => await updateStats(change))
async function updateStats (change:
functions.Change<functions.firestore.DocumentSnapshot>){
const chartRating = change.after.ref.parent
let title = (await chartRating.where('Title', '==', 'Game').get()).size;
//duration
const restaurantRef = chartRating.parent!
console.log('{restaurantRef.path} now has ${Title}')
await restaurantRef.update({
Title: Title,
})
Once you fetch all the documents using a query it's you can use reduce() to find total duration:
let title = await chartRating.where('Title', '==', 'Game').get();
const totalDuration = title.docs.reduce((a, b) => a + b.data()["duration"], 0)
// Adds 'duration' field of all documents
A cheaper alternative would be to read previous total from restaurantRef, subtracting old value of that's document's duration and adding the updated value. This will cost only 1 read and 1 write operation whereas reading all documents will cost N reads where N is number of documents matched.

Mongoose | Reuse query object

I am trying to compute the skip and limit with the total number of documents (count).
The issue is that the query object returns the count value when I try to get the items.
Follows an example:
const query = MyModel.find().or([AbilityRule1, AbilityRule2, ...]);
const count = await query.countDocuments(); // count = 3
// Some logic to compute the values of `skip` and `limit` with `count`
// const skip = ...
// const limit = ...
const items = await query.skip(skip).limit(limit); // items = 3 instead of [Model, Model, Model]
I found myself with a similar question when I was trying to implement pagination. The answer I came up with was to use the merge function on the Query object.
const query = MyModel.find().or([AbilityRule1, AbilityRule2, ...]);
const count = await MyModel.find().merge(query).countDocuments();
const items = await query.skip(skip).limit(limit);
Source: https://mongoosejs.com/docs/api/query.html#query_Query-merge
countDocuments() looks to be a method of Model, not Query. I guess in the way you're using it here by calling it on an existing query object, you may be just overwriting it.
Why not just:
const query = MyModel.find();
const count = await MyModel.countDocuments();
// ...
const items = await query.skip(skip).limit(limit);
Inspired by Mathew's answer:
I am adding this answer because I find it important that the second and third instructions do not depend on MyModel, they just depend on the query object.
const query = MyModel.find().or([AbilityRule1, AbilityRule2, ...]);
const count = await query.model.find().merge(query).countDocuments();
const items = await query.skip(skip).limit(limit);

Mongoose, NodeJS & Express: Sorting by column given by api call

I'm currently writing a small API for a cooking app. I have a Recipe model and would like to implement sorting by columns based on the req Parameter given.
I'd like to sort by whatever is passed in the api call. the select parameter works perfectly fine, I can select the columns to be displayed but when I try to sort anything (let's say by rating) the return does sort but I'm not sure what it does sort by.
The code i'm using:
query = Recipe.find(JSON.parse(queryStr));
if(req.query.select){
const fields = req.query.select.split(',').join(' ');
query = query.select(fields);
}
if(req.query.sort){
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort({ sortBy: 1 });
} else {
query = query.sort({ _id: -1 });
}
The result, when no sorting is set: https://pastebin.com/rPLv8n5s
vs. the result when I pass &sort=rating: https://pastebin.com/7eYwAvQf
also, when sorting my name the result is also mixed up.
You are not using the value of sortBy but the string "sortBy". You will need to create an object that has the rating as an object key.
You need the sorting object to look like this.
{
rating: 1
}
You can use something like this so it will be dynamic.
if(req.query.sort){
const sortByKey = req.query.sort.split(',').join(' ');
const sortByObj = {};
sortByObj[sortByKey] = 1; // <-- using sortBy as the key
query = query.sort(sortByObj);
} else {
query = query.sort({ _id: -1 });
}

Filtering Out unnecessary portions of scraped data

I am trying to make a scraper that scrapes Post ID and Poster's ID from a Facebook public post link, using puppeteer and nodejs.
(async() => {
let url = 'https://m.facebook.com/photo/?fbid=1168301430177531&set=gm.1386874671702414'; //demo link
let brw = await puppeteer.launch();
let page = await brw.newPage();
await page.goto(url,{ waitUntil:'networkidle2'});
let data = await page.evaluate(()=>{
let ids = document.querySelector('div[class="_57-p"] > a[class="_57-s touchable"]').search; // for
image post
return{
ids
}
});
console.log(data);
and I get output like:
{
ids: '?fbid=1168301430177531&id=100009930549147&set=gm.1386874671702414&refid=13&__tn__=%2B%3E'
}
how can I filter out the unnecessary portions?(I just want fbid and id values)
Thanks in advance
It seems this is the most reliable and simple way:
const href = document.querySelector('div[class="_57-p"] > a[class="_57-s touchable"]').href;
const searchParams = new URL(href).searchParams;
return {
fbid: searchParams.get('fbid'),
id: searchParams.get('id'),
};
Try use query-string.
it will help you to parse the query strings
let search = '?foo=bar'
const parsed = queryString.parse(search);
console.log(parsed);
//=> {foo: 'bar'}
it is just a simple example of what you should do
You can use a match() method with a regex like /\Wfbid=(\w+)(\W|$)/, the search result under index 1 of the capturing groups will contain the desired parameter value.
let ids = '?fbid=1168301430177531&id=100009930549147&set=gm.1386874671702414&refid=13&__tn__=%2B%3E'
const fbid = ids.match(/\Wfbid=(\w+)(\W|$)/)[1] // 1168301430177531
const id = ids.match(/\Wid=(\w+)(\W|$)/)[1] // 100009930549147
Without [1] you'd get all matches, e.g.:
ids.match(/\Wid=(\w+)(\W|$)/)
=>
["&id=100009930549147&", "100009930549147", "&", index: 22, input: "?fbid=1168301430177531&id=100009930549147&set=gm.1386874671702414&refid=13&__tn__=%2B%3E", groups: undefined]
And you need the string between the capturing & characters, the 2nd element of the array (so: [1]).

Get data by id from firebase

I use firebase on node.js .
My given structure should look like this:
{
...
batch-1:
id-1(suppose):
name:...
phone:...
id-2:
...
id-3:
...
batch-2:
...
batch-3:
...
...
batch-n:
...
}
How can I get an id-1 object by its identifier in such an architecture?
Does the database have to go around all the batches?
Is there a better solution?
The main task: Create a batch with many objects that will have SHORT and a UNIQUE identifier and optimally receive data by this identifier
To search for a particular ID that is a child of a list of unknown IDs, you need to use orderByChild(). In your use case, you are looking for a particular ID in a list of batch IDs. If you used orderByChild() on this list, you would get back results for each and every batch ID, even if it didn't have the ID you wanted. This is because even null (non-existant) values are included (and sorted at the start) in the results. To get the data of the desired ID, you would get the data for the last result of the query, which if it existed, would be sorted to the end of the list. Note that if the desired ID doesn't exist, the last result (if there are any results) would have a null value. To return only the last result of the query, you would use limitToLast(1).
Putting this all together, gives the following code:
let idToFind = "unique-id-1";
let batchesRef = firebase.database().ref(); // parent key of "batch-1", "batch-2", etc.
// assumed to be the database root here
batchesRef.orderByChild(idToFind).limitToLast(1).once('value')
.then((querySnapshot) => {
if (!querySnapshot.numChildren()) { // handle rare no-results case
throw new Error('expected at least one result');
}
let dataSnapshot;
querySnapshot.forEach((snap) => dataSnapshot = snap); // get the snapshot we want out of the query's results list
if (!dataSnapshot.exists()) { // value may be null, meaning idToFind doesn't exist
throw new Error(`Entry ${idToFind} not found.`);
}
// do what you want with dataSnapshot
console.log(`Entry ${idToFind}'s data is:`, dataSnapshot.val());
})
.catch((error) => {
console.log("Unexpected error:", error);
})
For small data sets, the above code will work just fine. But if the list of batches starts growing quite large, you may wish to build an index that maps a particular ID to the batch ID that contains it.
Here is my method which allows you to search by id or to search by key value such as email uniqueemail
// gets primary key
const getSnapshotValKey = snapshot => (Object.keys(snapshot).length > 0 ? Object.keys(snapshot)[0] : null)
const getUser = async ({ id, key, value }) => {
let user = null
const ref = id ? '/users/' + id : 'users'
const userRef = admin.database().ref(ref)
const valueRef = id ? userRef : await userRef.orderByChild(key).equalTo(value)
const snapshot = await valueRef.once('value')
const val = snapshot.val()
if (val) {
const key = id || getSnapshotValKey(val)
user = {
id: key,
...(id ? val : val[key]),
}
}
return user
}

Resources