Get map of map field from Firestore - node.js

I have a field of type map that contains Maps of data in firestore.
I am trying to retrieve this data using a cloud function in node.js. I can get the document and the data from the field but i can't get it in a usable way. I have tried every solution i can find on SO and google but the below is the only code that can give me access to the data. I obviously need to be able to access each field with in the Map individually. in swift i build an array of String:Any but i can get that to work in Node.JS
const docRef = dbConst.collection('Comps').doc('XEVDk6e4AXZPkNprQRn5Imfcsah11598092006.724980');
return docRef.get().then(docSnap => {
const tagets = docSnap.get('targets')
console.log(tagets);
}).catch(result => { console.log(result) });
this is what i am getting back in the console.
In Swift i do the following and am so far not able to find an equivalent in typescript. (i don't need to build the custom object just ability to access the keys and values)
let obj1 = doc.get("targets") as! [String:Any]
for objs in obj1{
let obs = objs.value as! [String:Any]
let targObj = compUserDetails(IDString: objs.key, activTarg: obs["ActivTarget"] as! Double, stepTarg: obs["StepTarget"] as! Double, name: obs["FullName"] as! String)
UPDATE
After spending a whole day working on it thought i had a solution using the below:
const docRef = dbConst.collection('Comps').doc('XEVDk6e4AXZPkNprQRn5Imfcsah11598092006.724980');
return docRef.get().then(docSnap => {
const tagets = docSnap.get('targets') as [[string, any]];
const newDataMap = [];
for (let [key, value] of Object.entries(tagets)) {
const tempMap = new Map<String,any>();
console.log(key);
const newreWorked = value;
tempMap.set('uid',key);
for(let [key1, value1] of Object.entries(newreWorked)){
tempMap.set(key1,value1);
newDatMap.push(tempMap);
};
};
newDatMap.forEach(element => {
const name = element.get('FullName');
console.log(name);
});
However the new data map has 6 seperate mapped objects. 3 of each of the original objects from the cloud. i can now iterate through and get the data for a given key but i have 3 times as many objects.

So after two days of searching an getting very close i finnaly worked out a solution, it is very similar to the code above but this works. it may not be the "correct" way but it works. feel free to make other suggestions.
return docRef.get().then(docSnap => {
const tagets = docSnap.get('targets') as [[string, any]];
const newDatarray = [];
for (let [key, value] of Object.entries(tagets)) {
const tempMap = new Map<String,any>();
const newreWorked = value;
tempMap.set('uid',key);
for(let [key1, value1] of Object.entries(newreWorked)){
tempMap.set(key1,value1);
};
newDatarray.push(tempMap);
};
newDatarray.forEach(element => {
const name = element.get('FullName');
const steps = element.get('StepTarget');
const avtiv = element.get('ActivTarget');
const UID = element.get('uid');
console.log(name);
console.log(steps);
console.log(avtiv);
console.log(UID);
});
}).catch(result => { console.log(result) });

I made this into a little function that gets the underlying object from a map:
function getMappedValues(map) {
var tempMap = {};
for (const [key, value] of Object.entries(map)) {
tempMap[key] = value;
}
return tempMap;
}
For an object with an array of maps in firestore, you can get the value of the first of those maps like so:
let doc = { // Example firestore document data
items: {
0: {
id: "1",
sr: "A",
},
1: {
id: "2",
sr: "B",
},
2: {
id: "3",
sr: "B",
},
},
};
console.log(getMappedValues(doc.items[0]));
which would read { id: '1', sr: 'A' }

Related

Neo4jError: Unable to pack the given value: - Invalid input 'MERGE': expected "OPTIONS" or <EOF> error depending on the parameter passed to the cypher

I'm starting implementing Neo4j in my Node.js project where I first write to MongoDB within a transaction and then write to Neo4j which should fails aborts the transaction so consistency between the two is ensured. I'm getting two different errors when executing the cypher depending if I pass in a JS Object or direct parameters.
If I pass the alert object and try to read its parameters I get this error:
Neo4jError: Unable to pack the given value: function() {
let states = [...arguments];
const callback = states.pop();
if (!states.length) states = this.stateNames;
const _this = this;
const paths = states.reduce(function(paths, state) {
if (_this.states[state] == null) {
return paths;
}
return paths.concat(Object.keys(_this.states[state]));
}, []);
return paths[iterMethod](function(path, i, paths) {
return callback(path, i, paths);
});
}
at captureStacktrace (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:611:17)
at new Result (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:105:23)
at newCompletedResult (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:501:12)
at Object.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:333:20)
at Transaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:174:34)
at ManagedTransaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction-managed.js:56:21)
at /Users/vincenzocalia/server-node/api/src/neo4j/alert_neo4j.js:22:53
at TransactionExecutor._safeExecuteTransactionWork (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:141:26)
at TransactionExecutor.<anonymous> (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:128:46)
at step (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:52:23) {
constructor: [Function: Neo4jError] { isRetriable: [Function (anonymous)] },
code: 'ProtocolError',
retriable: false
}
so I guess that there is no mapping for the JS object. If I instead pass the object's values I get a Merge error:
Neo4jError: Invalid input 'MERGE': expected "OPTIONS" or <EOF> (line 3, column 13 (offset: 83))
" MERGE (u:User {id: $userId})"
^
at captureStacktrace (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:611:17)
at new Result (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:105:23)
at newCompletedResult (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:501:12)
at Object.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:333:20)
at Transaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:174:34)
at ManagedTransaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction-managed.js:56:21)
at /Users/vincenzocalia/server-node/api/src/neo4j/alert_neo4j.js:22:53
at TransactionExecutor._safeExecuteTransactionWork (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:141:26)
at TransactionExecutor.<anonymous> (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:128:46)
at step (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:52:23) {
constructor: [Function: Neo4jError] { isRetriable: [Function (anonymous)] },
code: 'Neo.ClientError.Statement.SyntaxError',
retriable: false
}
As I understood it Merge should create a node if it doesn't exists otherwise will match a node, in my cypher I set the :Alert .id property as unique, then I get a :User node if exists or create it if it doesn't, create a :Alert node, create the :POSTED_ALERT relationship and return the alert so I can check that it was created.
Can you spot what I'm doing wrong?
exports.saveAlert = async (alert) => {
console.log('neoAlert.saveAlert alert:', alert);
const driver = neo4j.getDriver();
const session = driver.session();
const userId = alert.userId;
const alertId = alert.id;
const res = await session.executeWrite(tx => tx.run(
// `
// CREATE CONSTRAINT alert_id ON (alert:Alert) ASSERT alert.id IS UNIQUE
// MERGE (u:User {id: $alert.userId})-[r:POSTED_ALERT]->(a:Alert{id: $alert.id})
`
CREATE CONSTRAINT alert_id ON (alert:Alert) ASSERT alert.id IS UNIQUE
MERGE (u:User {id: $userId})
MERGE (a:Alert {id: $alertId})
MERGE (u)-[r:POSTED_ALERT]->(a)
RETURN a AS alert
`
,
{alert ,alertId, userId}
));
await session.close();
if ( res.records.length === 0 ) {
throw new NotFoundError(`Neo4j could not create record for Alert ${alert.id} by User ${alert.userId}`);
}
console.log(`Neo4j saved ${res.records.length} alerts`);
const alerts = res.records;
const saved = alerts.length('alert');
console.log('Neo4j saved alert is: ', saved);
return saved;
// throw Error;
}
Found the problem. It seems that creating a constraint within the same cypher was what messed the transaction, so I put it in its own method.
Also as the JS object parameters are not available inside the cypher I just map them to variables when passing parameters to the cypher.
Hope this will help others.
exports.saveAlert = async (alert) => {
console.log('neoAlert.saveAlert alert:', alert);
const driver = neo4j.getDriver();
const session = driver.session();
const userId = alert.userId;
const alertId = alert.id;
const constraint = await session.run('CREATE CONSTRAINT alert_id IF NOT EXISTS FOR (alert:Alert) REQUIRE alert.id IS UNIQUE');
const res = await session.executeWrite(tx => tx.run(
`
MERGE (u:User {id: $userId})
MERGE (a:Alert {id: "$alertId"})
SET
a.city = $city,
a.region = $region,
a.country = $country,
a.date = $date,
a.latitude = $latitude,
a.longitude = $longitude,
a.description = $description,
a.alertIcon = $alertIcon,
a.alertImageUrl = $alertImageUrl,
a.alertImageName = $alertImageName,
a.userId = $userId,
a.userName = $userName,
a.fcmToken = $fcmToken,
a.utilityPoints = $utilityPoints,
a.votesToDelete = $votesToDelete
MERGE (u)-[r:POSTED_ALERT]->(a)
RETURN a AS alert
`
,
{
alertId: alert.id,
userId: alert.userId,
city: alert.city,
region: alert.region,
country: alert.country,
date: alert.date,
latitude: alert.latitude,
longitude: alert.longitude,
description: alert.description,
alertIcon: alert.alertIcon,
alertImageUrl: alert.alertImageUrl,
alertImageName: alert.alertImageName,
userName: alert.userName,
fcmToken: alert.fcmToken,
utilityPoints: alert.utilityPoints,
votesToDelete: alert.votesToDelete
}
));
await session.close();
if ( res.records.length === 0 ) {
throw new NotFoundError(`Neo4j could not create record for Alert ${alert.id} by User ${alert.userId}`);
}
console.log(`Neo4j saved ${res.records.length} alerts`);
const alerts = res.records;
const saved = alerts[0].get('alert');
console.log('Neo4j saved alert is: ', saved);
return saved;
}

How do I use transactions or batches to read the value of an update, then perform more updates using that value?

What is the best approach to do a batch update or transaction, that reads a value of the first update, then uses this value to make further updates?
Here is an example:
//create person
const id = await db
.collection("person")
.add({ ...person })
.then(ref => ref.id)
//then do a series of updates
let batch = db.batch()
const private_doc = db
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc, {
last_modified,
version: 1,
versions: []
})
const some_index = db.collection("data").doc("some_index")
batch.update(some_index, {
[id]: { first_name: person.first_name, last_name: person.last_name, last_modified }
})
const another_helpful_doc = db.collection("some_other_collection").doc("another_helpful_doc")
batch.update(another_helpful_doc, {
[id]: { first_name: person.first_name, last_name: person.last_name, image: person.image }
})
return batch.commit().then(() => {
person.id = id
return person
})
You can see here if there is an error any of the batch updates, the person doc will still be created - which is bad. I could add in a catch to delete the person doc if anything fails, however interested to see if this is possible with transactions or batches.
You can call the doc() method, without specifying any path, in order to create a DocumentReference with an auto-generated ID and, then, use the reference later. Note that the document corresponding to the DocumentReference is NOT created.
So, the following would do the trick, since all the writes/updates are included in the batched write:
const new_person_ref = db.collection("person").doc();
const id = new_person_ref.id;
let batch = db.batch()
batch.set(new_person_ref, { ...person })
const private_doc_ref = db // <- note the addition of ref to the variable name, it could help avoiding errors, as this is not a DocumentSnapshot but a DocumentReference.
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc_ref, {
last_modified,
version: 1,
versions: []
})
//....

Json/Nodejs: How do I get specific data from a JSON file

I am trying to develop a discord bot with nodejs. My question is how do I get a specific data out of a JSON file (for instance, from this json. I want to only specify a single uuid like "59d4c622c7cc459a98c2e947054e2b11" and get the data.
Assuming you have already parsed the JSON into an actual Javascript object named data and given the way the data is currently organized, you would have to search the data.guild.members array to find the object that had the desired uuid property value.
function findDataForUuid(uuid) {
for (let obj of data.guild.members) {
if (obj.uuid === uuid) {
return obj;
}
}
// no match found
return null;
}
let item = findDataForUuid("59d4c622c7cc459a98c2e947054e2b11");
if (item) {
console.log(item); // will show all the properties of the member object
}
Or, using .find() on the array:
function findDataForUuid(uuid) {
return data.guild.members.find(item => {
return item.uuid === uuid;
});
}
The simple soln you can use filter. Best is use reduce.
const data = {
guild: {
_id: "5b2906070cf29ddccd0f203c",
name: "Dulcet",
coins: 122010,
coinsEver: 232010,
created: 1529415175560,
members: [
{
uuid: "59d4c622c7cc459a98c2e947054e2b11",
rank: "MEMBER",
joined: 1529683128302,
questParticipation: 39,
expHistory: {
"2020-02-16": 0,
"2020-02-15": 0,
"2020-02-14": 0,
"2020-02-13": 0,
"2020-02-12": 0,
"2020-02-11": 0,
"2020-02-10": 0
}
}
]
}
};
const members = data.guild.members.filter(
member => member.uuid == "59d4c622c7cc459a98c2e947054e2b11"
);
const firstMember = members[0]
console.log(firstMember)

Dynamo DB Query Filter Node.js

Running a Node.js serverless backend through AWS.
Main objective: to filter and list all LOCAL jobs (table items) that included the available services and zip codes provided to the filter.
Im passing in multiple zip codes, and multiple available services.
data.radius would be an array of zip codes = to something like this:[ '93901', '93902', '93905', '93906', '93907', '93912', '93933', '93942', '93944', '93950', '95377', '95378', '95385', '95387', '95391' ]
data.availableServices would also be an array = to something like this ['Snow removal', 'Ice Removal', 'Salting', 'Same Day Response']
I am trying to make an API call that returns only items that have a matching zipCode from the array of zip codes provided by data.radius, and the packageSelected has a match of the array data.availableServices provided.
API CALL
import * as dynamoDbLib from "./libs/dynamodb-lib";
import { success, failure } from "./libs/response-lib";
export async function main(event, context) {
const data = JSON.parse(event.body);
const params = {
TableName: "jobs",
FilterExpression: "zipCode = :radius, packageSelected = :availableServices",
ExpressionAttributeValues: {
":radius": data.radius,
":availableServices": data.availableServices
}
};
try {
const result = await dynamoDbLib.call("query", params);
// Return the matching list of items in response body
return success(result.Items);
} catch (e) {
return failure({ status: false });
}
Do I need to map the array of zip codes and available services first for this to work?
Should I be using comparison operators?
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.QueryFilter.html
Is a sort key value or partition key required to query and filter? (the table has a sort key and partition key but i would like to avoid using them in this call)
Im not 100% sure on how to go about this so if anyone could point me in the right direction that would be wonderful and greatly appreciated!!
I'm not sure what your dynamodb-lib refers to but here's an example of how you can scan for attribute1 in a given set of values and attribute2 in a different set of values. This uses the standard AWS JavaScript SDK, and specifically the high-level document client.
Note that you cannot use an equality (==) test here, you have to use an inclusion (IN) test. And you cannot use query, but must use scan.
const AWS = require('aws-sdk');
let dc = new AWS.DynamoDB.DocumentClient({'region': 'us-east-1'});
const data = {
radius: [ '93901', '93902', '93905', '93906', '93907', '93912', '93933', '93942', '93944', '93950', '95377', '95378', '95385', '95387', '95391' ],
availableServices: ['Snow removal', 'Ice Removal', 'Salting', 'Same Day Response'],
};
// These hold ExpressionAttributeValues
const zipcodes = {};
const services = {};
data.radius.forEach((zipcode, i) => {
zipcodes[`:zipcode${i}`] = zipcode;
})
data.availableServices.forEach((service, i) => {
services[`:services${i}`] = service;
})
// These hold FilterExpression attribute aliases
const zipcodex = Object.keys(zipcodes).toString();
const servicex = Object.keys(services).toString();
const params = {
TableName: "jobs",
FilterExpression: `zipCode IN (${zipcodex}) AND packageSelected IN (${servicex})`,
ExpressionAttributeValues : {...zipcodes, ...services},
};
dc.scan(params, (err, data) => {
if (err) {
console.log('Error', err);
} else {
for (const item of data.Items) {
console.log('item:', item);
}
}
});

node js access value in json result and get the value

i have following json object which i get from API end point
let myjson = { Team1: { SCORE: 10 } }
i want to access the score inside Team but not able to complete as i need to just the result as 10
i have tried following code but not able to get the result
for(var attribute name in JSON.parse(myjson)){
return console.log(attributename+": "+body[attributename]);
}
i also used bellow code
const userStr = JSON.stringify(myjson);
JSON.parse(userStr, (key, value) => {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
});
Not a node developer but why do you need to json.stringify it? Can't you just reach the value with dot notation like this:
myJson.Team1.SCORE
myjson is already an Object, you don't need to do JSON.parse nor JSON.stringify on it.
Just access the property directly:
console.log(myjson.Team1.SCORE)
If you have multiple teams, or want to access it dynamically:
const obj = { Team1: { SCORE: 10 }, Team2: { SCORE: 20 } }
for(const [team, value] of Object.entries(obj)) {
console.log(`${team}: ${value.SCORE}`)
}
you also can use this if it fulfills your query.
here is the code.
let myjson = {Team1: {SCORE:10}, Team2: {SCORE: 20}};
Object.keys(myjson).forEach(function(item) {
console.log(myjson[item].SCORE);
});
Not sure if there can be more teams in that object, so I put here some more complex solution and then the straightforward one.
const myjson = { Team1: { SCORE: 10 }, Team2: { SCORE: 20 } }
const result = Object.keys(myjson).map(key => myjson[key].SCORE);
console.log('For dynamic resolution', result);
console.log('If there is only Team1', myjson.Team1.SCORE);

Resources