check many req.body values (nodejs API) - node.js

Is there a good, efficient way to check many req.body values whether they are not undefined or null?
At most I have about 17 values to check.

You can create a function that takes a list of property names and checks to see if they have values other than null or undefined.
// checks to see if all props in the list are available and non-null
// list can either be an array or a | separated string
function checkProps(obj, list) {
if (typeof list === "string") {
list = list.split("|");
}
for (prop of list) {
let val = obj[prop];
if (val === null || val === undefined) {
return false;
}
}
return true;
}
Then, you could use it like this:
if (!checkProps(req.body, "email|firstName|lastName|address1|city|state|zip")) {
// some properties missing
} else {
// all properties available
}

Related

How to listen for .onUpdate changes for a map object using .before and .after firebase functions

I am using .onUpdate function with snap.before.data() and snap.after.data() with a map field value. The issue is that the changes trigger doesn't seem to be working. It's calling both strat and mark functions, when only one map object (mark) has been updated.
I know that .onUpdate does not trigger with a fieldvalue, but .before and .after changes of a field value can be compared. Can this work with a parent map/object? Both mark and strat maps have no parents in the doc.
export const BARupdate = functions.firestore
.document('inputs/{inputUserID}')
.onUpdate(async (snap, context) => {
functions.logger.log('bar update is running');
// .after should represent after changes
const userInfo: any = {
...snap.after.data(),
userID: snap.after.id,
};
const newValue = snap.after.data();
const oldValue = snap.before.data()
if (userInfo['strat'] !== undefined) {
if (newValue.strat !== oldValue.strat){
console.log('snap same strat? NO')
const stratData = userBusinessInfo['strat'];
const userID = userBusinessInfo?.userID;
} else {
console.log('STRAT IS THE SAME')
}
return
}
if (userBusinessInfo['mark'] !== undefined) {
if (newValue.mark !== oldValue?.mark){
console.log('snap same mark? NO')
const markData = userInfo['mark'];
const userID = userInfo?.userID;
} else {
console.log('MARK IS THE SAME')
}
return
}
The reason is that the map objects may be returned with their properties in a different order.
Since you only have two properties by object, you can modify your code as follows:
if (newValue.strat.id !== oldValue.strat.id || newValue.strat.name !== oldValue.strat.name) {
console.log('snap same strat? NO')
//...
} else {
console.log('STRAT IS THE SAME')
}
if (newValue.mark.id !== oldValue.mark.id || newValue.mark.name !== oldValue.mark.name) {
console.log('snap same mark? NO')
//...
} else {
console.log('MARK IS THE SAME')
}
In case the map objects are more complex, you should use another approach, e.g. using the lodash library, which performs a deep comparison between two values to check if they are equivalent with _.isEqual(value1, value2).
See also: https://www.google.com/search?q=javascript+how+to+compare+two+objects

Is this good design for updating/deleting from list using useState and useEffect?

I'm trying to create a React app that:
sends data (json message) from backend to frontend using socket.io
if a json message with same is sent, update the existing list
This is how i'm implementing it right now but i'm not sure if this is a good design methodology or if there's a better way to achieve what I want to do.
function App(){
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0){ //only check for update/delete if more than 1 item present
let exists = !!currentList.find((item) => item.ID === data.ID)
if (exists){ //if new item exists in database list
if (data.deleteFlag === true){ // incoming data will have a json field declaring whether to delete or not
//deleting item
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
return (currentList.splice(ind,1))
}
else{ // else if delete flag is not true... update fields
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
if (item.dataField !== data.dataField){
currentList[ind].dataField = data.dataField
}
return (currentList)
}
}
//if incoming data doesnt exist in list, add to it
else{ return([...currentList, data]) }
}
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
})
}, [socket])
return(/*using map to display list in front end*/)
}
Right now, this code works in the following ways:
Checks if there are 0 items in 'database', if so, it adds items to it.
What it's not doing:
updating items in database
deleting items properly. Sometimes it deletes items, other times it does nothing.
Any help would be great!
Use higher-order functions to simplify code like filter, findIndex, etc.
use findIndex method to check items exist and use the same index to update currentList.
use the filter function to delete items from the list.
function App() {
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0) { //only check for update/delete if more than 1 item present
// Use same index to find item
let itemIndex = currentList.findIndex((item) => item.ID === data.ID)
if (itemIndex !== -1) { //if new item exists in database list
if (data.deleteFlag === true) { // incoming data will have a json field declaring whether to delete or not
// use filter for delete
return currentList.filter((item) => item.ID !== data.ID);
}
else {
let item = currentList[itemIndex]
const newItem = { ...item, dataField: data.dataField }
if (item.dataField !== newItem.dataField) {
currentList[itemIndex] = newItem;
return [...currentList]; // Set new value for updates
}
return (currentList)
}
}
//if incoming data doesn't exist in list, add to it
else { return ([...currentList, data]) }
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
});
});
}, [socket])
return (/*using map to display list in front end*/)
}

Firestore doesn't support JavaScript objects with custom prototypes?

I'm using the node Bigquery Package, to run a simple job. Looking at the results (say data) of the job the effective_date attribute look like this:
effective_date: BigQueryDate { value: '2015-10-02' }
which is obviously an object within the returned data object.
Importing the returned json into Firestore gives the following error:
UnhandledPromiseRejectionWarning: Error: Argument "data" is not a
valid Document. Couldn't serialize object of type "BigQueryDate".
Firestore doesn't support JavaScript objects with custom prototypes
(i.e. objects that were created via the 'new' operator).
Is there an elegant way to handle this? Does one need to iterate through the results and convert / remove all Objects?
The firestore Node.js client do not support serialization of custom classes.
You will find more explanation in this issue:
https://github.com/googleapis/nodejs-firestore/issues/143
"We explicitly decided to not support serialization of custom classes for the Web and Node.JS client"
A solution is to convert the nested object to a plain object. For example by using lodash or JSON.stringify.
firestore.collection('collectionName')
.doc('id')
.set(JSON.parse(JSON.stringify(myCustomObject)));
Here is a related post:
Firestore: Add Custom Object to db
Another way is less resource consuming:
firestore
.collection('collectionName')
.doc('id')
.set(Object.assign({}, myCustomObject));
Note: it works only for objects without nested objects.
Also you may use class-transformer and it's classToPlain() along with exposeUnsetFields option to omit undefined values.
npm install class-transformer
or
yarn add class-transformer
import {classToPlain} from 'class-transformer';
firestore
.collection('collectionName')
.doc('id')
.set(instanceToPlain(myCustomObject, {exposeUnsetFields: false}));
If you have a FirebaseFirestore.Timestamp object then don't use JSON.parse(JSON.stringify(obj)) or classToPlain(obj) as those will corrupt it while storing to Firestore.
It's better to use {...obj} method.
firestore
.collection('collectionName')
.doc('id')
.set({...obj});
Note: do not use new operator for any nested objects inside document class, it'll not work. Instead, create an interface or type for nested object properties like this:
interface Profile {
firstName: string;
lastName: string;
}
class User {
id = "";
isPaid = false;
profile: Profile = {
firstName: "",
lastName: "",
};
}
const user = new User();
user.profile.firstName = "gorv";
await firestore.collection("users").add({...user});
And if you really wanna store class object consists of deeply nested more class objects then use this function to first convert it to plain object while preserving FirebaseFirestore.Timestamp methods.
const toPlainFirestoreObject = (o: any): any => {
if (o && typeof o === "object" && !Array.isArray(o) && !isFirestoreTimestamp(o)) {
return {
...Object.keys(o).reduce(
(a: any, c: any) => ((a[c] = toPlainFirestoreObject(o[c])), a),
{}
),
};
}
return o;
};
function isFirestoreTimestamp(o: any): boolean {
if (o &&
Object.getPrototypeOf(o).toMillis &&
Object.getPrototypeOf(o).constructor.name === "Timestamp"
) {
return true;
}
return false;
}
const user = new User();
user.profile = new Profile();
user.profile.address = new Address();
await firestore.collection("users").add(toPlainFirestoreObject(user));
Serializes a value to a valid Firestore Document data, including object and its childs and Array and its items
export function serializeFS(value) {
const isDate = (value) => {
if(value instanceof Date || value instanceof firestore.Timestamp){
return true;
}
try {
if(value.toDate() instanceof Date){
return true;
}
} catch (e){}
return false;
};
if(value == null){
return null;
}
if(
typeof value == "boolean" ||
typeof value == "bigint" ||
typeof value == "string" ||
typeof value == "symbol" ||
typeof value == "number" ||
isDate(value) ||
value instanceof firestore.FieldValue
) {
return value;
}
if(Array.isArray(value)){
return (value as Array<any>).map((v) => serializeFS(v));
}
const res = {};
for(const key of Object.keys(value)){
res[key] = serializeFS(value[key]);
}
return res;
}
Usage:
await db().collection('products').doc()
.set(serializeFS(
new ProductEntity('something', 123, FieldValue.serverTimestamp()
)));

Leaflet, geojson: filter out entire features/objects with a null value in them

I have a geojson file which I'm getting from this website which somehow contains corrupt data, with a coordinate value = null.
http://measuringamsterdam.nl/datalist/kijk/
And I'm using it in my code like this:
//Retrieve all data and add to map
$.each(datalistObject['idlist'], function(key, value) {
$.getJSON('http://measuringamsterdam.nl/datalist/kijk/' + value['id'], function(data) {
textbox = value['name'];
var dataid = L.geoJson([data], {
style: function (feature) {
return feature.properties && feature.properties.style;
},
onEachFeature: onEachFeature,
pointToLayer: function (feature, latlng) {
return L.marker(latlng, {
icon: value['icon']
});
}
}).addTo(jsonGroup);
console.log(jsonGroup);
},function(xhr) { console.error(xhr); });
});
Now somehow I need to filter out the features/objects where the coordinates have a null value.
I really need to filter the data that point in my code since I need the + value['id'] part in the getJSON code.
Ane ideas?
Using the following code you will generate a new array. Which will include only the filtered data.
var newArray = data.filter(function (el) {
return el.value != 'null';
});
You can also apply multiple filters, for example:
return el.value_a != 'null' && el.value_b > 100;
Hopefully this will work!

Mongoose default sorting order

Is there a way to specify sorting order on the schema/model level in Mongoose?
I have model Posts, and I always fetch posts ordered by 'createdAt' field. Thus on each query I have to write .sort('-createdAt'). Can I make this order default for this model?
There is no way, in Mongoose, directly to define a default sort order on your query.
If you're doing something over and over again though, you might want to abstract this into a function that does it for you:
function findPostsByDate(cb){
Posts.find({}).sort('-createdAt').exec(cb);
}
Or even something more generic than that:
function findXByDate(model, findCriteria, cb){
model.find(findCriteria).sort('-createdAt').exec(cb);
}
You can achieve this by creating a static method in your schema definition.
Mongoose documentation for Methods and statics here: http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
Example
In your schema file:
PostSchema.statics.sortedFind = function sortedFind(query, fields, options cb){
//First 3 parameters are optional
if( arguments.length === 1){
cb = query;
} else if (arguments.length === 2) {
cb = fields;
} else if(arguments.length === 3){
cb = options;
}
this.find(query, fields, options).sort('-createdAt').exec(cb);
}
Then you can use:
var query = {user_id: currentUser.id}; // query example, modify according to your needs
Post.sortedFind(query, function(err, response){ /* Your code goes here */ });
This is how I enforce sortable columns and provide a default sort. I copy this code into each model and just supply the allowSortOn array.
postSchema.pre('find', function (){
if (typeof this.options.sort !== 'undefined') {
var allowSortOn = ["_id","createdAt"] // add other allowable sort columns here
, propCount = 0;
for (var prop in this.options.sort)
if (this.options.sort.hasOwnProperty(prop)) {
if (allowSortOn.indexOf(prop) === -1) {
console.log('Invalid sort column ' + prop);
delete this.options.sort[prop];
} else {
propCount++;
}
}
if (propCount === 0) {
this.options.sort[allowSortOn[1]] = 1;
console.log('Setting sort column to ' + JSON.stringify(this.options.sort));
}
}
})

Resources