Cloud Functions for Firebase: how to reference a key in a list that has been just pushed - javascript

My question is basically what to do in your cloud function, if you want to reference keys that have been generated when the client called push().
/providerApps/{UID}/ is my path to a list of appointment nodes, so each appointment node is at /providerApps/{UID}/someKey.
I need the "new item in the list", the one that was added with push(), so I thought I could order the keys and simply get the last one, but that does not work:
// (Try to) Listen for new appointments at /providerApps/{pUID}
// and store the appointment at at /clientApps/{cUID}
// cUID is in the new appointment node
exports.storeNewAppForClient = functions.database.ref("/providerApps/{UID}").onWrite(event => {
// Exit when the data is deleted.
if (!event.data.exists()) {
console.log("deletion -> exiting");
return;
}
const pUID = event.params.UID;
const params = event.params;
console.log("params: ", params);
const firstAppVal = event.data.ref.orderByKey().limitToLast(1).val();
// TypeError: event.data.ref.orderByKey(...).limitToLast(...).val is not a function
const date = firstAppVal["dateStr"];
const cUID = firstAppVal["clientUID"];
return event.data.ref.root.child("clientApps").child(cUID).child(date).set(pUID);
});
I guess I could do it on the client side with push().getKey() and allow providers to write into clientApps node, but that seems to be less elegant.
Any ideas how to do this with cloud functions?
As an illustration, my data structure looks like this:
there are provider and their clients who make appointments
Cheers

Change your trigger location to be the newly created appointment instead of the list of appointments. Then you can access the appointment data directly:
exports.storeNewAppForClient = functions.database.ref("/providerApps/{UID}/{pushId}").onWrite(event => {
// Exit when the data is deleted.
if (!event.data.exists()) {
console.log("deletion -> exiting");
return;
}
const pUID = event.params.UID;
const params = event.params;
console.log("params: ", params);
const date = event.data.child('dateStr').val();
const cUID = event.data.child('clientUID').val();
return admin.database().ref('clientApps').child(cUID).child(date).set(pUID);
});
(updated for Frank's comment)

Related

Delete item from CosmosDB

I'm trying to delete entries from Azure CosmosDB.
Documetation says:
/**
* Delete item
* Pass the id and partition key value to delete the item
*/
const { resource: result } = await container.item(id, category).delete();
console.log(`Deleted item with id: ${id}`);
My function to delete and items is:
// takes in an entry id to detete and deletes it with the documentation
async function findItemsToDelete(idToDelete){
const category = config.partitionKey; // partitionKey is {kind: "Hash", paths: ["/requests"]} and I've also tried just "/requests"
// the four lines below are correct
const { endpoint, key, databaseId, containerId } = config;
const client = new CosmosClient({ endpoint, key });
const database = client.database(databaseId);
const container = database.container(containerId);
// query to return item with id (random code to make sure the item exists)- not important
const querySpec = {
query: `SELECT * FROM c WHERE c.id = "${idToDelete}"`
};
const { resources: items } = await container.items
.query(querySpec)
.fetchAll();
// below is the delete code from the documentation
const { resource: result } = await container.item(idToDelete, category).delete();
// random array holding the item that was just deleted- not important
return items;
}
When I try calling this, I get an error that says: Entity with the specified id does not exist in the system. Does anyone know how to properly implement this? I know the id is correct but I believe I may be doing something wrong with the partitionKey/Category part.
I saw in this post: Cannot delete item from CosmosDB
that i may need the partition key value, but I dont know what that is or how to get it. Please let me know if you know what's going on!

Store multiple channel IDs in a JSON file

I am making a bot using Discord.js and it only needs to track messages in a certain channel, which I currently have this hard-coded for testing purposes.
var { channelID } = require(`./config.json`);
bot.on("message", async (message) => {
const args = message.content.split(/ +/g);
if (message.channel.id === channelID) {
// ...
}
});
I would like for it to store multiple IDs in a JSON file and to have a [p]setchannel command, that would allow me to add one.
I tried this guide, with no luck.
What you probably want to do is store an array of IDs so that you can retrieve them later.
You should have a channelIDs property in your JSON file set to an empty array. Inside your code you can fetch it like this:
const { channelIDs } = require('./config.json') // Now it's an empty array: []
When you want to update this array you should update your local one first, and then you can update the config file: to do that you can use fs.writeFileSync() in combination with JSON.stringify().
const fs = require('fs')
function addChannelID(id) {
channelIDs.push(id) // Push the new ID to the array
let newConfigObj = { // Create the new object...
...require('./config.json'), // ...by taking all the current values...
channelIDs // ...and updating channelIDs
}
// Create the new string for the file so that it's not too difficult to read
let newFileString = JSON.stringify(newConfigObj, null, 2)
fs.writeFileSync('./config.json', newFileString) // Update the file
}
Once you set this function you can add a new ID every time you want, just by calling addChannelID('channel_id').
To check whether the channel the message is coming from should be considered you can use this:
if (channelIDs.includes(message.channel.id)) {
// OK
}

Flutter Firebase Functions Error: Database not defined

I've created a delete oldFiles function for my Database that deletes nodes from my chat messages. I've used the example function provided by Firebase and updated it to fit my use. My database structure is databaseName/messages/{pushId} and I've added const functions = require('firebase-functions') and const admin = require('firebase-admin') and admin.initializeApp(). Here is what I have...
exports.deleteOldItems = functions.database.ref('messages/{pushId}').onWrite(async (change) => {
const ref = change.after.ref.parent; // reference to the parent
const now = Date.now();
const cutoff = (DateTime.now().millisecondsSinceEpoch - CUT_OFF_TIME);
const oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
const snapshot = await oldItemsQuery.once('value');
// create a map with all children that need to be removed
const updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
When I review my Function logs, I'm getting the following errors...
ReferenceError: DateTime is not defined
at exports.deleteOldItems.functions.database.ref.onWrite (/srv/index.js:17:18)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:131:23)
at /worker/worker.js:825:24
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
And my Functions are finishing with status: error. Any ideas to what may be going on?
DateTime isn't a valid JavaScript object or identifier. If you want to work with dates and times, you will need to work with Date, as you are in the line just above where you have DateTime. You should probably review the JavaScript documentation for Date to learn how it works.

Delete same value from multiple locations Firebase Functions

I have a firebase function that deletes old messages after 24 hours as in my old question here. I now have just the messageIds stored in an array under the user such that the path is: /User/objectId/myMessages and then an array of all the messageIds under myMessages. All of the messages get deleted after 24 hours, but the iDs under the user's profile stay there. Is there a way to continue the function so that it also deletes the messageIds from the array under the user's account?
I'm new to Firebase functions and javascript so I'm not sure how to do this. All help is appreciated!
Building upon #frank-van-puffelen's accepted answer on the old question, this will now delete the message IDs from their sender's user data as part of the same atomic delete operation without firing off a Cloud Function for every message deleted.
Method 1: Restructure for concurrency
Before being able to use this method, you must restructure how you store entries in /User/someUserId/myMessages to follow best practices for concurrent arrays to the following:
{
"/User/someUserId/myMessages": {
"-Lfq460_5tm6x7dchhOn": true,
"-Lfq483gGzmpB_Jt6Wg5": true,
...
}
}
This allows you to modify the previous function to:
// Cut off time. Child nodes older than this will be deleted.
const CUT_OFF_TIME = 24 * 60 * 60 * 1000; // 2 Hours in milliseconds.
exports.deleteOldMessages = functions.database.ref('/Message/{chatRoomId}').onWrite(async (change) => {
const rootRef = admin.database().ref(); // needed top level reference for multi-path update
const now = Date.now();
const cutoff = (now - CUT_OFF_TIME) / 1000; // convert to seconds
const oldItemsQuery = ref.orderByChild('seconds').endAt(cutoff);
const snapshot = await oldItemsQuery.once('value');
// create a map with all children that need to be removed
const updates = {};
snapshot.forEach(messageSnapshot => {
let senderId = messageSnapshot.child('senderId').val();
updates['Message/' + messageSnapshot.key] = null; // to delete message
updates['User/' + senderId + '/myMessages/' + messageSnapshot.key] = null; // to delete entry in user data
});
// execute all updates in one go and return the result to end the function
return rootRef.update(updates);
});
Method 2: Use an array
Warning: This method falls prey to concurrency issues. If a user was to post a new message during the delete operation, it's ID could be removed while evaluating the deletion. Use method 1 where possible to avoid this.
This method assumes your /User/someUserId/myMessages object looks like this (a plain array):
{
"/User/someUserId/myMessages": {
"0": "-Lfq460_5tm6x7dchhOn",
"1": "-Lfq483gGzmpB_Jt6Wg5",
...
}
}
The leanest, most cost-effective, anti-collision function I can come up for this data structure is the following:
// Cut off time. Child nodes older than this will be deleted.
const CUT_OFF_TIME = 24 * 60 * 60 * 1000; // 2 Hours in milliseconds.
exports.deleteOldMessages = functions.database.ref('/Message/{chatRoomId}').onWrite(async (change) => {
const rootRef = admin.database().ref(); // needed top level reference for multi-path update
const now = Date.now();
const cutoff = (now - CUT_OFF_TIME) / 1000; // convert to seconds
const oldItemsQuery = ref.orderByChild('seconds').endAt(cutoff);
const snapshot = await oldItemsQuery.once('value');
// create a map with all children that need to be removed
const updates = {};
const messagesByUser = {};
snapshot.forEach(messageSnapshot => {
updates['Message/' + messageSnapshot.key] = null; // to delete message
// cache message IDs by user for next step
let senderId = messageSnapshot.child('senderId').val();
if (!messagesByUser[senderId]) { messagesByUser[senderId] = []; }
messagesByUser[senderId].push(messageSnapshot.key);
});
// Get each user's list of message IDs and remove those that were deleted.
let pendingOperations = [];
for (let [senderId, messageIdsToRemove] of Object.entries(messagesByUser)) {
pendingOperations.push(admin.database.ref('User/' + senderId + '/myMessages').once('value')
.then((messageArraySnapshot) => {
let messageIds = messageArraySnapshot.val();
messageIds.filter((id) => !messageIdsToRemove.includes(id));
updates['User/' + senderId + '/myMessages'] = messageIds; // to update array with non-deleted values
}));
}
// wait for each user's new /myMessages value to be added to the pending updates
await Promise.all(pendingOperations);
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
Update: DO NOT USE THIS ANSWER (I will leave it as it may still be handy for detecting a delete operation for some other need, but do not use for the purpose of cleaning up an array in another document)
Thanks to #samthecodingman for providing an atomic and concurrency safe answer.
If using Firebase Realtime Database you can add an onChange event listener:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.onDeletedMessage = functions.database.ref('Message/{messageId}').onChange(async event => {
// Exit if this item exists... if so it was not deleted!
if (event.data.exists()) {
return;
}
const userId = event.data.userId; //hopefully you have this in the message document
const messageId = event.data.messageId;
//once('value') useful for data that only needs to be loaded once and isn't expected to change frequently or require active listening
const myMessages = await functions.database.ref('/users/' + userId).once('value').snapshot.val().myMessages;
if(!myMessages || !myMessages.length) {
//nothing to do, myMessages array is undefined or empty
return;
}
var index = myMessages.indexOf(messageId);
if (index === -1) {
//nothing to delete, messageId is not in myMessages
return;
}
//removeAt returns the element removed which we do not need
myMessages.removeAt(index);
const vals = {
'myMessages': myMessages;
}
await admin.database.ref('/users/' + userId).update(vals);
});
If using Cloud Firestore can add an event listener on the document being deleted to handle cleanup in your user document:
exports.onDeletedMessage = functions.firestore.document('Message/{messageId}').onDelete(async event => {
const data = event.data();
if (!data) {
return;
}
const userId = data.userId; //hopefully you have this in the message document
const messageId = data.messageId;
//now you can do clean up for the /user/{userId} document like removing the messageId from myMessages property
const userSnapShot = await admin.firestore().collection('users').doc(userId).get().data();
if(!userSnapShot.myMessages || !userSnapShot.myMessages.length) {
//nothing to do, myMessages array is undefined or empty
return;
}
var index = userSnapShot.myMessages.indexOf(messageId);
if (index === -1) {
//nothing to delete, messageId is not in myMessages
return;
}
//removeAt returns the element removed which we do not need
userSnapShot.myMessages.removeAt(index);
const vals = {
'myMessages': userSnapShot.myMessages;
}
//To update some fields of a document without overwriting the entire document, use the update() method
await admin.firestore().collection('users').doc(userId).update(vals);
});

parse server: include pointer in live query in javascript sdk

I am using parse server to live query a class containing rows with pointers.
When I use include() in the normal query it get all the data of the pointer but in the live query I only get the objectId
Code:
var currentUser = Parse.User.current();
const Conversation = Parse.Object.extend("conversations");
var fromQuery = new Parse.Query(Conversation);
fromQuery.equalTo("from", currentUser );
var toQuery = new Parse.Query(Conversation);
toQuery.equalTo("to", currentUser);
var mainQuery = Parse.Query.or(fromQuery, toQuery);
mainQuery.include("to")
mainQuery.include("from")
mainQuery.include("lastMessage")
// FIXME: DEBUG:
this.convsubscription = mainQuery.subscribe();
mainQuery.find().then((conversations) => {
for (var i = 0; i < conversations.length; i++){
var object = conversations[i]
this.conversations.unshift(object);
}
})
this.convsubscription.on('update', (object) => {
// we will get the index of updated object
var index = this.conversations.findIndex(x => x.id == object.id);
console.log(index);
// then we will remove the old object and insert the updated one
this.conversations.splice(index, 1 ,object)
console.log(JSON.stringify(this.conversations[index].get('lastMessage')))
})
When I do JSON.stringify(this.conversations[index].get('lastMessage')) it only gives the objectId. I need a way to access the content of the pointer lastMessage
Regards
includeKey()/include() isn't supported in Live Queries:
this is a server side issue, the includeKey is ignored when subscribing to the query. The decision tree is processed synchronously after an object is saved on parse-server, therefore we don't have the opportunity to inject inclusions. We'd need to refactor the whole serverside logic in order to support those.
See related issues to keep track:
https://github.com/parse-community/ParseLiveQuery-iOS-OSX/issues/30
https://github.com/parse-community/parse-server/issues/1686

Categories

Resources