Change words with Firestore trigger onCreate - javascript

Im trying to use a Firestore trigger to change words, when sending a message.
This is my first time working with Firestore, and can't seams to figure it out.
I have a Firestore database that stores messages. A message have:
Name
Message
Timestamp
I would like to change the message to something else, on creation.
One of the things I have been trying is this:
const functions = require('firebase-functions');
exports.badWords = functions.firestore
.document('messeges/{messegeID}')
.onCreate(event => {
var data = event.data.data()
return data.ref.update({
message: "The message has been changes"
});
});

Just use the ref property of the event DocumentSnapshot, as follows:
exports.badWords = functions.firestore
.document('messeges/{messegeID}')
.onCreate((snap, context) => {
return snap.ref.update({
message: "The message has been changed"
});
});
Note that with Cloud Functions version >= v1.0.0 the callback function has two parameters (snap and context), see https://firebase.google.com/docs/functions/beta-v1-diff#cloud-firestore.
In case you are using a version < v1, you should update your SDK for Cloud Functions to the latest version, by running the following in the functions folder:
npm install firebase-functions#latest --save
npm install firebase-admin#latest --save-exact

Related

GCP Cloud Functions Gen 2 - Cannot call through NodeJs or gcloud

I created a gen 2 cloud function on my project "project-x" and used everything default, with permission Allow unauthenticated:
const functions = require('#google-cloud/functions-framework');
functions.http('helloHttp', (req, res) => {
res.send(`Hello ${req.query.name || req.body.name || 'World'}!`);
});
This generated a URL for this function, e.g. https://my-function-bvskvwq11c-uc.a.run.app, which when I call unauthenticated (or visit on the browser) it works. I see the response.
Now here's the problem...
A. Using the npm package #google-cloud/functions I tried to call this endpoint with the following:
await functionsClient.callFunction({
name: 'my-function',
})
This gives me a weird error of the format:
7 PERMISSION_DENIED: Cloud Functions API has not been used in project ********* before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudfunctions.googleapis.com/overview?project=********* then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
I say this is a weird error, cause the project ID provided in this error is not the ID of my project on GCP and when I visit the link provided it says I do not have permissions to view this project.
Then I tried to list my functions by doing the following, but I get back an empty list.
const [functions] = await functionsClient.listFunctions({
parent: `projects/project-x/locations/us-central1`,
});
// functions = [];
B. I thought to try gcloud this time, so first I listed all functions for my project:
gcloud functions list
This actually returned the correct function:
NAME STATE TRIGGER REGION ENVIRONMENT
my-function ACTIVE HTTP Trigger us-central1 2nd gen
I'm like cool! Let's try to call it now. So I run:
gcloud functions call my-function
And I get the following back:
ResponseError: status=[404], code=[Ok], message=[Function my-function in region us-central1 in project project-x does not exist]
Can someone please shed some light in all of these? Listing through the npm package yields different results than the gcloud command and neither of them are able to call the function. One gives 404 and the other one permission denied. What would be the best approach?
Answering my own question and summarizing.
To call a gen2 function from the gcloud command, use the --gen2 option, as stated on the gcloud documentation:
gcloud functions call my-function --gen2
To call gen2 functions from NodeJs, do not use the #google-cloud/functions package. As stated in Google's Rate Limits documentation:
The CALL API only applies to Cloud Functions (1st gen)... Please keep in mind that this API is meant for testing via Cloud Console or gcloud functions call CLI, and it cannot handle heavy traffic.
Instead, use the google-auth-library package, where they also have an example on how to call a function:
const {GoogleAuth} = require('google-auth-library');
async function main() {
const url = 'https://cloud-run-1234-uc.a.run.app';
const auth = new GoogleAuth();
const client = await auth.getIdTokenClient(url);
const res = await client.request({url});
console.log(res.data);
}

How to unit test a function that is calling other functions?

I am testing my REST API with jest for the first time and I am having a hard time unit testing the controllers.
How should I go about testing a function that contains other function calls (npm modules as well as other controllers). Here's pseudo code. (I've tried mocking but can't seem to get it right)
async insertUser(uid, userObject){
// Function to check user role and permissions
const isAllowed = await someotherController.checkPermissions(uid);
//Hash password using an npm module
const pass = password.hash;
//const user = new User(userObj)
user.save();
}
So basically, how to test such a function that contains all these different functions.
I have written tests for simple function and they went all good but I am stuck at these functions.
I would go with https://sinonjs.org/ and mock somecontroller. Be carefull with the user.save(). It looks like you use some kind of persistence here. In case you use mongoose, you should have a look at https://github.com/Mockgoose/Mockgoose.

Firestore Function Trigger wont trigger

For some reason the onWrite Event is not triggering in Firestore.
Here you see the look of the function in the Firebase Website. Here is the trigger inside of my function ts file:
exports.newCommentCounter = functions.region('europe-west1').database.ref('posts/{PostID}/comments/{CommentID}').onWrite(async(change) => {
The logs are empty like the function never got triggered.
For example adding a Document to posts/postidblabla/comments/commentidblabla wont trigger the function.
If I am not mistaking, this comes from the fact that you are using async in a Node.js 6 Cloud Function.
The following should work:
exports.newCommentCounter = functions.region('europe-west1').database.ref('posts/{PostID}/comments/{CommentID}').onWrite((change, context) => {
=> {})
Also, note that since v 1.0, onWrite() takes two parameters, see https://firebase.google.com/docs/functions/beta-v1-diff#realtime-database.
So, in addition to the change proposed above, please double-check that you have a recent version of the Firebase SDK for Cloud Functions.

objects are not initialized in another .js file?

[SOLVED - see at the end please]
[EDITED]
I have the following source:
index.js:
functions = require('firebase-functions');
admin = require('firebase-admin');
globals = require('./globals');
.....
admin.initializeApp();
exports.gateway = functions.https.onCall((data, context) => {
.....
return globals.dumpItemName(1);
});
exports.POST_gateway = functions.https.onRequest((req, res) => {
.....
return globals.dumpItemName(1);
});
globals.dumpItemName(1);
globals.js:
....
ITEM_NAMES = new Map()
.set(1, 'item1')
.set(2, 'item2');
....
getMapValue = function(map, key) {
let value = map.get(key);
if (value === undefined) {
throw new Error('Missing value for key ' + key);
}
return value;
}
....
dumpItemName = function(key) {
console.log(getMapValue(ITEM_NAMES, key));
}
exports.dumpItemName = dumpItemName;
I'm facing the following problem:
If I run/debug it locally by F5 it executes fine and dumps the expected value.
If I try to deploy it by firebase deploy --only functions
and then call via postman an https.onRequest(...) or https.onCall(....) function which tries to do the same:
globals.dumpItemName(1);
it fails with 'Missing value for key 1'
So I guess when I run it locally with F5 it executes index.js like a program and loads required modules and also run the code in them - so the Map in globals.js is initialized.
But when I deploy it via firebase deploy --functions only it does not execute the code.
So what should I do in order to have Map initialized after deploy ?
[SOLVED - THE REASON FOR THE "PROBLEM"]
Extremely dummy of my side.
Obviously for some of the local tests
(I have by now 10 end-to-end test scenario functions) I've put in index.js the following piece of code:
//!!! JUST FOR TEST - REMOVE IT !!!
ITEM_NAMES = new Map()
.set(globals.ITEM_TEST, 'test');
//!!! JUST FOR TEST - REMOVE IT !!!
so this map was redeclared with just one value in it. and the value of globals.ITEM_TEST is 200.
and it was used in one temporary test scenario function.
And just bad coincidences:
obviously I was too tired and forgot about it.
my other local tests were commented and that's why the local run was successful - in this temporary scenario, only this value was used.
I've decided to deploy in this exact moment. And none of the values I used in my onCall() or onRequest() functions used globals.ITEM_TEST (in real and in dummy both)
So it is...
Sorry for bothering. There is no problem in fact.

Firebase function .onWrite not working?

Ok, I have looked at similar questions like Firebase function onWrite not being called and thought it was my fault getting the reference, but I have no idea what is happening here with my Firebase functions.
I am just trying to get a function to write to my database when a write has been made to database. I followed the firebase tutorial exactly:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
//https://firebase.google.com/docs/functions/database-events
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// const gl = require('getlocation');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
exports.enterLocation = functions.database.ref('/Users/{name}') //brackets is client param
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
// const original = event.data.val();
console.log('SKYLAR HERE:', event.params.name);
// You must return a Promise when performing asynchronous tasks inside a Functions such as
return firebase.database().ref('/Users/{name}').set({ location: 'test loc' });
});
The function is being run, yet in my logs I get a pretty unhelpful error that it is getting the {name} param, and data is definitely written to my database, however my SERVER code is not writing:
I get -
ReferenceError: firebase is not defined at
exports.enterLocation.functions.database.ref
Which makes no sense as it is defined. I just want to add an extra child under the user I create, like I do already with "password"
What am I doing wrong?
Two problems here. First, you haven't defined firebase anywhere in your code. I think you meant to use admin instead to use the Admin SDK.
Second, it looks like you're trying to do variable interpolation into a string to build the name of the ref. Your syntax is wrong here.
I imagine you're trying to say this instead in your last line of code:
return admin.database().ref(`/Users/${name}`).set({ location: 'test loc' });
Note the backticks on the string quotes. That JavaScript syntax lets you use ${exp} to insert the contents of some expression in the string.
It turns out you don't even need to use the admin SDK here. Since you're trying to write back to the same location that triggered the function, you can just use the ref that comes from the event object:
return event.data.adminRef.set({ location: 'test loc' });
instead of this:
return firebase.database().ref('/Users/{name}').set({ location: 'test loc' });
use this:
return admin.database().ref('/Users/{name}').set({ location: 'test loc' });

Categories

Resources