I'm having two issues this moment
I am having trouble displaying the data from my firestore document after the user signs in and they do have a document with data
I have a button that lets the user upload a document and I will parse it to extract all important data. That data is then put into the document with Ids, which is basically the userId + what number doc it is belonging to the user. When they upload the data it seems that all data extracted uses the same ID and only the last one extracted gets displayed.
this is my code:
const loansRef = firebase.firestore().collection('goals');
const [ courseGoals, setCourseGoals ] = useState([]);
const [goalCounter, setGoalCounter] = useState(0)
//let the user upload doc, what gets called when they press button
const pickDocument = async () => {
try {
let input = await DocumentPicker.getDocumentAsync({
type: "text/plain",
});
setUserOut(await FileSystem.readAsStringAsync(input.uri));
} catch (error) {
console.log(error);
}
createLoans();
};
//extracts important info and returns array with all info for one loan
const fileParser = () => {
const parsedLoans = [];
var newUserOut = userOut;
if (newUserOut.length == 0) {
return;
}
//remove the grants
var grantPos = newUserOut.search("Grant Type:");
var pos = newUserOut.search("Loan Type:");
//hopefully just the loans now
newUserOut = newUserOut.slice(pos, grantPos);
while (newUserOut.length > 0) {
var lastPos = newUserOut.lastIndexOf("Loan Type:");
parsedLoans.push(newUserOut.slice(lastPos, newUserOut.length));
newUserOut = newUserOut.slice(0, lastPos);
}
//console.log('parsed loans: ' + parsedLoans)
return parsedLoans;
};
//where we actually create loans and get the important data for each loan
const createLoans = () => {
const newLoans = fileParser();
const title= 'Loan Amount:$'
const interest = 'Loan Interest Rate:'
for(let i =0; i < newLoans.length; i++){
let loan = newLoans[i]
let goalTitle=loan.substring(loan.indexOf(title)+title.length,loan.indexOf('Loan Disbursed Amount:'))
//console.log("goalTitle: " + goalTitle)
let interestRate = loan.substring(loan.indexOf(interest)+interest.length,loan.indexOf('Loan Repayment Plan Type'))
//console.log("Interest rate: "+ interestRate)
let years = 0
let paidOff = 0
addGoalHandler(goalTitle,interestRate,years,paidOff)
}
return
};
useEffect(() => {
getPW()
let isMounted = true;
if (isMounted) {
loansRef.doc(userId).onSnapshot(
(docSnapshot) => {
if(!docSnapshot.exists){console.log('doc doesnt exist, start from scratch')}
else{
console.log('loaded successfully '+docSnapshot.data())
setGoalCounter(docSnapshot.data().loans.length)
console.log(goalCounter)
}
},
(error) => {
console.log(error);
}
);
}
return () => {
isMounted = false;
};
}, []);
const addGoalHandler = (goalTitle, interestRate, years, paidOff) => {
//setCourseGoals([...courseGoals, enteredGoal])
setGoalCounter(goalCounter+1)
setCourseGoals((prevGoals) => [
...courseGoals,
{
id:userId.toString() + goalCounter.toString(),
value: goalTitle,
interest: interestRate,
years: years,
paidOff: paidOff
}
]);
addToFB(goalTitle, interestRate,years,paidOff)
//var oldIDS = docIDS
//oldIDS.push(userId.toString() + goalCounter.toString())
//setDocIDS(oldIDS)
setIsAddMode(false);
};
const cancelGoalAdditionHandler = () => {
setIsAddMode(false);
};
const addToFB = async (goalTitle, interestRate, years, paidOff) => {
//adding data to firebase, takes into account if doc exists already
const loadDoc = await loansRef.doc(userId).get()
.then((docSnapshot)=> {
if(docSnapshot.exists){
loansRef.doc(userId).onSnapshot((docu)=>{
const updateLoansArr = loansRef.doc(userId).update({
loans: firebase.firestore.FieldValue.arrayUnion({
id: userId+goalCounter.toString(),
value: goalTitle,
interest: interestRate,
years: years,
paidOff: paidOff
})
})
//setGoalCounter(goalCounter+1)
})
}
else{
const addDoc = loansRef.doc(userId).set({
loans: firebase.firestore.FieldValue.arrayUnion({
id: userId+goalCounter.toString(),
value: goalTitle,
interest: interestRate,
years: years,
paidOff: paidOff
})
})
//setGoalCounter(goalCounter+1)
}})
//setGoalCounter(goalCounter+1)
}
const removeGoalHandler = async (goalId) => {
/*
setCourseGoals((currentGoals) => {
loansRef.doc(goalId).delete().then(console.log('removed correctly'))
return currentGoals.filter((goal) => goal.id !== goalId);
});
setGoalCounter(goalCounter-1)
*/
//need to use the value and not the goalId
const removeGoal = await loansRef.doc(goalId).update({
goals: firebase.firestore.FieldValue.arrayRemove(goalId)
})
setCourseGoals((currentGoals)=> {
return currentGoals.filer((goal)=> goal.id !== goalId)
})
};
Related
I'm trying to create an anti-crash function, but got confused at the moment that the channel does not return the author. How can I get the author in another way?
I tried to connect to AuditLogEvent, but it didn't work
My code:
const { AuditLogEvent } = requier('discord.js')
const usersMap = new Map();
const LIMIT = 3;
const TIMES = 10000
bot.rest.on('channelDelete', async channel => {
const fetchedLogs = await channel.guild.fetchAuditLogs({
limit: 1,
type: AuditLogEvent.ChannelDelete,
})
const deletionLog = fetchedLogs.entries.first();
const { executor, target } = deletionLog
if(channel.guild.id != "940990129307263046") return
if(usersMap.has(executor.id)) {
const userData = usersMap.get(executor.id);
const { lastDelete, timer } = userData;
let deleteCount = userData.deleteCount;
const tim = channel.createdTimestamp - lastDelete.createdTimestamp
if(tim > TIMES) {
usersMap.delete(executor.id)
} else {
++deleteCount;
if(parseInt(deleteCount) === LIMIT) {
executor.ban()
}
}
}
})
I am trying to write a function that takes into account 3 conditions whenever Stores/{storeId}/{departmentId}/{productId} gets triggered and write new data in ref.child('Home').child('Chiep').child(departmentId).child(productId).
1) When there is no data in firestore, I need to fill up all the fields in Realtime DB, by making queries in 2 different firestore's nodes: Stores and Products in order to take their images.
2) When a change is made in Stores node and it comes from the same {storeId}, I just need to update some data without making any additional query.
3) And finally, when a change is made in Stores node and it comes from other {storeId}, I need to make only one query in the Stores node.
exports.homeChiepest = functions.firestore
.document('Stores/{storeId}/{departmentId}/{productId}')
.onWrite((change, context) => {
const storeId = context.params.storeId;
const departmentId = context.params.departmentId;
const productId = context.params.productId;
const ref = admin.database().ref();
// Get an object with the current document value.
// If the document does not exist, it has been deleted.
const document = change.after.exists ? change.after.data() : null;
// Get an object with the previous document value (for update or delete)
const oldDocument = change.before.exists ? change.before.data() : null;
// Prevent infinite loops
if (!change.after.exists) {
console.log('DATA DELETED RETURN NULL');
return null;
}
const newPrice = document.price;
const newTimestamp = document.timestamp;
return ref.child('Home').child('Chiep')
.child(departmentId).child(productId)
.once('value')
.then(dataSnapshot => {
if (dataSnapshot.val() !== null) {
console.log('CHIEP DOES exist');
const oldPrice = dataSnapshot.val().price;
const storeKey = dataSnapshot.val().storeKey;
if (storeId === storeKey) {
console.log('SAME STORE - Change price and timestamp');
var newChiepest = {
timestamp: newTimestamp,
price: newPrice
};
return dataSnapshot.ref.update(newChiepest);
} else {
console.log('OTHER STORE - Verify if price is chieper...');
if (newPrice <= oldPrice) {
console.log('NEW PRICE: '+newPrice+' is chieper than the older one: '+oldPrice);
return change.after.ref.parent.parent.get().then(doc => { // HERE Avoid nesting promises
newStoreImg = doc.data().image;
var newStoreChiep = {
price: newPrice,
storeImg: newStoreImg,
storeKey: storeId,
timestamp: newTimestamp
};
return dataSnapshot.ref.update(newStoreChiep);
});
} else {
console.log('NEW PRICE: '+newPrice+' is mode EXPENSIVE than the older one: '+oldPrice);
}
return null;
}
} else {
console.log('data does NOT exist, so WRITE IT!');
let getStoreData = change.after.ref.parent.parent.get();
let getProductData = admin.firestore().collection('Products').doc('Departments').collection(departmentId).doc(productId).get();
return Promise.all([getStoreData, getProductData]).then(values => { // HERE Avoid nesting promises
const [store, product] = values;
var newHomeChiepest = {
depId: departmentId,
price: newPrice,
prodImg: product.data().image,
prodKey: productId,
storeKey: storeId,
storeImg: store.data().image,
timestamp: newTimestamp
};
return dataSnapshot.ref.set(newHomeChiepest);
});
}
})
.catch(error => {
console.log('Catch error reading Home: ',departmentId ,'/', productId,'; message: ',error);
return false;
});
});
The problem is: different possibilities of querying or not querying another firestore node led me to a warning while uploading the Clound Function, that is:
warning Avoid nesting promises promise/no-nesting
I appreciate any help to refactor this code.
You could use a variable to manage a "shunting", depending on the different cases, as follows (untested):
exports.homeChiepest = functions.firestore
.document('Stores/{storeId}/{departmentId}/{productId}')
.onWrite((change, context) => {
const storeId = context.params.storeId;
const departmentId = context.params.departmentId;
const productId = context.params.productId;
const ref = admin.database().ref();
const document = change.after.exists ? change.after.data() : null;
// Prevent infinite loops
if (!change.after.exists) {
console.log('DATA DELETED RETURN NULL');
return null;
}
const newPrice = document.price;
const newTimestamp = document.timestamp;
let shunting; // <-- We manage the shunting through this variable
let chiepRef;
return ref.child('Home').child('Chiep')
.child(departmentId).child(productId)
.once('value')
.then(dataSnapshot => {
chiepRef = dataSnapshot.ref;
if (dataSnapshot.val() !== null) {
console.log('CHIEP DOES exist');
const oldPrice = dataSnapshot.val().price;
const storeKey = dataSnapshot.val().storeKey;
if (storeId === storeKey) {
shunting = 1
console.log('SAME STORE - Change price and timestamp');
var newChiepest = {
timestamp: newTimestamp,
price: newPrice
};
return chiepRef.update(newChiepest);
} else {
console.log('OTHER STORE - Verify if price is chieper...');
if (newPrice <= oldPrice) {
console.log('NEW PRICE: ' + newPrice + ' is chieper than the older one: ' + oldPrice);
shunting = 2
return change.after.ref.parent.parent.get();
} else {
console.log('NEW PRICE: ' + newPrice + ' is mode EXPENSIVE than the older one: ' + oldPrice);
shunting = 3
return null;
}
}
} else {
console.log('data does NOT exist, so WRITE IT!');
shunting = 4;
let getStoreData = change.after.ref.parent.parent.get();
let getProductData = admin.firestore().collection('Products').doc('Departments').collection(departmentId).doc(productId).get();
return Promise.all([getStoreData, getProductData])
}
})
.then(result => {
if (shunting === 2) {
const newStoreImg = result.data().image;
var newStoreChiep = {
price: newPrice,
storeImg: newStoreImg,
storeKey: storeId,
timestamp: newTimestamp
};
return chiepRef.update(newStoreChiep);
} else if (shunting === 4) {
const [store, product] = result;
const newHomeChiepest = {
depId: departmentId,
price: newPrice,
prodImg: product.data().image,
prodKey: productId,
storeKey: storeId,
storeImg: store.data().image,
timestamp: newTimestamp
};
return chiepRef.set(newHomeChiepest);
} else {
return null;
}
})
.catch(error => {
console.log('may be adapted, function of shunting', error);
return null;
});
});
I have problems with my money app. When I add/delete data from my app (products collection), my app do function "sumPrices()" more than one. For example: When I add one product, make once, add another product, make twice, add another product make three etc. This happen in the same way with delete data.
A do something wrong in my code?
Callback.push push data do array where I unsubscribe events from firebase.
AddStatsUI add UI to my DOM.
index.js:
// delete products
const handleTableClick = e => {
console.log(e); // mouseevent
if (e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.parentElement.getAttribute('data-id');
db.collection('users')
.doc(user.uid)
.collection('products')
.doc(id)
.delete()
.then(() => {
// show message
updateMssg.innerText = `Product was deleted`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
productUI.delete(id);
products.sumPrices(user.uid, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user.uid).get().then(snapshot => {
sumStats.addStatsUI(value[0], snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
})
}
}
table.addEventListener('click', handleTableClick);
callbacks.push(() => table.removeEventListener('click', handleTableClick))
//add new products to firebase
const handleExpenseFormSubmit = e => {
e.preventDefault();
const name = expenseForm.productName.value.trim();
const price = Number(expenseForm.price.value.trim());
console.log(`Product added: ${name}, ${price}`);
const user = firebase.auth().currentUser.uid;
products.addProduct(name, price, user)
.then(() => {
products.sumPrices(user, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user).onSnapshot(snapshot => {
sumStats.addStatsUI(value, snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
expenseForm.reset()
})
.catch(err => console.log(err));
}
expenseForm.addEventListener('submit', handleExpenseFormSubmit);
callbacks.push(() => expenseForm.removeEventListener('submit', handleExpenseFormSubmit))
product.js:
class Product {
constructor(name, price, budget, user) {
this.products = db.collection('users');
this.budget = budget;
this.name = name;
this.price = price;
this.user = user;
}
async addProduct(name, price, user) { //dodaje produkt do firebase
const now = new Date();
const product = {
name: name,
price: price,
created_at: firebase.firestore.Timestamp.fromDate(now),
};
const response = await this.products.doc(user).collection('products').add(product);
return response;
}
getProducts(callback, user){ //download list from firebase
this.products.doc(user).collection('products')
.orderBy("created_at", "desc")
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user){
this.budget = budget;
db.collection('users').doc(user).update({budget: budget});
// callbacks.push(unsubscribe);
}
async sumPrices(user, callbacks){
let finish = [];
const unsubscribe = this.products.doc(user).collection('products').onSnapshot(snapshot => {
let totalCount = 0;
snapshot.forEach(doc => {
totalCount += doc.data().price;
});
const a = totalCount;
console.log(a);
finish.push(a);
return finish;
})
callbacks.push(unsubscribe);
return finish;
};
};
sumStatsUI.js:
class Stats {
constructor(stats, circle, budget){
this.stats = stats;
this.circle = circle;
this.budget = budget;
}
addStatsUI(data, budget){
if(data) {
const outcome = Math.round(data * 100) / 100;
const sumAll = Math.round((budget - outcome) * 100) / 100;
this.stats.innerHTML += `
<div><span class="budget-name">Budget: </span> <span class="stat-value">${budget}$</span></div>
<div><span class="budget-name">Outcome: </span> <span class="stat-value outcome-value">${outcome}$</span></div>
<div><span class="budget-name">All: </span> <span class="stat-value last-value">${sumAll}$</span></div>
`;
const circle = Math.round(((outcome * 100) / budget) * 100) / 100;
this.circle.innerHTML += `${circle}%`;
} else {
this.stats.innerHTML = '';
this.circle.innerHTML = '';
}};
};
export default Stats;
I add console.log to sumPrices
App screenshot, when I add 2 products and try update budget
Okey, a add some improvement to my code, but still have problems with subscriptions. Now everything it's okey, but when I log out and log in functions getProducts() and updateBudget() no unsubscribe.
Code here:
index.js:
//get the products and render
const unsubscribe = products.getProducts((data, id) => {
console.log(data, id);
productUI.render(data, id);
}, user.uid);
callbacks.push(unsubscribe);
//update budget + form
const handleBudgetFormSubmit = e => {
e.preventDefault();
//update budget
const budget = Number(budgetForm.budget_value.value.trim());
sumStats.addStatsUI('', '');
products.updateBudget(budget, user.uid);
//reset form
budgetForm.reset();
const budgetCart = document.querySelector('#budget');
budgetCart.classList.remove('active');
// show message
updateMssg.innerText = `Your budget was updated to ${budget}$`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
};
budgetForm.addEventListener('submit', handleBudgetFormSubmit);
callbacks.push(() =>
budgetForm.removeEventListener('submit', handleBudgetFormSubmit)
);
and else to onAuthStateChanged() -> if(user):
} else {
console.log('user logged out');
authUI('');
productUI.render('');
sumStats.addStatsUI('');
console.log('Callbacks array', callbacks);
callbacks.forEach(callback => callback());
callbacks.length = 0;
}
});
getProducts() and updateBudget():
getProducts(callback, user) {
//download list from firebase
this.products
.doc(user)
.collection('products')
.orderBy('created_at', 'desc')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user) {
console.log('budget', budget, user);
const db = firebase.firestore();
// this.budget = budget;
db.collection('users')
.doc(user)
.update({ budget: budget });
}
When I log out and log in:
When I have getProducts and add product to collection, this function render (render()) product twice, but add to collection once. When I update budget this return budget but after that, return 0 (on DOM where show budget a can see "Infinity")
And one thing, when I log out, console return error:
TypeError: callback is not a function
at eval (index.js:182)
at Array.forEach (<anonymous>)
at Object.eval [as next] (index.js:182)
at eval (index.cjs.js:1226)
at eval (index.cjs.js:1336)
I think it's because getProducts and updateBudget don't return unsubscribe, but undefined.
Maybe someone have solution for this?
So I need to take data only from the day before. Example: today is 2018/9/25, I need the data to be taken on 2018/9/24 only. But from my code below, it takes from 23 until 25. Which is more than one day, and it took also two days before from the date I need. I don't know which code that make a wrong result. Anyone can help me with this? I really appreciate it.
Api.js
const TO_DAYS = 4194304 * 1000 * 60 * 60 * 24; // this part that might be the cause
const ROOT_DATE = moment([2018, 3, 30]); // this part that might be the cause
const ROOT_DATE_ID = 440557948108800000; // this part that might be the cause
const DATE_ID = function(date) {
return ROOT_DATE_ID + date.diff(ROOT_DATE, "days") * TO_DAYS;
}; // this part that might be the cause
class discordApi {
constructor() {
this.lock = new AsyncLock();
}
get(momentDate, authorId, offset = 0) {
const url =
config.endpoint +
querystring.stringify({
min_id: DATE_ID(momentDate),
author_id: authorId
});
return fetch(url, {
headers: {
method: "GET",
Authorization: config.auth
}
}).then(res => {
// console.log(res.url);
return res.json();
});
}
async getAllData(momentDate) {
const allData = config.targets.map(author_id =>
this.get(momentDate, author_id)
);
return Promise.all(allData);
}
index.js
var yesterday = moment().subtract(1, "days"); // this part that might be the cause
async function sendEmail() {
const data = await discordApi.getAllData(yesterday);
const unfilteredMessages = data.reduce(
(prev, current) => [...prev, ...current.messages],
[]
);
const filteredMessages = unfilteredMessages.reduce((prev, current) => {
if (prev.length === 0) {
return [...prev, current];
}
const currentConversationIsDuplicated = isConversationDuplicated(
prev[prev.length - 1],
current
);
if (currentConversationIsDuplicated) {
return prev;
}
ret
urn [...prev, current];
}, []);
const convo = await discordApi.AllConvo(filteredMessages);
const mailOptions = {
from: "lala#gmail.com",
to: maillist,
subject: "Discord-Bot Daily Data",
html: convo
};
transporter.sendMail(mailOptions, function(err, info) {
if (err) console.log(err);
else console.log("Message Sent!");
});
}
I'm trying to create a NodeJS app. Below is code that is supposed to be called when an admin creates a new product. Most of the code works, but I'm having trouble rendering the view only after the rest of the code has executed (the DB functions are asynchronous). I've wrapped much of the code in promises (to make certain blocks execute in the right order) and console logs (to pinpoint problems).
I'd like to point out that the console.dir(rejProducts)just below console.log(111) logs and empty array. Also, adding console.dir(rejProducts) just before the end bracket of the for loop logs an empty array. Thanks! Please let me know if you need more information.
app.post('/products/new', function (req, res, next) {
// Async function: find all categories
Category.find(function(err, categories) {
// Hidden count that tells num products to be created by form
var numProducts = req.body[`form-item-count`];
// Array of all rejected products adds
var rejProducts = [];
var promiseLoopProducts = new Promise(function(resolve, reject) {
var promiseProducts = [];
// Loop through all addded products
for (let i = 0; i < numProducts; i++) {
var promiseProductCheck = new Promise(function(resolve, reject) {
var name = validate.sanitize(req.body[`name_${i}`]);
var category = validate.sanitize(req.body[`category_${i}`]);
var price = validate.sanitize(req.body[`price_${i}`].replace(/\$/g, ""));
var stock = validate.sanitize(req.body[`stock_${i}`]);
var image = validate.sanitize(req.body[`image_${i}`]);
var description = validate.sanitize(req.body[`description_${i}`]);
var rejProduct;
var rejFields = { 'name': name, 'category': category, 'price': price,
'stock': stock, 'image': image,
'description': description };
var rejErrors = {};
var productData = {
name: name,
category: category,
price: price,
stock: stock,
image: image,
description: description
};
var promiseCategoryCheck = new Promise(function(resolve, reject) {
if (ObjectId.isValid(category)) {
var promiseCategoryCount = new Promise(function(resolve, reject) {
Category.count({'_id': category}, function(error, count) {
rejErrors['name'] = validate.isEmpty(name);
if (count == 0) rejErrors['category'] = true;
rejErrors['price'] = !validate.isPrice(price);
rejErrors['stock'] = !validate.isInt(stock);
if( validate.isEmpty(name) || !validate.isPrice(price) ||
count == 0 || !validate.isInt(stock) ) {
rejProduct = { 'fields': rejFields, 'errors': rejErrors };
rejProducts.push(rejProduct);
console.dir(rejProducts);
console.log(count);
return resolve();
}
else {
Product.create(productData, function (error, product) {
if (error) return next(error);
console.log(77);
console.dir(rejProducts);
return resolve();
});
}
if (error) return next(error);
});
});
promiseCategoryCount.then(function() {
console.dir(rejProducts);
return resolve();
});
} else {
rejErrors['category'] = true;
rejProduct = { 'fields': rejFields, 'errors': rejErrors };
rejProducts.push(rejProduct);
console.dir(rejProducts);
}
});
promiseCategoryCheck.then(function() {
console.dir(rejProducts);
promiseProducts.push(promiseProductCheck);
console.log(promiseProductCheck);
console.log(promiseProducts);
return resolve();
});
});
promiseProductCheck.then(function() {
console.log(106);
console.dir(rejProducts);
});
}
Promise.all(promiseProducts).then(function() {
console.log(111);
console.dir(rejProducts); // Empty array!
return resolve();
});
});
promiseLoopProducts.then(function() {
console.log(118);
console.dir(rejProducts); // Empty array!
res.render('products/new', { categories: categories, rejProducts: rejProducts });
});
});
});
Edit: I've made some changes to the code. There it seems like util.promisify is not being recognized as a function. I am using Node 9.4.
module.exports = function(app){
const util = require('util');
require('util.promisify').shim();
const validate = require('../functions/validate');
const Category = require('../db/categories');
const Product = require('../db/products');
var ObjectId = require('mongoose').Types.ObjectId;
//Category.find as function that returns a promise
const findCategories = util.promisify(Category.find);
const countCategories = (categoryId) => {
util.promisify(Category.count({'_id': categoryId}));
};
const bodyToProduct = (body, i) => {
var name = validate.sanitize(body[`name_${i}`]);
var category = validate.sanitize(body[`category_${i}`]);
var price = validate.sanitize(body[`price_${i}`].replace(/\$/g, ""));
var stock = validate.sanitize(body[`stock_${i}`]);
var image = validate.sanitize(body[`image_${i}`]);
var description = validate.sanitize(body[`description_${i}`]);
return {
name: name,
category: category,
price: price,
stock: stock,
image: image,
description: description
};
};
app.post('/products/new', function (req, res, next) {
// Async function: find all categories
return findCategories()
.then(
categories=>{
// Hidden count that tells num products to be created by form
var numProducts = req.body[`form-item-count`];
// Array of all rejected products adds
var rejProducts = [];
return Promise.all(
Array.from(new Array(numProducts),(v,i)=>i)
.map(//map [0...numProducts] to product object
i=>bodyToProduct(req.body,i)
)
.map(
product => {
var rejErrors;
var rejName = validate.isEmpty(name);
var rejCategory;
var rejPrice = !validate.isPrice(price);
var rejStock = !validate.isInt(stock);
if (ObjectId.isValid(product.category)) {
return countCategories()
.then(
count=> {
if (count == 0) rejCategory = true;
if(rejName || rejCategory || rejPrice || rejStock ) {
rejErrors = {
name: rejName,
category: rejCategory,
price: rejPrice,
stock: rejStock
}
rejProduct = { 'fields': product, 'errors': rejErrors };
rejProducts.push(rejProduct);
console.dir(rejProducts);
console.log(count);
} else {
Product.create(productData, function (error, product) {
if (error) return next(error);
console.log(77);
console.dir(rejProducts);
});
}
}
).catch(function() {
console.log("Count function failed.");
});
} else {
rejCategory = true;
rejErrors = {
name: rejName,
category: rejCategory,
price: rejPrice,
stock: rejStock
}
rejProduct = { 'fields': product, 'errors': rejErrors };
rejProducts.push(rejProduct);
console.dir(rejProducts);
}
}
)
).then(function() {
res.render('products/new', { categories: categories, rejProducts: rejProducts });
}).catch(function() {
console.log("Promise all products failed.");
});
}
).catch(function() {
console.log("Find categories failed.");
})
});
}
Some tips: if your function is over 100 lines you may be doing to much in the function.
If you have to get data from your request the way you get products of it then write better client side code (products should be an array of product objects that should not need to be sanitized). Validation is needed on the server because you an never trust what the client is sending you. But looking at the sanitize you don't even trust what your client script is sending you.
Try to write small functions that do a small thing and try to use those.
Use .map to map type a to type b (for example req.body to array of products as in the example code).
Use the result of .map as an argument to Promise.all
Use util.promisify to change a callback function into a function that returns a promise, since you are using an old version of node I've added an implementation of promisify:
var promisify = function(fn) {
return function(){
var args = [].slice.apply(arguments);
return new Promise(
function(resolve,reject){
fn.apply(
null,
args.concat([
function(){
var results = [].slice.apply(arguments);
(results[0])//first argument of callback is error
? reject(results[0])//reject with error
: resolve(results.slice(1,results.length)[0])//resolve with single result
}
])
)
}
);
}
};
module.exports = function(app){
const validate = require('../functions/validate');
const Category = require('../db/categories');
const Product = require('../db/products');
var ObjectId = require('mongoose').Types.ObjectId;
//Category.find as function that returns a promise
const findCategories = promisify(Category.find.bind(Category));
const countCategories = (categoryId) => {
promisify(Category.count.bind(Category))({'_id': categoryId});
};
const createProduct = promisify(Product.create.bind(Product));
const REJECTED = {};
const bodyToProduct = (body, i) => {
var name = validate.sanitize(body[`name_${i}`]);
var category = validate.sanitize(body[`category_${i}`]);
var price = validate.sanitize(body[`price_${i}`].replace(/\$/g, ""));
var stock = validate.sanitize(body[`stock_${i}`]);
var image = validate.sanitize(body[`image_${i}`]);
var description = validate.sanitize(body[`description_${i}`]);
return {
name: name,
category: category,
price: price,
stock: stock,
image: image,
description: description
};
};
const setReject = product => {
var rejErrors;
var rejName = validate.isEmpty(product.name);
var rejCategory;
var rejPrice = !validate.isPrice(product.price);
var rejStock = !validate.isInt(product.stock);
const countPromise = (ObjectId.isValid(product.category))
? countCategories()
: Promise.resolve(0);
return countPromise
.then(
count => {
if (count == 0) rejCategory = true;
if (rejName || rejCategory || rejPrice || rejStock) {
rejErrors = {
type:REJECTED,
name: rejName,
category: rejCategory,
price: rejPrice,
stock: rejStock
}
return rejErrors;
}
return false;
}
);
};
const productWithReject = product =>
Promise.all([
product,
setReject(product)
]);
const saveProductIfNoRejected = ([product,reject]) =>
(reject===false)
? Product.create(product)
.catch(
err=>({
type:REJECTED,
error:err
})
)
: reject;
app.post('/products/new', function (req, res, next) {
// Async function: find all categories
return findCategories()
.then(
categories => {
// Hidden count that tells num products to be created by form
var numProducts = req.body[`form-item-count`];
// Array of all rejected products adds
var rejProducts = [];
return Promise.all(
Array.from(new Array(numProducts), (v, i) => i)
.map(//map [0...numProducts] to product object
i => bodyToProduct(req.body, i)
)
.map(
product=>
productWithReject(product)
.then(saveProductIfNoRejected)
)
).then(
results =>
res.render(
'products/new',
{
categories: categories,
rejProducts: results.filter(x=>(x&&x.type)===REJECTED)
}
)
).catch(
error=>
console.log("Promise all products failed.",error)
);
}
).catch(
error=>
console.log("Find categories failed.",error)
)
});
}