ES6 : Object restructuration for mailchimp api - object

I want to construct a object base on an array and another object.
The goal is to send to mailchimp api my users interests, for that, I've got :
//Array of skills for one user
const skillsUser1 = ["SKILL1", "SKILL3"]
//List of all my skills match to mailchimp interest group
const skillsMailchimpId = {
'SKILL1': 'list_id_1',
'SKILL2': 'list_id_2',
'SKILL3': 'list_id_3',
}
//Mapping of user skill to all skills
const outputSkills = skillsUser1.map((skill) => skillsMailchimpId[skill]);
console.log(outputSkills);
The problem is after, outputSkill get me an array :
["ID1", "ID3"]
But what the mailchimp api need, and so what I need : :
{ "list_id_1": true,
"list_id_2": false, //or empty
"list_id_3" : true
}

A simple way would be this (see comments in code for explanation):
// Array of skills for one user
const skillsUser1 = ["SKILL1", "SKILL3"]
// List of all my skills match to mailchimp interest group
const skillsMailchimpId = {
'SKILL1': 'list_id_1',
'SKILL2': 'list_id_2',
'SKILL3': 'list_id_3',
}
// Create an output object
const outputSkills = {};
// Use `Object.entries` to transform `skillsMailchimpId` to array
Object.entries(skillsMailchimpId)
// Use `.forEach` to add properties to `outputSkills`
.forEach(keyValuePair => {
const [key, val] = keyValuePair;
outputSkills[val] = skillsUser1.includes(key);
});
console.log(outputSkills);
The basic idea is to loop over skillsMailchimpId instead of skillsUser.
But that is not very dynamic. For your production code, you probably want to refactor it to be more flexible.
// Array of skills for one user
const skillsUser1 = ["SKILL1", "SKILL3"]
// List of all my skills match to mailchimp interest group
const skillsMailchimpId = {
'SKILL1': 'list_id_1',
'SKILL2': 'list_id_2',
'SKILL3': 'list_id_3',
}
// Use `Object.entries` to transform `skillsMailchimpId` to array
const skillsMailchimpIdEntries = Object.entries(skillsMailchimpId);
const parseUserSkills = userSkills => {
// Create an output object
const outputSkills = {};
// Use `.forEach` to add properties to `outputSkills`
skillsMailchimpIdEntries.forEach(([key, val]) => {
outputSkills[val] = userSkills.includes(key);
});
return outputSkills;
}
// Now you can use the function with any user
console.log(parseUserSkills(skillsUser1));

Related

Best way to search march in an array of JavaScript Objects?

I'm facing a challenge to match values within an array of JS objects. Let me give you an example,
var dynamicObjectArray = [
{Id: 1422859025, FromTime: "2023-02-12T19:00:00Z", ToTime: "2023-02-12T20:00:00Z"},
{Id: 1422859027, FromTime: "2023-02-12T18:00:00Z", ToTime: "2023-02-12T19:00:00Z"}
]
I need to find all Ids for FromTime or ToTime match with "2023-02-12T19:00:00Z"
Using a database, it can be done easily. But, here, I need to play an array of 100 objects max. Also, data will be dynamic. Can you please suggest me a way to achieve the goal using NodeJS.
You could use the JS Date builtin constructor to compare dates instead of using the string directly.
Filter your matching objects, and map the ids.
You can do something like this.
const dynamicObjectArray = [{
Id: 1422859025,
FromTime: "2023-02-12T19:00:00Z",
ToTime: "2023-02-12T20:00:00Z"
},
{
Id: 1422859027,
FromTime: "2023-02-12T18:00:00Z",
ToTime: "2023-02-12T19:00:00Z"
}
];
const matchTime = new Date("2023-02-12T19:00:00Z").getTime();
const matchIds = dynamicObjectArray.filter(obj => {
const fromTime = new Date(obj.FromTime).getTime();
const toTime = new Date(obj.ToTime).getTime();
return matchTime === fromTime || matchTime === toTime;
}).map(obj => obj.Id);
console.log(matchIds);
If you want a suggestion, you can do something like this:
Create a function that takes as a parameter, the wanted Date
Create a variable containing an array where you will save all those "id's" that match your condition
Create a for loop
Create an if condition, that matches the following condition:
if date exists on "FromTime" or exists on "ToTime", push to your "id's array"
Return your ids array
Here is the code implementation:
function filterObjByDate(TO_MATCH_DATE) {
let ids = [];
for (let i = 0; i < dynamicObjectArray.length; i++) {
if (
dynamicObjectArray[i].FromTime === TO_MATCH_DATE ||
dynamicObjectArray[i].ToTime === TO_MATCH_DATE
) {
ids.push(dynamicObjectArray[i].Id);
}
}
return ids
}
That's it. If you have more question, ask me 😀👍

Best way to organize firebase writes on update trigger

There may be more than one correct answer to this question, but here's my issue: I have a user document in firebase with many fields that can be updated and which interact in different ways. If one field is updated, it may require a change to another field on the backend. Is it better to have a whole bunch of if statements each with their own write action if the condition is met or, or do single write at the end of the function for all the fields that might change. If a field does not change, I would have to write its original value back to itself. That seems clunky, but so does the other option. Am I missing a third option? What is the best practice here?
Here's an example of what I'm talking about. Updating fields one at a time is what I have now, which looks like this:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
// user levels up and gets more HP
if(beforeData.userLevel != afterData.userLevel){
const newMaxHP = 15 + 5 * afterData.userLevel;
change.after.ref.update({
maxHp: newMaxHP
})
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
change.after.ref.update({
userRating: newRating
})
}
//replenish user funds from zero
if (afterData.money == 0){
change.after.ref.update({
money: 20
})
}
If I did it all in a single write, the if statements would assign a value to a variable, but not update the firestore document. Each if statement would include an else statement assigning the variable to the field's original value. There would be a single write at the end like this:
change.after.ref.update({
maxHp: newMaxHP,
userRating: newRating,
money: 20
})
I hope that helps.
[edit to add follow-up question about updating a map value]
#Dharmaraj's answer works great, but I'm struggling to apply it when updating a map value. BTW - I'm using Typescript.
Before using #Dharmaraj's solution, I was doing this:
admin.firestore().collection("users").doc(lastPlayerAttacker).update({
"equipped.weapon.usesLeft": admin.firestore.FieldValue.increment(-1)
});
Using the update object, I'm trying it like this, but I get the error "Object is of type 'unknown'"
const lastPlayerUpdates:{[key:string]:unknown} = {};
lastPlayerUpdates.equipped.weapon.usesLeft = admin.firestore.FieldValue.increment(-1);
admin.firestore().collection("users").doc(lastPlayerAttacker).update(lastPlayerUpdates);
Any advice on how to fix it?
Every time you call update(), you are being charged for 1 write operation. It'll be best to accumulate all updated fields in an object and then update the document only once as it'll be more efficient too. Try refactoring the code as shown below:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
const updatedData = {};
// user levels up and gets more HP
if (beforeData.userLevel != afterData.userLevel) {
const newMaxHP = 15 + 5 * afterData.userLevel;
updatedData.maxHp = newMaxHP;
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
updatedData.userRating = newRating;
}
//replenish user funds from zero
if (afterData.money == 0) {
updatedData.money = 20;
}
await change.after.ref.update(updatedData);
console.log("Data updated");
return null;
})

Firebase NodeJS SDK: Query by value on nested object

I have a collection/table that is structured like this:
{
UsedPromos: {
"userid_1": {
"product_1_1": "promo_1_1",
"product_1_2": "promo_1_2",
...
"product_1_n": "promo_1_n",
},
"userid_2": {
"product_2": "promo_2"
},
...
"userid_m": {
...
}
}
}
How can I query an exact match to some "promo_x_y"? I have tried this so far:
const admin = require("firebase-admin");
admin.initializeApp(...);
const ref = admin.database().ref("/UsedPromos");
ref.orderByValue()
.equalTo("promo_x_y")
.once("child_added")
.then((snapshot) => {
console.log(`{key: ${snapshot.key}, value: ${snapshot.val()}}`);
return snapshot
});
But it didn't return anything.
If you are looking for all products that are part of the promotion promo_x_y, you need to adjust your query one level deeper than what you are currently doing.
Currently you are comparing the values of user_1, user_2, and so on to the value "promo_x_y". You get no results, because no entry /UsedPromos/user_1 = "promo_x_y" exists.
/UsedPromosByUser/{user}/{product} = {promo} (your current structure)
To fix this, you will need to search an individual user's list of products. Using the below snippet will log each product that has a value of "promo_x_y".
const admin = require("firebase-admin");
admin.initializeApp(/* ... */);
const ref = admin.database().ref("/UsedPromos");
const userToSearch = "user_1";
ref.child(userToSearch)
.orderByValue()
.equalTo("promo_x_y")
.once("child_added")
.then((snapshot) => {
// snapshot.val() will always be "promo_x_y", so don't bother logging it.
console.log(`${snapshot.key} uses "promo_x_y"`);
return snapshot;
});
Depending on your use case, it may be better to use a "value" event instead.
const admin = require("firebase-admin");
admin.initializeApp(/* ... */);
const ref = admin.database().ref("/UsedPromos");
const userToSearch = "user_1";
ref.child(userToSearch)
.orderByValue()
.equalTo("promo_x_y")
.once("value")
.then((querySnapshot) => {
const productKeys = [];
// productSnapshot.val() will always be "promo_x_y", so don't bother logging it.
querySnapshot.forEach((productSnapshot) => productKeys.push(productSnapshot.key));
console.log(`The following products have a value of "promo_x_y" for user "${userToSearch}": ${productKeys.join(", "}`);
return productKeys;
});
If you are looking to find all products, across all users, that use "promo_x_y", you should create an index in your database instead of using a query.
/UsedPromosByPromo/{promo}/{user}/{product} = true
OR
/UsedPromosByPromo/{promo}/{product}/{user} = true
Instead of using true in the above structure, you could store a timestamp (time of purchase, time promo expires, etc)

Array list with 2 values and doing it to a top 10 list

im working on a Discord bot and have a reputation system with fs (npm package) and saving peoples reps in a file and doing the file name as they discord id
now im working on a top 10 command and would need some help here, i currently have this as code:
let users = [];
let reps = [];
fs.readdirSync('./data/reps/').forEach(obj => {
users.push(obj.replace('.json', ''))
let file = fs.readFileSync(`./data/reps/${obj}`)
let data = JSON.parse(file)
reps.push(data.reps)
})
let top = [...users, ...reps]
top.sort((a,b) => {a - b})
console.log(top)
the files form the users are like this:
{
"users": [
"437762415275278337"
],
"reps": 1
}
users are the current users that can't rep the persion anymore and don't need to use it in the command
i wan to get the top 10 of reps so that i can get the user id and how many reps they have, how could i do it with the code above?
You could try this
const topTen = fs.readdirSync('./data/reps/').map(obj => {
const file = fs.readFileSync(`./data/reps/${obj}`);
const data = JSON.parse(file);
return { ...data, name: obj.replace('.json', '') };
}).sort((a, b) => a.reps - b.reps).slice(0, 10);
console.log(topTen);
I would change how you push the data
const users = [];
fs.readdirSync('./data/reps/').forEach(obj => {
let file = fs.readFileSync(`./data/reps/${obj}`)
let data = JSON.parse(file)
reps.push({ reps: data.reps, id: obj.replace(".json", "") });
})
That way when you sort the array the id goes along with
//define this after the fs.readdirSync.forEach method
const top = users.sort((a,b)=> a.reps-b.reps).slice(0,10);
If you want an array of top ids
const topIds = top.map(e => e.id);
If you want a quick string of it:
const str = top.map(e => `${e.id}: ${e.reps}`).join("\n");
Also you should probably just have one or two json files, one would be the array of user id's and their reps and then the other could be of user id's and who they can't rep anymore

setting context with list of objects as prameters in dialogflow

I have a list of values each having another KEY value corresponding to it, when i present this list to user, user has to select a value and agent has to call an external api with selected value's KEY. how can i achieve this in dialogflow?
I tried to send the entire key value pair in the context and access it in the next intent but for some reason when i set a list(array) to context parameters dialogflow simply ignoring the fulfillment response.
What is happening here and is there any good way to achieve this? I am trying to develop a food ordering chatbot where the category of items in menu is presented and list items in that menu will fetched when user selects a category, this menu is not static thats why i am using api calls to get the dynamic menu.
function newOrder(agent)
{
var categories = []
var cat_parameters = {}
var catarray = []
const conv = agent.conv();
//conv.ask('sure, select a category to order');
agent.add('select a category to order');
return getAllCategories().then((result)=>{
for(let i=0; i< result.restuarantMenuList.length; i++)
{
try{
var name = result.restuarantMenuList[i].Name;
var catid = result.restuarantMenuList[i].Id;
categories.push(name)
//categories.name = catid
cat_parameters['id'] = catid;
cat_parameters['name'] = name
catarray.push(cat_parameters)
}catch(ex)
{
agent.add('trouble getting the list please try again later')
}
}
agent.context.set({
name: 'categorynames',
lifespan: 5,
parameters: catarray, // if i omit this line, the reponse is the fultillment response with categories names, if i keep this line the reponse is fetching from default static console one.
})
return agent.add('\n'+categories.toString())
})
function selectedCategory(agent)
{
//agent.add('category items should be fetched and displayed here');
var cat = agent.parameters.category
const categories = agent.context.get('categorynames')
const cat_ob = categories.parameters.cat_parameters
// use the key in the catarray with the parameter cat to call the external API
agent.add('you have selected '+ cat );
}
}
The primary issue is that the context parameters must be an object, it cannot be an array.
So when you save it, you can do something like
parameters: {
"cat_parameters": catarray
}
and when you deal with it when you get the reply, you can get the array back with
let catarray = categories.parameters.cat_parameters;
(There are some other syntax and scoping issues with your code, but this seems like it is the data availability issue you're having.)

Resources