Updating a value in JSON file - javascript

I am trying to update quantity and price in cart.json.
addProduct(id) {
// Fetch the previous cart
fs.readFile(p, (err, fileContent) => {
let cart = [];
if (!err) {
cart = JSON.parse(fileContent);
}
// Analyze the cart => Find existing product
const existingProduct = cart.find((prod) => prod.id === id);
const existingProductIndex = cart.findIndex((prod) => prod.id === id);
if (existingProduct) {
cart[existingProductIndex].quantity = (parseFloat(cart[existingProductIndex].quantity) + 1).toString();
cart[existingProductIndex].price = (parseFloat(this.price) * parseFloat(cart[existingProductIndex].quantity)).toString();
} else {
// Add new product
cart.push(this);
}
fs.writeFile(p, JSON.stringify(cart, null, 2), (err) => {
console.log(err);
});
});
}
in cart.json file
[
{
"id": "ee195a4a-4870-4067-af50-c53c1d8f9594",
"title": "Love",
"imageUrl": "...",
"price": "39.98",
"description": "Love the way you lie",
"quantity": "2"
}
]
This is the code I wrote in cartModel.js and it working fine as I expected. But I don't think it's a good code.
Can you guys give me more beautiful code solution?

Actually, the way to add changes in the JSON file is a reading file, adding the changes, and writing a file. In your code, you can add some optimizations like this
addProduct(id) {
// Fetch the previous cart
fs.readFile(p, (err, fileContent) => {
if (err) throw new Error(err.message);
const cart = JSON.parse(fileContent);
// Analyze the cart => Find existing product index
const existingProductIndex = cart.findIndex((prod) => prod.id === id);
if (existingProductIndex >= 0) {
const existingProduct = cart[existingProductIndex];
existingProduct.quantity = (
parseFloat(existingProduct.quantity) + 1
).toString();
existingProduct.price = (
parseFloat(this.price) * parseFloat(existingProduct.quantity)
).toFixed(2);
} else {
// Add new product
cart.push(this);
}
fs.writeFile(p, JSON.stringify(cart, null, 2), (err) => {
if (err) throw new Error(err.message);
});
});
}
You don't need to find a product twice. findIndex will return an index of the product if it exists (for example 0, 1, 34 ....), or -1 if it does not. Also, I suggest using the toFixed function, because the number can be like 12.123232523 so it will be shown as 12.12 if you are using toFixed(2). Also, toFixed converting the number to a string, so you don't need to use toString() for the price.

Related

VS Code debugger not working as expected while debugging Nodejs app

I am debugging a node app and it has a method deleteProduct. Inside this method is two static methods Cart.deleteProductFromCart(productID, product.price) and Product.deleteProductWithId(productID) that read files from the filesystem and write to the file.
Using the VSCode built-in debugger, When I set the breakpoints to see if the callbacks are getting called back properly, only the callback fired from the first method gets registered whereas the callback from the second method never gets called. I also experimented by changing the order of the two methods. And, the result is that only the method which gets called first has its callback registered whereas the callback from the second method is never called. This happens only during the debug session. The program runs as expected with yarn start though.
So, it's really hard to debug the whole process.
I am not sure if this is an issue with the vs-code debugger or something I am doing wrong due to which the callback is not being registered during the debugging process.
URL to the repository is attached here
const deleteProduct = async (req, res, next) => {
const productID = req.params.productID;
const product = await Product.fetchProductWithId(productID);
Cart.deleteProductFromCart(productID, product.price);
Product.deleteProductWithId(productID);
res.redirect(301, '/products');
};
Cart Model
const fs = require('fs');
const path = require('path');
const rootDir = require('../utils/path');
const filePath = path.join(rootDir, 'data', 'cart.json');
class Cart {
static addProduct(productID, productPrice) {
fs.readFile(filePath, (err, data) => {
let cart = { items: [], totalPrice: 0 };
if (!err && data?.length > 0) {
cart = JSON.parse(data);
}
const existingProduct = cart.items.find((item) => item.id === productID);
if (existingProduct) {
existingProduct.quantity++;
} else {
const product = { id: productID, quantity: 1 };
cart.items.push(product);
}
cart.totalPrice = cart.totalPrice + +productPrice;
console.log(existingProduct === cart.items[0]);
fs.writeFileSync(filePath, JSON.stringify(cart));
});
}
// Delete logic
static deleteProductFromCart(productID, productPrice) {
fs.readFile(filePath, (err, data) => {
// Attached breakpoints within this callback
if (err) {
return;
}
const cart = JSON.parse(data);
const product = cart.items.find((item) => item.id === productID);
if (product) {
cart.totalPrice = cart.totalPrice - product.quantity * +productPrice;
cart.items = cart.items.filter((item) => item.id !== productID);
}
fs.writeFileSync(filePath, JSON.stringify(cart));
});
}
}
module.exports = { Cart };
Product Model
const fs = require('fs');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const rootDir = require('../utils/path');
const filePath = path.join(rootDir, 'data', 'products.json');
class Product {
constructor(id, title, price, imageURL, description) {
this.id = id;
this.title = title;
this.price = price;
this.imageURL = imageURL;
this.description = description;
}
save() {
fs.readFile(filePath, (err, data) => {
let products = [];
if (!err && data.length > 0) {
products = JSON.parse(data);
}
if (this.id) {
const productIndex = products.findIndex((item) => item.id === this.id);
products[productIndex] = this;
} else {
this.id = uuidv4();
products.push(this);
}
fs.writeFileSync(filePath, JSON.stringify(products));
});
}
static deleteProductWithId(id) {
fs.readFile(filePath, (err, data) => {
// Attached callback within this callback
let products = [];
if (!err && data.length > 0) {
products = JSON.parse(data);
}
const productIndex = products.findIndex((item) => item.id === id);
products.splice(productIndex, 1);
fs.writeFileSync(filePath, JSON.stringify(products));
});
}
static async fetchProductWithId(id) {
return new Promise((resolve, reject) => {
try {
fs.readFile(filePath, (err, data) => {
if (data) {
const products = JSON.parse(data);
const product = products.find((item) => item.id === id);
resolve(product);
}
resolve({
id: null,
title: 'null',
price: 0,
description: null,
imageURL: null,
});
});
} catch {
reject(err);
}
});
}
static async fetchAll() {
return new Promise((resolve, reject) => {
try {
fs.readFile(filePath, (err, data) => {
if (data.length > 0) {
resolve(JSON.parse(data));
}
resolve([]);
});
} catch {
reject(err);
}
});
}
}
module.exports = { Product };
lauch.json file
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/app.js",
// Auto Restart Debugger on file Change
// nodemon should be installed globally
// Also integrated console should be used so that they both listen to same port or something like that
"restart": true,
"runtimeExecutable": "nodemon",
"console": "integratedTerminal"
}
]
}
To Reproduce
Steps to reproduce the behavior:
Attach breakpoints inside callback of readFile as shown in the snippet in both the static methods
Run the debugger
Observe Callstack or Run Step Over, Step Into to see the program flow
Observe that callback from the second method which read file from the filesystem is never reached till the end of program execution.

How to query a firestore search for a name within a document?

What i have set up for my firestore database is one collection called 'funkoPops'. That has documents that are genres of funkoPops, with an array of funkoData that holds all pops for that genre. it looks like this below
I should also note, that the collection funkoPops has hundreds of documents of 'genres' which is basically the funko pop series with the sub collections of funkoData that I web scraped and now need to be able to search through the array field of 'funkoData' to match the name field with the given search parameter.
collection: funkoPops => document: 2014 Funko Pop Marvel Thor Series => fields: funkoData: [
{
image: "string to hold image",
name: "Loki - with helmet",
number: "36"
},
{
image: "string to hold image",
name: "Black and White Loki with Helmet - hot topic exsclusive",
number: "36"
},
{
etc...
}
So how could i run a query in firestore to be able to search in collection('funkoPops'), search through the document fields for name.
I have the ability to search for genres like so, which gives the genre back and the document with the array of data below:
const getFunkoPopGenre = async (req, res, next) => {
try {
console.log(req.params);
const genre = req.params.genre;
const funkoPop = await firestore.collection("funkoPops").doc(genre);
const data = await funkoPop.get();
if (!data.exists) {
res.status(404).send("No Funko Pop found with that search parameter");
} else {
res.send(data.data());
}
} catch (error) {
res.status(400).send(error.message);
}
};
what i am trying to use to search by the field name is below and returns an empty obj:
const getFunkoPopName = async (req, res, next) => {
try {
const name = req.params.name;
console.log({ name });
const funkoPop = await firestore
.collection("funkoPops")
.whereEqualTo("genre", name);
const data = await funkoPop.get();
console.log(data);
res.send(data.data());
} catch (error) {
res.status(400).send(error);
}
};
Any help would be great, thanks!
So the way i went about answering this as it seems from top comment and researching a little more on firebase, you do you have to match a full string to search using firebase queries. Instead, I query all docs in the collection, add that to an array and then forEach() each funkoData. From there i then create a matchArray and go forEach() thru the new funkoData array i got from the first query. Then inside that forEach() I have a new variable in matches which is filter of the array of data, to match up the data field name with .inlcudes(search param) and then push all the matches into the matchArr and res.send(matchArr). Works for partial of the string as well as .includes() matches full and substring. Not sure if that is the best and most efficient way but I am able to query thru over probably 20k data in 1-2 seconds and find all the matches. Code looks like this
try {
const query = req.params.name.trim().toLowerCase();
console.log({ query });
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArray = [];
if (data.empty) {
res.status(404).send("No Funko Pop records found");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArray.push(funkoObj);
});
const matchArr = [];
funkoArray.forEach((funko) => {
const genre = funko.genre;
const funkoData = funko.funkoData;
const matches = funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(matches).length > 0) {
matchArr.push({
matches,
genre,
});
}
});
if (matchArr.length === 0) {
res.status(404).send(`No Funko Pops found for search: ${query}`);
} else {
res.send(matchArr);
}
}
} catch (error) {
res.status(400).send(error.message);
}
with a little bit of tweaking, i am able to search for any field in my database and match it with full string and substring as well.
update
ended up just combining genre, name, and number searches into one function so that whenver someone searches, the query param is used for all 3 searches at once and will give back data on all 3 searches as an object so that we can do whatever we like in front end:
const getFunkoPopQuery = async (req, res) => {
try {
console.log(req.params);
const query = req.params.query.trim().toLowerCase();
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArr = [];
if (data.empty) {
res.status(404).send("No Funko Pop records exsist");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArr.push(funkoObj);
});
// genre matching if query is not a number
let genreMatches = [];
if (isNaN(query)) {
genreMatches = funkoArr.filter((funko) =>
funko.genre.toLowerCase().includes(query)
);
}
if (genreMatches.length === 0) {
genreMatches = `No funko pop genres with search: ${query}`;
}
// name & number matching
const objToSearch = {
notNullNameArr: [],
notNullNumbArr: [],
nameMatches: [],
numbMatches: [],
};
funkoArr.forEach((funko) => {
const genre = funko.genre;
if (funko.funkoData) {
const funkoDataArr = funko.funkoData;
funkoDataArr.forEach((data) => {
if (data.name) {
objToSearch.notNullNameArr.push({
funkoData: [data],
genre: genre,
});
}
if (data.number) {
objToSearch.notNullNumbArr.push({
funkoData: [data],
genre: genre,
});
}
});
}
});
// find name that includes query
objToSearch.notNullNameArr.forEach((funko) => {
const genre = funko.genre;
const name = funko.funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(name).length > 0) {
objToSearch.nameMatches.push({
genre,
name,
});
}
});
// find number that matches query
objToSearch.notNullNumbArr.forEach((funko) => {
const genre = funko.genre;
const number = funko.funkoData.filter((data) => data.number === query);
if (Object.keys(number).length > 0) {
objToSearch.numbMatches.push({
genre,
number,
});
}
});
if (objToSearch.nameMatches.length === 0) {
objToSearch.nameMatches = `No funko pops found with search name: ${query}`;
}
if (objToSearch.numbMatches.length === 0) {
objToSearch.numbMatches = `No funko pop numbers found with search: ${query}`;
}
const searchFinds = {
genre: genreMatches,
name: objToSearch.nameMatches,
number: objToSearch.numbMatches,
};
res.send(searchFinds);
}
} catch (error) {
res.status(400).send(error.message);
}
};
If anyone is well suited in backend and knows more about firestore querying, please let me know!

How to update a global variable with a sqlite query in javascript?

I want to update a globally declared variable after sqlite query,
but I cant get it to work,
I have read that it might be related to asynchronous functions but I have no idea how to implement callbacks and stuff in this example,
can you guys help please. Here is the code:
const sqlite3 = require('sqlite3').verbose();
const dbPath = './src/db/db.sqlite3';
let db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error(err.message);
}
console.log('Connected to database.');
});
let number = null;
let rowsExist = null;
db.get("select count(*) from PRICE", [], (err, row) => {
if (err) {
console.error(err.message)
}
else {
rowsExist = Object.values(row)[0];
console.log(rowExist) //this works but outside the function it doesnt get updated
}
});
// here rowExist remains the same after query
if (rowsExist === null) {
number = 1
}
else {
db.get("SELECT number FROM PRICE ORDER BY number DESC LIMIT 1", [], (err, row) => {
if (err) {
console.error(err.message)
}
else {
number = Object.values(row)[0] + 1
}
})
};

Get data from dynamoDB using secondary index

I am using node js to get data from dynamoDB. However at the moment I am retrieving the data with the UserId. I want to retrieve the data using the secondary index that I have created, which is dateId. My dynamoDB table consists of userId, Exercises, Sets, Reps and dateId. How can I achieve that?
I already tried to add the Index-name under the table name with the dateId value but it's not working.
My dbHelper.js looks like this.
dbHelper.prototype.getExercises = (userID) => {
return new Promise((resolve, reject) => {
const params = {
TableName: tableName,
KeyConditionExpression: "#userID = :user_id",
ExpressionAttributeNames: {"#userID": "userId"},
ExpressionAttributeValues: {":user_id": userID }
}
docClient.query(params, (err, data) => {
if (err) {
console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
return reject(JSON.stringify(err, null, 2))
}
console.log("GetItem succeeded:", JSON.stringify(data, null, 2));
resolve(data.Items)
})
});
}
My index.js looks like this
const GetExercisesIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'GetExercisesIntent';
},
async handle(handlerInput) {
const {responseBuilder } = handlerInput;
const userID = handlerInput.requestEnvelope.context.System.user.userId;
return dbHelper.getExercises(userID)
.then((data) => {
var speechText = 'Your exercises of the day are '
if (data.length == 0) {
speechText = "You do not have any favourite exercises yet, add exercise by
saving add moviename "
} else {
speechText += data.map(e => e.Exercise).join(", ")
}
return responseBuilder
.speak(speechText)
.reprompt(GENERAL_REPROMPT)
.getResponse();
})
.catch((err) => {
const speechText = "we cannot get your exercise right now. Try again!"
return responseBuilder
.speak(speechText)
.getResponse();
})
}
}
I think your function should look more like this example, based on combining AWS's documentation with your original code.
I think you'll want to supply the partition key for the Index (which I'm assuming is dateId) rather than the table's partition key.
The ProjectionExpression seems to let us specify a list of attributes to return, and I gather that you want just the Exercises attribute for each item matching the specified dateId.
dbHelper.prototype.getExercises = (dateID) => {
return new Promise((resolve, reject) => {
const
tableName = NameOfDynamoDbTable, // Must be the actual table name
indexName = NameOfDynamoDbIndex, // Must be the actual index name on that table
params = {
"TableName": tableName,
"IndexName": indexName,
"KeyConditionExpression": "#dateID = :date_id",
"ExpressionAttributeNames": {"#dateID": "dateId"},
"ExpressionAttributeValues": {":date_id": dateID },
"ProjectionExpression": "Exercises",
"ScanIndexForward": true
};
docClient.query(params, (err, data) => {
if (err) {
console.error("Can't read item. Error JSON:", JSON.stringify(err, null, 2));
return reject(JSON.stringify(err, null, 2));
}
console.log("GetItem succeeded:", JSON.stringify(data, null, 2));
resolve(data.Items)
});
});
};

Loading Records from a child table with a node.js API

Ok,
I'm pretty new to node.js and the whole concept of callback functions, and I've been racking my brain trying to figure this out. Maybe some of you guys/gals can help.
So the basic setup is that I have a React/Redux app on the front-end that uses an API as a data source. On the front, I want to load some number of products at once and load all of their images, then I have a Product component that shows a single product, and would show an image gallery (which is why I need all the product images at once)
I'm trying to write an API the when I request http://myapi.com/randomproducts/ which will return an indicated number of random products from a MySQL database. That part I have. Here's the part that is throwing me... each product that I return in this array of products has an unknown of images associated with it. I want to load those at the same time such that my what I return looks something like this:
[
{
product_id: 0,
product_name: "my cool product",
product_desc: "desc of my cool product",
price: 10.00,
product_images: [
{
product_image_id: 10,
product_image: "img1.jpg",
},
{
product_image_id: 11,
product_image: "img2.jpg",
},
{
product_image_id: 12,
product_image: "img3.jpg",
}
]
},
{
product_id: 2,
product_name: "my other cool product",
product_desc: "desc of my other cool product",
price: 20.00,
product_images: [
{
product_image_id: 13,
product_image: "img21.jpg",
},
{
product_image_id: 14,
product_image: "img22.jpg",
},
{
product_image_id: 15,
product_image: "img23.jpg",
}
]
},
]
... and so on.
So it's clear that there's a parent-child relationship between products (the parent table) and product_images (the child table). Unfortunately, I have had no success creating an API function that will return an array of images which also has it's array of associated images. The ultimate goal is that I want to load multiple products and their associated images at once in one single API call, instead of getting the the products, then getting the images.
Here's a snapshot of the database tables:
CLICK HERE TO VIEW DATABASE TABLES
Here is the code that I have:
1. THE ROUTE
Send a text/json body to this route. It accepts a userId and a number of products to return:
app.route('/multiplerandomproducts/')
.post(products.get_multiple_random_products);
post body: {user_id: <num>, product_count: <num>}
2. GETTING MY PRODUCTS
In my ProductsController.js Controller, I have the following:
exports.get_multiple_random_products = function(req,res) {
var postData = new Product(req.body);
Product.getMultipleRandomProducts(postData.data, function(err,products) {
if(err) {
res.send(err);
} else {
res.json(products);
}
});
}
In my Products.js Model, I have the following:
// get a random product for the userId
Product.getMultipleRandomProducts = function getMultipleRandomProducts(postData, result) {
var userId = postData.user_id;
var productCount = postData.product_count;
var sqlStr = "SELECT DISTINCT a.* FROM product a "
+ " INNER JOIN product_category b ON b.product_id = a.product_id "
+ " INNER JOIN user_category c ON c.category_id = b.category_id "
+ " WHERE c.user_id= ? "
// get only products that the user hasn"t seen before
if(process.env.APP_HIDESEENPRODS === "1") {
sqlStr += " AND a.product_id NOT IN (SELECT c.product_id from seen_product c WHERE c.user_id = ?)";
}
sqlStr += " ORDER BY RAND() LIMIT ?";
sql.query(sqlStr, [userId, userId, productCount], function(err, res) {
if(err) {
result(err, null);
} else {
// hide from the use if this flag is set in the app
if(!process.env.APP_HIDESEENPRODS === "1") {
insertUserSeenProduct(userId, res[0].product_id);
}
result(null,res);
}
});
}
I also have a ProductImages.js model, which as a method to get Images for a particular product, which looks like this:
ProductImage.getAllProductImages = function getAllProductImages(productId, result) {
sql.query("Select * from product_image WHERE product_id = ? ", productId, function (err, res) {
if(err) {
console.log("error: ", err);
result(null, err);
}
else {
result(null, res);
}
});
};
What I've tried:
So this is the version of my function that I've tried to implement. When I indicate that I want three records, I get an object with three null values, i.e., [null,null,null]. I think I'm close, just not seeing what I'm doing wrong....
Product.getMultipleRandomProducts = function getMultipleRandomProducts(postData, result) {
var userId = postData.user_id;
var productCount = postData.product_count;
var sqlStr = "SELECT DISTINCT a.* FROM product a "
+ " INNER JOIN product_category b ON b.product_id = a.product_id "
+ " INNER JOIN user_category c ON c.category_id = b.category_id "
+ " WHERE c.user_id= ? "
// get only products that the user hasn"t seen before
if(process.env.APP_HIDESEENPRODS === "1") {
sqlStr += " AND a.product_id NOT IN (SELECT c.product_id from seen_product c WHERE c.user_id = ?)";
}
sqlStr += " ORDER BY RAND() LIMIT ?";
sql.query(sqlStr, [userId, userId, productCount], function(err, res) {
if(err) {
result(err, null);
} else {
// hide from the use if this flag is set in the app
if(!process.env.APP_HIDESEENPRODS === "1") {
insertUserSeenProduct(userId, res[0].product_id);
}
result(null, res.map((item) => {
sql.query("Select * from product_image WHERE product_id = ? ", item.product_id, function (err, res2) {
if(err) {
console.log("error: ", err);
result(null, err);
}
else {
item.product_images = res2;
}
});
}));
}
});
}
The Problem that I'm running into:
So, I've tried to simply iterate through the returned array of products that I get back within and get the product images, which would work fine if this wasn't asynchronous. But when I try this, I return an object with three products (if I ask for three) but only one (the first) of those products has it's images assigned to it.
In summary, I want to return a single object that looks something like:
Product
image
image
image
Product
image
image
image
Product
image
image
image
So, can someone please, PLEASE tell me how I can do this? I'm still trying to wrap my head around callback functions and I'm about to have a brain aneurysm!
Thanks!!
Because the images of each product were fetch individually, the expected result is what your getting now. In fact, if you use a server (not local), chances are no images return to any of the products.
Use async/await and promisify your function to fetch the production images:
sql.query(sqlStr, [userId, userId, productCount], async function(err, res) {
if(err) {
result(err, null);
} else {
// hide from the use if this flag is set in the app
if(!process.env.APP_HIDESEENPRODS === "1") {
insertUserSeenProduct(userId, res[0].product_id);
}
// Get the list of images thru promisify
const getImages = function(item) {
return new Promise( function(resolve, reject) {
sql.query("Select * from product_image WHERE product_id = ? ", item.product_id, function (err, res2) {
if(err) {
console.log("error: ", err);
reject(result(null, err));
}
else {
item.product_images = res2;
resolve(item);
}
});
});
};
let products = [];
const returnFalse = () => false;
for (let i = 0; i < res.length; i++ ) {
let product = await getImages(res[i]).catch(returnFalse);
if (product) {
products.push(product);
}
}
result( null, products);
}
});

Categories

Resources