Admin SDK cannot set settings for Firestore - javascript

So, I've been getting this warning recently:
The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to add the
following code to your app before calling any other Cloud Firestore methods:
const firestore = new Firestore();
const settings = {/* your settings... */ timestampsInSnapshots: true};
firestore.settings(settings);
With this change, timestamps stored in Cloud Firestore will be read
back as Firebase Timestamp objects instead of as system Date objects.
So you will also need to update code expecting a Date to instead
expect a Timestamp. For example:
// Old:
const date = snapshot.get('created_at');
// New:
const timestamp = snapshot.get('created_at');
const date = timestamp.toDate();
Please audit all existing usages of Date when you enable the new
behavior. In a future release, the behavior will change to the new
behavior, so if you do not follow these steps, YOUR APP MAY BREAK.
I am trying to implement the suggested correction in the admin SDK in my Cloud Functions code, since most of what I am doing is through there.
I tried using admin.firestore().settings({ timestampsInSnapshots: true }) but got the following warning:
admin.firestore(...).settings is not a function
How do I solve it?

I had the same problem. I had to update firebase-functions and firebase-admin.
To upgrade, go to your CLI, then
ProjectDirectory > Functions > npm install firebase-functions#latest firebase-admin#latest --save
Then, at the top, before triggering functions:
const firestore = admin.firestore();
const settings = {timestampsInSnapshots: true};
firestore.settings(settings);

To prevent the "Firestore.settings() has already been called" error, change
db.settings(settings);
to
try{ db.settings(settings); }catch(e){}

i solved with:
const settings = { timestampsInSnapshots: true };
const db = admin.firestore();
db.settings(settings);
db.collection('any');

Try configuring the settings right after initializing the admin SDK that might help.

Related

How can I invoke a firebase storage function locally and manually?

I am fairly familiar with invoking firebase firestore functions manually for testing. I have done this in the past with the docs here. In short I make a wrappedOnWrite variable, assign it to my admin sdk.wrap(my firebase function), and then I invoke it with an object that represents the firestore collection before and after the call. Example:
const testEnv = functions({ projectId: **** }, '****');
let wrappedOnWrite:
| WrappedScheduledFunction
| WrappedFunction<Change<QueryDocumentSnapshot>>;
beforeAll(() => {
// wrap the firebase function, ready to be invoked
wrappedOnWrite = testEnv.wrap(moderateCompetition);
});
const changeDoc: Change<QueryDocumentSnapshot> = {
before: testEnv.firestore.makeDocumentSnapshot(dataBefore, path),
after: testEnv.firestore.makeDocumentSnapshot(dataAfter, path),
};
// call firebase function with the changes inputted to the function
await wrappedOnWrite(changeDoc);
This is great for testing my firestore collections with jest, however, I am never seen this done with firebase storage, and I haven't been able to find many useful resources either. I have a pretty basic firestore .onFinalize function for storage:
export const blurImages = functions.storage.object().onFinalize(async (object) => {}
Is there any way to manually test it like in the example I gave above? When I initially deployed the function, it ran recursively and charged my company a couple of dollars, and I need to be able to run it periodically so that recession doesn't happen on the live function. Thanks in advance :)

How do I deploy my firebase functions into a specific region?

const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
exports.sendChatMsgNotification = functions.region('europe-west1')
firestore
.document('chats/{idChat}/messages/{idMessage}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
I am trying to deploy my Firebase cloud function into europe-west1 region because the default is us-central1, but I am always getting errors.
Yes, I have already tried to follow the official documentation https://firebase.google.com/docs/functions/locations#android. I just have no idea how to apply it correctly. I have tried many times.
If someone can just show me how to apply the correct way using my code above, would be highly appreciated!
Before
exports.sendChatMsgNotification = functions.region('europe-west1')
firestore
After (working)
exports.sendChatMsgNotification = functions.region('europe-west1').
firestore
I was only missing a dot (.) before "firestore". Thanks #Jason Berryman for pointing it out!

How to update a document when a document is added to a specified collection in cloud firestore?

I have 2 collections in my firestore (global and local) and when I add a doc to local I need to update a field in the global doc by 1
Below is the code I have for the same. I am very new to this so I might have some syntactical error as well, please do highlight if you find any.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello world");
}); // For testing, even this is not being deployed
exports.updateGlobal = functions.firestore
.document("/local/{id}")
.onCreate((snapshot, context) => {
console.log(snapshot.data());
return admin
.firebase()
.doc("global/{id}")
.update({
total: admin.firestore.FieldValue.increment(1),
});
});
The Terminal says "function failed on loading user code"
Before this, it showed something along the lines of "admin is undefined" or "cannot access firestore of undefined" which I'm unable to replicate now.
This is a part of a react app which has normal firestore working through firebase npm module
Any other info needed regarding the issue I'll edit the question accordingly, thank you so much for the help.
In addition to loading the firebase-functions and firebase-admin modules, you need to initialize an admin app instance from which Cloud Firestore changes can be made, as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
//...
I see another problem in your CF. You need to use the context object to get the value of id.
exports.updateGlobal = functions.firestore
.document("/local/{id}")
.onCreate((snapshot, context) => {
const docId = context.params.id;
return admin
.firebase()
.doc("global/" + docId)
.update({
total: admin.firestore.FieldValue.increment(1),
});
});
You can also use template literals as follows:
return admin
.firebase()
.doc(`global/${docId}`)
//...

Failure to Deploy Cloud Function When Using Wildcard in Trigger

I'm trying to deploy a cloud function that will trigger whenever a document is added to a particular collection as below:
const admin = require("firebase-admin");
const functions = require("firebase-functions");
const Firestore = require("#google-cloud/firestore");
const firestore = new Firestore({ projectId: config.projectId });
admin.initializeApp(config);
exports.checkCapacity = functions.firestore.document("gran_canaria/las_palmas_1/miller_pendientes/{albnum}")
.onCreate(async (snapshot, context) => {});
However this throws the Deployment failure error:
Failed to configure trigger providers/cloud.firestore/eventTypes/document.create#firestore.googleapis.com (gcf.us-central1.checkCapacity)
The error clears if I remove the wildcard and change the reference to:
"gran_canaria/las_palmas_1/miller_pendientes/albnum"
I've attempted changing the method to onWrite(), deleting and re-deploying the function and checking the cloud status at https://status.cloud.google.com/ but can't find any solutions.
I have been able to deploy successfully a Cloud Function with a trigger on an onCreate event on my Cloud Firestore.
I have been successful by imply using the provided template in the Console UI when creating the Cloud with the following:
The index.js used is the sample provided by GCP when created the function, which simply prints to the logs which document triggered the change.
Looking at the documentation in Firestore, I see that you probably used the samples provided there, so maybe using the above settings will make it work for you.

Where should I initialize pg-promise

I just started to learn nodejs-postgres and found the pg-promise package.
I read the docs and examples but I don't understand where should I put the initialization code? I using Express and I have many routes.
I have to put whole initialization (including pg-monitor init) to every single file where I would like to query the db or I need to include and initalize/configure them only in the server.js?
If I initialized them only in the server.js what should I include other files where I need a db query?
In other words. Its not clear to me if pg-promise and pg-monitor configuration/initalization was a global or a local action?
It's also unclear if I need to create a db variable and end pgp for every single query?
var db = pgp(connection);
db.query(...).then(...).catch(...).finally(**pgp.end**);
You need to initialize the database connection only once. If it is to be shared between modules, then put it into its own module file, like this:
const initOptions = {
// initialization options;
};
const pgp = require('pg-promise')(initOptions);
const cn = 'postgres://username:password#host:port/database';
const db = pgp(cn);
module.exports = {
pgp, db
};
See supported Initialization Options.
UPDATE-1
And if you try creating more than one database object with the same connection details, the library will output a warning into the console:
WARNING: Creating a duplicate database object for the same connection.
at Object.<anonymous> (D:\NodeJS\tests\test2.js:14:6)
This points out that your database usage pattern is bad, i.e. you should share the database object, as shown above, not re-create it all over again. And since version 6.x it became critical, with each database object maintaining its own connection pool, so duplicating those will additionally result in poor connection usage.
Also, it is not necessary to export pgp - initialized library instance. Instead, you can just do:
module.exports = db;
And if in some module you need to use the library's root, you can access it via property $config:
const db = require('../db'); // your db module
const pgp = db.$config.pgp; // the library's root after initialization
UPDATE-2
Some developers have been reporting (issue #175) that certain frameworks, like NextJS manage to load modules in such a way that breaks the singleton pattern, which results in the database module loaded more than once, and produce the duplicate database warning, even though from NodeJS point of view it should just work.
Below is a work-around for such integration issues, by forcing the singleton into the global scope, using Symbol. Let's create a reusable helper for creating singletons...
// generic singleton creator:
export function createSingleton<T>(name: string, create: () => T): T {
const s = Symbol.for(name);
let scope = (global as any)[s];
if (!scope) {
scope = {...create()};
(global as any)[s] = scope;
}
return scope;
}
Using the helper above, you can modify your TypeScript database file into this:
import * as pgLib from 'pg-promise';
const pgp = pgLib(/* initialization options */);
interface IDatabaseScope {
db: pgLib.IDatabase<any>;
pgp: pgLib.IMain;
}
export function getDB(): IDatabaseScope {
return createSingleton<IDatabaseScope>('my-app-db-space', () => {
return {
db: pgp('my-connect-string'),
pgp
};
});
}
Then, in the beginning of any file that uses the database you can do this:
import {getDB} from './db';
const {db, pgp} = getDB();
This will ensure a persistent singleton pattern.
A "connection" in pgp is actually an auto-managed pool of multiple connections. Each time you make a request, a connection will be grabbed from the pool, opened up, used, then closed and returned to the pool. That's a big part of why vitaly-t makes such a big deal about only creating one instance of pgp for your whole app. The only reason to end your connection is if you are definitely done using the database, i.e. you are gracefully shutting down your app.

Categories

Resources