How to avoid sharing Email on algolia, Using Firbase Functions - firebase

I am using Firebase functions to create Algolia indices for the full-text search app
here is how I create an index when someone post on Firebase:
export const onListingCreated = functions.firestore
.document('Listings/{listingId}')
.onCreate((snap, ctx) => {
return index.saveObject({
objectID: snap.id,
...snap.data(),
})
})
//...snap.data() has - AdTitle:"House" Email:"example#gmail.com"
//this works well the index is created like this on algolia
But I want to avoid sharing the email
I tried it Like this:
export const onListingCreated = functions.firestore
.document('Listings/{listingId}')
.onCreate((snap, ctx) => {
return index.saveObject({
objectID: snap.id,
...snap.data().AdTitle,
})
})
//on algolia the index was created like:
//0:H
//1:o
//2:u
//3:s
//4:e
I wanted to be AdTitle: "House"
Is there a way to avoid sharing sensitive information on algolia?

In your case snap.data().AdTitle is the string House and you are spreading over the string which makes your output object look like that.
Once you spread a string it is converted to an array. For example House is converted to ["H", "o", "u", "s", "e"] Now in javascript arrays are also type of object so when you try {...["H", "o", "u", "s", "e"]} it takes ["H", "o", "u", "s", "e"] as { 0: "H", 1: "o", 2: "u", 3: "s", 4: "e" } and spreads over that object giving what you have mentioned
let randomString = 'House'
let obj = {...randomString} //converts to array and then spreads the array
console.log(obj)
let arr = [...randomString]
console.log(arr)
let obj2 = {...arr}
console.log(obj2) //obj and obj2 give same result
Instead what you can do is
let randomString = 'House'
let obj = {AdTitle: randomString}
console.log(obj)
Or you can delete the Email property from the data object and then use your usual implementation
.onCreate((snap, ctx) => {
const dataObj = snap.data()
delete dataObj.Email
return index.saveObject({
objectID: snap.id,
...dataObj,
})
})

Related

Is there any way to combine range queries for different fields in cloud firestore?

I know that the firestore does not support range queries on different fields (as per the doc).
But I ran into a situation where I needed to check the quantity > 0 and order by discount percentage.
Like this
db.collection("Inventory").where("availableQuantity", ">", 0).orderBy("discountPercentage", "desc").get();
Can anyone help me with achieving this functionality?
Or suggest me a different data model to store the data.
here's the current data model
Let me suggest a different data model to enable you run your query.
add a bool field called availableQuantityGreaterThan0.
Keep it updated using cloud functions. Then run your query like this:
db.collection("Inventory")
.where("availableQuantityGreaterThan0", "==", true)
.orderBy("discountPercentage", "desc").get();
Example cloud function to keep the field updated:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const db = admin.firestore();
exports.onUpdate = functions.firestore
.document("/Inventory/{inventory_id}")
.onUpdate((change, context) => {
const params = context.params;
const inventoryId = params.inventory_id;
const inventory = change.after.data();
const availableQuantityGreaterThan0 = inventory.availableQuantity > 0;
return db.doc("/Inventory/" + inventoryId)
.set({ availableQuantityGreaterThan0 }, { merge: true });
});
Do this for onCreate also.

PROBLEM Getting a single field in a document in Firebase Firestore

I'm trying to do a Cloud Function in Firebase. Initially, I read my document. Then I would take two fields "a" and "b", from this document. Then I want to set another field of my doc ('rank') as the sum of 'a' and 'b'. I can't find a solution!
I want to take only the fields "a" and "b" from my document. Save them in variables. And use those variables to do the sum, and set the result in "rank" field.
I tried:
var data = doc.data()
a = data.a
b = data.b
but it doesn't work.
Code:
export const daicazzo = functions.https.onRequest((request,response)=>{
const store = admin.firestore();
//var b;
store.collection('questions').doc('LD92BBDOihAC3fHDyoV').get().then(doc =>{
if(doc.exists){
response.send(doc.data())
}
else{
response.send("Nothing")
}
}).catch(reason => {
console.log(reason)
response.send(reason)
})
store.collection('questions').doc('LD92BBDOihAC3fHDyoV').set({
rank: //a+b
})
.then(function() {
console.log("done");
})
.catch(function(error){
console.log("Error:",error);
});
});
Should do the work (might be some better solutions)
const store = admin.firestore();
export const daicazzo = functions.https.onRequest(async (request,response)=>{
const questionRef = store.doc(`questions/${LD92BBDOihAC3fHDyoV}`)
const doc = await questionRef.get()
const foundDoc = doc.exists
if (foundDoc) {
// getting both key/value pairs from doc object
const {a, b} = doc.data()
const rank = a + b // whatever is your logic...
// saving the rank in the same document
await questionRef.update({rank}) // or questionRef.set({rank}, {merge: true})
if you want to send back the doc with the updated rank without making another read, assuming you handle errors:
const { id } = doc
const updatedDoc = { ...doc.data(), id, rank }
return response.send(updatedDoc)
} else {
return response.send("Nothing")
}
});

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.

Firebase function - query firestore

Im trying to retrieve some data from firestore within a cloud function, but get nothing back. The same query on the client-side gives me the correct results. It's probably something small but I don't see the issue. What am I doing wrong?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
exports.myFunction = functions.https.onCall((data, context) => {
const info = getInfo();
//do some stuff with the info
return info;
}
function getInfo() {
const query = db
.collection('info')
.where('table_nr', '==', 1)
.where('number', '<=', 25)
.orderBy('number', 'desc')
.limit(1);
const info = query.get().then(snapshot => {
snapshot.forEach(doc => {
return doc.data();
})
})
return info;
}
When I make a call to this function I get: "data: null"
let info = functions.httpsCallable('myFunction')
info().then(res => { console.log(res) })
I tried a lot of different options, like when I change the last part to:
const info = query.get().then(snapshot => {
snapshot.docs;
})
I get an array with 1 object. So I'm sure there is a document in the query with data. The console.log gives me:
{data: Array(1)}
data: Array(1)
0: {_ref: {…}, _fieldsProto: {…}, _serializer: {…}, _validator: {…},
_readTime: {…}, …}
length: 1
__proto__: Array(0)
__proto__: Object
And:
return query.get().then(querySnapshot => {
if (querySnapshot.empty) {
return { exists: false }
} else {
return { exists: true }
}
})
The console.log:
{data: {…}}
data:
exists: true
__proto__: Object
__proto__: Object
Mabye good to add that I created an (working) index for the query.
In both cases, you're returning a promise for an object that isn't what you really want to send to the client. When you write a callable, you need to return a promise that resolves to the exact JavaScript object that you want to send. You can't just return anything. What you'll have to do is convert that querySnapshot into plain old JavaScript objects that describe what you want the client to know. A querySnapshot object itself is not serializable - it is a complex object that describes many things about the query results.
First define this: What exactly do you want the client to receive? Define what the actual JavaScript object should look like. Now, convert the query results to look like that. At a minimum, you can send the entire set of documents as plain JS objects like this:
return query.get().then(querySnapshot => {
return querySnapshot.docs.map(doc => doc.data());
})
This will return an array to the client with raw document objects. But it's not clear to me that's what you want to send (since you didn't define your expectations). But it's a start.
So to give a clear example of using the .where in the Firebase Functions library for Firebase Cloud Functions
admin.firestore().collection("fruit")
.where("color", "==", "purple")
With ordering
admin.firestore().collection("fruit").orderBy("size")
.where("color", "==", "purple")
See demo doc if you want more details about how admin functions
Furthermore, the list of all query functions are found in the Query class can be used from the Firestore Functions library by using method chaining like my two examples in case you'd like things like "limit", "offset", etc.

Query firestore to get all tokens from userid's

In my cloud function i have an array that contains all userId's that need to get a cloud-message(notification)
const aNotify = [{id: 'id001', text: 'specialTextFor001'}, {id: 'id002', text: 'specialTextFor002'};
This is how the devices collection looks like. the Document ID is the token ID but to find them i need to query on the userId
Is it possible to do it through the DB like with a where clause or do I need to do this by getting all devices and in cloud method do a foreach... ?
In order to find a device document corresponding to a userId, you have to use a simple query like:
const db = admin.firestore();
db.collection('devices').where("userId", "==", element.id).get();
see the corresponding doc here.
Since you need to make a query for each element of the aNotify array, you need to use Promise.all(), since get() returns a Promise.
Something like the following will work. You have to adapt it in order to correctly return the promises in your Cloud Function (since you didn't share your Cloud Function code it is difficult to give more guidance on this point).
const db = admin.firestore();
var aNotify = [{ id: 'id001', text: 'specialTextFor001' }, { id: 'id002', text: 'specialTextFor002' }];
var promises = []
aNotify.forEach(function (element) {
promises.push(db.collection('devices').where("userId", "==", element.id).get());
});
return Promise.all(promises)
.then(results => {
results.forEach(querySnapshot => {
querySnapshot.forEach(function (doc) {
console.log(doc.id, " => ", doc.data());
//here, either send a notification for each user of populate an array, or....
//e.g. return admin.messaging().sendToDevice(doc.data().token, ....);
});
});
});
Note that the results array has exactly the same order than the promises array. So it is not complicated to get the text property of the corresponding object of the aNotify array when you send the notifications.

Resources