Firebase CLI not deploying any Cloud Functions - node.js

I have an Angular project where I am trying to deploy a single firebase function.
This is what my functions directory looks like:
When I deploy these function with the command firebase deploy --only functions the output looks normal and no errors:
PS C:\Users\project-directory> firebase deploy --only functions
=== Deploying to 'firebase-project-name'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run build
> build
> tsc
+ functions: Finished running predeploy script.
i functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i functions: ensuring required API cloudbuild.googleapis.com is enabled...
+ functions: required API cloudfunctions.googleapis.com is enabled
+ functions: required API cloudbuild.googleapis.com is enabled
i functions: cleaning up build files...
+ Deploy complete!
Project Console: https://console.firebase.google.com/project/project-name/overview
src/user/index.ts file with the function I am trying to deploy:
import functions = require('firebase-functions');
import admin = require('firebase-admin');
// import * as functions from "firebase-functions";
// import * as admin from 'firebase-admin';
const FieldValue = require('firebase-admin').firestore.FieldValue;
const db = admin.firestore();
/**
* Add user to firestore
*/
export const createProfile = async (userRecord: any) => {
const uid = userRecord.uid;
const admin = false;
const email = userRecord.email;
const photoURL = userRecord.photoUrl || 'enter shortened url for default image';
const name = userRecord.displayName || 'New User';
const spouse = userRecord.spouse || 'TBA';
const forumUserName = userRecord.forumUserName || 'New Username set by admin';
const address = userRecord.address || 'TBA';
const suburb = userRecord.suburb || 'TBA';
const state = userRecord.state || 'QLD';
const postCode = userRecord.postCode || '2000';
const homePhone = userRecord.homePhone || '02 1234 5678';
const mobilePhone = userRecord.mobilePhone || '0400 123 456';
const memNum = userRecord.memNum || 123;
const timestamp = FieldValue.serverTimestamp();
const memType = userRecord.memType || 'Nominated';
const memStatus = userRecord.memStatus || `Pending`;
const isStateCoord = userRecord.isStateCoord || false;
const stateCoordState = userRecord.stateCoordState || 'QLD';
//const newUserRef = db.doc(`users/${uid}`)
// Convert any date to timestamp for consistency
try {
return await db
.collection(`users`)
.doc(userRecord.uid)
.set({
uid: uid,
email: email,
photoURL: photoURL,
fullName: name,
mDOB: timestamp,
spouse: spouse,
sDOB: timestamp,
forumUserName: forumUserName,
address: address,
suburb: suburb,
state: state,
postCode: postCode,
homePhone: homePhone,
mobilePhone: mobilePhone,
memNum: memNum,
memType: memType,
memStatus: memStatus,
memDueDate: timestamp,
lastLoginDate: timestamp,
joined: timestamp,
updated: timestamp,
admin: admin,
isAdmin: admin,
isStateCoord: isStateCoord,
stateCoordState: stateCoordState,
});
} catch (message) {
return console.error(message);
}
};
exports.authOnCreate = functions.auth.user().onCreate(createProfile);
src/index.ts file imports the above file:
import * as user from './user';
export const createProfile = user.createProfile
The issue is that I am not seeing the function appear in the Firebase console.
What am I overlooking?

You have two different export syntaxes in src/user/index.ts:
export const createProfile = async (userRecord: any) => { /* ... */ }
exports.authOnCreate = functions.auth.user().onCreate(createProfile);
Use one, or the other, not both. They are incompatible with each other.
export const createProfile = async (userRecord: any) => { /* ... */ }
export const authOnCreate = functions.auth.user().onCreate(createProfile);
Then in your main src/index.ts, import the Cloud Function export, not the plain function:
import { authOnCreate } from './user'; // import specific parts for the best performance when transpiled
export const createProfile = authOnCreate;

In the original code I posted for you, createProfile was a const not an export.
Out of the box, should work just fine. The only difference I see is you've dropped the code into a user sub directory.
Again no issue and probably ok for readability.
Might be poor practice on my part, but ALL of my functions are in one file. Even when extensions are installed, they go into the same index.js(ts) file. Then if I modify a function I use firebase deploy --only functions:functionName

Related

How to read RTDB randomly generated node and it's value?

Below is my typescript code and its already connected to telegram and managed to sent a message.
Currently I'm trying to tinker with it even more by reading the randomly generated node Arduino and MQ7 created and sending it as a message on telegram.
import * as functions from "firebase-functions";
import * as express from "express";
import * as cors from "cors";
import * as admin from "firebase-admin";
admin.initializeApp();
const bot = express();
bot.use(cors( {origin: true}));
bot.post("/", async function(req, res) {
const telegramText = req.body;
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name;
if (telegramText) {
const chat_id = req.body.message.chat.id;
const first_name = req.body.message.from.first_name;
const receivedMessage = req.body.message.text;
// Define your RTDB Reference
const rtdbReference = admin.database().ref("Sensor MQ7");
const mq7ref = rtdbReference.child("-NHi7dBPMlVi6hXrnI03");
const valref = mq7ref.child("MQ7");
// Fetch the data
const snap = await valref.get();
const snapValue = snap.val();
// Inject snapvalue in the response
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `${first_name}\n${receivedMessage}\n${snapValue}`,
});
}
return res.status(200).send({status: "An error occured"});
});
export const router = functions.https.onRequest(bot);
The typescript code works I figured out how to read entries from the database and write it into telegram as well as learning a little bit about the reference and functions. Right now I'm trying to figure out how to output the value whenever a new node is created. The new node are randomly generated # Arduino and mq7 gas sensor. Basically whenever the sensor picks up dangerous amount of carbon monoxide, it'll send the input to firebase, and a new node is created with a new value. I've classified the Parent = Sensor MQ7, Middle child = "randomly generated node", Last child = MQ7. Any help, advice, or recommendation is greatly appreciated. Please explain like I'm 5 because I just started doing all of it this week and I'm really really new to cloud functions. Thank you!
RTDB
The typescript code (for sending to Telegram) works and right now I'm
trying to figure out how to read entries from the database and write
it into telegram
In a Cloud Function you need to use the Node.js Admin SDK to interact with the Firebase services.
Here is how to read the data at a specific Reference in the Realtime Database:
import * as functions from "firebase-functions";
import * as express from "express";
import * as cors from "cors";
import * as admin from 'firebase-admin'; <== NEW
admin.initializeApp(); <== NEW
const bot = express();
bot.use(cors( {origin: true}));
bot.post("/", async function(req, res) {
const telegramText = req.body;
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name;
if (telegramText) {
const chat_id = req.body.message.chat.id;
const first_name = req.body.message.from.first_name;
const receivedMessage = req.body.message.text;
//Define your RTDB Reference
const rtdbReference = admin.admin.database().ref('foo/bar');
// Fetch the date
const snap = await rtdbReference.get();
const snapValue = snap.val();
// Do whatever you need with snapValue to inject it in your response...
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Hello ${first_name}, \n You sent us message: ${receivedMessage}`,
});
}
return res.status(200).send({status: "An error occured"});
});
export const router = functions.https.onRequest(bot);

ReferenceError: FirebaseFirestore is not defined firebase functions

I'm trying to create a firebase function that triggers when object metadata on a cloud storage bucket changes, but when the function triggers I am getting an error:
ReferenceError: FirebaseFirestore is not defined
at addKapsulFromStorageObject
Here's the code:
import * as functions from "firebase-functions";
const { v4: uuidv4 } = require('uuid');
import admin = require('firebase-admin');
import { ObjectMetadata } from "firebase-functions/lib/providers/storage";
admin.initializeApp();
const database = admin.firestore();
database.settings({ ignoreUndefinedProperties: true })
const KAPSUL_COLLECTION_ID = 'kapsuls';
exports.onKapsulFileAdded = functions.storage.object().onFinalize(async (object) =>{
addKapsulFromStorageObject(object);
})
exports.onKapsulMetaDataUpdated = functions.storage.object().onMetadataUpdate(async (object) => {
addKapsulFromStorageObject(object);
});
async function addKapsulFromStorageObject(object: ObjectMetadata) {
const metaData = object.metadata;
if(metaData == null || metaData == undefined) return;
const kapsulId = uuidv4()
console.log("Adding Kapsul to firestore.")
await database.collection(KAPSUL_COLLECTION_ID).doc(kapsulId).set({
id: kapsulId,
coordinates: new FirebaseFirestore.GeoPoint(parseFloat( metaData['lat']), parseFloat(metaData['long'])),
first_name: metaData['first_name'],
last_name: metaData['last_name'],
date: FirebaseFirestore.Timestamp.fromDate(new Date(metaData['date'])),
is_online: JSON.parse(metaData['is_online']),
is_paid: JSON.parse(metaData['is_paid']),
always_unlockable: metaData['always_unlockable'],
title: metaData['title'],
unlock_distance: parseFloat(metaData['unlock_distance']),
video_url: object.mediaLink,
metaData: metaData['thumbnail_url']
});
}
I'm not really sure what's going on.
FirebaseFirestore is not defined but you are using it at 2 places:
date: FirebaseFirestore.Timestamp.fromDate(new Date(metaData['date'])),
// and
coordinates: new FirebaseFirestore.GeoPoint(parseFloat( metaData['lat']), parseFloat(metaData['long'])),
Try using admin.firestore instead:
coordinates: new admin.firestore.GeoPoint(parseFloat( metaData['lat']), parseFloat(metaData['long'])),
^^^^^^^^^^^^^^^
date: admin.firestore.Timestamp.fromDate(new Date(metaData['date'])),
If you were following any tutorial, then they may have declared that somewhere above like:
const FirebaseFirestore = admin.firestore

Flakey tests when testing firebase functions using Jest

I'm testing Firebase functions using Jest and the emulator, though the tests are flakey presumably from a race condition. By flakey, I mean sometimes they pass and sometimes they don't, even on the same machine.
Tests and functions are written in TypeScript, then transpiled with babel.
Example test/function
Note: This is an example of just one of the flakey tests. Many other tests are flakey. A solution is preferably one that doesn't just solve this one case, but rather the general issue.
The test
import { onProfilesWrite } from '../src/profiles/on-write'
import { initializeAdminApp } from '#firebase/rules-unit-testing'
const admin = initializeAdminApp({ projectId: 'projectId' }).firestore()
const wrappedFunction = testEnvironment.wrap(onProfilesWrite)
const profilePath = `profiles/${uid}`
const customerProfile = {
roles: ['customer'],
slug: 'slug',
image: 'image.png',
fullName: 'John Smith',
}
const publisherRoles = ['customer', 'publisher']
const publisherProfile = {
...customerProfile,
roles: publisherRoles,
}
const createChange = async (
before: Record<string, unknown> | undefined,
changes: Record<string, unknown>
) => {
const publisherStatsRef = admin.doc(profilePath)
if (before) await publisherStatsRef.set(before)
const beforeSnapshot = await publisherStatsRef.get()
await publisherStatsRef.set(changes, { merge: true })
const afterSnapshot = await publisherStatsRef.get()
return testEnvironment.makeChange(beforeSnapshot, afterSnapshot)
}
test('If user profile is created as a publisher, publisherDetails is created', async () => {
const change = await createChange(undefined, publisherProfile)
await wrappedFunction(change)
const snapshot = await admin.doc(`profileDetails/${uid}`).get()
const data = snapshot.data()
expect(data).toBeTruthy()
expect(data?.id).toBeTruthy()
expect(data?.slug).toBe(publisherProfile.slug)
expect(data?.profileImage).toBe(publisherProfile.image)
expect(data?.publisherName).toBe(publisherProfile.fullName)
expect(data?.music).toMatchObject([])
})
Run the test
firebase emulators:exec \"jest functions/__tests__ --detectOpenHandles\" --only firestore
Output
If user profile is created as a publisher, publisherDetails is created
expect(received).toBeTruthy()
Received: undefined
46 | const snapshot = await admin.doc(`profileDetails/${uid}`).get()
47 | const data = snapshot.data()
> 48 | expect(data).toBeTruthy()
| ^
49 | expect(data?.id).toBeTruthy()
50 | expect(data?.slug).toBe(publisherProfile.slug)
51 | expect(data?.profileImage).toBe(publisherProfile.image)
The function
import * as functions from 'firebase-functions'
// initializes the admin app, then exports admin.firestore
import { firestore } from '../admin'
const database = firestore()
const createPublisherId = async (): Promise<string> => {
let id = ''
const MAX_NUMBER = 1000000
while (id === '') {
const temporaryId = String(Math.ceil(Math.random() * MAX_NUMBER))
const snapshot = await firestore()
.collection('publisherDetails')
.where('sku', '==', temporaryId)
.limit(1)
.get()
if (snapshot.empty) id = temporaryId
}
return id
}
export const createPublisherDetails = async (
newData: firestore.DocumentData,
uid: string
): Promise<void> => {
const id = await createPublisherId()
await database.doc(`publisherDetails/${uid}`).set(
{
id,
slug: newData.slug,
publisherName: newData.fullName,
profileImage: newData.image,
music: [],
},
{ merge: true }
)
}
export const onProfilesWrite = functions.firestore.document('profiles/{uid}').onWrite(
async (change): Promise<void> => {
const { id: uid } = change.after
const oldData = change.before.data()
const newData = change.after.data()
if (
newData?.roles?.includes('publisher') &&
(typeof oldData === 'undefined' || !oldData.roles?.includes('publisher'))
)
await createPublisherDetails(newData, uid)
}
)
Debug steps
All promises are awaited in the cloud functions (as affirmed by an ESLint rule #typescript-eslint/no-floating-promises)
Also converted the tests to Mocha (as suggested by the Firebase docs), same errors
Converting async/await in tests to promise.then() syntax
Metadata
OS: macOS 11.2, Ubuntu 18.04
Jest: 26.6.3
Firebase: 8.2.6
Firebase tools: 9.3.0
As comments roll in, with either questions or suggestions, I'll continue to update this post.
Change your test portion to as follows :
test('If user profile is created as a publisher, publisherDetails is created', async () => {
const change = await createChange(undefined, publisherProfile)
await wrappedFunction(change)
const documentObject = await admin.doc(`profileDetails/${uid}`)
const snapshot = await documentObject.get()
const data = snapshot.data()
expect(data).toBeTruthy()
expect(data?.id).toBeTruthy()
expect(data?.slug).toBe(publisherProfile.slug)
expect(data?.profileImage).toBe(publisherProfile.image)
expect(data?.publisherName).toBe(publisherProfile.fullName)
expect(data?.music).toMatchObject([])
})
Reason being that in your test region, your use of await is a bit incorrect (function chaining on an object that is being waited for is a big no-no in the same calling line)

Error upon Cloud Function for Firebase deployment

I've been trying to deploy a Cloud Function to my Firebase project.
It's my first time doing so, also my first time programming with JavaScript.
Here's my code in Node.JS:
'use strict'
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
const firebaseTriggers = functions.region('europe-west1').firestore;
const db = admin.firestore();
exports.postNotification = firebaseTriggers
.document('/post notifications/{notificatioId}').onWrite((snap, context) => {
const notifcationRecieverId = snap.data().mReciever;
const payload = {
data: {
notification_type: 'POST',
title: snap.data().mTitle,
body: snap.data().mDescription,
sender_id: snap.data().mSender,
reciever_id: snap.data().mReciever,
notification_id: context.params.notificatioId
}
};
return db.collection('dog owners')
.document(notifcationRecieverId)
.get()
.then(recieverDoc => {
console.log('Retrieving FCM tokens');
const tokens = recieverDoc.data().mTokens;
console.log('Sending notification payload');
return admin.message().sendToDevice(tokens, payload);
});
});
Upong deployment, I'm getting the following error:
Can someone help me understand why?
Firstly you have got space in your colleciton name. This is bad convetion.
post notifications => postNotifications

Upload stripeCharge function to Firebase cloud function not working

I tried deploying this function to Firebase
Also, Im using Cloud Firestore as the database
const stripe = require("stripe")("STRIPE_API_KEY");
exports.stripeCharge = functions.firestore
.document("/payments/{userId}/{paymentId}")
.onWrite(event => {
const payment = event.data.val();
const userId = event.params.userId;
const paymentId = event.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) return;
return admin
.firestore()
.doc(`/users/${userId}`)
.once("value")
.then(snapshot => {
return snapshot.val();
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = "eur";
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
admin
.firestore()
.doc(`/payments/${userId}/${paymentId}/charge`)
.set(charge),
{ merge: true };
});
});
I followed this tutorial
Process Stripe Payments with Firebase Cloud Functions - Part 2
YouTube · 14 000+ views · 2017/07/11 · by Fireship - AngularFirebase
When I run firebase deploy --only functions
This appears in the terminal
! functions: failed to create function stripeCharge
HTTP Error: 400, The request has errors
Functions deploy had errors with the following functions:
stripeCharge
To try redeploying those functions, run:
firebase deploy --only functions:stripeCharge
To continue deploying other features (such as database), run:
firebase deploy --except functions
Error: Functions did not deploy properly.
And I get this error in the Firebase log
Firebase Log error
Anybody have a clue with what could be wrong?
At least one of your problem is that you use the syntax of the Firebase SDK for Cloud Functions for version < 1.0.0 and your package.json shows that you use a version that is >= 2.2.0.
You should use the new syntax:
exports.stripeCharge = functions.firestore
.document("/payments/{userId}/{paymentId}")
.onWrite((change, context) => {
const payment = change.after.data();
const userId = event.params.userId;
const paymentId = event.params.paymentId;
...
});

Resources