Query element in array in google datastore - google-cloud-datastore

How can I retrieve and update an element inside an array stored in datastore?
I have an entity called 2019-06-26. This entity has an array called houses. I want to update houses[3] in datastore, how do I do that? I find it impossible to find the right query.

With Google’s Node.js client library you can retrieve the entity with the array property and then change the values of the elements you want.
The sample code below retrieves an entity of kind “Team” by its name, set the value for the player at index 2 as a new object and saves the entity with the new values:
const {Datastore} = require('#google-cloud/datastore');
async function quickStart() {
const projectId = '[PROJECT_ID]';
const datastore = new Datastore({
projectId: projectId,
});
const kind = 'Team';
const name = 'T01';
const taskKey = datastore.key([kind, name]);
const [entity] = await datastore.get(taskKey);
entity.players[2] = {name: ‘new_player’};
await datastore.save(entity);
}
quickStart().catch(console.error);
You can check Google’s Entities, Properties, and Keys documentation and the Google Datastore Client official npm page for more information about how to work with entities.

Related

Firestore trigger not working when run in emulator

I'm new to Firebase and tried following this video.
code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp()
const db = admin.firestore()
exports.onUserCreate = functions.firestore.document('users/{userId}').onCreate(async(snapshot, context) => {
const values = snapshot.data()
await db.collection('logging').add({description: `Email was sent to user with username:${values.username}`}) })
This should create a new collection called 'logging' whenever a new collection is created. But the trigger doesn't work in emulator, it shows only the collection which we created.
It looks like my comment was helpful, posting it as an answer for visibility.
A new collection is automatically created when you create a new document using the set() method.
There is no separate method for creating collections. Try adding a document as described in the docs. For example,
await db.collection('logging').doc('journal').set(data);

Within a firebase project, can I update values in the firestore database from the real time database

There is an integer value in the my real time database that I'd like to have sync'd with an integer value in my firestore database. The realtime database is fed through an external source and when it gets an update, I'd like that pushed to the firestore database
Here's what I have so far, I am able to access the value in the realtime database but not the firestore database.
=============== Data Structure===================
Real Time database
user1:
{ meter : 20 }
Firestore database
Collection: Users
{Document : user1
{ meter : 20 }}
/// =============Code Sample ======================================
const functions = require('firebase-functions');
// Initialize the Firebase application with admin credentials
const admin = require('firebase-admin');
admin.initializeApp();
// Define user sync method
exports.meterSync = functions.database.ref('/user1/meter').onUpdate( (change, context) => {
// Get a reference to the Firestore document of the changed user
var userDoc = admin.firestore().doc(`user/${context.params.user1}`);
const meterReading = change.after.val();
console.log(meterReading);
console.log(userDoc); /// Not able to access this
return null
});
My expectation is that user doc will give me the document, so I can update the fields within it. But I am getting a documentReference object, not sure how to access the meter field.
By doing
var userDoc = admin.firestore().doc(`user/${context.params.user1}`);
You actually defined a DocumentReference.
You have to use this DocumentReference to write to the Firestore database, using the set() or update() methods.
Here is a code using the set() method:
exports.meterSync = functions.database
.ref('/{userId}/meter')
.onUpdate((change, context) => {
const meterReading = change.after.val();
console.log(meterReading);
// Get a reference to the Firestore document of the changed user
const userDoc = admin.firestore().doc(`user/${context.params.userId}`);
return userDoc.set(
{
meter: meterReading
},
{ merge: true }
);
});
You will need to use cloud functions for this, lets say, you have one background function:
export const one = functions.firestore.document('users').onWrite((change, context) => {
// ... Your code here
})
export const two = functions.database.ref('/users/{userId}')
.onCreate((snapshot, context) => {
// ... you code here
})
Since you have access to the firebase sdk admin in there you basically can achieve your goal. You can read more about it here

Making automated notifications with Firebase Cloud Functions, Messaging,Firestore

I've been trying to push notifications with .onUpdate() trigger but It doesn't work. I'm not sure what is wrong since anything I find on docs is useless pretty much and it's my first time working with Node.js.
I want to notify the user (with Firebase Messaging) when any product gets updated (in Firebase Realtime Database) using Firebase Cloud Functions, which is after submitting an order, and the requirement is that the product stock is <= 5.
Structure of the collection is like this:
products (collection) -> {productID} (document) -> attributes: {name, barcode, price, stock, sold}
//import firebase
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/products/{product}')
.onUpdate((change, context) => {
const prodName = context.data.child('name');
const numProd = context.data.child('stock');
if(numProd<=5){
const payload = {
notification: {
title: 'Low stock!',
body: `Product ${prodName} is running out.`
}
}
const registrationToken = 'token';
return admin.messaging().sendToDevice(registrationToken,payload)
.then(function(response){
console.log('Notification sent successfully:',response);
return 1;
})
.catch(function(error){
console.log('Notification sent failed:',error);
});
}
});
Apparently you are mixing up the two Firebase's database services: Firestore and the Realtime Database.
As a matter of fact, you indicate that your data is organised in collections ("Structure of the collection is like this: products (collection) -> {productID} (document)") which means that you are using Firestore (Realtime Database doesn't have collections).
But your background trigger is corresponding to a Realtime Database trigger, see https://firebase.google.com/docs/functions/database-events.
If the assumption that you are mixing up the two database services is right, you need to use a background trigger for Firestore, see https://firebase.google.com/docs/functions/firestore-events, in particular the onUpdate() one, as follows:
exports.updateUser = functions.firestore
.document('/products/{productId}')
.onUpdate((change, context) => {
// Get an object representing the document
const newValue = change.after.data();
const prodName = newValue.name;
const numProd = newValue.stock;
// ...
});
Note that it seems that you are not handling correctly the case when numProd > 5. You may throw an error or just do return null;
It is a also a good idea to watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/.

Error updating different Collection document using Cloud Function

By using Cloud Functions, when a document from "users" collection is edited, the edited files should be updated in uploads collection wherever the user id is stored.
For the above requirement I am using the below function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const settings = {
timestampsInSnapshots: true
};
admin.initializeApp();
admin.firestore().settings(settings);
var db = admin.firestore();
exports.updateUser = functions.firestore.document('users/{userId}')
.onUpdate((change, context) => {
var userId = context.params.userId;
const newValue = change.after.data();
const name = newValue.display_name;
var uploadsRef = db.collection('uploads');
uploadsRef.where('user.id', '==', userId).get().then((snapshot) => {
snapshot.docs.forEach(doc => {
doc.set({"display_name" : name}); //Set the new data
});
}).then((err)=> {
console.log(err)
});
});
When this executes, I get the below error in the logs.
TypeError: doc.set is not a function
at snapshot.docs.forEach.doc (/user_code/index.js:31:21)
at Array.forEach (native)
at uploadsRef.where.get.then (/user_code/index.js:29:27)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
And also the below.
Unhandled rejection
How do I approach the problem? What is the best approach to deal with the snapshots document updates?
When you do a get() on a Query object, it will yield a
QuerySnapshot object. When you use its docs property, you're iterating an array of QuerySnapshotDocument objects that contain all the data from the matched documents. It looks like you're assuming that a QuerySnapshotDocument object has a set() method, but you can see from the linked API docs that it does not.
If you want to write back to a document identified in a QuerySnapshotDocument, use its ref property to get a DocumentReference object that does have a set() method.
doc.ref.set({"display_name" : name}); //Set the new data
Bear in mind that if you make this change, it will run, but may not update all the documents, because you're also ignoring the promise returned by the set() method. You'll need to collect all those promises into an array and use Promise.all() to generate a new promise to return from the function. This is necessary to help Cloud Functions know when all the asynchronous work is complete.

Sorting a map of keys and values please to order a list in ES6 with Lodash (Keeping Firebase Key: UID)?

I'm currently trying to sort a list of employees in order of names but I'm having difficulty in such.
I'm using React-Native, with ES6, Lodash and firebase.
Firebase structure appears as:
The state.employees structure appears as:
{"-KgMYnBrqXCIPqjs0n7x":{"name":"James","phone":"123766","shift":"Monday"}, "-KgRiK6qiJsoZ_HBXt7K":{"name":"Nick","phone":"123767","shift":"Tuesday"},"-KgRiM77VTOejvYPWPIp":{"name":"Henry","phone":"123","shift":"Thursday"},"-KgRiOaN14OeSjaYWb1O":{"name":"Charlie","phone":"5643","shift":"Saturday"}}
and continues with more employees each having a UID and then properties such as name, phone and shift.
Although I am able to order the list with _.orderBy provided by Lodash, it appears that when doing so the UID as the key provided by firebase is removed when applied say before _.map.
This is what I currently have which orders but the UID is removed, the _.map works fine keeping the UID with the properties but is unordered.
const mapStateToProps = state => {
const sortedEmployees = _.orderBy(
state.employees,
[employee => employee.name.toLowerCase()]
);
const employees = _.map(sortedEmployees, (val, uid) => {
return { ...val, uid };
});
return { employees };
};
Thanks very much
_.orderBy(collection, [iteratees=[_.identity]], [orders]) https://lodash.com/docs/4.17.4#orderBy
It seems when lodash maps over collection of type Object, it omits its key.
One way to fix would be to convert the collection of object from firebase response to an array . Ex:
const arrWithKeys = Object.keys(state.employees).map((key) => {
return {
key,
...state.employees[key]
}
});
and then use
const sortedEmployees = _.orderBy(
arrWithKeys,
[employee => employee.name.toLowerCase()]
);
You can also use firebase sorting in your firebase query
https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data
Thanks for your response #agent_hunt. I tried implementing it as suggested having the same structure as that of _.map but it then complained about TypeError: "Cannot convert undefined or null to object" where it is not null nor undefined and actually matches the original _.map but this time ordered.
To stop it complaining about, I added as a final thought the _.map again but this time taking the sortedEmployees and returning { ...val, uid} and it works.
Couldn't get the firebase query approach to work, as I had to order by a child of a child (key) not knowing the key for each employee.
Here's what I implemented following your above arrWithKeys and sortedEmployees adding _.map as an after thought works
const mapStateToProps = state => {
const arrWithKeys = Object.keys(state.employees).map((uid) => {
return {
...state.employees[uid],
uid,
};
});
const sortedEmployees = _.orderBy(
arrWithKeys,
[employee => employee.name.toLowerCase()],
);
const employees = _.map(sortedEmployees);
return { employees };
};
https://lodash.com/docs/4.17.4#map
https://lodash.com/docs/4.17.4#orderBy

Resources