how to calculate duration with cloud functions - node.js

i want to calculate duration for a given task/event. I am using cloud functions. I have four fields in flutter app;
Start date
Start time
End date
End time
how can i calculate the duration. Please help. i am using typescript for my cloud functions.
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
admin.initializeApp()
export const updateCharts =
functions.firestore.document('users/{UserId}/count/{uid}')
.onWrite(async(change, _) => await updateStats(change))
async function updateStats (change:
functions.Change<functions.firestore.DocumentSnapshot>){
const chartRating = change.after.ref.parent
let title = (await chartRating.where('Title', '==', 'Game').get()).size;
//duration
const restaurantRef = chartRating.parent!
console.log('{restaurantRef.path} now has ${Title}')
await restaurantRef.update({
Title: Title,
})

// place this code where you want to measure from.
var startTime = new Date().getTime();
...
// place this code where you want to get the difference
var diff = new Date().getTime() - startTime;
Use the getTime method to get the time in total milliseconds since 1970-01-01.

Related

Firebase Admin in Cloud Functions data manipulation problem

Firebase Realtime Database structure
freepacks contains 2 important elements:
current, that is the quizpack ID that I will download from the mobile app (retrieving it from quizpacks).
history, that is a node where I add all the quizpacks ID that I put in current over time with a scheduled function in Cloud Functions.
What I need to do EVERY TIME THE CLOUD FUNCTION EXECUTES
Step 1: Add value of current in history with the current timestamp.
Step 2: Substitute the current value with another quizpack ID that is not in history.
Done
How I tried to accomplish this target
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.scheduledFunction = functions.pubsub.schedule('* * * * *').onRun((context) => {
// Current timestamp
const dt = new Date();
const timestamp = `${
(dt.getMonth()+1).toString().padStart(2, '0')}/${
dt.getDate().toString().padStart(2, '0')}/${
dt.getFullYear().toString().padStart(4, '0')} ${
dt.getHours().toString().padStart(2, '0')}:${
dt.getMinutes().toString().padStart(2, '0')}:${
dt.getSeconds().toString().padStart(2, '0')}`
// Download the entire node 'freepacks'
return admin.database().ref('quiz/freepacks').once('value').then((currentSnap) => {
const currentPack = currentSnap.val().current;
// Add current to history
admin.database().ref('quiz/freepacks/history/' + currentPack).set(timestamp);
// Download entire history node
admin.database().ref('quiz/freepacks/history').once('value').then((historySnap) => {
const history = historySnap.val();
console.log('HISTORY: ' + history);
// Download entire quizpacks node
admin.database().ref('quiz/quizpacks').once('value').then((quizpacksSnap) => {
for(quizpack in Object.keys(quizpacksSnap.val())) {
console.log('ITERANDO: ' + quizpack);
// Add the new current if it isn't in history
if (historySnap[quizpack] == undefined) {
admin.database().ref('quiz/freepacks/current').set(quizpack);
break;
}
}
});
})
});
});
What I get from the previous code
Starting point:
First execution: history updates well but updating current didn't work
Second execution ad so on...
current doesn't update anymore (it is stuck on 0)
My experience with JavaScript and Firebase Admin is ~0... What is the problem with my code? Thanks in advance for the help!
First thing is all read/write operations return promises hence you should handle them. In this answer I used async-await syntax. .ref("quiz/freepacks") fetches the complete node i.e. current and the history so you don't have to query the history node again as in original code. Other changes are just Javascript tweaks such as using .find() instead of for-loop and so on..
Try changing your function to:
exports.scheduledFunction = functions.pubsub
.schedule("* * * * *")
.onRun(async (context) => {
// Getting Date
const dt = new Date();
const timestamp = `${(dt.getMonth() + 1).toString().padStart(2, "0")}/${dt
.getDate()
.toString()
.padStart(2, "0")}/${dt.getFullYear().toString().padStart(4, "0")} ${dt
.getHours()
.toString()
.padStart(2, "0")}:${dt.getMinutes().toString().padStart(2, "0")}:${dt
.getSeconds()
.toString()
.padStart(2, "0")}`;
// Download the entire node 'freepacks'
const currentSnap = await firebase
.database()
.ref("quiz/freepacks")
.once("value");
// Checking current free pack ID and array of previous free packs
const currentPack = currentSnap.val().current || "default";
const freeHistoryIDs = [
...Object.keys(currentSnap.val().history || {}),
currentPack,
];
// Add current free pack to free history
await firebase
.database()
.ref("quiz/freepacks/history/" + currentPack)
.set(timestamp);
// Download entire quizpacks node
const quizpacksSnap = await firebase
.database()
.ref("quiz/quizpacks")
.once("value");
const quizPackIDs = Object.keys(quizpacksSnap.val() || {});
const newPack = quizPackIDs.find((id) => !freeHistoryIDs.includes(id));
console.log(newPack);
if (!newPack) {
console.log("No new pack found")
}
return firebase.database().ref("quiz/freepacks/current").set(newPack || "none");
});

delete data in firestore after n time using cloud functions

I have been trying to find a way in which the documents from a collection in firestore can get deleted after a certain amount of time in my case 24h, for doing this I realized that I have to use firebase cloud functions so after investigating I found this post which does the work that I need to add in my project except that I am using typescript instead of javascript which is being used in that question and I cant change it to javascript because I am already using typescipt for sending notifications to users. How can I change the following code to typescript?
//index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
const db = admin.firestore();
export const deleteOldItems = functions.database
.ref('stories/{storyId}')
.onWrite((change, context) => {
var ref = change.after.ref.parent;
var now = Date.now();
var cutoff = now - 24 * 60 * 60 * 1000;
var oldItemsQuery = ref.orderByChild('created').endAt(cutoff);
return oldItemsQuery.once('value', function(snapshot) {
// create a map with all children that need to be removed
var updates = {};
snapshot.forEach(function(child) {
updates[child.key] = null
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
});
here is an image of the data that needs to be erased after 24h, created field is the date when the document was created and deleted id the date where the doc should be deleted.
EDIT: I turns out the problem is not because of using typescript but because I am using firestore instead of firebase real time database so I created this code which is supposed to make things work but it doesnt. How can I fix this?
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
const db = admin.firestore();
export const deleteOldItems = functions.firestore
.document('stories/{storyId}')
.onWrite(async (change, context) => {
const getData = await db.collection('stories').where('deleted', '>', Date.now()).get().then(
(snapshot) => snapshot.forEach((doc) =>
doc.ref.delete()
)
);
return getData;
});
You are mixing async/await with then and, in addition, you are not waiting that the set of calls to the delete() asynchronous method are all completed. So the Cloud Function platform may clean up the Cloud Function instance before all the work is done. You need to wait that all this asynchronous work is done before returning a Promise or a value. More details here in the doc.
The following should do the trick:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
const db = admin.firestore();
export const deleteOldItems = functions.firestore
.document('stories/{storyId}')
.onWrite(async (change, context) => {
const querySnapshot = await db.collection('stories').where('deleted', '>', Date.now()).get();
const promises = [];
querySnapshot.forEach((doc) => {
promises.push(doc.ref.delete());
});
return Promise.all(promises);
});
Since you want to delete a variable number of documents, you need to use Promise.all() to execute in parallel the asynchronous deletion tasks (Since the delete() method returns a Promise).
Updates following you questions in the comments below:
1/ Promise.all() returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the promises in the array have resolved. The main interest of Promise.all() is that it returns a single Promise, therefore you can easily know when all the calls to the asynchronous delete() method (which returns a Promise) are done.
2/ The onWrite event type is triggered when a doc is created, modified or deleted. Since you used this event type in your question I kept it. If you want to schedule the execution, use a scheduled Cloud Function. The "inner code" is exactly the same.
I think that ".onWrite(async (change, context) " its only called when the document is created, you need to create a task every x time to check If there is a document to delete, then you make the query and delete de document.
You can use Cron job from you server o task scheduler, I think google cloud have its own task scheduler.
A functions can not last more than 60 seconds, after that sent an error.

Pub/Sub Cloud Function does not Update Document in Subcollection

I am trying to update a field in my document in Firestore. The general location of the document would be "/games/{userId}/userGames/{gameId}. And in this game, there is a property called "status" which changes accordingly to the games start and end time.
As you can guess, the if the start time is bigger than the "now" timestamp and the status is "TO_BE_PLAYED", the game will begin and the status will be 1, "BEING_PLAYED". Also, if the end time is bigger than the "now" timestamp and the status is "BEING_PLAYED", the game will end, therefore the status will be 2, "PLAYED". I want to create a cloud function that is capable to do so.
However, even if the function logs output 'ok', the values are never updated. Unfortunately, I do not have that much experience in Javascript too.
THE CODE
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const STATUS_PLAYED = 2;
const STATUS_BEING_PLAYED = 1;
const STATUS_TO_BE_PLAYED = 0;
exports.handleBeingPlayedGames = functions.runWith({memory: "2GB"}).pubsub.schedule('* * * * *')
.timeZone('Europe/Istanbul') // Users can choose timezone - default is America/Los_Angeles
.onRun(async () => {
// current time & stable
// was Timestamp.now();
const now = admin.firestore.Timestamp.fromDate( new Date());
const querySnapshot = await db.collection("games").get();
const promises = [];
querySnapshot.forEach( doc => {
const docRef = doc.ref;
console.log(docRef);
promises.push(docRef.collection("userGames").where("status", "==", STATUS_BEING_PLAYED).where("endtime", "<", now).get());
});
const snapshotArrays = await Promise.all(promises);
const promises1 = [];
snapshotArrays.forEach( snapArray => {
snapArray.forEach(snap => {
promises1.push(snap.ref.update({
"status": STATUS_PLAYED,
}));
});
});
return Promise.all(promises1);
});
exports.handleToBePlayedGames = functions.runWith({memory: "2GB"}).pubsub.schedule('* * * * *')
.onRun(async () => {
// current time & stable
// was Timestamp.now();
const now = admin.firestore.Timestamp.fromDate(new Date());
const querySnapshot = await db.collection("games").get();
const promises = [];
querySnapshot.forEach( async doc => {
const docData = await doc.ref.collection("userGames").where("status", "==", STATUS_TO_BE_PLAYED).where("startTime", ">", now).get();
promises.push(docData);
});
const snapshotArrays = await Promise.all(promises);
const promises1 = [];
snapshotArrays.forEach( snapArray => {
snapArray.forEach(snap => {
promises1.push(snap.ref.update({
"status": STATUS_BEING_PLAYED,
}));
});
});
return Promise.all(promises1);
});
Okay, so this answer goes to lurkers trying to solve this problem.
First I tried to solve this problem by brute force and not including much thinking and tried to acquire the value in subcollection. However, as I searched, I've found that denormalizing (flattening) data actually solves the problem a bit.
I created a new directory under /status/{gameId} with the properties
endTime, startTime, and status field and I actually did it on a single level by using promises. Sometimes denormalizing data can be your savior.
How can startTime be greater than now? Is it set by default to a date in the future?
My current assumption is that a game cannot set it's status to STATUS_BEING_PLAYED because of the inconsistency with startTime. Moreover, a game cannot have the status STATUS_PLAYED because it depends on having STATUS_BEING_PLAYED, which cannot have.
My recommendation would be to set the field startTime and endTime to null by default. If you do so you can check if a game has to be set to STATUS_BEING_PLAYED with this:
doc.ref.collection("userGames")
.where("status", "==", STATUS_TO_BE_PLAYED)
.where("startTime", "<", now)
.where("endTime", "==", null)
.get();
You could check if a game has to be on STATUS_PLAYED with this (exactly as you did):
docRef.collection("userGames")
.where("status", "==", STATUS_BEING_PLAYED)
.where("endtime", "<", now)
.get();
Now there's something that you should wonder, is this the best approach to change a game's status? You are querying the whole game library of a user every single minute as you know read operations are charged so this approach would imply meaningful charges. Maybe you should simply use update the game's status when the game is started and closed.
Also notice that the equals operation is ==, not =.

Firebase Cloud Functions deletes nodes directly after rather than 24 hours later

My goal is to delete all the message nodes 24 hours after they were sent using Firebase Cloud Functions and the Realtime Database. I tried copy and pasting the answer from this post however for some reason the messages delete directly after they were created rather than the 24 hours later. If someone could help me solve this problem I would really appreciate it. I have tried multiple different answers based on the same issue and they haven't worked for me.
Here is my index.js file:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// 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 ref = change.after.ref.parent; // reference to the parent
const now = Date.now();
const cutoff = now - CUT_OFF_TIME;
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(child => {
updates[child.key] = null;
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
And my database structure is:
In the comments you indicated that you're using Swift. From that and the screenshot it turns out that you're storing the timestamp in seconds since 1970, while the code in your Cloud Functions assumes it is in milliseconds.
The simplest fix is:
// 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 ref = change.after.ref.parent; // reference to the parent
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(child => {
updates[child.key] = null;
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
Also see my answer here: How to remove a child node after a certain date is passed in Firebase cloud functions?

how to properly perform query by date in Firestore?

I am trying to make a cronjob to delete an event that has been passed by using http trigger. today is 7th September, so the event at 5 september ( 6fqmROcWD7K1pTFtXmJJ )like the picture below shall be captured in the query snapshot. but the document length is zero
here is the code I use:
export const cronJobDeletingPastEvents = functions.https.onRequest(async (request,response) => {
const dateNow = Date.now()
const pastEventsSnapshot = await admin.firestore().collection("events").where('dateTimeFinish', '<', dateNow).get()
console.log(pastEventsSnapshot.docs.length) // <---- the result is zero
// perform some actions ....
}
I don't know what went wrong in here, but I guess this is because dateNow is number, but the field dateTimeFinish in firestore is Timestamp object. but i dont know how to fix that
The following will work:
const dateNow = new Date()
const pastEventsSnapshot = await admin.firestore().collection("events").where('dateTimeFinish', '<', dateNow).get()
...
The followings would also work:
const dateNow = new Date('2018-09-07T01:25:54.000Z')
const dateNow = new Date('2018-09-07')

Resources