Delete item after a date (Firebase Cloud function) - javascript

I'm trying to write a firebase cloud function to delete automatically an event after is date is passed.
Based on this example Firebase example, I came to this, but when I'm uploading it on Firebase, it is running on Firebase side but it is not deleting events.
Do you guys have advices or see something wrong in my code ? Is it possible that the problem may coming from the trigger onWrite() ?
/* My database structure
/events
item1: {
MyTimestamp: 1497911193083
},
item2: {
MyTimestamp: 1597911193083
}
...
*/
// Cloud function to delete events after the date is passed
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.deleteOldItems = functions.database.ref('/events/{eventId}').onWrite((change) => {
const ref = change.after.ref.parent; // reference to the parent
const now = Date.now();
const oldItemsQuery = ref.orderByChild('MyTimestamp').endAt(now);
return oldItemsQuery.once('value').then((snapshot) => {
// create a map with all children that need to be removed
const updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
return ref.update(updates);
// execute all updates in one go and return the result to end the function
});
});

There is nothing wrong with the code, just update you cloud functions & admin:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.deleteOldItems = functions.database.ref("/events/{eventId}").onWrite((change, context) => {
if (change.after.exists() && !change.before.exists()) {
const ref = change.after.ref.parent;
const now = Date.now();
const oldItemsQuery = ref.orderByChild('MyTimestamp').endAt(now);
return oldItemsQuery.once('value').then((snapshot) => {
const updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
return ref.update(updates);
});
} else {
return null;
}
});
Run the following in the functions folder:
npm install firebase-functions#latest --save
npm install firebase-admin#5.11.0 --save
Reference here for more details

try to change
admin.initializeApp();
to :
admin.initializeApp(functions.config().firebase);

Related

Node.js TypeError: admin.firestore(...).collection(...).doc(...).get(...).data is not a function

I am trying to get a url property of a document by first running an if function to see whether another document has certain property
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require("firebase-admin");
admin.initializeApp();
exports.writeToClay = functions
.region("europe-west1")
.firestore
.document("/reviews/{documentId}")
.onWrite((change, context) => {
// Grab the current value of what was written to Firestore.
const websiteId = change.before.data().websiteId;
console.log("websiteID:", websiteId);
if (change.after.data().code == "1") {
const url = admin.firestore().collection(
"websites"
).doc(websiteId).get().data().url;
console.log("url:", url);
}
});
The get() returns a Promise and does not have data() method on it. Try refactoring the code as shown below and handle the promise:
exports.writeToClay = functions
.region("europe-west1")
.firestore
.document("/reviews/{documentId}")
.onWrite(async (change, context) => { // <-- async function
const websiteId = change.before.data().websiteId;
console.log("websiteID:", websiteId);
if (change.after.data().code == "1") {
// add await here
const snap = await admin.firestore().collection("websites").doc(websiteId).get()
const url = snap.data().url;
console.log("url:", url);
}
return
});

Test firestore trigger locally

I am writing a test which tests a firebase trigger. The problem, however, is that I cannot make it work.
I want to use the local firestore emulator and Jest in order to simulate a change in the firestore and see if the trigger does what it needs to do.
I require the cloud function in my test and I initialize my app
Setup.js:
const firebase = require('#firebase/testing');
const PROJECT_ID = 'project';
let admin;
let db;
const setupAdmin = async () => {
admin = firebase.initializeAdminApp({
projectId: PROJECT_ID
});
db = admin.firestore();
};
const getAdmin = () => {
return admin;
};
const getDb = () => {
return db;
};
module.exports.setupAdmin = setupAdmin;
module.exports.getAdmin = getAdmin;
module.exports.getDb = getDb;
Test.js
describe('Billing', () => {
let dbRef;
beforeAll(async () => {
const {db, admin} = require('../../../functions/helpers/setup');
dbRef = db;
});
afterAll(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
console.log(`View rule coverage information at ${COVERAGE_URL}\n`);
});
it('test', async () => {
const mockData = {
'Users/user1': {
uid: 'user1'
},
['Users/user1/Taxes/' + new Date().getFullYear().toString()]: {
totalExpenseEuro: 0
}
};
for (const key in mockData) {
const ref = dbRef.doc(key);
await ref.set(mockData[key]);
}
// Create mockup data
await dbRef.collection('Users').doc('user1').collection('Expenses').doc('expense1').set({
amountEuroInclVAT: 100
});
// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({amountEuroInclVAT: 0}, 'Users/user1/Expenses/expense1');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot(
{amountEuroInclVAT: 100},
'Users/user1/Expenses/expense1'
);
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(calculateTaxesOnExpenseUpdate);
wrapped(change, {
params: {
uid: 'test1'
}
});
});
});
Now the main problem comes when I try to access this db object in my trigger
const calculateTaxesOnExpenseUpdate = functions.firestore
.document('Users/{uid}/Expenses/{expenseId}')
.onWrite(async (change, context) => {
const {getDb} = require('../helpers/setup'); // This setup is the same as above
let db = getDb();
...
For some reason when I perform an action like (await db.collection('Users').get()).get('totalExpenseEuro'), Jest stops executing my code. When I set a debugger right after that line, it never gets printed. That piece of code crashes, and I have no idea why. I think the DB instance if not properly configured in my cloud trigger function.
Question: What is a good way of sharing the DB instance (admin.firestore()) between the test and the cloud trigger functions?

Error while splitting index.js file in firebase cloud function

I tried to split my index.js file in multiple files. I'd like to count the number of children in a database reference. Previously my index.js file was
exports.updateUserBookCount = functions.database.ref('/Users/{userID}/Books/{bookID}')
.onWrite(async (change,context)=>{
const collectionRef = change.after.ref.parent;
const userID = context.params.userID;
const countRef = admin.database().ref(`/UserInfo/${userID}/usersBooks`);
console.log("book counter : "+collectionRef);
const bookList = await collectionRef.once('value');
return await countRef.set(bookList.numChildren());
});
I created new file counter.js it's
//counter.js
exports.userBookCount = function(change,context,admin){
const collectionRef = change.after.ref.parent;
const userID = context.params.userID;
const countRef = admin.database().ref(`/UserInfo/${userID}/usersBooks`);
console.log("book counter : "+collectionRef);
const bookList = await collectionRef.once('value');
return await countRef.set(bookList.numChildren());
}
Then I changed index.js like
//index.js
const admin = require('firebase-admin');
admin.initializeApp();
const counter = require('./counter');
exports.updateUserBookCount = functions.database.ref('/Users/{userID}/Books/{bookID}')
.onWrite(async (change,context)=>{
counter.userBookCount(change,context,admin);
});
But I'm getting error in counter.js 9:28 error Parsing error: Unexpected token collectionRef while deploying.
I'm not clear on your structure, but I'm guessing you just want to be able to split up the files for code organization? If so, here's how I would have structured it:
//index.js
const admin = require('firebase-admin')
const functions = require('firebase-functions')
admin.initializeApp()
const counter = require('./counter.js')
exports.updateUserBookCount = functions.database.ref('/Users/{userID}/Books/{bookID}').onWrite(counter);
//counter.js
const admin = require('firebase-admin')
//This function becomes counter in your index.js - you don't get counter.userBookCount because you have a single export from this file
module.exports = (change, context) => {
// rest of your logic
}
//If you really want counter.userBookCount because you'll have other functions here, export multiple functions like this:
module.exports = {
userBookCount: (change, context) => {
// rest of your logic
},
someOtherBookFunction: (change, context) => { ... }
}

Calling a Firebase Cloud Function 'ForEach' child of a Snapshot

I'm trying to deploy a Firebase Cloud Function that sends a text message to its associated recipient for x number of text messages. The function is triggered in my iOS app when an update is made to the 'send' Realtime Database reference, indicating that the user has pressed the 'send' button.
My Firebase structure is
{
"user1uid": {
"send": false
"messagesToSend": {
"messageuid1": {
"messageText": "What's for dinner?",
"recipientNumber": "+18017378888",
}
"messageuid2:
"messageText": "Who won the Cowboys game?",
"recipientNumber": "+18017377787",
}
}
"user2uid": {
"send": false
"messagesToSend": {
"messageuid1": {
"messageText": "What's for dinner?",
"recipientNumber": "+18017378888",
}
"messageuid2:
"messageText": "Who won the Cowboys game?",
"recipientNumber": "+18017377787",
}
}
}
My code currently only sends one message, and I'm not sure how I can properly iterate through the messagesToSend node for each user and send all the messages in it.
I've been trying to follow the tutorial located here. I have looked at the following Stack Overflow responses but am unable to decipher or derive a solution from them:
Firebase cloud function promises
Am I using ForEach correctly?
My index.js code that sends one message is as follows:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
const twilio = require('twilio')
const accountSid = functions.config().twilio.sid;
const authToken = functions.config().twilio.token;
const client = new twilio(accountSid, authToken);
const twilioNumber = functions.config().twilio.number;
// Start cloud function
exports.sendSecrets = functions.database
.ref('/{uid}/send')
.onUpdate((change,context) => {
const uid = context.params.uid;
return admin.database().ref(uid+'/messagesToSend').once('value').then(snapshot => {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var messageData = childSnapshot.val();
**if (messageData.sanitized) return true;**
var message = messageData.messageText;
var phoneNumber = messageData.recipientNumber;
const textMessage = {
body: `From My App - ${message}`,
from: twilioNumber, // From Twilio number
to: phoneNumber // Text to this number
}
return client.messages.create(textMessage)
})
**return snapshot.ref.toString();**
});
});
Please note that the lines marked with ** at either end indicate that I know I need to return something based on error messages I received indicating that 'Each then() should return a value or throw'.
I make the assumption that you are using the twilio-node library that use promises: https://www.npmjs.com/package/twilio.
Since you want to send several messages in parallel, you have to use Promise.all(), as follows:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
const twilio = require('twilio')
const accountSid = functions.config().twilio.sid;
const authToken = functions.config().twilio.token;
const client = new twilio(accountSid, authToken);
const twilioNumber = functions.config().twilio.number;
// Start cloud function
exports.sendSecrets = functions.database
.ref('/{uid}/send')
.onUpdate((change,context) => {
const uid = context.params.uid;
return admin.database().ref(uid+'/messagesToSend').once('value')
.then(snapshot => {
const promises = [];
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var messageData = childSnapshot.val();
//**if (messageData.sanitized) return true;**
var message = messageData.messageText;
var phoneNumber = messageData.recipientNumber;
const textMessage = {
body: `From My App - ${message}`,
from: twilioNumber, // From Twilio number
to: phoneNumber // Text to this number
}
promises.push(client.messages.create(textMessage));
})
return Promise.all(promises);
})
// Edits made below to parentheses/brackets
.then(results => {
//Do whatever you want !!
// e.g. print the results which will be an array of messages
// (see https://www.twilio.com/docs/libraries/node#testing-your-installation)
})
});
You can also simply return Promise.all() as follows:
....
return Promise.all(promises);
})
});

Firebase Cloud Functions - Functions Predeploy Error When Splitting Functions into Multiple .JS Files

I understand Cloud Functions recently updated to v1.0.
I am trying to write multiple functions from within Android Studio. I plan on having several cloud functions, and want to ensure my data structure is correct. Here is the current setup I have:
index.js
const functions = require('firebase-functions');
const trackVote = require('./trackVote')
const trendingCopy = require('./trendingCopy')
const admin = require('firebase-admin');
admin.initializeApp();
exports.trackVote = functions.firestore.document('Polls/{pollId}/responses/{userId}').onCreate(trackVoteModule.handler);
exports.trendingCopy = functions.firestore.document('Polls').onCreate(trendingCopyModule.handler);
trackVote:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.handler = (change, context => {
const data = change.after.data();
const answerSelected = data.answer;
const answerRef = admin.firestore().doc(`Polls/${event.params.pollId}/answers/${answerSelected}`);
const voteCountRef = admin.firestore.doc(`Polls/${event.params.pollId}/vote_count`);
return admin.firestore().runTransaction(t => {
return t.get(answerRef)
.then(doc => {
if (doc.data()) {
t.update(answerRef, { vote_count: doc.data().vote_count + 1 });
}
})
};
//TODO DO NOT ADD TO GIT
return admin.firestore().runTransaction(t => {
return t.get(voteCountRef)
.then(doc =>){
if (doc.data()){
t.update(voteCountRef, {vote_count:doc.data().vote_count+1});
}
}
});
});
});
Below is my console:
Error: functions predeploy error: Command terminated with non-zero exit code1
EDIT: I have seen this as a proposed solution, however it provides multiple options and unsure of best practice: https://github.com/firebase/functions-samples/issues/170

Categories

Resources